雙指針問題
兩個鏈條求交點,每一條鏈條到達尾端的時候,執行一次鏈條的切換,可以最終使兩個鏈條達到同步運行的效果。這裏注意到達尾部的條件是爲null,而不是下一個結點爲null
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pointA = headA, pointB = headB;
while (pointA != pointB) {
// 到達尾部的時候才需要進行切換鏈條
pointA = (pointA == null) ? headB : pointA.next;
pointB = (pointB == null) ? headA : pointB.next;
}
return pointA;
}
反轉鏈表
總結:需要替換什麼的時候,一定要給這個結點做好備份
public ListNode reverseList(ListNode head) {
ListNode cur = head, prev = null;
while(cur != null){
// 將下一個結點做一個備份
ListNode temp = cur.next;
cur.next = prev;
prev = cur;
// 切換到下一個結點
cur = temp;
}
return prev;
}
有序鏈表的歸併
採用一個優先隊列進行存儲;注意不能直接使用優先隊列的toArray方法,而需要依次執行出入隊來獲取元素。
同時哦注意對於輸入鏈表本身的判空。還需要注意對最後一個鏈表的尾部置位null
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null && l2 == null) return null;
PriorityQueue<ListNode> queue = new PriorityQueue<>((o1, o2) -> o1.val - o2.val);
while (l1 != null) {
queue.offer(l1);
l1 = l1.next;
}
while (l2 != null) {
queue.offer(l2);
l2 = l2.next;
}
ListNode[] nodes = new ListNode[queue.size()];
int i = 0;
while (!queue.isEmpty()) {
nodes[i++] = queue.poll();
}
for (int j = 0; j < nodes.length - 1; j++) {
nodes[j].next = nodes[j + 1];
}
nodes[nodes.length - 1].next = null;
return nodes[0];
}
更優解:藉助了本身有序的特性,以及遞歸的思想:
public ListNode mergeTwoListsTraverse(ListNode l1, ListNode l2) {
// 遞歸邊界
if (l1 == null) return l2;
if (l2 == null) return l1;
if (l1.val < l2.val) {
// 如果l1的值更小,滿足升序,這時只需要將l1的下個結點與l2進行比較即可
// l1的下一個鏈表鏈條即是兩個鏈表結合過後的鏈條
l1.next = mergeTwoListsTraverse(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoListsTraverse(l1, l2.next);
return l2;
}
}
刪除鏈表的重複結點
重點: 遞歸條件來源 – 當遍歷到達尾部的時候停止
中間操作: 刪除下一個結點的重複結點 — 這個是從尾部到頭部
返回的確認:當有相等的值,則將head.next壓棧,進行刪除操作,直到最後出棧頭部結點
遞歸是一個入棧的操作
public ListNode deleteDuplicates(ListNode head) {
if(head == null || head.next == null) return head;
head.next = deleteDuplicates(head.next);
return head.val == head.next.val ? head.next : head;
}
刪除倒數第N個結點
注意:
- 啞結點的作用 – 避免列表只有一個結點的情況出現。用於存儲一個頭結點
- 第二次遍歷的時候,從啞結點開始遍歷
public ListNode removeNthFromEnd2(ListNode head, int n) {
// 啞結點--避免出現列表只有一個結點的情況
ListNode dummy = new ListNode(0);
dummy.next = head;
int length = 0;
ListNode first = head;
while (first != null) {
length++;
first = first.next;
}
first = dummy;
length -= n;
while (length > 0) {
length--;
first = first.next;
}
first.next = first.next.next;
// 頭結點
return dummy.next;
}
還有一次遍歷法 – 方法類似雙指針
交換鏈表中的相鄰結點
重點:
- 啞結點的使用,用在頭部,使頭部結點可以更換
- 中間結點儲存結果
- 如果鏈接一個頭部變換過的節點
public ListNode swapPairs(ListNode head) {
// 啞結點代替頭部,可以更換頭部結點
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
while (pre.next != null && pre.next.next != null) {
ListNode l1 = pre.next, l2 = pre.next.next;
// 中間結點, 用於存儲節點的轉移位置
ListNode next = l2.next;
// 執行結點翻轉操作
l1.next = next;
l2.next = l1;
// 指針重接
pre.next = l2;
pre = l1;
}
return dummy.next;
}
鏈表求和
tips:一般需要逆序求解的時候都需要用到棧來進行輔助。
- 中間鏈表插入結點,需要熟記,凡是最後需要返回頭結點的,都需要有一個dummy結點作爲固定指針位
- 鏈表的逆序求解,需要藉助棧進行
public ListNode addTwoNumbers2(ListNode l1, ListNode l2) {
Stack<Integer> stack1 = buildStack(l1);
Stack<Integer> stack2 = buildStack(l2);
// 頭部固定指針位
ListNode head = new ListNode(-1);
// 計算進位
int carry = 0;
while (!stack1.isEmpty() || !stack2.isEmpty() || carry != 0) {
// 通過三元表達式判定,當棧爲空時,返回0,否則棧頂出棧
int num1 = stack1.isEmpty() ? 0 : stack1.pop();
int num2 = stack2.isEmpty() ? 0 : stack2.pop();
int sum = num1 + num2 + carry;
ListNode node = new ListNode(sum % 10);
// 插入鏈表結點
node.next = head.next;
head.next = node;
// 進位計算
carry = sum / 10;
}
return head.next;
}
// 鏈表只能從左至右添加,但是需要從右至左遍歷的時候,需要藉助棧
private Stack<Integer> buildStack(ListNode node) {
Stack<Integer> stack = new Stack<>();
while (node != null) {
stack.push(node.val);
node = node.next;
}
return stack;
}