C++双指针:算法优化的“左右互搏术”与高效问题破解全指南
C++双指针:算法优化的“左右互搏术”与高效问题破解全指南
开篇故事:迷宫中的“双人探路策略”
想象两名探险者在迷宫中寻找出口:
- 快慢指针:一人快速探索死路,另一人稳步记录正确路径。
- 左右指针:两人从两端向中间夹击,快速排除无效区域。
- 滑动窗口:两人共同维护一个动态区域,确保覆盖所有可能的出口。
这种协同合作的智慧正是双指针算法的核心思想!它通过两个指针的巧妙配合,将时间复杂度从O(n²)优化至O(n),成为解决数组、链表、字符串问题的利器。
一、双指针的深度解析
1. 双指针的严格定义
双指针算法通过维护两个指针(索引),在特定规则下移动,高效解决问题。
- 核心优势:将嵌套循环优化为单次遍历,减少时间复杂度。
- 三大类型:
- 快慢指针:检测循环、找中点。
- 左右指针:有序数组操作、反转系列。
- 滑动窗口:子数组/子串问题。
2. 与暴力解法的对比
| 问题类型 | 暴力解法时间复杂度 | 双指针时间复杂度 |
|---|---|---|
| 有序数组两数之和 | O(n²) | O(n) |
| 链表检测环 | O(n²) | O(n) |
| 最长无重复子串 | O(n³) | O(n) |
二、双指针的三大类型与代码实现
1. 快慢指针:链表与循环检测
应用场景:链表中间节点、检测环、找环入口。
示例1:检测链表环
struct ListNode {int val;ListNode* next;ListNode(int x) : val(x), next(nullptr) {}
};bool hasCycle(ListNode* head) {ListNode* slow = head;ListNode* fast = head;while (fast && fast->next) {slow = slow->next;fast = fast->next->next;if (slow == fast) return true;}return false;
}
示例2:找链表中点
ListNode* findMiddle(ListNode* head) {ListNode* slow = head;ListNode* fast = head;while (fast && fast->next && fast->next->next) {slow = slow->next;fast = fast->next->next;}return slow;
}
2. 左右指针:数组与字符串操作
应用场景:两数之和、反转数组、回文判断。
示例1:有序数组两数之和
vector<int> twoSum(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1;while (left < right) {int sum = nums[left] + nums[right];if (sum == target) return {left, right};else if (sum < target) left++;else right--;}return {};
}
示例2:反转字符串
void reverseString(vector<char>& s) {int left = 0, right = s.size() - 1;while (left < right) {swap(s[left++], s[right--]);}
}
3. 滑动窗口:子数组/子串问题
应用场景:最长无重复子串、最小覆盖子串、区间统计。
示例:最长无重复子串
int lengthOfLongestSubstring(string s) {unordered_map<char, int> window;int left = 0, right = 0, maxLen = 0;while (right < s.size()) {char c = s[right++];window[c]++;while (window[c] > 1) { // 出现重复char d = s[left++];window[d]--;}maxLen = max(maxLen, right - left);}return maxLen;
}
三、双指针的六大实战场景
1. 合并有序数组
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {int p = m + n - 1, p1 = m - 1, p2 = n - 1;while (p1 >= 0 && p2 >= 0) {nums1[p--] = (nums1[p1] > nums2[p2]) ? nums1[p1--] : nums2[p2--];}while (p2 >= 0) nums1[p--] = nums2[p2--];
}
2. 盛最多水的容器
int maxArea(vector<int>& height) {int left = 0, right = height.size() - 1, maxArea = 0;while (left < right) {int area = (right - left) * min(height[left], height[right]);maxArea = max(maxArea, area);if (height[left] < height[right]) left++;else right--;}return maxArea;
}
3. 三数之和
vector<vector<int>> threeSum(vector<int>& nums) {sort(nums.begin(), nums.end());vector<vector<int>> res;for (int i = 0; i < nums.size(); i++) {if (i > 0 && nums[i] == nums[i-1]) continue; // 去重int left = i + 1, right = nums.size() - 1;while (left < right) {int sum = nums[i] + nums[left] + nums[right];if (sum == 0) {res.push_back({nums[i], nums[left], nums[right]});while (left < right && nums[left] == nums[left+1]) left++; // 去重while (left < right && nums[right] == nums[right-1]) right--;left++; right--;} else if (sum < 0) left++;else right--;}}return res;
}
4. 最小覆盖子串(滑动窗口进阶)
string minWindow(string s, string t) {unordered_map<char, int> need, window;for (char c : t) need[c]++;int left = 0, right = 0, valid = 0;int start = 0, len = INT_MAX;while (right < s.size()) {char c = s[right++];if (need.count(c)) {window[c]++;if (window[c] == need[c]) valid++;}while (valid == need.size()) {if (right - left < len) {start = left;len = right - left;}char d = s[left++];if (need.count(d)) {if (window[d] == need[d]) valid--;window[d]--;}}}return len == INT_MAX ? "" : s.substr(start, len);
}
5. 链表的倒数第K个节点
ListNode* getKthFromEnd(ListNode* head, int k) {ListNode* fast = head;ListNode* slow = head;for (int i = 0; i < k; i++) fast = fast->next;while (fast) {fast = fast->next;slow = slow->next;}return slow;
}
6. 颜色分类(荷兰国旗问题)
void sortColors(vector<int>& nums) {int left = 0, right = nums.size() - 1, curr = 0;while (curr <= right) {if (nums[curr] == 0) {swap(nums[left++], nums[curr++]);} else if (nums[curr] == 2) {swap(nums[curr], nums[right--]);} else {curr++;}}
}
四、双指针的陷阱与优化
1. 常见陷阱
- 指针越界:未检查指针移动后的合法性,导致访问越界。
- 循环条件错误:滑动窗口的收缩条件设计不当,导致死循环。
- 状态更新顺序:先更新指针还是先处理数据需严格确定。
2. 优化技巧
- 提前终止:在找到可行解后提前跳出循环。
- 减少冗余计算:缓存中间结果,如滑动窗口中的
valid计数器。 - 剪枝策略:在有序数组中根据当前结果跳过不可能的分支。
五、双指针的进阶应用
1. 多指针协同
解决四数之和、合并K个有序链表等复杂问题:
vector<vector<int>> fourSum(vector<int>& nums, int target) {sort(nums.begin(), nums.end());vector<vector<int>> res;for (int i = 0; i < nums.size(); i++) {if (i > 0 && nums[i] == nums[i-1]) continue;for (int j = i+1; j < nums.size(); j++) {if (j > i+1 && nums[j] == nums[j-1]) continue;int left = j+1, right = nums.size()-1;while (left < right) {long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];if (sum == target) {res.push_back({nums[i], nums[j], nums[left], nums[right]});while (left < right && nums[left] == nums[left+1]) left++;while (left < right && nums[right] == nums[right-1]) right--;left++; right--;} else if (sum < target) left++;else right--;}}}return res;
}
2. 与贪心算法结合
如分配饼干、跳跃游戏等:
int findContentChildren(vector<int>& g, vector<int>& s) {sort(g.begin(), g.end());sort(s.begin(), s.end());int child = 0, cookie = 0;while (child < g.size() && cookie < s.size()) {if (s[cookie] >= g[child]) child++;cookie++;}return child;
}
3. 动态窗口调整
处理数据流中的实时统计问题:
class MovingAverage {
private:queue<int> window;int sum = 0, size;
public:MovingAverage(int size) : size(size) {}double next(int val) {window.push(val);sum += val;if (window.size() > size) {sum -= window.front();window.pop();}return (double)sum / window.size();}
};
六、调试与性能分析
1. 可视化指针移动
打印指针位置与关键变量,辅助调试:
void twoSumDebug(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1;while (left < right) {cout << "left=" << left << ", right=" << right << ", sum=" << nums[left] + nums[right] << endl;// ...原有逻辑}
}
2. 复杂度验证
通过大规模数据测试验证时间复杂度:
vector<int> largeData(1000000, 1); // 百万级数据
auto start = chrono::high_resolution_clock::now();
twoSum(largeData, 2); // 应接近O(n)
auto end = chrono::high_resolution_clock::now();
cout << "耗时: " << chrono::duration_cast<chrono::milliseconds>(end - start).count() << "ms";
总结:双指针——算法优化的“左右互搏术”
双指针通过两个索引的协同操作,将复杂问题化繁为简。
- 像迷宫中的双人探路:一个指针开拓,另一个确认,高效覆盖所有可能。
- 像舞蹈中的双人配合:通过精妙的节奏控制,达到算法效率的巅峰。
掌握双指针技术,你将在算法面试与工程实践中游刃有余,轻松破解各类难题!
(完)
希望这篇深度解析能帮助你彻底掌握双指针技术!如需进一步调整或补充,请随时告知! 😊
相关文章:
C++双指针:算法优化的“左右互搏术”与高效问题破解全指南
C双指针:算法优化的“左右互搏术”与高效问题破解全指南 开篇故事:迷宫中的“双人探路策略” 想象两名探险者在迷宫中寻找出口: 快慢指针:一人快速探索死路,另一人稳步记录正确路径。左右指针:两人从两端…...
高级SQL技术在Python项目中的应用:ORM与深度性能优化
引言 在现代Python项目开发中,数据库交互远不止是数据的简单存取,它已成为构建高性能、可维护应用的核心瓶颈和关键能力所在。 仅仅依赖基础SQL查询,虽然入门简单,却难以应对日益增长的应用挑战。这些挑战主要体现在以下几个方面: 性能瓶颈: 数据量剧增: 从百万到数十亿乃…...
Pytorch实现论文:基于多尺度融合生成对抗网络的水下图像增强
简介 简介:提出了一种新型的水下图像增强算法,基于多尺度融合生成对抗网络,名为UMSGAN,以解决低对比度和颜色失真的问题。首先经过亮度的处理,将处理后的图像输入设计的MFFEM模块和RM模块生成图像。该算法旨在适应各种水下场景,提供颜色校正和细节增强。 论文题目:Und…...
从单片机的启动说起一个单片机到点灯发生了什么下——使用GPIO点一个灯
目录 前言 HAL库对GPIO的抽象 核心分析:HAL_GPIO_Init 前言 我们终于到达了熟悉的地方,对GPIO的初始化。经过漫长的铺垫,我们终于历经千辛万苦,来到了这里。关于GPIO的八种模式等更加详细的细节,由于只是点个灯&am…...
基于大语言模型的推荐系统(1)
推荐系统(recommendation system)非常重要。事实上,搜索引擎,电子商务,视频,音乐平台,社交网络等等,几乎所有互联网应用的核心就是向用户推荐内容,商品,电影&…...
Docker基础实践与应用举例
Docker 是一个轻量级容器化平台,通过将应用及其依赖打包到容器中,实现快速部署和环境一致性。以下是 Docker 的实践与应用场景举例,结合具体操作步骤: 一、基础实践 1. 快速启动一个容器 # 运行一个Nginx容器,映射宿…...
计算机毕业设计SpringBoot+Vue.js新闻推荐系统(源码+文档+PPT+讲解)
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
Android 布局系列(一):LinearLayout 使用指南
引言 在 Android 开发中,布局是每个应用的基础,而 LinearLayout 无疑是最常见、最简单的布局之一。它允许我们将多个视图按顺序排列,可以选择水平方向(horizontal)或垂直方向(vertical)。 Line…...
蓝桥杯备赛-精卫填海-DP
精卫终于快把东海填平了!只剩下了最后的一小片区域了。同时,西山上的木石也已经不多了。精卫能把东海填平吗? 事实上,东海未填平的区域还需要至少体积为 v 的木石才可以填平,而西山上的木石还剩下 n 块,每块…...
git中,如何查看具体单个文件的log
在 Git 中,可以使用多种方式查看单个文件的提交日志(Log),以下详细介绍不同场景下的查看方法: 目录 一、基本命令查看文件的完整提交日志 二、查看文件提交日志并显示差异内容 三、限制显示的提交日志数量 四、按…...
Winform工具箱、属性、事件
工具箱 Button------按钮:用户可以点击的按钮控件。 CheckBox------复选框:允许用户选择或取消选择选项的复选框。 CheckedListBox:结合了ListBox和CheckBox的功能,允许多项选择。 ColorDialog------颜色选择对话框:用…...
科普:HTTP端口80和HTTPS端口443
你会发现,有的网址不带端口号,怎么回事? HTTP协议默认端口:HTTP协议的默认端口是80。当用户在浏览器中输入一个没有指定端口的以http://开头的网址时,浏览器会自动使用80端口与服务器建立连接,进行超文本数…...
数据分析和数据挖掘的工作内容
基本的数据分析工作通常包含以下几个方面的内容: 确定目标(输入):理解业务,确定指标口径。获取数据:数据仓库(SQL提数)、电子表格、三方接口、网络爬虫、开放数据集等。清洗数据&am…...
Android级联选择器,下拉菜单
近期android开发,遇到的需求,分享二个android可能用到的小组件 下拉选择器:它的实现,主要是需要监听它依附的组件当前距离屏幕顶端的位置。 在显示下拉菜单中,如果需要点击上面有响应。可通过activity拿到decorview(ac…...
【每日八股】MySQL篇(一):概述
关系的三个范式是什么? 第一范式(1NF):用来确保每列的原子性,要求每列都是不可再分的最小数据单元。 概括:表中的每一列都是不可分割的最小原子值,且每一行都是唯一的。 第二范式(…...
大白话Vue2和Vue3双向数据绑定的原理
大白话Vue2和Vue3双向数据绑定的原理 下面用大白话来给你详细介绍一下Vue2和Vue3双向数据绑定的原理: Vue2双向数据绑定原理 Vue2的双向数据绑定主要是通过Object.defineProperty()这个方法来实现的,就好像有一个小管家在帮你看着数据和页面。 数据劫…...
Remainder Problem CF1207F
题目:题目链接 题目大意 题目描述 给你一个长度为 500000 的序列,初值为 0 ,你要完成 q 次操作,操作有如下两种: 1 x y : 将下标为 x 的位置的值加上 y2 x y : 询问所有下标模 x 的结果为 y 的位置的值之和 输入格…...
SpringBoot之自定义简单的注解和AOP
1.引入依赖 <!-- AOP依赖--> <dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.8</version> </dependency>2.自定义一个注解 package com.example.springbootdemo3.an…...
2.2 添加注释
版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。 注释是为了方便理解代码含义而添加的简短的解释性说明。在编译时,编辑器不会将注释加入最终生成的文件中,不…...
自由学习记录(38)
python语法 def def print_receipt (store_name, items, total_price, cashier"Self-Checkout", payment_method"Credit Card"): Python 的 函数定义 语法 def print_receipt(...) → 定义了一个名为 print_receipt 的函数。store_name, items, total_…...
【SQL实验】触发器
下载素材文件”tsgl”、“成绩管理”,将tsgl.bak和成绩管理.bak数据库还原到库中【导入操作在之前的文章中详细讲过】 触发器 1、为图书表设置更新触发器,根据总编号来更新书名、作者、出版社、分类号和单价(根据总编号找到相应记录,然后更新书名、作者…...
C语言:二维数组在内存中是怎么存储的
目录 1. 二维数组的定义: 2. 行主序存储: 具体内存排列: 3. 如何通过指针访问数据: 4. 总结: 在 C 语言中,二维数组是按 行主序(row-major order) 存储的。也就是说,…...
CPU多级缓存机制
目录 一、前置知识 ---- CPU的核心 1.1. 单核与多核CPU 二、CPU多级缓存机制 三. 缓存的基本结构/缓存的存储结构 四、CPU缓存的运作流程/工作原理 五、CPU多级缓存机制的工作原理【简化版】 5.1. 缓存访问的过程 (5.1.1) L1缓存(一级缓存)访问 …...
Ansible剧本-playbook
Ansible剧本-playbook 1 playbook基础1.1 简介1.2 playbook的组成结构Task 任务列表任务报错,如何继续执行响应事件Handler 1.3 常用选项执行playbookplaybook查询帮助信息校验playbook语法测试playbook能否正常运行 2 变量 的定义方式2.1 定义规则2.2 vars 变量2.3…...
神经网络八股(3)
1.什么是梯度消失和梯度爆炸 梯度消失是指梯度在反向传播的过程中逐渐变小,最终趋近于零,这会导致靠前层的神经网络层权重参数更新缓慢,甚至不更新,学习不到有用的特征。 梯度爆炸是指梯度在方向传播过程中逐渐变大,…...
SmartMediakit之音视频直播技术的极致体验与广泛应用
引言 在数字化时代,音视频直播技术已经深入到各个行业和领域,成为信息传递和交流的重要手段。视沃科技自2015年成立以来,一直致力于为传统行业提供极致体验的音视频直播技术解决方案,其旗下的大牛直播SDK凭借强大的功能和卓越的性…...
【R包】tidyplots----取代ggplot2的科研绘图利器
文章目录 介绍安装Usage文档参考 介绍 tidyplots----取代ggplot2的科研绘图利器。tidyplots的目标是简化为科学论文准备出版的情节的创建。它允许使用一致和直观的语法逐渐添加,删除和调整情节组件。 安装 You can install the released version of tidyplots fro…...
DeepSeek 15天指导手册——从入门到精通 PDF(附下载)
DeepSeek使用教程系列--DeepSeek 15天指导手册——从入门到精通pdf下载: https://pan.baidu.com/s/1PrIo0Xo0h5s6Plcc_smS8w?pwd1234 提取码: 1234 或 https://pan.quark.cn/s/2e8de75027d3 《DeepSeek 15天指导手册——从入门到精通》以系统化学习路径为核心&…...
C++知识点总结与复习
c中常见的关键字(面试题中经常出现) const 总结常见用法: const int a; //定义了常量整形的变量 a; 常量表示不可修改,定义的时候必须初始化。除此之外,和 int a;使用一样。 const int * p;//定义了指向常量整形变量的指针。…...
微信小程序实现拉卡拉支付
功能需求:拉卡拉支付(通过跳转拉卡拉平台进行支付),他人支付(通过链接进行平台跳转支付) 1.支付操作 //支付 const onCanStartPay async (obj) > {uni.showLoading({mask: true})// 支付接口获取需要传…...
