剑指Offer57-Ⅱ.和为s的连续正数序列

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:不如写法1class 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.数学题,解一元二次方程:
拓展知识,没必要;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值