news 2026/4/14 18:36:53

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

作者头像

张小明

前端开发工程师

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/4/11 18:45:15

Kotaemon + 大模型Token:高效处理海量文本生成任务

Kotaemon 大模型Token&#xff1a;高效处理海量文本生成任务 在企业智能化浪潮中&#xff0c;一个常见的痛点浮出水面&#xff1a;用户问客服“我的订单为什么还没发货&#xff1f;”&#xff0c;系统却只能机械回复“请查看物流信息”——因为它既不了解上下文&#xff0c;也…

作者头像 李华
网站建设 2026/4/10 0:24:04

AI语音滥用风险防控:EmotiVoice的应对措施

AI语音滥用风险防控&#xff1a;EmotiVoice的应对措施 在某次虚拟偶像直播中&#xff0c;观众突然听到主播用一种从未听过的“愤怒”语气回应弹幕&#xff1a;“你根本不懂我&#xff01;”——而这条语音并非预录&#xff0c;也非真人发声&#xff0c;而是由AI实时生成。这一幕…

作者头像 李华
网站建设 2026/4/13 10:27:31

EmotiVoice降低语音AI使用门槛

EmotiVoice&#xff1a;让每个人都能拥有会“说话”的AI 你有没有想过&#xff0c;只需几秒钟的录音&#xff0c;就能让AI用你的声音讲故事&#xff1f;或者让虚拟角色在对话中真正“愤怒”或“开心”&#xff0c;而不是机械地念出字句&#xff1f;这不再是科幻电影里的桥段——…

作者头像 李华
网站建设 2026/4/12 16:58:13

EmotiVoice语音合成引擎的热更新能力实现方式

EmotiVoice语音合成引擎的热更新能力实现方式 在智能语音应用日益普及的今天&#xff0c;用户对TTS&#xff08;文本转语音&#xff09;系统的要求早已超越“能说话”的基本功能。无论是虚拟主播的情绪起伏、客服机器人的语气亲和力&#xff0c;还是有声书中不同角色的音色切换…

作者头像 李华
网站建设 2026/4/12 10:07:27

EmotiVoice开源项目常见问题解答(FAQ)汇总

EmotiVoice开源项目常见问题解答&#xff08;FAQ&#xff09;汇总 在AI语音技术飞速发展的今天&#xff0c;我们不再满足于“能说话”的机器。用户期待的是有情绪、有个性、像真人一样的声音——这正是EmotiVoice诞生的初衷。 这款开源语音合成引擎自发布以来&#xff0c;因其强…

作者头像 李华
网站建设 2026/4/8 17:27:42

低成本实现产品语音提示功能的新路径

低成本实现产品语音提示功能的新路径 在智能硬件日益普及的今天&#xff0c;用户对交互体验的要求早已超越“能用”&#xff0c;转向“好用”和“有温度”。一个简单的语音提示&#xff0c;比如“门已锁好”或“电量即将耗尽”&#xff0c;如果只是机械朗读&#xff0c;很容易被…

作者头像 李华