• 对于暴力枚举的一些优化方法的题解


    话不多说 先上题目

    输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。

    序列内的数字由小到大排列,不同序列按照首个数字从小到大排列

    举例:

    输入:target = 9
    输出:[[2,3,4],[4,5]]
    输入:target = 15
    输出:[[1,2,3,4,5],[4,5,6],[7,8]]
    接下来是分析题目:
    这种题很简单 当然是为了作为例子来说明的啦
    题目我们要注意这几点:输出的大致上是一个二维数组,最里边的数组至少两个元素 并且是连续的 这提示给我们一个信息 如果暴力枚举 最终是停在target/2向下取整上
    在程序里 我们用(target - 1) / 2 来表示 target / 2 下取整(纯粹的数学问题)
    我们首先想到的方法肯定是暴力枚举 做是肯定好做 问题在一些细节以及简洁程度上 这里就给出暴力枚举比较简洁的代码:

     1 class Solution {
     2 public:
     3     vector<vector<int>> findContinuousSequence(int target) {
     4         vector<vector<int>> vec;
     5         vector<int> res;
     6         int sum = 0, limit = (target - 1) / 2; 
     7         for (int i = 1; i <= limit; ++i) {
     8             for (int j = i;; ++j) {
     9                 sum += j;
    10                 if (sum > target) {
    11                     sum = 0;
    12                     break;
    13                 }
    14                 else if (sum == target) {
    15                     res.clear();//清空数组
    16                     for (int k = i; k <= j; ++k) res.emplace_back(k);//类似栈一样向容器后添加元素 
    17                     vec.emplace_back(res);
    18                     sum = 0;
    19                     break;
    20                 }
    21             }
    22         }
    23         return vec;
    24     }
    25 };
    对于这种算法 我们给出分析:
    首先是时间复杂度 由于时间复杂度的性质 我们可以看出
    真正决定时间复杂度的无非就是
       for (int i = 1; i <= limit; ++i) {
       for (int j = i;; ++j) {
    这两行 他们是外层循环和内层循环 时间复杂度要相乘的  而后边的操作都是常数级别的操作 或者说
     for (int k = i; k <= j; ++k) res.emplace_back(k);//类似栈一样向容器后添加元素 

    时间复杂度比它小或者同阶 总之不比它大

    所以我们只需要关注

      for (int i = 1; i <= limit; ++i) {
       for (int j = i;; ++j) {

     这两个

    因为 外层循环 最多到limit次 也就是(target - 1) / 2;时间复杂度O(n)
    内层 如果是从1开始累加 最多也就是加到根号下target(这还是数学)时间复杂度O(根号n)
    也就是最终的时间复杂度O(target√target)
    对于空间复杂度
    因为是用常数数组存放变量 所以是O(1)

    接着我们对这种方法进行优化:

    对于这种数组问题 还是连续的变量 是由起点到终点的一个遍历求和验证 用双指针(统称)会好很多 其实具体的称呼是“滑动窗口法”
    所以我们可以用l和r(左边和右边)两个指针表示起点终点

    sum 表示 [l,r] 的区间和,由求和公式可 求得
    sum= (l+r)(r-l+1)/2

    
    

    之后 一共有三种情况:

    如果 sum<target 则说明指针 r 还可以向右拓展使得 sum增大,此时指针 r向右移动,即 r++
    如果 sum>target则说明以 l 为起点不存在一个 r 使得 sum=target ,此时要枚举下一个起点,指针 l 向右移动,即l++
    如果 sum==target 则说明我们找到了以 l 为起点得合法解 [l,r],我们需要将 [l,r] 的序列放进答案数组,且我们知道以 l 为起点的合法解最多只有一个,所以需要枚举下一个起点,指针 l 向右移动,即 l++

    终止条件也就是l>r,此时 r移动到了 

    (target - 1) / 2+1  l<r时区间和始终大于target
    此方法其实是对方法一的优化,因为方法一是没有考虑区间与区间的信息可以复用,只是单纯的枚举起点
    我们是利用了已知的信息来优化时间复杂度的 是考虑的实际情况 除非是sum不够大 我们才需要向右扩展 不然我们对于其他情况都是l++
    代码如下
     1 class Solution {
     2 public:
     3     vector<vector<int>> findContinuousSequence(int target) {
     4         vector<vector<int>>vec;
     5         vector<int> res;
     6         for (int l = 1, r = 2; l < r;){
     7             int sum = (l + r) * (r - l + 1) / 2;
     8             if (sum == target){
     9                 res.clear();
    10                 for (int i = l; i <= r; ++i) res.emplace_back(i);
    11                 vec.emplace_back(res);
    12                 l++;
    13             }
    14             else if (sum < target) r++;
    15             else l++;
    16         }
    17         return vec;
    18     }
    19 };

    而从另一个方面 在数学的角度

    既然是求和 还是等差数列求和 完全可以公式化 化为一个函数表达式 然后把一个np问题转化为p问题 把时间复杂度化为常数阶

    也就是是否存在一个正整数y(y>x)使得

     也就是:

    我们只需要求解这个一元二次方程就好(求根公式)

    但是解必须是整数

     也就是这两个条件

    class Solution {
    public:
        vector<vector<int>> findContinuousSequence(int target) {
            vector<vector<int>> vec;
            vector<int> res;
            int sum = 0, limit = (target - 1) / 2; // (target - 1) / 2 等效于 target / 2 下取整
            for (int x = 1; x <= limit; ++x) {
                long long delta = 1 - 4 * (x - 1ll * x * x - 2 * target);
                if (delta < 0) continue;
                int delta_sqrt = (int)sqrt(delta + 0.5);
                if (1ll * delta_sqrt * delta_sqrt == delta && (delta_sqrt - 1) % 2 == 0){
                    int y = (-1 + delta_sqrt) / 2; // 另一个解(-1-delta_sqrt)/2必然小于0,不用考虑
                    if (x < y) {
                        res.clear();
                        for (int i = x; i <= y; ++i) res.emplace_back(i);
                        vec.emplace_back(res);
                    }
                }
            }
            return vec;
        }
    };

    时间复杂度仅仅为枚举开头(到target-1/2)

    也就是O(target)

    空间复杂度为O(1)

  • 相关阅读:
    HEC-ResSim原文档
    水文模型大全
    用word发布博客到博客园
    给你的浏览器标题栏加上小图标
    系统性能调优必知必会学习
    系统性能调优必知必会学习
    容器化学习
    Redisson学习
    mysql回顾
    事物、源码学习,spring-tx
  • 原文地址:https://www.cnblogs.com/ranzhong/p/12427932.html
Copyright © 2020-2023  润新知