C++---雙指針

在C++編程中,雙指針算法是一種高效的解題思路,其核心是通過設置兩個指針(或索引)遍歷數據結構(如數組、鏈表、字符串等),利用指針的移動規則減少無效操作,從而將時間復雜度從暴力解法的O(n2)優化至O(n)或O(n log n)。這種算法廣泛應用于鏈表操作、數組處理、字符串匹配等場景。

一、雙指針算法的核心思想

雙指針算法的本質是通過兩個指針的協同移動,縮小問題的處理范圍。與暴力解法中嵌套循環的“盲目遍歷”不同,雙指針的移動具有明確的邏輯(如基于數據的有序性、結構特性等),從而避免冗余計算。

其核心優勢體現在:

  • 時間優化:將多層循環轉化為單層遍歷,降低時間復雜度;
  • 空間優化:多數情況下無需額外空間(原地操作),空間復雜度可保持O(1);
  • 邏輯清晰:通過指針的移動規則直觀反映問題的解決思路。

二、雙指針的常見類型及應用場景

根據指針的移動方向和作用,雙指針可分為三大類:快慢指針左右指針同向指針。以下結合具體場景詳細講解。

(一)快慢指針(Floyd’s Tortoise and Hare)

快慢指針指兩個指針以不同速度遍歷數據結構(如鏈表中,快指針每次走2步,慢指針每次走1步)。其核心應用是處理鏈表中的環問題查找特定位置(如中間節點)。

1. 鏈表環檢測(LeetCode 141)

問題:判斷一個單鏈表是否存在環。
暴力解法:用哈希表記錄訪問過的節點,若重復訪問則有環,時間O(n)但空間O(n)。
雙指針解法

  • 設快指針fast和慢指針slow,初始均指向頭節點;
  • fast每次移動2步,slow每次移動1步;
  • 若鏈表有環,fast會先進入環并繞環移動,最終與slow在環內相遇;
  • fast到達nullptr,則無環。

代碼實現

struct ListNode {int val;ListNode *next;ListNode(int x) : val(x), next(nullptr) {}
};bool hasCycle(ListNode *head) {if (head == nullptr || head->next == nullptr) return false;ListNode *slow = head;ListNode *fast = head->next; // 初始錯開,避免直接相遇while (slow != fast) {if (fast == nullptr || fast->next == nullptr) return false;slow = slow->next;       // 慢指針走1步fast = fast->next->next; // 快指針走2步}return true;
}

時間復雜度:O(n)(無環時fast走n/2步;有環時相遇前slow最多走n步)。
空間復雜度:O(1)(僅用兩個指針)。

2. 尋找環的入口(LeetCode 142)

問題:若鏈表有環,找到環的入口節點。
算法思路

  1. 先用快慢指針判斷有環,記錄相遇點meet
  2. slow從頭節點出發,fastmeet出發,兩者均每次走1步;
  3. 兩指針再次相遇的節點即為環的入口。

原理:設頭節點到入口距離為a,入口到相遇點距離為b,環長為c。則相遇時:

  • slow走了a + b
  • fast走了a + b + k*c(繞環k圈);
  • fast速度是slow的2倍,故2*(a + b) = a + b + k*ca = k*c - b b=k*c-a
  • 因此,slow從頭部走a步,與fastmeetk*c - b步(等價于繞環k圈后回到入口)相遇。

重置slow = 0,fast仍在meet處(等價于初始走了a+b),當slow=a(slow走了a步),fast=a+b+kc-b=a+kc,所以a,b在環的入口相遇
在這里插入圖片描述

代碼實現

ListNode *detectCycle(ListNode *head) {if (head == nullptr) return nullptr;ListNode *slow = head, *fast = head;// 第一步:找到相遇點do {if (fast->next == nullptr || fast->next->next == nullptr) return nullptr;slow = slow->next;fast = fast->next->next;} while (slow != fast);// 第二步:尋找入口slow = head;while (slow != fast) {slow = slow->next;fast = fast->next;}return fast;
}
3. 尋找鏈表的中間節點(LeetCode 876)

問題:返回單鏈表的中間節點(若長度為偶數,返回第二個中間節點)。
算法思路

  • 快指針每次走2步,慢指針每次走1步;
  • fast到達尾節點時,slow恰好指向中間節點。

代碼實現

ListNode* middleNode(ListNode* head) {ListNode *slow = head, *fast = head;while (fast != nullptr && fast->next != nullptr) {slow = slow->next;fast = fast->next->next;}return slow;
}

(二)左右指針(相向指針)

左右指針指兩個指針分別從數據結構的兩端出發,相向而行(左指針從左向右,右指針從右向左),多用于有序數組/字符串的處理。其核心是利用數據的有序性,通過指針移動排除無效解。

1. 兩數之和(有序數組版,LeetCode 167)

問題:給定有序數組nums和目標值target,找到兩個數使得和為target,返回索引(下標從1開始)。
暴力解法:嵌套循環遍歷所有組合,時間O(n2)。
雙指針解法

  • 左指針left初始指向0,右指針right指向n-1
  • 計算當前和sum = nums[left] + nums[right]
    • sum == target,返回[left+1, right+1]
    • sum > target,說明右指針太大,right--
    • sum < target,說明左指針太小,left++

原理:數組有序保證了指針移動的有效性——當sum > target時,減小右指針可降低總和;當sum < target時,增大左指針可提高總和,無需檢查其他組合。

代碼實現

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 + 1, right + 1};} else if (sum > target) {right--;} else {left++;}}return {-1, -1}; // 題目保證有解,此行為冗余
}

時間復雜度:O(n),空間復雜度O(1)。

2. 反轉字符串(LeetCode 344)

問題:原地反轉字符串(如["h","e","l","l","o"]["o","l","l","e","h"])。
算法思路

  • 左指針left指向0,右指針right指向n-1
  • 交換nums[left]nums[right],然后left++right--,直到left >= right

代碼實現

void reverseString(vector<char>& s) {int left = 0, right = s.size() - 1;while (left < right) {swap(s[left], s[right]);left++;right--;}
}
3. 二分查找(本質是左右指針的變體)

二分查找中,leftright指針分別指向搜索范圍的兩端,通過比較中間值與目標值,不斷縮小范圍,本質是左右指針的“跳躍式移動”。

代碼實現

int search(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1;while (left <= right) {int mid = left + (right - left) / 2; // 避免溢出if (nums[mid] == target) return mid;else if (nums[mid] < target) left = mid + 1;else right = mid - 1;}return -1;
}

(三)同向指針(一前一后指針)

同向指針指兩個指針從同一端出發,沿相同方向移動(通常一個在前“探索”,一個在后“記錄”),多用于數組去重、子數組/子串問題

1. 刪除有序數組中的重復項(LeetCode 26)

問題:原地刪除有序數組中的重復元素,返回新長度(如[1,1,2]→長度2,數組變為[1,2])。
算法思路

  • 慢指針slow記錄有效元素的尾位置(初始0);
  • 快指針fast遍歷數組(初始1);
  • nums[fast] != nums[slow],說明找到新元素,slow++并將nums[fast]復制到nums[slow]
  • 最終slow + 1為新長度。

代碼實現

int removeDuplicates(vector<int>& nums) {if (nums.empty()) return 0;int slow = 0;for (int fast = 1; fast < nums.size(); fast++) {if (nums[fast] != nums[slow]) {slow++;nums[slow] = nums[fast];}}return slow + 1;
}
2. 移動零(LeetCode 283)

問題:將數組中的0移到末尾,保持非零元素的相對順序(如[0,1,0,3,12][1,3,12,0,0])。
算法思路

  • 慢指針slow記錄非零元素的尾位置(初始0);
  • 快指針fast遍歷數組,若nums[fast] != 0,則交換nums[slow]nums[fast]slow++
  • 遍歷結束后,slow及之后的位置全部賦0。

代碼實現

void moveZeroes(vector<int>& nums) {int slow = 0;for (int fast = 0; fast < nums.size(); fast++) {if (nums[fast] != 0) {swap(nums[slow], nums[fast]);slow++;}}// 剩余位置補0(可選,因原數組0已被交換到后面)for (int i = slow; i < nums.size(); i++) {nums[i] = 0;}
}
3. 滑動窗口(同向指針的高級應用)

滑動窗口是同向指針的典型場景,用于解決子數組/子串的最值問題(如最長、最短、包含特定元素等)。其核心是用leftright指針維護一個“窗口”,通過移動right擴張窗口,移動left收縮窗口,在O(n)時間內找到最優解。

示例:長度最小的子數組(LeetCode 209)
問題:給定數組nums和目標值s,找到和≥s的最短子數組長度(若無則返回0)。

算法思路

  • leftright初始均為0,sum記錄窗口內元素和;
  • 移動right,將nums[right]加入sum
  • sum ≥ s時,嘗試移動left縮小窗口,更新最小長度;
  • 重復直至right遍歷結束。

代碼實現

int minSubArrayLen(int s, vector<int>& nums) {int n = nums.size();int min_len = INT_MAX;int left = 0, sum = 0;for (int right = 0; right < n; right++) {sum += nums[right];// 收縮窗口while (sum >= s) {min_len = min(min_len, right - left + 1);sum -= nums[left];left++;}}return min_len == INT_MAX ? 0 : min_len;
}

時間復雜度:O(n)(每個元素被rightleft各訪問一次)。

三、雙指針算法的進階技巧

  1. 指針初始化的靈活性
    快慢指針初始可不同步(如環檢測中fastslow超前一步);左右指針可從非端點出發(如處理子數組時限制窗口范圍)。

  2. 結合數據結構特性
    有序數組優先考慮左右指針;鏈表問題優先考慮快慢指針;子串問題優先考慮滑動窗口(同向指針)。

  3. 多指針擴展
    某些場景需3個指針(如荷蘭國旗問題:用leftmidright劃分0、1、2),核心思想與雙指針一致。

  4. 邊界條件處理
    需注意指針越界(如鏈表fast->next是否為nullptr)、空數據結構(如數組長度為0)等特殊情況。


雙指針算法是C++編程中優化效率的核心思想之一,其核心在于通過指針的協同移動減少無效遍歷。根據應用場景可分為快慢指針(鏈表環、中間節點)、左右指針(有序數組、反轉)、同向指針(去重、滑動窗口)三大類,每種類型均通過明確的移動規則將時間復雜度從O(n2)降至O(n)。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/94460.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/94460.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/94460.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【LLM】GLM-4.5模型架構和原理

note 文章目錄note一、GLM-4.5模型二、Slime RL強化學習訓練架構Reference一、GLM-4.5模型 大模型進展&#xff0c;GLM-4.5技術報告,https://arxiv.org/pdf/2508.06471&#xff0c;https://github.com/zai-org/GLM-4.5&#xff0c;包括GLM-4.5&#xff08;355B總參數&#xff…

LLM 中增量解碼與模型推理解讀

在【LLM】LLM 中 token 簡介與 bert 實操解讀一文中對 LLM 基礎定義進行了介紹&#xff0c;本文會對 LLM 中增量解碼與模型推理進行解讀。 一、LLM 中增量解碼定義 增量解碼&#xff08;Incremental Decoding&#xff09;是指在自回歸文本生成過程中&#xff0c;模型每次只計…

1.Spring Boot:超越配置地獄,重塑Java開發體驗

目錄 一、Spring框架&#xff1a;偉大的基石 歷史背景與挑戰 Spring的革命性貢獻 新的挑戰&#xff1a;配置地獄 二、Spring Boot&#xff1a;約定大于配置的革命 四大核心特性 1. 快速創建獨立應用 2. 自動配置&#xff1a;智能化的魔法 3. 起步依賴&#xff1a;依賴管…

assert使用方法

assert 是 Python 中用來進行 調試 和 驗證 的一個關鍵字&#xff0c;它用于測試一個 條件表達式 是否為真。如果條件為假&#xff0c;assert 會拋出一個 AssertionError 異常&#xff0c;通常帶有錯誤信息。語法&#xff1a;assert condition, "Error message"condi…

【實習總結】快速上手Git:關鍵命令整理

目錄 git的四大工作區域 git首次配置 克隆遠程倉庫 提交代碼到遠程倉庫 查看文件狀態&#xff08;可選&#xff09; 添加文件到暫存區 將暫存區的內容提交到本地倉庫 將本地的提交上傳到遠程倉庫 拉取并合并代碼 第一種方式 第二種方式 分支管理 查看與創建分支 …

02-開發環境搭建與工具鏈

第2課&#xff1a;開發環境搭建與工具鏈 &#x1f4da; 課程目標 掌握DevEco Studio的下載、安裝和配置熟悉HMS Core&#xff08;華為移動服務&#xff09;的使用了解鴻蒙模擬器與真機調試環境掌握必備開發工具的使用 &#x1f6e0;? DevEco Studio環境搭建 2.1 下載與安裝…

刪掉一個元素以后全為1的最長子數組-滑動窗口

1493. 刪掉一個元素以后全為 1 的最長子數組 - 力扣&#xff08;LeetCode&#xff09; Solution #include<iostream> #include<vector> using namespace std;class Solution { public://滑動窗口//動態維護一個窗口&#xff0c;窗口內只能有1個0&#xff0c;記錄窗…

【計算機網絡 | 第8篇】編碼與調制

文章目錄通信系統中的編碼與調制&#xff1a;從信道基礎到信號傳輸技術一、信道與通信電路&#x1f342;二、三種基本通信方式&#x1f4d6;1. 單向通信&#xff08;單工通信&#xff09;2. 雙向交替通信&#xff08;半雙工通信&#xff09;3. 雙向同時通信&#xff08;全雙工通…

當AI遇上終端:Gemini CLI的技術魔法與架構奧秘

"代碼不僅僅是指令的集合&#xff0c;更是思想的載體。當AI與終端相遇&#xff0c;會碰撞出怎樣的火花&#xff1f;" 在這個AI技術日新月異的時代&#xff0c;Google推出的Gemini CLI無疑是一顆璀璨的明星。它不僅僅是一個命令行工具&#xff0c;更是一個將人工智能無…

ViLU: Learning Vision-Language Uncertainties for Failure Prediction

研究方向&#xff1a;Image Captioning1. 論文介紹本文提出ViLU&#xff08;Vision-Language Uncertainties&#xff09;&#xff0c;一個用于學習視覺語言不確定性量化&#xff08;UQ&#xff09;和檢測視覺語言模型故障的事后框架。使用VLMs進行量化&#xff08;UQ&#xff0…

數據集筆記:百度地圖高德地圖坐標互轉

1 為什么會有高德坐標系和百度坐標系&#xff1f;根據《測繪法》和國家保密法規&#xff0c;在中國大陸范圍內的地理坐標數據必須做加密處理&#xff0c;不允許直接使用 WGS84&#xff08;openstreetmap&#xff09;所以出現了GCJ-02 和 BD-09高德、騰訊、谷歌中國都遵循 GCJ-0…

SkyWalking高效線程上下文管理機制:確保調用鏈中traceId來自同一個請求

SkyWalking Agent 能確保獲取到“正確”的 traceId,其核心在于它建立并維護了一套高效的線程上下文管理機制。這套機制確保了即使在復雜的多線程、異步環境下,也能將正確的上下文(包含 traceId)與當前正在執行的代碼邏輯關聯起來。 其工作原理可以概括為下圖所示的流程: …

Kafka-Eagle安裝

目錄Eagle環境安裝Mysql環境準備Kafka環境準備Eagle安裝Kafka-Eagle框架可以監控Kafka集群的整體運行情況&#xff0c;在生產環境中經常使用 Eagle環境安裝 Mysql環境準備 Eagle的安裝依賴于Mysql&#xff0c;Mysql主要用來存儲可視化展示的數據 將mysql文件夾及里面所有內…

Matlab系列(005) 一 歸一化

目錄1、前言2、什么是歸一化&#xff1f;3、為什么要進行歸一化4、歸一化方法詳解與Matlab實現5、總結1、前言 ? ??歸一化技術是數據預處理的核心環節&#xff0c;本文將深度解析主流歸一化方法&#xff0c;提供可復現Matlab代碼&#xff0c;并探討其在各領域中的應用場景。…

【K8s】整體認識K8s之namespace

命名空間將資源劃分為相互隔離的組。kubectl get namespace/ns系統默認創建四個namespace&#xff0c;分別是default、kube-node-lease、kube-public、kube-system。default 沒有指明使用其它命名空間的對象所使用的默認命名空間、kube-system 系統創建對象所使用的命名空間。…

rust語言 (1.88) egui (0.32.1) 學習筆記(逐行注釋)(十八) 使用表格

使用表格egui_extras::TableBuilder // Cargo.toml [dependencies] eframe "0.32.1" egui "0.32.1" egui_extras "0.32.1"egui_extras::Column::auto() 列寬根據內容自動計算.resizable(true) 允許用戶手動拖動調整列寬 fn main() -> efra…

【C#】構造函數實用場景總結

文章目錄前言一、構造函數是什么&#xff1f;二、構造函數的用法1.初始化對象&#xff0c;避免無效狀態2 初始化靜態成員3 構造函數重載4.構造函數鏈5. 單例模式&#xff0c;多次實例化保持一個對象6. 依賴注入7. 初始化只讀對象前言 構造函數是我們平常編程里經常能碰到的老伙…

LLM預訓練架構全解析:從零構建一個語言世界的“操作系統”

導讀&#xff1a;作為開發者&#xff0c;我們每天都在import或#include各種庫&#xff0c;我們信任這些由無數代碼構成的底層依賴。那么&#xff0c;當我們調用一個LLM時&#xff0c;它所依賴的那個更底層的、無形的**“語言操作系統”**&#xff0c;又是如何被“編譯”出來的&…

Linux服務測試題(DNS,NFS,DHCP,HTTP)

一&#xff0c;實驗拓撲&#xff1a;二&#xff0c;需求APPSRV&#xff1a;主機名&#xff1a;appsrv.example.comip地址&#xff1a;192.168.100.10網關&#xff1a;192.168.100.254網卡為NAT模式STORAGESRV&#xff1a;主機名&#xff1a;storagesrv.example.comip地址&#…

DevOps 簡介及就業前景

DevOps 簡介及就業前景 目錄 DevOps簡介核心概念重難點解析具體場景使用就業前景學習路徑最佳實踐 DevOps簡介 什么是DevOps DevOps是Development&#xff08;開發&#xff09;和Operations&#xff08;運維&#xff09;的組合詞&#xff0c;是一種軟件開發和IT運維的文化…