Leetcode基础算法-递归算法

复制代码务必点击右侧的复制按钮, 不然他不一定完整


递龟

分治算法在这里Leetcode基础算法-分治算法
递归三原则:

一定有一个基础条件来退出递归
递归的过程要朝着上面的条件靠拢
要不断地调用自身

递归个人的看法就是套娃, 我可以无限调用我自己, 不过有一个或者多个终止的条件, 举个最简单的例子:斐波那契数列

def fib(x):
	if x == 1 or x == 2:
		return 1
	else:
		return fib(x-1)+fib(x-2)

这应该是递归最经典的用法了.我们知道fib数列是1, 1, 2, 3, 5, 8, 11从第三个数开始都是前两个数之和, 那么我们就进行两步走
第一步先把前两项特定条件写出来
第二步把后面的结果用前两项求和来表示
fib数列

当然前面的代码是针对于斐波那契数列的特定例子, 那么当我们遇到一般的递归问题应该怎么办呢?
还是一样的做法:
第一步先看看能不能把问题抽象为递归的形式, 即除了特定情况以外都能用前项的有限次运算推导出后项
第二步写出伪代码

def myfunc(x):
	if x为特殊情况:
		return 特殊情况的结果
	else:
		return myfunc(x)运算表示出后项, 但是要注意, x的变化情况一定是朝着x特殊情况靠近的

但是这会引出新的问题, 假设我们要计算100项fib
fib(100) = fib(99)+fib(98)
fib(99) = fib(98)+fib(97)
不难发现, 我们计算了两次fib(98)这会显著增加计算的时间, 因此我们可以考虑采用空间换时间的做法, 把已经计算过的fib(x)的值存在一个地方, 当我们下次遇到的时候可以直接从这个地方获取对应的值而不是重复计算.
举个栗子:
当我们不使用缓存来计算fib(40)大约需要24秒钟
无缓存计算fib(40)
现在让我们创建一个缓存用来存储已经计算过的结果
有缓存
计算时间相差了接近八万倍

509. 斐波那契数

上面已经讲的很清楚了.就不多赘述了

class Solution:
    def __init__(self):
        self.fib_dic = {}
    def fib(self, n: int) -> int:
        if n in self.fib_dic:
            return self.fib_dic[n]
        if n == 0:
            result = 0
        elif n == 1:
            result = 1
        else:
            result = self.fib(n-1) + self.fib(n-2)
        self.fib_dic[n] = result
        return result

104. 二叉树的最大深度

个人思路:
二叉树的节点深度总是等于max(左节点最大深度, 右节点最大深度)+1
左右节点的子节点情况和上面完全一样.直到全部为空, 则没有子节点
maxDepth(root)=max(maxDepth(root.left), maxDepth(root.right)+1
判定是否到达最大深度
root is None

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        if root is None:
            return 0
        else:
            result = max(self.maxDepth(root.left), self.maxDepth(root.right))+1
            return result

70.爬楼梯

个人思路:
第一层一共只有一种方法,
第二层一共只有两种方法,
第三层只能是从第一层或者第二层上来, 所以和fib其实是一样的

class Solution:
    def __init__(self):
        self.stairs_dic = {}
    def climbStairs(self, n: int) -> int:
        if n in self.stairs_dic:
            return self.stairs_dic[n]
        if n == 1:
            return 1
        elif n == 2:
            return 2
        else:
            result = self.climbStairs(n - 1) + self.climbStairs(n - 2)
        self.stairs_dic[n] = result
        return result

226. 翻转二叉树

个人思路:
和前面求二叉树的最大深度类似,
递归反转左节点和右节点
然后交换当前节点的左节点和右节点
再返回当前节点即可

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if root is None:
            return None
        left = self.invertTree(root.left)
        right = self.invertTree(root.right)
        
        root.left = right
        root.right = left

        return root

206. 反转链表

链表(Linked List)是一种常见的数据结构, 用于存储有序的元素。与Python内置的列表(list)不同, 链表的元素并不保存在连续的内存位置, 而是通过指针将它们连接在一起。

链表的结构:

链表由一系列节点(Node)组成, 每个节点包含两部分:

数据部分(val):存储节点的值。
指针部分(next):存储指向下一个节点的引用(即指针)。
链表的第一个节点称为头节点(head), 最后一个节点的next为None, 表示链表结束。这是一个最简单的链表结构, 如果我们想要创建一个简单的链表1->2->3

# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

node1 = ListNode(1)  # 第一个节点
node2 = ListNode(2)  # 第二个节点
node3 = ListNode(3)  # 第三个节点

node1.next = node2  # 让第一个节点指向第二个节点
node2.next = node3  # 让第二个节点指向第三个节点

链表的基础操作:

插入:可以在链表的任意位置插入一个节点。
删除:可以删除链表中的任意节点。
查找:从头开始逐个节点查找某个元素。
遍历:顺序访问链表的每个节点。

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next


node1 = ListNode(1)  # 第一个节点
node2 = ListNode(2)  # 第二个节点
node3 = ListNode(3)  # 第三个节点

node1.next = node2  # 让第一个节点指向第二个节点
node2.next = node3  # 让第二个节点指向第三个节点

# 插入 4 变成 1->2->4->3
node4 = ListNode(4)  # 先创建第四个节点
node2.next = node4
node4.next = node3  # 修改2和3节点对应的next就完成了插入

# 删除 4 变成 1->2->3
node2.next = node3


#查找元素
def find_value(head, value):
    current = head  # 从链表的头节点开始
    while current:  # 遍历链表, 直到到达末尾
        if current.val == value:  # 找到元素
            return True
        current = current.next  # 继续下一个节点
    return False  # 没找到


# 遍历链表
def traverse_linked_list(head):
    current = head  # 从头节点开始遍历
    while current:  # 遍历到最后一个节点
        print(current.val, end=" -> " if current.next else "\n")
        current = current.next  # 移动到下一个节点


traverse_linked_list(node1)
print(find_value(node1, 2))  # 输出 True
print(find_value(node1, 5))  # 输出 False

递归终止条件:当链表到达最后一个节点时, 它成为反转后链表的头节点。回溯过程中, 逐步将链表的指针反转, 最终返回这个反转后的链表

逐步反转指针:当递归到一个节点 head 时, 首先处理它后面的部分, 假设剩余部分已经反转了。在回溯时, 让 head.next 指向 head, 并且将 head.next 设为 None, 避免出现循环

class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # 如果链表为空, 则返回空链表, 如果链表next为空, 则说明到达了链表末尾, 那么返回末尾的链表
        if not head or not head.next:
            return head
        # 递归找到最后的节点作为new_head    
        new_head = self.reverseList(head.next)
        head.next.next = head
        head.next = None
        return new_head

链表是 1 -> 2 -> 3 -> 4 -> 5

递归展开:

reverseList(1) 调用 reverseList(2)
reverseList(2) 调用 reverseList(3)
reverseList(3) 调用 reverseList(4)
reverseList(4) 调用 reverseList(5)
reverseList(5) 返回 5, 因为 5 是最后一个节点

递归回溯时反转:

回溯到 4, 让 5.next = 4, 并把 4.next = None, 现在链表变成 5 -> 4
回溯到 3, 让 4.next = 3, 并把 3.next = None, 现在链表变成 5 -> 4 -> 3
回溯到 2, 让 3.next = 2, 并把 2.next = None, 现在链表变成 5 -> 4 -> 3 -> 2
回溯到 1, 让 2.next = 1, 并把 1.next = None, 现在链表变成 5 -> 4 -> 3 -> 2 -> 1

0092. 反转链表 II

我的想法是,先找到左右边界,然后直接调用上一题的做法,把中间部分反转,再拼接回去

class Solution:

    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # 如果链表为空, 则返回空链表, 如果链表next为空, 则说明到达了链表末尾, 那么返回末尾的链表
        if not head or not head.next:
            return head
        # 递归找到最后的节点作为new_head
        new_head = self.reverseList(head.next)
        head.next.next = head
        head.next = None
        return new_head

    def reverseBetween(
        self, head: Optional[ListNode], left: int, right: int
    ) -> Optional[ListNode]:
        if left == right:
            return head

        pre = None
        current = head

        idx = 1
        # 左边界
        while idx < left:
            pre = current
            current = current.next
            idx += 1

        tail = current

        # 右边界
        while idx < right:
            current = current.next
            idx += 1

        next_node = current.next  # 记录右边界的下一个节点
        current.next = None  # 将当前节点的 next 置为 None

        # 反转从 left 到 right 的子链表 调用前面一题的函数
        new_head = self.reverseList(tail)  # 反转

        # 连接反转后的子链表与原链表
        if pre:
            pre.next = new_head  # 将前驱节点连接到新头节点
        tail.next = next_node  # 反转部分的尾节点连接到 next_node

        # 如果 pre 仍为 None,说明 left 为 1,返回新头节点
        return new_head if pre is None else head

779. 第K个语法符号

0
01
01 10
01 10 10 01
01 10 10 01 10 01 01 10

从第五行开始
第n行 = n-1行 + n-1行翻转
如果k小于了这行的中点,那k一定就是和上一行的位置结果一样

class Solution:
    def kthGrammar(self, n: int, k: int) -> int:
        if n == 1:
            return 0
        #如果k小于了这行的中点,那k一定就是和上一行的位置结果一样
        else:
            mid = 2**(n-2)
            if k <= mid:
                return self.kthGrammar(n-1,k)
            else:
                return 1 - self.kthGrammar(n-1,k - mid)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值