鏈表
是以節點(node)存儲的鏈式存儲結構
,一個node包含一個data域(存放數據)和一個next域(存放下一個node的指針),鏈表的各個節點不必定是連續的,它能夠分爲帶頭結點
和不帶頭結點
。頭結點僅包含next域。java
若是不熟悉鏈表的同窗,建議先看看個人一篇文章。在這篇文章中,主要講解使用鏈表的小技巧,如何使用這些技巧來解題,深刻解析了LeetCode
中具備表明性
的鏈表題目,相信我,看了這篇文章,你不再用擔憂關於鏈表的題目了。node
事實上,鏈表的結構比較簡單,阻礙咱們理解鏈表的經常是由於鏈表的指針
、邊界問題
等,這有時會讓咱們很煩躁,不要慌,咱們下面一一對這下概念解析,相信你看了會有收穫。面試
咱們學習C語言時,學過指針,它描述的是指向一個內存地址
,在Java語言中,是不存在指針的,可是咱們能夠把它理解爲引用
。算法
當咱們將某個變量(對象)賦值給指針(引用),實際上就是將這個變量(對象)的地址賦值給指針(引用)。數組
p—>next = q; //表示p節點的後繼指針存儲了q節點的內存地址。
p—>next = p—>next—>next; //表示p節點的後繼指針存儲了p節點的下下個節點的內存地址。
複製代碼
咱們寫鏈表代碼時,使用的指針的指來指去
,很快就把咱們搞糊塗了,在這種狀況下很容易發生指針丟失
和內存泄漏
。咱們先普及下這兩個概念:markdown
指針丟失:本身定義的指針不知道指到哪裏了,沒有明確的指向。數據結構
內存泄漏:鏈表中的節點沒有確切的指針判斷,運行時會拋出空指針異常。函數
咱們以插入節點
和刪除結點
來分析指針丟失和內存泄漏的具體狀況oop
在節點a和節點b之間插入節點x,b是a的下一節點,p指針指向節點a,學習
p—>next = x;
x—>next = p—>next;
複製代碼
這樣的代碼會形成指針丟失和內存泄漏,由於這會致使x節點的後繼指針指向了本身自己。
正確代碼應該爲:
x—>next = p—>next;
p—>next = x;
複製代碼
一樣的,在節點a和節點c之間刪除節點b,b是a的下一節點,p指針指向節點a,正確的代碼應該爲:
p—>next = p—>next—>next;
複製代碼
在刪除節點,考慮到刪除的節點多是鏈表中的第一個節點,咱們一般在鏈表頭部加入哨兵(頭結點),這樣可使得刪除鏈表的代碼是一致的,不用再額外考慮是不是第一個節點的狀況。
在鏈表加入哨兵的代碼爲:
//定義一個哨兵做爲傳入鏈表的頭結點
ListNode pre =new ListNode(0);
pre.next=head;
複製代碼
處理鏈表問題時,要充分考慮鏈表的邊界判斷條件
,一般狀況下,咱們常用如下幾種判斷條件:
若是鏈表爲空時,代碼是否能正常工做?
若是鏈表只包含一個結點時,代碼是否能正常工做?
若是鏈表只包含兩個結點時,代碼是否能正常工做?
代碼邏輯在處理頭結點和尾結點的時候,是否能正常工做?
這些判斷條件須要結合本身的實際場景來使用
在上面的學習中,咱們對鏈表的一些易錯的概念進行了解析,下面,咱們就真正的代碼實踐,我在LeetCode上刷題時發現,鏈表題目一般分爲如下幾類:
這幾類鏈表題基本涵蓋了大部分知識點
,在下面的學習中,咱們將一一攻克它,相信掌握它們以後,在之後筆試/面試
中,更能爲所欲爲。
思路:從前日後將每一個節點的指針反向,即.next內的地址換成前一個節點的,但爲了防止後面鏈表的丟失,在每次換以前須要先建立個指針指向下一個節點。
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null||head.next==null){
return head;
}
ListNode p1=head;
//用一個新的鏈表
ListNode p2=null;
while(p1!=null){
//每次更換指向以前都須要保存下一個節點
ListNode temp=p1.next;
p1.next=p2;
p2=p1;
p1=temp;
}
return p2;
}
}
複製代碼
思路:定義兩個指針,p1和p2,指針p1每次走一步,指針p2每次走兩步,若是鏈表中存在環,則必然會在某個時刻知足p1==p2
public class Solution {
public boolean hasCycle(ListNode head) {
if(head==null||head.next==null){
return false;
}
ListNode slow=head;
ListNode fast=head.next;
while(fast!=null&&fast.next!=null){
if(slow==fast){
return true;
}
slow=slow.next;
fast=fast.next.next;
}
return false;
}
}
複製代碼
NOTE:對於快指針來講,由於一次跳兩步,若是要使用快指針做爲判斷條件,fast和fast.next都須要判斷是否爲空。(不可跨級
)
思路
:能夠新建立一個鏈表用於合併後的結果,合併的條件以下
定義一個指針,查找合適的節點並放入新建立鏈表的下一位置
將不爲空的鏈表放入新建立鏈表的下一位置
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1==null){
return l2;
}
if(l2==null){
return l1;
}
ListNode result=new ListNode(0);
ListNode temp=result;
//兩個鏈表都不爲空
while(l1!=null&&l2!=null){
if(l1.val<=l2.val){
temp.next=l1;
temp=temp.next;
l1=l1.next;
}
else{
temp.next=l2;
temp=temp.next;
l2=l2.next;
}
}
//有一個鏈表爲空
if(l1==null){
temp.next=l2;
}
else{
temp.next=l1;
}
return result.next;
}
}
複製代碼
思路:能夠在鏈表頭加一個哨兵(頭結點),刪除鏈表時先找到刪除鏈表的上一個位置,按照刪除規則刪除便可。
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if(head==null){
return null;
}
//定義一個哨兵做爲傳入鏈表的頭結點
ListNode pre =new ListNode(0);
pre.next=head;
ListNode temp=pre;
while(temp!=null){
if(temp.next.val==val){
temp.next=temp.next.next;
break;
}
else{
temp=temp.next;
}
}
return pre.next;
}
}
複製代碼
思路:刪除節點時要利用好哨兵
(帶頭結點的鏈表)
- 遍歷數組的長度count
- 找到要刪除節點的前一個位置count-n-1
- 刪除節點
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode pre=new ListNode(0);
pre.next=head;
ListNode temp1=pre;
ListNode temp2=pre;
int count=0;
while(temp1!=null){
temp1=temp1.next;
count++;
}
while(count-n-1>0){
temp2=temp2.next;
count--;
}
temp2.next=temp2.next.next;
return pre.next;
}
}
複製代碼
思路:找出鏈表中結點的個數count,而後count/2找出中間結點,刪除便可。
class Solution {
public ListNode middleNode(ListNode head) {
if(head==null) return null;
ListNode temp=head;
int count=0;
while(temp!=null){
temp=temp.next;
count++;
}
int mid=count/2;
ListNode result=head;
while(mid>0){
result=result.next;
mid--;
}
return result;
}
}
複製代碼
Note:實踐是檢驗真理的惟一標準,要真正的學好鏈表這個知識點,僅僅學理論是不可靠的,咱們須要多敲代碼
,多思考
,多寫多練
,針對抽象的題目,能夠舉例畫圖
,來輔助的本身的思考。
一、 函數中須要移動鏈表時,最好新建一個指針來移動
,以避免更改原始指針位置。
二、 單鏈表有帶頭節點
和不帶頭結點
的鏈表之分,通常作題默認頭結點是有值的
。
三、 鏈表的內存時不連續
的,一個節點佔一塊內存,每塊內存中有一塊位置(next)存放下一節點的地址。
三、 鏈表中找環
的思想:快慢指針
,建立兩個指針,一個快指針:一次走兩步
,一個慢指針:一次走一步
,若相遇則有環,若指向null則無環。
四、 鏈表找倒數第k個節點思想
:建立兩個指針
,第一個指針查詢鏈表中結點的個數count
,而後count-k
肯定刪除結點的位置,用第二個指針遍歷鏈表到count-n-1
個位置。
五、 反向鏈表思想:從前日後
將每一個節點的指針反向,即next內的地址換成前一個節點的
,但爲了防止後面鏈表的丟失,在每次換以前須要先建立個指針指向下一個節點
。
不管學習任何一個知識點,咱們都須要在掌握術(使用方法)
的基礎上,學習道(本源)
,學習數據結構與算法也是同樣,咱們不只要掌握如何使用它
,更要掌握爲何要是用它
,相比其它的方法,它有什麼優勢
,難道是時間複雜度低
,空間複雜度小
,仍是它的數據結構適合這個場景等等
...
參考文獻
[1]王爭.數據結構與算法之美
[2]LeetCode中國網站
筆者在過去的3個月時間裏整理經常使用的數據結構與算法
和秒殺劍指offer
,公衆號分別回覆數據結構與算法
,秒殺劍指offer
,便可領取兩套電子書,但願可以幫助你們。
我是Simon郎
,一個想要天天博學一點點的小青年,關注我,讓咱們一塊兒進階,一塊兒博學。