zoukankan      html  css  js  c++  java
  • 和至少为 K 的最短子数组

    返回 A 的最短的非空连续子数组的长度,该子数组的和至少为 K 。
    如果没有和至少为 K 的非空子数组,返回 -1 。

    示例 1:
    输入:A = [1], K = 1
    输出:1

    一开始想的很好,直接暴力循环嘛,然而这当然是不能通过全解的,这是困难难度,不是中等难度。
    果然,最后一个样例,A有十万个数字,暴力循环直接超时。
    看官方解,思路如下:

    我们用数组 P 表示数组 A 的前缀和,即 P[i] = A[0] + A[1] + ... + A[i - 1]。我们需要找到 x 和 y,使得 P[y] - P[x] >= K 且 y - x 最小。

    这一步大家都理解,无非是避免重复计算,优化算法。

    我们用 opt(y) 表示对于固定的 y,最大的满足 P[x] <= P[y] - K 的 x,这样所有 y - opt(y) 中的最小值即为答案。

    第一步得到了前缀和数组P,通过对于P的遍历,我们可以找到答案。到这一步都还好理解,但是仅仅如此优化的程度还不够,还是暴力循环的路子。

     1. 如果 x1 < x2 且 P[x2] <= P[x1],那么 opt(y) 的值不可能为 x1,这是因为 x2 比 x1 大,并且如果 x1 满足了 P[x1] <= P[y] - K,那么 P[x2] <= P[x1] <= P[y] - K,即 x2 同样满足 P[x2]<= P[y] - K。
     2. 如果 opt(y1) 的值为 x,那么我们以后就不用再考虑 x 了。这是因为如果有 y2 > y1 且 opt(y2) 的值也为 x,但此时y2 - x 显然大于 y1 - x,不会作为所有 y - opt(y) 中的最小值。

    这是此题的两条性质,都还好理解。它们的作用是舍弃不必要的计算,来达到优化时间复杂度的作用。


    性质一举例: 若P[1]=2,P[2]=5,而P[3]=3,显然题目给出的数组A中,A[0]=2,A[1]=3,A[2]=-2
    那么在考虑元素坐标y=6时,寻找opt(y)即最大的满足 P[x] <= P[y] - K 的 x时,我们可以直接略过2,遍历顺序为P[1],[3]........这就是利用性质一来略去不必要的计算,实质则是利用前缀和的性质直接略去A中值为负数的坐标。但这不是简单的略去,我们略去的只是该坐标的前缀和,这个为负数的元素仍然存在于坐标位于它之后的前缀和中,例如上面提到的A[2]值为-2,我们舍去了P[2],但A[2]仍然被包含于P[3]中。

     我们维护一个关于前缀和数组 P 的单调队列C,它是一个双端队列(deque),其中存放了下标 x:x0, x1, ... 满足 P[x0], P[x1], ... 单调递增。这是为了满足性质一。

     当我们遇到了一个新的下标 y 时,我们会在C队尾移除若干元素,直到 P[x0],P[x1], ..., P[y] 单调递增。这同样是为了满足性质一。

    同时,我们会在C队首也移除若干元素,如果 P[y] >= P[x0] + K,则将C队首元素移除,直到该不等式不满足。这是为了满足性质二。

    注意,为了便于理解有修改。

    这是这道题的精华之处,首先看第一条,之所以要有双端队列C,是为了便于两端pop元素。而之所以需要在两端pop元素,是为了略去不必要的计算(之所以能略去则是由于本题的性质所决定,上面已经提到)。

    我们算法的所有操作,都在双端队列C上进行,前缀和数组P只是一个提供基础数据的作用。需要注意的是,双端队列中保存的x1,x2,x3指的是原题中给出的数组A的元素坐标,而非前缀和。

    leetcode这串话,其实第一段和第二段是一个意思,即利用性质一使双端队列C单调递增,pop掉C队尾的不能使单调递增性质成立的前缀和元素,至于为什么上面已经提到。
    性质二很简单,我就不详细说了。
    代码如下:



     1 int n = A.size();//这里需要注意一下,最好将A.size()转化为int,因为A.size()其实是自然数,后续要是有A.size()-7之类的会发生下溢
     2 vector<int> p(n + 1, 0);
     3 for (int i = 0; i < n; ++i)
     4 {
     5 p[i + 1] = p[i] + A[i];
     6 }//生成前缀和数组p
     7 deque<int> c;//定义双端队列c
     8 int ans = n + 1;
     9 for (int y = 0; y <= n; ++y)//其实就是对前缀和队列p进行遍历
    10 {
    11 while (!c.empty() && p[y] <= p[c.back()])//性质一
    12 {
    13 c.pop_back();
    14 }
    15 while (!c.empty() && p[y] - p[c.front()] >= K)//性质二
    16 {
    17 ans = min(ans, y - c.front());//寻找y-opt(y)最小值
    18 c.pop_front();
    19 }
    20 c.push_back(y);
    21 
    22 }
    23 return ans <= n ? ans : -1;


    参考:
    https://leetcode-cn.com/problems/shortest-subarray-with-sum-at-least-k/solution/he-zhi-shao-wei-k-de-zui-duan-zi-shu-zu-by-leetcod/

  • 相关阅读:
    关于同步解释
    dll 问题 (转)
    SQL SERVER 触发器中如何调用外部程序
    SQL SERVER所有表、字段名、主键、类型、长度、小数位数等信息
    variant 和 Stream 的互換
    Variant的相关函数
    使用 wxPython 创建“目录树”(5)
    wxPython 绘图演示——模拟雷达信号图(2)
    wxpython 实现简易画板(1)
    使用 wx.tools.img2py (4)
  • 原文地址:https://www.cnblogs.com/BYGAO/p/12331576.html
Copyright © 2011-2022 走看看