题目描述
给定一个大小为n的正整数数组和一个正整数s,要求找到一个最小的连续子数组之和大于等于s,求这个最小长度,如果不存在则返还0,原题以及例子如下,
上面这只是题目的基础要求,不考虑时间复杂度的情况下是比较容易解决的,但是这道题加了两个实现条件,要求能得到时间复杂度为O(n)和O(n * log n)的解法。
————————想直接看题目要求解法的同学请跳往下一条分割线———————————–
首先我先来简单介绍一个坑了我很久的实现方法。
最初考虑到时间复杂度为O(n),我想的方法是先求和得到整个数组的和,然后从两头开始逐渐的减去较小的那个值(因为如果连续数组长度更短,一定包含的是更大的那个值),直到和再也不能减去任何一个数为止,那么我便得到了结果。咋一想,仿佛没什么问题,但仔细一想我括号里的那句话,就会发现这种解法不具有完备性,为什么怎么说呢?如果不理解,可以先看看我举的一个例子。
int s = 13;
int a[] = {1,2,3,7,6,5,2,3};
很明显,在比较3和3的时候,这两个数相等,本来我以为随便取一个就好,但实际上这是不行的,因为这一步的选择将会影响到最终的求解,比如这儿如果选择保留左面的3,那么最终得到的结果长度为3,如果选择保留右面的3,则结果长度为2。
所以这里不完备的原因在于,当遇到两个相等的数时,无法判断这个是否属于解集,如果属于,是左边的数还是右边的数。如果有大佬有判断的方法,麻烦联系我,试了好多方法都没成功。
———————————————–答案分割线———————————————————-
上面那种方法求解无果,我突然想到了另外一种方法,一边求和,一边舍去,保留最小长度。下面我来简单介绍下,我的想法。
我从0开始,连续的求和,知道和大于等于s,这时,我便开始从头开始逐渐的减去一个数,知道和小于s时,如果这个长度+1的和小于最小长度,我便更新最小长度。这个方法和上面方法的思想有些许类似,不过这个方法遍历了所有的可行的长度,所以是可行的,下面直接上代码,
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int j = 0, i = 0;
int l = nums.size();
int sum = 0;
int min = l + 1;
while (j < l) {
while (sum < s) {
if (j == l) {
if (min == l + 1) {
return 0;
} else {
return min;
}
}
sum += nums[j];
++j;
}
while (i <= j && sum >= s) {
sum -= nums[i];
++i;
}
min = (j - i + 1) < min ? j - i + 1 : min;
}
return min % (l+1);
}
};
最后,简单介绍下时间复杂度为O(n * log n )的算法,我觉得这个比O(n)的算法还要难想一点。在这里用到了二分查找的思想,我考虑设置一个窗口的大小为k,使这个窗口在数组上滑动,如果能找到这么一个窗口,满足里面数的和大于等于s,那么我便记录下这个长度,缩小窗口大小为原来的一半,如果不满足则增加当窗口的一半大小,最终返还最小长度。