【刷力扣 TS 版】难度 中等 链表专题(四)

简介: 【刷力扣 TS 版】难度 中等 链表专题(四)

原文来自 我的个人博客

前言

拒绝摆烂ヾ(◍°∇°◍)ノ゙

从今天开始(2023/02/12),定一个小目标,先刷个 300Leetcode 题目(之前刷的不计入)。

当然作为一个小前端,我选择的语言是 TS,而且刷的题目的难度会偏中等一些,大概按照 简单3 中等6 困难1 这样的题型分布吧。嗯,目前是这么打算的。

本题 Github 地址:因为比较喜欢 vscode 的界面,而且方便调试,所以 AC 完就顺便提到 github 了,也算做一个记录吧。

本篇的题目是这个系列的第

  1. NO.172487. 从链表中移除节点
  2. NO.18剑指 Offer II 025. 链表中的两数相加
  3. NO.19面试题 02.05. 链表求和
  4. NO.202181. 合并零之间的节点

1. 从链表中移除节点

1.1 题目描述

给你一个链表的头节点 head 。

对于列表中的每个节点 node ,如果其右侧存在一个具有 严格更大 值的节点,则移除 node 。

返回修改后链表的头节点 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

1.2 解法一:递归

按照正常的思维遍历链表,每次遇到右边节点比当前节点的值大的节点就删除当前节点,但是删除完之后还有判断删除的节点左边的节点与右边节点的大小,而此时我们又很难拿到左边的节点。

思路:用递归的思维,我们将链表递归到最后的节点,在递归回来的过程判读左右两个节点的大小。

function removeNodes(head: ListNode | null): ListNode | null {
  let curr: ListNode | null = head;
  if (curr && !curr.next) {
    return curr;
  }
  let newHead: ListNode | null = removeNodes(curr!.next);
  if (head!.val < newHead!.val) {
    head!.next = null;
  } else {
    head!.next = newHead
    newHead = head;
  }
  return newHead;
}

复杂度分析:

  • 时间复杂度:O(n),其中 n 为链表的长度。
  • 空间复杂度:O(n),需要 O(n) 的栈空间。

1.3 迭代:反转两次链表

这种方法的思路就是先将原先的链表反转过来,此时我们的问题从 "移除右边节点比当前节点大的当前节点" 变成了 “移除右边节点比当前节点小的右边节点

// 1 -> 2 -> 3 -> 4 -> 5
// 1 <- 2 <- 3 <- 4 <- 5
function reverseList(head: ListNode | null): ListNode | null {
  let prev: ListNode | null = null;
  let curr: ListNode | null = head;
  let next: ListNode | null = head?.next ?? null;

  if (!curr || !next) return head;

  while (curr && next) {
    curr.next = prev;
    prev = curr;
    curr = next;
    next = next?.next;
    curr.next = prev;
  }
  return curr;
}

// 5 -> 2 -> 13 -> 3 -> 8
// 8 -> 3 -> 13 -> 2 -> 5
//13 -> 8
function removeNodes(head: ListNode | null): ListNode | null {
  let newHead = reverseList(head);
  let curr: ListNode | null = newHead;
  while (curr && curr.next) {
    while (curr && curr.next && curr.val > curr.next.val) curr.next = curr.next.next;
    curr = curr.next;
  }
  newHead = reverseList(newHead);
  return newHead;
}

复杂度分析

  • 时间复杂度:O(n),其中 n 为链表的长度。
  • 空间复杂度:O(1),仅用到若干额外变量。

2. 链表中的两数相加

2.1 题目描述

给定两个 非空链表 l1和 l2 来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。

可以假设除了数字 0 之外,这两个数字都不会以零开头。

示例1:

输入: l1 = [7,2,4,3], l2 = [5,6,4]
输出: [7,8,0,7]

示例2:

输入: l1 = [2,4,3], l2 = [5,6,4]
输出: [8,0,7]

示例3:

输入: l1 = [0], l2 = [0]
输出: [0]

提示:

  • 链表的长度范围为 [1, 100]
  • 0 <= node.val <= 9
  • 输入数据保证链表代表的数字无前导 0

进阶: 如果输入链表不能修改该如何处理?换句话说,不能对列表中的节点进行翻转。

注意:本题与主站 445 题相同:https://leetcode-cn.com/problems/add-two-numbers-ii/

2.2 解:链表反转相加

这道题的思路就是将两个链表相加,因为加法运算要从个位数开始加。加完之后再将生成的链表返回即可

function reverseList(head: ListNode | null): ListNode | null {
  let prev: ListNode | null = null;
  let curr: ListNode | null = head;
  let next: ListNode | null = curr?.next ?? null;

  while (curr && next) {
    curr.next = prev;
    prev = curr;
    curr = next;
    next = next.next;
    curr.next = prev;
  }
  return curr;
}

function addTwoNumbers(
  l1: ListNode | null,
  l2: ListNode | null
): ListNode | null {
  l1 = reverseList(l1);
  l2 = reverseList(l2);
  let tmp: number = 0;
  let ret = new ListNode();
  let p = ret;

  while (l1 && l2) {
    p.next = new ListNode((tmp + l1.val + l2.val) % 10);
    tmp = Math.floor((tmp + l1.val + l2.val) / 10);
    l1 = l1.next;
    l2 = l2.next;
    p = p.next;
  }

  while (l1) {
    p.next = new ListNode((tmp + l1.val) % 10);
    tmp = Math.floor((tmp + l1.val) / 10);
    l1 = l1.next;
    p = p.next;
  }

  while (l2) {
    p.next = new ListNode((tmp + l2.val) % 10);
    tmp = Math.floor((tmp + l2.val) / 10);
    l2 = l2.next;
    p = p.next;
  }

  if (tmp) p.next = new ListNode(tmp);
  return reverseList(ret.next);
}

3. 链表求和

3.1 题目描述

给定两个用链表表示的整数,每个节点包含一个数位。

这些数位是反向存放的,也就是个位排在链表首部。

编写函数对这两个整数求和,并用链表形式返回结果。

 

示例:

输入: (7 -> 1 -> 6) + (5 -> 9 -> 2),即617 + 295
输出: 2 -> 1 -> 9,即912

进阶: 思考一下,假设这些数位是正向存放的,又该如何解决呢?

示例:

输入: (6 -> 1 -> 7) + (2 -> 9 -> 5),即617 + 295
输出: 9 -> 1 -> 2,即912

3.2 解

这道题比上面的还要简单,都不需要反转,直接拿来加就好了

function addTwoNumbers(
  l1: ListNode | null,
  l2: ListNode | null
): ListNode | null {
  let tmp: number = 0;
  let ret: ListNode | null = new ListNode();
  let p = ret;
  while (l1 && l2) {
    p.next = new ListNode((l1.val + l2.val + tmp) % 10);
    tmp = Math.floor((l1.val + l2.val + tmp) / 10);
    l1 = l1.next;
    l2 = l2.next;
    p = p.next;
  }

  while (l1) {
    p.next = new ListNode((l1.val + tmp) % 10);
    tmp = Math.floor((l1.val + tmp) / 10);
    l1 = l1.next;
    p = p.next;
  }

  while (l2) {
    p.next = new ListNode((l2.val + tmp) % 10);
    tmp = Math.floor((l2.val + tmp) / 10);
    l2 = l2.next;
    p = p.next;
  }

  if (tmp) p.next = new ListNode(tmp);
  return ret.next;
}

4. 合并零之间的节点

4.1 题目描述

给你一个链表的头节点 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

4.2 解

思路与算法:

我们设置有一个指针指向 head,边遍历链表边计算当前节点到上一个 0 节点之间节点的合 num,直到遇到下一个 0 节点,将 num 重置。

function mergeNodes(head: ListNode | null): ListNode | null {
  let curr: ListNode | null = head;
  let ret: ListNode | null = new ListNode();
  let p = ret;
  let num: number = 0;
  while(curr) {
    if(curr.val === 0 && num !== 0) {
      p.next = new ListNode(num)
      p = p.next
      num = 0
    }

    if(curr.val !== 0) {
      num += curr.val
    }
    curr = curr.next
  }
  return ret.next
}
相关文章
|
1月前
【力扣】-- 移除链表元素
【力扣】-- 移除链表元素
36 1
|
1月前
Leetcode第21题(合并两个有序链表)
这篇文章介绍了如何使用非递归和递归方法解决LeetCode第21题,即合并两个有序链表的问题。
50 0
Leetcode第21题(合并两个有序链表)
|
1月前
|
算法
【链表】算法题(二) ----- 力扣/牛客
【链表】算法题(二) ----- 力扣/牛客
|
1月前
LeetCode第二十四题(两两交换链表中的节点)
这篇文章介绍了LeetCode第24题的解法,即如何通过使用三个指针(preNode, curNode, curNextNode)来两两交换链表中的节点,并提供了详细的代码实现。
18 0
LeetCode第二十四题(两两交换链表中的节点)
|
1月前
Leetcode第十九题(删除链表的倒数第N个节点)
LeetCode第19题要求删除链表的倒数第N个节点,可以通过快慢指针法在一次遍历中实现。
44 0
Leetcode第十九题(删除链表的倒数第N个节点)
|
1月前
|
索引
力扣(LeetCode)数据结构练习题(3)------链表
力扣(LeetCode)数据结构练习题(3)------链表
78 0
|
1月前
【LeetCode 10】142. 环形链表 II
【LeetCode 10】142. 环形链表 II
21 0
|
1月前
【LeetCode 09】19 删除链表的倒数第 N 个结点
【LeetCode 09】19 删除链表的倒数第 N 个结点
17 0
|
1月前
【LeetCode 08】206 反转链表
【LeetCode 08】206 反转链表
12 0
|
1月前
【LeetCode 06】203.移除链表元素
【LeetCode 06】203.移除链表元素
30 0