LeetCode Two Sum 新解

Two Sum问题是非常经典的问题了。是leetcode第一题,但很可惜,AC率并不高。如果没记错的话,在算法导论前三章的某个习题里就有这个问题的解答。双循环暴力解法没有探讨价值,本文提出两种解法,并探讨这两种解法的对比。

解法一:时间复杂度T=O(nlogn),空间复杂度C=O(n)

一波斩:排序两端走。

解释:现在假设一个数组A={ a1,a2,...,an }已经从小到大排好序了,问数组中是否存在两个数 ai,aj ,数之和为target。那我们可以做两个指针,一个指向数组最左端,一个指向数组最右端。

  • 若指针指向的两个数之和等于target,则这就是我们要的结果;
  • 若指针指向的两个数之和小于target,则说明,这两个数小了,那么把左边的指针left右移一位,就得到离原left最近且稍微大一点的数,两数之和也相应的大了一点;
  • 若指针指向的两个数之和大于target,则说明,这两个数大了,那么把右边的指针right左移一位,就得到离原right最近且稍微小一点的数,两数之和也相应的小了一点;
  • 当左边的指针left遇到右边的指针right的时候,则说明数组被访问完了,不需要继续往下走了。

有人会想,按上述策略移动指针,一定会同时碰到 ai aj 吗?证明很简单:不妨假设 ai<=aj ,这样一来,就是说left会遇到 ai right会遇到 aj 。首先,leftright是会访问完数组中的每一个元素的,所以一定存在某一个时刻,left ai 上,或者right aj 上。再次假设,第一次遇到的是 ai 。也就说是left ai 上,right aj 的右边,此时两数之和肯定大于target,所以right会一直移动,直到 aj 。这样leftright在某一时刻,就同时在 ai aj 上了。

那么,该方案能找全所有符合要求的数对吗?答案是不能的。举个简单的例子,target = 6, A = { 2, 2, 3, 4, 4},符合要求的一共有4对数,但该方案最多只能找出3对。所以该解法只能验证,不能找全。一个简单的不增加复杂度的改进是unique + 计数 + 1个特判,这就超出本文讨论范围了,不再详述。

再看LeetCode上这题,1. 数组没有排序;2. 输出是下标。那么我们只需要用任意一种O(nlogn)的算法来给数组排序,排序的同时带上下标即可。这里我们巧妙的用pair<int, int>类型,省去了自己定义结构体、重载函数和稳定排序的工作量。

该解法的代码如下:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector< pair<int, int> > num_with_index;
        for(int i = 0; i < nums.size(); ++i)
            num_with_index.push_back(make_pair(nums[i], i + 1));
        vector<int> ret;
        sort(num_with_index.begin(), num_with_index.end());
        int left = 0, right = num_with_index.size() - 1;
        while(left < right){
            if(num_with_index[left].first + num_with_index[right].first == target){
                ret.push_back(num_with_index[left].second);
                ret.push_back(num_with_index[right].second);
                if(ret[0] > ret[1]) swap(ret[0], ret[1]);
                break;
            }else if(num_with_index[left].first + num_with_index[right].first < target)
                ++left;
            else
                --right;
        }
        return ret;
    }
};

解法二:时间复杂度T=O(n),空间复杂度C=O(n),但C>>n。

一波斩:利用Hashtable查询时间复杂度是O(1)的bug技能,不断地在Hashtable里查有没有我的另一半。
解释:首先将数组的n个元素全部塞进一张hashtable里。然后依次访问数组中的元素,并查询另一半target-nums[i]是不是也在hashtable里。然而,这里需要一个特判:题目规定,符合要求的数对有且只有一对,这就是说,若 ai!=aj ,则 aiaj 都不可能重复;否则 ai==aj==target/2 。因此,在hashtable中查自己时要注意。

最后,C++中hashtable的实现是unordered_map/unordered_set系列,题目要求输出下表,并且元素可重复,所以我们使用unordered_multimap来实现。还需要注意的是,unordered_multimap不保证键有序,也不保证值有序。代码如下:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> ret;
        unordered_multimap<int, int> hashcode;
        for(int i = 0; i < nums.size(); ++i)
            hashcode.insert(make_pair(nums[i], i + 1));
        if(target % 2 == 0 && hashcode.count(target / 2) == 2){
            unordered_multimap<int, int>::iterator it = hashcode.find(target / 2);
            for(int i = 0; i < 2; ++i, ++it)
                ret.push_back(it -> second);
        }else{
            for(int i = 0; i < nums.size(); ++i)
                if(nums[i] * 2 != target && hashcode.count(target - nums[i])){
                    ret.push_back(i + 1);
                    unordered_multimap<int, int>::iterator it = hashcode.find(target - nums[i]);
                    ret.push_back(it -> second);
                    break;
                }
        }
        if(ret[0] > ret[1]) swap(ret[0], ret[1]);
        return ret;
    }
};

排序真的比hashtable慢吗?

我经常遇到有人教育我,hashtable复杂度是O(n),sort复杂度是O(nlogn),因此hashtable更优,还试图让我信服。

我从不否认hashtable的复杂度比sort低一个量级,但这并不代表hashtable在实际中比sort更优。到底谁占优,取决于数据的分布。当数据量不多的情况下,数据变化范围很小或者hash算法能够很好的将数据分开的话,hashtable是占优的,hashtable的常数c基本等同于hash算法的复杂度。但显然,当数据量增多时,hashtable的冲突将变得更多,hashtable的常数c变成hash算法复杂度和冲突算法复杂度的加权和,这就会使hashtable的性能锐减。而基于比较的排序算法相对来说是比较稳定的,常数c处于一个固定的值,而复杂度量级上的这个logn这个数究竟有多大呢?当n等于100万的时候,logn等于20。

实际情况如何?这是我用解法一和解法二提交的反馈:
feedback1
可以看出,这两个解法耗时基本在同一个量级上,甚至在这里解法二更加耗时(当然这里面还有很多不稳定因素,比较合适的方法是提交100次求个平均数什么的)。

所以我更加倾向于,在一般情况下,二者的性能差不多这个说法,否则C++ STL里的sort为何不用类似hash的基数排序或者桶排序来实现得了。但如果你在面试,口才又不怎么好,请务必说hashtable更优o(╯□╰)o,世界就是这么讨厌。

終わりです。Thanks.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值