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

本文针对LeetCode上的第862题“和至少为K的最短子数组”,提供了三种不同的解题思路及代码实现。从简单暴力解法到动态规划,最后采用单调队列配合二分搜索实现高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

最近在刷LeetCode,随便写点备忘巩固一下。

题目

返回 A 的最短的非空连续子数组的长度,该子数组的和至少为 K 。

如果没有和至少为 K 的非空子数组,返回 -1 。

示例 1:

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

示例 2:

输入:A = [1,2], K = 4
输出:-1

示例 3:

输入:A = [2,-1,2], K = 3
输出:3

提示:

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

解法1

直接想到一个题,求最大子数组和,在算法优化后可以在O(n)时间内完成.这题稍微有些变化,但是有一点点像.假设对每一个数组中的,计算以它开始子数组和,一旦满足后就与当前最短对比,后续就不用算了.

    class Solution {
        public int shortestSubarray(int[] A, int K) {
            int size = A.length;
            int ans = size + 1;
            for (int i = 0; i < size; ++i) {
                if (A[i] >= K)
                    return 1;
                int sum = A[i], j = 1;
                while (sum > 0 && j < ans && j < size - i) {
                    sum += A[i+j];
                    ++j;
                    if (sum >= K)
                        ans = j;
                }
            }
            if (ans == size + 1)
                return -1;
            else
                return ans;
        }
    }

嗯,结果显然是超时了。时间复杂度O(n²)

解法2

试试动态规划吧

class Solution {
public:
    int shortestSubarray(vector<int>& A, int K) {
        int size=A.size();
        int temp[size];
        for(int i=0;i<size;++i) {
            if(A[i]>=K)
                return 1;
            temp[i]=A[i];
        }
        for(int i=2;i<=size;++i) {
            for(int j=0;j<=size-i;++j) {
                if(temp[j]>0) {
                    temp[j]+=A[i+j-1];
                    if (temp[j]>=K)
                        return i;
                }
            }
        }
        return -1;
    }
};

建立一个数组temp,用于存放长度为i时以各元素为起点的子数组和。
emmmmm,这时间复杂度不还是O(n²)嘛!
果不其然,继续超时

解法3

想不出来,我认怂了,在社区找了个别人的代码

class Solution {
    public int shortestSubarray(int[] A, int K) {
        int result = 0x7fffffff;

        int[] distance = new int[A.length + 1];
        distance[0] = 0;
        for(int i = 1; i <= A.length; i++) {
                distance[i] = A[i - 1] + distance[i-1];
        }

        //维护单调栈 从右边开始入栈 栈底就是当前元素往右走最大的元素
        //如果从该元素出发有最解 则解的右端必定位于当前元素与当前元素往右的最大元素之间
        //且解的右端必定在栈内(因为栈单调往栈底递减 所有比当前元素大的元素均在栈中)
        //再对该区间进行二分法求最靠近左端的元素

        //进一步优化可以使用单调队列
        //将过于遥远(大于当前最优解距离)的栈底最大元素变为队列头部出队


        LinkedList<Integer> queue = new LinkedList<>();

        for(int i = A.length; i >= 0; i--) {
                //去除过于遥远的队头元素
                while(!queue.isEmpty() && queue.peek()-i >= result) queue.removeFirst();
                //当新入队元素比队尾元素大时 队尾元素出队
                while(!queue.isEmpty() && distance[queue.peekLast()] <= distance[i]) queue.removeLast();
                //当前元素入队
                queue.add(i);
                //开始二分搜索
                if(distance[queue.peek()] >= distance[i] + K) { //先直接判断最大元素是否满足条件
                        //二分搜索最短距离
                        int currentMinLength = binarySearch(queue, i, distance, K);
                        //判断是否当前最短距离
                        result = result < currentMinLength? result : currentMinLength;
                }
        }

        return result == 0x7fffffff ? -1 : result;
    }

    public int binarySearch(LinkedList<Integer> queue, int i, int[] distance, int K) {
            //二分搜索初始上下边界
            int downer = 0, upper = queue.size()-1, mid = 0;
            //最终结果下标
            int j = 0x7fffffff;

            while(upper >= downer) {
                    mid = (upper + downer) >> 1;
                if(distance[queue.get(mid)] >= distance[i] + K) {
                        j = queue.get(mid);
                        downer = mid + 1;
                }
                else upper = mid - 1;
            }

            return j == 0x7fffffff ? 0x7fffffff : (j - i);
    }
}

运用单调队列实现。所谓单调队列,就是一端进,两端出的队列了。每一个新元素入队前,首先检查队首元素是否过于陈旧,若是就移除 ;随后检查队尾元素是否比当前元素值更大,若不是则移除。

假设某时刻队列中从队首到队尾有三个元素abc,此时来了个d ∈(b,c),假设a过旧,则d入队后队列将会变成bd

回归本题,解法2的动态规划法实际上是按照子数组长度从低到高生成,看似一旦出现解立刻就可以得到答案,后续更长的都不需要算了,实际时间复杂度仍然是O(n²)级。解法3的巧妙之处在于,它逐步计算每个子数组的最大结果,而不是一次计算整个数组(这么一看之前的解法2根本算不上动态规划了)。

考虑Si(j)为以i为起点的,长度为j的子数组和。那么有

Si(j)=S0(i+j)-S0(i)

由此可以直接计算任何位置任何长度的子数组和。

distance数组实际就是S0。所以只要得知distance[队首]-distance[当前位置]K,就说明当前位置开始的子数组有满足条件的存在。那么假设某时刻单调队列里队首至队尾有三个元素q1q2q3则有distance[q1]>distance[q2]>distance[q3]q1>q2>q3。故可以用二分查找加速搜索过程。

算法整体时间复杂度为O(nlog(n)),提交通过(废话)。

感想

我好菜啊

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值