剑指 Offer 57. 和为s的两个数字

剑指 Offer 57. 和为s的两个数字 - 力扣(LeetCode) (leetcode-cn.com)

目录

方案1:基本方法

思路

复杂度

代码

运行结果

 

方案2:对基本方法的改进

思路

复杂度

代码

 运行结果

方案3:二分查找

思路

复杂度

代码

运行结果

 方案4:从两边,向中间

思路

代码

正确性证明

直接证明

反证法

情况1:x[i - θ1] + x[j - θ2] == target.

分析1

情况2:x[i + θ1] + x[j + θ2] == target

分析2

复杂度

运行结果


方案1:基本方法

思路

首先把数组所有元素放进哈希表里,然后遍历哈希表,对于每个元素x,查找s-x是否也在哈希表。

复杂度

时间复杂度为O(N),空间复杂度为O(N).

代码

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_set<int> iset(nums.begin(), nums.end());
        for (auto x : iset) {
            int y = target - x;
            if (x != y && iset.find(y) != iset.end()) return {x, y};
        }
        return {};
    }
};

运行结果

这种方案使用STL的unordered_set,时间常数比较大,当测试用例规模比较小的时候,在性能上体现不出优势。

 

方案2:对基本方法的改进

思路

遍历数组,对于数组的每个元素x,首先查找x是否在哈希表里,如果x不在哈希表里,那么把s - x放进哈希表里;直到在哈希表中找到某个x。

复杂度

时间复杂度低于O(N),空间复杂度低于O(N),二者均取决于测试用例。

代码

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int s) {
        unordered_set<int> iset;
        for (auto x : nums) {
            int y = s - x;
            if (y != x && iset.find(x) != iset.end()) return {x, y};
            else iset.emplace(y);
        }
        return {};
    }
};

 运行结果

 同样的问题:维护STL哈希表的时间常数较大,在数据量较小的时候表现不出性能优势。

方案3:二分查找

思路

遍历递增数列:x[1], x[2], ……, x[n],对于某个x[i],设y[i] = s - x[i],如果y[i] > x[n],那么进行下一个;如果y[i] <= x[n],那么在x[i+1]~x[n]中查找y[i]。

复杂度

我们可以构造一个数列和一个和,从第一个元素开始,每次都需要查找,直到最后才找到,这时有最坏时间复杂度:O( log2(n) + log2(n-1) + …… + log2(1) = log2(n!)  )<  O( n*log2(n) )

空间复杂度为O(1).

代码

class Solution {
public:
    vector<int> twoSum(vector<int>& x, int s) {
        vector<int> ans;
        int N = x.size();
        for (int i = 0; i != N; ++i) {
            int y = s - x[i];
            if (y <= x[N - 1]) {
                int beg = i + 1, end = N;
                while (beg != end) {
                    int mid = beg + (end - beg) / 2;
                    if (x[mid] == y) return { x[i], y };
                    x[mid] > y ? end = mid : beg = mid + 1;
                }
            }
            //if (y <= x[N - 1] && Search(x, i + 1, N, y)) return { x[i], y };
        }
        return {};
    }
    /*
    bool Search(vector<int>& x, int beg, int end, int target) {
        while (beg != end) {
            int mid = beg + (end - beg) / 2;
            if (x[mid] == target) return true;
            x[mid] > target ? end = mid : beg = mid + 1;
        }
        return false;
    }
    */
};

运行结果

 

 方案4:从两边,向中间

思路

数列:x[1], x[2], ……, x[n]。iter 从前向后,riter 从后向前,设s = x[i] + x[j],如果s < target,那么正向迭代器后移,即++iter,否则反向迭代器前移,即--riter,直到s == target。

代码

class Solution {
public:
    vector<int> twoSum(vector<int>& x, int target) {
        int i = 0, j = x.size() - 1;
        while (i < j) {
            int s = x[i] + x[j];
            if (s == target) return { x[i], x[j] };
            s < target ? ++i : --j;
        }
        return {};
    }
};

正确性证明

直接证明

从iter = 1,riter = n开始,始终保持iter < riter.

  1. 如果s(iter, riter) = x[iter] + x[riter] < target的话,那么从s(iter, iter+1)到s(iter, riter-1)所有的这些和均小于target,因此可以排除它们—— ++iter.
  2. 如果s(iter, riter) = x[iter] + x[riter] > target的话,那么从s(iter + 1, riter)到s(riter-1, riter)所有的这些和均大于target,因此可以排除它们—— --riter.

因此,我们可以保证从第一步开始,每一步排除的都是错误答案,走一遍完成的流程其实就是排除所有错误答案的过程;因为我们每一步都是正确的,所以整个流程是正确的。

反证法

假设当前正进行到 i 和 j 。如果要错过正确答案,只能有两种情况:

  • 情况1:x[i - θ1] + x[j - θ2] == target.

 此时:x[i] > x[i - θ1],x[j] > x[j - θ2]        x[i] + x[j] > target

由于 i 只能增大,不能减小,因此而错过正确答案。

分析1

在正向迭代器iter达到 i 之前,必然会经过 i - θ1,此时反向迭代器riter在 j - θ2之后,x[riter] > x[j-θ2],则x[i - θ1] + x[riter] > target,根据规则,此时应该反向迭代器前移,即--riter,正向迭代器iter不可能达到 i。

  • 情况2:x[i + θ1] + x[j + θ2] == target

此时x[i] < x[i + θ1],x[j] < x[j + θ2]        x[i] + x[j] < target

由于 j 只能减小,不能增大,因此而错过正确答案。

分析2

 在反向迭代器riter达到 j 之前,必然会经过j + θ2,此时正向迭代器iter在i + θ1之前,x[iter] < x[i + θ1],则x[iter] + x[j + θ2] < target,根据规则,此时应该正向迭代器后移,即++iter,反向迭代器riter不可能达到 j 。

复杂度

时间复杂度O(N),空间复杂度O(1)。

运行结果

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值