反转链表
题目
版本1 正确
public ListNode reverseList(ListNode head) {
if (head == null) {
return head;
}
// 翻转链表
ListNode newHead = new ListNode(-1);
newHead.next = head;
// head永远指向翻转完链表的最后一个元素
while (head.next != null) {
ListNode next = head.next;
head.next = next.next;
next.next = newHead.next;
newHead.next = next;
}
return newHead.next;
}
删除链表的节点
题目
版本1 正确
public ListNode deleteNode(ListNode head, int val) {
if (head == null) {
return head;
}
ListNode temp = head;
if (temp.val == val) {
return temp.next;
}
while (temp.next != null) {
ListNode next = temp.next;
if (next.val == val) {
temp.next = next.next;
next.next = null;
break;
}
temp = temp.next;
}
return head;
}
从尾到头打印链表
题目
版本1 正确
public int[] reversePrint(ListNode head) {
if (head == null) {
return new int[0];
}
List<Integer> ans = new ArrayList<>();
while (head != null) {
ans.add(head.val);
head = head.next;
}
Collections.reverse(ans);
return ans.stream().mapToInt(Integer::intValue).toArray();
}
正确的原因
(1) 先存到list中, 然后翻转, 然后转成数组输出
版本2 正确
public int[] reversePrint(ListNode head) {
if (head == null) {
return new int[0];
}
List<Integer> ans = new ArrayList<>();
while (head != null) {
ans.add(head.val);
head = head.next;
}
int [] arrayAns = new int[ans.size()];
for (int i = ans.size() - 1; i >= 0; i --) {
arrayAns[ans.size() - 1 - i] = ans.get(i);
}
return arrayAns;
}
正确的原因
(1) 不采用现成的api, 自己实现翻转
合并两个排序的链表
题目
版本1 正确
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// 合并两个递增的链表
// 不创建新的节点
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
ListNode ans = l1.val > l2.val ? l2 : l1;
// 将l1指向第一个元素小的链表
if (l1.val > l2.val) {
ListNode temp = l2;
l2 = l1;
l1 = temp;
}
while (l1 != null) {
// l1走
while (l1.next != null && l1.next.val < l2.val) {
l1 = l1.next;
}
if (l1.next == null) {
l1.next = l2;
break;
} else {
// 交换l1和l2
ListNode temp = l1.next;
l1.next = l2;
l1 = l2;
l2 = temp;
}
}
return ans;
}
正确的原因
(1) l1指向的元素永远比l2的小, 然后l1运动, 遇见比l1.next比l2.val大的时候, 就把l2链接到l1后面, 然后l1变成l2, l2变成l1.next, 此时依旧是l1指向的元素永远比l2的小
- 例如
- 初始状态
- l1 : 1 -> 2 -> 3 -> 8 -> null
- l2 : 4 -> 5 -> 6 -> null
- 第一次结束后:
- l1指向3, l2指向4, 交换链接后
- l1 : 1 -> 2 -> 3-> 4 -> 5-> 6 -> null, 此时l1指向3
- l2 : 8 -> null 此时l2指向8
最后一定是l1.next == null的情况, 可以跳出循环
链表中倒数第K个节点
题目
版本1 正确
public ListNode getKthFromEnd(ListNode head, int k) {
// 倒数第K个节点
// 利用快慢指针
ListNode fast = head;
ListNode slow = head;
while (fast != null) {
fast = fast.next;
k --;
if (k < 0) {
slow = slow.next;
}
}
return slow;
}
正确的原因
(1) 利用快慢指针即可, 注意慢指针啥时候开始走
删除链表中倒数第n个节点
题目
版本1 正确
public ListNode removeNthFromEnd(ListNode head, int n) {
// 删除链表的倒数第n个节点
// 利用快慢指针, 慢指针比快指针慢n步触发
ListNode fast = head;
ListNode slow = head;
ListNode preNode = null;
while (fast != null) {
fast = fast.next;
n --;
if (n < 0) {
preNode = slow;
slow = slow.next;
}
}
if (preNode == null) {
// 就表示要删除第一个节点
return head.next;
}
// 断开preNode和slow之间的连接即可
preNode.next = slow.next;
slow.next = null;
return head;
}
正确的原因
(1) 题目限制了, n一定是小于链表节点的总数目的, 因此当preNode == null的时候, 就表示要删除的其实是头节点.
两个链表的第一个公共节点
题目
版本1 正确
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 先获得两个链表的长度, 然后长链表先走几步
// 然后两个链表一起走, 相遇的节点就是第一个公共节点
ListNode tempA = headA;
ListNode tempB = headB;
int lenA = 0;
while (tempA != null) {
lenA ++;
tempA = tempA.next;
}
int lenB = 0;
while (tempB != null) {
lenB ++;
tempB = tempB.next;
}
if (lenA > lenB) {
// headA先走几步
for (int i = 0; i < lenA - lenB; i++) {
headA = headA.next;
}
}
if (lenB > lenA) {
// headB先走几步
for (int i = 0; i < lenB - lenA; i++) {
headB = headB.next;
}
}
// 两个一起走
while (headA != headB) {
headA = headA.next;
headB = headB.next;
}
return headA;
}
正确的原因
(1) 长链表先走几步, 然后同时走, 遇见的相同的节点就是公共节点
环形链表
题目
版本1 正确
public boolean hasCycle(ListNode head) {
if (head == null) {
return Boolean.FALSE;
}
// 判断链表是否是环形
// 利用快慢指针
ListNode fast = head;
ListNode slow = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
break;
}
}
if (fast.next == null || fast.next.next == null) {
return Boolean.FALSE;
}
return Boolean.TRUE;
}
正确的原因
(1) 快慢指针, 判断指针是否相遇即可
环形链表II
题目
版本1 正确
public ListNode detectCycle(ListNode head) {
if (head == null) {
return null;
}
// 环形链表
// 首先判断是否有环形, 如果没有返回null
// 如果有, 返回构成环形的节点
// 利用快慢指针 判断链表是否是环形的
ListNode fast = head;
ListNode slow = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
// 如果快慢指针相遇了, 表示构成了环形
break;
}
}
// 千万注意 这里不能用slow != fast来判断, 因为假设链表就一个元素[1], 那么此时slow和fast是相等的
if (fast.next == null || fast.next.next == null) {
// 表示上面的循环是因为fast到头而结束的, 没有构成环形
return null;
}
// 寻找环形的节点
// 令fast此时从头开始走
fast = head;
// 同时走, 相遇的节点就是环形的节点
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
正确的原因
(1) 注意判断快慢指针相遇后的条件, 不能用fast != slow, 一定要用fast.next == null || fast.next.next == null
回文链表
题目
版本1 正确
public boolean isPalindrome(ListNode head) {
if (head.next == null) {
return Boolean.TRUE;
}
// 寻找到链表的中间节点
// 然后将后半部分翻转, 再比较前半部分和后半部分对应的值
ListNode fast = head;
ListNode slow = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
if (fast.next == null) {
// 奇数个节点
slow.next = reverseList(slow.next);
} else {
// 偶数个节点
slow.next = reverseList(slow.next);
}
fast = slow.next;
slow = head;
while (fast != null && slow != null) {
if (fast.val != slow.val) {
return Boolean.FALSE;
}
fast = fast.next;
slow = slow.next;
}
return Boolean.TRUE;
}
public ListNode reverseList(ListNode head) {
if (head == null) {
return head;
}
// 翻转链表
ListNode newHead = new ListNode(-1);
newHead.next = head;
// head永远指向翻转完链表的最后一个元素
while (head.next != null) {
ListNode next = head.next;
head.next = next.next;
next.next = newHead.next;
newHead.next = next;
}
return newHead.next;
}
正确的原因
(1) 寻找构成回文的链表的两部分, 将后半部分翻转, 就可以同时比较前半部分和后半部分的值了
(2) 后半部分的链表, 必须经过反转后再链接
复杂链表的复制
题目
版本1 正确
public Node copyRandomList(Node head) {
if (head == null) {
return head;
}
// 用一个map, key为原始节点, value为新创建的节点
Map<Node, Node> map = new HashMap<>();
Node cur = head;
while (cur != null) {
Node newNode = new Node(cur.val);
map.put(cur, newNode);
cur = cur.next;
}
cur = head;
// 赋予链接关系
while (cur != null) {
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
return map.get(head);
}
正确的原因
(1) 先创建完所有的节点, 再完成链接关系的赋值
旋转链表
题目
版本1 正确
public ListNode rotateRight(ListNode head, int k) {
if (head == null) {
return head;
}
// 就是寻找到倒数第k个节点, 然后将尾巴节点链接到头部
// 新的头指针就是倒数第k个节点
// 例如 1 -> 2 -> 3 -> 4 -> 5 旋转2
// 就是找到倒数第二个节点4, 然后把5链1, 4作为头节点
// 变成4 -> 5 -> 1 -> 2 -> 3
// 先求链表的长度
int len = 0;
ListNode cur = head;
while (cur != null) {
len ++;
cur = cur.next;
}
// 旋转len长度等于没旋转
k = k % len;
if (k == 0) {
return head;
}
// 旋转小于len次
ListNode fast = head;
ListNode slow = head;
while (fast.next != null) {
fast = fast.next;
k --;
if (k < 0) {
slow = slow.next;
}
}
fast.next = head;
ListNode ans = slow.next;
slow.next = null;
return ans;
}
正确的原因
(1) 问题转化成寻找倒数第k个节点, 注意旋转次数过多的情况
分割链表
题目
版本1 正确
public ListNode[] splitListToParts(ListNode root, int k) {
// 先计算出每个链表的大小应该是多少
int len = 0;
ListNode cur = root;
while (cur != null) {
len ++;
cur = cur.next;
}
ListNode [] ans = new ListNode[k];
int extra = len % k;
int base = len / k;
// cur指向每个分组的开始
cur = root;
for (int i = 0; i < ans.length; i ++) {
if (cur == null) {
ans[i] = null;
} else {
// temp指向每个分组的结尾
ListNode temp = cur;
for(int j = 1; j < base; j ++) {
temp = temp.next;
}
if (extra > 0) {
if (base != 0) {
temp = temp.next;
}
extra --;
}
ListNode result = cur;
if (temp != null) {
// 修改cur
cur = temp.next;
temp.next = null;
}
// 赋予值
ans[i] = result;
}
}
return ans;
}
正确的原因
(1) 先计算出每个分组应该有几个元素, 然后利用两个指针cur和temp指向分组的开头和结尾, 注意元素的个数和指针移动的对应关系, 边界比较麻烦.
分隔链表
题目
版本1 正确
public ListNode partition(ListNode head, int x) {
if (head == null || head.next == null) {
return head;
}
int target = x;
// cur作为临时指针, 用来遍历链表中的每个元素
ListNode cur = head;
// dif指向小于target节点的最后一个, dif.next >= target
ListNode dif = null;
if (cur.val < target) {
dif = cur;
}
while (cur.next != null) {
if (cur.next.val >= target) {
cur = cur.next;
} else {
// 当cur.next小于target的时候
if (dif == null) {
// dif还没有赋值, 表示遇见了第一个小于target的数字
dif = cur.next;
cur.next = cur.next.next;
dif.next = head;
head = dif;
// 这里cur不需要移动, 因为cur.next已经发生了变化
} else {
if (cur == dif) {
// 表面此时cur经过的元素都是小于target的
cur = cur.next;
dif = dif.next;
} else {
// 将cur.next插入到dif之后
ListNode next = cur.next;
cur.next = next.next;
next.next = dif.next;
dif.next = next;
dif = next;
}
}
}
}
return head;
}
正确的原因
(1) 利用一个指针将链表分割开来, 小于x的就插入到指针到后面, 否则就不移动
两两交换链表中的节点
题目
版本1 正确
class Solution {
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = head.next;
// 相邻两个节点翻转
ListNode cur = head;
ListNode pre = null;
while (cur != null) {
ListNode next = cur.next;
if (next == null) {
break;
}
ListNode nextNext = next.next;
if (pre == null) {
cur.next = nextNext;
next.next = cur;
pre = cur;
cur = nextNext;
} else {
pre.next = next;
cur.next = nextNext;
next.next = cur;
pre = cur;
cur = nextNext;
}
}
return newHead;
}
}
正确的原因
(1) 注意pre节点的赋值
对链表进行插入排序
题目
版本1 正确
public ListNode insertionSortList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
// 链表排序
// 采用两个指针分别指向有序链表的头部和尾部
// 一个cur指针指向链表中的每个元素
ListNode sortHead = head;
ListNode sortTail = head;
// head永远是sortTail的下一个元素, 维持这一点
head = sortTail.next;
while (head != null) {
if (head.val <= sortHead.val) {
// 将head对应的节点, 插入到有序链表的头部
sortTail.next = head.next;
head.next = sortHead;
sortHead = head;
} else if (head.val >= sortTail.val) {
// 将head对应的节点, 插入到有序链表的尾部
// head本身就是有序链表的下一个节点, 只需要转移sortTail指针即可
sortTail = head;
} else {
// 将head对应的节点, 插入到有序链表的中间
// 寻找head应该插入的位置
ListNode cur = sortHead;
while (cur.next != null && cur.next.val < head.val) {
cur = cur.next;
}
// 将head插入到cur之后
ListNode temp = cur.next;
cur.next = head;
sortTail.next = head.next;
head.next = temp;
}
head = sortTail.next;
}
return sortHead;
}
正确的原因
(1) 明确两个指针指向有序链表的头部和尾部, 然后head永远指向的是有序链表尾部的下一个, 才能保证不出错.
链表排序
题目
版本1 快排 其实本质就是链表分区
public ListNode sortList(ListNode head) {
// 链表的快排, 其实就是链表分区的问题, 给出一个值, 将链表分成2部分
// 然后对于另外两个部分, 再递归进行快排
return quirkSort(head);
}
public ListNode quirkSort(ListNode head) {
if (head == null || head.next == null) {
return head;
}
// 选择head作为基准值
int target = head.val;
// cur作为临时指针, 用来遍历链表中的每个元素
ListNode cur = head;
// dif指向小于target节点的最后一个, dif.next >= target
ListNode dif = null;
if (cur.val < target) {
dif = cur;
}
while (cur.next != null) {
if (cur.next.val >= target) {
cur = cur.next;
} else {
// 当cur.next小于target的时候
if (dif == null) {
// dif还没有赋值, 表示遇见了第一个小于target的数字
dif = cur.next;
cur.next = cur.next.next;
dif.next = head;
head = dif;
// 这里cur不需要移动, 因为cur.next已经发生了变化
} else {
if (cur == dif) {
// 表面此时cur经过的元素都是小于target的
cur = cur.next;
dif = dif.next;
} else {
// 将cur.next插入到dif之后
ListNode next = cur.next;
cur.next = next.next;
next.next = dif.next;
dif.next = next;
dif = next;
}
}
}
}
ListNode leftHead = null;
ListNode rightHead = null;
// dif如果存在, 那么dif.next一定存在
ListNode base = dif == null ? head : dif.next;
// dif可能为null, 也就是head是链表中的最小值
if (dif == null) {
// head就是base, 只需要判断head的右侧链表
rightHead = quirkSort(base.next);
} else {
// 左侧链表
dif.next = null;
leftHead = quirkSort(head);
// 右侧链表
rightHead = quirkSort(base.next);
}
// 然后拼接一次
if (leftHead == null) {
base.next = rightHead;
return base;
}
ListNode leftTail = leftHead;
while (leftTail.next != null) {
leftTail = leftTail.next;
}
leftTail.next = base;
base.next = rightHead;
return leftHead;
}
正确的原因
(1) 每一轮快排, 其实就是链表分区的问题, 链表分区完成后, 需要根据不同的情况, 递归对左右链表进行处理.
(2) 递归处理的时候, 记得讨论dif存不存在的情况
版本2 归并排序
public ListNode sortList(ListNode head) {
// 拆分链表的终止条件
if (head == null || head.next == null) {
return head;
}
// 链表最适合的排序方法, 是归并排序
// 将链表从中间节点分成两半, 最后再归并两个有序链表
// 链表的拆分 采用快慢指针的方式
ListNode fast = head;
ListNode slow = head;
while (fast.next != null && fast.next.next != null) {
// 快指针走两步, 慢指针走一步
fast = fast.next.next;
slow = slow.next;
}
// slow指针的节点就是中间节点
ListNode mid = slow.next;
// 将原链表断成两个链表
slow.next = null;
ListNode leftHead = sortList(head);
ListNode rightHead = sortList(mid);
// 合并两个有序链表
return mergeSortList(leftHead, rightHead);
}
public ListNode mergeSortList(ListNode l1, ListNode l2) {
// 合并两个有序链表
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
// 以l1为基准, 让l1指向头节点小的链表
if (l1.val > l2.val) {
ListNode temp = l2;
l2 = l1;
l1 = temp;
}
ListNode head = l1;
// l1指针不断扫描链表中比l2指针小的元素
// 当l1.next > l2的时候, 将l1的部分连接到l2上, 然后交换l1和l2
while (l1 != null && l2 != null) {
while (l1.next != null && l1.next.val <= l2.val) {
l1 = l1.next;
}
// temp是大于l2的元素
ListNode temp = l1.next;
if (temp == null) {
l1.next = l2;
break;
} else {
l1.next = l2;
l1 = l2;
l2 = temp;
}
}
return head;
}
正确的原因
(1) 其实本质就是寻找链表的中间节点, 然后断开分成两部分链表, 将原链表不断拆分
(2) 合并的时候, 就是合并两个有序链表的解法
所以综上所述, 就是寻找链表中间节点 + 合并两个有序链表