本文首發於個人我的博客:尾尾部落java
鏈表是面試過程當中常常被問到的,這裏把劍指offer 和 LeetCode 中的相關題目作一個彙總,方便複習。node
題目描述:給定單向鏈表的頭指針和一個節點指針,定義一個函數在O(1)時間刪除該節點。 解題思路:常規的作法是從鏈表的頭結點開始遍歷,找到須要刪除的節點的前驅節點,把它的 next 指向要刪除節點的下一個節點,平均時間複雜度爲O(n),不知足題目要求。 那是否是必定要獲得被刪除的節點的前一個節點呢?其實不用的。咱們能夠很方面地獲得要刪除節點的下一個節點,若是咱們把下一個節點的內容複製到要刪除的節點上覆蓋原有的內容,再把下一個節點刪除,那就至關於把當前要刪除的節點刪除了。舉個栗子,咱們要刪除的節點i,先把i的下一個節點j的內容複製到i,而後把i的指針指向節點j的下一個節點。此時再刪除節點j,其效果恰好是把節點i給刪除了。 要注意兩種狀況:面試
參考代碼算法
public static ListNode deleteNode(ListNode head, ListNode toBeDeleted) {
// 若是輸入參數有空值就返回表頭結點
if (head == null || toBeDeleted == null) {
return head;
}
// 若是刪除的是頭結點,直接返回頭結點的下一個結點
if (head == toBeDeleted) {
return head.next;
}
// 下面的狀況鏈表至少有兩個結點
// 在多個節點的狀況下,若是刪除的是最後一個元素
if (toBeDeleted.next == null) {
// 找待刪除元素的前驅
ListNode tmp = head;
while (tmp.next != toBeDeleted) {
tmp = tmp.next;
}
// 刪除待結點
tmp.next = null;
}
// 在多個節點的狀況下,若是刪除的是某個中間結點
else {
// 將下一個結點的值輸入當前待刪除的結點
toBeDeleted.value = toBeDeleted.next.value;
// 待刪除的結點的下一個指向原先待刪除引號的下下個結點,即將待刪除的下一個結點刪除
toBeDeleted.next = toBeDeleted.next.next;
}
// 返回刪除節點後的鏈表頭結點
return head;
}
複製代碼
題目描述:輸出一個單鏈表的逆序反轉後的鏈表。 解題思路:用三個臨時指針 prev、cur、next 在鏈表上循環一遍便可。函數
題目描述:給定一個單向鏈表的頭結點head,以及兩個整數from和to,在單鏈表上把第from個節點和第to個節點這一部分進行反轉ui
舉例:1->2->3->4->5->null, from = 2, to = 4 結果:1->4->3->2->5->nullspa
public ListNode reverseBetween(ListNode head, int m, int n) {
if (head == null) return null;
if (head.next == null) return head;
int i = 1;
ListNode reversedNewHead = null;// 反轉部分鏈表反轉後的頭結點
ListNode reversedTail = null;// 反轉部分鏈表反轉後的尾結點
ListNode oldHead = head;// 原鏈表的頭結點
ListNode reversePreNode = null;// 反轉部分鏈表反轉前其頭結點的前一個結點
ListNode reverseNextNode = null;
while (head != null) {
if (i > n) {
break;
}
if (i == m - 1) {
reversePreNode = head;
}
if (i >= m && i <= n) {
if (i == m) {
reversedTail = head;
}
reverseNextNode = head.next;
head.next = reversedNewHead;
reversedNewHead = head;
head = reverseNextNode;
} else {
head = head.next;
}
i++;
}
reversedTail.next = reverseNextNode;
if (reversePreNode != null) {
reversePreNode.next = reversedNewHead;
return oldHead;
} else {
return reversedNewHead;
}
}
複製代碼
題目描述:給定一個單鏈表,設計一個算法實現鏈表向右旋轉 K 個位置。 舉例: 給定 1->2->3->4->5->6->NULL, K=3 則4->5->6->1->2->3->NULL 解題思路:設計
方法二代碼:指針
public class Solution { {
public ListNode rotateRight(ListNode head, int k) {
if (!head) return null;
int n = 1;
ListNode cur = head;
while (cur.next) {
++n;
cur = cur.next;
}
cur.next = head;
int m = n - k % n;
for (int i = 0; i < m; ++i) {
cur = cur.next;
}
ListNode newhead = cur.next;
cur.next = NULL;
return newhead;
}
};
複製代碼
題目描述:刪除單鏈表倒數第 n 個節點,1 <= n <= length,儘可能在一次遍歷中完成。 解題思路:雙指針法,找到倒數第 n+1 個節點,將它的 next 指向倒數第 n-1個節點。
題目描述:求單鏈表的中間節點,若是鏈表的長度爲偶數,返回中間兩個節點的任意一個,若爲奇數,則返回中間節點。 解題思路:快慢指針,慢的走一步,快的走兩步,當快指針到達尾節點時,慢指針移動到中間節點。
// 遍歷一次,找出單鏈表的中間節點
public ListNode findMiddleNode(ListNode head) {
if (null == head) {
return;
}
ListNode slow = head;
ListNode fast = head;
while (null != fast && null != fast.next) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
複製代碼
題目描述: 給定一個單鏈表和數值x,劃分鏈表使得全部小於x的節點排在大於等於x的節點以前。
public class Solution {
/** * @param head: The first node of linked list. * @param x: an integer * @return: a ListNode */
public ListNode partition(ListNode head, int x) {
// write your code here
if(head == null) return null;
ListNode leftDummy = new ListNode(0);
ListNode rightDummy = new ListNode(0);
ListNode left = leftDummy, right = rightDummy;
while (head != null) {
if (head.val < x) {
left.next = head;
left = head;
} else {
right.next = head;
right = head;
}
head = head.next;
}
right.next = null;
left.next = rightDummy.next;
return leftDummy.next;
}
}
複製代碼
題目描述:你有兩個用鏈表表明的整數,其中每一個節點包含一個數字。數字存儲按照在原來整數中相反的順序,使得第一個數字位於鏈表的開頭。寫出一個函數將兩個整數相加,用鏈表形式返回和。 解題思路:作個大循環,對每一位進行操做:
當前位:(A[i]+B[i])%10 進位:(A[i]+B[i])/10
public class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode c1 = l1;
ListNode c2 = l2;
ListNode sentinel = new ListNode(0);
ListNode d = sentinel;
int sum = 0;
while (c1 != null || c2 != null) {
sum /= 10;
if (c1 != null) {
sum += c1.val;
c1 = c1.next;
}
if (c2 != null) {
sum += c2.val;
c2 = c2.next;
}
d.next = new ListNode(sum % 10);
d = d.next;
}
if (sum / 10 == 1)
d.next = new ListNode(1);
return sentinel.next;
}
}
複製代碼
題目描述:在O(nlogn)時間內對鏈表進行排序。 快速排序:
public ListNode sortList(ListNode head) {
//採用快速排序
quickSort(head, null);
return head;
}
public static void quickSort(ListNode head, ListNode end) {
if (head != end) {
ListNode node = partion(head, end);
quickSort(head, node);
quickSort(node.next, end);
}
}
public static ListNode partion(ListNode head, ListNode end) {
ListNode p1 = head, p2 = head.next;
//走到末尾才停
while (p2 != end) {
//大於key值時,p1向前走一步,交換p1與p2的值
if (p2.val < head.val) {
p1 = p1.next;
int temp = p1.val;
p1.val = p2.val;
p2.val = temp;
}
p2 = p2.next;
}
//當有序時,不交換p1和key值
if (p1 != head) {
int temp = p1.val;
p1.val = head.val;
head.val = temp;
}
return p1;
}
複製代碼
歸併排序:
public ListNode sortList(ListNode head) {
//採用歸併排序
if (head == null || head.next == null) {
return head;
}
//獲取中間結點
ListNode mid = getMid(head);
ListNode right = mid.next;
mid.next = null;
//合併
return mergeSort(sortList(head), sortList(right));
}
/** * 獲取鏈表的中間結點,偶數時取中間第一個 * * @param head * @return */
private ListNode getMid(ListNode head) {
if (head == null || head.next == null) {
return head;
}
//快慢指針
ListNode slow = head, quick = head;
//快2步,慢一步
while (quick.next != null && quick.next.next != null) {
slow = slow.next;
quick = quick.next.next;
}
return slow;
}
/** * * 歸併兩個有序的鏈表 * * @param head1 * @param head2 * @return */
private ListNode mergeSort(ListNode head1, ListNode head2) {
ListNode p1 = head1, p2 = head2, head;
//獲得頭節點的指向
if (head1.val < head2.val) {
head = head1;
p1 = p1.next;
} else {
head = head2;
p2 = p2.next;
}
ListNode p = head;
//比較鏈表中的值
while (p1 != null && p2 != null) {
if (p1.val <= p2.val) {
p.next = p1;
p1 = p1.next;
p = p.next;
} else {
p.next = p2;
p2 = p2.next;
p = p.next;
}
}
//第二條鏈表空了
if (p1 != null) {
p.next = p1;
}
//第一條鏈表空了
if (p2 != null) {
p.next = p2;
}
return head;
}
複製代碼
題目描述:輸入兩個單調遞增的鏈表,輸出兩個鏈表合成後的鏈表,固然咱們須要合成後的鏈表知足單調不減規則。
題目描述:輸入一個複雜鏈表(每一個節點中有節點值,以及兩個指針,一個指向下一個節點,另外一個特殊指針指向任意一個節點),返回結果爲複製後複雜鏈表的head。(注意,輸出結果中請不要返回參數中的節點引用,不然判題程序會直接返回空)
題目描述:在一個排序的鏈表中,存在重複的結點,請刪除該鏈表中重複的結點,重複的結點不保留,返回鏈表頭指針。 例如,鏈表1->2->3->3->4->4->5 處理後爲 1->2->5
題目描述:判斷一個單鏈表是否有環 分析:快慢指針,慢指針每次移動一步,快指針每次移動兩步,若是存在環,那麼兩個指針必定會在環內相遇。
題目描述:判斷單鏈表是否有環,若是有,找到環的入口點 解題思路:在第 5 題兩個指針相遇後,讓其中一個指針回到鏈表的頭部,另外一個指針在原地,同時往前每次走一步,當它們再次相遇時,就是在環路的入口點。
題目描述:給出兩個無環單鏈表 解題思路:
方法三的代碼:
public boolean isIntersect(ListNode headA, ListNode headB) {
if (null == headA || null == headB) {
return false;
}
if (headA == headB) {
return true;
}
while (null != headA.next) {
headA = headA.next;
}
while (null != headB.next) {
headB = headB.next;
}
return headA == headB;
}
複製代碼
題目描述:找到兩個無環單鏈表第一個相交點,若是不相交返回空,要求在線性時間複雜度和常量空間複雜度內完成。 解題思路:
方法四的代碼:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (null == headA || null == headB) {
return null;
}
if (headA == headB) {
return headA;
}
ListNode p1 = headA;
ListNode p2 = headB;
while (p1 != p2) {
// 遍歷完所在鏈表後從另一個鏈表再開始
// 當 p1 和 p2 都換到另外一個鏈表時,它們對齊了:
// (1)若是鏈表相交,p1 == p2 時爲第一個相交點
// (2)若是鏈表不相交,p1 和 p2 同時移動到末尾,p1 = p2 = null,而後退出循環
p1 = (null == p1) ? headB : p1.next;
p2 = (null == p2) ? headA : p2.next;
}
return p1;
}
複製代碼
題目描述:上面的問題是針對無環鏈表的,若是是鏈表有環呢? 解題思路:若是兩個有環單鏈表相交,那麼它們必定共有一個環,即環上的任意一個節點都存在於兩個鏈表上。所以能夠先用以前快慢指針的方式找到兩個鏈表中位於環內的兩個節點,若是相交的話,兩個節點在一個環內,那麼移動其中一個節點,在一次循環內確定能夠與另一個節點相遇。