160. 相交链表(Easy)
编写一个程序,找到两个单链表相交的起始节点。
如下面的两个链表:
在节点 c1 开始相交。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。
注意:
- 如果两个链表没有交点,返回 null.
- 在返回结果后,两个链表仍须保持原有的结构。
- 可假定整个链表结构中没有循环。
- 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
解法一:两个单链表有交点,则说明从交点之后的链表都是同一段,那么我们可以用两个指针,在两条链表的同个点同时出发,第一个相同点的时候就是第一个交点。
- 先遍历两条单链表,得到他们的长度
- 让较长的单链表先走|lenA-lenB|步,然后两者同步开始走
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null){
return null;
}
//统计两条单链表的长度,并判断是否相交
//如果相交,那么最后一个ListNode相等
int countA = 0;
int countB = 0;
int k = 0;
ListNode A = headA;
ListNode B = headB;
while (A.next != null){
countA++;
A = A.next;
}
while (B.next != null){
countB++;
B = B.next;
}
if (A != B){
return null;
}
A = headA;
B = headB;
//让长链表先走长度之差步
if (countA <= countB){
k = countB - countA;
//长的先走
for (int i = 0; i < k; i++) {
B = B.next;
}
//然后两者一起走
while (A != B){
A = A.next;
B = B.next;
}
return A;
}else {
k = countA - countB;
//长的先走
for (int i = 0; i < k; i++) {
A = A.next;
}
//然后两者一起走
while (A != B){
A = A.next;
B = B.next;
}
return B;
}
}
}
方法二:链表A长度a+c(c为公共部分长度),链表B长度b+c,那么a+c+b=b+c+a。
- 如果存在交点,那么当访问 A链表的指针访问到链表尾部时,令它从链表 B的头部开始访问链表 B;(a+c+b的过程)
- 同样地,当访问 B链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。(b+c+a的过程)
- 这样就能控制访问 A 和 B两个链表的指针能同时访问到交点。
public class Solution {
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode A = headA;
ListNode B = headB;
//A从链表A出发->链表B
//B从链表B出发->链表A
while (A != B){
//A先走完链表A走B;B反过来
A = (A == null) ? headB : A.next;
B = (B == null) ? headA : B.next;
}
return A;
}
}
206. 反转链表(Easy)
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
递归解法:递归解法理解起来比较困难,我在看了几位大佬的解答之后才能理解。
- 首先需要先知道递归是怎么解决一个问题的,和一般解答不同的是:递归是先从顶部递归到底部才开始解决问题,然后至下而上。总的过程是由上到下->由下到上。
- 理解了这个过程之后再去看解答过程就比较容易理解了。借用题解区一位大佬的图

/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
//递归的终止条件
if (head == null || head.next == null){
return head;
}
//递归到最后一个节点(cur)
ListNode cur = reverseList(head.next);
//这个翻转过程可以借鉴上面的动图配合理解
//链表是1->2->3->4->5,此时cur就是5,而head是4
//我们的目标是从4->5变成4<-5
// head.next.next = head 相当于 5.next=4
head.next.next = head;
//这里的head.next设置为空是为了防止链表循环
head.next = null;
//返回翻转之后的头节点
return cur;
}
}
迭代解法:这种解法应该是我们最熟悉的一种,也是比较简单的,直接使用三个变量就可以解决。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
//准备一个空节点当做前节点,tmp存储当前节点的下一节点
ListNode pre = null;
ListNode cur = head;
ListNode tmp = null;
while (cur != null){
//存储下一节点,以防更改指针之后丢失下一节点
tmp = cur.next;
//翻转
cur.next = pre;
//cur和pre都前进一步
pre = cur;
cur = tmp;
}
return pre;
}
}
21. 合并两个有序链表(Easy)
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
迭代解法:大概思路是先准备一个新链表,然后比较两单链表,小的加进去。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
ublic ListNode mergeTwoLists(ListNode l1, ListNode l2) {
//如果其中一条单链表为空,直接返回另外一条
if (l1 == null){
return l2;
}
if (l2 == null){
return l1;
}
//准备一条空链表
ListNode head = new ListNode(-1);
ListNode cur = head;
//比较两链表
while (l1 != null && l2 != null){
//小的添加进去
if (l1.val <= l2.val){
cur.next = l1;
l1 = l2.next;
}else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
//两条链表长度可能不一致,剩下的加到新链表末尾
cur.next = (l1 != null) ? l1 : l2;
return head.next;
}
}
递归解法:一般链表题目都可以用递归解决,用递归解答时,要先明确递归函数的定义,mergeTwoLists的定义是返回递归之后的新链表头节点,比较两条链表,小的那个next指针指向mergeTwoLists。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
//如果其中一条单链表为空,直接返回另外一条
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
//比较两条链表,小的next指针接递归函数,并返回拼接好的
if (l1.val <= l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
}
83. 删除排序链表中的重复元素(Easy)
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
输入: 1->1->2
输出: 1->2
示例 2:
输入: 1->1->2->3->3
输出: 1->2->3
迭代解法:准备一个指针cur,遍历链表,当前节点cur等于下一节点next的话,删除下一节点next。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode cur = head;
//遍历单链表
while (cur != null && cur.next != null){
if (cur.val == cur.next.val){
cur.next = cur.next.next;
}else {
cur = cur.next;
}
}
return head;
}
}
递归解法:同样地,这道题也有递归解法,依然先明确递归函数定义:deleteDuplicates代表的是删除重复节点之后单链表的头节点
class Solution{
public ListNode deleteDuplicates(ListNode head) {
//base case
if (head == null || head.next == null) {
return head;
}
// heda.next连接的是已经删除重复节点之后的单链表
head.next = deleteDuplicates(head.next);
// 此时,head后面的链表已经去重,只需要判断head和head.next是否重复即可
return head.val == head.next.val ? head.next : head;
}
}
19. 删除链表的倒数第N个节点(Medium)
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
- 给定的 n 保证是有效的。
进阶:
- 你能尝试使用一趟扫描实现吗?
解法一:可以先遍历一趟链表,得出链表长度len,第二遍的时候走len-n-1步得到倒数第n个节点的前一个节点,然后删除倒数第n个即可
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//统计链表长度
int len = 0;
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode cur = head;
while (cur != null){
cur = cur.next;
len++;
}
//指针走len-n步,得到的就是倒数第n个
cur = dummy;
for (int i = 0; i < len - n; i++) {
cur = cur.next;
}
//跳过被删除的元素
cur.next = cur.next.next;
return dummy.next;
}
}
解法二:使用快慢指针
- 让快指针先走n步,如果此时快指针值为null,那么说明要删除的为首节点,返回head.next即可
- 不为空的话就两个指针继续走,快指针走到末尾时,慢指针指向要删除节点的前一个,删除即可
class Soution{
public ListNode removeNthFromEnd(ListNode head, int n) {
if (head == null){
return null;
}
ListNode fast = head;
ListNode slow = head;
//快指针先走n步
for (int i = 0; i < n; i++) {
fast = fast.next;
}
//如果为空,则说明要删除的为首节点
if (fast == null){
return head.next;
}
while (fast.next != null){
fast = fast.next;
slow = slow.next;
}
//删除节点
slow.next = slow.next.next;
return head;
}
}
24. 两两交换链表中的节点(Medium)
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.
递归解法:参考了题解区一位大佬的解答
- 递归的解法和迭代解法的区别是递归关注的是当前的处理过程,因为递归的本质就是重复,而迭代关注的是整个过程,
- 关于递归我们需要关注3点
- 返回值
- 调用单元做了什么
- 终止条件
- 在本题中:
- 返回值:交换完成的子链表
- 调用单元:设需要交换的两个点为 head 和 next,head 连接后面交换完成的子链表,next 连接 head,完成交换
- 终止条件:head 为空指针或者 next 为空指针,也就是当前无节点或者只有一个节点,无法进行交换
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
//只有一个节点无法进行交换,直接返回
if (head == null || head.next == null){
return head;
}
//交换
ListNode next = head.next;
head.next = swapPairs(head.next);
next.next = head;
//返回交换之后的头节点
return next;
}
}
迭代解法:基本思路和递归解法相似,迭代解法需要准备一个dummy节点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
//准备一个dummy节点
ListNode pre = new ListNode(0);
pre.next = head;
//准备cur节点
ListNode cur = pre;
//遍历链表,进行交换
while (cur.next != null && cur.next.next != null){
ListNode l1 = cur.next;
ListNode l2 = cur.next.next;
//交换
l1.next = l2.next;
l2.next = l1;
cur.next = l2;
//将cur往后移
cur = l1;
}
return pre.next;
}
}
445. 两数相加 II(Medium)
给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:
- 如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
示例:
输入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 8 -> 0 -> 7
解法一:数字最高位置链表起始位置,所以我们做加法的时候需要将链表翻转过来,可以考虑使用“栈”来存储,弹出来的时候顺序就是相反的。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//将两条链表存储进栈
Stack<Integer> stack1 = buildStack(l1);
Stack<Integer> stack2 = buildStack(l2);
//准备新链表的dummy节点
ListNode head = new ListNode(-1);
//进位
int carry = 0;
while (!stack1.isEmpty() || !stack2.isEmpty() || carry != 0){
//当前位=x+y+carry
int x = stack1.isEmpty() ? 0 : stack1.pop();
int y = stack2.isEmpty() ? 0 : stack2.pop();
int sum = x + y + carry;
ListNode node = new ListNode(sum % 10);
//更新进位
carry = sum / 10;
//连接顺序要符合链表头部为数字高位的顺序
//新更新的节点后面接的是上次链表的头节点
node.next = head.next;
//更新头节点
head.next = node;
}
return head.next;
}
}
解法二:这道题还可以使用递归解法,递归实质上也是一种栈,也就可以实现从后往前取数字,我们首先统计出两个链表长度,然后根据长度来调用递归函数。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
int flow = 0;
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
if (l1 == null) return l2;
if (l2 == null) return l1;
ListNode res1 = l1, res2 = l2;
//统计两链表的长度
int len1 = 0, len2 = 0;
while (l1 != null) {
len1++;
l1 = l1.next;
}
while (l2 != null) {
len2++;
l2 = l2.next;
}
//根据长度之差判断哪个链表为辅助链表
ListNode res = len1 > len2 ? add(res1, res2, len1, len2) : add(res2, res1, len2, len1);
if (flow == 1) {
res1 = new ListNode(1);
res1.next = res;
return res1;
}
return res;
}
public ListNode add(ListNode l1, ListNode l2, int len1, int len2) {
//base case
if (l1 == null) {
return null;
}
//复用l1,也可以选择不复用,复用是为了进一步优化
int temp;
if (len1 > len2) {
temp = l1.val;
l1.next = add(l1.next, l2, len1 - 1, len2);
l1.val = (temp + flow) % 10;
flow = (temp + flow) / 10;
return l1;
}
l1.next = add(l1.next, l2.next, len1 - 1, len2 - 1);
temp = l1.val;
l1.val = (temp + flow + l2.val) % 10;
flow = (temp + flow + l2.val) / 10;
return l1;
}
}
234. 回文链表(Easy)
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
- 你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
解法一:回文链表的特点是中心对称,即顺着和反着是一样的,所以可以使用栈将节点装进去,然后弹出来一一比较
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
Stack<Integer> stack = new Stack<>();
ListNode cur = head;
//stack存储链表
while (cur != null){
stack.push(cur.val);
cur = cur.next;
}
//比较stack和单链表
while (!stack.isEmpty()){
int num = stack.pop();
if (num != head.val){
return false;
}
head = head.next;
}
return true;
}
}
解法二:进阶要求空间复杂度为O(1),那么就不能使用栈。可以想到既然回文结构是中心对称,那么可以将链表的后半部分翻转过来比较即可
- 首先找到后半段的起点,有两种方法
- 一是遍历链表,得到链表长度len,然后准备一个指针走len/2步就是后半段起点
- 二是用“快慢”指针,快指针一次走两步,慢指针一次走一步,慢指针从head出发,快指针从head.next出发,最后慢指针停下来的点就是后半段起点
- 将后半段翻转,逐一比较
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null){
return true;
}
//准备快慢指针
//链表个数为偶数的话,slow最后到前半段最后一个,奇数的话在正中间
ListNode slow = head;
ListNode fast = head.next;
while (fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
//复用fast,将fast成为右半部分的第一个节点
fast = slow.next;
//将左边链表断开
slow.next = null;
//将右半部分翻转
ListNode pre = null;
while (fast != null){
//保存下个节点
ListNode next = fast.next;
//翻转
fast.next = pre;
//将节点往下移
pre = fast;
fast = next;
}
//比较左右两部分,pre在右段头部分
slow = head;
while (slow != null && pre != null){
if (slow.val != pre.val){
return false;
}
slow = slow.next;
pre = pre.next;
}
return true;
}
}
725. 分割链表(Medium)
给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。 每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。
这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。 返回一个符合上述规则的链表的列表。
举例: 1->2->3->4, k = 5 // 5 结果 [ [1], [2], [3], [4], null ]
示例 1:
输入:
root = [1, 2, 3], k = 5
输出: [[1],[2],[3],[],[]]
解释: 输入输出各部分都应该是链表,而不是数组。
例如, 输入的结点 root 的 val= 1, root.next.val = 2, \root.next.next.val = 3, 且 root.next.next.next = null。
第一个输出 output[0] 是 output[0].val = 1, output[0].next = null。
最后一个元素 output[4] 为 null, 它代表了最后一个部分为空链表。
示例 2:
输入:
root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
输出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
解释:
输入被分成了几个连续的部分,并且每部分的长度相差不超过1.前面部分的长度大于等于后面部分的长度。
提示:
- root 的长度范围: [0, 1000].
- 输入的每个节点的大小范围:[0, 999].
- k 的取值范围: [1, 50].
题解:
- 题目让我们尽量把链表均分成k份,那么我们需要知道链表的长度len,然后len除于k,得到size份,len对k取余,得到最后剩余的mod个是多出来的,加到每一份里面。
- 接着遍历链表,将链表分成k份
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode[] splitListToParts(ListNode root, int k) {
//统计链表长度
int len = 0;
ListNode cur = root;
while (cur != null) {
len++;
cur = cur.next;
}
//每1份节点 = 商大小的节点 + 从余数里取出1个节点
//取余
int mod = len % k;
//求商
int size = len / k;
//将链表分成k份
ListNode[] listNodes = new ListNode[k];
cur = root;
//分成k份
for (int i = 0; cur != null && i < k; i++) {
listNodes[i] = cur;
//有些份是没有多余的节点的
int curSize = size + (mod-- > 0 ? 1 : 0);
//添加curSize个节点
for (int j = 0; j < curSize - 1; j++) {
cur = cur.next;
}
//断开当前份与后面份
ListNode next = cur.next;
cur.next = null;
//后移节点
cur = next;
}
return listNodes;
}
}
328. 奇偶链表(Medium)
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例 1:
输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
示例 2:
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
说明:
- 应当保持奇数节点和偶数节点的相对顺序。
- 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
题解:准备奇偶指针,分别指向奇偶其实位置,用一个指针来保存偶数指针其实位置,把奇节点的指向偶节点的下一个(一定是奇节点),此奇节点后移一步,再把偶节点指向下一个奇节点的下一个(一定是偶节点),此偶节点后移一步,以此类推直至末尾,此时把分开的偶节点的链表连在奇节点的链表后即可。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode oddEvenList(ListNode head) {
if (head == null){
return null;
}
//准备奇偶指针和保存偶数起点位置的指针
ListNode odd = head;
ListNode even = head.next;
ListNode evenHead = even;
//聚集奇偶链表元素
while (even != null && even.next != null){
//连接奇数元素,然后后移
odd.next = odd.next.next;
odd = odd.next;
even.next = even.next.next;
even = even.next;
}
odd.next = evenHead;
return head;
}
}