Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1477. Find Two Non-overlapping Sub-arrays Each With Target Sum - Leetcode Solution

Problem Description

Given an array of integers arr and an integer target, you are asked to find two non-overlapping sub-arrays of arr such that the sum of elements in each sub-array is exactly target. The two sub-arrays must not share any elements (they cannot overlap or touch). Your task is to return the minimum possible sum of the lengths of these two sub-arrays. If no such pair of sub-arrays exists, return -1.

  • Each sub-array must have a sum equal to target.
  • The sub-arrays must be non-overlapping (no shared indices).
  • Return the minimal combined length among all possible valid pairs.
  • If no valid pair exists, return -1.

Thought Process

At first glance, it seems natural to try every possible pair of sub-arrays and check if both sum to target and do not overlap. However, this brute-force approach would require checking all possible sub-arrays, leading to a very inefficient solution.

To optimize, we can focus on efficiently finding sub-arrays that sum to target and then, for each such sub-array, look for another non-overlapping sub-array. We need a way to quickly look up the shortest sub-array ending before or starting after a given index. This hints at using prefix sums and dynamic programming to store the shortest sub-array up to each point.

By maintaining information about the shortest valid sub-arrays as we scan the array, we can efficiently pair non-overlapping candidates and keep track of the minimal total length.

Solution Approach

Here is a step-by-step breakdown of the optimized algorithm:

  1. Prefix Sum and Hash Map:
    • Compute prefix sums as you iterate through the array.
    • Use a hash map to store the earliest index where a prefix sum occurs.
    • This allows you to find sub-arrays that sum to target in O(1) time per index.
  2. Track Shortest Sub-array Lengths:
    • Maintain an array best_left where best_left[i] is the length of the shortest sub-array ending at or before i with sum target.
    • Similarly, build best_right from the right side.
  3. Combine Non-Overlapping Sub-arrays:
    • For each possible split point, combine the shortest left sub-array with the shortest right sub-array that do not overlap.
    • Track the minimal total length found.
  4. Return the Result:
    • If a valid pair is found, return the minimal sum of lengths; otherwise, return -1.

By precomputing shortest sub-array lengths for each possible endpoint, we ensure that each combination is checked only once, leading to an efficient solution.

Example Walkthrough

Let's consider arr = [3,2,2,4,3] and target = 3.

  1. First, find all sub-arrays that sum to 3:
    • [3] at index 0 (length 1)
    • [2,1] doesn't exist; next, [2,2,4] is too big
    • [3] at index 4 (length 1)
  2. Build best_left:
    • At index 0: shortest is 1 (sub-array [3])
    • At index 1,2,3: no new sub-array, so carry over value 1
    • At index 4: new sub-array [3], so still 1
  3. Build best_right similarly from the right.
  4. For each split, combine left and right non-overlapping sub-arrays:
    • Left: [3] at index 0, Right: [3] at index 4 (non-overlapping)
    • Total length: 1 + 1 = 2
  5. The answer is 2.

Time and Space Complexity

Brute-force:

  • Time: O(N3) – For each pair of sub-arrays, check if they sum to target and do not overlap.
  • Space: O(1) (ignoring input and output).
Optimized Solution:
  • Time: O(N) – Each pass (left-to-right and right-to-left) takes linear time using prefix sums and hash maps.
  • Space: O(N) – For prefix sums, hash maps, and the best_left and best_right arrays.

The optimization comes from avoiding redundant checks and using efficient data structures for quick lookups.

Summary

This problem demonstrates how prefix sums and dynamic programming can be combined to efficiently solve a two-sub-array sum problem. By precomputing the shortest sub-array lengths for each endpoint and leveraging hash maps for O(1) sub-array sum lookups, we reduce the solution from cubic to linear time. The key insight is to decompose the problem into manageable parts and use auxiliary arrays to quickly pair non-overlapping sub-arrays, resulting in a clean and efficient solution.

Code Implementation

class Solution:
    def minSumOfLengths(self, arr, target):
        n = len(arr)
        best_left = [float('inf')] * n
        prefix_sum = {0: -1}
        curr_sum = 0
        min_len = float('inf')
        # Left to right: shortest subarray ending at or before i
        for i in range(n):
            curr_sum += arr[i]
            if (curr_sum - target) in prefix_sum:
                length = i - prefix_sum[curr_sum - target]
                min_len = min(min_len, length)
                best_left[i] = min_len
            else:
                best_left[i] = min_len
            prefix_sum[curr_sum] = i
        # Right to left: shortest subarray starting at or after i
        best_right = [float('inf')] * n
        prefix_sum = {0: n}
        curr_sum = 0
        min_len = float('inf')
        for i in range(n - 1, -1, -1):
            curr_sum += arr[i]
            if (curr_sum - target) in prefix_sum:
                length = prefix_sum[curr_sum - target] - i
                min_len = min(min_len, length)
                best_right[i] = min_len
            else:
                best_right[i] = min_len
            prefix_sum[curr_sum] = i
        # Combine
        result = float('inf')
        for i in range(n - 1):
            if best_left[i] != float('inf') and best_right[i + 1] != float('inf'):
                result = min(result, best_left[i] + best_right[i + 1])
        return result if result != float('inf') else -1
      
class Solution {
public:
    int minSumOfLengths(vector<int>& arr, int target) {
        int n = arr.size();
        vector<int> best_left(n, INT_MAX);
        unordered_map<int, int> prefix_sum;
        prefix_sum[0] = -1;
        int curr_sum = 0, min_len = INT_MAX;
        for (int i = 0; i < n; ++i) {
            curr_sum += arr[i];
            if (prefix_sum.count(curr_sum - target)) {
                int length = i - prefix_sum[curr_sum - target];
                min_len = min(min_len, length);
                best_left[i] = min_len;
            } else {
                best_left[i] = min_len;
            }
            prefix_sum[curr_sum] = i;
        }
        vector<int> best_right(n, INT_MAX);
        prefix_sum.clear();
        prefix_sum[0] = n;
        curr_sum = 0;
        min_len = INT_MAX;
        for (int i = n - 1; i >= 0; --i) {
            curr_sum += arr[i];
            if (prefix_sum.count(curr_sum - target)) {
                int length = prefix_sum[curr_sum - target] - i;
                min_len = min(min_len, length);
                best_right[i] = min_len;
            } else {
                best_right[i] = min_len;
            }
            prefix_sum[curr_sum] = i;
        }
        int result = INT_MAX;
        for (int i = 0; i < n - 1; ++i) {
            if (best_left[i] != INT_MAX && best_right[i + 1] != INT_MAX) {
                result = min(result, best_left[i] + best_right[i + 1]);
            }
        }
        return result == INT_MAX ? -1 : result;
    }
};
      
class Solution {
    public int minSumOfLengths(int[] arr, int target) {
        int n = arr.length;
        int[] bestLeft = new int[n];
        Arrays.fill(bestLeft, Integer.MAX_VALUE);
        Map<Integer, Integer> prefixSum = new HashMap<>();
        prefixSum.put(0, -1);
        int currSum = 0, minLen = Integer.MAX_VALUE;
        for (int i = 0; i < n; ++i) {
            currSum += arr[i];
            if (prefixSum.containsKey(currSum - target)) {
                int length = i - prefixSum.get(currSum - target);
                minLen = Math.min(minLen, length);
                bestLeft[i] = minLen;
            } else {
                bestLeft[i] = minLen;
            }
            prefixSum.put(currSum, i);
        }
        int[] bestRight = new int[n];
        Arrays.fill(bestRight, Integer.MAX_VALUE);
        prefixSum.clear();
        prefixSum.put(0, n);
        currSum = 0;
        minLen = Integer.MAX_VALUE;
        for (int i = n - 1; i >= 0; --i) {
            currSum += arr[i];
            if (prefixSum.containsKey(currSum - target)) {
                int length = prefixSum.get(currSum - target) - i;
                minLen = Math.min(minLen, length);
                bestRight[i] = minLen;
            } else {
                bestRight[i] = minLen;
            }
            prefixSum.put(currSum, i);
        }
        int result = Integer.MAX_VALUE;
        for (int i = 0; i < n - 1; ++i) {
            if (bestLeft[i] != Integer.MAX_VALUE && bestRight[i + 1] != Integer.MAX_VALUE) {
                result = Math.min(result, bestLeft[i] + bestRight[i + 1]);
            }
        }
        return result == Integer.MAX_VALUE ? -1 : result;
    }
}
      
var minSumOfLengths = function(arr, target) {
    const n = arr.length;
    const bestLeft = new Array(n).fill(Infinity);
    let prefixSum = new Map();
    prefixSum.set(0, -1);
    let currSum = 0, minLen = Infinity;
    for (let i = 0; i < n; ++i) {
        currSum += arr[i];
        if (prefixSum.has(currSum - target)) {
            const length = i - prefixSum.get(currSum - target);
            minLen = Math.min(minLen, length);
            bestLeft[i] = minLen;
        } else {
            bestLeft[i] = minLen;
        }
        prefixSum.set(currSum, i);
    }
    const bestRight = new Array(n).fill(Infinity);
    prefixSum = new Map();
    prefixSum.set(0, n);
    currSum = 0;
    minLen = Infinity;
    for (let i = n - 1; i >= 0; --i) {
        currSum += arr[i];
        if (prefixSum.has(currSum - target)) {
            const length = prefixSum.get(currSum - target) - i;
            minLen = Math.min(minLen, length);
            bestRight[i] = minLen;
        } else {
            bestRight[i] = minLen;
        }
        prefixSum.set(currSum, i);
    }
    let result = Infinity;
    for (let i = 0; i < n - 1; ++i) {
        if (bestLeft[i] !== Infinity && bestRight[i + 1] !== Infinity) {
            result = Math.min(result, bestLeft[i] + bestRight[i + 1]);
        }
    }
    return result === Infinity ? -1 : result;
};