It can be done in O(n) as well:
First, create an auxilary array that sums each prefix of the array:
sums[i] = arr[0] + arr[1] + ... + arr[i]
The above can be computed in O(n) time.
Next, create a hash map Map<Integer,List<Integer>>, where the key is representing a prefix sum, and the value is a list of indices with this prefix sum. Pseudo code:
Map<Integer,List<Integer>> map = new HashMap<>();
for (int i = 0; i < sums.length; i++) {
List<Integer> l = map.get(sums[i]);
if (l == null) {
l = new ArrayList<>();
map.put(sums[i],l);
}
l.add(i);
}
Now, iterate the sums array, and for each element x, check if the map contains a key k such that x-k == S.
This is done by checking if it has a key k = S-x, which is O(1) in a hash map.
If there is such a key, then get the last index in the values list, which is also done in O(1), and take it as a candidate match.
Pseudo code:
int currentMaxLength = Integer.MIN_VALUE;
for (int i = 0; i < sums.length; i++) {
int k = S-sums[i];
List<Integer> l = map.get(k);
if (l == null) continue;
int width = Math.abs(l.getLast() - i);
if (width > currentMaxLength) currentMaxLength = width;
}
return currentMaxLength;
The idea is, if you have multiple matches for some x1,x2 such that x2-x1 = S, and where x1,x2 are prefix sums, the candidates for longest subarray are the first place where x1 appears, and the last place where x2 appears.
For x1, this is the element which i refers to in the main loop, and it is always regarded as a candidate.
For x2, you will always check the last occurance of x2, and thus it's correct.
Quicknote: The actual code also needs to regard sums[-1] = 0.
Java code:
public static int solution(int[] arr, int S) {
int[] sums = new int[arr.length+1];
int sum = 0;
//generate the sums array:
sums[0] = 0;
for (int i = 0; i < arr.length; i++) sums[i+1] = sum = sum+arr[i];
//generate map:
Map<Integer,List<Integer>> map = new HashMap<>();
for (int i = 0; i < sums.length; i++) {
List<Integer> l = map.get(sums[i]);
if (l == null) {
l = new ArrayList<>();
map.put(sums[i],l);
}
l.add(i);
}
//find longest:
int currentMaxLength = Integer.MIN_VALUE;
for (int i = 0; i < sums.length; i++) {
int k = S - sums[i];
List<Integer> l = map.get(k);
if (l == null) continue;
int width = Math.abs(l.get(l.size()-1) - i);
if (width > currentMaxLength) currentMaxLength = width;
}
return currentMaxLength;
}
public static void main(String[] args) {
System.out.println(solution(new int[] { 1, 0, -1, 1, 1, -1, -1 }, 2));
}