[LeetCode 1477] Find Two Non-overlapping Sub-arrays Each With Target Sum

Given an array of integers arr and an integer target.

You have to find two non-overlapping sub-arrays of arr each with sum equal target. There can be multiple answers so you have to find an answer where the sum of the lengths of the two sub-arrays is minimum.

Return the minimum sum of the lengths of the two required sub-arrays, or return -1 if you cannot find such two sub-arrays.

 

Example 1:

Input: arr = [3,2,2,4,3], target = 3
Output: 2
Explanation: Only two sub-arrays have sum = 3 ([3] and [3]). The sum of their lengths is 2.

Example 2:

Input: arr = [7,3,4,7], target = 7
Output: 2
Explanation: Although we have three non-overlapping sub-arrays of sum = 7 ([7], [3,4] and [7]), but we will choose the first and third sub-arrays as the sum of their lengths is 2.

Example 3:

Input: arr = [4,3,2,6,2,3,4], target = 6
Output: -1
Explanation: We have only one sub-array of sum = 6.

Example 4:

Input: arr = [5,5,4,4,5], target = 3
Output: -1
Explanation: We cannot find a sub-array of sum = 3.

Example 5:

Input: arr = [3,1,1,1,5,1,2,1], target = 3
Output: 3
Explanation: Note that sub-arrays [1,2] and [2,1] cannot be an answer because they overlap.

 

Constraints:

  • 1 <= arr.length <= 10^5
  • 1 <= arr[i] <= 1000
  • 1 <= target <= 10^8

Given the constraints, we need to come up with a O(N * logN) or O(N) solution. Because this problem deals with subarray, sorting is out of the picture. So we should work toward a linear solution.

The tricky part of this problem is that we must only use non-overlapping subarrays toward the answer. How should we ensure this? Well, if we fix an index i, then we can try to find one shortest subarray of sum target whose end index is <= i, then find another shortest subarray of sum target whose start index is >= i + 1. This way the two subarrays never overlap. This looks promising. But there is one more issue we need to address: how do we ensure that we are getting the best answer? We'll always pick the shortest subarray so far in arr[0, i]. We'll also try to pick the shortest subarray in arr[i + 1, N - 1] as well. Since we'll be scanning the input from left to right, so picking the best in arr[0, i] is straightforward: keep a running sum S and check if there is prefix sum S - target, update the shortest so far if needed. How do we pick the shortest from arr[i + 1, N - 1]? Similarly with the prior prefix, we try to find if there is prefix sum S + target. If this subarray exists, it starts at >= i + 1. Given that arr only has positive integers, we know the prefix sum will be strictly increasing. This means there will only be at most one such subarray in arr[i + 1, N - 1]. Now the solution becomes clear, we just need to do the above for all indices.

Algorithm

1. create a prefix sum to index mapping.

2. scan the input array and keep a running sum. For each index i, get the shortest subarray of sum target in index [0, i]. Then get the subarray of sum target in [i + 1, N - 1] if exists. Return the best result after going through the entire input array.

class Solution {
    public int minSumOfLengths(int[] arr, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, -1);
        int sum = 0;
        for(int i = 0; i < arr.length; i++) {
            sum += arr[i];
            map.put(sum, i);
        }
        int ans = Integer.MAX_VALUE;
        sum = 0;
        
        int leftBest = Integer.MAX_VALUE;
        for(int i = 0; i < arr.length; i++) {
            sum += arr[i];
            if(map.containsKey(sum - target)) {
                leftBest = Math.min(leftBest, i - map.get(sum - target));
            }
            if(leftBest < Integer.MAX_VALUE && map.containsKey(sum + target)) {
                ans = Math.min(ans, leftBest + map.get(sum + target) - i);
            }
        }
        return ans < Integer.MAX_VALUE ? ans : -1;
    }
}

The first solution we come up with already has the best conceviable runtime O(N). However, it requires two passes. Can you solve it with only one pass? The reason we need two passes is because for an index i, we try to "look ahead" to find a possible subarray to its right. We wouldn't be able to do this unless we have a pre-processing pass to create a prefix sum to index mapping. If we want to use only one pass, then we should not "look ahead". Instead we "look back" even further. For index i, if we find a matching subarray [j, i], we try to find the shortest matching subarray in [0, j - 1]. This'll give us the following one pass algorithm.

1. Create an array called best[] and init all its values to Integer.MAX_VALUE. best[i] stores the length of the shortest subarray with sum target that ends at index <= i.

2. Use sliding window + two pointers to find each matching subarray that ends at each index. There should be at most one such subarray for each different index since arr[i] > 0 for all i.

3. Call this subarray[j, i]. Then we check the shortest subarray in [0, j - 1] if applicable. If there is one, update the current answer. If there is a matching subrray ends at the current index, we need to update the best length we've seen so far. Set best[i] to best so far.

One pitfall here is that the updating best[i] logic must happen at each index, no matter if there is a matching sum or not. Remember, best[i] stores the shortest subarray whose index ends at <= i. Even if index i has no matching subarray, there might be smaller indices that have matching subarray and this must be reflected in best[i]!

class Solution {
    public int minSumOfLengths(int[] arr, int target) {
        int n = arr.length, ans = Integer.MAX_VALUE, bestSoFar = Integer.MAX_VALUE;
        int[] best = new int[n];
        Arrays.fill(best, Integer.MAX_VALUE);
        
        int start = 0, sum = 0;
        for(int i = 0; i < n; i++) {
            sum += arr[i];
            while(sum > target) {
                sum -= arr[start];
                start++;
            }
            if(sum == target) {
                if(start > 0 && best[start - 1] < Integer.MAX_VALUE) {
                    ans = Math.min(ans, best[start - 1] + i - start + 1);
                }
                bestSoFar = Math.min(bestSoFar, i - start + 1);
            }
            best[i] = bestSoFar;
        }
        return ans < Integer.MAX_VALUE ? ans : -1;
    }
}

Related Problems

[LeetCode 560] Subarray Sum Equals K

原文地址:https://www.cnblogs.com/lz87/p/13155351.html