链表问题,如何优雅递龟吗?

开发 前端
今天「小熊」主要介绍采用「递归」的策略,秒杀「链表」相关问题,使得代码更「优雅」,并以两道常见的面试题作为例题来讲解,供大家参考,希望对大家有所帮助。

 [[403833]]

本文转载自微信公众号「程序员小熊」,作者程序员小熊。转载本文请联系程序员小熊公众号。

前言

大家好,我是来自华为的「程序员小熊」。相信绝大部分童鞋都知道,在处理与「链表」相关问题时,常用的解题套路主要包括「双指针」、「迭代」和「虚拟头节点」等等。

今天「小熊」主要介绍采用「递归」的策略,秒杀「链表」相关问题,使得代码更「优雅」,并以两道常见的面试题作为例题来讲解,供大家参考,希望对大家有所帮助。

链表与递归

链表具有天然的递归性,一个链表可以看出头节点后面挂接一个更短的链表,这个更短的链表是以原链表的头节点的下一节点为头节点,依次内推,直到最后的更短的链表为空,空本身也是一个链表(最基础的)。

以单链表 1->2->3->null 为例子,如下图示:

原链表

将原链表看出头节点 1 后挂接一个更短的链表

头节点+更短链表

继续拆解,直到无法拆解

更更短链表

更更更短链表

有了这样的思考,很多「链表」相关问题,都可以采用「递归」的思路来解答。

剑指 Offer 24. 反转链表

  1. 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。 
  2.   
  3.  
  4. 示例: 
  5.  
  6. 输入: 1->2->3->4->5->NULL 
  7. 输出: 5->4->3->2->1->NULL 
  8.   
  9.  
  10. 限制: 
  11. 0 <= 节点个数 <= 5000 

解题思路

要反转链表,即将原链表的头节点变为新链表的尾节点,原链表的尾节点变为新链表的头节点。如下图示:

反转之前:

原链表

反转之后:

新链表

主要策略主要有:1、直接修改链表的值,如上图中的原链表图所示,将原链表值 1 的头节点改为原链表尾节点的值,依次类推;2、让遍历整个链表,让链表的尾节点指向其前一个节点,依次类推。

虽然这两个策略都可行,不过在面试中通常要求采用「策略2」。

由上面的「递归与链表」可知,本题也可以采用「递归法」去求解。

具体如何通过「递归」去解答呢?见下面例子。

「举例」

链表 1->2->3->null 为例子,如下图示。

示例

不断遍历找到原链表为尾节点,即新链表的头节点。

原链表尾节点

然后让尾节点指向其前驱节点,依次类推。

递归反转

详细步骤,如下动图示:

递归反转单链表

Show me the Code

「C」

  1. struct ListNode* reverseList(struct ListNode* head){ 
  2.     /* 递归终止条件 */ 
  3.     if (head == NULL || head->next == NULL) { 
  4.         return head; 
  5.     } 
  6.  
  7.     /* 反转当前所在的链表节点 */ 
  8.     struct ListNode* newHead = reverseList(head->next); 
  9.  
  10.     /* 由原链表的尾节点挨个指向其前驱节点 */ 
  11.     head->next->next = head; 
  12.  
  13.     /* 防止成环 */ 
  14.     head->next = NULL
  15.  
  16.     /* 返回新的链表头节点 */ 
  17.     return newHead; 

「java」

  1. ListNode reverseList(ListNode head) { 
  2.     if (head == null || head.next == null) { 
  3.         return head; 
  4.     } 
  5.  
  6.     ListNode node = reverseList(head.next); 
  7.     head.next.next = head; 
  8.     head.next = null
  9.  
  10.     return node; 

当然本题也可以采用「迭代」的方法去做,其代码(python 版)也很优雅,具体如下:

Show me the Code

「python」

  1. def reverseList(self, head: ListNode) -> ListNode: 
  2.     cur, pre = head, None 
  3.     while cur: 
  4.         cur.next, pre, cur = pre, cur, cur.next 
  5.          
  6.     return pre  

「复杂度分析」

时间复杂度:「O(n)」,n 是链表的长度。对链表的每个节点都进行了反转操作。

空间复杂度:「O(n)」,n 是链表的长度。递归调用的栈空间,最多为 n 层。

203. 移除链表元素

  1. 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足  
  2.  
  3. Node.val == val 的节点,并返回 新的头节点 。 
  4.  
  5. 示例 1: 
  6.  
  7. 输入:head = [1,2,6,3,4,5,6], val = 6 
  8. 输出:[1,2,3,4,5] 
  9.  
  10. 示例 2: 
  11.  
  12. 输入:head = [], val = 1 
  13. 输出:[] 
  14.  
  15. 示例 3: 
  16.  
  17. 输入:head = [7,7,7,7], val = 7 
  18. 输出:[] 

解题思路

要移除链表中给定值的节点,一般策略是「找到给点值的节点的前驱节点,然后让其指向给定值的节点的后继节点」。

例如要删除链表 1->2->3->null 中,节点值为 3 的节点,就得先找到其前驱节点(值为 2 的节点),如下图示:

删除给定值的节点

由上面的「递归与链表」可知,本题同样也可以采用「递归法」去求解,不断删除更短链表中给定值的节点,然后再将处理后的更短的链表,挂接在其前驱节点后。

「注意」最后要判断原链表的头节点是否是待移除的节点。

「举例」

以链表 1->2->3->null 为例子,移除链表中给定值的节点的过程,如下动图示。

示例动图

Show me the Code

「C++」

  1. ListNode* removeElements(ListNode* head, int val) { 
  2.     /* 递归终止条件 */ 
  3.     if(head == NULL) { 
  4.         return NULL
  5.     } 
  6.  
  7.     /* 删除链表中头节点后值为 val 的元素的节点 */ 
  8.     head->next=removeElements(head->next,val); 
  9.  
  10.     /* 判断头节点是否为值为 val 的节点,再返回*/ 
  11.     return head->val==val ? head->next : head; 

「Golang」

  1. func removeElements(head *ListNode, val int) *ListNode { 
  2.     if head == nil { 
  3.         return head 
  4.     } 
  5.  
  6.     head.Next = removeElements(head.Next, val) 
  7.     if head.Val == val { 
  8.         return head.Next 
  9.     } 
  10.  
  11.     return head 

「复杂度分析」

时间复杂度:「O(n)」,n 是链表的长度。递归需要遍历链表一次。

空间复杂度:「O(n)」,n 是链表的长度。递归调用栈,最多不会超过 n 层。

 

责任编辑:武晓燕 来源: 程序员小熊
相关推荐

2022-08-11 11:09:38

线上问题程序员

2021-04-19 07:41:37

AcceptEmfile问题

2019-03-21 15:30:05

JavaStream性能

2021-03-24 10:20:50

Fonts前端代码

2015-11-26 10:53:45

LinuxWindowsMac OS

2021-01-19 10:35:49

JVM场景函数

2017-07-26 11:32:50

NETRabbitMQ系统集成

2022-09-28 12:23:36

Promise代码

2020-10-16 11:48:06

服务器系统运维

2021-11-15 06:56:45

系统运行空指针

2023-06-28 08:25:14

事务SQL语句

2022-02-18 17:34:47

数组多维五维数组

2023-06-16 09:08:39

ReactContextRFC

2022-04-11 08:17:07

JVMJava进程

2017-12-19 10:03:44

JavaLinux代码

2023-10-19 19:42:25

IstioPodkubernetes

2023-10-10 13:23:18

空指针异常Java

2020-08-26 07:17:19

通信

2022-09-09 15:17:02

CentOS 7Linux

2011-09-21 17:09:16

梭子鱼负载均衡
点赞
收藏

51CTO技术栈公众号