您的位置:首页 > 其它

LeetCode 1.Two Sum

2016-03-10 12:07 295 查看
这个题目意思很简单,给你一个整数数组和一个目标值,问你在数组里是否存在两个数字加和等于目标值,输出两个数字在数组中的位置。数据保证唯一解。

看到这个题目最先想到的应该是一个二重循环,枚举这两个数字来找到答案。这种方法的复杂度是O(n ^ 2),特别当数据量较大的时候,运行非常慢。如果问题规模较小,为了编程简单可以这么做。但用在这里就会超时。

这里给出两种比较好的做法,一种是利用哈希表实现,复杂度O(n),是比较典型的用空间换时间的方法。另一种方法是先排序(很多算法问题再不知道怎么做的时候完全可以先排个序再考虑方案,数列有序之后会带来许多便利),然后通过两个游标查找答案,复杂度O(nlogn)。不过这个问题要求的是数组下标值,所以在排序的时候必须连同下标一起进行,需要重写compare的函数。这里把它列出来主要是我特别喜欢这种游标查找的方法,单看查找,它的复杂度为O(n),这种利用游标处理的思想非常重要,在许多算法问题里都会利用这种思想来进行优化。还有之前看过别人的代码有先排序然后用BinarySearch的方法实现,对num[i]用二分的方法找target
- num[i]是否存在,查找复杂度为O(nlogn)。因为前面排序已经是O(nlogn)的了,就复杂度而言其实不用优化到O(n),二分法本身实现也很简单。但本着学习算法的目的还是应该不同的方法多试一试。

一、HASH表实现

这种方法思路比较简单,不知道HASH表的可以百度学习下,明白了HASH表保证瞬间就会了。而且现在的HASH表都是直接调用函数就好了,非常方便,也不需要考虑冲突了该怎么办,直接用就好。最近一直在学习java,这里给出java实现(是不是特别短~)。

import java.util.Hashtable;
public class Solution {
public static int[] twoSum(int[] nums, int target){
Hashtable<Integer,Integer> hash = new Hashtable<Integer,Integer>();
int[] rst = {0,0};
for(int i = 0; i < nums.length; i++){
int tmp = target - nums[i];
if(hash.get(tmp)!=null){
rst[1] = i;
rst[0] = hash.get(tmp);
break;
}
hash.put(nums[i], i);
}
return rst;
}
}


二、排序 + 游标法

首先要先排序,但是这道题因为要知道每个数字原来对应的下标,只好写了个class然后重写了compare,特别蛋疼,但这部分不是我们要讨论的重点,暂时放过。我们这里用的是增序排列,就是从小到大排。反过来也行,算法稍微做点小修改就好。

开始介绍游标法。首先我们要使用两个变量 l 和 r 代表两个游标,开始时令 l = 0, r = n - 1,就是一个指在头,一个指在尾。每一次我就判断 num[l] 和 num[r] 相加是否满足条件。如果num[l] + num[r] 相加大于target,这组配对不满足条件,那么令 r--,因为这个时候 num[l] 和 num[r - 1] 也是有可能配对的;如果等于target,自然直接找到答案退出啦;如果小于target,就让
l++,r 不变( r 没变这一点很重要,直接决定了它的复杂度是O(n) 而不是 O(n ^ 2))。因为这个时候 num[l] 的所有配对情况我都考虑过了,这一组 num[l] + num[r] 小于target不满足目标,那么 num[l] + num[r - 1]自然也不满足(num[l] + num[r] 总是大于等于 num[l] + num[r - 1] 的,因为最初我们是增序排列,必定也小于target)。

说清楚了为什么让 l++,再来看为什么这个时候 r 不变。再回到 l++ 的条件,是 num[l] + num[r] < target,从这里面还能得到一个结论,num[l] + num[r + 1] > target(这里不讨论等于的情况,因为等于就表示找到了答案不再需要继续执行了)。再回到 l++ 之后,那个结论现在成了 num[l - 1] + num[ r + 1] > target。好,因为 num[l] > num[l
- 1],所以 num[l] + num[r + 1] > target。这个结论就说明了为什么 r 不变,因为比这个 r 大的肯定不满足条件。

对每个 l 都执行这样的操作,如果num[l] + num[r] > target, r-- 否则 l++。如果发现 l >= r,说明所有的情况都找了一遍,不需要再继续。那些 l > r 的组合一定在之前讨论过。比方说 l = 5,r = 3的情况其实在 l = 3,r = 5的时候就讨论了。

不知道听明白没,有没讲明白的地方可以看看代码对照一下。

最后我们来证明下为什么复杂度是O(n)。可能好多人看懂了方法但是会觉得复杂度明明是O(n ^ 2)。因为你用到了两个游标。实际上复杂度证明也非常简单,每次比较后,要么 l++, 要么 r--,总有一个在变化并且两个数越来越接近,这就是为什么我们强调当时 r 不能发生变化,你要是当时把 r 重新赋值成 n - 1,这就是O(n ^ 2)的了。很明显最多比较 n 次两个游标就会相遇然后算法结束,故复杂度为 O(n)。

给出源代码如下:

public class Solution {
static class Node implements Comparable<Node>{
int val,index;
public Node(int v,int id){
val = v;
index = id;
}
@Override
public int compareTo(Node o) {
return this.val - o.val;
}
}

public int[] twoSum(int[] nums, int target) {
int[] rst = new int[2];
Node[] nodes = new Node[nums.length];
for(int i=0; i<nodes.length; i++){
nodes[i] = new Node(nums[i], i);
}
Arrays.sort(nodes);
int l = 0, r = nums.length-1;
for (; l < r ; l++)
{
while (nodes[l].val + nodes[r].val > target && l < r) r--;
if (nodes[l].val + nodes[r].val == target)
{
rst[0] = nodes[l].index; rst[1] =nodes[r].index ; break;
}
}
Arrays.sort(rst);
return rst;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: