面試官常考的15道鏈表題,你會多少?
鏈表題,在平時練起來感覺難度還行,但是在面試的程序中,在那種氛圍,面試者很容易因為緊張,導致面試表現不好,所以這里總結了一些鏈表最常見的練習題,反復練習,孰能生巧,希望能幫助到大家!!!

| 題目 | 在線OJ鏈接 | 難度 |
|---|---|---|
| 反轉單向鏈表 | 牛客網 | 易 |
| 反轉部分單向鏈表 | 牛客網 | 易 |
| 在鏈表中洗掉倒數第K個節點 | 牛客網 | 易 |
| 環形鏈表的約瑟夫環問題 | 牛客網 | 中 |
| 判斷一個鏈表是否為回文結構 | 牛客網 | 易 |
| 將單鏈表按某值劃分左邊小、中間等于、右邊大于 | 牛客網 | 易 |
| 單鏈表的選擇排序 | 牛客網 | 易 |
| 單鏈表中每K個節點之間進行反轉 | LeetCode | 中 |
| 復制一個帶隨機指標的單鏈表 | LeetCode | 中 |
| 合并兩個有序的鏈表 | 牛客網 | 易 |
| 一種怪異節點的洗掉方式 | 牛客網 | 中 |
| 按照左右半區的方式重新組合鏈表 | 牛客網 | 中 |
| 在單鏈表中洗掉重復的節點 | LeetCode | 中 |
| 判斷單鏈表是否有環,若有,回傳第一個入環節點 | LeetCode | 易 |
//單鏈表結點
public class ListNode {
public int val;
public ListNode next;
public ListNode(int val) {
this.val = val;
}
}
本期文章所有原始碼-GitHub
文章目錄
- 面試官常考的15道鏈表題,你會多少?
- 一、反轉單向鏈表
- 二、反轉部分單向鏈表
- 三、在鏈表中洗掉倒數第K個節點
- 四、環形鏈表的約瑟夫環問題
- 五、判斷一個鏈表是否為回文結構
- 六、將單鏈表按某值劃分左邊小、中間等于、右邊大
- 七、單鏈表的選擇排序
- 八、單鏈表中每K個節點之間進行反轉
- 九、復制一個帶隨機指標的單鏈表
- 十、合并兩個有序的鏈表
- 十一、一種怪異節點的洗掉方式
- 十二、按照左右半區的方式重新組合鏈表
- 十三、在單鏈表中洗掉重復的節點
- 十四、判斷單鏈表是否有環,若有,回傳第一個入環節點
- 十五、終極大招之兩個單鏈表相交的一系列問題
一、反轉單向鏈表
在線OJ鏈接

題意:輸入一個鏈表,反轉鏈表之后,回傳新鏈表的表頭!
這道題,解法有好幾種,我們這里就討論兩種思路,
方法一: 頭插法與三指標法

頭插法:定義pre 、cur和next參考,pre 指向前驅結點,cur指向當前結點,next指向后繼結點,
- 首先保存后繼結點next,
- 當next保存之后,cur的下一個結點就指向pre,
- 然后pre和cur分別往下走一步,
- 回圈往復,由圖可知,當cur == null時,回圈結束,
//頭插法
public ListNode reverseLinkList(ListNode head) {
if (head == null || head.next == null) { //沒有結點,或者只有一個結點的情況
return head;
}
ListNode pre = null; //前驅結點
ListNode cur = head; //當前結點
ListNode next = null; //后繼結點
while (cur != null) {
next = cur.next; //首先保存后驅結點
cur.next = pre; //改變鏈表的指向
pre = cur; //pre和cur往下走一步
cur = next;
}
return pre;
}
//三指標法---只是在頭插法的基礎之上改了一下,本質上沒有什么區別
public ListNode reverseLinkList(ListNode head) {
if (head == null || head.next == null) { //沒有結點,或者只有一個結點的情況
return head;
}
ListNode pre = null; //前驅結點
ListNode cur = head; //當前結點
ListNode next = null; //后驅結點
ListNode newHead = null;
while (cur != null) {
next = cur.next; //首先保存后驅結點
if (newHead == null) {
newHead = cur;
}
cur.next = pre; //改變鏈表的指向
pre = cur; //pre和cur往下走一步
cur = next;
}
return pre;
}
方法二:遞回
思想:我們只需要一直往下遞回,如果cur == null,說明pre這就是原來鏈表的最后一個結點,我們作為回傳值,直接回傳即可,遞回函式有兩個引數ListNode cur, ListNode pre,代表當前結點和上一結點,在找到最后一個結點后,回傳的程序中,將cur的下一結點連上pre就行,
public ListNode reverseLinkList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
return process(head, null); //第一次呼叫,上一結點就是null
}
public ListNode process(ListNode cur, ListNode pre) {
if (cur == null) {
return pre;
}
ListNode newHead = process(cur.next, cur); //遞回呼叫下一結點
cur.next = pre; //連接pre
return newHead; //將頭結點回傳去
}
二、反轉部分單向鏈表
在線OJ鏈接

這個題就是在反轉鏈表的基礎之上進行了改進,換湯不換藥,核心代碼還是那幾行,這道題就是需要一些細節問題,我們先看看反轉后的情況:
反轉 第2個結點到第4個結點的情況:

由上圖可知,我們大概知道思路,該怎么對這個鏈表著手,
- 首先遍歷鏈表,找到需要反轉鏈表的頭結點的上一個結點,對應上圖就是
1號結點,定義變數名為left - 然后繼續往下遍歷鏈表,找到需要反轉鏈表的尾結點 的下一個結點,對應上圖就是
5號結點,定義變數名為right - 此時宣告一個函式reverse,將引數
(left,開始結點,結束結點,right),轉入進去,進行反轉,反轉后連接left和right即可
public ListNode reversePartList(ListNode head, int L, int R) {
if (head == null || head.next == null || L > R) { //沒有結點,或者只有一個結點的情況
return head;
}
ListNode cur = head;
ListNode pre = null; //臨時變數 , pre 是 cur的前驅結點
ListNode left = null; //表頭的上一個結點
ListNode right = null; //尾結點的下一個結點
ListNode start = null; //需要反轉鏈表的表頭
ListNode end = null; //需要反轉鏈表的尾結點
for (int i = 1; i <= R && cur != null; i++) {
if (i == L) {
left = pre; //pre 是 cur的前驅結點
start = cur;
}
if (i == R) {
end = cur;
right = cur.next; //反轉鏈表的尾結點 的下一結點
break;
}
pre = cur;
cur = cur.next;
}
reverse(left, start, end, right);
return left == null? end : head; //有可能反轉后,頭結點被換了,
}
public void reverse(ListNode left, ListNode start, ListNode end, ListNode right) {
ListNode next = null;
ListNode pre = right; //頭結點需要連接right,比如上圖 2號結點連接5號結點
while (start != right) {
next = start.next;
start.next = pre;
pre = start;
start = next;
}
//回圈結束后,此時pre指向反轉鏈表的尾結點,也就是上圖的 4號結點
if (left != null) {
left.next = pre; //上圖的 1號結點連接4號結點
}
}

總結:這道題找出4個關鍵結點,呼叫reverse函式即可,reverse函式跟第一題的差不多,
三、在鏈表中洗掉倒數第K個節點
在線OJ鏈接

題意:洗掉倒數第K個結點,跟另一道題很像“回傳倒數第K個結點”,都是一樣的意思,

分析:對比上面兩幅圖,上面那一幅圖是倒著走的情況,下面這一副是正著走的情況, 我們會發現下面那一幅圖,當fast剛好走4個結點后,接下來pre和fast同時往下走一步,此時pre就是指向了2號結點,
? 也就是說,我們會發現一個規律,假設倒著走K步,我們定義兩個參考變數fast和slow,fast先正著走K步后,slow才從頭結點開始出發,此時fast和slow一起走,一次走一步,當fast來到尾結點時,此時的slow指向的結點,就是倒數第K個結點,(畫草稿圖,更容易理解)
public ListNode delBackKNode(ListNode head, int k) {
if (head == null || k < 1) {
return head;
}
ListNode slow = head;
ListNode fast = head;
ListNode pre = null; //slow的前驅結點
for (; fast != null; fast = fast.next) {
if (--k < 0) {
pre = slow; //pre緊跟著slow
slow = slow.next;
}
}
pre.next = slow.next; //C++的朋友,需要自己手動回收ListNode結點的記憶體
return head;
}

四、環形鏈表的約瑟夫環問題
在線OJ鏈接

題意:總之就是一句話:給你兩個數,一個數是回圈鏈表的長度,另一個數是m,每個人報數,報到m的就出局,剩下的接著報數,回傳最后剩下的那個結點,

分析: 從頭開始遍歷,用一個變數記錄當前報的數,pre指向前驅結點,cur指向當前結點,回圈終止條件就是還剩下一個結點的時候,
public ListNode josephusKill(ListNode head, int k) {
if (head == null || head.next == head) { //沒有結點,或者只有一個結點的情況
return head;
}
int count = 1; //計數
ListNode pre = null;
ListNode cur = head;
while (cur != cur.next) { //當自己的next指向自己時,說明只有一個結點了
if (count++ == k) {
pre.next = cur.next;
count = 1; //重置為1
} else {
pre = cur;
}
cur = cur.next;
}
return cur;
}
這個約瑟夫環問題,還有一個進階版的,進階版的和原題一樣,只是測驗的資料要多很多,所以一個個去建立回圈鏈表,再去一個個的洗掉結點,時間效率就很低,感興趣的朋友可以去看看《程式員代碼面試指南》,書中有講解,如何通過一些規律,來推匯出剩下的那個結點,這里我們就不多講了,
回圈鏈表的約瑟夫環問題(進階)
五、判斷一個鏈表是否為回文結構
在線OJ鏈接

題意:判斷一個單鏈表是不是回文結構, 回文結構就是:從中間為軸,左右兩邊對折起來,左右兩邊每個位置所對應的數值是一樣的,比如1221,12321就是一個回文數.
方法一:: 判斷是不是回文數,我們可以用一個容器先把整個鏈表的資料裝在一起,這里就用堆疊,比如鏈表就是1 -> 2 -> 2 -> 1 ->null;我們壓堆疊后的情況就是這樣:

此時我們就已經將整個鏈表的全部資料壓入到堆疊中,我們都知道,堆疊是先進后出的,所以在彈出資料的時候,是先彈出堆疊頂的元素,彈出的元素,我們再去和鏈表進行比較,如果其中有不相等的,說明這個鏈表就不是回文結構,

public boolean isPlalindromeList(ListNode head) {
if (head == null || head.next == null) {
return true;
}
Stack<Integer> stack = new Stack<>(); //堆疊
ListNode cur = head;
while (cur != null) {
stack.push(cur.val); //壓堆疊
cur = cur.next;
}
cur = head;
while (cur != null) {
if (cur.val != stack.pop()) {
return false; //彈出的元素不相等的情況
}
cur = cur.next;
}
return true;
}
上面的代碼,就是運用堆疊來求解,當然這個方法還可以優化空間復雜度,假設我只壓入后半部分的資料,再去和前半部分鏈表進行比較,也是一樣的效果,這里就不多贅述了,自己動手實作一下吧,
方法二: 反轉后半部分的鏈表,優化空間復雜度O(1)

分析:一個向左遍歷,一個向右遍歷,每次都比較一下,如果有一個不相等,那就不是回文結構,回傳結果前,要先把鏈表反轉回來,
//進階解法,將右邊部分,反轉鏈表,指向中間結點
public boolean isPlalindromeList3(ListNode head, int size) { //size,是鏈表的總長度
if (head == null || head.next == null) {
return true;
}
boolean res = true;
ListNode leftStart = head;
ListNode rightStart = null;
ListNode cur = head;
for (int i = 0; i < size / 2; i++) {
cur = cur.next;
}
rightStart = reverseList(cur); //右半部分的頭結點
cur = rightStart;
for (int i = 0; i < size / 2; i++) {
if (cur.val != leftStart.val) {
res = false;
break;
}
cur = cur.next;
leftStart = leftStart.next;
}
reverseList(rightStart); //恢復鏈表,不需要接識訓傳值,本身上一個結點的next域,沒被修改
return res;
}
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode pre = null;
ListNode cur = head;
ListNode next = null;
while (cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
六、將單鏈表按某值劃分左邊小、中間等于、右邊大
在線OJ鏈接

題意:給你一個單鏈表,和一個數值,根據這個數值將單鏈表分為小于區、等于區和大于區,
方法一:跟“荷蘭國旗問題”一樣,我們只需將所有結點放到一個陣列里面,然后在陣列上做partition操作,具體的思想,前面的文章八大排序演算法 中的快速排序,就是參考了這個思想,可以看一看,
public ListNode listPartiton(ListNode head, int pivot) {
if (head == null || head.next == null) {
return head;
}
ListNode cur = head;
int count = 0; //計算鏈表的長度
while (cur != null) {
count++;
cur = cur.next;
}
ListNode[] arr = new ListNode[count];
for (int i = 0; i < count; i++) { //將所有結點放入陣列
arr[i] = head;
head = head.next;
}
int left = -1; //小于區域的范圍
int right = count; //大于區域的范圍
int index = 0; //用于遍歷陣列的下標
while (index < right) { //只要index沒有和大于區域相遇,回圈就繼續
if (arr[index].val == pivot) {
index++; //等于區域,別動,index往后走即可
} else if (arr[index].val > pivot) {
swap(arr, index, --right); //切記,這里index還不能動,因為從后面拿前來的資料,還沒有判斷大小
} else {
swap(arr, index++, ++left);
}
}
for (int i = 1; i < count; i++) { //連接每個結點
arr[i - 1].next = arr[i];
}
arr[count - 1].next = null; //最后一個結點的next,賦值null
return arr[0];
}
public void swap(ListNode[] arr, int left, int right) {
ListNode tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
}
方法二: 方法一空間復雜度O(N),可能還不能夠得到面試官的青睞,現在再說一種空間復雜度O(1)的解法,
分析:題目是要我分為三個區域,我們就把這三個區域分別看成是3個獨立的鏈表,每個鏈表都有頭尾指標,我們只需要6個參考變數來指向這頭尾結點即可,
如圖:

public ListNode listPartition2(ListNode head, int pivot) {
if (head == null || head.next == null) {
return head;
}
ListNode sH = null;
ListNode sT = null; //小于區域
ListNode eH = null;
ListNode eT = null; //等于區域
ListNode bH = null;
ListNode bT = null; //大于區域
while (head != null) {
ListNode next = head.next;
head.next = null;
if (head.val < pivot) {
if (sH == null) {
sH = head;
sT = head;
} else {
sT.next = head; //尾結點去連接
sT = head;
}
} else if (head.val == pivot) {
if (eH == null) {
eH = head;
eT = head;
} else {
eT.next = head; //尾結點去連接
eT = head;
}
} else {
if (bH == null) {
bH = head;
bT = head;
} else {
bT.next = head; //尾結點去連接
bT = head;
}
}
head = next; //往后走
}
//連接三個區域
if (sH != null) {
sT.next = eH;
eT = eH == null? sT : eT; //判斷等于區域是否有結點
}
if (bH != null) {
eT.next = bH;
}
return sH != null? sH : (eH != null? eH : bH);
}
七、單鏈表的選擇排序
在線OJ鏈接

題意:也就是說,在單鏈表上做選擇排序,(選擇排序:在一定的范圍內,選擇最大值或者最小值,把它放到最前面,回圈往復)
方法一:也可以將所有結點放入陣列,在陣列上做選擇排序,然后再連接起來,這樣的演算法能過OJ,面試官那里可能過不了,這里就不說了,
方法二:在單鏈表的基礎之上,做選擇排序操作,我們定義一個newHead,作為新的頭結點,minPre指向最小結點的前驅結點,每一次回圈進去,尋找當前鏈表中最小的結點,使其“掛”在newHead下,

public ListNode sortList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = null;
ListNode tail = null;
while (head != null) {
ListNode minNode = null; //最小結點
ListNode minPre = null; //最小結點的前驅結點
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
if (minNode == null || cur.val < minNode.val) {
minPre = pre; //保存最小結點的前驅結點
minNode = cur; //保存最小結點
}
pre = cur;
cur = cur.next;
}
if (minNode == head) {
head = head.next; //換頭結點的情況
} else {
minPre.next = minNode.next; //將最小結點拿出
}
if (newHead == null) {
newHead = minNode;
tail = minNode;
} else {
tail.next = minNode;
tail = minNode; //尾插法
}
}
tail.next = null;
return newHead;
}
八、單鏈表中每K個節點之間進行反轉
在線OJ鏈接

題意:根據給的K,K個一組進行反轉,也就是前面幾道題的進階版,
分析:我們可以定義參考變數start,表示需要反轉鏈表部分的開始,參考變數end表示反轉鏈表的尾結點,還有left和right,分別表示start的前驅結點和end的后驅結點,如圖:

所以,我們只需要封裝一個reverse函式,傳遞這四個參考變數,就能實作反轉,
public ListNode reverseKGroup(ListNode head, int k) {
if (head == null || head.next == null || k < 2) { //k=1時,等于沒有反轉
return head;
}
ListNode left = null;
ListNode start = head;
ListNode cur = head;
int count = 1;
while (cur != null) {
ListNode next = cur.next;
if (count++ == k) { //反轉
reverse(left, start, cur, cur.next); //這里的end和right,就是cur和cur.next
if (start == head) { //更換頭結點
head = cur;
}
left = start; //更新left和start的指向
start = start.next; //上圖的right
count = 1;
}
cur = next;
}
return head;
}
public void reverse(ListNode left, ListNode start, ListNode end, ListNode right) {
ListNode pre = right;
ListNode next = null;
while (start != right) {
next = start.next;
start.next = pre;
pre = start;
start = next;
}
if (left != null) {
left.next = pre; //也就是上圖的1號結點 連接4號結點
}
}
九、復制一個帶隨機指標的單鏈表
在線OJ鏈接

題意:給定一個鏈表,鏈表本身除了一個next域指向下一結點以外,還有一個random域,它可以指向這個鏈表中的任意一個結點,問:怎么復制出一個一模一樣的鏈表出來,(深拷貝與淺拷貝,前期文章有講解)
方法一: 運用哈希表,復制一個一模一樣的結點出來,然后再連接起來即可,
//哈希表實作
public Node copyRandomList(Node head) {
HashMap<Node, Node> map = new HashMap<>();
Node cur = head;
while (cur != null) {
map.put(cur, new Node(cur.val)); //復制結點
cur = cur.next;
}
Node res = map.get(head);
while (head != null) {
map.get(head).next = map.get(head.next); //復制next域
map.get(head).random = map.get(head.random); //復制random域
head = head.next;
}
return res;
}
方法二: 遍歷一遍鏈表,將每一個結點都復制一個出來,復制出來的結點連接在原來結點的后面,然后可以得到如下圖所示:

當我們將結點復制出來,并且連接在原結點之后,我們就會清晰地看見:
假設1號結點的random -> 3號結點,那么復制的結點1號結點random = 3號結點的后驅結點,
所以這道題,整體三步:
- 復制結點,連接到原結點的后面
- 處理每個結點的random指標,
(1'號結點.random = 1號結點.random.next) - 然后分離兩個鏈表即可
class Node {
public int val;
public Node next;
public Node random;
public Node(int val) {
this.val = val;
}
}
public Node copyRandomList(Node head) {
if (head == null) {
return head;
}
//第一步-復制結點
Node cur = head;
while (cur != null) {
Node next = cur.next;
Node node = new Node(cur.val);
node.next = next; //連接下一結點
cur.next = node; //新結點掛在舊結點的后面
cur = next;
}
//第二步 - random指標
cur = head;
Node res = cur.next; //復制鏈表的頭結點
Node copy = null;
while (cur != null) {
Node next = cur.next.next; //下一個舊結點
copy = cur.next;
copy.random = cur.random == null? null : cur.random.next; //連接random指標,需要注意null
cur = next;
}
//第三步-分離
cur = head;
copy = res; //指向復制鏈表的第一個結點
while (cur != null) {
Node next = cur.next.next; //下一個舊結點
copy.next = next == null? null : next.next; //注意,這里需要判斷是否為null
cur.next = next; //將原來的鏈表還原
copy = copy.next;
cur = next;
}
return res;
}
十、合并兩個有序的鏈表
在線OJ鏈接

題意:給定了兩個已經有序的單鏈表,將它們合并在一起,且還是有序的!
分析:我們只需宣告一個參考newHead,然后兩個變數依次取出結點進行比較,誰小,誰就先掛在newHead上,回圈終止條件是其中有一個鏈表遍歷完了,回圈就結束!
- 宣告newHead,回圈遍歷兩個鏈表
- 取出的結點誰更小,誰就先掛newHead上
- 回圈結束的原因是,肯定有一個鏈表遍歷完了,還剩一個鏈表直接連上newHead即可

public ListNode mergeList(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return headA == null? headB : headA; //其中一個是null,回傳另一個鏈表
}
ListNode newHead = null;
ListNode tail = null; //尾指標,用尾插法
while (headA != null && headb != null) { //兩個鏈表都不是null,回圈就繼續
if (headA.val < headB.val) {
if (newHead == null) {
newHead = headA;
} else {
tail.next = headA;
}
tail = headA;
headA = headA.next; //繼續往下走
} else {
if (newHead == null) {
newHead = headB;
} else {
tail.next = headB;
}
tail = headB;
headB = headB.next; //繼續往下走
}
}
//回圈結束后,一定是有一個鏈表遍歷完了,此時將另一個鏈表連接上即可
tail.next = headA != null? headA : headB;
return newHead;
}
十一、一種怪異節點的洗掉方式
在線OJ鏈接

題意:只給你單鏈表中的一個結點(非頭結點),問:如何將給你的結點洗掉?例如: 原鏈表: 1 -> 2 -> 3 -> 4-> null ,假設給你3號結點,在沒有頭結點的情況下,如何洗掉3號結點?
可能有人會說:將給你的結點,賦值為null不就行了嗎?
答案:肯定是不行的,給你的node結點,只是一個參考(地址),而在java中,變數是在堆疊上開辟的,直接賦值為null,只是讓這個變數找不到node結點了而已,具體的,畫一畫堆疊區和堆區的記憶體圖,你或許就明白了,
分析:這道題,有一定的局限性,如果結點的資料只是普通的資料,我們可以直接將下一個結點的資料覆寫到node結點上,然后node去連接下一個結點的下一個結點,切記:如果給你的node,是尾結點的話,這道題就沒有解,如圖:

public void delStrangerNode(ListNode node) {
if (node == null || node.next == null) {
return;
}
node.val = node.next.val;
node.next = node.next.next;
}
注:要跟面試過說清楚情況,這樣的方式,其實并不是洗掉了結點,只是進行了資料的覆寫,假設這一個結點時一個很大的工程,又或者是一個服務器,如果去進行資料的覆寫,工程量還是很大的,
十二、按照左右半區的方式重新組合鏈表
在線OJ鏈接

題意:先將單鏈表從中間分開,分為兩個鏈表,然后這兩個鏈表交叉著插入結點,得到新的鏈表,
整體分為兩步:
- 使用快慢指標,得到整個鏈表的中間結點,分為兩個鏈表
- 交叉著穿插結點,得到新的鏈表

由上圖,我們可以發現當fast和slow停下來的時候,slow的下一結點就是當前鏈表的中間結點,此時我們就可以分為兩個鏈表了,

public ListNode againCombinationList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode slow = head;
ListNode fast = head.next;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
//當回圈停下的時候,slow的下一結點就是整個鏈表的中間結點
ListNode right = slow.next; //右半部分的鏈表
slow.next = null; //置為null
ListNode left = head; //左半部分的鏈表
while (left.next != null) {
ListNode next = right.next;
right.next = left.next;
left.next = right;
left = right.next;
right = next;
}
left.next = right;
return head;
}
十三、在單鏈表中洗掉重復的節點
在線OJ鏈接

分析:首先看到洗掉重復結點,我就想到了HashSet集合,做容器,遍歷一遍鏈表,將所有資料添加到集合中,最后再連接起來,但是這樣的話,空間復雜度就高了,這樣的思路只是適合在筆試的時候用,在面試的時候,入不了面試官的眼的,
? 所以我們只能想辦法在鏈表上直接動手了,一開始,我們以鏈表的第一個結點作為目標,在這個結點的后面的結點做遍歷,如果剩下的結點中沒有與目標結點相等的,我們的目標結點就轉換到下一個結點,繼續做這樣的遍歷,時間復雜度O(N2),
public ListNode removeDuplicateNodes(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode cur = head;
ListNode pre = null;
ListNode next = null;
while (cur != null) {
next = cur.next; //首先保存cur的下一結點,在這個結點上去做比較
pre = cur;
while (next != null) { //從next往下走,每一個結點都與cur比較
if (cur.val == next.val) {
pre.next = next.next; //相等的情況,pre直接連接下一個結點
} else {
pre = next;
}
next = next.next;
}
cur = cur.next;
}
return head;
}
十四、判斷單鏈表是否有環,若有,回傳第一個入環節點
在線OJ鏈接

分析:有一道鏈表題是判斷給定鏈表是否有環,這道題就是在有環的基礎之上,需要回傳第一個入環的結點,如果就是入環的第一個結點;

此時的3號結點,我們就稱為入環的第一個結點,
如何找到這個入環結點并回傳?
還是要引入小學的數學問題,追趕問題,引入slow和fast指標,slow每次走一步,fast每次走兩步,如果給定的鏈表是有環的,slow和fast肯定能在環上相遇(fast的速度是slow的兩倍),此時我們讓fast從head重新出發,這次slow和fast每次只有一步,這樣slow和fast肯定能在入環結點處相遇,至于如何證明這個問題,我就不多說了,動圖如下:

public ListNode detectCycle(ListNode head) {
if (head == null || head.next == null) {
return null;
}
ListNode slow = head;
ListNode fast = head; //都從第一個結點出發
while (fast != null && fast.next != null) { //如果不是回圈鏈表,就退出
slow = slow.next;
fast = fast.next.next;
if (slow == fast) { //相等了
fast = head; //從頭開始
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
return null;
}
十五、終極大招之兩個單鏈表相交的一系列問題
題目:在本題中,單鏈表可能有環,也可能無環,給定兩個單鏈表的頭結點head1和head2,這兩個鏈表可能相交,也可能不相交,
請實作一個函式,如果兩個鏈表相交,請回傳相交的第一個結點;如果不相交,回傳null即可,
要求:如果鏈表1的長度為N,鏈表2的長度為M,時間復雜度請達到O(N+M),額外空間復雜度請達到O(1),
出自《程式員代碼面試指南》一書,
注:如果其中一個鏈表有環,另一個鏈表無環,那么這兩個鏈表肯定不相交,
這道題我就留個大家自己研究啦,因為暫時還沒找到OJ題,所以就不講啦,我會將原始碼放到GitHub上,
好啦,各位朋友!本期更新就到此結束啦!鏈表題說難也不是很難,說簡單也算不上,這畢竟是面試官必問的相關題,好好研究研究,趕快上手寫代碼吧!
下期見啦!!!拜拜
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/292722.html
標籤:其他
