相关基操
查找链表的的中间节点1
[1,2,3,4,5] -> 3 [1,2,3,4,5,6] -> 3
class Solution {
public ListNode findMiddleListNode(ListNode head) {
ListNode slow = head,fast = head;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
查找链表的的中间节点2
[1,2,3,4,5] -> 3 [1,2,3,4,5,6] -> 4
class Solution {
public ListNode findMiddleListNode(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
打印链表工具类
public class ListNodeUtil {
public static void printlnListNode(ListNode head) {
printlnListNode(head,"");
}
public static void printlnListNode(ListNode head, String howTime) {
if(null != head) {
System.out.print(howTime + "[");
}else {
System.out.print(howTime + "null\n");
return;
}
System.out.print(head.val);
ListNode next = head.next;
while (null != next) {
System.out.print(",");
System.out.print(next.val);
next = next.next;
}
System.out.print("]\n");
}
}
头插法和尾插法
public class ListSimple {
public static void main(String[] args) {
int[] nums = new int[]{1,2,3,4,5,6};
// 尾插法构建链表
ListNode head = null;
ListNode lastNode = null;
for (int num : nums) {
ListNode listNode = new ListNode(num);
if(head == null) {
head = listNode;
}else {
lastNode.next = listNode;
}
lastNode = listNode;
}
// 头插法反转链表
ListNode newHead = null;
while (head != null) {
ListNode next = head.next;
head.next = newHead;
newHead = head;
head = next;
}
}
}
基操实战
002. 两数相加
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
int carryNum = 0;
ListNode head = null;
ListNode tempNode = null;
while(l1 != null || l2 != null || carryNum != 0) {
int val1 = l1 == null ? 0 : l1.val;
int val2 = l2 == null ? 0 : l2.val;
// 判断相加后的数,取个数位,进位数存起来
int value = (val1 + val2 + carryNum) % 10;
carryNum = (val1 + val2 + carryNum) / 10;
// 尾插法
ListNode listNode = new ListNode(value);
if(tempNode == null) {
head = listNode;
}else {
tempNode.next = listNode;
}
tempNode = listNode;
if(l1 != null) l1 = l1.next;
if(l2 != null) l2 = l2.next;
}
return head;
}
}
061. 旋转链表
本质就是向后倒数K长度链表拼接剩下的前半段,比较 k 和 链表的余数,连接原本头尾之后,遍历到 k - 1 个结点断开首尾即可。
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if(k == 0 || head == null || head.next == null) return head;
ListNode listNode = head;
int length = 1;
// 遍历获取链表长度
while(listNode.next != null) {
length++;
listNode = listNode.next;
}
// 计算位移偏移量
k = (k > length) ? k % length : k;
if(k == 0) return head;
// 构建环链表
listNode.next = head;
// 移动到倒数k+1位置
for(int i = 0; i < length - k; i++) {
listNode = listNode.next;
}
ListNode newHead = listNode.next;
listNode.next = null;
return newHead;
}
}
083. 删除排序链表中的重复元素
非递归版本尾插法,单结点记录不重复的结点。
class Solution {
public static ListNode deleteDuplicates(ListNode head) {
if(head == null) return null;
ListNode temp = head;
while(temp.next != null) {
if(temp.next.val == temp.val) {
temp.next = temp.next.next;
}else {
temp = temp.next;
}
}
return head;
}
}
086. 分隔链表
小于 x 和大于等于 x 分别用尾插法构建链表,最后连起来。
class Solution {
public ListNode partition(ListNode head, int x) {
if(head == null || head.next == null) return head;
ListNode head1 = null;
ListNode last1 = null;
ListNode head2 = null;
ListNode last2 = null;
ListNode newHead = head;
while (newHead != null) {
ListNode newHeadNext = newHead.next;
if(newHead.val < x) {
if(head1 == null) head1 = newHead;
else last1.next = newHead;
newHead.next = null;
last1 = newHead;
}else {
if(head2 == null) head2 = newHead;
else last2.next = newHead;
newHead.next = null;
last2 = newHead;
}
newHead = newHeadNext;
}
if(last1 != null) last1.next = head2;
else return head2;
return head1;
}
}
092. 反转链表 II
迭代反转比递归反转的好处就是消耗内存比较小。
class Solution {
public ListNode reverseBetween(ListNode head, int m, int n) {
// 虚拟头结点防止越界
ListNode newHead = new ListNode(-1);
newHead.next = head;
ListNode prevNode = newHead;
// prevNode 移动到要反转的前一个结点
for (int i = 1; i < m; i++) { // 链表位置从 1 开始
prevNode = prevNode.next;
}
// 开始反转的结点
head = prevNode.next;
// 迭代反转链表
for (int i = m; i < n; i++) {
ListNode next = head.next;
head.next = next.next;
next.next = prevNode.next;
prevNode.next = next;
}
return newHead.next;
}
}
143. 重排链表
找到中间结点,迭代反转后半部分,遍历拼接。
class Solution {
public void reorderList(ListNode head) {
if(head == null || head.next == null) return;
ListNode slow = head,fast = head;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// 找到要重排的头结点
ListNode newHead = slow.next;
// 迭代反转重排链表
while (newHead.next != null) {
ListNode next = newHead.next;
newHead.next = next.next;
next.next = slow.next;
slow.next = next;
}
// 断开链表 防止死循环
ListNode reverseHead = slow.next;
slow.next = null;
fast = head;
// 遍历拼接
while (fast != null && reverseHead != null) {
ListNode next = fast.next;
ListNode reverseNext = reverseHead.next;
fast.next = reverseHead;
reverseHead.next = next;
fast = next;
reverseHead = reverseNext;
}
}
}
160. 相交链表
设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null) return null;
ListNode l1 = headA,l2 = headB;
while (l1 != l2) {
l1 = (l1 == null) ? headB : l1.next;
l2 = (l2 == null) ? headA : l2.next;
}
return l1;
}
}
递归专题
递归三部曲
- 找到终止条件
- 找到返回值
- 一级递归该做什么
021. 合并两个有序链表
终止条件为两个链表为空,返回头结点为最小的那个结点,一级递归需要对两个链表的值进行比较并合并,同时考虑后续结点的处理。
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if(list1 == null) return list2;
if(list2 == null) return list1;
if(list1.val < list2.val) {
list1.next = mergeTwoLists(list1.next,list2);
return list1;
}else {
list2.next = mergeTwoLists(list1,list2.next);
return list2;
}
}
}
024. 两两交换链表中的节点
终止条件为剩余结点为空或者为单数(不需要交换),返回值为最开始交换的第二个结点,一级递归需要交换相邻的两个结点,同时考虑处理后续结点(也就是结点 next 的指向)。
class Solution {
public ListNode swapPairs(ListNode head) {
if(head == null || head.next == null) return head;
ListNode newHead = head.next;
head.next = swapPairs(newHead.next);
newHead.next = head;
return newHead;
}
}
082. 删除排序链表中的重复元素 II
while 循环来处理重复元素结点,if 判断主要用来判断当前结点是否重复。
class Solution {
public ListNode deleteDuplicates2(ListNode head) {
if(head == null || head.next == null) return head;
ListNode tempNode = head.next;
if(tempNode.val == head.val) {
while (tempNode != null && tempNode.val == head.val) {
tempNode = tempNode.next;
}
head = deleteDuplicates2(tempNode);
}else {
head.next = deleteDuplicates2(tempNode);
}
return head;
}
}
083. 删除排序链表中的重复元素
递归版本,从后向前比较,不相同返回头结点。
class Solution {
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;
}
}
206. 反转链表
递归返回最后的结点(也就是头结点),同时在递归的过程中反转链表(下一个结点指向当前结点,当前结点的下一个结点置为空)。
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) return head;
ListNode newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
}
328. 奇偶链表
记录奇偶链表的头结点,尾插法形成奇偶链表,奇链表尾结点指向偶链表头结点。
class Solution {
public ListNode oddEvenList(ListNode head) {
if(head == null) return null;
ListNode oddLast = head;
ListNode evenHead = head.next;
ListNode even = evenHead;
while(even != null && even.next != null) {
oddLast.next = oddLast.next.next;
oddLast = oddLast.next;
even.next = even.next.next;
even = even.next;
}
oddLast.next = evenHead;
return head;
}
}
快慢指针专题
中间节点
2095. 删除链表的中间节点
变异快慢指针,快指针先出发一步,慢指针会在常规中间结点之前一步停下来。
class Solution {
public ListNode deleteMiddle(ListNode head) {
if(head == null || head.next == null) return null;
ListNode slow = head,fast = head.next;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
slow.next = slow.next.next;
return head;
}
}
第 N 个结点
019. 删除链表的倒数第 N 个结点
双指针法解决,虚拟头结点防止删除头结点,快指针先出发 N+1 步(此时链表有虚拟头结点,长度+1),同时移动快慢指针,快指针为空时,慢指针刚好在要删除的节点之前。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
if(head == null) return null;
ListNode newHead = new ListNode(-1);
newHead.next = head;
ListNode fast = newHead,slow = newHead;
for (int i = 0; i <= n; i++) {
fast = fast.next;
}
while(fast != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return newHead.next;
}
}
092. 反转链表 II
遍历到 m - 1 位置结点,对 m - n 位置的结点迭代反转。
class Solution {
public ListNode reverseBetween2(ListNode head, int m, int n) {
// 虚拟头结点防止越界
ListNode newHead = new ListNode(-1);
newHead.next = head;
ListNode prevNode = newHead;
// prevNode 移动到要反转的前一个结点
for (int i = 1; i < m; i++) { // 链表位置从 1 开始
prevNode = prevNode.next;
}
// 开始反转的结点
head = prevNode.next;
// 迭代反转链表
for (int i = m; i < n; i++) {
ListNode next = head.next;
head.next = next.next;
next.next = prevNode.next;
prevNode.next = next;
}
return newHead.next;
}
}
1669. 合并两个链表
找到 a - 1 和 b + 1 位置的结点,拼接 list2 即可。
class Solution {
public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
ListNode listNode = list1;
ListNode prevNode = null;
ListNode lastNode = null;
for (int i = 0; i < b; i++) {
if(i == a - 1) prevNode = listNode;
listNode = listNode.next;
}
lastNode = listNode.next;
if(prevNode != null) prevNode.next = list2;
while (list2.next != null) {
list2 = list2.next;
}
list2.next = lastNode;
return list1;
}
}
1721. 交换链表中的节点
添加虚拟头结点(长度 + 1),快指针先出发 k 步,保存当前的结点(正数 k 位置),同时移动快慢指针,快指针为空时,慢指针刚好在倒数 k 位置,此时交换值即可。
class Solution {
public ListNode swapNodes(ListNode head, int k) {
if(head == null || head.next == null) return head;
ListNode newHead = new ListNode(-1);
newHead.next = head;
ListNode slow = newHead,fast = newHead;
int n = k;
while (n > 0) {
fast = fast.next;
n--;
}
ListNode tempNode = fast;
while (fast != null) {
slow = slow.next;
fast = fast.next;
}
if(tempNode != null) {
int tempVal = slow.val;
slow.val = tempNode.val;
tempNode.val = tempVal;
}
return newHead.next;
}
}
环形链表
142. 环形链表 II
设 x 为头结点到环入口结点的距离,假设链表有环,快慢指针必定相遇。设 y 为环入口结点移动到相遇点的距离,设 z 为相遇点移动到环入口结点的距离,设 c 为环的周长,得:c = y + z。
假设快指针移动到相遇点经过的距离为 F ,F = x + y + m * c(m 为 快指针绕环的圈数),假设慢指针移动到相遇点的距离为 S ,S = x + y + n * c (n 为 慢指针绕环的圈数);在快指针步长为 2,慢指针步长为 1 的情况下,有 F = 2 * S。综上所述得到:2 * (x + y + n * c) = x + y + m * c,移项之后使得:x = (m - 2 * n - 1) * c + z。此时将快指针指向头结点,快指针和慢指针同时移动 (m - 2 * n - 1) * c 距离,快指针此时肯定在环中,则快指针在环中移动的距离为 (m - 2 * n - 1) * c - x,即为 z。慢指针开始移动时已经在环中,移动 (m - 2 * n - 1) * c 距离还是在原来的位置,同样为 z。此时快指针和慢指针又在相遇点相遇,可以这样看,快指针从环入口结点出发,慢指针从相遇点出发,快指针少移动了 x 距离,慢指针多移动了 z 距离,使得 x = z。
综上所述,快慢指针相遇点与头结点距离环入口结点长度相等。
class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head,slow = head,temp = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if(slow == fast) {
fast = head;
while(fast != slow) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
return null;
}
}