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实现(是不是特别短~)。
二、排序 + 游标法
首先要先排序,但是这道题因为要知道每个数字原来对应的下标,只好写了个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)。
给出源代码如下:
看到这个题目最先想到的应该是一个二重循环,枚举这两个数字来找到答案。这种方法的复杂度是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; } }
相关文章推荐
- Python 学习之中的一个:在Mac OS X下基于Sublime Text搭建开发平台包括numpy,scipy
- linux系统下c程序分多文件实现
- idea快捷键
- 极客范:如何使用 Cloud Insight 来监控闭路电视?
- 【Python】用递归函数简单实现汉诺塔的移动
- SpringMVC中四个基本注解
- 从开发者角度解析 Android N 新特性!
- [斜率优化小结]
- IOS之NSCache解析
- 153.View the Exhibits and examine the structures of the PRODUCTS and SALES tables.
- 开发错误记录2 .MainActivity (server)' ~ Channel is unrecoverably broken and will be disposed!
- LeetCode 7. Reverse Integer
- 奇怪的分式
- Kotlin学习笔记——类和对象
- POJ 2342 Anniversary party (树形dp入门)
- 进程和线程的区别
- 开发错误记录1:解决:Only the original thread that created a view hierarchy can touch its views.
- iOS-----Crash文件分析(一)
- Codeforces Round #274 (Div. 2) E. Riding in a Lift(DP)
- Codeforces Round #274 (Div. 2) E. Riding in a Lift(DP)