两数之和
原题链接:https://leetcode.cn/problems/two-sum/
题目描述
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
提示:
- 2 <= nums.length <= 104
- -109 <= nums[i] <= 109
- -109 <= target <= 109
- 只会存在一个有效答案
方法一:暴力破解
第一种方法:循环遍历
题目要求在数组中找两个数,这两个数不相等,并且和为目标值。
那么思路就是设置两个循环,从原数组中遍历,判断和是否等于目标值,并判断两数是否相异。
应注意的是,返回的是该元素在数组中的下标,而非元素本身。
这个方法思路很简单,直接上代码
为什么标题叫暴力破解呢?因为从头到尾都循环一遍了,暴力破解就是把每个可能的值都举出来判断一下,比较费时。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
for (int index1 = 0; index1 < nums.size(); index1++)
{
for (int index2 = index1 + 1; index2 < nums.size(); index2++)
{
if (nums[index1] + nums[index2] == target)
{
return { index1,index2 };
}
}
}
return {};
}
};
运行结果
57 / 57 个通过测试用例
执行用时: 296 ms
内存消耗: 9.9 MB
可见,暴力破解的方法,比较费时
方法二:哈希表
哈希表就是一个map容器,一次能存储一个对组,对组的第一个元素为key值,用于索引,第二个元素为value值,是实值。通过.find();
的方法,能够查找map容器中的key值,返回一个迭代器,利用间接访问操作符何以获取到实值。
观察暴力破解的代码,可以发现,每次执行外层的for循环,内层的for循环都会从头到尾执行一遍,执行是为了获取符合条件的数据,但该数据被多次取出。
因此,我们需要一种更优秀的方法,能够快速寻找数组中是否存在目标元素。如果存在,我们需要找出它的索引。
使用哈希表,可以将寻找index2的时间复杂度降低到从O(N)降低到O(1)。
如果事先将数据存到map中,检索到符合条件的元素提前退出,免去执行多轮不必要的查找。
也许可以优化时间。
上代码
也可以使用orderedset
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
map<int, int>mp;
for (int i = 0; i < nums.size(); i++)
{
if (mp.find(target - nums[i]) != mp.end())
{
return { i,mp.find(target - nums[i])->second };
}
mp.insert(make_pair(nums[i], i));
}
return {};
}
};
运行结果
57 / 57 个通过测试用例
执行用时: 8 ms
内存消耗: 11 MB
如图,使用哈希表后,时间大幅降低。
三数之和
原题链接:https://leetcode.cn/problems/3sum/
题目描述
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。
请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
提示:
- 3 <= nums.length <= 3000
- -105 <= nums[i] <= 105
三个指针
先按从小到大排序(从大到小也可以,下面以从小到大排序为例)。
创建三个变量,用于标记三个位置:
- NOW,随循环移动,表示当前循环的位置。
- LEFT,每轮循环重定向到NOW右侧,根据和的情况向右修正位置。
- RIGHT,每轮循环开始时重定向到数组最右端,根据和的情况想左修正位置。
判断三个指针指向的值的和与零的关系:
- 如果和小于零,说明值偏小,LEFT右移
- 如果和等于零,说明值偏大,RIGHT左移
- 如果和等于零,添加到结果数组中,结果数组是一个存储整型数组的数组
代码第一版
听着不难,来写一下吧
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int left(0), right(0), now(0);
vector<vector<int>>result;
sort(nums.begin(), nums.end());
for (; now < nums.size() - 1; now++)
{
left = now++;
right = nums.size() - 1;
while (nums[left] + nums[right] + nums[now] != 0 && left < right)
{
if (nums[left] + nums[right] + nums[now] < 0)
left++;
else
right--;
}
if (nums[left] + nums[right] + nums[now] == 0)
result.push_back({ nums[now],nums[left],nums[right] });
}
return result;
}
};
运行结果
看示例,感觉应该能通过
提交之后,出现了问题:在处理[0,0,0,0]
时,返回了两遍相同的结果。
原因是上面的代码中,碰到符合条件的就直接添加到结果数组中了。
由于每次会返回三个值,所以当出现四个或以上重复值的时候,这个问题就会出现。
这也是本题比较麻烦的一点:如何去重。
修改后
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int left(0), right(0), now(0);
vector<vector<int>>result = {};
sort(nums.begin(), nums.end());
for (; now < nums.size() - 1; now++)
{
if (nums[now] > 0)
return result;
if (now > 0 && nums[now] == nums[now - 1]) {
continue;
}
left = now + 1;
right = nums.size() - 1;
while (left < right)
{
if (nums[left] + nums[right] + nums[now] < 0)
left++;
else if (nums[left] + nums[right] + nums[now] > 0)
right--;
else
{
result.push_back({ nums[now], nums[left], nums[right] });
while (right > left && nums[right] == nums[right - 1])right--;
while (right > left && nums[left] == nums[left - 1])left--;
right--;
left++;
}
}
}
return result;
}
};
附上力扣大佬的注释
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for (int i = 0; i < nums.size(); i++) {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if (nums[i] > 0) {
return result;
}
// 错误去重方法,将会漏掉-1,-1,2 这种情况
/*
if (nums[i] == nums[i + 1]) {
continue;
}
*/
// 正确去重方法
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while (right > left) {
// 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组
/*
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
*/
if (nums[i] + nums[left] + nums[right] > 0) {
right--;
} else if (nums[i] + nums[left] + nums[right] < 0) {
left++;
} else {
result.push_back(vector<int>{nums[i], nums[left], nums[right]});
// 去重逻辑应该放在找到一个三元组之后
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
// 找到答案时,双指针同时收缩
right--;
left++;
}
}
}
return result;
}
};
运行结果
执行用时:120 ms, 在所有 C++ 提交中击败了42.00%的用户
内存消耗:23.3 MB, 在所有 C++ 提交中击败了45.57%的用户
通过测试用例:312 / 312
哈希表
思路跟三个指针、处理两数之和的方法类似,不同的是遍历的方法:由指针换成了表。
但在本题中,并没有表现出更好的性能。
由于这次我们要返回的是值而非下标,因此不需要使用map容器。
以下注释来自力扣大佬的分享。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
// 找出a + b + c = 0
// a = nums[i], b = nums[j], c = -(a + b)
for (int i = 0; i < nums.size(); i++) {
// 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
if (nums[i] > 0) {
break;
}
if (i > 0 && nums[i] == nums[i - 1]) { //三元组元素a去重
continue;
}
unordered_set<int> set;
for (int j = i + 1; j < nums.size(); j++) {
if (j > i + 2
&& nums[j] == nums[j - 1]
&& nums[j - 1] == nums[j - 2]) { // 三元组元素b去重
continue;
}
int c = 0 - (nums[i] + nums[j]);
if (set.find(c) != set.end()) {
result.push_back({ nums[i], nums[j], c });
set.erase(c);// 三元组元素c去重
}
else {
set.insert(nums[j]);
}
}
}
return result;
}
};
运行结果
超时