leetcode 862. 和至少为 K 的最短子数组

地址

题目

给你一个整数数组 nums 和一个整数 k ,找出 nums 中和至少为 k 的 最短非空子数组 ,并返回该子数组的长度。如果不存在这样的 子数组 ,返回 -1 。
子数组 是数组中 连续 的一部分。

直觉和前缀和有关,但数组有负数,简单的前缀和解决不了该问题。

暴力求解

遍历每个值,找到每个值最短的子数组,最后对数组长度求min

滑动窗口

官方给出的题解有点抽象。

有些题目中,滑动窗口体现了整个子数组,而在本题中,由于数组中元素元素是有负数的,使用滑动窗口来保存子
数组,不能确认某个子数组的长度是否为最短。

所以,本题中的滑动窗口中保存的是起点的集合。遍历每个元素y时,元素y作为子数组的终点,滑动窗口中保存的是子数组的可能的起点x。子数组的长度是用y-x来计算,而不是滑动窗口的长度。并且相关的前缀和需要满足presum[y]-presum[x]>k。

首先计算前缀和。关于前缀和第一位置0(presum[0] =0),而不是presume[0] = arr[0],是为了解决arr数组为空的情况。这样不需要判断特殊情况,逻辑比较清晰。

其次,构建滑动窗口,即链表。这里使用双向队列实现。

  1. 保证滑动窗口的单调性。对于给定的元素y,在滑动窗口中找对应的x,使得presum[y]-presum[x]>k。显然需要presum[x]小于presum[y],所以需要移除presum[x]大于presum[y]的x。此时,只保证了presum[x]小于presum[y],但presum[x]前面的值是否小于presum[y]?显然是小于的,在计算以x为结尾的子数组时,会保证前面小于presum[x],所以只需要保证presum[y]大于滑动窗口中的最后一个值。保证将当前的y加入滑动窗口后,滑动窗口依然是单调递增的。在移除了presum[x]大于presum[y]的所有x后,再找到差大于k的既可。

  2. 当presum[y]-presum[x]>k时,此时x是一个解,但未必是最小的。由于滑动窗口是中的x所对应的presum是单调递增的,所以从滑动窗口的头部取出元素,若做差大于k,则更新 子数组的最短距离,并从滑动窗口删除该元素,直到差值小于k。

  3. 最后,将y加入滑动窗口。完成了对以元素y结尾的所有的子数组的搜索。

是否存在保证滑动窗口的单调性时,删除后面可能存在的最短的子数组的解?如下图,在处理b时,会将a移除,在计算c时,假设c-a大于k,那么c-b一定也大于k,且c和b之间的距离是小于c和a之间的距离。更严谨的证明见官方题解

             *(c)
   *(a)
        *(b)
class Solution {
    public int shortestSubarray(int[] nums, int k) {
        int len = nums.length;

        long[] dp = new long[len+1];
        Deque<Integer> list = new LinkedList();// Deque !!! not sublist 
        long  r = len +1;

        dp[0] = 0;
        for(int i=0;i<len;i++){
             dp[i+1] = (long)nums[i] +dp[i];  
        }  

        for(int i=0;i<len+1;i++){
            //dp[i] = dp[i]+(long)nums[i];
            while(!list.isEmpty() && dp[i] < dp[list.getLast()]){
                list.removeLast();
            }
            while(!list.isEmpty() && dp[i] -k >= dp[list.getFirst()]){
                r = Math.min(r,i-list.removeFirst());
            }
            list.addLast(i);
        }
        return r== len+1 ? -1:(int)r;
    }
}

trick

链表在java中可以直接使用arrayList,或linkedlist,但也可以使用原生数组,使用left和right来标记区间。

reference

https://github.com/Shellbye/Shellbye.github.io/issues/41

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值