LeetCode203 移除链表元素 递归法解析(JavaScript)

558 阅读4分钟

本文示例代码均使用JavaScript

零、前言

本文针对算法小白而写。首先分析为何本题可以使用递归的方法解决,随后拆解子问题并给出代码,并用JavaScript构建了一个简单的链表方便调试。在此之后详细解析了代码的执行过程。

一、题干

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

示例 1:

image.png

输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]

示例 2:

输入:head = [], val = 1 输出:[]

示例 3:

输入:head = [7,7,7,7], val = 7 输出:[]

二、解法

想要运用递归法解决此问题需要了解如下两个知识点

1.如何删除链表的结点

首先,我们需要了解如何删除链表中的结点。对于非头结点来说,直接让被删除结点的前一个结点的next指针指向它原本的next结点的next就可以了,即,prev.next = prev.next.next。对于头结点来说,我们只需要直接取头结点的next结点就好了,这样头结点被忽略就相当于被删除了。

2.递归的条件

判断一个问题是否可以用递归的方法来解决,需要看这个问题是否满足如下三个条件。

  • 这个问题可以分解成若干子问题
  • 解决子问题的思路和解决这个问题的思路一样,只是规模更小
  • 有终止条件,也可以叫做递归基。(即子问题足够小,不需要继续递归,可以直接求解)

思路:

这道题可以拆解成头结点是否需要删除和除了头结点以外的余下链表的本题求解(即删除除了头结点外链表中所有满足 Node.val == val 的节点),比如链表为:1->2->6->3->4->5->6,值为6,就可以拆解成1是否需要删除,和删除2->6->3->4->5->6这个链表中值为6的所有结点,而2->6->3->4->5->6又可以拆解成2是否需要删除和删除6->3->4->5->6这个链表中值为6的结点,以此类推,直到最后一个结点,因为最后一个结点的next指向null,所以可以作为终止条件。通过以上分析可以看出本题是可以采用递归的方式求解的。

以上思路用公式可以表示成这样(仅表达思路 = 和 + 不是数学意义上的):

image.png

按照以上思路我们可以写出如下代码:

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
 
// 构建链表
const node3 = new ListNode(3, null)
const node2 = new ListNode(6, node3)
const node1 = new ListNode(2, node2)
const head = new ListNode(1, node1)

// 移除链表函数 LeetCode上只需提交这个函数的代码就行了 上面构建对链表是为了方便本地观察函数调用过程写的 (函数每一行前面的数字为行号,为了下面的代码讲解方便加的)
1 let removeElements = function(head, val) {
2  if(head === null) {
3      return null
4  }
5  head.next = removeElements(head.next, val)
6  return head.val === val ? head.next : head
7 };

removeElements(head, 6)

回看一下代码执行过程(链表1->2->6->3,val=6)

  • head为1,递归执行removeElements
  • head为2,递归执行removeElements
  • head为6,递归执行removeElements
  • head为3,递归执行removeElements(到目前为止代码还没有走到第6行)
  • head为null,返回null
  • 回到上一级没有执行完的代码继续执行,这一级head为3,刚刚求出来head.next为null,所以执行第六行之后,返回3
  • 回到上一级没有执行完的代码继续执行,这一级head为6,刚刚求出来head.next为3,所以执行第六行,返回3
  • 回到上一级没有执行完的代码继续执行,这一级head为2,刚刚求出来head.next为3,所以执行第六行,返回2(即2->3)
  • 回到上一级没有执行完的代码继续执行,这一级head为1,刚刚求出来head.next为2->3,所以执行第六行,返回1(即1->2->3)

三、其他方法

1. 头结点特殊处理

let removeElements = function(head, val) {
  while(head && head.val === val) {
    head = head.next
  }
  if (!head) {
    return null
  }
  let pre = head
  while(pre.next) {
    if (pre.next.val === val) {
      pre.next = pre.next.next
    } else {
      pre = pre.next
    }
  }
  return head
};

2. 加一个伪头结点

let removeElements = function(head, val) {
  const newHead = new ListNode(0, head)
  let pre = newHead
  while(pre && pre.next) {
    if (pre.next.val === val) {
      pre.next = pre.next.next
    } else {
      pre = pre.next
    }
  }
  return pre.next
};

四、参考资料

  1. 阿里云开发者社区 LeetCode 203:移除链表元素

力扣(LeetCode)链接leetcode-cn.com/problems/re…

如有疑问欢迎留言 ღ( ´・ᴗ・` )比心