news 2026/6/10 10:39:14

双指针-快慢指针(龟兔指针)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
双指针-快慢指针(龟兔指针)

快慢指针本质上是一种思想,而非一种算法,就和贪心一样,不能把其简单地看作一种算法。

概念

这里的指针并非C和C++中的指针,你可以理解为数组下标或者类似的东西。

快指针:快速遍历并检测符合条件的数据,先行一步查找满足前置条件的数据

慢指针:维护已处理数据的边界,即对已满足条件数据的维护

双指针可以帮我们降低遍历的次数,加快程序运行速度,降低时间复杂度。

例题

有序数组去重

将一个已经排序好的数组去重,如1,2,3,3,3,4,5在去重后变成1,2,3,4,5

代码部分

常规做法
#include<iostream> using namespace std; int main(){ int num[7] = {1 , 2 , 3 , 3 , 3 , 4 , 5}; int cnt = 0; //计算重了几个 for(int i = 0 ; i < 7 - cnt ; i ++){ if(i != 0 && num[i] == num[i - 1]){ cnt ++; i --; for(int j = i ; j < 7 - cnt ; j++){ num[j] = num[j + 1]; } } } for(int i = 0 ; i < 7 - cnt ; i ++){ cout << num[i] << ' '; } cout << endl; return 0; }

但是这样双重嵌套的情况下,如果数组比较长,就会导致代码的运行效率异常低下。所以我们将使用双指针。

快慢指针写法

我们将转换思考方式,我们现在想要去重,就只需要把没出现过的暂时存起来,这样就不需要嵌套遍历(其实是空间换时间的思想)

#include<iostream> using namespace std; int main(){ int num[7] = {1 , 2 , 3 , 3 , 3 , 4 , 5}; int temp[7] = {}; //定义一个零时数组 temp[0] = num[0]; int j = 0; //慢指针,即temp的数组边界(下标) for(int i = 0 ; i < 7 ; i++){ if(temp[j] != num[i]){ //遍历存储 temp[j + 1] = num[i]; j++; } } for(int i = 0 ; i < j + 1 ; i++){ cout << temp[i] << ' '; } cout << endl; return 0; }

肥肠简单的一个例子,相信没有看这个文章大家也可以想到这种方法。

当然,也可以不用temp[j] != num[i]这种判断方式,由于数组本身已经有序,所以可以直接将相同的右边界放入temp数组中,如2,2,2,3的时候判断快指针所指数和下一个数是否相同,不同则将该数放入。

当然,我们也可以对内存空间进一步优化,我们舍弃temp数组,直接将两个指针都用在num上

完全体的快慢指针
#include<iostream> using namespace std; int main(){ int num[7] = {1 , 2 , 3 , 3 , 3 , 4 , 5}; int j = 0; //慢指针,数组边界 for(int i = 0 ; i < 7 - 1 ; i++){ if(num[i] != num[i + 1]){ //边界判断,如果到边界则进行存储 num[j] = num[i]; j++; } } if(num[j] != num[6]){ num[j] = num[6]; //由于循环的时候我们将最后一位舍弃,所以这里我们要放回 j++; } for(int i = 0 ; i < j ; i++){ cout << num[i] << ' '; } cout << endl; return 0; }

如此一来,我们连temp的空间都省下来了

原地删除

如数组1,2,3,4,5,6,2,2,2,3,1,4,5

我们想把2全都删掉的同时保证数组成员之间的相对关系保持不变

同样的,我们可以用常规方法来实现,但是常规方法不但麻烦而且复杂度也比叫高,所以我们使用快慢指针来实现数组成员的删除

代码实现

#include<iostream> using namespace std; int main(){ int num[13] = {1,2,3,4,5,6,2,2,2,3,1,4,5}; int j = 0; //慢指针 for(int i = 0 ; i < 13 ; i++){ if(num[i] != 2){ num[j] = num[i]; j++; } } for(int i = 0 ; i < j ; i++){ cout << num[i] << ' '; } cout << endl; return 0; }

代码简介而优雅

最长连续不重复子序列

洛谷U224090

题目描述:给定长度为 n 的整数序列,找出最长的不包含重复的数的连续区间,输出它的长度

输入格式:第一行包含整数n(1 <= n <= 100000)

第二行包含 n 个整数(均在0~10^5范围内),表示整数序列

输入

5

1 2 2 3 5

输出

3

对于这道题目,如果我们直接使用暴力做法,就会生成三层循环,导致超时。

暴力写法

#include<iostream> using namespace std; const int N = 1e5 + 10; int arr[N]; int main(){ int n; cin >> n; for(int i = 0 ; i < n ; i++){ cin >> arr[i]; } int maxlen = 0; //最长长度 for(int i = 0 ; i < n - 1 ; i++){ int len = 1; //i和j的起始长度相差1 for(int j = i + 1 ; j < n ; j++){ int flag = 0; //标记,0为范围内无重复,1为有重复 for(int k = i ; k < j ; k++){ //从头到尾查一遍 if(arr[k] == arr[j]){ flag = 1; break; } } if(flag) break; else len++; } if(len > maxlen){ maxlen = len; } } cout << maxlen << endl; return 0; }

由于太过暴力,十个测试点我们只能通过4个,剩下的全部都超时了。所以使用暴力来解决这个问题是不恰当的

此时的时间复杂度为O(),所以我们要对这段代码进行优化

第一次优化

最内层循环进行优化,优化其判断区间是否有重复元素的逻辑

这里我们首先采取空间换时间的方法来降低时间复杂度,即利用计数排序的逻辑来判断这段连续子序列中每个元素的出现次数

#include<iostream> #include<cstring> using namespace std; const int N = 1e5 + 10; int arr[N]; int main(){ int n; cin >> n; for(int i = 0 ; i < n ; i++){ cin >> arr[i]; } int maxlen = 0; //最长长度 for(int i = 0 ; i < n - 1 ; i++){ int cnt[N]; // 定义统计出现次数的数组 memset(cnt , 0 , sizeof(cnt)); // 清空cnt数组为0 int len = 0; for(int j = i ; j < n ; j++){ if(cnt[ arr[j] ] == 0){ len++; cnt[ arr[j] ] = 1; }else{ break; } } if(len > maxlen){ maxlen = len; } } cout << maxlen << endl; return 0; }

在进行一次优化后,我们的时间复杂度下降到了O()

然后我们发现十个测试点就全都通过了

过不去的多提交几遍就过了(bushi)

第二次优化

但是O()的时间复杂度还是相当大,如果题目给出的 n 再大几个数量级,那么我们也无法顺利通过这道题,所以我们还需要进行再次优化

这一次,我们将根据题目的本质来进行优化,题目的要求是找出最长的不重复子序列,所以当出现重复的数时,我们直接把慢指针移到出现过的数的后一位继续遍历

1,2,5,6,7,6,9,10

我们定义两个指针 i 和 j ,i 为慢指针

当 i 为0的时候,我们发现当 j 等于 5 的时候,出现了重复的数据,这个时候我们直接将 i 移到第一个 6 后方,也就是让 i 等于 3 ,然后让 j 继续遍历

这个方法成立的原因在于:当 i 在第一个 6 之前时,无论 i 如何移动, j 都无法越过第二个 6 ,所以我们便让 i 直接到第一个 6 后方

#include<iostream> #include<cstring> using namespace std; const int N = 1e5 + 10; int arr[N]; int main(){ int n; cin >> n; for(int i = 0 ; i < n ; i++){ cin >> arr[i]; } int maxlen = 0; //最长长度 for(int i = 0 ; i < n - 1 ; i++){ //慢指针 int cnt[N] = {0} , len = 0; // 统计数据出现次数 for(int j = i ; j < n ; j++){ //快指针 if(cnt[arr[j]] == 0){ cnt[arr[j]] = 1; len++; }else{ for(int k = i ; k <= j ; k++){ //查找第一次出现位置,直接把i移到次位置后 if(arr[k] == arr[j]){ i = k; // 注意,不需要加一,当外层循环结束后,i会自动加1 break; } } break; } } if(len > maxlen){ maxlen = len; } } cout << maxlen << endl; return 0; }

虽然看起来我们的循环变成了三层,但是我们利用内层循环减少了最外层循环,所以从整体上看我们的代码的时间复杂度还是在下降的

当然,嵌套的存在还是为我们的代码增添了不确定性,所以我们可以最终优化将代码变为单层循环

最终优化

#include<iostream> #include<cstring> using namespace std; const int N = 1e5 + 10; int arr[N]; int main(){ int n; cin >> n; for(int i = 0 ; i < n ; i++){ cin >> arr[i]; } int maxlen = 0; //最长长度 int i = 0; // 慢指针 int cnt[N] = {0}; // 统计数据出现次数 for(int j = 0 ; j < n ; j++){ //快指针 cnt[arr[j]]++; //当前位置的数统计次数加1 while(cnt[arr[j]] > 1){ // 循环直到当前位置的数统计次数降到1 cnt[arr[i]]--; i++; } if(j - i + 1 > maxlen){ maxlen = j - i + 1; } } cout << maxlen << endl; return 0; }

通过这次优化,我们最终将时间复杂度降到了O(n)

总结

双指针本质上不是一种算法,它是一种思维方式,当我们可以理解这种思维方式的时候,我们就可以对代码进行优化,同时也可以缩减代码量

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 13:34:21

如何用Kotaemon构建可追溯的智能问答系统?

如何用Kotaemon构建可追溯的智能问答系统&#xff1f; 在企业知识管理日益复杂的今天&#xff0c;一个看似简单的员工提问——“我该怎么申请新的笔记本电脑&#xff1f;”——背后却可能隐藏着巨大的风险。如果系统随口编造一个错误的邮箱地址或流程步骤&#xff0c;轻则耽误工…

作者头像 李华
网站建设 2026/6/10 14:15:39

EmotiVoice支持语音情感迁移至不同音色

EmotiVoice&#xff1a;让情感跨越音色的语音合成新范式 在虚拟主播的一场直播中&#xff0c;观众发来一条弹幕&#xff1a;“你现在听起来好难过啊。” 而实际上&#xff0c;这位“主播”并非真人&#xff0c;而是由AI驱动的数字形象——她刚刚用温柔女声说出了一句愤怒的台词…

作者头像 李华
网站建设 2026/6/9 3:25:59

EmotiVoice开源模型推理速度实测与GPU选型建议

EmotiVoice开源模型推理速度实测与GPU选型建议 在智能语音交互日益普及的今天&#xff0c;用户早已不再满足于“能说话”的机器。从虚拟偶像到游戏NPC&#xff0c;从有声读物到情感化客服&#xff0c;市场对自然、富有情绪表达且高度个性化的语音合成技术提出了更高要求。传统…

作者头像 李华
网站建设 2026/6/10 12:36:48

EmotiVoice情感语音生成与用户接受度调研

EmotiVoice情感语音生成与用户接受度调研 在智能语音助手越来越频繁地进入我们生活的今天&#xff0c;一个明显的问题浮现出来&#xff1a;为什么大多数AI合成的声音仍然让人感觉“冷冰冰”&#xff1f;无论是车载导航里一成不变的提示音&#xff0c;还是客服机器人机械式的回应…

作者头像 李华
网站建设 2026/6/10 1:46:58

Kotaemon电力巡检报告自动生成流程

Kotaemon电力巡检报告自动生成流程 在现代电网运维中&#xff0c;一个变电站的日常巡检可能产生数百条设备状态记录、数十份操作日志和多轮人工观测笔记。面对如此庞杂的信息流&#xff0c;传统依赖纸质表单与人工汇总的方式不仅效率低下&#xff0c;还极易因疏漏或主观判断导致…

作者头像 李华