1. 程式人生 > >LeetCode題862 —— Shortest Subarray with Sum at Least K

LeetCode題862 —— Shortest Subarray with Sum at Least K

date 重新 滑動窗口 nbsp sub etl gen index min

Return the length of the shortest, non-empty, contiguous subarray of A with sum at least K.

If there is no non-empty subarray with sum at least K, return -1.

Example 1:

Input: A = [1], K = 1
Output: 1

Example 2:

Input: A = [1,2], K = 4
Output: -1

Example 3:

Input: A = [2,-1,2], K = 3
Output: 3

Note:

  1. 1 <= A.length <= 50000
  2. -10 ^ 5 <= A[i] <= 10 ^ 5
  3. 1 <= K <= 10 ^ 9

思路

本來以為用dp來做,看了下答案,解法中並沒有。使用的解法是滑動窗口,將問題重新定義為和A的前綴和有關,定義

P[i] = A[0] + A[1] + ... + A[i-1]

我們想要求的便是最小的 y-x,y>x 並且P[y] - P[x] >= K

Motivated by that equation, let opt(y) be the largest x such that P[x] <= P[y] - K

. We need two key observations:

  • If x1 < x2 and P[x2] <= P[x1], then opt(y) can never be x1, as if P[x1] <= P[y] - K, then P[x2] <= P[x1] <= P[y] - K but y - x2 is smaller. This implies that our candidates x for opt(y) will have increasing values of P[x].

  • If opt(y1) = x, then we do not need to consider this x

    again. For if we find some y2 > y1 with opt(y2) = x, then it represents an answer of y2 - x which is worse (larger) than y1 - x.

opt(y)是使得當 P[x] <= P[y] - K 時 x 能取到的最大值。

1. 如果有 x1<x2 並且 P[x2]<=P[x1],那麽opt(y)一定不是 x1,因為如果有P[x1] <= P[y] - K,那麽 P[x2] <= P[x1] <= P[y] - K,但是 y - x2 is smaller。這表明對於opt(y)的候選x應該是在使P(x)遞增的區間去找。要註意這裏的P[x1]指的是從0到X1的數組元素之和,不是單單指一個x1位置上元素的值。

2. 如果opt(y1)=x, 那麽不需要再次考慮x。因為如果我們找到某些y2>y1並且opt(y2)=x,那麽這表明這個解答 y2-x 是比之前的解答 y1-x 是更壞的答案。

Calculate prefix sum B of list A.
B[j] - B[i] represents the sum of subarray A[i] ~ A[j-1]
Deque d will keep indexes of increasing B[i].
For every B[i], we will compare B[i] - B[d[0]] with K.

    public int shortestSubarray(int[] A, int K) {
        int N = A.length, res = N + 1;
        int[] B = new int[N + 1];
   // 下面利用數組A重新構造了數組B,滿足B[i+1]-B[j]=A[i]+A[i-1].....+A[j]
        for (int i = 0; i < N; i++) B[i + 1] = B[i] + A[i];
        Deque<Integer> d = new ArrayDeque<>();
        for (int i = 0; i < N + 1; i++) {  
            while (d.size() > 0 && B[i] - B[d.getFirst()] >=  K)
                res = Math.min(res, i - d.pollFirst()); // 雙端隊列存的是索引
            while (d.size() > 0 && B[i] <= B[d.getLast()]) d.pollLast(); // Deque d keep indexes of increasing B[i]
            d.addLast(i);
        }
        return res <= N ? res : -1;
    }

上面的出入隊列順序是這樣的:首先對於每個索引i,對應的是B[i],將這個索引作為y位置來考慮,因為雙端隊列保持的索引是的B[i]是遞增的,為了從最大處逼近K,我們從隊頭依次取索引出來計算:

B[i] - B[d.getFirst()]

如果比K大,那麽則要找這其中距離索引i最近的那一個:

res = Math.min(res, i - d.pollFirst());

然後是隊列要keep indexes of increasing B[i],索引判斷當前的B[i]是否大於隊列尾部的索引處的

B[i] <= B[d.getLast()

如果不能構成遞增,根據之前的分析,當前y所在的位置i的最優解opt(y)一定不會是在前面遞增的部分取,所以隊列要從後往前一個個彈出隊尾直至能和B[i]構成遞增序列。

LeetCode題862 —— Shortest Subarray with Sum at Least K