0

The problem is fairly simple. Given an input of N (3 <= N <= 3000) integers, find the largest sum of a 3-integer arithmetic series in the sequence. Eg. (15, 8, 1) is a larger arithmetic series than (12, 7, 2) because 15 + 8 + 1 > 12 + 7 + 2. The integers apart of the largest arithmetic series do NOT have to be adjacent, and the order they appear in is irrelevant.

An example input would be:

6
1 6 11 2 7 12

where the first number is N (in this case, 6) and the second line is the sequence N integers long.

And the output would be the largest sum of any 3-integer arithmetic series. Like so:

21

because 2, 7 and 12 has the largest sum of any 3-integer arithmetic series in the sequence, and 2 + 7 + 12 = 21. It is also guaranteed that a 3-integer arithmetic series exists in the sequence.

EDIT: The numbers that make up the sum (output) have to be an arithmetic series (constant difference) that is 3 integers long. In the case of the sample input, (1 6 11) is a possible arithmetic series, but it is smaller than (2 7 12) because 2 + 7 + 12 > 1 + 6 + 11. Thus 21 would be outputted because it is larger.

Here is my attempt at solving this question in C++:

#include <bits/stdc++.h>
using namespace std;
vector<int> results;
vector<int> middle;
vector<int> diff;
int main(){
    int n;
    cin >> n;
    int sizes[n];
    for (int i = 0; i < n; i++){
        int size;
        cin >> size;
        sizes[i] = size;
    }
    sort(sizes, sizes + n, greater<int>());
    for (int i = 0; i < n; i++){
        for (int j = i+1; j < n; j++){
            int difference = sizes[i] - sizes[j];
            diff.insert(diff.end(), difference);
            middle.insert(middle.end(), sizes[j]);
        }
    }
    for (size_t i = 0; i < middle.size(); i++){
        int difference = middle[i] - diff[i];
        for (int j = 0; j < n; j++){
            if (sizes[j] == difference) results.insert(results.end(), middle[i]);
        }
    }
    int max = 0;
    for (size_t i = 0; i < results.size(); i++) {
        if (results[i] > max) max = results[i];
    }
    int answer = max * 3;
    cout << answer;
    return 0;
}

My approach was to record what the middle number and the difference was using separate vectors, then loop through the vectors and search if the middle number minus the difference is in the array, where it gets added to another vector. Then the largest middle number is found and multiplied by 3 to get the sum. This approach made my algorithm go from O(n^3) to roughly O(n^2). However, the algorithm doesn't always produce the correct output (and I can't think of a test case where this doesn't work) every time, and since I'm using separate vectors, I get a std::bad_alloc error for large N values because I am probably using too much memory. The time limit in this question is 1.4 sec per test case, and memory limit is 64 MB.

Since N can only be max 3000, O(n^2) is sufficient. So what is an optimal O(n^2) solution (or better) to this problem?

Star Man
  • 171
  • 1
  • 2
  • 11
  • So it means that the 3 numbers you need to pick needs to be adjacent? – silverfox Jun 07 '21 at 16:11
  • 1
    You should be able to do this in `O(N)` time. Take elements 0, 1, and 2, get the sum, store it as the max. Then get elements 1, 2, and 3, get the sum, compare against max. If it is higher set max to that new value and record the starting position. Repeat until you get to the last three elements. – NathanOliver Jun 07 '21 at 16:12
  • 1
    Then isn't the problem simply choosing the 3 largest numbers, if they do not have to be adjacent? – Logicrat Jun 07 '21 at 16:14
  • 1
    @StarMan Your requirements on contradicting themselves. If the elements don't have to be adjacent, then why isn't `11 7 12` the maximum sum? – NathanOliver Jun 07 '21 at 16:14
  • @NathanOliver 11 7 12 is not the max sum. 2 7 12 is because they have a difference of 5. 2 + 7 + 12 = 21 – Star Man Jun 07 '21 at 16:15
  • @StarMan In short, can you describe what you mean by `integer arithmetic series`? We're getting really confused here. Because if the series can be non-adjacent, `11 7 12` would made the cut. – silverfox Jun 07 '21 at 16:16
  • 2
    An [arithmetic progression](https://en.wikipedia.org/wiki/Arithmetic_progression) or arithmetic series is a well-established mathematical concept. – Nathan Pierson Jun 07 '21 at 16:17
  • Aha, now I get it. The elements in the result set all need to have the same distance between them. – NathanOliver Jun 07 '21 at 16:18
  • @NathanPierson Ah, I get it now. – silverfox Jun 07 '21 at 16:19
  • A simpler approach would be to go over all pairs in the array, and assume that we have the first and second value of an arithmetic sequence, as each pair is trivially an arithmetic sequence. Is there a way we can find the third value/candidate quickly? – wLui155 Jun 07 '21 at 16:22
  • @wLui155 wouldn't that be index(second)+index(second)-index(first)? – Surt Jun 07 '21 at 16:26
  • @NathanOliver I'm sorry, I thought the term arithmetic series would have made it clear. My apologies. – Star Man Jun 07 '21 at 16:31
  • @Surt Close, but there's no need for indexing. For example, if the first two terms are `[a, b]` and we want to find `c` that is part of the progression, then it follows `b = a + (b - a)`, so `c = b + (b - a)`. So, if we have a good way to find `c` quickly the solution meets the problem constraints. – wLui155 Jun 07 '21 at 16:36
  • Can you reorder the elements? For instance, if the input is `[3, 9, 6, 1, 2, 3]` would it be valid to return `3 + 6 + 9 = 18`, or would it have to be `1 + 2 + 3 = 6`? – Nathan Pierson Jun 07 '21 at 16:38
  • @NathanPierson Yes. 18 would be the largest. Order does not matter. – Star Man Jun 07 '21 at 16:39

2 Answers2

2

So, a simple solution for this problem is to put all elements into an std::map to count their frequencies, then iterate over the first and second element in the arithmetic progression, then search the map for the third.

Iterating takes O(n^2) and map lookups and find() generally takes O(logn).

include <iostream>
#include <map>
using namespace std;

const int maxn = 3000;
int a[maxn+1];
map<int, int> freq;

int main()
{
    int n; cin >> n;
    for (int i = 1; i <= n; i++) {cin >> a[i]; freq[a[i]]++;} // inserting frequencies

    int maxi = INT_MIN;
    for (int i = 1; i <= n-1; i++)
    {
        for (int j = i+1; j <= n; j++)
        {
            int first = a[i], sec = a[j]; if (first > sec) {swap(first, sec);} //ensure that first is smaller than sec
            int gap = sec - first; //calculating difference
            if (gap == 0 && freq[first] >= 3) {maxi = max(maxi, first*3); } //if first = sec then calculate immidiately
            else
            {
                int third1 = first - gap; //else there're two options for the third element
                if (freq.find(third1) != freq.end() && gap != 0) {maxi = max(maxi, first + sec + third1); } //finding third element
            }
        }
    }
    cout << maxi;
}

Output : 21

Another test :

6
3 4 5 7 7 7

Output : 21

Another test :

5
10 10 9 8 7

Output : 27

You can try std::unordered_map to try and reduce the complexity even more.

Also see Why is "using namespace std;" considered bad practice?

silverfox
  • 1,568
  • 10
  • 27
  • You could simplify this a bit by not checking for both `(third, first, second)` and `(first, second, third)` and only considering the latter case. If both options are present the latter will always be larger. If only the former is present, you'll find it when evaluating the pair `(third, first)`. – Nathan Pierson Jun 07 '21 at 16:53
  • I think this is very close. But this does not work if the sequence is [10,10,9,8,7]. It outputs 30 where the output should be 27. Because the largest arithmetic series in this sequence is 8+9+10 (constant difference of 1). – Star Man Jun 07 '21 at 16:54
  • @StarMan Sorry, I missed a condition :-) I fixed it again, is this what you need? – silverfox Jun 07 '21 at 16:59
0

The sum of a 3-element arithmetic progression is 3-times the middle element, so I would search around a middle element, and would start the search from the "upper" end of the "array" (and have it sorted). This way the first hit is the largest one. Also, the actual array would be a frequency-map, so elements are unique, but still track if any element has 3 copies, because that can become a hit (progression by 0).
I think it may be better to create the frequency-map first, and sort it later, simply because it may result in sorting fewer elements - though they are going to be pairs of value and count in this case.

function max3(arr){
  let stats=new Map();
  for(let value of arr)
    stats.set(value,(stats.get(value) || 0)+1);
  let array=Array.from(stats);       // array of [value,count] arrays
  array.sort((x,y)=>y[0]-x[0]);      // sort by value, descending
  for(let i=0;i<array.length;i++){
    let [value,count]=array[i];
    if(count>=3)
      return 3*value;
    for(let j=0;j<i;j++)
      if(stats.has(2*value-array[j][0]))
        return 3*value;
  }
}

console.log(max3([1,6,11,2,7,12]));  // original example
console.log(max3([3,4,5,7,7,7]));    // an example of 3 identical elements
console.log(max3([10,10,9,8,7]));    // an example from another answer
console.log(max3([1,2,11,6,7,12]));  // example with non-adjacent elements
console.log(max3([3,7,1,1,1]));      // check for finding lowest possible triplet too
tevemadar
  • 12,389
  • 3
  • 21
  • 49