- Two Sum
Given an array of integers, find two numbers such that they add up to a specific target number.
The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are zero-based.
Example
Example1:
numbers=[2, 7, 11, 15], target=9
return [0, 1]
Example2:
numbers=[15, 2, 7, 11], target=9
return [1, 2]
Challenge
Either of the following solutions are acceptable:
O(n) Space, O(nlogn) Time
O(n) Space, O(n) Time
Notice
You may assume that each input would have exactly one solution
解法1:先排序后双指针
O(n) Space, O(nlogn) Time
这个是最经典的解法了。但我在网上看到关于其原理的真正阐述清楚的好像不多,因为细想一下就不清楚为什么这个解法不会漏掉解呢?比如说sum<target时,为什么是p1++,为什么不会是p2–呢?
下面我提一下我的证明。
假设p1,p2已经分别指向i, j,其中0<=i<j<n,我们用状态(i,j)来表示p1,p2所处的状态。这里的i,j是排序后的序号。现在sum(i,j)=nums[i]+num[j],根据我们的算法,(i,j)要么来自(i-1,j),要么来自(i, j+1)。
假定sum < target (sum>target的情况类似):
- 假设上个状态是(i-1,j),那么根据我们的算法,sum(i-1, j) = nums[i-1]+nums[j]<target。现在我们已经是(i,j),当然我们不需要再选择i-1了。我们也不能选择j-1,因为nums[j-1]<nums[j],所以sum(i, j-1)<target。那么我们为什么不选择j+1呢? 假设j+1还是小于n的话,因为(i,j)之前的某个状态必定是(?,j+1),这里0<=?<=i-1。之前可能有很多这样的状态(即p1=0…i, p2=j+1)。
现在我们假定这里面最近的那个状态是(x,j+1)。那么状态变化必定是下面的情况:
(x, j+1)=>(x,j)=>…=>(i,j).
sum(x,j+1)>target,so it moves to sum(x,j)
sum(x,j)<target, x < i, so it moves to sum(x-1, j, and so on, to sum(i,j) < target
sum(i,j+1) > sum(x,j+1) > target,故不用考虑(i,j+1)这种情况。
为什么不会是情况(x,j+1)=>(x-1,j+1)=>…=>(i,j),因为我们已经说了x是p2=j+1的最近的那个状态。
class Node {
public:
int value;
int index;
Node(int v = 0, int id = 0) : value(v), index(id) {}
bool operator < (const Node & node) const {
return value < node.value;
}
};
class Solution {
public:
/**
* @param numbers: An array of Integer
* @param target: target = numbers[index1] + numbers[index2]
* @return: [index1, index2] (index1 < index2)
*/
vector<int> twoSum(vector<int> & numbers, int target) {
int n = numbers.size();
if (n == 0) return {};
vector<Node> nodes(n);
for (int i = 0; i < n; ++i) {
nodes[i] = Node(numbers[i], i);
}
sort(nodes.begin(), nodes.end());
int p1 = 0, p2 = n - 1;
while(p1 < p2) {
int sum = nodes[p1].value + nodes[p2].value;
if (sum == target)
{
return {min(nodes[p1].index, nodes[p2].index), max(nodes[p1].index, nodes[p2].index)};
} else if (sum < target) {
p1++;
} else {
p2--;
}
}
return {};
}
};
另外,在网上跟高人讨论了一下为什么这种方法不会漏掉最优解。几个高手的论述如下(比我的说法更好):
我们把排好序的array,重复n次构成一个2D array。这样,p2起始点在左上角,往下方移动。p1起始点在左下角,往右方移动。这样,(p1, p2) 可能会出现在左上角的三角形的任何一处(不会超过主对角线,因为p1<p2)。我们要找这样的(p1,p2),满足其sum==target。
当sum > target时,说明(p1,p2)所在位置的右下方(包括本身位置)都不满足要求,那么p2必须往左移。因为根据单调性,对于每一个p1,我们要找使得nums[p1]+nums[p2]>=target的最小p2,那么随着p1变大,对应的p2一定变小!
假如p1.old对应p2.old,p1.new对应p2.new,那么p1.new>p1.pld,一定有p2.new<p2.old。即随着我们从小到大枚举p1,p2一定是逐渐变小的。同样,当我们从大到小枚举p2,p1一定时逐渐变大的。
同理,当sum<target时,p1往下移。
0 1 2 3 4
0 X
1 // row A
2 // row B
3
4
这个思想其实和"Search a 2D Matrix"是一个原理,唯一不同的地方是这里(p1,p2)只能在左上角矩阵里面变。
今天回顾了以下这个问题,发现上面讲的都不确切。实际上可以把思路画成下面的图,一开始(P1,P2)=(0,N-1),下面的蓝色箭头清楚的表示了(P1,P2)是怎么一步一步走到target的,注意没有回头路。

解法2:hashmap
O(n) Space, O(n) Time
class Solution {
public:
/**
* @param numbers: An array of Integer
* @param target: target = numbers[index1] + numbers[index2]
* @return: [index1, index2] (index1 < index2)
*/
vector<int> twoSum(vector<int> &numbers, int target) {
unordered_map<int, int> m; // <number, index>
vector<int> result;
int len = numbers.size();
for (int i = 0; i < len; ++i) {
if (m.find(target - numbers[i]) != m.end()) {
if (i > m[target - numbers[i]]) {
result.push_back(m[target - numbers[i]]);
result.push_back(i);
}
else {
result.push_back(i);
result.push_back(m[target - numbers[i]]);
}
return result;
}
m[numbers[i]] = i;
}
return result;
}
};
二刷:
class Solution {
public:
/**
* @param numbers: An array of Integer
* @param target: target = numbers[index1] + numbers[index2]
* @return: [index1 + 1, index2 + 1] (index1 < index2)
*/
vector<int> twoSum(vector<int> &numbers, int target) {
unordered_map<int, int> um; //value, index
for (int i = 0; i < numbers.size(); ++i) {
if (um.find(target - numbers[i]) != um.end()) {
return {um[target - numbers[i]], i};
} else {
um[numbers[i]] = i;
}
}
return {};
}
};
以上代码同步更新在
https://github.com/luqian2017/Algorithm
本文深入探讨了经典的两数之和问题,提供两种高效解决方案:一是利用排序和双指针技巧实现O(nlogn)时间复杂度;二是采用哈希表策略达到O(n)时间复杂度。通过实例和代码详细讲解了每种方法的原理和应用。
6884

被折叠的 条评论
为什么被折叠?



