递归:一维链表和数组

Posted 我家大宝最可爱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了递归:一维链表和数组相关的知识,希望对你有一定的参考价值。

反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
递归是将一个大问题转化成同样的小问题,原始问题与小问题是一样的,只不过问题的规模给缩小了。再反转字符串的时候,其实就是头尾两端的字符进行交换,然后依次向中间走即可。

l —><— r
hello
l —>l<— r
oellh

可以看到,就是重复操作左右两侧的字符串进行交换,然后字符串不断缩小(问题规模缩小),但是操作一直是重复不变的

class Solution:
    def reverseString(self, s: List[str]) -> None:
        """
        Do not return anything, modify s in-place instead.
        """
        def dfs(arr, l, r):
            if l >= r: # 递归的终止条件
                return
            
            arr[l],arr[r] = arr[r],arr[l] # 重复操作部分。交换左右的两个字符
            dfs(arr,l+1,r-1) # 缩减问题的规模,重复操作
        
        dfs(s,0,len(s)-1)
        return s

从尾到头打印链表

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

在递归函数后面还有操作,也就说这个递归函数一直往后找,最终找到了停止条件,这个时候就会返回了,在返回之后再进行一些操作,这里是把数据保存下来。
而且发现了一个问题,就是我们输入的参数是head,然后再递归函数中还有一个head.next,也就是说此时我们其实是由cur和next的。

一开始递归不是不断的从上往下走的,并不需要什么操作,一直走到最后找到了停止条件,然后开始逐步返回,这个时候就是从下往上走(逆向了),我们把每一步的head存储下来即可。

12345None
head
head
head
此时head是4head
此时head是5,传入head.nexthead
停止条件,head is Nonehead
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def reversePrint(self, head: ListNode) -> List[int]:
        res = []
        def dfs(head):
            if head is None:
                return
            
            dfs(head.next) # head.next is None,此时找到了停止条件
            res.append(head.val) # head的值并不是None,所以将值添加进来,并且此时开始逐步返回递归的head,也就是上一步的head
        
        dfs(head)
        return res

删除链表的倒数第 N 个结点

链表的递归我感觉更像是递归遍历,不通过for循环操作进行遍历,直接使用递归的原理进行遍历,再删除倒数第N个节点的时候,通过递归会顺向遍历到尾部,然后再从下往上返回,这个时候我们就可以计数了,记到第N+1个的时候,我们可以进行操作。

这其中有两个小技巧,一个是定义一个dummy的节点放到head的前面,这样如果要删除head的结点依然可以返回dummy.next,第二个是使用nonlocal这个关键字,这个关键字定义了内部函数可以操作函数以外的变量。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        dummy = ListNode(0,head)
        def dfs(head):
            nonlocal n
            if head is None:
                return
            dfs(head.next)

            n -= 1
            if n == -1:
                head.next = head.next.next
        
        dfs(dummy)
        return dummy.next


两两交换链表中的节点


这个同样可以考虑递归,我们首先交换两个节点,后面的节点都是重复的操作,所以,这个问题可以用循环,也可以使用递归,不过这个问题需要思考返回的是什么。

我们怎么知道这个迭代公式的?怎么判断这个问题就是可以用递归呢?万一大问题并不能拆解成小问题呢?

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def swapPairs(self, head: ListNode) -> ListNode:
        if head is None or head.next is None:
            return head
        a = head
        b = head.next
        
        a.next = self.swapPairs(b.next)
        b.next = a
        return b

2的幂

给你一个整数 n,请你判断该整数是否是 2 的幂次方

这个问题同样可以考虑使用递归,大问题是判断一个数是否是2的幂次方,这个数除以2之后得到的新数同样会判断是否是2的幂次方,即大问题可以一步一步拆解成小问题,并且每一步都是再重复操作,重复去判断是否可以被2整除

可以看到这个问题是直接返回了递归函数,而且没有任何其他操作,这就意味着递归到最后一层的结果是直接层层返回去了,没有任何中间商,所以直接最后一步能否整除就是最后的结果。

我不知道n是否是2的幂次方,但是如果可以通过求解n%2来判断,如果我知道了n%21,那么n肯定不是2的幂次方,如果n%20,我依然不知道,但是我可以缩减问题的规模,如果我知道了(n//2)%==1,那么n肯定不是2的幂次方。

class Solution:
    def isPowerOfTwo(self, n: int) -> bool:
        if n == 1:
            return True
        elif n % 2 > 0 or n < 1:
            return False
        return self.isPowerOfTwo(n // 2)
        

汉诺塔


汉诺塔也是用递归的方式来做。假如我们有N个圆盘,我们先通过C把前N-1个圆盘移动到B上面,然后把A上面的圆盘给移动到C上面,然后我们再通过A,把B上上面的N-1个圆盘移动到C上面。
为什么会想到用递归呢?因为原始问题与缩减后的问题是一样的,我们可以把N转化成N-1,N-1转化成N-2,一直转化到2,1等等。使用的都是同样的方式,递归其实非常像是高中学习的递推公式,首先增明N=1的时候符合规律,然后证明N+1的时候也是符合规律的。也就是说不管问题规模有多大,都是符合同一个规律的,因此我们可以通过迭代的方式计算出来。

汉诺塔还有一个很有意思的地方,我们的输入是move(n,A,B,C),要得到这个结果,所以我们函数中递归了一个move(n-1.A,C,B),问题是我们该如何把n-1个圆盘进行移动呢?不知道,所以这就是递归让人疑惑的地方,就这么不断的递归下去问题就能解决了吗?是的,就这么解决了。反正我现在还没有想明白,通过move(n-1.A,C,B)之后,A上面就只有一个圆盘了,此时我们直接把这个圆盘移动到C上面即可。问题是,我现在都不知道C上面是否还有圆盘,问什么就可以直接移动,很神奇。

class Solution:
    def hanota(self, A: List[int], B: List[int], C: List[int]) -> None:
        """
        Do not return anything, modify C in-place instead.
        """

        def move(n,A,B,C):
            if n == 1: # 剩下一个圆盘的时候,直接将A的圆盘移动到C上
                C.append(A.pop())
                return

            move(n-1, A, C, B) # 将A上面的n-1个圆盘通过C,移动到B上面
            C.append(A.pop()) # 此时把A上面最后一个圆盘移动到C上面即可
            move(n-1, B, A, C) # 此时B上面有n-1个圆盘,B再通过A把n-1个圆盘移动到C上面即可
        
        n = len(A)
        move(n,A,B,C)
        

50. Pow(x, n)

这个递归也非常有意思,同样是有点难以理解,我们求 x n x^n xn其实可以递归成两种情况
x n = ( x n 2 ) 2 n%2==0 x ∗ ( x n 2 ) 2 n%2==1 x^n= \\begincases (x^\\fracn2)^2 & \\textn\\%2==0 \\\\ x*(x^\\fracn2)^2 & \\textn\\%2==1 \\\\ \\endcases xn=(x2n)2x(x2n)2n%2==0n%2==1
转化成递归函数的形式就是
p o w ( x , n ) = p o w ( x , n / / 2 ) 2 n%2==0 x ∗ p o w ( x , n / / 2 ) 2 n%2==1 pow(x,n)= \\begincases pow(x,n//2)^2& \\textn\\%2==0 \\\\ x*pow(x,n//2)^2 & \\textn\\%2==1 \\\\ \\endcases pow(x,n)=pow(x,n//2)2xpow(x,n//2)2n%2==0n%2==1

你问我怎么求解 p o w ( x , n / / 2 ) pow(x,n//2) pow(x,n//2)我也不知道,反正就是根据这个递归公式不断递归就可以求解出来了。我也不知道怎么求 p o w ( x , n ) pow(x,n) pow(x,n),但是如果我求出了 p o w ( x , n / / 2 ) pow(x,n//2) pow(x,n//2)我就可以求出 p o w ( x , n ) pow(x,n) pow(x,n)了,如和求求解 p o w ( x , n / / 2 ) pow(x,n//2) pow(x,n//2)呢?我只要求解出 p o w ( x , n / / 4 ) pow(x,n//4) pow(x,n//4)就可以了,这样问题最后就会缩减到n=1也就是停止条件上,然后递归再进行返回,我们操作这个返回结果即可。

这个跟汉诺塔的问题非常的相像, 我不知道如何把n个圆盘从A移到C,但是如果我把n-1一个圆盘能移动到B,那么就可以完成。

class Solution:
    def myPow(self, x: float, n: int) -> float:

        def dfs(x,n):
            if n == 0:
                return 1
            y = dfs(x, n//2)
            if n % 2 == 0:
                return y * y
            else:
                return x * y * y
        
        if n < 0:
            x = 1 / x
            n = -n

        r = dfs(x,n)
        return r

以上是关于递归:一维链表和数组的主要内容,如果未能解决你的问题,请参考以下文章

玩转数据结构——链表和递归

单向链表和二进制

玩转数据结构:第5章 链表和递归

python 链表和递归的问题

python 用字符串,链表和递归来练习问题

(王道408考研数据结构)第六章图-第二节1:图的存储结构(邻接矩阵邻接表十字链表和邻接多重表)