鏈表高級操作與算法

鏈表是數據結構中的基礎,但也是面試和實際開發中的重點考察對象。今天我們將深入探討鏈表的高級操作和常見算法,讓你能夠輕松應對各種鏈表問題。

1.?鏈表翻轉 - 最經典的鏈表問題

鏈表翻轉是面試中的常見題目,也是理解鏈表指針操作的絕佳練習。

1.1 迭代方法實現

ListNode*?reverseList(ListNode*?head)?{ListNode*?prev?=?nullptr;ListNode*?curr?=?head;while?(curr?!=?nullptr)?{ListNode*?nextTemp?=?curr->next;?//?暫存下一個節點curr->next?=?prev;??????????????//?反轉指針prev?=?curr;????????????????????//?prev?前進curr?=?nextTemp;????????????????//?curr?前進}return?prev;?//?新的頭節點}

這種方法就像是在倒一疊書:你需要一本一本地翻轉,過程中需要記住當前的書、前一本書和下一本書的位置。時間復雜度為?O(n),空間復雜度為 O(1)。

1.2?遞歸方法實現

ListNode*?reverseList(ListNode*?head)?{//?基本情況:空鏈表或只有一個節點if?(head?==?nullptr?||?head->next?==?nullptr)?{return?head;}//?遞歸反轉剩余部分ListNode*?newHead?=?reverseList(head->next);//?改變指針方向head->next->next?=?head;head->next?=?nullptr;return?newHead;}

遞歸方法更像是魔法,它先抵達鏈表尾部,然后在"歸"的過程中一個接一個地反轉指針。這就像是我們先走到隊列末尾,然后從末尾開始依次讓每個人面朝相反方向。

2. 檢測環形鏈表

在許多實際應用中,確定鏈表是否存在環(循環)非常重要,因為環會導致無限循環。

2.1 快慢指針法

bool?hasCycle(ListNode?*head)?{if?(!head?||?!head->next)?return?false;ListNode?*slow?=?head;ListNode?*fast?=?head;while?(fast?&&?fast->next)?{slow?=?slow->next;??????//?慢指針每次走一步fast?=?fast->next->next;?//?快指針每次走兩步if?(slow?==?fast)?return?true;?//?相遇則存在環}return?false;?//?如果fast到達NULL,則無環}

這就像操場上跑步的兩個人:一個跑得快,一個跑得慢。如果跑道是環形的,快的人最終會從后面追上慢的人;如果跑道是直線,快的人會先到終點。

2.2 找到環的入口點

ListNode?*detectCycle(ListNode?*head)?{if?(!head?||?!head->next)?return?nullptr;ListNode?*slow?=?head;ListNode?*fast?=?head;bool?hasCycle?=?false;//?檢測是否有環while?(fast?&&?fast->next)?{slow?=?slow->next;fast?=?fast->next->next;if?(slow?==?fast)?{hasCycle?=?true;break;}}if?(!hasCycle)?return?nullptr;//?找到環的入口點slow?=?head;while?(slow?!=?fast)?{slow?=?slow->next;fast?=?fast->next;}return?slow;?//?環的入口點}

這個算法使用了一個有趣的數學結論:當快慢指針相遇后,將慢指針重置到鏈表頭,然后兩個指針以相同的速度前進,它們會在環的入口處相遇。這就像兩個人在環形操場不同位置出發,經過一定圈數后在某個特定點相遇。

3. 找到鏈表的中間節點

找到鏈表的中間節點對于很多算法都是關鍵一步,比如排序或二分查找。


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

這個技巧也利用了快慢指針。想象你和朋友沿著一條路走,朋友的速度是你的兩倍。當朋友到達終點時,你恰好在中間位置。如果鏈表長度為奇數,返回的是正中間的節點;如果為偶數,則返回的是中間偏右的節點。

4.?合并兩個有序鏈表

將兩個已排序的鏈表合并成一個新的排序鏈表是另一個常見問題。


ListNode*?mergeTwoLists(ListNode*?l1,?ListNode*?l2)?{//?創建啞節點作為合并鏈表的頭ListNode?dummy(0);ListNode*?tail?=?&dummy;while?(l1?&&?l2)?{if?(l1->val?<?l2->val)?{tail->next?=?l1;l1?=?l1->next;}?else?{tail->next?=?l2;l2?=?l2->next;}tail?=?tail->next;}//?連接剩余部分tail->next?=?l1???l1?:?l2;return?dummy.next;}

這就像合并兩隊排好隊的人,每次從兩隊的隊頭選擇較小的一個人加入新隊伍。

5. 判斷回文鏈表

回文是指從前向后和從后向前讀都相同的序列。判斷一個鏈表是否為回文鏈表是一個有趣的挑戰。

bool?isPalindrome(ListNode*?head)?{if?(!head?||?!head->next)?return?true;//?找到中間節點ListNode*?slow?=?head;ListNode*?fast?=?head;while?(fast->next?&&?fast->next->next)?{slow?=?slow->next;fast?=?fast->next->next;}//?反轉后半部分ListNode*?secondHalf?=?reverseList(slow->next);//?比較前半部分和反轉后的后半部分ListNode*?p1?=?head;ListNode*?p2?=?secondHalf;bool?result?=?true;while?(p2)?{if?(p1->val?!=?p2->val)?{result?=?false;break;}p1?=?p1->next;p2?=?p2->next;}//?恢復鏈表原狀(可選)slow->next?=?reverseList(secondHalf);return?result;}

這個算法的思路是:先找到鏈表的中點,然后反轉后半部分,最后從兩端向中間比較。這就像檢查一個單詞是否為回文:我們可以從兩端同時讀取并比較。

6. 刪除鏈表中的倒數第N個節點

這是一道考察鏈表遍歷技巧的經典題目。

ListNode*?removeNthFromEnd(ListNode*?head,?int?n)?{ListNode?dummy(0);dummy.next?=?head;ListNode*?first?=?&dummy;ListNode*?second?=?&dummy;//?第一個指針先前進?n+1?步for?(int?i?=?0;?i?<=?n;?i++)?{first?=?first->next;}//?兩個指針一起前進,直到第一個指針到達末尾while?(first)?{first?=?first->next;second?=?second->next;}//?刪除倒數第?n?個節點ListNode*?toDelete?=?second->next;second->next?=?second->next->next;delete?toDelete;return?dummy.next;}

這個技巧使用了兩個指針,兩者之間保持固定距離(n+1)。當第一個指針到達鏈表末尾時,第二個指針恰好指向倒數第?n+1 個節點,這樣我們就可以刪除倒數第 n?個節點了。這就像一列行進的士兵,當排頭到達終點時,排尾的位置也是確定的。

7. 劃分鏈表 -?奇偶節點分離

將鏈表按照奇偶位置劃分,先奇數位置的節點,再偶數位置的節點。

ListNode*?oddEvenList(ListNode*?head)?{if?(!head?||?!head->next)?return?head;ListNode*?odd?=?head;???????????//?奇數節點ListNode*?even?=?head->next;????//?偶數節點ListNode*?evenHead?=?even;??????//?保存偶數鏈表的頭while?(even?&&?even->next)?{odd->next?=?even->next;?????//?連接奇數節點odd?=?odd->next;even->next?=?odd->next;?????//?連接偶數節點even?=?even->next;}odd->next?=?evenHead;???????????//?連接奇偶兩個鏈表return?head;}

這個算法將鏈表分成兩部分:奇數位置節點和偶數位置節點,然后將偶數鏈表接在奇數鏈表后面。它就像是把隊伍中的人按單雙號分成兩隊,然后再把第二隊排在第一隊后面。

8. 復雜鏈表的復制

一個復雜鏈表,其中每個節點除了有一個 next 指針外,還有一個 random 指針,隨機指向鏈表中的任意節點或?NULL。復制這樣的鏈表是一個挑戰。

Node*?copyRandomList(Node*?head)?{if?(!head)?return?nullptr;//?第一步:在每個原始節點后創建一個新節點Node*?curr?=?head;while?(curr)?{Node*?copy?=?new?Node(curr->val);copy->next?=?curr->next;curr->next?=?copy;curr?=?copy->next;}//?第二步:處理random指針curr?=?head;while?(curr)?{if?(curr->random)?{curr->next->random?=?curr->random->next;}curr?=?curr->next->next;}//?第三步:分離兩個鏈表Node?dummy(0);Node*?newTail?=?&dummy;curr?=?head;while?(curr)?{newTail->next?=?curr->next;newTail?=?newTail->next;curr->next?=?curr->next->next;curr?=?curr->next;}return?dummy.next;}

這個巧妙的算法分三步:首先,在每個原始節點后創建其復制節點;然后,利用這種交替的結構設置random指針;最后,分離兩個鏈表。這就像是為一組人創建克隆體,每個克隆體站在原人后面,然后根據原有的社交關系建立克隆體之間的聯系,最后將克隆體組成新的隊伍。

9. 實際應用案例

9.1?LRU (最近最少使用) 緩存

LRU 緩存是一種常見的緩存淘汰策略,可以用鏈表實現。

class?LRUCache?{private:int?capacity;list<pair<int,?int>>?cache;?//?key-value對的鏈表unordered_map<int,?list<pair<int,?int>>::iterator>?map;?//?哈希表,快速找到key在鏈表中的位置public:LRUCache(int?capacity)?:?capacity(capacity)?{}int?get(int?key)?{auto?it?=?map.find(key);if?(it?==?map.end())?return?-1;//?將訪問的節點移到鏈表前端cache.splice(cache.begin(),?cache,?it->second);return?it->second->second;}void?put(int?key,?int?value)?{auto?it?=?map.find(key);if?(it?!=?map.end())?{//?更新已存在的keyit->second->second?=?value;cache.splice(cache.begin(),?cache,?it->second);return;}//?緩存已滿,刪除最久未使用的元素if?(cache.size()?==?capacity)?{int?oldKey?=?cache.back().first;cache.pop_back();map.erase(oldKey);}//?插入新元素到前端cache.emplace_front(key,?value);map[key]?=?cache.begin();}};

在這個實現中,我們使用雙向鏈表保存鍵值對,最近使用的在前,最久未使用的在后。哈希表用于O(1)時間內找到鏈表中的節點。這個例子展示了如何將鏈表和哈希表結合使用,實現高效的緩存機制。

9.2 多項式表示

鏈表可以用來表示多項式,每個節點代表一項,包含系數和指數。


struct?PolyNode?{int?coef;??//?系數int?exp;???//?指數PolyNode*?next;PolyNode(int?c,?int?e)?:?coef(c),?exp(e),?next(nullptr)?{}};//?兩個多項式相加PolyNode*?addPoly(PolyNode*?poly1,?PolyNode*?poly2)?{PolyNode?dummy(0,?0);PolyNode*?tail?=?&dummy;while?(poly1?&&?poly2)?{if?(poly1->exp?>?poly2->exp)?{tail->next?=?new?PolyNode(poly1->coef,?poly1->exp);poly1?=?poly1->next;}?else?if?(poly1->exp?<?poly2->exp)?{tail->next?=?new?PolyNode(poly2->coef,?poly2->exp);poly2?=?poly2->next;}?else?{int?sumCoef?=?poly1->coef?+?poly2->coef;if?(sumCoef?!=?0)?{tail->next?=?new?PolyNode(sumCoef,?poly1->exp);}poly1?=?poly1->next;poly2?=?poly2->next;}if?(tail->next)?tail?=?tail->next;}//?處理剩余項tail->next?=?poly1???poly1?:?poly2;return?dummy.next;}

這個例子展示了如何使用鏈表表示和操作多項式,是鏈表在代數計算中的一個實際應用。

10. 性能優化與實踐建議

  1. 避免頻繁分配/釋放內存:在處理大量鏈表操作時,考慮使用內存池或節點緩存來減少內存分配的開銷。
  1. 使用啞節點簡化代碼:在處理鏈表頭部可能變化的情況時,使用啞節點(dummy node)可以統一處理流程,避免特殊情況。
  1. 理解并靈活運用快慢指針:快慢指針是鏈表操作的利器,掌握它可以解決大量問題,如檢測環、找中點等。
  1. 注意指針操作順序:在修改鏈表結構時,務必注意指針操作的順序,避免丟失節點引用。
  1. 學會利用遞歸思想:某些鏈表問題用遞歸解決會更加簡潔優雅,如反轉鏈表、合并有序鏈表等。

總結

鏈表作為一種基礎數據結構,其靈活性和多變性使得它在許多場景下都有應用。通過掌握本文介紹的高級操作和算法,你將能夠應對大部分鏈表相關的編程挑戰。

記住,鏈表的精髓在于理解和操作指針。只要你掌握了這一點,再復雜的鏈表問題也能迎刃而解。希望這篇文章能幫助你更深入地理解和應用鏈表這一重要的數據結構!

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

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

相關文章

架構思維:構建高并發讀服務_使用懶加載架構實現高性能讀服務

文章目錄 一、引言二、讀服務的功能性需求三、兩大基本設計原則1. 架構盡量不要分層2. 代碼盡可能簡單 四、實戰方案&#xff1a;懶加載架構及其四大挑戰五、改進思路六、總結與思考題 一、引言 在任何后臺系統設計中&#xff0c;「讀多寫少」的業務場景占據主流&#xff1a;瀏…

在運行 Hadoop 作業時,遇到“No such file or directory”,如何在windows里打包在虛擬機里運行

最近在學習Hadoop集群map reduce分布運算過程中&#xff0c;經多方面排查可能是電腦本身配置的原因導致每次運行都會報“No such file or directory”的錯誤&#xff0c;最后我是通過打包文件到虛擬機里運行得到結果&#xff0c;具體步驟如下&#xff1a; 前提是要保證maven已經…

軟考-軟件設計師中級備考 11、計算機網絡

1、計算機網絡的分類 按分布范圍分類 局域網&#xff08;LAN&#xff09;&#xff1a;覆蓋范圍通常在幾百米到幾千米以內&#xff0c;一般用于連接一個建筑物內或一個園區內的計算機設備&#xff0c;如學校的校園網、企業的辦公樓網絡等。其特點是傳輸速率高、延遲低、誤碼率低…

【C#】.net core6.0無法訪問到控制器方法,直接404。由于自己的不仔細,出現個低級錯誤,這讓DeepSeek看出來了,是什么錯誤呢,來瞧瞧

&#x1f339;歡迎來到《小5講堂》&#x1f339; &#x1f339;這是《C#》系列文章&#xff0c;每篇文章將以博主理解的角度展開講解。&#x1f339; &#x1f339;溫馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不對之處望指正&#xff01;&#…

當LLM遇上Agent:AI三大流派的“復仇者聯盟”

你一定聽說過ChatGPT和DeepSeek&#xff0c;也知道它們背后的LLM&#xff08;大語言模型&#xff09;有多牛——能寫詩、寫代碼、甚至假裝人類。但如果你以為這就是AI的極限&#xff0c;那你就too young too simple了&#xff01; 最近&#xff0c;**Agent&#xff08;智能體&a…

Spring Boot多模塊劃分設計

在Spring Boot多模塊項目中&#xff0c;模塊劃分主要有兩種思路&#xff1a;??技術分層劃分??和??業務功能劃分??。兩種方式各有優缺點&#xff0c;需要根據項目規模、團隊結構和業務特點來選擇。 ??1. 技術分層劃分&#xff08;橫向拆分&#xff09;?? 結構示例&…

兩次解析格式化字符串 + 使用SQLAlchemy的relationship執行任意命令 -- link-shortener b01lersCTF 2025

題目描述: A fast and reliable link shortener service, with a new feature to add private links! 我們走一遍邏輯 注冊 app.route("/register", methods[GET, POST]) def register(): """ 用戶注冊路由&#xff0c;處理用戶注冊請求&#xff…

后端id類型為long類型時,返回給前端瀏覽器四舍五入,導致id精度缺失問題

背景 今天在代碼里&#xff0c;掉了別人寫的接口&#xff0c;有個id的字段是long類型的&#xff0c;我這邊加點參數返回給前端&#xff0c;然后前端根據id修改&#xff0c;結果修改的數據記錄有&#xff0c;但是沒起作用&#xff0c;后來發現根據他傳給我的id在后臺數據庫查不…

Scartch038(四季變換)

知識回顧 1.了解和簡單使用音樂和視頻偵測模塊 2.使用克隆體做出波紋特效 3.取色器妙用偵測背景顏色 前言 我國幅員遼闊,不同地方的四季會有不同的美麗景色,這節課我帶你使用程序做一個體現北方四季變化的程序 之前的程序基本都是好玩的,這節課做一個能夠賞心悅目的程序。…

JVM happens-before 原則有哪些?

理解Java Memory Model (JMM) 中的 happens-before 原則對于編寫并發程序有很大幫助。 Happens-before 關系是 JMM 用來描述兩個操作之間的內存可見性以及執行順序的抽象概念。如果一個操作 A happens-before 另一個操作 B (記作 A hb B)&#xff0c;那么 JMM 向你保證&#x…

從 Eclipse Papyrus / XText 轉向.NET —— SCADE MBD技術的演化

從KPN[1]的萌芽開始&#xff0c;到SCADE的推出[2]&#xff0c;再到Scade 6的技術更迭[3]&#xff0c;SCADE 基于模型的開發技術已經歷許多。現在&#xff0c;Scade One 已開啟全新的探索 —— 從 Eclipse Papyrus / XText 轉向.NET 8跨平臺應用。 [1]: KPN, Kahn進程網絡 (197…

osquery在網絡安全入侵場景中的應用實戰(二)

背景 上次寫了osquery在網絡安全入侵場景中的應用實戰(一)結果還不錯,這次篇目二再增加一些場景。osquery主要解決的時員工被入侵之后電腦該如何溯源取證的問題。通常EDR會有日志,但是不會上報全量的日志。發現機器有惡意文件需要上級取證的時候,往往是比較麻煩的,會有這…

opencv+opencv_contrib+cuda和VS2022編譯

本文介紹使用OpenCV和OpenCV_Contrib源碼及Cuda進行編譯的過程&#xff0c;編譯過程中會用到OpenCV、OpenCV_Contrib、Toolkit、Cmake、VS2022等工具&#xff0c;最終編譯OpenCV的Cuda版本。 一、OpenCV下載地址 OpenCV官網下載地址:https://opencv.org/releases/#&#xff0…

spring中的@ConfigurationProperties注解詳解

一、核心功能與作用 ConfigurationProperties 是Spring Boot中用于將外部配置&#xff08;如application.properties或application.yml中的屬性&#xff09;綁定到Java對象的核心注解。其核心功能包括&#xff1a; 配置集中管理&#xff1a;將分散的配置屬性按前綴綁定到Java類…

【C/C++】函數模板

&#x1f3af; C 學習筆記&#xff1a;函數模板&#xff08;Function Template&#xff09; 本文是面向 C 初學者的函數模板學習筆記&#xff0c;內容包括基本概念、定義與使用、實例化過程、注意事項等&#xff0c;附帶示例代碼&#xff0c;便于理解與復現。 &#x1f4cc; 一…

電子病歷高質量語料庫構建方法與架構項目(智能數據目錄篇)

電子病歷高質量語料庫的構建是醫療人工智能發展的基礎性工作,而智能數據目錄作為數據治理的核心組件,能夠有效管理這些語料資源。本文將系統闡述電子病歷高質量語料庫的構建方法與架構,特別聚焦于智能數據目錄的設計與實現,包括數據目錄的功能定位、元數據管理、構建步驟以…

前端懶加載(Lazy Loading)實戰指南

&#x1f680; 前端懶加載&#xff08;Lazy Loading&#xff09;實戰指南 懶加載是現代 Web 性能優化的“常規操作”。它的目標簡單直接&#xff1a;讓用戶只加載“當下真正需要的資源”。從靜態資源、組件、模塊到數據&#xff0c;每一層都可以使用懶加載技術&#xff0c;構建…

在 Ubuntu 系統中,查看已安裝程序的方法

在 Ubuntu 系統中&#xff0c;查看已安裝程序的方法取決于軟件的安裝方式&#xff08;如通過 apt、snap、flatpak 或手動安裝&#xff09;。以下是幾種常見方法&#xff1a; 通過 apt 包管理器安裝的軟件 適用于通過 apt 或 dpkg 安裝的 .deb 包。 列出所有已安裝的軟件包&…

性能優化實踐:性能監控體系

性能優化實踐&#xff1a;性能監控體系 在Flutter應用開發中&#xff0c;建立一個完善的性能監控體系對于保證應用質量和用戶體驗至關重要。本文將從實戰角度深入講解如何搭建Flutter應用的性能監控體系&#xff0c;包括監控指標的設計、數據采集實現、分析平臺搭建等內容。 …

kotlin 02flow-sharedFlow 完整教程

一 sharedFlow是什么 SharedFlow 是 Kotlin 協程中 Flow 的一種 熱流&#xff08;Hot Flow&#xff09;&#xff0c;用于在多個訂閱者之間 共享事件或數據流。它適合處理 一次性事件&#xff08;如導航、彈窗、Toast、刷新通知等&#xff09;&#xff0c;而不是持續狀態。 ? …