基本逻辑
双指针,顾名思义就是使用两个指针来遍历元素,而要想使用两个指针,必须有额外的条件,最典型的就是有序数组。
下面来举一个最最最最典型的例子说明。
167. 两数之和 II - 输入有序数组
https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/description/

两数之和等于target,可以说是双指针题目的鼻祖了。
一般一下子能想到的就是暴力枚举了,但是时间复杂度在O(n^2),显然太低效了。
暴力枚举,只用到了 numbers 是一个数组,但仔细看题目给出了 numbers 按非递减顺序排列,也就是有序数组。所以我们需要把这个条件也用上,去优化时间复杂度。
下面给出暴力枚举和双指针,对比讲解。简单来说,双指针就是正向遍历和逆向遍历同时进行。
int n = numbers.size();
/*
l 代表的是正向遍历,r 代表的从逆向遍历
因为numbers数组是非递减的
那么就有numbers[l] <= numbers[l+i] (i = 1, 2, 3...)
同样numbers[r] >= numbers[r-i] (i = 1, 2, 3...)
*/
int l = 0, r = n-1;
//直接遍历
for (l; l < n; l++) {
for (r; r >l ; r--) {
if (numbers[l] == numbers[r]) {
return {l, r};
}
}
}
//使用双指针
while (l < r) {
/*
当numbers[l] + numbers[r] < target时,
则numbers[l] + numbers[r-i] < numbers[l] + numbers[r] < target
所以不需要再用r依次遍历 l+1 ~ r-1,因为它们肯定都是小于target
*/
if (numbers[l] + numbers[r] < target) {
l++;
}
//同理,不需要用l依次遍历 l+1 ~ r-1,因为它们肯定都是大于target
else if (numbers[l] + numbers[r] > target) {
r--;
}
else {
return {l, r};
}
}
基本的模版就是这样了,大家可以去LeetCode写完上面那题。
另外,双指针还有一种用法,用来判断是否为回文串,也是双指针的经典题目。
看起来和前面的两数之和不一样,但本质上都是双指针,只是改了判断条件,所以大家在用双指针的时候一定要灵活,不要太死板。
string s;
int l = 0, r = s.size();
while (l < r) {
if (s[l] == s[r]) {
l++; r--;
}
else {
return false;
}
}
return true;
上面就是判断回文串的核心代码,大家可以去写一下。
双指针的应用
实践巩固一下,举几个题目,灵活使用双指针。
15. 三数之和
https://leetcode.cn/problems/3sum/description/
这道题就是前面两数之和模版题的一个升级(实际上就是枚举 target),需要注意的是,nums数组无序,所以我们要先排序才能使用双指针。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
//排序,以便后面使用双指针。
sort(nums.begin(), nums.end());
vector<vector<int>> ans;
int size = nums.size();
for (int i = 0; i < size-2; i++) {
if (i>0 && nums[i] == nums[i-1]) continue;
//枚举target
int target = 0 - nums[i];
//优化:如果最小的两个数大于target,那么后面的数都会大于target,直接break
if (nums[i+1] + nums[i+2] > target) break;
//优化:如果最大的两个数小于target,那么前面的数都会小于target,继续遍历循环
if (nums[size-1] + nums[size-2] < target) continue;
int l = i + 1, r = size - 1;
//双指针
while(l < r) {
//剔除重复
if (l > i+1 && nums[l-1] == nums[l] && r < size - 1 && nums[r] == nums[r+1]) {
l++;r--;
continue;
}
if (nums[r] + nums[l] > target) {
r--;
}
else if (nums[r] + nums[l] < target) {
l++;
}
else {
ans.push_back({nums[i], nums[l], nums[r]});
l++;r--;
}
}
}
return ans;
}
};
18. 四数之和
https://leetcode.cn/problems/4sum/description/?envType=problem-list-v2&envId=x5grcpId
这道题是三数之和的升级,实际上就是再多加一个枚举,大家可以去尝试一下,我这边直接给出代码。
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
ranges::sort(nums);
int size=nums.size();
vector<vector<int>> ans;
set<vector<int>> exist;
//枚举第一个数
for(int i=0;i<size-3;i++){
//枚举第二个数
for (int j=i+1;j<size-2;j++){
//剩下来的两个数就用双指针枚举
long long pre_sum=nums[i]+nums[j];
int l=j+1,r=size-1;
if (pre_sum+nums[j+1]+nums[j+2]>target) break;
if (pre_sum+nums[size-1]+nums[size-2]<target) continue;
while(l<r){
long long cur=pre_sum+nums[l]+nums[r];
if (cur==target){
vector<int> temp={nums[i],nums[j],nums[l],nums[r]};
if (exist.count(temp)==0){
exist.insert(temp);
ans.push_back(temp);
}
l++;r--;
}
else if (cur>target) r--;
else l++;
}
}
}
return ans;
}
};
相较于前面的有点难度,这题要注意数据范围,需要开long long
P8708 [蓝桥杯 2020 省 A1] 整数小拼接
https://www.luogu.com.cn/problem/P8708
#include <bits/stdc++.h>
using ll = long long;
using namespace std;
//判断 string a 是否大于 string b
bool compare(string a, string b) {
if (a.size() > b.size()) {
return true;
}
else if (a.size() == b.size() && a > b) {
return true;
}
return false;
}
int main() {
int n;
string k;
cin >> n >> k;
//long long 型 k
ll k_num = stoll(k);
vector<ll> nums;
for (int i = 0; i < n; i++) {
ll tmp;
cin >> tmp;
nums.push_back(tmp);
}
int size = nums.size();
//排序,为双指针做准备
sort(nums.begin(), nums.end());
int l = 0, r = size-1;
ll ans = 0;
//双指针
while (l < r) {
//拼接
string cur = to_string(nums[l]) + to_string(nums[r]);
if (compare(cur, k)) {
r--;
}
else {
//如果 l 和 r 拼接 <= k,那么,l不变,r的范围在[l+1, r]拼接 <= k,共计 r-l 符合要求
ans += r-l;
l++;
}
}
//判断逻辑上面那个双指针是一样的,只是拼接的方式不同
l = 0, r = size-1;
while (l < r) {
string cur = to_string(nums[r]) + to_string(nums[l]);
if (compare(cur, k)) {
r--;
}
else {
ans += r-l;
l++;
}
}
cout << ans << endl;
}
一些心得
算法这种东西容易忘,需要的是平时的积累,如果上面的题目写不出来,没关系,可以先看一遍博主的题解(但一定要自己先思考过了,比如用暴力枚举做出来了,那也是一种进步),抄一下通关获点成就感,然后理解一下代码,等过几天再回来写。
ps:(其实就和打游戏一样,不断去闯关,去叠场次,像永劫里练连招、球弓这些都是依靠时间练出来的,捏蓝克烈他们都是玩了上万个小时;火影里主播的技术他们也是通过上万场才练出来的像我知道的心安,银色都是这样的,所以说,如果想让自己的算法实力变厉害,必须要投入时间,也许几道看不出来自己的提升,也许比别人慢,都没关系的,没有天赋,那就重复,大家可以去LeetCode、洛谷上刷题,写对自己来说简单的题目,相当于炸鱼,每天可以来几道,爽一爽,而对自己来说难的题目,才能更好提升实力,其实博主我也没什么实力,大家一起共勉吧),
大家想要参加算法比赛的一定要去练习,不参加的偶尔也要去写写,算法作为计算机基础,无论是找工作面试,还是考研都是必要的,而且能锻炼你的计算机思维。
感兴趣的可以给博主点个免费的关注,博主后面还会继续更新算法和其它知识。

1025

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



