链表问题,如何优雅递龟?
Posted 程序员小熊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了链表问题,如何优雅递龟?相关的知识,希望对你有一定的参考价值。
前言
大家好,我是来自「华为」的「程序员小熊」。绝大部分童鞋都知道,解决「链表」相关问题时,常用的解题套路主要包括「双指针」、「迭代」和「虚拟头节点」等等。
今天「小熊」介绍采用「递归」的策略,秒杀「链表」相关问题,使得代码更「优雅」,并以两道常见的面试题作为例题来讲解,供大家参考,希望对大家有所帮助。
链表与递归
链表具有天然的递归性,一个链表可以看出头节点后挂接一个更短的链表,这个更短的链表是以原链表的头节点的下一节点为头节点,依次内推,直到最后的更短的链表为空,空本身也是一个链表(最基础的)。
以单链表 1->2->3->null 为例子,如下图示:
将原链表看出头节点 1 后挂接一个更短的链表
继续拆解,直到无法拆解
有了这样的思考,很多「链表」相关问题,都可以采用「递归」的思路来解答。
剑指 Offer 24. 反转链表
解题思路
要反转链表,即将原链表的头节点变为新链表的尾节点,原链表的尾节点变为新链表的头节点。如下图示:
反转之前:
反转之后:
主要策略主要有:1、修改链表的值,如上图示,将原链表头节点的值 1 改为原链表尾节点的值 3,依次类推;2、让遍历整个链表,让链表的尾节点指向其前一个节点,依次类推。
虽然这两个策略都可行,不过在面试中通常要求采用「策略2」。
由上面的「递归与链表」可知,本题也可以采用「递归法」去求解。
具体如何通过「递归」去解答呢?见下面例子。
「举例」
链表 1->2->3->null 为例子,如下图示。
不断遍历找到原链表为尾节点,即新链表的头节点。
然后让尾节点指向其前驱节点,依次类推。
详细步骤,如下动图示:
Show me the Code
1 // c 语言 2 struct ListNode* reverseList(struct ListNode* head){ 3 /* 递归终止条件 */ 4 if (head == NULL || head->next == NULL) { 5 return head; 6 } 7 8 /* 反转当前所在的链表节点 */ 9 struct ListNode* newHead = reverseList(head->next); 10 11 /* 由原链表的尾节点挨个指向其前驱节点 */ 12 head->next->next = head; 13 14 /* 防止成环 */ 15 head->next = NULL; 16 17 /* 返回新的链表头节点 */ 18 return newHead; 19 }
1 // Java 2 ListNode reverseList(ListNode head) { 3 if (head == null || head.next == null) { 4 return head; 5 } 6 7 ListNode node = reverseList(head.next); 8 head.next.next = head; 9 head.next = null; 10 11 return node; 12 }
当然本题也可以采用「迭代」的方法去做,其代码(python 版)也很优雅,具体如下:
Show me the Code
1 # Python3 2 def reverseList(self, head: ListNode) -> ListNode: 3 cur, pre = head, None 4 while cur: 5 cur.next, pre, cur = pre, cur, cur.next 6 7 return pre
「复杂度分析」
时间复杂度:「O(n)」,其中 n 是链表的长度。对链表的每个节点都进行了反转操作。
空间复杂度:「O(n)」,其中 n 是链表的长度。递归调用的栈空间,最多为 n 层。
203. 移除链表元素
解题思路
要移除链表中给定值的节点,一般策略是「找到给点值的节点的前驱节点,然后让其指向给定值的节点的后继节点」。
例如要删除链表 1->2->3->null 中,节点值为 3 的节点,就得先找到其前驱节点(值为 2 的节点),如下图示:
由上面的「递归与链表」可知,本题同样也可以采用「递归法」去求解,不断删除更短链表中给定值的节点,然后再将处理后的更短的链表,挂接在其前驱节点后。
「注意」
最后要判断原链表的头节点是否是待移除的节点。
「举例」
以链表 1->2->3->null 为例子,移除链表中给定值的节点的过程,如下动图示。
Show me the Code
1 // C++ 2 ListNode* removeElements(ListNode* head, int val) { 3 /* 递归终止条件 */ 4 if(head == NULL) { 5 return NULL; 6 } 7 8 /* 删除链表中头节点后值为 val 的元素的节点 */ 9 head->next=removeElements(head->next,val); 10 11 /* 判断头节点是否为值为 val 的节点,再返回*/ 12 return head->val==val ? head->next : head; 13 }
1 // Golang 2 func removeElements(head *ListNode, val int) *ListNode { 3 if head == nil { 4 return head 5 } 6 7 head.Next = removeElements(head.Next, val) 8 if head.Val == val { 9 return head.Next 10 } 11 12 return head 13 }
「复杂度分析」
时间复杂度:「O(n)」,其中 n 是链表的长度。递归需要遍历链表一次。
空间复杂度:「O(n)」,其中 n 是链表的长度。递归调用栈,最多不会超过 n 层。
以上是关于链表问题,如何优雅递龟?的主要内容,如果未能解决你的问题,请参考以下文章