反转链表
反转链表需要会两种,一种是迭代,一种是递归。这里来看一下这两种的解法:
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:
迭代做法:我们考虑一下如何反转呢,反转即把原来的next指向反向,即原来是 1.next = 2变成2.next = 1,但next指针只要改变我们就无法找到下一节点,即 1 -> 2 -> 3 1.next =2 改为 2.next = 1 就变成 1<-2 3 3就变成孤魂,直接被垃圾回收掉,所以我们需要记录下一个节点是什么,然后不断移动两个节点的指向即可,移到什么时候结束呢,移动到最后的节点为null,可以简单已 1->2->null 举例 pre =null 1 = cur tmp = 2 1.next = null ; pre = 1 cur = 2 tmp = null 2.next = 1,此时全部移动结束,然后就可以跳出了,此时 cur再往前为null,即 pre = 2 cur = null tmp = null ,此时跳出,返回pre指针。
// 迭代需要谨记一点: 反转链表结束后,pre 指向末尾, cur 指向下一段链表的开头或者null
class Solution {
public ListNode reverseList(ListNode head) {
ListNode cur = head, pre = null;
while (cur != null) {
ListNode nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
}
递归做法:
对于递归算法,最重要的就是明确递归函数的定义。具体来说,我们的 reverse
函数定义是这样的:
输入一个节点 head
,将「以 head
为起点」的链表反转,并返回反转之后的头结点。
明白了函数的定义,再来看这个问题。比如说我们想反转这个链表:
那么输入 reverse(head)
后,会在这里进行递归:
ListNode last = reverse(head.next);
不要跳进递归(你的脑袋能压几个栈呀?),而是要根据刚才的函数定义,来弄清楚这段代码会产生什么结果:
这个 reverse(head.next)
执行完成后,整个链表就成了这样:
并且根据函数定义,reverse
函数会返回反转之后的头结点,我们用变量 last
接收了。
现在再来看下面的代码:
Copy
head.next.next = head;
接下来:
Copy
head.next = null;
return last;
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
// 递归解法: 是递归问题吗?可以看到反转一个元素是里面一个子问题,一个总问题可以拆解成反转子问题,且相互独立,所以可以使用递归去解
class Solution {
// 明确定义: 给一个头节点,反转以这个头结点为开始的所有元素,并且返回反转之后的头结点
public ListNode reverseList(ListNode head) {
// base case
if (head == null) {
return head;
}
// 要返回的末尾元素,即头结点,只有找到这个头结点,才不会被JVM回收
if (head.next == null) {
return head;
}
// 递归 反转下一个元素是什么? reverseList(head.next)
// 假设以1为头, head.next = 2 , 后面的链表都反转结束后,此节点需要做什么操作呢? 1 -> 2 <-3 <-4 <-5 2 -> null 1为head last为反转后的头结点
ListNode last = reverseList(head.next);
// 递归之后
head.next.next = head;
head.next = null;
return last;
}
}
反转双向链表
class Solution {
public ListNode reverseList(ListNode head) {
ListNode cur = head, pre = null;
while (cur != null) {
ListNode nxt = cur.next;
cur.next = pre;
cur.last = nxt;
pre = cur;
cur = nxt;
}
return pre;
}
}
//双向链表反转,递归反转
public DoubleListNode reverse2(DoubleListNode head) { //参数传入链表的头指针(不带头结点)
if(head==null || head.next==null)
return head;
DoubleListNode temp = reverse2(head.next);
head.next.next = head;
head.last = head.next;
return temp;
}
反转链表II
给你单链表的头指针 head
和两个整数 left
和 right
,其中 left <= right
。请你反转从位置 left
到位置 right
的链表节点,返回 反转后的链表 。
示例 1:
输入:head = [1,2,3,4,5], left = 2, right = 4 输出:[1,4,3,2,5]
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummy = new ListNode(0, head);
ListNode p0 = dummy;
// 先找到前一个节点,反转之后需要续接
for (int i = 0; i < left - 1; i++) {
p0 = p0.next;
}
// 需要反转的元素是 right - left + 1个元素,需要循环 right - left + 1 次,可以举例
ListNode pre = null, cur = p0.next;
for (int i = 0; i < right - left + 1; i++) {
ListNode nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
// 1 -> 2 <-3 <-4 5 cur = 5 pre = 4
p0.next.next = cur;
p0.next = pre;
return dummy.next;
}
}
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
// 定义: 给一个头节点,整数left,整数right,反转left到right之间内的元素,并返回头节点
public ListNode reverseBetween(ListNode head, int left, int right) {
// left = 1,反转前right个元素
if (left == 1) {
return reverseN(head, right);
}
// 递归
head.next = reverseBetween(head.next, left -1, right - 1);
// 递归后
return head;
}
ListNode succssor = null;
//定义:给一个头结点head,反转前n个元素,并且返回反转后的头节点
private ListNode reverseN(ListNode head, int n) {
if (n == 1) {
succssor = head.next;
return head;
}
// 递归
ListNode last = reverseN(head.next, n - 1);
// 递归后
head.next.next = head;
head.next = succssor;
return last;
}
}
K个一组反转链表
给你链表的头节点 head
,每 k
个节点一组进行翻转,请你返回修改后的链表。
k
是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k
的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例 1:
输入:head = [1,2,3,4,5], k = 2 输出:[2,1,4,3,5]
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
// 先获取有多少个元素,小于K个的时候不需要反转
int n = 0;
ListNode p = head;
while (p != null) {
p = p.next;
n++;
}
ListNode dummy = new ListNode(0, head);
ListNode p0 = dummy;
ListNode pre = null, cur = head;
for (;n>=k; n-=k) {
for (int i = 0; i < k; i++) {
ListNode nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
ListNode next = p0.next;
p0.next.next = cur;
p0.next = pre;
p0 = next;
}
return dummy.next;
}
}
class Solution {
// 定义: 以head节点为头,并且k个元素反转链表,且返回反转后的头部
public ListNode reverseKGroup(ListNode head, int k) {
if (head == null || head.next == null) {
return head;
}
ListNode tail = head;
for (int i = 0;i <k; i++) {
if (tail == null) {
return head;
}
tail = tail.next;
}
// 左闭右开
ListNode newHead = reverse(head, tail);
head.next = reverseKGroup(tail, k);
return newHead;
}
private ListNode reverse(ListNode head, ListNode tail) {
ListNode pre = null;
ListNode next = null;
while (head != tail) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
}
合并两个链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummy = new ListNode(-1);
ListNode p = dummy, p1 = list1, p2 = list2;
while (p1 != null && p2 != null) {
if (p1.val > p2.val) {
p.next = p2;
p2 = p2.next;
}else {
p.next = p1;
p1 = p1.next;
}
p = p.next;
}
if (p1 != null) {
p.next = p1;
}
if (p2 != null) {
p.next = p2;
}
return dummy.next;
}
}
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
// 定义:给定两个链表,按照元素值大小从头到尾拼接到一起,并且返回头结点
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
// base case
if (list1 == null) {
return list2;
}
if (list2 == null) {
return list1;
}
// 递归 当list1元素值小的时候,应该使用list1的值,合并list1.next和list2
if (list1.val < list2.val) {
list1.next = mergeTwoLists(list1.next, list2);
return list1;
}else {
list2.next = mergeTwoLists(list1, list2.next);
return list2;
}
// 递归后
}
}
合并k个升序链表
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]] 输出:[1,1,2,3,4,4,5,6] 解释:链表数组如下: [ 1->4->5, 1->3->4, 2->6 ] 将它们合并到一个有序链表中得到。 1->1->2->3->4->4->5->6
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0) {
return null;
}
ListNode dummy = new ListNode(-1);
ListNode p = dummy;
PriorityQueue<ListNode> pq = new PriorityQueue<>(
lists.length, (a, b) -> (a.val - b.val)
);
for (ListNode head: lists) {
if (head != null) {
pq.add(head);
}
}
while (!pq.isEmpty()) {
ListNode node = pq.poll();
p.next = node;
if (node.next != null) {
pq.add(node.next);
}
p = p.next;
}
return dummy.next;
}
}
class Solution {
// 最朴素的方法,就是两个链表,两个链表合并
public ListNode mergeKLists(ListNode[] lists) {
ListNode ans = null;
for (int i = 0; i < lists.length; i++) {
ans = mergeTwoLists(ans, lists[i]);
}
return ans;
}
private ListNode mergeTwoLists(ListNode a, ListNode b) {
if (a == null) {
return b;
}
if (b == null) {
return a;
}
ListNode dummy = new ListNode(0);
ListNode p = dummy, p1 = a, p2 = b;
while (p1 != null && p2 != null) {
if (p1.val < p2.val) {
p.next = p1;
p1 = p1.next;
}else {
p.next = p2;
p2 = p2.next;
}
p = p.next;
}
if (p1 != null) {
p.next = p1;
}
if (p2 != null) {
p.next = p2;
}
return dummy.next;
}
}
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0) {
return null;
}
return mergeKLists(lists, 0, lists.length);
}
// 定义:给定一个链表数组,两两合并链表
private ListNode mergeKLists(ListNode[] lists, int i, int j) {
// base case 什么时候递归结束?
int m = j - i;
if (m == 1) {
return lists[i];
}
// 递归
ListNode left = mergeKLists(lists, i, i + m / 2);
ListNode right = mergeKLists(lists, i + m / 2, j);
// 递归后
return mergeTwoLists(left, right);
}
private ListNode mergeTwoLists(ListNode left, ListNode right) {
ListNode dummy = new ListNode(0);
ListNode p = dummy, p1 = left, p2 = right;
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 dummy.next;
}
}
给你两个链表 list1
和 list2
,它们包含的元素分别为 n
个和 m
个。
请你将 list1
中下标从 a
到 b
的全部节点都删除,并将list2
接在被删除节点的位置。
下图中蓝色边和节点展示了操作后的结果:
请你返回结果链表的头指针。
示例 1:
输入:list1 = [0,1,2,3,4,5], a = 3, b = 4, list2 = [1000000,1000001,1000002] 输出:[0,1,2,1000000,1000001,1000002,5] 解释:我们删除 list1 中下标为 3 和 4 的两个节点,并将 list2 接在该位置。上图中蓝色的边和节点为答案链表。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
ListNode preA = list1;
for (int i = 0; i < a -1; i++) {
preA = preA.next;
}
ListNode preB = preA;
for (int i = 0; i < b -a + 2; i++) {
preB = preB.next;
}
preA.next = list2;
ListNode p = list2;
while (p.next != null) {
p = p.next;
}
p.next = preB;
return list1;
}
}
环形链表
给你一个链表的头节点 head
,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true
。 否则,返回 false
。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:true 解释:链表中有一个环,其尾部连接到第二个节点
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
return true;
}
}
return false;
}
}
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (slow == fast) {
break;
}
}
if (fast == null || fast.next == null) {
return null;
}
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
给定一个头节点为 head
的链表用于记录一系列核心肌群训练项目编号,请查找并返回倒数第 cnt
个训练项目编号。
示例 1:
输入:head = [2,4,7,8], cnt = 1 输出:8
从前往后寻找单链表的第 k
个节点很简单,一个 for 循环遍历过去就找到了,但是如何寻找从后往前数的第 k
个节点呢?
那你可能说,假设链表有 n
个节点,倒数第 k
个节点就是正数第 n - k + 1
个节点,不也是一个 for 循环的事儿吗?
是的,但是算法题一般只给你一个 ListNode
头结点代表一条单链表,你不能直接得出这条链表的长度 n
,而需要先遍历一遍链表算出 n
的值,然后再遍历链表计算第 n - k + 1
个节点。
也就是说,这个解法需要遍历两次链表才能得到出倒数第 k
个节点。
那么,我们能不能只遍历一次链表,就算出倒数第 k
个节点?可以做到的,如果是面试问到这道题,面试官肯定也是希望你给出只需遍历一次链表的解法。
这个解法就比较巧妙了,假设 k = 2
,思路如下:
首先,我们先让一个指针 p1
指向链表的头节点 head
,然后走 k
步:
现在的 p1
,只要再走 n - k
步,就能走到链表末尾的空指针了对吧?
趁这个时候,再用一个指针 p2
指向链表头节点 head
:
接下来就很显然了,让 p1
和 p2
同时向前走,p1
走到链表末尾的空指针时前进了 n - k
步,p2
也从 head
开始前进了 n - k
步,停留在第 n - k + 1
个节点上,即恰好停链表的倒数第 k
个节点上:
这样,只遍历了一次链表,就获得了倒数第 k
个节点 p2
。
class Solution {
public ListNode trainingPlan(ListNode head, int k) {
ListNode p1 = head;
for (int i = 0; i < k; i++) {
p1 = p1.next;
}
ListNode p2 = head;
while (p1 != null) {
p2 = p2.next;
p1 = p1.next;
}
return p2;
}
}
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(-1, head);
// 为什么传入dummy节点呢? 前面加几个元素,倒数第三个还是第三个,倒数是不会变的
ListNode x = findNEnd(dummy, n + 1);
x.next = x.next.next;
return dummy.next;
}
private ListNode findNEnd(ListNode head, int k) {
ListNode p = head;
for (int i = 0; i < k; i++) {
p = p.next;
}
ListNode p1 = head;
while (p != null) {
p1 = p1.next;
p = p.next;
}
return p1;
}
}
给定一个已排序的链表的头 head
, 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
示例 1:
输入:head = [1,1,2] 输出:[1,2]
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return null;
}
ListNode slow = head, fast = head;
while (fast != null) {
if (slow.val != fast.val) {
slow.next = fast;
slow = slow.next;
}
fast = fast.next;
}
slow.next = null;
return head;
}
}
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
// 定义:给定head节点,删除重复元素,并返回删除之后的头结点
public ListNode deleteDuplicates(ListNode head) {
//base case
if (head == null || head.next == null) {
return head;
}
// 递归
head.next = deleteDuplicates(head.next);
// 递归后 后面已经没重复元素,看第一个元素,与后面的元素是否一致
return head.val == head.next.val ? head.next : head;
}
}
给定一个已排序的链表的头 head
, 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
示例 1:
输入:head = [1,2,3,3,4,4,5] 输出:[1,2,5]
示例 2:
输入:head = [1,1,1,2,3] 输出:[2,3]
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return head;
}
ListNode dummy = new ListNode(0, head);
ListNode p = dummy;
while (p.next != null && p.next.next != null) {
if (p.next.val == p.next.next.val) {
int x = p.next.val;
while (p.next != null && p.next.val == x) {
p.next = p.next.next;
}
}else {
p = p.next;
}
}
return dummy.next;
}
}
class Solution {
// 定义:去除以head为头的重复元素,并且返回去重元素后的头结点
public ListNode deleteDuplicates(ListNode head) {
// base case
if (head == null || head.next == null) {
return head;
}
// 如何递归,从哪递归呢?需要判断第一个元素和第二个元素相不相等
if (head.val != head.next.val) {
head.next = deleteDuplicates(head.next);
return head;
}
// 如果如果头结点和身后节点的值相同,则说明从 head 开始存在若干重复节点
// 越过重复节点,找到 head 之后那个不重复的节点
while (head.next != null && head.next.val == head.val) {
head =head.next;
}
// 递归
ListNode last = deleteDuplicates(head.next);
// 递归后
return last;
}
}
class Solution {
public ListNode deleteDuplicates(ListNode head) {
Map<Integer, Integer> map = new HashMap<>();
ListNode p = head;
while (p != null) {
map.put(p.val, map.getOrDefault(p.val, 0) + 1);
p = p.next;
}
ListNode dummy = new ListNode(-1, head);
p = dummy;
while (p != null) {
ListNode unqiue = p.next;
while (unqiue != null && map.get(unqiue.val) > 1) {
unqiue = unqiue.next;
}
p.next = unqiue;
p = p.next;
}
return dummy.next;
}
}
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return head;
}
ListNode dummy = new ListNode(-1, head);
ListNode p = dummy;
while (p.next != null) {
if (p.next.val == val) {
p.next = p.next.next;
}else {
p = p.next;
}
}
return dummy.next;
}
}
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return head;
}
head.next = removeElements(head.next, val);
return head.val == val ? head.next : head;
}
}
有一个单链表的 head
,我们想删除它其中的一个节点 node
。
给你一个需要删除的节点 node
。你将 无法访问 第一个节点 head
。
链表的所有值都是 唯一的,并且保证给定的节点 node
不是链表中的最后一个节点。
删除给定的节点。注意,删除节点并不是指从内存中删除它。这里的意思是:
- 给定节点的值不应该存在于链表中。
- 链表中的节点数应该减少 1。
node
前面的所有值顺序相同。node
后面的所有值顺序相同。
自定义测试:
- 对于输入,你应该提供整个链表
head
和要给出的节点node
。node
不应该是链表的最后一个节点,而应该是链表中的一个实际节点。 - 我们将构建链表,并将节点传递给你的函数。
- 输出将是调用你函数后的整个链表。
示例 1:
输入:head = [4,5,1,9], node = 5 输出:[4,1,9] 解释:指定链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9
class Solution {
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
}
给定一个单链表 L
的头节点 head
,单链表 L
表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例 1:
输入:head = [1,2,3,4] 输出:[1,4,2,3]
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
// 1 -> 2-> 3 <-4
// head1 = 1 head2 = 4
// head1.next = nxt;
// head.next = head2;
// head2.next = nxt;
//
*/
class Solution {
public void reorderList(ListNode head) {
ListNode mid = middle(head);
ListNode head2 = reverse(mid);
while (head2.next != null) {
ListNode nxt = head.next;
ListNode nxt2 = head2.next;
head.next = head2;
head2.next = nxt;
head = nxt;
head2 = nxt2;
}
}
private ListNode middle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
private ListNode reverse(ListNode head) {
ListNode pre = null, cur = head;
while (cur != null) {
ListNode nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
}
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
// cur = 1 next = 2 last = 5 1 -> 5 -> 2
// cur = 2 next = 3 last = 4 1 -> 5 -> 2 -> 4 -> 3
// cur = 3 next = 4 last = 3 3.next = 4
// cur = 1 next = 2 last = 4 1 -> 4 -> 2
// cur = 2 next = 3 last = 3 1 -> 4 -> 2 -> 3
*/
class Solution {
public void reorderList(ListNode head) {
Stack<ListNode> stk = new Stack<>();
ListNode p = head;
while (p != null) {
stk.push(p);
p = p.next;
}
p = head;
while (p != null) {
ListNode last = stk.pop();
ListNode next = p.next;
if (last == next || last.next == next) {
last.next = null;
break;
}
p.next = last;
last.next = next;
p = next;
}
}
}
给定单个链表的头 head
,使用 插入排序 对链表进行排序,并返回 排序后链表的头 。
插入排序 算法的步骤:
- 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
- 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
- 重复直到所有输入数据插入完为止。
下面是插入排序算法的一个图形示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。
对链表进行插入排序。
示例 1:
输入: head = [4,2,1,3] 输出: [1,2,3,4]
class Solution {
public ListNode insertionSortList(ListNode head) {
if (head == null) {
return head;
}
ListNode dummy = new ListNode(-1, head);
ListNode lastNode = head, curr = head.next;
while (curr != null) {
if (lastNode.val <= curr.val) {
lastNode = lastNode.next;
curr = lastNode.next;
}else {
// 找到要插入位置的前一个位置
ListNode prev = dummy;
while (prev.next != null && prev.next.val <= curr.val) {
prev = prev.next;
}
// -1 4 5 3 2 curr = 3 nxt = 2 prev = -1
lastNode.next = curr.next;
curr.next = prev.next;
prev.next = curr;
curr = lastNode.next;
}
}
return dummy.next;
}
}
给你链表的头结点 head
,请将其按 升序 排列并返回 排序后的链表 。
示例 1:
输入:head = [4,2,1,3] 输出:[1,2,3,4]
class Solution {
public ListNode sortList(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 tmp = slow.next;
// 链表要切开,先切开再合并
slow.next = null;
// [left, slow] [tmp, right]
// 左边递归拆分,并且返回左头节点
ListNode left = sortList(head);
// 右边递归拆分,并切返回右头节点
ListNode right = sortList(tmp);
// 合并最后的链表
ListNode dummy = new ListNode(0);
ListNode p1 = left, p2 = right, p = dummy;
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 dummy.next;
}
}
给你一个链表的头节点 head
和一个特定值 x
,请你对链表进行分隔,使得所有 小于 x
的节点都出现在 大于或等于 x
的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。
示例 1:
输入:head = [1,4,3,2,5,2], x = 3 输出:[1,2,2,4,3,5]
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode dummy1 = new ListNode(-1);
ListNode dummy2 = new ListNode(-1);
ListNode p1 = dummy1, p2 =dummy2, p = head;
while (p != null) {
if (p.val < x) {
p1.next = p;
p1 = p1.next;
}else {
p2.next = p;
p2 = p2.next;
}
ListNode tmp = p.next;
p.next = null;
p = tmp;
}
p1.next = dummy2.next;
return dummy1.next;
}
}
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4] 输出:[2,1,4,3]
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode pre = new ListNode(0, head);
ListNode tmp = pre;
while (tmp.next != null && tmp.next.next != null) {
ListNode start = tmp.next;
ListNode end = tmp.next.next;
tmp.next = end;
start.next = end.next;
end.next = start;
tmp = start;
}
return pre.next;
}
}
class Solution {
// 定义:输入以head为头的链表,将这个单链表中的每两个元素翻转,并且返回反转后的头结点.
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode next = head.next;
head.next = swapPairs(next.next);
next.next = head;
return next;
}
}
给你链表的头节点 head
和一个整数 k
。
交换 链表正数第 k
个节点和倒数第 k
个节点的值后,返回链表的头节点(链表 从 1 开始索引)。
示例 1:
输入:head = [1,2,3,4,5], k = 2 输出:[1,4,3,2,5]
示例 2:
输入:head = [7,9,6,6,7,8,3,0,9,5], k = 5 输出:[7,9,6,6,8,7,3,0,9,5]
示例 3:
输入:head = [1], k = 1 输出:[1]
示例 4:
输入:head = [1,2], k = 1 输出:[2,1]
示例 5:
输入:head = [1,2,3], k = 2 输出:[1,2,3]
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode swapNodes(ListNode head, int k) {
ListNode dummy = new ListNode(0, head);
ListNode pre1 = dummy, left = head, pre2 = dummy, right = head;
// 寻找左边第k个节点和前一个节点
// 从1开始,第k个,即偏移 k - 1
for (int i = 0; i < k - 1; i++) {
left = left.next;
pre1 = pre1.next;
}
// 寻找右边第k个节点和前一个节点
ListNode cur = left;
while (cur.next != null) {
cur = cur.next;
right = right.next;
pre2 = pre2.next;
}
// 分为两种方式开始,相邻还是不相邻
// 1 2 3 4 pre1 = 2 left = 3 right = 2 pre2 = 1
// 1 3 2 4
ListNode temp = left.next;
if (pre1 == right) {
right.next = temp;
left.next = right;
pre2.next = left;
}else {
// 1 2 3 4 pre1 = 1 left = 2 pre2 = 2 right = 3
// 1 3 2 4
if (left == pre2) {
left.next = right.next;
right.next = left;
pre1.next = right;
}else {
// 不相邻 1 2 3 4 5 pre1 = 1 pre2 = 3 left = 2 right = 4 变成 1 4 3 2 5
ListNode tmp = left.next;
left.next = right.next;
pre2.next = left;
right.next = tmp;
pre1.next = right;
}
}
return dummy.next;
}
}
给你一个单链表的引用结点 head
。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。
请你返回该链表所表示数字的 十进制值 。
示例 1:
输入:head = [1,0,1] 输出:5 解释:二进制数 (101) 转化为十进制数 (5)
示例 2:
输入:head = [0] 输出:0
示例 3:
输入:head = [1] 输出:1
示例 4:
输入:head = [1,0,0,1,0,0,1,1,1,0,0,0,0,0,0] 输出:18880
示例 5:
输入:head = [0,0] 输出:0
class Solution {
public int getDecimalValue(ListNode head) {
ListNode cur = head;
int ans = 0;
while (cur != null) {
ans = ans * 2 + cur.val;
cur = cur.next;
}
return ans;
}
}
这就相当于,比如说计算十进制的数:526,你先读取到52,如果发现后面还有一个6,那直接把52乘10加6就可以了.
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4] 输出:[7,0,8] 解释:342 + 465 = 807.
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
int carry = 0;
// 两条链表都遍历结束后
while (l1 != null || l2 != null) {
int x = l1 == null ? 0 : l1.val;
int y = l2 == null ? 0 : l2.val;
int sum = x + y + carry;
carry = sum / 10;
cur.next = new ListNode(sum % 10);
cur = cur.next;
if (l1 != null) {
l1 = l1.next;
}
if (l2 != null) {
l2 = l2.next;
}
}
if (carry == 1) {
cur.next = new ListNode(carry);
}
return dummy.next;
}
}
class Solution {
// 定义: 两个链表逐位相加,并返回相加后链表的头结点.
// 需要考虑的是:元素相加 + carry 当sum > 9的时候
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
// base case
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
int sum = l1.val + l2.val;
ListNode head = new ListNode(sum % 10);
// 递归
head.next = addTwoNumbers(l1.next, l2.next);
// 递归后
if (sum > 9) {
head.next = addTwoNumbers(head.next, new ListNode(1));
}
return head;
}
}
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
示例1:
输入:l1 = [7,2,4,3], l2 = [5,6,4] 输出:[7,8,0,7]
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
l1 = reverseList(l1);
l2 = reverseList(l2);
ListNode l3 = addTwo(l1, l2);
return reverseList(l3);
}
private ListNode reverseList(ListNode head) {
ListNode pre = null, cur = head;
while (cur != null) {
ListNode nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
private ListNode addTwo(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
int carry = 0;
while (l1 != null || l2 != null) {
int x = l1 == null ? 0 : l1.val;
int y = l2 == null ? 0 : l2.val;
int sum = x + y + carry;
carry = sum / 10;
cur.next = new ListNode(sum % 10);
cur = cur.next;
if (l1 != null) {
l1 = l1.next;
}
if (l2 != null) {
l2 = l2.next;
}
}
if (carry == 1) {
cur.next = new ListNode(carry);
}
return dummy.next;
}
}
// 栈 + 相加的方式
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
Stack<Integer> stk1 = new Stack<>();
while (l1 != null) {
stk1.push(l1.val);
l1 = l1.next;
}
Stack<Integer> stk2 = new Stack<>();
while (l2 != null) {
stk2.push(l2.val);
l2 = l2.next;
}
ListNode dummy = new ListNode(-1);
int carry = 0;
while (!stk1.isEmpty() || !stk2.isEmpty() || carry > 0) {
int sum = carry;
if (!stk1.isEmpty()) {
sum += stk1.pop();
}
if (!stk2.isEmpty()) {
sum += stk2.pop();
}
carry = sum / 10;
sum = sum % 10;
ListNode newNode = new ListNode(sum);
newNode.next = dummy.next;
dummy.next = newNode;
}
return dummy.next;
}
}
用一个 非空 单链表来表示一个非负整数,然后将这个整数加一。
你可以假设这个整数除了 0 本身,没有任何前导的 0。
这个整数的各个数位按照 高位在链表头部、低位在链表尾部 的顺序排列。
示例:
输入: [1,2,3]
输出: [1,2,4]
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode plusOne(ListNode head) {
Stack<Integer> st = new Stack();
while (head != null) {
st.push(head.val);
head = head.next;
}
int carry = 0;
ListNode dummy = new ListNode(0);
int adder = 1;
while (!st.empty() || adder != 0 || carry > 0) {
int digit = st.empty() ? 0 : st.pop();
int sum = digit + adder + carry;
carry = sum >= 10 ? 1 : 0;
sum = sum >= 10 ? sum - 10 : sum;
ListNode cur = new ListNode(sum);
cur.next = dummy.next;
dummy.next = cur;
adder = 0;
}
return dummy.next;
}
}
给定单链表的头节点 head
,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。
第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。
请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。
你必须在 O(1)
的额外空间复杂度和 O(n)
的时间复杂度下解决这个问题。
示例 1:
输入: head = [1,2,3,4,5] 输出: [1,3,5,2,4]
// 造两条链表,找出所有的奇偶元素,然后奇链表 续接偶链表 链表默认为null,返回head
class Solution {
public ListNode oddEvenList(ListNode head) {
ListNode oddHead = new ListNode();
ListNode oddTail = oddHead;
ListNode evenHead = new ListNode();
ListNode evenTail = evenHead;
boolean isOdd = true;
while (head != null) {
if (isOdd) {
oddTail.next = head;
oddTail = oddTail.next;
}else {
evenTail.next = head;
evenTail = evenTail.next;
}
ListNode tmp = head.next;
head.next = null;
head = tmp;
isOdd = !isOdd;
}
oddTail.next = evenHead.next;
return oddHead.next;
}
}
对着86题分割链表看
排序奇升偶降链表
题目描述 : 给定一个奇数位升序,偶数位降序的链表,将其重新排序。
输入: 1->8->3->6->5->4->7->2->NULL 输出: 1->2->3->4->5->6->7->8->NULL
要求: 时间复杂度为O(N),空间复杂度O(1)。
思路:
1. 按奇偶位置拆分链表,得1->3->5->7->NULL和8->6->4->2->NULL
2. 反转偶链表,得1->3->5->7->NULL和2->4->6->8->NULL
3. 合并两个有序链表,得1->2->3->4->5->6->7->8->NULL
public class SortList {
@Data
static class ListNode {
int val;
ListNode next;
ListNode() {
}
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
ListNode(int val) {
this.val = val;
next = null;
}
}
// 1.拆分成两个链表得1->3->5->7->NULL和8->6->4->2->NULL
private ListNode[] partition(ListNode head) {
ListNode oddHead = new ListNode();
ListNode oddTail = oddHead;
ListNode evenHead = new ListNode();
ListNode evenTail = evenHead;
boolean isOdd = true;
while (head != null) {
if (isOdd) {
oddTail.next = head;
oddTail = oddTail.next;
}else {
evenTail.next = head;
evenTail = evenTail.next;
}
ListNode tmp = head.next;
head.next = null;
head = tmp;
isOdd = !isOdd;
}
return new ListNode[]{oddHead.next, evenHead.next};
}
// 2.反转偶链表得1->3->5->7->NULL和2->4->6->8->NULL
private static ListNode reverse(ListNode head) {
ListNode pre = null, cur = head;
while (cur != null) {
ListNode nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
// 3.合并两个链表
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummy = new ListNode(-1);
ListNode p = dummy, p1 = list1, p2 = list2;
while (p1 != null && p2 != null) {
if (p1.val > p2.val) {
p.next = p2;
p2 = p2.next;
}else {
p.next = p1;
p1 = p1.next;
}
p = p.next;
}
if (p1 != null) {
p.next = p1;
}
if (p2 != null) {
p.next = p2;
}
return dummy.next;
}
public ListNode sortList(ListNode list) {
// 1. 拆分奇偶链表
ListNode[] arr = partition(list);
// 2. 翻转偶数链表
ListNode oddList = arr[0];
ListNode evenList = reverse(arr[1]);
// 3. 合并有序链表
return mergeTwoLists(oddList, evenList);
}
public static void main(String[] args) {
ListNode listNode = new SortList().sortList(new ListNode(1, new ListNode(8, new ListNode(3, new ListNode(6, new ListNode(5,
new ListNode(4, new ListNode(7, new ListNode(2)))))))));
System.out.println(listNode);
}
}
给你一个单链表的头节点 head
,请你判断该链表是否为回文链表。如果是,返回 true
;否则,返回 false
。
示例 1:
输入:head = [1,2,2,1] 输出:true
class Solution {
public boolean isPalindrome(ListNode head) {
if (head == null) {
return true;
}
ListNode first = endOfFirstHalf(head);
ListNode second = reverse(first);
ListNode p1 = head;
ListNode p2 = second;
while (p2 != null) {
if (p1.val != p2.val) {
return false;
}
p1 = p1.next;
p2 = p2.next;
}
return true;
}
private ListNode endOfFirstHalf(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
private ListNode reverse(ListNode head) {
ListNode pre = null, cur = head;
while (cur != null) {
ListNode nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
}
class Solution {
// 定义: 给定一个头结点,如果此链表是回文链表则返回true,如果不是回文链表则返回false;
// 需要一个节点从前往后遍历,另一个节点从后往前遍历,然后不断比较两个节点的值是否相同;
// 一个节点指向头节点
ListNode temp;
boolean ans = true;
public boolean isPalindrome(ListNode head) {
temp = head;
check(head);
return ans;
}
private void check(ListNode head) {
if (head == null) {
return;
}
check(head.next);
if (ans) {
if (temp.val != head.val) {
ans = false;
}else {
temp = temp.next;
}
}
}
}
给你一个长度为 n
的链表,每个节点包含一个额外增加的随机指针 random
,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n
个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next
指针和 random
指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X
和 Y
两个节点,其中 X.random --> Y
。那么在复制链表中对应的两个节点 x
和 y
,同样有 x.random --> y
。
返回复制链表的头节点。
用一个由 n
个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index]
表示:
val
:一个表示Node.val
的整数。random_index
:随机指针指向的节点索引(范围从0
到n-1
);如果不指向任何节点,则为null
。
你的代码 只 接受原链表的头节点 head
作为传入参数。
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]] 输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
class Solution {
public Node copyRandomList(Node head) {
// 创建一个map,存之前的节点和复制后节点,这个问题在于,在创建后节点后还需要将对应的 next节点和random节点连接起来.
Map<Node, Node> cache = new HashMap<>();
Node p = head;
while (p != null) {
if (!cache.containsKey(p)) {
cache.put(p, new Node(p.val));
}
p = p.next;
}
p = head;
while (p != null) {
if (p.next != null) {
cache.get(p).next = cache.get(p.next);
}
if (p.random != null) {
cache.get(p).random = cache.get(p.random);
}
p = p.next;
}
return cache.get(head);
}
}
你会得到一个双链表,其中包含的节点有一个下一个指针、一个前一个指针和一个额外的 子指针 。这个子指针可能指向一个单独的双向链表,也包含这些特殊的节点。这些子列表可以有一个或多个自己的子列表,以此类推,以生成如下面的示例所示的 多层数据结构 。
给定链表的头节点 head ,将链表 扁平化 ,以便所有节点都出现在单层双链表中。让 curr
是一个带有子列表的节点。子列表中的节点应该出现在扁平化列表中的 curr
之后 和 curr.next
之前 。
返回 扁平列表的 head
。列表中的节点必须将其 所有 子指针设置为 null
。
示例 1:
输入:head = [1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]
输出:[1,2,3,7,8,11,12,9,10,4,5,6]
解释:输入的多级列表如上图所示。
扁平化后的链表如下图:
// 定义将连边头位head的链表进行扁平话,并将扁平化的头节点返回;
class Solution {
public Node flatten(Node head) {
Node dummy = new Node(0);
dummy.next = head;
while (head != null) {
if (head.child == null) {
head = head.next;
}else {
// 递归扁平
Node next = head.next;
Node lastNode = flatten(head.child);
head.next = lastNode;
lastNode.prev = head;
head.child = null;
while (head.next != null) {
head = head.next;
}
head.next = next;
if (next != null) {
next.prev = head;
}
head = next;
}
}
return dummy.next;
}
}
给你一个链表的头节点 head
,旋转链表,将链表每个节点向右移动 k
个位置。
示例 1:
输入:head = [1,2,3,4,5], k = 2 输出:[4,5,1,2,3]
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if (head == null || k == 0) {
return head;
}
ListNode p = head;
int n = 0;
// 注意: 要获得最后一个节点
while (p.next != null) {
p = p.next;
n++;
}
n++; // 加上最后一个节点 长度或者从1开始计数
k = k % n;
ListNode tail = p;
p = head;
for (int i = 0; i < n -k -1; i++) {
p = p.next;
}
tail.next = head;
ListNode newHead = p.next;
p.next = null;
return newHead;
}
}