基础知识
双指针法基本都是应用在数组,字符串与链表的题目上
题目
⭐1.寻找重复数( LeetCode 287 )
难度: 中等
题目表述:
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
代码(C++):
class Solution {
public:
// 二分查找
int findDuplicate(vector<int>& nums) {
int l = 1, r = nums.size() - 1, ans;
while (l <= r) {
int mid = (l + r) >> 1;
int cnt = 0;
for (int i = 0; i < nums.size(); i++) {
cnt += nums[i] <= mid;
}
if (cnt <= mid) {
l = mid + 1;
} else {
r = mid - 1;
ans = mid;
}
}
return ans;
}
// 二进制
int findDuplicate(vector<int>& nums) {
int n = nums.size(), ans = 0;
int bit_max = 17; // nums最大值是1e5,17是可覆盖的最小位数 = 131072
while (!(n - 1) >> bit_max) {
bit_max--;
}
for (int i = 0; i < bit_max; i++) {
int x = 0, y = 0;
for (int j = 0; j < n; j++) {
if (nums[j] & 1 << i)
x++;
if (j >= 1 && (j & 1 << i))
y++;
}
if (x > y) {
ans |= 1 << i;
}
}
return ans;
}
// 快慢指针
int findDuplicate(vector<int>& nums) {
int slow = 0, fast = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while(slow != fast);
slow = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
};
题解: 二分查找 / 二进制 / 快慢指针
二分查找:数组中的数在1-n之间,小于中间数的数出现的次数满足单调性,不重复的情况下,出现次数必然小于等于中间数,重复数必然出现在中间数的右侧,反之则表明中间数左侧必有重复数,因此可用二分法。
二进制:考虑到第 i 位,我们记 nums 数组中二进制展开后第 i 位为 1 的数有 x 个,数字 [1,n] 这 n 个数二进制展开后第 i 位为 1 的数有 y 个,那么当且仅当 x>y,重复的数第 i 位为 1 。按位与&、按位或|。
快慢指针(「Floyd 判圈算法」(又称龟兔赛跑算法),它是一个检测链表是否有环的算法):我们对 nums 数组建图,每个位置 i 连一条 i →nums[i] 的边。由于存在的重复的数字 target,因此 target 这个位置一定有起码两条指向它的边,因此整张图一定存在环,且我们要找到的 target 就是这个环的入口,那么整个问题就等价于 二(9)环形链表 II。如 1 4 6 6 6 2 3:1->4->6->3->6出现环,环入口就是重复值。
2.验证回文串( LeetCode 125 )
难度: 简单
题目表述:
如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。字母和数字都属于字母数字字符。给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false 。
代码(C++):
class Solution {
public:
bool isPalindrome(string s) {
string a = "";
for (auto &c: s) {
if (c >= 'A' && c <= 'Z')
a += c - 'A' + 'a';
else if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
a += c;
}
int l = 0,r = a.size() - 1;
while (l < r) {
if (a[l] != a[r])
return false;
l++;
r--;
}
return true;
}
bool isPalindrome(string s) {
int l = 0,r = s.size() - 1;
while (l < r) {
while (l < r && !isalnum(s[l])) {
l++;
}
while (l < r && !isalnum(s[r])) {
r--;
}
if (l < r) {
if (tolower(s[l]) != tolower(s[r]) ) {
return false;
}
l++;
r--;
}
}
return true;
}
};
题解:
isalnum判字母或数字字符、tolower转小写、toupper转大写、a(a.rbegin(), a.rend())反转字符串
3.x 的平方根 ( LeetCode 69 )
难度: 简单
题目表述:
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
代码(C++):
class Solution {
public:
// 二分查找
int mySqrt(int x) {
int l = 0, r = x / 2 + 1;
while (l < r) {
long mid = (l + r + 1) >> 1;
if (mid * mid > x) {
r = mid - 1;
} else {
l = mid;
}
}
return l;
}
// 牛顿迭代
int mySqrt(int x) {
if (x == 0) return 0;
double C = x, x0 = x;
while (true) {
double x1 = (x0 + C / x0) / 2;
if (x1 - x0 < 1e-6) {
break;
}
x0 = x1;
}
return int(x0);
}
};
题解: 二分查找 / 牛顿迭代
牛顿迭代法是一种可以用来快速求解函数零点的方法。
4.移除元素 ( LeetCode 27 )
难度: 简单
题目表述:
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
代码(C++):
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int l = 0, r = nums.size() - 1;
while (l <= r) {
if (nums[l] == val) {
nums[l] = nums[r];
r--;
} else {
l++;
}
}
return l;
}
};
题解:
5.反转字符串 ( LeetCode 344 )
难度: 简单
题目表述:
将输入的字符串反转过来。
代码(C++):
class Solution {
public:
void reverseString(vector<char>& s) {
int l = 0, r = s.size() - 1;
while (l < r) {
swap(s[l], s[r]);
l++;
r--;
}
}
};
题解:
6.翻转字符串里的单词 ( LeetCode 151 )
难度: 中等
题目表述:
给你一个字符串 s ,请你反转字符串中 单词 的顺序。
代码(C++):
class Solution {
public:
string reverseWords(string s) {
string res = "";
int n = s.size();
int l = n - 1;
while (l >= 0) {
while (l >= 0 && s[l] == ' ') {
l--;
}
if (l < 0) break;
int r = l;
while (l >= 0 && s[l] != ' ') {
l--;
}
res += s.substr(l + 1, r - l);
res += ' ';
}
return res.substr(0, res.size() - 1);
}
};
题解:
7.最接近的三数之和( LeetCode 16 )
难度: 中等
题目表述:
给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。返回这三个数的和。
代码(C++):
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
int n = nums.size(), res = nums[0] + nums[1] + nums[2];
for (int i = 0; i < n - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
if (nums[i] + nums[n - 2] + nums[n - 1] < target) {
int newSum = nums[i] + nums[n - 2] + nums[n - 1];
res = (abs(newSum - target) < abs(res - target)) ? newSum : res;
continue;
}
if (nums[i] + nums[i + 1] + nums[i + 2] > target) {
int newSum = nums[i] + nums[i + 1] + nums[i + 2];
res = (abs(newSum - target) < abs(res - target)) ? newSum : res;
continue;
}
int l = i + 1, r = n - 1;
while (l < r) {
int newSum = nums[i] + nums[l] + nums[r];
if (newSum == target) return target;
res = (abs(newSum - target) < abs(res - target)) ? newSum : res;
if (newSum > target) {
int r0 = r - 1;
// 移动到下一个不相等的元素
while (l < r0 && nums[r0] == nums[r]) r0--;
r = r0;
} else {
int l0 = l + 1;
// 移动到下一个不相等的元素
while (l0 < r && nums[l0] == nums[l]) l0++;
l = l0;
}
}
}
return res;
}
};
题解:
和三数之和、四数之和类似,使用双指针代替两重循环来枚举所有的可能情况
8.三数之和( LeetCode 15 )
难度: 中等
题目表述:
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
代码(C++):
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end());
int n = nums.size();
vector<vector<int>> res;
for (int i = 0; i < n - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
if (nums[i] + nums[i + 1] + nums[i + 2] > 0) break;
if (nums[i] + nums[n - 2] + nums[n - 1] < 0) continue;
int l = i + 1, r = n - 1;
while (l < r) {
if (nums[i] + nums[l] + nums[r] == 0) {
res.push_back({nums[i], nums[l], nums[r]});
while (l < r && nums[l] == nums[l + 1]) {
l++;
}
l++;
while (l < r && nums[r] == nums[r - 1]) {
r--;
}
r--;
} else if (nums[i] + nums[l] + nums[r] < 0) {
l++;
} else {
r--;
}
}
}
return res;
}
};
题解:
9.四数之和( LeetCode 18 )
难度: 中等
题目表述:
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出不复用、和为target且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复)。
代码(C++):
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
int n = nums.size();
sort(nums.begin(), nums.end());
for (int i = 0; i < n - 3; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) break;
if ((long) nums[i] + nums[n - 3] + nums[n - 2] + nums[n - 1] < target) continue;
for (int j = i + 1; j < n - 2; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
if ((long) nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) break;
if ((long) nums[i] + nums[j] + nums[n - 2] + nums[n - 1] < target) continue;
int l = j + 1, r = n - 1;
while (l < r) {
long sum = (long) nums[i] + nums[j] + nums[l] + nums[r];
if (sum == target) {
res.push_back({nums[i], nums[j], nums[l], nums[r]});
while (l < r && nums[l] == nums[l + 1]) {
l++;
}
l++;
while (l < r && nums[r] == nums[r - 1]) {
r--;
}
r--;
} else if (sum < target) {
l++;
} else {
r--;
}
}
}
}
return res;
}
};
题解: 排序 + 双指针
两重循环分别枚举前两个数,使用双指针枚举剩下的两个数,根据排序后递增的特性,可以加入剪枝。
10.两数之和( LeetCode 1 )
难度: 简单
题目表述:
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
代码(C++):
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> mp;
for (int i = 0; i < nums.size(); i++) {
if (mp.find(target - nums[i]) != mp.end()) return {mp[target - nums[i]], i};
mp[nums[i]] = i;
}
return {};
}
};
题解: 哈希
小结
具有单调性应联想到二分查找法,双指针可抵消掉一重循环。
「Floyd 判圈算法」(又称龟兔赛跑算法),检测链表是否有环的算法,并且返回入环点:
(1)先快慢指针找相遇点;
(2)相遇之后,让slow回到起点,再和fast一起一步一步走,直至相遇,相遇点就是入环点。
x数之和模板:
- 升序排序
- 枚举第一个
2.1 判当前和前一个相同 continue;
2.2 判(当前 + 当前的后几个) > target break;(看题意决定break还是continue)
2.3 判 (当前 + 最后几个) < target continue; (看题意决定break还是continue) - (类似第一个)枚举第二个
- (类似第一个) 枚举第…个
- 双指针枚举最后两个数,可以加入while判重以避免将重复的结果多次返回。
// 1.升序排序
sort();
// 2.枚举第一个
for (int i = 0; i < n - (x-1); i++) {
// 1.1 判重以保证和上一次枚举的元素不同
if (i > 0 && nums[i] == nums[i - 1]) continue;
// 1.2 判第一个元素和后面紧跟着的x-1个元素之和是否>target,如果是,就说明后面的枚举和一定都是大于target,直接break;
if ((long) nums[i] + nums[i + 1] + nums[i + ...] > target) break;
// 1.3 判第一个元素和最后的x-1个元素之和是否<target,如果是,就说明该轮枚举和的最大值都是小于target的,就没必要再继续枚举后面几个元素了,直接进入下一轮continue;
if ((long) nums[i] + nums[n - (x-1)] + nums[n - ...] < target) continue;
// 3.枚举第二个
for (int j = i + 1; j < n - (x-2); j++) {
// 2.1 判重
// 2.2 判前几个
// 2.3 判后几个
// 4.双指针枚举最后两个数
while (l < r) {
long sum = (long) nums[i] + nums[j] + nums[l] + nums[r];
if (sum == target) {
return / push_back
// 判重,避免将重复的结果多次返回
while (l < r && nums[l] == nums[l + 1]) {
l++;
}
l++;
// 判重,避免将重复的结果多次返回
while (l < r && nums[r] == nums[r - 1]) {
r--;
}
r--;
} else if (sum < target) {
l++;
} else {
r--;
}
}
}
}