C++中双指针、多指针的实际用途是什么?
嘿,作为刚接触C++的新手,搞不懂双指针、三指针到底干啥用太正常了——我当初学的时候也对着这些概念挠头半天!别着急,我用最直白的方式给你拆解清楚,搭配简单的代码例子,保证你看完能get到它们的核心作用。
双指针其实就是用两个指针变量(可以是指向数组元素的指针,也可以是链表的节点指针)配合着移动,核心是减少不必要的遍历,把时间复杂度降下来,常见的有两种典型用法:
1. 左右指针(相向移动):处理有序数组/字符串的问题
比如经典的“有序数组中找两个数之和等于目标值”,如果不用双指针,你可能会嵌套两层循环遍历,时间复杂度是O(n²),但用左右指针的话,只需要O(n)的时间。
举个代码例子:
#include <vector> #include <iostream> using namespace std; vector<int> twoSum(vector<int>& nums, int target) { int left = 0; // 左指针从数组开头出发 int 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 {}; // 没找到返回空 } int main() { vector<int> nums = {2,7,11,15}; int target = 9; vector<int> result = twoSum(nums, target); cout << "索引:" << result[0] << " 和 " << result[1] << endl; return 0; }
这里的逻辑很简单:因为数组是有序的,所以通过调整左右指针的位置,就能快速缩小范围,不用挨个试所有组合。类似的还有回文字符串判断、有序数组去重,都是这个思路。
2. 快慢指针(同向移动):处理链表或数组的“追击”问题
比如判断链表有没有环,或者找链表的中间节点,快慢指针简直是神器。
举个判断链表环的例子:
#include <iostream> using namespace std; struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(NULL) {} }; bool hasCycle(ListNode *head) { if (head == nullptr || head->next == nullptr) { return false; // 空链表或只有一个节点,肯定没环 } ListNode* slow = head; // 慢指针每次走1步 ListNode* fast = head->next; // 快指针每次走2步 while (slow != fast) { if (fast == nullptr || fast->next == nullptr) { return false; // 快指针走到头了,说明没环 } slow = slow->next; fast = fast->next->next; } return true; // 快慢指针相遇,说明有环 } int main() { // 构造一个带环的链表:1->2->3->2 ListNode* head = new ListNode(1); head->next = new ListNode(2); head->next->next = new ListNode(3); head->next->next->next = head->next; cout << (hasCycle(head) ? "有环" : "无环") << endl; return 0; }
这里的思路就像操场跑步,跑得快的人总会追上跑得慢的——如果链表有环,快指针一定会和慢指针相遇;如果没环,快指针会先走到链表末尾。另外找链表中间节点也是一样,快指针走到头时,慢指针正好在中间。
三指针其实是双指针的延伸,主要用来解决三个元素组合的问题,最典型的就是“有序数组的三数之和”,要求找出所有不重复的三元组,使得它们的和为0。
直接上代码例子:
#include <vector> #include <algorithm> #include <iostream> using namespace std; vector<vector<int>> threeSum(vector<int>& nums) { vector<vector<int>> result; sort(nums.begin(), nums.end()); // 先排序,方便用指针调整 int n = nums.size(); for (int i = 0; i < n; i++) { if (nums[i] > 0) break; // 第一个数就大于0,后面的数更大,不可能和为0 if (i > 0 && nums[i] == nums[i-1]) continue; // 跳过重复的第一个数 int left = i + 1; // 左指针在i的下一位 int right = n - 1; // 右指针在数组末尾 while (left < right) { int sum = nums[i] + nums[left] + nums[right]; if (sum == 0) { result.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 result; } int main() { vector<int> nums = {-1,0,1,2,-1,-4}; vector<vector<int>> result = threeSum(nums); for (auto& triplet : result) { cout << triplet[0] << ", " << triplet[1] << ", " << triplet[2] << endl; } return 0; }
这里的逻辑是:先固定第一个数,然后用左右两个指针在剩下的元素里找另外两个数,让三者之和为0。本质上是把三数问题转化成了双指针的两数问题,时间复杂度从O(n³)降到了O(n²),效率提升非常明显。
不管是双指针还是三指针,它们的核心都是用多个指针的配合移动,减少重复的遍历操作,从而优化算法的时间复杂度,把原本需要嵌套多层循环的问题,简化成线性或者线性平方级别的时间消耗。而且这些思路不仅限于C++,在其他语言里也一样通用,掌握了之后能解决很多算法题里的经典问题。
内容的提问来源于stack exchange,提问作者Mark Green

