LeetCode Go · 链表(2) ​
来源与许可
题目版权归 LeetCode;题解来自 Doocs/leetcode,按 CC BY-SA 4.0 共享。补充 Go 代码来自灵茶山艾府、halfrost,保留 MIT 许可及逐题署名。本站提取 Go、调整分类及折叠排版;标注“本站整理”的 Go 解法依据题意与参考思路补写,按 CC BY-SA 4.0 共享。收录日期:2026-10-31;主题源版本:91ef8c241a5e。
本卷 29 题 · 点击题目展开,先独立思考,再查看解法。
876. 链表的中间结点 ​
题目、示例与约束
给你单链表的头结点 head ,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:head = [1,2,3,4,5] 输出:[3,4,5] 解释:链表只有一个中间结点,值为 3 。
示例 2:
输入:head = [1,2,3,4,5,6] 输出:[4,5,6] 解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点。
提示:
- 链表的结点数范围是
[1, 100] 1 <= Node.val <= 100
方法一:快慢指针 · Go 实现
思考
返回链表中间结点,偶数长度取靠后的那个。先数长度再走一半需要两遍。
快指针每次两步、慢指针一步,快指针到末尾时慢指针恰在中点。链表很短,一次遍历即可。
我们定义快慢指针 和 ,初始时均指向链表的头结点。
快指针 每次走两步,慢指针 每次走一步。当快指针走到链表的尾部时,慢指针所指的结点即为中间结点。
时间复杂度 ,其中 是链表的长度。空间复杂度 。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func middleNode(head *ListNode) *ListNode {
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow, fast = slow.Next, fast.Next.Next
}
return slow
}
1019. 链表中的下一个更大节点 ​
题目、示例与约束
给定一个长度为 n 的链表 head
对于列表中的每个节点,查找下一个 更大节点 的值。也就是说,对于每个节点,找到它旁边的第一个节点的值,这个节点的值 严格大于 它的值。
返回一个整数数组 answer ,其中 answer[i] 是第 i 个节点( 从1开始 )的下一个更大的节点的值。如果第 i 个节点没有下一个更大的节点,设置 answer[i] = 0 。
示例 1:

输入:head = [2,1,5] 输出:[5,5,0]
示例 2:

输入:head = [2,7,4,3,5] 输出:[7,0,5,5,0]
提示:
- 链表中节点数为
n 1 <= n <= 1041 <= Node.val <= 109
方法一:单调栈 · Go 实现
思考
对每个节点向右扫描第一个更大值需要 , 偏紧。所求正是「右侧下一个更大元素」,与数组上的经典问题相同。
从右往左维护单调递减栈:栈里是尚未找到更小左邻的候选,当前值会弹出所有不大于它的栈顶,此时栈顶(若存在)就是下一个更大值。
为此先把链表展成数组,再逆序扫一遍填答案。
题目要求找到链表中每个节点的下一个更大的节点,即找到链表中每个节点的右边第一个比它大的节点。我们先遍历链表,将链表中的值存入数组 中。那么对于数组 中的每个元素,我们只需要找到它右边第一个比它大的元素即可。求下一个更大的元素的问题可以使用单调栈来解决。
我们从后往前遍历数组 ,维护一个从栈底到栈顶单调递减的栈 ,遍历过程中,如果栈顶元素小于等于当前元素,则循环将栈顶元素出栈,直到栈顶元素大于当前元素或者栈为空。
如果此时栈为空,则说明当前元素没有下一个更大的元素,否则当前元素的下一个更大的元素就是栈顶元素,更新答案数组 。然后将当前元素入栈,继续遍历。
遍历结束后,返回答案数组 即可。
时间复杂度 ,空间复杂度 。其中 为链表的长度。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func nextLargerNodes(head *ListNode) []int {
nums := []int{}
for ; head != nil; head = head.Next {
nums = append(nums, head.Val)
}
stk := []int{}
n := len(nums)
ans := make([]int, n)
for i := n - 1; i >= 0; i-- {
for len(stk) > 0 && stk[len(stk)-1] <= nums[i] {
stk = stk[:len(stk)-1]
}
if len(stk) > 0 {
ans[i] = stk[len(stk)-1]
}
stk = append(stk, nums[i])
}
return ans
}
1171. 从链表中删去总和值为零的连续节点 ​
题目、示例与约束
给你一个链表的头节点 head,请你编写代码,反复删去链表中由 总和 值为 0 的连续节点组成的序列,直到不存在这样的序列为止。
删除完毕后,请你返回最终结果链表的头节点。
你可以返回任何满足题目要求的答案。
(注意,下面示例中的所有序列,都是对 ListNode 对象序列化的表示。)
示例 1:
输入:head = [1,2,-3,3,1] 输出:[3,1] 提示:答案 [1,2,1] 也是正确的。
示例 2:
输入:head = [1,2,3,-3,4] 输出:[1,2,4]
示例 3:
输入:head = [1,2,3,-3,-2] 输出:[1]
提示:
- 给你的链表中可能有
1到1000个节点。 - 对于链表中的每个节点,节点的值:
-1000 <= node.val <= 1000.
方法一:前缀和 + 哈希表 · Go 实现
思考
连续结点和为 当且仅当两处前缀和相等。若每次找到一段就删再重扫,最坏多次遍历。先扫一遍,哈希表记下每个前缀和最后出现的结点;再扫一遍,把 接到该前缀和最后结点的后继,中间段落被一次性跳过。哑结点处理从头开始的零和段。
若链表节点的两个前缀和相等,说明两个前缀和之间的连续节点序列的和为 ,那么可以消去这部分连续节点。
我们第一次遍历链表,用哈希表 记录前缀和以及对应的链表节点,对于同一前缀和 ,后面出现的节点覆盖前面的节点。
接下来,我们再次遍历链表,若当前节点 的前缀和 在 出现,说明 与 之间的所有节点和为 ,我们直接修改 的指向,即 ,这样就删去了这部分和为 的连续节点。继续往后遍历,删除所有和为 的连续节点。
最后返回链表的头节点 。
时间复杂度 ,空间复杂度 。其中 为链表的长度。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func removeZeroSumSublists(head *ListNode) *ListNode {
dummy := &ListNode{0, head}
last := map[int]*ListNode{}
cur := dummy
s := 0
for cur != nil {
s += cur.Val
last[s] = cur
cur = cur.Next
}
s = 0
cur = dummy
for cur != nil {
s += cur.Val
cur.Next = last[s].Next
cur = cur.Next
}
return dummy.Next
}
1265. 逆序打印不可变链表 🔒 ​
题目、示例与约束
给您一个不可变的链表,使用下列接口逆序打印每个节点的值:
ImmutableListNode: 描述不可变链表的接口,链表的头节点已给出。
您需要使用以下函数来访问此链表(您 不能 直接访问 ImmutableListNode):
ImmutableListNode.printValue():打印当前节点的值。ImmutableListNode.getNext():返回下一个节点。
输入只用来内部初始化链表。您不可以通过修改链表解决问题。也就是说,您只能通过上述 API 来操作链表。
示例 1:
输入:head = [1,2,3,4] 输出:[4,3,2,1]
示例 2:
输入:head = [0,-4,-1,3,-5] 输出:[-5,3,-1,-4,0]
示例 3:
输入:head = [-2,0,6,4,4,-6] 输出:[-6,4,4,6,0,-2]
提示:
- 链表的长度在
[1, 1000]之间。 - 每个节点的值在
[-1000, 1000]之间。
进阶:
您是否可以:
- 使用常数级空间复杂度解决问题?
- 使用线性级时间复杂度和低于线性级空间复杂度解决问题?
方法一:递归 · Go 实现
思考
链表不可变,只能 与 ,长度不超过 。要逆序打印,须先走到后继再打印当前。递归在返回前打印,自然形成从尾到头的输出;调用栈暂存前缀节点。
我们可以使用递归来实现链表的逆序打印。在函数中,我们判断当前节点是否为空,如果不为空,则获取下一个节点,然后递归调用函数本身,最后打印当前节点的值。
时间复杂度 ,空间复杂度 。其中 是链表的长度。
/* Below is the interface for ImmutableListNode, which is already defined for you.
*
* type ImmutableListNode struct {
*
* }
*
* func (this *ImmutableListNode) getNext() ImmutableListNode {
* // return the next node.
* }
*
* func (this *ImmutableListNode) printValue() {
* // print the value of this node.
* }
*/
func printLinkedListInReverse(head ImmutableListNode) {
if head != nil {
printLinkedListInReverse(head.getNext())
head.printValue()
}
}
1290. 二进制链表转整数 ​
题目、示例与约束
给你一个单链表的引用结点 head。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。
请你返回该链表所表示数字的 十进制值 。
最高位 在链表的头部。
示例 1:

输入:head = [1,0,1] 输出:5 解释:二进制数 (101) 转化为十进制数 (5)
示例 2:
输入:head = [0] 输出:0
提示:
- 链表不为空。
- 链表的结点总数不超过
30。 - 每个结点的值不是
0就是1。
方法一:遍历链表 · Go 实现
思考
链表从高位到低位给出二进制。边走边把已有值左移一位再并上当前位,等价于按位累加。长度不超过 ,不会溢出。不必先收集再换算。
我们用变量 记录当前的十进制值,初始值为 。
遍历链表,对于每个结点,将 左移一位,然后再或上当前结点的值。遍历结束后, 即为十进制值。
时间复杂度 ,其中 为链表的长度。空间复杂度 。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func getDecimalValue(head *ListNode) (ans int) {
for ; head != nil; head = head.Next {
ans = ans<<1 | head.Val
}
return
}
1474. 删除链表 M 个节点之后的 N 个节点 🔒 ​
题目、示例与约束
给定链表 head 和两个整数 m 和 n. 遍历该链表并按照如下方式删除节点:
- 开始时以头节点作为当前节点.
- 保留以当前节点开始的前
m个节点. - 删除接下来的
n个节点. - 重复步骤 2 和 3, 直到到达链表结尾.
在删除了指定结点之后, 返回修改过后的链表的头节点.
示例 1:

输入: head = [1,2,3,4,5,6,7,8,9,10,11,12,13], m = 2, n = 3 输出: [1,2,6,7,11,12] 解析: 保留前(m = 2)个结点, 也就是以黑色节点表示的从链表头结点开始的结点(1 ->2). 删除接下来的(n = 3)个结点(3 -> 4 -> 5), 在图中以红色结点表示. 继续相同的操作, 直到链表的末尾. 返回删除结点之后的链表的头结点.
示例 2:

输入: head = [1,2,3,4,5,6,7,8,9,10,11], m = 1, n = 3 输出: [1,5,9] 解析: 返回删除结点之后的链表的头结点.
示例 3:
输入: head = [1,2,3,4,5,6,7,8,9,10,11], m = 3, n = 1 输出: [1,2,3,5,6,7,9,10,11]
示例 4:
输入: head = [9,3,7,7,9,10,8,2], m = 1, n = 2 输出: [9,7,8]
提示:
- 链表中节点数目在范围
[1, 104]内 1 <= Node.val <= 1061 <= m, n <= 1000
进阶: 你能通过 就地 修改链表的方式解决这个问题吗?
方法一:模拟 · Go 实现
思考
按「保留 个、删除 个」重复直到链表结束。用 走完要保留的一段,再用 跳过要删除的节点,改接 。剩余不足时提前结束。
我们可以模拟整个删除过程,首先用 指针指向链表头部,然后遍历链表,移动 步,如果 为空,说明从当前节点开始的节点个数小于 ,直接返回头部;否则,用 指针指向 ,然后移动 步,如果 为空,说明从 开始的节点个数小于 ,直接将 的 指向 ;否则,将 的 指向 的 ,然后将 移动到 的 。继续遍历链表,直到 为空,返回头部。
时间复杂度 ,其中 是链表中节点的个数。空间复杂度 。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func deleteNodes(head *ListNode, m int, n int) *ListNode {
pre := head
for pre != nil {
for i := 0; i < m-1 && pre != nil; i++ {
pre = pre.Next
}
if pre == nil {
return head
}
cur := pre
for i := 0; i < n && cur != nil; i++ {
cur = cur.Next
}
pre.Next = nil
if cur != nil {
pre.Next = cur.Next
}
pre = pre.Next
}
return head
}
1634. 求两个多项式链表的和 🔒 ​
题目、示例与约束
多项式链表是一种特殊形式的链表,每个节点表示多项式的一项。
每个节点有三个属性:
coefficient:该项的系数。项9x4的系数是9。power:该项的指数。项9x4的指数是4。next:指向下一个节点的指针(引用),如果当前节点为链表的最后一个节点则为null。
例如,多项式 5x3 + 4x - 7 可以表示成如下图所示的多项式链表:

多项式链表必须是标准形式的,即多项式必须 严格 按指数 power 的递减顺序排列(即降幂排列)。另外,系数 coefficient 为 0 的项需要省略。
给定两个多项式链表的头节点 poly1 和 poly2,返回它们的和的头节点。
PolyNode 格式:
输入/输出格式表示为 n 个节点的列表,其中每个节点表示为 [coefficient, power] 。例如,多项式 5x3 + 4x - 7 表示为: [[5,3],[4,1],[-7,0]] 。
示例 1:

输入:poly1 = [[1,1]], poly2 = [[1,0]] 输出:[[1,1],[1,0]] 解释:poly1 = x. poly2 = 1. 和为 x + 1.
示例 2:
输入:poly1 = [[2,2],[4,1],[3,0]], poly2 = [[3,2],[-4,1],[-1,0]] 输出:[[5,2],[2,0]] 解释:poly1 = 2x2 + 4x + 3. poly2 = 3x2 - 4x - 1. 和为 5x2 + 2. 注意,我们省略 "0x" 项。
示例 3:
输入:poly1 = [[1,2]], poly2 = [[-1,2]] 输出:[] 解释:和为 0。我们返回空链表。
提示:
0 <= n <= 104-109 <= PolyNode.coefficient <= 109PolyNode.coefficient != 00 <= PolyNode.power <= 109PolyNode.power > PolyNode.next.power
方法一:Go 解法(本站整理) · Go 实现
基于 Doocs 有序链表合并参考改写。每次取幂较高的一项;幂相同则系数相加,零系数不写入结果。新建输出节点,不改变输入。n、m 分别为两个多项式的项数,时间 O(n+m),辅助空间 O(1),输出空间 O(n+m)。平台 PolyNode 为 struct { Coefficient, Power int; Next *PolyNode },按幂严格递减;空多项式用 nil。
出处:Doocs 原题与题解,固定提交 91ef8c241a5eaabf96f9ca8d45cfb1a338bcb011;题意、参考及本 Go 改写采用 CC BY-SA 4.0。
// 题意与参考:Doocs LeetCode,CC BY-SA 4.0。
// https://github.com/doocs/leetcode/blob/91ef8c241a5eaabf96f9ca8d45cfb1a338bcb011/solution/1600-1699/1634.Add%20Two%20Polynomials%20Represented%20as%20Linked%20Lists/README.md
// Go 实现与测试沿用 CC BY-SA 4.0;来源性质见配套说明。
func addPoly(poly1, poly2 *PolyNode) *PolyNode {
dummy := &PolyNode{}
tail := dummy
for poly1 != nil || poly2 != nil {
c, p := 0, 0
if poly2 == nil || (poly1 != nil && poly1.Power > poly2.Power) {
c, p = poly1.Coefficient, poly1.Power
poly1 = poly1.Next
} else if poly1 == nil || poly2.Power > poly1.Power {
c, p = poly2.Coefficient, poly2.Power
poly2 = poly2.Next
} else {
c, p = poly1.Coefficient+poly2.Coefficient, poly1.Power
poly1, poly2 = poly1.Next, poly2.Next
}
if c != 0 {
tail.Next = &PolyNode{Coefficient: c, Power: p}
tail = tail.Next
}
}
return dummy.Next
}
1669. 合并两个链表 ​
题目、示例与约束
给你两个链表 list1 和 list2 ,它们包含的元素分别为 n 个和 m 个。
请你将 list1 中下标从 a 到 b 的全部节点都删除,并将list2 接在被删除节点的位置。
下图中蓝色边和节点展示了操作后的结果:
请你返回结果链表的头指针。
示例 1:

输入:list1 = [10,1,13,6,9,5], a = 3, b = 4, list2 = [1000000,1000001,1000002] 输出:[10,1,13,1000000,1000001,1000002,5] 解释:我们删除 list1 中下标为 3 和 4 的两个节点,并将 list2 接在该位置。上图中蓝色的边和节点为答案链表。
示例 2:
输入:list1 = [0,1,2,3,4,5,6], a = 2, b = 5, list2 = [1000000,1000001,1000002,1000003,1000004] 输出:[0,1,1000000,1000001,1000002,1000003,1000004,6] 解释:上图中蓝色的边和节点为答案链表。
提示:
3 <= list1.length <= 1041 <= a <= b < list1.length - 11 <= list2.length <= 104
方法一:模拟 · Go 实现
思考
用 替换 下标 那一段。找到 的前驱与 结点,把前驱接到 头,再把 尾接到 的后继。
两指针各走相应步数,再扫到 末尾完成链接,最后断开 的 。
直接模拟题目中的操作即可。
在实现上,我们使用两个指针 和 ,初始时均指向链表 list1 的头节点。
然后我们向后移动指针 和 ,直到指针 指向链表 list1 中第 个节点的前一个节点,指针 指向链表 list1 中第 个节点。此时我们将 的 next 指针指向链表 list2 的头节点,将链表 list2 的尾节点的 next 指针指向 的 next 指针指向的节点,即可完成题目要求。
时间复杂度 ,空间复杂度 。其中 和 分别为链表 list1 和 list2 的长度。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func mergeInBetween(list1 *ListNode, a int, b int, list2 *ListNode) *ListNode {
p, q := list1, list1
for ; a > 1; a-- {
p = p.Next
}
for ; b > 0; b-- {
q = q.Next
}
p.Next = list2
for p.Next != nil {
p = p.Next
}
p.Next = q.Next
q.Next = nil
return list1
}
1721. 交换链表中的节点 ​
题目、示例与约束
给你链表的头节点 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]
提示:
- 链表中节点的数目是
n 1 <= k <= n <= 1050 <= Node.val <= 100
方法一:快慢指针 · Go 实现
思考
要交换正数第 个与倒数第 个结点的值。先量出长度再二次遍历可行,但可以一次走完。
快指针先走 步定位第 个结点 ;再与慢指针同步前进,快指针到末尾时慢指针恰为倒数第 个。
交换两结点的值后返回头指针。
我们可以先用快指针 找到链表的第 个节点,用指针 指向它。然后我们再用慢指针 从链表的头节点出发,快慢指针同时向后移动,当快指针到达链表的最后一个节点时,慢指针 恰好指向倒数第 个节点,用指针 指向它。此时,我们只需要交换 和 的值即可。
时间复杂度 ,其中 是链表的长度。空间复杂度 。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func swapNodes(head *ListNode, k int) *ListNode {
fast := head
for ; k > 1; k-- {
fast = fast.Next
}
p := fast
slow := head
for fast.Next != nil {
fast, slow = fast.Next, slow.Next
}
q := slow
p.Val, q.Val = q.Val, p.Val
return head
}
1836. 从未排序的链表中移除重复元素 🔒 ​
题目、示例与约束
给定一个链表的第一个节点 head ,找到链表中所有出现多于一次的元素,并删除这些元素所在的节点。
返回删除后的链表。
示例 1:
输入: head = [1,2,3,2] 输出: [1,3] 解释: 2 在链表中出现了两次,所以所有的 2 都需要被删除。删除了所有的 2 之后,我们还剩下 [1,3] 。
示例 2:
输入: head = [2,1,1,2] 输出: [] 解释: 2 和 1 都出现了两次。所有元素都需要被删除。
示例 3:
输入: head = [3,2,2,1,3,2,4] 输出: [1,4] 解释: 3 出现了两次,且 2 出现了三次。移除了所有的 3 和 2 后,我们还剩下 [1,4] 。
提示:
- 链表中节点个数的范围是
[1, 105] 1 <= Node.val <= 105
方法一:哈希表 · Go 实现
思考
无序链表中凡是值出现超过一次的节点全部删除。一遍遍历无法同时知道全局频次,且链表不能随机访问。
先扫描一遍用哈希表计数,再扫描一遍跳过 的节点。哑结点简化头节点被删的情形。
我们可以用哈希表 统计链表中每个元素出现的次数,然后遍历链表,删除出现次数大于 1 的元素。
时间复杂度 ,空间复杂度 。其中 为链表的长度。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func deleteDuplicatesUnsorted(head *ListNode) *ListNode {
cnt := map[int]int{}
for cur := head; cur != nil; cur = cur.Next {
cnt[cur.Val]++
}
dummy := &ListNode{0, head}
for pre, cur := dummy, head; cur != nil; cur = cur.Next {
if cnt[cur.Val] > 1 {
pre.Next = cur.Next
} else {
pre = cur
}
}
return dummy.Next
}
2046. 给按照绝对值排序的链表排序 🔒 ​
题目、示例与约束
head ,这个链表是根据结点的绝对值进行升序排序, 返回重新根据节点的值进行升序排序的链表。
示例 1:
输入: head = [0,2,-5,5,10,-10] 输出: [-10,-5,0,2,5,10] 解释: 根据结点的值的绝对值排序的链表是 [0,2,-5,5,10,-10]. 根据结点的值排序的链表是 [-10,-5,0,2,5,10].
示例 2:

输入: head = [0,1,2] 输出: [0,1,2] 解释: 这个链表已经是升序的了。
示例 3:
输入: head = [1] 输出: [1] 解释: 这个链表已经是升序的了。
提示:
- 链表节点数的范围是
[1, 105]. -5000 <= Node.val <= 5000head是根据结点绝对值升序排列好的链表.
进阶:
- 你可以在
O(n)的时间复杂度之内解决这个问题吗?
方法一:头插法 · Go 实现
思考
链表已按绝对值有序,负值在原相对次序下应被整体移到最前并倒序。一次遍历即可:遇到负数则头插到当前头之前,非负则指针顺移。
空间 ,不必收集后重排。
我们先默认第一个点已经排序完毕,然后从第二个点开始,遇到值为负数的节点,采用头插法;非负数,则继续往下遍历即可。
时间复杂度 ,其中 为链表的长度。空间复杂度 。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func sortLinkedList(head *ListNode) *ListNode {
prev, curr := head, head.Next
for curr != nil {
if curr.Val < 0 {
t := curr.Next
prev.Next = t
curr.Next = head
head = curr
curr = t
} else {
prev, curr = curr, curr.Next
}
}
return head
}
2058. 找出临界点之间的最小和最大距离 ​
题目、示例与约束
链表中的 临界点 定义为一个 局部极大值点 或 局部极小值点 。
如果当前节点的值 严格大于 前一个节点和后一个节点,那么这个节点就是一个 局部极大值点 。
如果当前节点的值 严格小于 前一个节点和后一个节点,那么这个节点就是一个 局部极小值点 。
注意:节点只有在同时存在前一个节点和后一个节点的情况下,才能成为一个 局部极大值点 / 极小值点 。
给你一个链表 head ,返回一个长度为 2 的数组 [minDistance, maxDistance] ,其中 minDistance 是任意两个不同临界点之间的最小距离,maxDistance 是任意两个不同临界点之间的最大距离。如果临界点少于两个,则返回 [-1,-1] 。
示例 1:

输入:head = [3,1] 输出:[-1,-1] 解释:链表 [3,1] 中不存在临界点。
示例 2:

输入:head = [5,3,1,2,5,1,2] 输出:[1,3] 解释:存在三个临界点: - [5,3,1,2,5,1,2]:第三个节点是一个局部极小值点,因为 1 比 3 和 2 小。 - [5,3,1,2,5,1,2]:第五个节点是一个局部极大值点,因为 5 比 2 和 1 大。 - [5,3,1,2,5,1,2]:第六个节点是一个局部极小值点,因为 1 比 5 和 2 小。 第五个节点和第六个节点之间距离最小。minDistance = 6 - 5 = 1 。 第三个节点和第六个节点之间距离最大。maxDistance = 6 - 3 = 3 。
示例 3:

输入:head = [1,3,2,2,3,2,2,2,7] 输出:[3,3] 解释:存在两个临界点: - [1,3,2,2,3,2,2,2,7]:第二个节点是一个局部极大值点,因为 3 比 1 和 2 大。 - [1,3,2,2,3,2,2,2,7]:第五个节点是一个局部极大值点,因为 3 比 2 和 2 大。 最小和最大距离都存在于第二个节点和第五个节点之间。 因此,minDistance 和 maxDistance 是 5 - 2 = 3 。 注意,最后一个节点不算一个局部极大值点,因为它之后就没有节点了。
示例 4:

输入:head = [2,3,3,2] 输出:[-1,-1] 解释:链表 [2,3,3,2] 中不存在临界点。
提示:
- 链表中节点的数量在范围
[2, 105]内 1 <= Node.val <= 105
方法一:直接遍历 · Go 实现
思考
临界点是局部峰或谷。 ,一次滑窗看相邻三值即可。最大距离是首尾临界点之差,最小距离是相邻临界点间距的最小。
维护第一次与上一次临界下标,边走边更新。不足两个则返回 。
根据题目描述,我们需要找出链表的第一个临界点和最后一个临界点位置 和 ,这样可以计算出最大距离 。对于最小距离 ,我们需要遍历链表,计算相邻两个临界点之间的距离,取最小值即可。
时间复杂度 ,其中 是链表的长度。空间复杂度 。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func nodesBetweenCriticalPoints(head *ListNode) []int {
ans := []int{1 << 30, 0}
first, last := -1, -1
for i := 0; head.Next.Next != nil; head, i = head.Next, i+1 {
a, b, c := head.Val, head.Next.Val, head.Next.Next.Val
if b < min(a, c) || b > max(a, c) {
if last == -1 {
first, last = i, i
} else {
ans[0] = min(ans[0], i-last)
last = i
ans[1] = max(ans[1], last-first)
}
}
}
if first == last {
return []int{-1, -1}
}
return ans
}
2074. 反转偶数长度组的节点 ​
题目、示例与约束
给你一个链表的头节点 head 。
链表中的节点 按顺序 划分成若干 非空 组,这些非空组的长度构成一个自然数序列(1, 2, 3, 4, ...)。一个组的 长度 就是组中分配到的节点数目。换句话说:
- 节点
1分配给第一组 - 节点
2和3分配给第二组 - 节点
4、5和6分配给第三组,以此类推
注意,最后一组的长度可能小于或者等于 1 + 倒数第二组的长度 。
反转 每个 偶数 长度组中的节点,并返回修改后链表的头节点 head 。
示例 1:

输入:head = [5,2,6,3,9,1,7,3,8,4] 输出:[5,6,2,3,9,1,4,8,3,7] 解释: - 第一组长度为 1 ,奇数,没有发生反转。 - 第二组长度为 2 ,偶数,节点反转。 - 第三组长度为 3 ,奇数,没有发生反转。 - 最后一组长度为 4 ,偶数,节点反转。
示例 2:

输入:head = [1,1,0,6] 输出:[1,0,1,6] 解释: - 第一组长度为 1 ,没有发生反转。 - 第二组长度为 2 ,节点反转。 - 最后一组长度为 1 ,没有发生反转。
示例 3:

输入:head = [2,1] 输出:[2,1] 解释: - 第一组长度为 1 ,没有发生反转。 - 最后一组长度为 1 ,没有发生反转。
提示:
- 链表中节点数目范围是
[1, 105] 0 <= Node.val <= 105
方法一:Go 实现(灵茶山艾府) · Go 实现
本站根据 Go 源码整理,非作者原文。
思路:按计划大小 1、2、3……依次收集每组节点指针;达到组大小或链表末尾时,以这一组的实际长度判断奇偶。若为偶数,就从两端向中间交换节点的 Val。此实现保留 Next 连接关系,只反转组内的值;最后一组也按实际长度处理。
复杂度:设节点数为 n,实际最大分组长度为 g。每个节点只被收集和交换常数次,时间为 O(n),峰值辅助空间为 O(g)。由于组大小递增,g=O(√n),所以空间也可写为 O(√n)。
import . "github.com/EndlessCheng/codeforces-go/leetcode/testutil"
// github.com/EndlessCheng/codeforces-go
func reverseEvenLengthGroups(head *ListNode) *ListNode {
var nodes []*ListNode
for node, size := head, 1; node != nil; node = node.Next {
nodes = append(nodes, node)
if len(nodes) == size || node.Next == nil {
if n := len(nodes); n%2 == 0 {
for i := 0; i < n/2; i++ {
nodes[i].Val, nodes[n-1-i].Val = nodes[n-1-i].Val, nodes[i].Val // 直接交换元素值
}
}
nodes = nil
size++
}
}
return head
}
2095. 删除链表的中间节点 ​
题目、示例与约束
给你一个链表的头节点 head 。删除 链表的 中间节点 ,并返回修改后的链表的头节点 head 。
长度为 n 链表的中间节点是从头数起第 ⌊n / 2⌋ 个节点(下标从 0 开始),其中 ⌊x⌋ 表示小于或等于 x 的最大整数。
- 对于
n=1、2、3、4和5的情况,中间节点的下标分别是0、1、1、2和2。
示例 1:

输入:head = [1,3,4,7,1,2,6] 输出:[1,3,4,1,2,6] 解释: 上图表示给出的链表。节点的下标分别标注在每个节点的下方。 由于 n = 7 ,值为 7 的节点 3 是中间节点,用红色标注。 返回结果为移除节点后的新链表。
示例 2:

输入:head = [1,2,3,4] 输出:[1,2,4] 解释: 上图表示给出的链表。 对于 n = 4 ,值为 3 的节点 2 是中间节点,用红色标注。
示例 3:

输入:head = [2,1] 输出:[2] 解释: 上图表示给出的链表。 对于 n = 2 ,值为 1 的节点 1 是中间节点,用红色标注。 值为 2 的节点 0 是移除节点 1 后剩下的唯一一个节点。
提示:
- 链表中节点的数目在范围
[1, 105]内 1 <= Node.val <= 105
方法一:快慢指针 · Go 实现
思考
删除中间结点(偶数个时偏后那个),需找到其前驱。快慢指针:慢从哑结点出发、快从头出发,快走两步慢走一步,快到末尾时慢恰好在中间前一个。
改
slow.next跳过中间结点。长度为 时哑结点使头被正确删掉。
快慢指针法是一种用于解决链表中的问题的常用技巧。我们可以维护两个指针,一个慢指针 和一个快指针 。初始时 指向一个虚拟节点,该虚拟节点的 指针指向链表的头节点 ,而 指向链表的头节点 。
然后,我们每次将慢指针向后移动一个位置,将快指针向后移动两个位置,直到快指针到达链表的末尾。此时,慢指针指向的节点的下一个节点就是链表的中间节点。我们将慢指针指向的节点的 指针指向下下个节点,即可删除中间节点。
时间复杂度 ,其中 是链表的长度。空间复杂度 。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func deleteMiddle(head *ListNode) *ListNode {
dummy := &ListNode{Val: 0, Next: head}
slow, fast := dummy, dummy.Next
for fast != nil && fast.Next != nil {
slow, fast = slow.Next, fast.Next.Next
}
slow.Next = slow.Next.Next
return dummy.Next
}
2130. 链表最大孪生和 ​
题目、示例与约束
在一个大小为 n 且 n 为 偶数 的链表中,对于 0 <= i <= (n / 2) - 1 的 i ,第 i 个节点(下标从 0 开始)的孪生节点为第 (n-1-i) 个节点 。
- 比方说,
n = 4那么节点0是节点3的孪生节点,节点1是节点2的孪生节点。这是长度为n = 4的链表中所有的孪生节点。
孪生和 定义为一个节点和它孪生节点两者值之和。
给你一个长度为偶数的链表的头节点 head ,请你返回链表的 最大孪生和 。
示例 1:

输入:head = [5,4,2,1] 输出:6 解释: 节点 0 和节点 1 分别是节点 3 和 2 的孪生节点。孪生和都为 6 。 链表中没有其他孪生节点。 所以,链表的最大孪生和是 6 。
示例 2:

输入:head = [4,2,2,3] 输出:7 解释: 链表中的孪生节点为: - 节点 0 是节点 3 的孪生节点,孪生和为 4 + 3 = 7 。 - 节点 1 是节点 2 的孪生节点,孪生和为 2 + 2 = 4 。 所以,最大孪生和为 max(7, 4) = 7 。
示例 3:

输入:head = [1,100000] 输出:100001 解释: 链表中只有一对孪生节点,孪生和为 1 + 100000 = 100001 。
提示:
- 链表的节点数目是
[2, 105]中的 偶数 。 1 <= Node.val <= 105
方法一:模拟 · Go 实现
思考
孪生结点是与对称下标配对的两个值,需要最大和。链表不能按下标随机访问,若反复走链表定位配对,时间为二次。
将值收入数组后,下标 与 可直接相加, 为偶数且至多 ,线性额外空间可接受。
遍历存入数组,再对前一半下标取 的最大值。
我们可以将链表中的节点值依次存入数组中,然后使用双指针分别指向数组的开头和结尾,计算每对孪生节点的孪生和,取最大值即为答案。
时间复杂度 ,空间复杂度 。其中 是链表的节点数。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func pairSum(head *ListNode) int {
var s []int
for ; head != nil; head = head.Next {
s = append(s, head.Val)
}
ans, n := 0, len(s)
for i := 0; i < (n >> 1); i++ {
if ans < s[i]+s[n-i-1] {
ans = s[i] + s[n-i-1]
}
}
return ans
}
方法二:快慢指针 + 反转链表 + 双指针 · Go 实现
思考
方法一使用了 数组。若要求常数空间,则需在链表上完成配对。
快慢指针找到中点后切断,反转后半段,再与前半段同步前进,孪生和即为对应结点值之和。
反转在原地完成,空间降为 。
时间复杂度 ,空间复杂度 。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func pairSum(head *ListNode) int {
reverse := func(head *ListNode) *ListNode {
dummy := &ListNode{}
curr := head
for curr != nil {
next := curr.Next
curr.Next = dummy.Next
dummy.Next = curr
curr = next
}
return dummy.Next
}
slow, fast := head, head.Next
for fast != nil && fast.Next != nil {
slow, fast = slow.Next, fast.Next.Next
}
pa := head
q := slow.Next
slow.Next = nil
pb := reverse(q)
ans := 0
for pa != nil {
ans = max(ans, pa.Val+pb.Val)
pa = pa.Next
pb = pb.Next
}
return ans
}
2181. 合并零之间的节点 ​
题目、示例与约束
给你一个链表的头节点 head ,该链表包含由 0 分隔开的一连串整数。链表的 开端 和 末尾 的节点都满足 Node.val == 0 。
对于每两个相邻的 0 ,请你将它们之间的所有节点合并成一个节点,其值是所有已合并节点的值之和。然后将所有 0 移除,修改后的链表不应该含有任何 0 。
返回修改后链表的头节点 head 。
示例 1:

输入:head = [0,3,1,0,4,5,2,0] 输出:[4,11] 解释: 上图表示输入的链表。修改后的链表包含: - 标记为绿色的节点之和:3 + 1 = 4 - 标记为红色的节点之和:4 + 5 + 2 = 11
示例 2:

输入:head = [0,1,0,3,0,2,2,0] 输出:[1,3,4] 解释: 上图表示输入的链表。修改后的链表包含: - 标记为绿色的节点之和:1 = 1 - 标记为红色的节点之和:3 = 3 - 标记为黄色的节点之和:2 + 2 = 4
提示:
- 列表中的节点数目在范围
[3, 2 * 105]内 0 <= Node.val <= 1000- 不 存在连续两个
Node.val == 0的节点 - 链表的 开端 和 末尾 节点都满足
Node.val == 0
方法一:模拟 · Go 实现
思考
相邻 之间的结点值和要合并成一个新结点。链表只前向遍历一次即可完成。
用哑结点与尾指针,在两个 之间累加 ,遇到 就接上值为 的新结点并清零。
返回哑结点之后的链表。
我们定义一个虚拟头节点 ,以及一个指向当前节点的指针 ,一个变量 用来记录当前节点的值之和。
接下来,我们从链表的第二个节点开始遍历,如果当前节点的值不为 0,我们将其加到 上,否则我们将 加到 的后面,并将 置为 0,更新 为 的下一个节点。
最后,我们返回 的下一个节点。
时间复杂度 ,空间复杂度 。其中 为链表的长度。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func mergeNodes(head *ListNode) *ListNode {
dummy := &ListNode{}
tail := dummy
s := 0
for cur := head.Next; cur != nil; cur = cur.Next {
if cur.Val != 0 {
s += cur.Val
} else {
tail.Next = &ListNode{Val: s}
tail = tail.Next
s = 0
}
}
return dummy.Next
}
2289. 使数组按非递减顺序排列 ​
题目、示例与约束
给你一个下标从 0 开始的整数数组 nums 。在一步操作中,移除所有满足 nums[i - 1] > nums[i] 的 nums[i] ,其中 0 < i < nums.length 。
重复执行步骤,直到 nums 变为 非递减 数组,返回所需执行的操作数。
示例 1:
输入:nums = [5,3,4,4,7,3,6,11,8,5,11] 输出:3 解释:执行下述几个步骤: - 步骤 1 :[5,3,4,4,7,3,6,11,8,5,11] 变为 [5,4,4,7,6,11,11] - 步骤 2 :[5,4,4,7,6,11,11] 变为 [5,4,7,11,11] - 步骤 3 :[5,4,7,11,11] 变为 [5,7,11,11] [5,7,11,11] 是一个非递减数组,因此,返回 3 。
示例 2:
输入:nums = [4,5,7,7,13] 输出:0 解释:nums 已经是一个非递减数组,因此,返回 0 。
提示:
1 <= nums.length <= 1051 <= nums[i] <= 109
方法一:单调栈 · Go 实现
思考
每一轮同时删掉所有「左边相邻且严格更大」的元素,问多少轮后数组非递减。 ,不能真的一轮轮模拟。一个元素被删掉的时刻,取决于它右边连续上升段被左侧更大值「吃掉」需要的步数。
从右往左维护单调栈:栈中是尚未被当前值消灭的下标。弹出时, ,表示 要多一轮才能轮到更远的那个。答案是 的最大值。
func totalSteps(nums []int) int {
stk := []int{}
ans, n := 0, len(nums)
dp := make([]int, n)
for i := n - 1; i >= 0; i-- {
for len(stk) > 0 && nums[i] > nums[stk[len(stk)-1]] {
dp[i] = max(dp[i]+1, dp[stk[len(stk)-1]])
stk = stk[:len(stk)-1]
ans = max(ans, dp[i])
}
stk = append(stk, i)
}
return ans
}
2326. 螺旋矩阵 IV ​
题目、示例与约束
给你两个整数:m 和 n ,表示矩阵的维数。
另给你一个整数链表的头节点 head 。
请你生成一个大小为 m x n 的螺旋矩阵,矩阵包含链表中的所有整数。链表中的整数从矩阵 左上角 开始、顺时针 按 螺旋 顺序填充。如果还存在剩余的空格,则用 -1 填充。
返回生成的矩阵。
示例 1:
输入:m = 3, n = 5, head = [3,0,2,6,8,1,7,9,4,2,5,5,0] 输出:[[3,0,2,6,8],[5,0,-1,-1,1],[5,2,4,9,7]] 解释:上图展示了链表中的整数在矩阵中是如何排布的。 注意,矩阵中剩下的空格用 -1 填充。
示例 2:
输入:m = 1, n = 4, head = [0,1,2] 输出:[[0,1,2,-1]] 解释:上图展示了链表中的整数在矩阵中是如何从左到右排布的。 注意,矩阵中剩下的空格用 -1 填充。
提示:
1 <= m, n <= 1051 <= m * n <= 105- 链表中节点数目在范围
[1, m * n]内 0 <= Node.val <= 1000
方法一:模拟 · Go 实现
思考
要把链表值按螺旋写入 矩阵,其余填 。格子至多 ,模拟走法即可,搜索或递归无必要。
预填 后,用方向数组右、下、左、上前进;下一步越界或已写入则转向。链表耗尽即停,未走到的格子保持 。
我们定义一个二维数组 ,用来存放链表中的元素,初始时全部填充为 。定义三个变量 ,分别表示当前的行、列和方向。定义一个数组 ,表示四个方向的偏移量。
然后我们开始遍历链表,每次遍历一个节点,就将当前节点的值填充到 中,然后更新链表的指针,如果链表为空,说明所有的元素都已经填充完毕,退出循环。
否则,我们需要找到下一个元素的位置,我们可以通过当前位置 和当前方向 来计算下一个位置 ,如果 在矩阵的范围内,并且 为 ,说明 还没有被填充过,我们就将 作为下一个位置,否则我们需要更换方向。
遍历完链表之后,我们就得到了一个螺旋矩阵,返回即可。
时间复杂度 ,空间复杂度 。其中 和 分别表示矩阵的行数和列数。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func spiralMatrix(m int, n int, head *ListNode) [][]int {
ans := make([][]int, m)
for i := range ans {
ans[i] = make([]int, n)
for j := range ans[i] {
ans[i][j] = -1
}
}
i, j, k := 0, 0, 0
dirs := [5]int{0, 1, 0, -1, 0}
for {
ans[i][j] = head.Val
if head = head.Next; head == nil {
break
}
for {
x, y := i+dirs[k], j+dirs[k+1]
if x >= 0 && x < m && y >= 0 && y < n && ans[x][y] == -1 {
i, j = x, y
break
}
k = (k + 1) % 4
}
}
return ans
}
2487. 从链表中移除节点 ​
题目、示例与约束
给你一个链表的头节点 head 。
移除每个右侧有一个更大数值的节点。
返回修改后链表的头节点 head 。
示例 1:

输入:head = [5,2,13,3,8] 输出:[13,8] 解释:需要移除的节点是 5 ,2 和 3 。 - 节点 13 在节点 5 右侧。 - 节点 13 在节点 2 右侧。 - 节点 8 在节点 3 右侧。
示例 2:
输入:head = [1,1,1,1] 输出:[1,1,1,1] 解释:每个节点的值都是 1 ,所以没有需要移除的节点。
提示:
- 给定列表中的节点数目在范围
[1, 105]内 1 <= Node.val <= 105
方法一:单调栈(数组辅助) · Go 实现
思考
删除所有右侧存在更大值的节点,即保留从右看的单调递减序列。 ,先把值放入数组,再维护从底到顶递减的栈,最后按栈重建链表。
先将链表中的节点值存入数组 ,再遍历 ,维护一个从栈底到栈顶单调递减的栈 。若当前元素比栈顶大,就弹出栈顶,直到当前值不大于栈顶,再入栈。最后按栈底到栈顶重建链表。
时间复杂度 ,空间复杂度 。其中 是链表的长度。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func removeNodes(head *ListNode) *ListNode {
nums := []int{}
for head != nil {
nums = append(nums, head.Val)
head = head.Next
}
stk := []int{}
for _, v := range nums {
for len(stk) > 0 && stk[len(stk)-1] < v {
stk = stk[:len(stk)-1]
}
stk = append(stk, v)
}
dummy := &ListNode{}
head = dummy
for _, v := range stk {
head.Next = &ListNode{Val: v}
head = head.Next
}
return dummy.Next
}
方法二:单调栈(链表原地) · Go 实现
思考
方法一额外用了值数组与新节点。改为在原链表上维护节点栈:当前值更大则弹栈,再把新的栈顶 接到当前节点。虚拟头初值为 ,避免空栈。
不使用数组,直接遍历链表,维护从栈底到栈顶单调递减的节点栈。当前值更大时弹出栈顶;若栈非空,把栈顶的 指向当前节点,否则接到虚拟头节点后面。然后将当前节点入栈。遍历结束后返回虚拟头节点的 。
时间复杂度 ,空间复杂度 。其中 是链表的长度。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func removeNodes(head *ListNode) *ListNode {
dummy := &ListNode{1 << 30, head}
stk := []*ListNode{dummy}
for cur := head; cur != nil; cur = cur.Next {
for stk[len(stk)-1].Val < cur.Val {
stk = stk[:len(stk)-1]
}
stk[len(stk)-1].Next = cur
stk = append(stk, cur)
}
return dummy.Next
}
2674. 拆分循环链表 🔒 ​
题目、示例与约束
现给定一个由正整数组成的 循环链表 list ,你的任务是将其拆分为 2 个 循环链表 ,使得第一个链表包含 list 前半部分 的节点(即 ceil(list.length / 2) 个节点),顺序与 list 中的顺序相同,而第二个链表包含 list 中 剩余 的节点,顺序也与 list 中的顺序相同。
返回一个长度为 2 的数组,其中第一个元素是表示 前半部分 链表的 循环链表 ,第二个元素是表示 后半部分 链表的 循环链表 。
循环链表 是一个普通的链表,唯一的区别是最后一个节点的下一个节点是头节点。
示例 1:
输入:nums = [1,5,7] 输出:[[1,5],[7]] 解释:初始链表有3个节点,因此前半部分是前两个元素,剩下的 1 个节点在后半部分。
示例 2:
输入:nums = [2,6,1,5] 输出:[[2,6],[1,5]] 解释:初始链表有4个节点,因此前半部分是前两个元素,剩下的 2 个节点在后半部分。
提示:
list中的节点数范围为[2, 105]0 <= Node.val <= 109LastNode.next = FirstNode,其中LastNode是链表的最后一个节点,FirstNode是第一个节点。
方法一:快慢指针 · Go 实现
思考
环形链表要拆成前后两段,前段不少于后段。先数长度再切需两遍。快慢指针一次遍历即可把慢指针停在前半末尾。
快指针走到环口前,慢指针位于中点;再把后半成环、前半接回头节点。
我们定义两个指针 和 ,初始时都指向链表的头节点。每次迭代时,指针 向前移动一步,指针 向前移动两步,直到指针 到达链表的末尾。此时,指针 指向链表节点数的一半,我们将链表从指针 处断开,即可得到两个链表的头节点。
时间复杂度 ,其中 是链表的长度。需要遍历链表一次。空间复杂度 。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func splitCircularLinkedList(list *ListNode) []*ListNode {
a, b := list, list
for b.Next != list && b.Next.Next != list {
a = a.Next
b = b.Next.Next
}
if b.Next != list {
b = b.Next
}
list2 := a.Next
b.Next = list2
a.Next = list
return []*ListNode{list, list2}
}
2807. 在链表中插入最大公约数 ​
题目、示例与约束
给你一个链表的头 head ,每个结点包含一个整数值。
在相邻结点之间,请你插入一个新的结点,结点值为这两个相邻结点值的 最大公约数 。
请你返回插入之后的链表。
两个数的 最大公约数 是可以被两个数字整除的最大正整数。
示例 1:

输入:head = [18,6,10,3] 输出:[18,6,6,2,10,1,3] 解释:第一幅图是一开始的链表,第二幅图是插入新结点后的图(蓝色结点为新插入结点)。 - 18 和 6 的最大公约数为 6 ,插入第一和第二个结点之间。 - 6 和 10 的最大公约数为 2 ,插入第二和第三个结点之间。 - 10 和 3 的最大公约数为 1 ,插入第三和第四个结点之间。 所有相邻结点之间都插入完毕,返回链表。
示例 2:

输入:head = [7] 输出:[7] 解释:第一幅图是一开始的链表,第二幅图是插入新结点后的图(蓝色结点为新插入结点)。 没有相邻结点,所以返回初始链表。
提示:
- 链表中结点数目在
[1, 5000]之间。 1 <= Node.val <= 1000
方法一:模拟 · Go 实现
思考
只需在每对相邻结点之间插入它们的最大公约数,一次顺序扫描即可完成。用 、 指向相邻结点,插入新结点后把 移到原 ,继续直到链表结束。
我们用两个指针 和 分别指向当前遍历到的结点和下一个结点,那么我们只需要在 和 之间插入一个新的结点即可。因此,每次计算出 和 的最大公约数 ,然后在 和 之间插入一个值为 的新结点,然后更新 ,并且 ,继续遍历链表,直到 为空。
时间复杂度 ,其中 是链表的长度,而 是链表中结点的最大值。忽略结果链表的空间消耗,空间复杂度 。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func insertGreatestCommonDivisors(head *ListNode) *ListNode {
for pre, cur := head, head.Next; cur != nil; cur = cur.Next {
x := gcd(pre.Val, cur.Val)
pre.Next = &ListNode{x, cur}
pre = cur
}
return head
}
func gcd(a, b int) int {
if b == 0 {
return a
}
return gcd(b, a%b)
}
2816. 翻倍以链表形式表示的数字 ​
题目、示例与约束
给你一个 非空 链表的头节点 head ,表示一个不含前导零的非负数整数。
将链表 翻倍 后,返回头节点 head 。
示例 1:
输入:head = [1,8,9] 输出:[3,7,8] 解释:上图中给出的链表,表示数字 189 。返回的链表表示数字 189 * 2 = 378 。
示例 2:
输入:head = [9,9,9] 输出:[1,9,9,8] 解释:上图中给出的链表,表示数字 999 。返回的链表表示数字 999 * 2 = 1998 。
提示:
- 链表中节点的数目在范围
[1, 104]内 0 <= Node.val <= 9- 生成的输入满足:链表表示一个不含前导零的数字,除了数字
0本身。
方法一:翻转链表 + 模拟 · Go 实现
思考
链表从高位指向低位,直接自左向右倍乘不便处理进位。先将链表翻转使低位在前,按乘法模拟进位,再翻转回去。
我们先将链表翻转,然后模拟乘法运算,最后再将链表翻转回来。
时间复杂度 ,其中 是链表的长度。忽略答案链表的空间消耗,空间复杂度 。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func doubleIt(head *ListNode) *ListNode {
head = reverse(head)
dummy := &ListNode{}
cur := dummy
mul, carry := 2, 0
for head != nil {
x := head.Val*mul + carry
carry = x / 10
cur.Next = &ListNode{Val: x % 10}
cur = cur.Next
head = head.Next
}
if carry > 0 {
cur.Next = &ListNode{Val: carry}
}
return reverse(dummy.Next)
}
func reverse(head *ListNode) *ListNode {
dummy := &ListNode{}
cur := head
for cur != nil {
next := cur.Next
cur.Next = dummy.Next
dummy.Next = cur
cur = next
}
return dummy.Next
}
3062. 链表游戏的获胜者 🔒 ​
题目、示例与约束
给定长度为 偶数 ,包含整数的链表的 head 节点。
每个 奇数编号 的节点包含一个奇数,并且每个 偶数编号 的节点包含一个偶数。
我们把每个偶数编号的节点和它的下一个节点叫做一个 对,例如编号为 0 和 1 的节点是一对,编号为 2 和 3 的节点是一对,以此类推。
对于每个 对,我们比较对中节点的值:
- 如果奇数节点更大,
"Odd"队得一分。 - 如果偶数节点更大,
"Even"队得一分。
返回分数更 高 的队名,如果分数相同,返回 "Tie"。
示例 1:
输入:head = [2,1] 输出:"Even" 解释:链表中只有一个对 (2,1)。因为 2 > 1,偶数队得分。 因此,答案是 "Even"。
示例 2:
输入:head = [2,5,4,7,20,5] 输出:"Odd" 解释:此链表中有 3 对。让我们分别对每一对进行分析: (2,5) -> 因为 2 < 5,奇数队得分。 (4,7) -> 因为 4 < 7,奇数队得分。 (20,5) -> 因为 20 > 5,偶数队得分。 奇数队得 2 分,偶数队得 1 分,奇数队得分更高。 因此,答案是 "Odd"。
示例 3:
输入:head = [4,5,2,1] 输出:"Tie" 解释:此链表中有 2 对。让我们分别对每一对进行分析: (4,5) -> 因为 4 < 5,奇数队得分。 (2,1) -> 因为 2 > 1,偶数队得分。 每队得 1 分。 因此,答案是 "Tie"。
提示:
- 链表中节点的数字在范围
[2, 100]内。 - 链表中的节点数为偶数。
1 <= Node.val <= 100- 每个奇数编号节点的值都是奇数。
- 每个偶数编号节点的值都是偶数。
方法一:模拟 · Go 实现
思考
偶数长度链表上,相邻奇偶位两两比大小计分。长度不超过 ,按对遍历即可。
每对中较大者给对应奇偶方加一分,最后比较两方得分。
一次走过链表,步长为两个结点。
遍历链表,每次取出两个节点,比较它们的值,然后根据比较结果更新奇数和偶数的得分。最后比较奇数和偶数的得分,返回结果。
时间复杂度 ,其中 是链表的长度。空间复杂度 。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func gameResult(head *ListNode) string {
var odd, even int
for ; head != nil; head = head.Next.Next {
a, b := head.Val, head.Next.Val
if a < b {
odd++
}
if a > b {
even++
}
}
if odd > even {
return "Odd"
}
if odd < even {
return "Even"
}
return "Tie"
}
3063. 链表频率 🔒 ​
题目、示例与约束
给定包含 k 个 不同 元素的链表的 head 节点,创建一个长度为 k 的链表,以 任何顺序 返回链表中所有 不同元素 出现的 频率。返回这个链表的头节点。
示例 1:
输入:head = [1,1,2,1,2,3] 输出:[3,2,1] 解释:列表中有 3 个不同的元素。1 的频率是 3,2 的频率是 2,3 的频率是 1。因此,我们返回 3 -> 2 -> 1。 注意 1 -> 2 -> 3,1 -> 3 -> 2,2 -> 1 -> 3,2 -> 3 -> 1,和 3 -> 1 -> 2 都是合法的答案。
示例 2:
输入:head = [1,1,2,2,2] 输出:[2,3] 解释:列表中有 2 个不同的元素。1 和 2 出现的频率是 2 和 3。因此,我们返回 2 -> 3。
示例 3:
输入:head = [6,5,4,3,2,1] 输出:[1,1,1,1,1,1] 解释:列表中有 6 个不同的元素。每个元素的频率是 1。因此,我们返回 1 -> 1 -> 1 -> 1 -> 1 -> 1。
提示:
- 链表中的节点数字范围在
[1, 105]之间。 1 <= Node.val <= 105
方法一:哈希表 · Go 实现
思考
把链表中每个值的出现次数做成新链表的结点值。需要先看完全表才能知道次数。
哈希表一遍计数,再按计数值建表。题目不要求顺序,头插即可。
我们用一个哈希表 记录链表中每个元素值出现的次数,然后再遍历哈希表的值构造新的链表即可。
时间复杂度 ,空间复杂度 。其中 为链表的长度。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func frequenciesOfElements(head *ListNode) *ListNode {
cnt := map[int]int{}
for ; head != nil; head = head.Next {
cnt[head.Val]++
}
dummy := &ListNode{}
for _, val := range cnt {
dummy.Next = &ListNode{val, dummy.Next}
}
return dummy.Next
}
3217. 从链表中移除在数组中存在的节点 ​
题目、示例与约束
给你一个整数数组 nums 和一个链表的头节点 head。从链表中移除所有存在于 nums 中的节点后,返回修改后的链表的头节点。
示例 1:
输入: nums = [1,2,3], head = [1,2,3,4,5]
输出: [4,5]
解释:

移除数值为 1, 2 和 3 的节点。
示例 2:
输入: nums = [1], head = [1,2,1,2,1,2]
输出: [2,2,2]
解释:

移除数值为 1 的节点。
示例 3:
输入: nums = [5], head = [1,2,3,4]
输出: [1,2,3,4]
解释:

链表中不存在值为 5 的节点。
提示:
1 <= nums.length <= 1051 <= nums[i] <= 105nums中的所有元素都是唯一的。- 链表中的节点数在
[1, 105]的范围内。 1 <= Node.val <= 105- 输入保证链表中至少有一个值没有在
nums中出现过。
方法一:哈希表 · Go 实现
思考
要从链表中删去值落在 中的节点。 ,对每个节点扫描数组判断则平方。
先把 放入哈希集合,查询期望 。虚拟头指向 ,扫描时若后继值在集合中则跳过该后继,否则指针前进。一遍即可处理头节点被删的情况。
我们可以使用一个哈希表 来存储数组 中的所有元素,然后定义一个虚拟节点 ,将其指向链表的头节点 。
接下来,我们遍历从虚拟节点 开始的链表,如果当前节点的下一个节点的值在哈希表 中,我们就将当前节点的指针指向下下个节点,否则我们就将当前节点指针指向下一个节点。
最后,我们返回虚拟节点 的下一个节点。
时间复杂度 ,空间复杂度 。其中 为数组 的长度,而 为链表 的长度。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func modifiedList(nums []int, head *ListNode) *ListNode {
s := map[int]bool{}
for _, x := range nums {
s[x] = true
}
dummy := &ListNode{Next: head}
for pre := dummy; pre.Next != nil; {
if s[pre.Next.Val] {
pre.Next = pre.Next.Next
} else {
pre = pre.Next
}
}
return dummy.Next
}
3263. 将双链表转换为数组 I 🔒 ​
题目、示例与约束
给定一个 双链表 的 head 节点,其中的节点具有指向下一个节点的指针和上一个节点的指针。
返回一个 按顺序 包含链表中元素的整数数组。
示例 1:
输入:head = [1,2,3,4,3,2,1]
输出:[1,2,3,4,3,2,1]
示例 2:
输入:head = [2,2,2,2,2]
输出:[2,2,2,2,2]
示例 3:
输入:head = [3,2,3,2,3,2]
输出:[3,2,3,2,3,2]
提示:
- 给定链表中节点的数量在
[1, 50]范围。 1 <= Node.val <= 50
方法一:直接遍历 · Go 实现
思考
给定双向链表的头节点,输出从左到右的值。节点数有限,按
next走一遍即可,无需利用prev。循环取出 再前进,直到空。线性时间,额外空间除答案外为常数。
我们可以直接遍历链表,将节点的值依次添加到答案数组 中。
遍历结束后,返回答案数组 即可。
时间复杂度 ,其中 为链表的长度。忽略答案数组的空间消耗,空间复杂度 。
/**
* Definition for a Node.
* type Node struct {
* Val int
* Next *Node
* Prev *Node
* }
*/
func toArray(head *Node) (ans []int) {
for ; head != nil; head = head.Next {
ans = append(ans, head.Val)
}
return
}
3294. 将双链表转换为数组 II 🔒 ​
题目、示例与约束
给定一个 双链表 的 任意 node,其中的节点具有指向下一个节点的指针和上一个节点的指针。
返回一个 按顺序 包含链表中元素的整数数组。
示例 1:
输入:head = [1,2,3,4,5], node = 5
输出:[1,2,3,4,5]
示例 2:
输入:head = [4,5,6,7,8], node = 8
输出:[4,5,6,7,8]
提示:
- 给定列表中的节点数在范围
[1, 500]内。 1 <= Node.val <= 1000- 所有节点的
Node.val互不相同。
方法一:遍历链表 · Go 实现
思考
给定的是双向链表中任意一个节点,需要输出整表从左到右的值。先向左走到头,再向右收集。
沿
prev直到空,然后与 I 相同沿next写入数组。节点至多 ,两次线性遍历。
我们可以从给定的节点开始,往前遍历链表,直到遍历到头节点,然后再从头节点开始往后遍历链表,将遍历到的节点的值添加到答案数组中。
遍历结束后,返回答案数组即可。
时间复杂度 ,其中 为链表的节点个数。忽略答案数组的空间消耗,空间复杂度 。
/**
* Definition for a Node.
* type Node struct {
* Val int
* Next *Node
* Prev *Node
* }
*/
func toArray(node *Node) (ans []int) {
for node != nil && node.Prev != nil {
node = node.Prev
}
for ; node != nil; node = node.Next {
ans = append(ans, node.Val)
}
return
}
3507. 移除最小数对使数组有序 I ​
题目、示例与约束
给你一个数组 nums,你可以执行以下操作任意次数:
- 选择 相邻 元素对中 和最小 的一对。如果存在多个这样的对,选择最左边的一个。
- 用它们的和替换这对元素。
返回将数组变为 非递减 所需的 最小操作次数 。
如果一个数组中每个元素都大于或等于它前一个元素(如果存在的话),则称该数组为非递减。
示例 1:
输入: nums = [5,2,3,1]
输出: 2
解释:
- 元素对
(3,1)的和最小,为 4。替换后nums = [5,2,4]。 - 元素对
(2,4)的和为 6。替换后nums = [5,6]。
数组 nums 在两次操作后变为非递减。
示例 2:
输入: nums = [1,2,2]
输出: 0
解释:
数组 nums 已经是非递减的。
提示:
1 <= nums.length <= 50-1000 <= nums[i] <= 1000
方法一:模拟 · Go 实现
思考
,每次将相邻和最小的一对合并,操作次数不超过 。直接模拟:每轮扫描相邻和、替换并删除右侧元素,直到数组非递减。
无需维护更复杂的结构;平方级扫描已落在约束之内。
我们定义一个函数 ,用于判断数组 是否为非递减数组。
我们使用一个循环,直到数组 为非递减数组为止。在每次循环中,我们找到数组 中相邻元素对的和的最小值,并记录该对的左边元素的下标 。然后,我们将该对的和替换左边的元素,并删除右边的元素。最后,我们返回操作的次数。
时间复杂度 ,空间复杂度 。其中 为数组的长度。
func minimumPairRemoval(nums []int) int {
arr := append([]int(nil), nums...)
ans := 0
isNonDecreasing := func(a []int) bool {
for i := 1; i < len(a); i++ {
if a[i] < a[i-1] {
return false
}
}
return true
}
for !isNonDecreasing(arr) {
k := 0
s := arr[0] + arr[1]
for i := 1; i < len(arr)-1; i++ {
t := arr[i] + arr[i+1]
if s > t {
s = t
k = i
}
}
arr[k] = s
copy(arr[k+1:], arr[k+2:])
arr = arr[:len(arr)-1]
ans++
}
return ans
}
3510. 移除最小数对使数组有序 II ​
题目、示例与约束
给你一个数组 nums,你可以执行以下操作任意次数:
- 选择 相邻 元素对中 和最小 的一对。如果存在多个这样的对,选择最左边的一个。
- 用它们的和替换这对元素。
返回将数组变为 非递减 所需的 最小操作次数 。
如果一个数组中每个元素都大于或等于它前一个元素(如果存在的话),则称该数组为非递减。
示例 1:
输入: nums = [5,2,3,1]
输出: 2
解释:
- 元素对
(3,1)的和最小,为 4。替换后nums = [5,2,4]。 - 元素对
(2,4)的和为 6。替换后nums = [5,6]。
数组 nums 在两次操作后变为非递减。
示例 2:
输入: nums = [1,2,2]
输出: 0
解释:
数组 nums 已经是非递减的。
提示:
1 <= nums.length <= 105-109 <= nums[i] <= 109
方法一:有序集合 · Go 实现
思考
上一题 模拟在 下不可行。仍须反复合并相邻和最小的一对,但每次扫描全数组过慢。
用有序集合按 取最小对,另一有序集合维护存活下标以便 定位左右邻居;再用 统计当前逆序相邻对数,只在受影响的两三处更新。当 时停止。
我们定义一个有序集合 来存储所有相邻元素对的和及其左侧下标的元组 ,定义另一个有序集合 来存储当前数组中剩余元素的下标,并使用变量 来记录当前数组中的逆序对数量。初始时,我们遍历数组 ,将所有相邻元素对的和及其左侧下标的元组加入有序集合 中,并计算逆序对数量 。
在每次操作中,我们从有序集合 中取出和最小的元素对 ,那么我们可以得到下标 和 (其中 是下标 在有序集合 中的下一个下标)对应的元素对是当前数组中和最小的相邻元素对。如果 ,则说明该元素对是一个逆序对,合并替换后逆序对数量 减一。
接下来,我们需要更新与下标 和 相关的元素对:
-
如果下标 在有序集合 中有前驱下标 ,则需要更新元素对 。如果 ,则说明该元素对是一个逆序对,合并替换后逆序对数量 减一。然后,我们从有序集合 中移除元素对 ,并将新的元素对 加入有序集合 中。如果 ,则说明新的元素对是一个逆序对,合并替换后逆序对数量 加一。
-
如果下标 在有序集合 中有后继下标 ,则需要更新元素对 。如果 ,则说明该元素对是一个逆序对,合并替换后逆序对数量 减一。然后,我们从有序集合 中移除元素对 ,并将新的元素对 加入有序集合 中。如果 ,则说明新的元素对是一个逆序对,合并替换后逆序对数量 加一。
接下来,我们将下标 处的元素替换为 ,并从有序集合 中移除下标 。我们重复上述过程,直到逆序对数量 为零为止。最终,操作次数即为将数组变为非递减所需的最小操作次数。
时间复杂度 ,空间复杂度 。其中 为数组的长度。
func minimumPairRemoval(nums []int) (ans int) {
type pair struct{ s, i int }
n := len(nums)
inv := 0
sl := redblacktree.NewWith[pair, struct{}](func(a, b pair) int { return cmp.Or(a.s-b.s, a.i-b.i) })
idx := redblacktree.New[int, struct{}]()
for i := 0; i < n; i++ {
idx.Put(i, struct{}{})
}
for i := 0; i < n-1; i++ {
if nums[i] > nums[i+1] {
inv++
}
sl.Put(pair{nums[i] + nums[i+1], i}, struct{}{})
}
for inv > 0 {
ans++
it := sl.Iterator()
it.First()
p := it.Key()
sl.Remove(p)
s, i := p.s, p.i
jNode, _ := idx.Ceiling(i + 1)
j := jNode.Key
if nums[i] > nums[j] {
inv--
}
if hNode, ok := idx.Floor(i - 1); ok {
h := hNode.Key
if nums[h] > nums[i] {
inv--
}
sl.Remove(pair{nums[h] + nums[i], h})
if nums[h] > s {
inv++
}
sl.Put(pair{nums[h] + s, h}, struct{}{})
}
if kNode, ok := idx.Ceiling(j + 1); ok {
k := kNode.Key
if nums[j] > nums[k] {
inv--
}
sl.Remove(pair{nums[j] + nums[k], j})
if s > nums[k] {
inv++
}
sl.Put(pair{s + nums[k], i}, struct{}{})
}
nums[i] = s
idx.Remove(j)
}
return
}
继续阅读
- 实践案例
- 实践案例
- 实践案例