1.两数之和
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
只会存在一个有效答案
进阶:你可以想出一个时间复杂度小于 O(n2)
的算法吗?
// 方法一 暴力枚举
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int len = (int)nums.size();
for(int i = 0; i < len ; i++){
for(int j = i + 1; j < len; j++){
if(target == nums[i] + nums[j]){
return{i, j}
}
}
}
return {};
}
};
// 方法二 哈希表
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> hashtable;
for (int i = 0; i < nums.size(); ++i) {
auto it = hashtable.find(target - nums[i]);
// 如果执行hashtable.find(x),哈希表中无x,就会返回hashtable.end()
if (it != hashtable.end()) {
return {it->second, i};
}
hashtable[nums[i]] = i; // 将数和下标存进哈希表
}
return {};
}
};
补充:
C++中哈希表的操作
(1)创建哈希表
unordered_map<int,int> m; //<string,string>,<char,char>
(2)添加元素
// 1. insert函数
m.insert(pair<int,int>(1, 10));
m.insert(pair<int,int>(2, 20));
// 2. 数组的方法添加
m[3]=30;
m[4]=40;
(3)成员函数
// 1. begin(), end()
m.begin() // 指向哈希表的第一个容器
m.end() // 指向哈希表的最后一个容器,实则超出了哈希表的范围,为空
// 2. find()
m.find(2) // 查找key为2的键值对是否存在 ,若没找到则返回m.end()
if(m.find(2)!=m.end()) // 判断找到了key为2的键值对,用 auto 关键字定义的变量来接收
// 3. count() 查找函数
// 查找哈希表中key为3和5的键值对,找到返回1,找不到返回0
m.count(3) //返回 1
m.count(5) //返回 0
// 4. size()
m.size() // 返回哈希表的大小
// 5. empty()
m.empty() // 判断哈希表是否为空,返回值为 true / false
// 6. swap()
// 交换两个哈希表中的元素,整个哈希表的键值对全部都交换过去
unordered_map<int,int> m1;
unordered_map<int,int> m2;
m1.swap(m2);
swap(m1,m2);
(4)遍历哈希表
// 第一种遍历
unordered_map<int, int> count;
for (auto p : count) {
int front = p.first; // key
int end = p.second; // value
}
// 第二种遍历
unordered_map<int, int> count;
for(auto it=m.begin();it!=m.end();it++)
{
int front = it->first; // key
int end = it->second; // value
}
2.最接近的三数之和
给你一个长度为 n
的整数数组 nums
和 一个目标值 target
。请你从 nums
中选出三个整数,使它们的和与 target
最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。
// 方法 排序 + 双指针
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
// 对数组进行排序
sort(nums.begin(), nums.end());
int n = nums.size();
// 1e7 == 一乘十的七次方,是一个整型
int best = 1e7;
// 这里是定义了一个匿名函数
// 根据差值的绝对值来更新答案
auto update = [&](int cur) {
if (abs(cur - target) < abs(best - target)) {
best = cur;
}
};
// 枚举 a
for (int i = 0; i < n; ++i) {
// 保证和上一次枚举的元素不相等
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
// 使用双指针枚举 b 和 c
int j = i + 1, k = n - 1;
while (j < k) {
int sum = nums[i] + nums[j] + nums[k];
// 如果和为 target 直接返回答案
if (sum == target) {
return target;
}
update(sum);
if (sum > target) {
// 如果和大于 target,移动 c 对应的指针
int k0 = k - 1;
// 移动到下一个不相等的元素
while (j < k0 && nums[k0] == nums[k]) {
--k0;
}
k = k0;
} else {
// 如果和小于 target,移动 b 对应的指针
int j0 = j + 1;
// 移动到下一个不相等的元素
while (j0 < k && nums[j0] == nums[j]) {
++j0;
}
j = j0;
}
}
}
return best;
}
};
补充:
C++中的 sort 函数
函数:sort()
头文件:#include<algorithm>
功能:对给定范围内的序列进行升序排序。sort(begin,end,cmp),cmp参数默认升序
示例:
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
int main(){
// 数组排序
int a[4] = {4, 2, 5, 1};
// 升序
sort(a, a + 4);
// 降序
sort(a, a + 4, greater<int>());
// 字符串排序
string s("hello world");
// 升序
sort(s.begin(), s.end());
// 降序,利用反向迭代器
sort(s.rbegin(), s.rend());
// vector 进行排序
sort(nums.begin(), nums.end());
return 0;
}
C++中的 auto 关键字
auto:用来声明自动变量。它是存储类型标识符,表明变量(自动)具有本地范围,块范围的变量声明(如for循环体内的变量声明)默认为auto存储类型。
// 更新变量cur与target差值
auto update = [&](int cur) {
if (abs(cur - target) < abs(best - target)) {
best = cur;
}
};
C++中的匿名函数
定义:
auto fp = [捕获参数列表](函数参数列表) mutable throw(异常类型)->返回值类型 {函数体语句};
[捕获列表] (形参列表) ->返回值类型
{
函数体;
}
[](){}; // 匿名函数声明
[](){} (); // 匿名函数的调用
// 注:在使用匿名函数时主要有以上两种方式:
// 第一种称之为匿名函数的声明,是在匿名函数作为参数时使用,类比普通函数的函数名
// 第二种称之为匿名函数的调用,是在直接调用匿名函数的方法。
捕获形式 | 说明 |
---|---|
[ ] | 不捕获任何外部变量(注意,可以使用全局变量和静态变量) |
[a, b…] | 默认以值得形式捕获指定的多个外部变量(用逗号分隔),如果引用捕获,需要显示声明(使用&说明符) |
[this] | 以值的形式捕获this指针 |
[=] | 以值的形式捕获所有外部变量(包括lambda所在类的this) |
[&] | 以引用形式捕获所有外部变量(同上) |
[=, &x] | 变量x以引用形式捕获,其余变量以传值形式捕获 |
[&, x] | 变量x以值的形式捕获,其余变量以引用形式捕获 |
按引用捕获相当于按引用传递。这里需要说明的是,按值捕获相当于实参传递形参的过程,捕获的变量在匿名函数内部生成一份等值的拷贝。
示例:
// 按引用捕获外部变量
int a = 10;
[&]() { cout << a << endl; }();
[&]() { a = 20; cout << a << endl; }();
[&]() mutable{ a = 30; cout << a << endl; }();
cout << a << endl;
// 输出 10 20 30 30
// 1. mutable 说明符只针对按值捕获的外部变量,按引用捕获的外部变量可以直接修改。 // 2. 修改按引用捕获的外部变量,原来的外部变量也会改变。
3.删除有序数组中的重复项
给你一个有序数组 nums
,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
// 方法 双指针
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int n = nums.size();
if (n == 0) {
return 0;
}
int fast = 1, slow = 1;
while (fast < n) {
if (nums[fast] != nums[fast - 1]) {
nums[slow] = nums[fast];
++slow;
}
++fast;
}
return slow;
}
};
4.移除元素
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
// 方法一 双指针
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int n = nums.size();
int left = 0;
for (int right = 0; right < n; right++) {
if (nums[right] != val) {
nums[left] = nums[right];
left++;
}
}
return left;
}
};
// 方法二 双指针优化
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int left = 0, right = nums.size();
while (left < right) {
if (nums[left] == val) {
nums[left] = nums[right - 1];
right--;
} else {
left++;
}
}
return left;
}
};
5.三数之和
给你一个包含 n
个整数的数组 nums
,判断 nums
中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
// 方法 排序 + 双指针
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n = nums.size();
sort(nums.begin(), nums.end());
vector<vector<int>> ans;
// 枚举 a
for (int first = 0; first < n; ++first) {
// 需要和上一次枚举的数不相同
if (first > 0 && nums[first] == nums[first - 1]) {
continue;
}
// c 对应的指针初始指向数组的最右端
int third = n - 1;
int target = -nums[first];
// 枚举 b
for (int second = first + 1; second < n; ++second) {
// 需要和上一次枚举的数不相同
if (second > first + 1 && nums[second] == nums[second - 1]) {
continue;
}
// 需要保证 b 的指针在 c 的指针的左侧
while (second < third && nums[second] + nums[third] > target) {
--third;
}
// 如果指针重合,随着 b 后续的增加
// 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
if (second == third) {
break;
}
if (nums[second] + nums[third] == target) {
ans.push_back({nums[first], nums[second], nums[third]});
}
}
}
return ans;
}
};