题目一:长度最小的数组

1、分析:这道题需要的是返回满足总和>=target的数组的最小长度。
先用暴力算法来求解,那么算法复杂度是O(N2)(带上sum一直在累加的话),我们发现这个数组的元素都是正整数,那么可以利用单调性来降低时间复杂度。
观察例图,此时假设target = 7,数组是num,当right走到下标为3的时候,此时sum已经为8>=target,那left此时固定的话,right还有必要往右走吗?没有必要!即使right往右走也不是我们要的结果,因为单调性,sum和len会变大,我们没必要枚举后面的结果。应该直接考虑让left往右走,继续判断sum是否满足条件。这个分析其实就是所谓的滑动窗口,利用的就是数组的单调性。
2、题解:
(1)定义left和right:相当于双指针,即使是暴力算法也要定义两个变量用于两层循环
(2)进窗口:把right下标对应的元素加入sum
(3)判断sum是否>=target,满足,更新数组长度len和sum,出窗口:left++(注意这个步骤3需要循环,下面会给出错误示范)

3、错误解析:拿target =7,nums =[2,3,1,2,4,3]举例。left=0,right=3,sum=8满足条件,更新结果后:len = 4,sum = 6;之后left = 1,right= 4时sum = 10,更新结果后:len = 4,sum = 7,这里按代码来走会让sum继续加上right = 5的元素3,很明显是不对的,因为sum =7时也满足条件,没有更新left之前就不应该让right往后走,这里要继续更新len和sum,所以if应该改成循环while。
可能有人会疑惑:那这样代码复杂度不还是O(N2)吗?仔细看这个执行过程,会发现right是不回退的,也就是说这里left和right各自最多走n步,最后就是O(2n),其实是降低了复杂度的。
题目二:无重复字符的最长子串

1、分析:这里的子串和第一道题的子数组类似,它需要我们给出不重复字符的最长子串长度。用暴力解法也需要两层循环O(N2),而且还要进行字符匹配,这里可以用哈希表解决。
观察例图,当right走到下标6时会出现重复字符,用暴力算法的话就是让left往右移动一步,right回退,但是后面right还是会走到下标6出现重复字符,并且len只会变小,因此right不需要回退,而是让left绕开重复字符走到下标4,这样一来就和第一道题一样,left和right都不回退,时间复杂度降为O(N)。
说明:这里不用哈希表,它只需要不重复的字符,可以用一个数组来模拟哈希表
2、题解:
(1)定义left,right(相当于双指针),arr[128](模拟哈希表)
(2)入窗口:将下标right的元素加入arr,让arr对应元素加1。--->arr[s[right]]++
(3)判断:arr[s[right]] 是否大于1,大于1表示有重复字符,出窗口:数组arr的有效值对应滑动窗口里的值,需要让arr[s[left]]--,然后再left++,直到没有重复字符出现。
(4)更新结果。(这一步不能在2后面进行,否则会把重复元素也算进len)
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int left = 0,right = 0, size = s.size(), len = 0;
int arr[128] = {0};
for(;right < size; right++)
{
arr[s[right]]++; //入窗口
while(arr[s[right]] > 1)//判断
{
arr[s[left]]--;
left++;//出窗口
}
len = max(right-left+1, len);//更新
}
return len;
}
};
这里虽然有两层循环,但是right没有回退,时间复杂度只有O(2N)-->O(N)。
题目三:最大连续1的个数 III

1、分析:这道题不推荐直接对0作出修改,因为你一个循环要修改,下一次循环就要把修改过的值重新变成0,这样代码编写非常麻烦。它需要的是最多翻转k个0后数组连续1的最多个数,我们可以借助一个计数器zero_count计算0出现的个数进一步得出数组连续1的最多个数。下面给出一个实例(假设k = 2,即最多翻转2个0)

用暴力解法来看,两个指针有套两层循环,枚举每个符合条件的子数组长度,很明显时间复杂度为O(N2)。第一次循环,right走到下标5的位置停下来,此时zero_count大于k了;但是再让left++,right回到left的位置,重复之前的步骤,会发现最后right还是走到下标5的位置,并且符合条件的子数组长度并没有变大,只会变下,说明让right回退是没有必要的。应该直接让left往右走,直到zero_count不大于k,再让right往后走。
2、题解:
(1)定义双指针:left,right
(2)进窗口:right走到0的下标,就让zero_count++;为1忽视。
(3)判断:当zero_count大于k,判断nums[left]是否为0,为0就让zero_count--(出窗口);left++。
(4)更新结果。
class Solution {
public:
int longestOnes(vector<int>& nums, int k) {
int zero = 0;
int ret = 0;
for (int left = 0, right = 0; right < nums.size(); right++) {
if (nums[right] == 0) {
zero++;//进窗口
}
while (zero > k) //判断
{
if (nums[left] == 0)
zero--;//出窗口
left++;
}
ret = max(ret, right - left + 1);//更新结果
}
return ret;
}
};
3、注意事项:如果把更新结果放在判断之前可以吗?看似好像可以,即使right遇到第k+1个0时,
只要让len变成right - left不就可以了吗。但其实是不行的,举个例子:nums = [0,0,0,1],k = 4。
len = right - left这种条件下,right走到3下标,len更新为3时就要结束函数了,很明显答案应该是len = 4。
题目四:将x减到0的最小操作数

1、分析:对于这道题,如果正着去做的话,你会发现什么时候移除最左什么时候移除最右的元素是不确定的,所以我们可以考虑反着来做,什么意思呢?它不是要我们找最左最右操作数最少吗,那反过来是不是就是相当于找中间长度最长的连续子数组呢。这样一来是不是就好办了,你可能会想到题目一,虽然题目一是要长度最小的数组,但是原理都是一样的。
2、题解:上面给了例图,我们反着过来做后,现在是要找中间连续数组的和 等于 数组的总和减去x的差值target。用暴力算法来做的话,left和right两层循环嵌套,时间复杂度是O(N2)。
每次一组循环后,再让right回退,重新开始,会有很多没必要的操作:就比如left = 0,right = 3时,满足条件,更新结果后,让left++,那让right回来从下标1开始还不是要回到下标3吗,而且也不满足条件,还是需要继续往后走,那还不如让right先不动,先更新left,直到中间连续数组的和小于target,再让right往后走,这里的前提都是数组是正整数,也就是说还是利用了单调性。
3、代码思路:
(1)定义left,right,找出target(数组总和- x)
(2)进窗口:tmp += nums[right]
(3)判断:tmp > target? 成立就出窗口:tmp -= nums[left],left++
(4)更新结果:tmp = target?len = right - left +1。
class Solution {
public:
int minOperations(vector<int>& nums, int x) {
int sum = 0;
for (int a : nums) {
sum += a;
}
int target = sum - x;//转换目标值
if (target < 0)
return -1;
int tmp = 0;
int len = -1;
for (int left = 0, right = 0; right < nums.size(); right++) {
tmp += nums[right];//进窗口
while (tmp > target) //判断
{
tmp -= nums[left++];//判断
}
if (tmp == target)//更新结果
len = max(len, right - left + 1);
}
if (len == -1)
return -1;
else
return nums.size() - len;
}
};
注意事项:最后结果不是返回len,而是数组总长度减去len。还有一点,可能有人会把结果更新放在判断循环里,乍一看好像没错,比如这个代码:
class Solution {
public:
int minOperations(vector<int>& nums, int x) {
int sum = 0;
for (int a : nums) {
sum += a;
}
int target = sum - x;
if (target < 0)
return -1;
int tmp = 0;
int len = -1;
for (int left = 0, right = 0; right < nums.size(); right++) {
tmp += nums[right];
while (tmp >= target)
{
if (tmp == target)
len = max(len, right - left + 1);
tmp -= nums[left++];
}
}
if (len == -1)
return -1;
else
return nums.size() - len;
}
};
那如果是这个例子是不是就不成立了:数组[1,2,3,4],x = 10,这个运行结果为-1而不是4,原因就是len一直没办法更新。

1万+

被折叠的 条评论
为什么被折叠?



