目录
8、leetcode82: 删除排序链表中所有重复节点【易错】
9、leetcode83: 删除排序链表中的重复节点【重复节点只保留一个】
10、leetcode86: 分割链表【=x的在x右边,位置不变】的在x左边,>
22、leetcode237: 删除链表中的节点【无法通过头节点删除】
一、链表
0、链表结构定义
public class ListNode{
private ListNode next;
private int val;
public ListNode(int val){
this.val = val;
}
}
1、leetcode2: 两数相加
题目描述:链表表示数据,例如[2,4,3] 表示342.求两个链表的和。结果正序表示。
例如: [2,4,3] + [5,6,4] = [7,0,8]
public static ListNode problem2_TwoSum(ListNode l1, ListNode l2){
if(l1 == null) return l2;
if(l2 == null) return l1;
ListNode res = new ListNode(-1), tmp = res;
int carry = 0;
while(l1 != null || l2 != null) {
int num1 = l1 == null ? 0 : l1.val;
int num2 = l2 == null ? 0 : l2.val;
int sum = num1 + num2 + carry;
tmp.next = new ListNode(sum % 10);
carry = sum/10;
if(l1 != null) l1 = l1.next;
if(l2 != null) l2 = l2.next;
tmp = tmp.next;
}
if (carry > =) tmp.next = new ListNode(carry);
return res.next;
}
2、leetcode19: 删除倒数第N个节点
题目描述:删除链表的倒数第N个节点。
例如:[1,2,3,4,5], n = 2。 输出[1,2,3,5]
解法思路: 双指针。快指针先走n步,然后快慢指针一起走
private static ListNode problem19_deleteLastNNode(ListNode head, int n) {
if(head == null || n <= 0) return head;
ListNode res = new ListNode(-1), fast = res, slow = res;
res.next = head;
for(int i = 0; i<n && fast != null; i++) {
fast = fast.next;
}
if( fast == null) return head;
while(fast.next != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return res.next;
}
3、leetcode21: 合并两个排序链表
题目描述:合并两个有序连表。
例如: [1,2,4] + [1,3,4] = [1,1,2,3,4,4]
private static ListNode problem21_MergeTwoSortedLinkedList(ListNode l1, ListNode l2) {
if(l1 == null) return l2;
if(l2 == null) return l1;
ListNode res = new ListNode(-1), tmp = res;
while(l1 != null && l2 != null) {
if(l1.val <= l2.val) {
tmp.next = l1;
l1 = l1.next;
}else {
tmp.next = l2;
l2 = l2.next;
}
tmp = tmp.next;
}
if(l1 != null) tmp.next = l1;
if(l2 != null) tmp.next = l2;
return res.next;
}
4、leetcode23: 合并k个有序连表
题目描述: 合并k个有序连表
解法思路: 使用优先队列。将链表头节点塞入优先队列,poll出最小的连表节点,改变指针指向。如果还有子节点,继续塞入队列中。
private static ListNode problem21_MergeKSortedLinkedList(ListNode[] lists) {
if(lists == null || lists.length == 0) return null;
if(lists.length == 1) return lists[0];
PriorityQueue<ListNode> queue = new PriorityQueue<ListNode>(lists.length, (o1, o2) -> {return o1.val - o2.val;});
// 优先队列添加有序连表头节点
for(int i = 0; i< lists.length; i++) {
if(lists[i] != null) {
queue.add(lists[i]);
}
}
ListNode res = new ListNode(-1), tmp = res;
while(!queue.isEmpty()) {
ListNode node = queue.poll();
ListNode next = node.next;
node.next = null;
tmp.next = node;
tmp = tmp.next;
if(next != null) queue.add(next);
}
return res.next;
}
5、leetcode24: 交换相邻节点
题目描述:交换连表相邻的两个节点
例1: [1,2,3,4].输出 [2,1,4,3]
例2: [1,2,3,4,5].输出 [2,1,4,3,5]
// 1,2,3,4 -> 2,1,4,3
// 1,2,3,4, 5 -> 2,1,4,3,5
private static ListNode problem24_ExchangeNodeNearby(ListNode head) {
if(head == null || head.next == null) return head;
ListNode res = new ListNode(-1), tmp = res, current = head, next, next2;
while(current != null) {
next = current.next;
if(next == null) break;
next2 = next.next;
tmp.next = next;
next.next = current;
current.next = next2;
tmp = current;
current = next2;
}
return res.next;
}
6、leetcode25: k个一组反转连表
题目描述: k个节点一组反转链表
例1: [1,2,3,4,5], 2个一组反转。输出: [2,1,4,3,5]
/**
* 例如: [1,2,3,4,5] k=2 -> [2,1,4,3,5]
* 解法思路: 反转链表升级版,每次走k步,截断链表,反转之后在连接上去
*/
private static ListNode problem25_ReverseByKNode(ListNode head, int k) {
if(head == null || head.next == null || k<= 1) return head;
ListNode res = new ListNode(-1), tmp = res, current = head, start, next;
tmp.next = head;
while(current != null) {
start = current;
for(int i = 0; i<k-1 && current != null; i++) {
current = current.next;
}
if(current == null) break;
next = current.next;
current.next = null;
// 注意: 此处反转之后,头节点变成current,尾节点是start
tmp.next = reverse(start);
start.next = next;
tmp = start;
current = next;
}
return res.next;
}
public static ListNode reverse(ListNode head){
if(head == null || head.next == null) return head;
ListNode node = reverse(head.next);
head.next.next = head;
head.next = null;
return node;
}
7、leetcode61: 将链表往后旋转k位
题目描述: 链表旋转k位.
例如: [1,2,3,4,5], k=2|7。输出[4,5,1,2,3]
/**
* 例1: [1,2,3,4,5] k=2 -> [4,5,1,2,3]
* 例2: [0,1,2] k = 4 -> [2,0,1]
* 解法思路: 先获取链表长度,对k取余数,将余数后对节点截断链接至头节点前
*/
private static ListNode problem61_RotateLinkedListByK(ListNode head, int k) {
if(head == null || head.next == null || k<=0) return head;
// TODO 获取链表长度时可以用一个全局变量保存链表尾节点,避免在遍历一次
int len = getListNodeLen(head);
int step = len - (k % len);
if(step == 0) return head;
ListNode res = new ListNode(-1), tmp = res;
tmp.next = head;
// 注意,如果是左移2位,前面剩下的是len-2个
for(int i = 0; i<len-step; i++) {
tmp = tmp.next;
}
ListNode next = tmp.next;
tmp.next = null;
res.next = next;
while(next.next != null){
next = next.next;
}
next.next = head;
return res.next;
}
public static int getListNodeLen(ListNode head) {
int len = 0;
ListNode tmp = head;
while(tmp != null) {
len++;
tmp = tmp.next;
}
return len;
}
8、leetcode82: 删除排序链表中所有重复节点【易错】
题目描述:删除链表中所有重复的节点【重复的一个不留】
例如: [1,2,3,3,4,4,5]。输出: [1,2,5]
private static ListNode problem82_DeletedAllDuplicateNode(ListNode head) {
if(head == null || head.next == null) return head;
ListNode res = new ListNode(-1), tmp = res;
tmp.next = head;
while(tmp != null) {
if(tmp.next != null && tmp.next.next != null && tmp.next.val == tmp.next.next.val) {
int duplicateVal = tmp.next.val;
while(tmp.next != null && tmp.next.val == duplicateVal) {
tmp.next = tmp.next.next;
}
}else {
tmp = tmp.next;
}
}
return res.next;
}
9、leetcode83: 删除排序链表中的重复节点【重复节点只保留一个】
题目描述:删除链表中所有重复的节点【重复的留一个】
例如: [1,2,3,3,4,4,5]。输出: [1,2,3,4,5]
private static ListNode problem83_DeletedDuplicateNode(ListNode head) {
if(head == null || head.next == null) return head;
ListNode res = new ListNode(-1), tmp = res;
tmp.next = head;
while(tmp != null) {
while(tmp.next != null && tmp.next.next != null && tmp.next.val == tmp.next.next.val) {
tmp.next = tmp.next.next;
}
tmp = tmp.next;
}
return res.next;
}
10、leetcode86: 分割链表【<x的在x左边,>=x的在x右边,位置不变】
题目描述:以x分割链表,使得小于x的出现在左侧。节点的相对位置不变
例1: [1,4,3,2,5,2], x=3, 输出: [1,2,2,4,3,5] ; 例2: [2,1], x=2, 输出: [1,2]
解法: 一次遍历,小于x的和大于等于x的分成两个链表,然后拼接
private static ListNode problem86_SplitLinkedListNode(ListNode head, int x) {
if(head == null || head.next == null) return head;
ListNode leftHead = new ListNode(-1), leftTmp = leftHead;
ListNode rightHead = new ListNode(-1), rightTmp = rightHead;
ListNode tmp = head;
while(tmp != null){
if(tmp.val < x){
leftTmp.next = tmp;
leftTmp = leftTmp.next;
}else {
rightTmp.next = tmp;
rightTmp = rightTmp.next;
}
ListNode next = tmp.next;
tmp.next = null;
tmp = next;
}
if(leftHead.next == null) {
return rightHead.next;
}
leftTmp.next = rightHead.next;
return leftHead.next;
}
11、leetcode92: 反转链表II
题目描述: 反转从位置
left
到位置right
的链表节点,返回 反转后的链表 。例如:[1,2,3,4,5] left= 2, right = 4。输出: [1,4,3,2,5]
// left、right代表的是位置. 1,2,3,4,5
private static ListNode problem92_ReverseListNodeII(ListNode head, int left, int right) {
if(head == null || head.next == null || left >= right) return head;
ListNode res = new ListNode(-1), tmp = res, start, pre = null, next;
tmp.next = head;
for(int i=0; i<left && tmp != null; i++) {
pre = tmp;
tmp = tmp.next;
}
if(tmp == null) return head;
start = tmp;
for(int i=0; i<right-left && tmp != null; i++) {
tmp = tmp.next;
}
if(tmp == null) return head;
next = tmp.next;
tmp.next = null;
pre.next = reverse(start);
start.next = next;
return res.next;
}
public static ListNode reverse(ListNode head) {
if(head == null || head.next == null) return head;
ListNode node = reverse(head.next);
head.next.next = head;
head.next = null;
return node;
}
12、leetcode109: 有序链表转二叉搜索树
题目描述:给一个升序链表,转换成平衡二叉树
例如:[-10,-3,0,5,9] , 层序遍历二叉树输出:[[0],[-3,9],[-10,null,5]]
// 有序链表转二叉搜索树:通过中序遍历(左中右)实现。
private static TreeNode problem109_sortedListToBST(ListNode head) {
// 递归出口1: 空节点
if(head == null) return null;
ListNode rightMid = findMidAndSplitListNode(head);
TreeNode root = new TreeNode(rightMid.val);
// 递归出口2: 只有一个节点。说明是叶子节点
if(rightMid == head){
return root;
}
root.left = problem109_sortedListToBST(head);
root.right = problem109_sortedListToBST(rightMid.next);
return root;
}
// -10,-3,0,5,9: 返回0
public static ListNode findMidAndSplitListNode(ListNode head) {
ListNode fast = new ListNode(-1), slow = fast;
fast.next = head;
while(fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
ListNode rightMid = slow.next;
slow.next = null;
return rightMid;
}
13、leetcode141: 判断链表是否有环
// 判断是否是有环: 快慢指针。相等说明有环
private static boolean problem141_IsRecycleLinkedList(ListNode head) {
if(head == null || head.next == null) return false;
ListNode fast = head, slow = head;
while(fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) return true;
}
return false;
}
14、leetcode142: 找到环形链表的入口
private static ListNode problem142_IsRecycleLinkedListII(ListNode head) {
if(head == null) return null;
ListNode fast = head, slow = head;
boolean hasCycle = false;
while(fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) {
hasCycle = true;
break;
}
}
if(!hasCycle) return null;
slow = head;
while(slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
15、leetcode143: 链表重排序
题目描述:给定一个单链表
L
的头节点head
,单链表L
表示为:L0 → L1 → … → Ln - 1 → Ln。请将其重新排列后变为:L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …。不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。解法分析:将链表拆分成两段;第二段反转;拼接两个链表
private static void problem143_ResortListNode(ListNode head) {
if(head == null || head.next == null) return;
ListNode rightMid = findMidAndSplitListNode(head);
merge(head, reverse(rightMid));
}
public static ListNode findMidAndSplitListNode(ListNode head) {
ListNode fast = head, slow = fast;
while(fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
ListNode rightMid = slow.next;
slow.next = null;
return rightMid;
}
public static ListNode reverse(ListNode head) {
if(head == null || head.next == null) return head;
ListNode node = reverse(head.next);
head.next.next = head;
head.next = null;
return node;
}
public static void merge(ListNode l1, ListNode l2) {
ListNode l1Tmp, l2Tmp;
while(l1 != null && l2 != null) {
l1Tmp = l1.next;
l2Tmp = l2.next;
l1.next = l2;
l2.next = l1Tmp;
l1 = l1Tmp;
l2 = l2Tmp;
}
}
16、leetcode147: 链表插入排序
// 插入排序的原理: 遍历将节点插入他应该在的位置
// [4,2,1,3] -> [1,2,3,4]
private static ListNode problem147_InsertSortListNode(ListNode head) {
if(head == null || head.next == null) return head;
ListNode current = head, res = new ListNode(-1), p = res;
res.next = head;
while(current.next != null) {
if(current.val <= current.next.val) {
current = current.next;
continue;
}
ListNode node = current.next;
current.next = current.next.next;
p = res;
while(p.next.val < node.val) {
p = p.next;
}
ListNode next = p.next;
p.next = node;
node.next = next;
}
return res.next;
}
17、leetcode148: 排序链表
解题思路: 采用分治法。从排序链表中间点分割成两个独立的链表【拆分到单个节点】,然后合并两个排序链表
private static ListNode problem148_SortListNode(ListNode head) {
if(head == null || head.next == null) return head;
ListNode rightMid = findMidAndSplit(head);
ListNode l1 = problem148_SortListNode(head);
ListNode l2 = problem148_SortListNode(rightMid);
return mergeTwoSortedListNode(l1, l2);
}
public static ListNode findMidAndSplit(ListNode head) {
if(head == null || head.next == null) return head;
ListNode fast = new ListNode(-1), slow = fast;
fast.next = head;
while(fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
ListNode node = slow.next;
slow.next = null;
return node;
}
public static ListNode mergeTwoSortedListNode(ListNode l1, ListNode l2) {
if(l1 == null) return l2;
if(l2 == null) return l1;
ListNode res = new ListNode(-1), tmp = res;
while(l1 != null && l2 != null) {
if(l1.val < l2.val) {
tmp.next = l1;
l1 = l1.next;
}else {
tmp.next = l2;
l2 = l2.next;
}
tmp = tmp.next;
}
tmp.next = l1 == null ? l2 : l1;
return res.next;
}
18、leetcode160: 判断两个链表是否相交
解题思路: 两个指针p、q分别从headA和headB往后遍历,如果为null了切换到另一个头节点。p、q相等【java中 == 比较对象时比较的是内存地址】说明相交。
private static ListNode problem160_CrossListNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null) return null;
ListNode p = headA, q = headB;
// 如果headA和headB不相交,那么最后p=q=null
while(p != q) {
p = p != null ? p.next : headB;
q = q != null ? q.next : headA;
}
return p;
}
19、leetcode203: 移除链表元素
题目描述:移除链表中val 等于 给定x的所有节点。
private static ListNode problem203_RemoveListNode(ListNode head, int deleteVal) {
if (head == null) return null;
ListNode res = new ListNode(-1), tmp = res;
tmp.next = head;
while(tmp.next != null) {
if(tmp.next.val == deleteVal) {
tmp.next = tmp.next.next;
}else {
tmp = tmp.next;
}
}
return res.next;
}
20、leetcode206: 反转链表
// 递归
private static ListNode problem206_ReverseListNode(ListNode head) {
if(head == null || head.next == null) return head;
ListNode node = problem206_ReverseListNode(head.next);
head.next.next = head;
head.next = null;
return node;
}
// 迭代
private static ListNode problem206_ReverseListNode(ListNode head) {
if(head == null || head.next == null) return head;
ListNode current = head, pre, next;
while(current != null) {
next = current.next;
current.next = pre;
pre = current;
current = next;
}
return pre;
}
21、leetcode234: 判断链表是否是回文链表
解题思路:找到链表的中点,分成两个链表。后面的反转,然后对比。
private static boolean problem234_HuiWenListNode(ListNode head) {
if(head == null) return false;
if(head.next == null) return true;
ListNode rightMid = findAndSplit(head);
rightMid = reverse(rightMid);
return isHuiwen(head, rightMid);
}
public static ListNode findAndSplit(ListNode head) {
if(head == null || head.next == null) return head;
ListNode fast = head, slow = head;
while(fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
ListNode rightMid = slow.next;
slow.next = null;
return rightMid;
}
public static ListNode reverse(ListNode head) {
if(head == null || head.next == null) return head;
ListNode node = reverse(head.next);
head.next.next = head;
head.next = null;
return node;
}
public static boolean isHuiwen(ListNode p, ListNode q){
while(p != null && q != null) {
if(p.val == q.val) {
p = p.next;
q = q.next;
}else {
return false;
}
}
return lengthIsOne(p) && lengthIsOne(q);
}
public static boolean lengthIsOne(ListNode head) {
return head == null || head.next == null;
}
22、leetcode237: 删除链表中的节点【无法通过头节点删除】
问题描述:给你一个需要删除的节点
node
。你将 无法访问 第一个节点head
。链表的所有值都是 唯一的,并且保证给定的节点node
不是链表中的最后一个节点。删除给定的节点。注意,删除节点并不是指从内存中删除它。这里的意思是:
- 给定节点的值不应该存在于链表中。
- 链表中的节点数应该减少 1。
node
前面的所有值顺序相同。node
后面的所有值顺序相同。
private static void problem237_RemoveListNode(ListNode node) {
node.val = node.next.val;
ListNode next = node.next.next;
node.next.next = null;
node.next = next;
}
23、leetcode328: 奇偶链表
题目描述:给定单链表的头节点
head
,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。你必须在O(1)
的额外空间复杂度和O(n)
的时间复杂度下解决这个问题。解题思路:一次遍历,将节点拆分成奇数和偶数链表,然后拼接到一起。
// [1,2,3,4,5] -> [1,3,5,2,4]
private static ListNode problem328_OddEvenListNode(ListNode head) {
if(head == null || head.next == null) return head;
ListNode oddHead = new ListNode(-1), oddTmp = oddHead;
ListNode evenHead = new ListNode(-1), evenTmp = evenHead;
boolean odd = true;
while(head != null) {
ListNode next = head.next;
head.next = null;
if(odd) {
oddTmp.next = head;
oddTmp = oddTmp.next;
odd = false;
}else {
evenTmp.next = head;
evenTmp = evenTmp.next;
odd = true;
}
head = next;
}
oddTmp.next = evenHead.next;
return oddHead.next;
}
24、leetcode445: 两数相加II
题目描述:与leetcode2_两数相加的区别之处在于本题的数字是正序存储,leetcode2的数字是逆序存储。如[1,2,3] 在leetcode2中表示321,在本体表示123.
解题思路:能想到最简单的方式是把链表反转,然后累加在反转链表。如果加限制条件,不能反转链表,官方题解是使用栈存储节点,然后通过头插法得到结果。
private static ListNode problem445_TwoNumSumV2(ListNode p1, ListNode p2) {
if(p1 == null) return p2;
if(p2 == null) return p1;
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
while(p1 != null) {
stack1.push(p1.val);
p1 = p1.next;
}
while(p2 != null) {
stack2.push(p2.val);
p2 = p2.next;
}
ListNode res = new ListNode(-1);
int carry = 0;
while(!stack1.isEmpty() || !stack2.isEmpty() || carry > 0) {
int num1 = stack1.isEmpty() ? 0 : stack1.pop();
int num2 = stack2.isEmpty() ? 0 : stack2.pop();
int sum = num1 + num2 + carry;
carry = sum/10;
// 头插法插入节点
ListNode node = new ListNode(sum % 10);
ListNode next = res.next;
res.next = node;
node.next = next;
}
return res.next;
}
25、leetcode725: 分隔链表
题目描述:给你一个头结点为
head
的单链表和一个整数k
,请你设计一个算法将链表分隔为k
个连续的部分。每部分的长度应该尽可能的相等:任意两部分的长度差距不能超过 1 。这可能会导致有些部分为 null 。这k
个部分应该按照在链表中出现的顺序排列,并且排在前面的部分的长度应该大于或等于排在后面的长度。例1: head = [1,2,3], k = 5, 输出: [[1],[2],[3],[],[]]
例2: head = [1,2,3,4,5,6,7,8,9,10], k = 3, 输出: [[1,2,3,4],[5,6,7],[8,9,10]]
解题思路:
- 计算链表长度。
- 获取每一个下标最少要的节点数: perNum=len/k。
- 获取最后多的节点数: left=len%k;
- 遍历链表拆分,每个节点的节点数= perNum + (left > 0 ? 1 : 0);
- left--;循环第4步。
public static ListNode[] splitListToParts(ListNode head, int k) {
ListNode[] res = new ListNode[k];
if(head == null) return res;
int len = getListNodeLength(head);
int perNum = len / k;
int left = len % k;
for(int i = 0; i<k; i++) {
int nodeNum = perNum + (left > 0 ? 1: 0);
ListNode node = new ListNode(-1), tmp = node;
for(int j = 0; j<nodeNum && head != null; j++) {
ListNode next = head.next;
head.next = null;
tmp.next = head;
head = next;
tmp = tmp.next;
}
left--;
res[i] = node.next;
}
return res;
}
public static int getListNodeLength(ListNode head) {
ListNode tmp = head;
int len = 0;
while(tmp != null) {
len++;
tmp = tmp.next;
}
return len;
}