LintCode第56题:两数之和
题目
题目分析
常见的思路是:两层for循环,任意两个数求其和,判断是否等于给定的target。但这样太慢,需要O(n^2)的时间,O(1)的额外空间。可以反过来思考,假如当前选择了一个数字a,那么为了满足条件,另一个数字b必选满足:b=target-a,即在数组中寻找是否存在b.
如何快速寻找数组中是否存在一个数字b?假如数组是有序的,可以使用二分查找方法,其查找时间复杂度是O(logn)。然而题目并没有给定这个条件。如果对数组排序,首先就要O(nlogn)的时间进行排序,并且排序后,数字的原始下标也要保存,显然需要O(nlogn)的时间以及O(n)的空间,并不是最好的方法。
如何对一个数组进行快速查找一个元素?算法中提供了一种方法--哈希(hash),即对数组中的每个元素按照某种方法(hash function)计算其“唯一值”Id(称为哈希值),存储在新的数组A中(一般称为哈希数组),并且该数组的下标就是这个“唯一”值。那么如果访问A[id]存在,则这个元素存在,否则,原始数组中不存在该元素。由于数组是顺序存储的支持随机访问,所以查找一个元素是否在数组中,只需要O(1)的时间,但是在初始化哈希数组时,需要O(n)的时间和O(n)的空间。对于某些特定应用中,需要快速的时间,而对空间要求不苛刻时,哈希数组是一个非常好的方法。为了能够满足各种应用场景,又衍生出容量大小可以动态增长的哈希集合(hash set)、哈希映射(hash map),STL提供了关于哈希的两个类:unordered_set和unordered_map,前者只存储元素,后者可以再增加额外的标志信息。详细的内容,请自行补充。
由于构造的哈希数组,其元素的下标已经改变了,所以需要额外存储元素原始的下标,因此此题使用unordered_map<int,int>,其存储的内容为<元素值,元素原始下标>,详细代码:
class Solution {
public:
/*
* @param numbers : An array of Integer
* @param target : target = numbers[index1] + numbers[index2]
* @return : [index1+1, index2+1] (index1 < index2)
*/
/* Tips: find any pair is ok not all pairs.
* using hash map to store the num value and index
* notice: if the target is 4 and the answer expection num 2 + 2,
* only the one num 2 is stored in hash map, but also work ok!
* because must have the other num 2 is not in hash map!
* */
vector<int> twoSum(vector<int> &nums, int target) {
// write your code here
vector<int> v(2,0);
unordered_map<int,int> hash;// val+id
// we can search num and insert it to hash map at same time
// and current num must be not in hash map
for(int i=nums.size(); i--; hash[nums[i]]=i){
if (hash.find(target-nums[i]) == hash.end()) continue;
v[0] = 1 + i; // the index from 1 to n not 0 to n-1
v[1] = 1 + hash[target-nums[i]];
return v;
}
return v; // no answer return {0,0}
}
};
需要注意的是:哈希无法存储相同元素,因为相同元素有相同的哈希值。如果数组{2,5,6},待求值target=4,没有解;而数组{2,2,5,6},target=4则有解。如何处理这种情况?可以反向遍历,初始hash为空,逐渐将已经遍历过的元素加入到哈希中。
Java实现
public class Solution {
/*
* @param numbers: An array of Integer
* @param target: target = numbers[index1] + numbers[index2]
* @return: [index1 + 1, index2 + 1] (index1 < index2)
*/
public int[] twoSum(int[] numbers, int target) {
// write your code here
int[] result = new int[2];
Map<Integer,Integer> map = new HashMap<>();
for(int i=0;i<numbers.length;i++)
{
if(map.containsKey(target-numbers[i]))
{
result[0]=map.get(target-numbers[i])+1;
result[1]=i+1;
return result;
}
else
{
map.put(numbers[i],i);
}
}
return result;
}
}