[剑指-Offer] 57. 和为s的两个数字及II. 和为s的连续正数序列(数学、二分法、滑动窗口、巧妙解法)

1. 题目来源

链接:和为s的两个数字
链接:II. 和为s的连续正数序列
来源:LeetCode——《剑指-Offer》专项

2. 题目说明

在这里插入图片描述
在这里插入图片描述

3. 题目解析 — 和为s的两个数字

方法一:二分法+常规解法

看到有序数组的问题,优先考虑二分法。对于这个问题而言,需要顺序遍历数组,在以二分查找的形式对当前位右边未遍历的所有数字进行二分查找即可,故时间复杂度为 O ( n l o g n ) O(nlogn) 。有以下几点需要注意:

  • 二分查找函数的数组参数一定要传引用,否则 TLE
  • 查到存在后,直接 return 两个数字即可,若 for 循环遍历完,则 TLE

关于这个还有些针对大数的优化点,但都很片面,优化意义不大,可自行研究。

参见代码如下:

// 执行用时 :244 ms, 在所有 C++ 提交中击败了71.63%的用户
// 内存消耗 :102.4 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
    bool dichotomy(vector<int>& nums, int left, int right, int tmp) {
        while (left <= right) {
            int mid = (left + right) >> 1;
            if (nums[mid] == tmp) return true;
            else if (nums[mid] < tmp) left = mid + 1;
            else right = mid - 1;
        }
        return false;
    }

    vector<int> twoSum(vector<int>& nums, int target) {
        for (int i = 0; i < nums.size(); ++i) {
            int tmp = target - nums[i];
            if (dichotomy(nums, i + 1, nums.size() - 1, tmp)) return {nums[i], tmp};
        }
        return {};
    }
};

方法二:双指针+巧妙解法

我是被有序数组蒙蔽了双眼,直接撸二分法去了。双指针,遍历一遍 O ( n ) O(n) 它不香吗?但是这执行用时就离谱…

参加代码如下:

// 执行用时 :240 ms, 在所有 C++ 提交中击败了77.14%的用户
// 内存消耗 :102.5 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while (left < right) {
            if (nums[left] + nums[right] > target) --right;
            else if (nums[left] + nums[right] < target) ++left;
            else return {nums[left], nums[right]};
        }
        return {};
    }
};

4. 题目解析 — II. 和为s的连续正数序列

方法一:滑动窗口+巧妙解法

有了解决前面问题的经验,我们也考虑用两个数 leftright 分别表示序列的最小值和最大值。思路如下:

  • 首先把 left 初始化为 1,right 初始化为 2
  • 如果从leftright 的序列的和大于 target,可以从序列中去掉较小的值,也就是增大 left 的值
  • leftright 的序列的和小于 target,可以增大 right,让这个序列包含更多的数字
  • 因为这个序列至少要有两个数字,一直增加 left(1+target)/2 为止

《剑指-Offer》上的例子能帮助形象理解这个过程:
在这里插入图片描述
在这里插入图片描述

// 执行用时 :4 ms, 在所有 C++ 提交中击败了87.69%的用户
// 内存消耗 :8.2 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        vector<vector<int>> res;
        int left = 1, right = 2;
        int mid = (1 + target) / 2, cnt = left + right;
        while (left < mid) {
            if (cnt == target) res.push_back(help(left, right));
            while (cnt > target and left < mid) {
                cnt -= left;
                ++left;
                if (cnt == target) res.push_back(help(left, right));
            }
            ++right;
            cnt += right;
        }
        return res;
    }

    vector<int> help(int left, int right) {
        vector<int> tmp;
        for (int i = left; i <= right; ++i) tmp.push_back(i);
        return tmp; 
    }
};

5. 拓展学习

题解里的数学解法,非常适合拓展,当兴趣看看活跃下思维就行了【详解】滑动窗口法 -> 求根法 -> 间隔法,一山还有一山高 这也太秀了吧…

发布了343 篇原创文章 · 获赞 197 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/yl_puyu/article/details/104766149