- 题目:剑指Offer57-Ⅱ.和为s的连续正数序列
- 思路:
1.暴力双循环:
枚举每个位置为起始点 i ,枚举终点 j 从 i+1开始;
class Solution {
public:
vector<vector<int>> findContinuousSequence(int target) {
vector<vector<int>> vec;
vector<int> res;//重复利用res和sum,减少内存消耗
int sum = 0;
for (int i = 1; i <= (target - 1) / 2; ++i) {
//target=9,最多以4起始;target=8,最多以3起始
for (int j = i; s < target; ++j) {
sum += j;
if (sum > target) {
sum = 0;
break;
} else if (sum == target) {
res.clear();
for (int k = i; k <= j; ++k) res.emplace_back(k);
vec.emplace_back(res);
sum = 0;
break;
}
}
}
return vec;
}
};
2.(最优解)滑动窗口:时间O(target):由于两个指针移动均单调不减,且最多移动到( target - 1 ) / 2 ,所以时间复杂度为O(target) ,空间O(1):除了结果序列,i,j,s,limited,tmp都只需要常数空间;
滑动窗口[ i,j ] 的操作只有两种:①通过j++来扩大窗口 ②通过i++来缩小窗口
序列是连续正整数:即[ 1,2,3,… ]
我们要找的目标序列可能以1,2,3,…均可能作为起始点,且如果以i为起始点存在一个目标序列的话,这个序列一定唯一;
滑动窗口实际就是枚举所有起始点 i ,去尝试找到以 i 起始的一个目标序列;但滑动窗口好的一点是节省了右边界的遍历过程,如果使用纯暴力需要双层循环左右边界,若以i为左边界,右边界需要从i+1开始尝试,而滑动窗口只需要接着以i-1失败的右边界开始尝试即可;
起始点最大可能取到target / 2,向下取整,例如target=9,起始点最大的序列是[4,5];若起始点都>=target的一半了,下个数更大,那两数和就超过target了;
如果以 i 为起始点没找到目标序列,即[ i,j ] 的元素和s < target,[ i,j + 1 ] 的元素和s > target;则说明不存在以 i 为起始点的目标序列,接下来尝试以 i + 1为起始点,且没必要以 i + 2为右边界开始尝试,因为[ i,j ] 的元素和s < target,那么 [ i+1, j ]的元素和一定<target,因此首先需要尝试的右边界依然是j+1,这也就是滑动窗口比纯暴力双层for循环优化的地方;
如果以 i 为起始点找到了一个目标序列[ i,j ],那么以 i+1为起始点一定找不到目标序列,因为[i+1 ,j]和<target,而[i+1,j+1]一定>target,毕竟少一个较小的数i,但增加了一个很大的数j+1,明显就超了;
写法1:动态的维护窗口内的元素和s
class Solution {
public:
vector<vector<int>> findContinuousSequence(int target) {
vector<vector<int>> res;
vector<int> tmp;
if (target < 3) return res;//题目说正整数序列至少有两个数,因此最小情况是1,2,此时和为3
int i = 1, j = 2, s = 3;//i,j分别表示当前的窗口左右边界,s表示当前窗口内的元素和
int limited = (target - 1) >> 1;//提前求好,不用每次循环都求一次
while (i <= limited) {
if (s > target) {
//说明以当前i为起始点无法找到满足要求的序列,接下来找以i+1为起始点的序列
s -= i;
++i;
}
else if (s < target) {
//说明当前窗口[i, j]小了,因此通过右移右边界来扩大窗口
++j;
s += j;
}
else {
//当前窗口[i, j]是一个符合要求的序列
tmp.clear();
for (int k = i; k <= j; ++k) tmp.emplace_back(k);
res.emplace_back(tmp);
s -= 2 * i + 1;这里如果起始点i找到了一个解,那么起始点i+1一定找不到解,因此可以直接以i+2为起始点,去寻找解
i += 2;
}
}
return res;
}
};
写法2:不如写法1好
class Solution {
public:
vector<vector<int>> findContinuousSequence(int target) {
vector<vector<int>> res;
if (target < 3) return res;
int i = 1, j = 2;
while (i <= target / 2) {
int s = (i + j) * (j - i + 1) * 0.5; //如果每次通过等差序列求当前窗口内的元素和虽然也是O(1),但还是比较慢的
if (s > target) ++i;
else if (s < target) ++j;
else {
vector<int> tmp;
for (int k = i; k <= j; ++k) {
tmp.emplace_back(k);
}
res.emplace_back(tmp);
++i;
}
}
return res;
}
};
3.数学题,解一元二次方程:
拓展知识,没必要;