标签:
# 地址
Q: https://leetcode.com/problems/two-sum/
A: https://github.com/mofadeyunduo/LeetCode/tree/master/1TwoSum
(希望各位多多支持本人刚刚建立的GitHub和博客,谢谢,有问题可以联系QQ:609092186或者留言,我尽快回复)
# 题目
1. Two Sum
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution.
Example:
Given nums = [2, 7, 11, 15], target = 9, Because nums[0] + nums[1] = 2 + 7 = 9, return [0, 1].
UPDATE (2016/2/13):
The return format had been changed to zero-based indices. Please read the above updated description carefully.
# 思路
无论三七二十一,上来先看看能不能暴力破解。
// brute force: time O(n^2) space O(1) result: 632ms public int[] TwoSum(int[] nums, int target) { for (int i = 0; i < nums.Length; i++) for (int j = i + 1; j < nums.Length; j++) if (nums[i] + nums[j] == target) return new int[] { i, j }; throw new Exception("no answer"); }
对整个数组的两个for循环注定了时间复杂度为O(n^2),测试出来的结果为623ms。
这个时候可以仔细思考一下慢在哪里了。假设nums = {1,7,4},target = 5,显然answer = {0, 2}。用暴力破解方法的时候,计算了1 + 7, 1 + 4, 7 + 4。这里面和结果无关的计算有1 + 7和 7 + 4。1 + 7 比 5(target)大,我们需要一个比7更小的数字,但我们也不希望太小。这一系列行为,就让人很容易就想到利用排序去寻找。
我们可以通过排序来寻找比7更小且离他最近的数字,在这里是4,很巧,配对成功了。如果不成功的话,有可能这两个数字之和还是比target大,那就再找一个除了4,离7接近的,继续配对。也有可能比target小,那说明对于1,我们找不到这样一个配对组合(请注意这里利用的是有序数组)。
// sort first, get left part and right part tricky, find left index and right index // time: O(nlgn) space: O(n) 500ms // reference: https://github.com/yangliguang yangliguang public int[] TwoSum(int[] nums, int target) { int[] copy = new int[nums.Length]; Array.Copy(nums, copy, nums.Length); Array.Sort(copy); // O(nlgn) int start = 0, end = copy.Length - 1, left = 0, right = 0; while (start < end) // O(n) { if (copy[start] + copy[end] == target) { left = copy[start]; right = copy[end]; break; } else if (copy[start] + copy[end] < target) { start++; // very tricky } else { end--; // very tricky } } start = end = -1; for (int i = 0; i < nums.Length; i++) { if (nums[i] == left) { start = i; break; } } for (int i = nums.Length - 1; i >= 0; i--) { if (nums[i] == right) { end = i; break; } } if (start > end) return new int[] { end, start }; else return new int[] { start, end }; }
可以看到时间复杂度为O(nlgn),还是比较理想的复杂度。具体测试出来的时间也是比较理想的,500ms(与下面的O(n)时间只差了4ms,原因可能是用HashTable实现,在时间复杂度方面,常数因子比较大)。
这个时候我们会想,能不能找到一个O(n)的算法?有。对于这个例子,nums = {1,7,4},target = 5,我们一般人会这么做:嗯...先拿出1,target是5啊,那我只要找到4就可以啦!其实可以进一步想,我怎么去找4呢?如果可以按照某种规则,看一看4存不存在就好了!
这种规则可以有吗?可以!就是桶(bucket)。这里正好是数字,直接放进数组对应下标的桶。这个例子中,我们先读取到1,让数组对应下标为1增加1,表明桶1里有1个1(似乎有点绕)。
我们每次读取一个元素,计算(target-该元素)的值,然后查看对应的桶,如果桶里有东西,恭喜你,找到答案了。没有的话,先把元素存入桶中,等待后面的元素配对(只有一个幸运儿可以配对成功)。
实际上,上面的思路我们还需要修改一下。别灰心。第一点就是,因为这个题目是要求下标的,而我们把元素放进桶的时候,并没有附带下标。这个时候可以用一种数组结构来附带下标,那就是映射(俗称键值对)(Map)。第二点就是,如果元素大小的范围比较大,比如nums= {1, 100000000},元素范围[1, 100000000],这个时候直接放进数组对应下标的桶就不好用了(int[]的长度到不了那么大)。所以这种放进数组的方式需要改变。显然,有关“放入”问题,Hash函数可以很好的帮我们做到这一点。
综合上述,我们选用Hashtable来完美的完成此题。
# 解决(Hashtable)
// right hash: time O(n) (degenerate when collison occured) ususlly O(1) (insert and search) space: O(n) result: 496ms public int[] TwoSum(int[] nums, int target) { int[] result = new int[2]; System.Collections.Hashtable table = new System.Collections.Hashtable(nums.Length); // use empty constructor, result: 544 ms for (int i = 0; i < nums.Length; i++) { int another = target - nums[i]; if (table.ContainsKey(another)) { int anotherIndex = (int)table[another]; result[0] = anotherIndex > i ? i : anotherIndex; result[1] = anotherIndex < i ? i : anotherIndex; return result; } else { if (!table.Contains(nums[i])) table.Add(nums[i], i); } } throw new Exception("no answer"); }
时间复杂度O(n),空间复杂度O(n),运行时间496ms。
# 题外话
这也是一段用Hashtable思想写的一段代码,我把它标记为bad hash,各位可以仔细推敲一下这里的问题(参考一下下面的测试用例)。
// bad hash: ignore duplication public int[] TwoSum(int[] nums, int target) { System.Collections.Hashtable table = new System.Collections.Hashtable(nums.Length); for (int i = 0; i < nums.Length; i++) table.Add(nums[i], i); // key can have duplications, so hash like this is a bad idea for (int i = 0; i < table.Count; i++) { int another = target - nums[i]; if (table.ContainsKey(another) && another != nums[i]) return new int[] { i, (int)table[another] }; } throw new Exception("no answer"); }
# 测试用例
static void Main(string[] args) { _1TwoSum solution = new _1TwoSum(); Debug.Assert(Test.match(solution.TwoSum(new int[] { 3, 2, 4 }, 6), new int[] { 1, 2 }), "wrong 1"); Debug.Assert(Test.match(solution.TwoSum(new int[] { 5, 3, 2, 4 }, 6), new int[] { 2, 3 }), "wrong 2"); Debug.Assert(Test.match(solution.TwoSum(new int[] { 2, 4 }, 6), new int[] { 0, 1 }), "wrong 3"); Debug.Assert(Test.match(solution.TwoSum(new int[] { 0, 4, 3, 0 }, 0), new int[] { 0, 3 }), "wrong 4"); // duplication is result int[] test = { 230, 863, 916, 585, 981, 404, 316, 785, 88, 12, 70, 435, 384, 778, 887, 755, 740, 337, 86, 92, 325, 422, 815, 650, 920, 125, 277, 336, 221, 847, 168, 23, 677, 61, 400, 136, 874, 363, 394, 199, 863, 997, 794, 587, 124, 321, 212, 957, 764, 173, 314, 422, 927, 783, 930, 282, 306, 506, 44, 926, 691, 568, 68, 730, 933, 737, 531, 180, 414, 751, 28, 546, 60, 371, 493, 370, 527, 387, 43, 541, 13, 457, 328, 227, 652, 365, 430, 803, 59, 858, 538, 427, 583, 368, 375, 173, 809, 896, 370, 789 }; int target = 542; Debug.Assert(Test.match(solution.TwoSum(test, target), new int[] { 28, 45 }), "wrong 5"); // duplication isn‘t result Debug.Assert(Test.match(solution.TwoSum(new int[] { 150, 24, 79, 50, 88, 345, 3 }, 200), new int[] { 0, 3 }), "wrong 6"); }
class Test { public static bool match(Array result, Array expect) { Debug.Assert(result.Length == expect.Length, "result‘s length isn‘t equal to expect‘s length"); for (int i = 0; i < result.Length; i++) if (!result.GetValue(i).Equals(expect.GetValue(i))) return false; return true; } }
标签:
原文地址:http://www.cnblogs.com/Piers/p/5727852.html