Leetcode 专题训练 递归和分治(一)

简介

在这里插入图片描述

分而治之的算法设计思想

在这里插入图片描述

递归与分治

在这里插入图片描述

递归函数的设计思想:分而治之(减而治之)

在这里插入图片描述

自顶向下地解决问题

在这里插入图片描述
在这里插入图片描述

为什么需要使用栈?

这一步在我看剑指offer的时候,也理解到了。
在这里插入图片描述

拆分的时候「先走出去」,合并的时候「再走回来」

在这里插入图片描述

总结

在这里插入图片描述

自顶向下与自底向上

在这里插入图片描述

使用「递归」与「循环」实现的求阶乘函数对比

我们实现一个函数,输入 n ,输出 n 的阶乘。为了简化描述,我们不考虑输入为负整数,且输出发生整型溢出的情况。也就是说,我们假设输入是合法的,并且计算阶乘得到的结果的整数在 32 位整型范围之内。

public int factorial(int n) {
    if (n == 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

注意:在递归方法调用返回以后,还能够做一些事情。就以上面的代码为例:factorial(n - 1) 返回以后,我们将返回的结果值和 n 相乘。以上的代码等价于下面这段代码:

public int factorial(int n) {
    if (n == 1) {
        return 1;
    }
    int res = factorial(n - 1);
    // 在这里还能够执行一次操作,实现「分治思想」里「合并」的逻辑
    return n * res;
}

这种「能够在递归调用返回的时候做一些事情」对应于「分治思想」的第 3 步「合并」的过程。「在上一层「递归」调用结束以后,我们可以实现一些逻辑」这一点是我们在学习递归的过程当中容易被忽略的,要重视这个细节的理解,才能够更好地理解,并且应用递归。
在这里插入图片描述

/**
  * @param n
  * @param res 递归函数上一层的结果,由于求的是阶乘,一开始需要传入 1
  * @return
  */
public int factorial(int n, int res) {
    if (n == 1) {
        return res;
    }
    return factorial(n - 1, n * res);
}

在这里插入图片描述
使用循环计算 5!
如果我们知道了一个问题最开始的样子,就可以通过递推的方式一步一步求解,直到得到了我们想要的问题的解,相对于递归而言,这样的思考方向是「自底向上」的,计算 5!5! 我们还可以使用循环实现。代码如下:

public int factorial(int n) {
    int res = 1;
    for (int i = 2; i <= n; i++) {
        res *= i;
    }
    return res;
}

友情提示:如果大家学习过「动态规划」的朋友就会知道,动态规划有两个思考的方向:一个是记忆化递归,另一个是递推。记忆化递归对应了「自顶向下」的解决问题的方向,递推对应了「自底向上」的逐步求解问题的方向。

很明显:「自底向上」思考问题的方向直接从一个问题的「源头」开始,逐步求解。相比较于「自顶向下」而言:

  • 少了一层一层拆分问题的步骤;
  • 也不需要借助一个数据结构(栈)记录拆分过程中的每一个子问题。

递推与递归

在这里插入图片描述

总结与练习

「自顶向下」与「自底向上」分别对应了我们解决问题的两种思考路径:

自顶向下:直接面对问题,直接解决问题;
自底向上:从这个问题最开始的样子出发,一点一点「逐步演化」成我们最终想要解决的问题的样子。

50. Pow(x, n)
在这里插入图片描述
我的代码,没有用到递归啊。。。。不过我因为昨天看了剑指offer,现在终于开始注意边界问题和代码鲁棒性了,现在看自己的代码,比之前还是进步不少的:

def myPow(self, x: float, n: int) -> float:
        negative_flag = False
        if x == 0: return 0
        elif n == 0: return 1
        elif n < 0: negative_flag = True
        res = 1

        for _ in range(abs(n)):
            res *= x
        
        if negative_flag: return 1 / res
        else: return res

大数那里过不去,时间超了:
在这里插入图片描述
写了递归,也是大数那里过不去:

def myPow(self, x: float, n: int) -> float:
        negative_flag = False
        if x == 0: return 0
        elif n == 0: return 1
        elif n < 0: negative_flag = True
        if n == 1: return x
        if negative_flag: return 1 / (x * self.myPow(x, abs(n)-1))
        else: return x * self.myPow(x, n-1)

下面是答案:
在这里插入图片描述
根据答案的思路,我写的快速幂➕递归,还是过不了大数:

def myPow(self, x: float, n: int) -> float:
        neg_flag = False
        if x == 0: return 0
        elif n == 0: return 1
        elif n < 0: neg_flag = True
        
        if n % 2 != 0: 
            if neg_flag: return 1 / (self.myPow(x, abs(n)//2)**2 * x)
            else: return self.myPow(x, n//2)**2 * x
        else: 
            if neg_flag: return 1 / (self.myPow(x, abs(n)/2)**2)
            else: return self.myPow(x, n/2)**2

注意这里用y*y代替了y**2,就可以过大数,不知道为什么

class Solution:
    def myPow(self, x: float, n: int) -> float:
        def quickMul(N):
            if N == 0:
                return 1.0
            y = quickMul(N // 2)
            return y * y if N % 2 == 0 else y * y * x
        
        return quickMul(n) if n >= 0 else 1.0 / quickMul(-n)

在这里插入图片描述
剑指 Offer 65. 不用加减乘除做加法

在这里插入图片描述
之前做过一遍,复习过一遍,知道用位运算,但还是不会写。
直接看答案吧,这说明我的做题效率不高,虽然刷了题,但是刷完了,复习了一遍,还是写不出来,我可能是猪。只有自己大脑加工过的东西,才会记住,别人的思路,直接抄过来,永远都不会。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下面是根据大佬思路,尝试写的代码:

def add(self, a: int, b: int) -> int:
        n = a ^ b
        y = a & b << 1
        return n + y

在这里插入图片描述
下面是大佬的代码:

def add(self, a: int, b: int) -> int:
        x = 0xffffffff
        a, b = a & x, b & x
        while b != 0:
            a, b = (a ^ b), (a & b) << 1 & x
        return a if a <= 0x7fffffff else ~(a ^ x)

在这里插入图片描述
递归:

class Solution:
    def add(self, a: int, b: int) -> int:
        if b == 0: return a 
        return add(a ^ b, (a & b) << 1 )

面试题 08.05. 递归乘法
在这里插入图片描述
递归,看完《编程之美》后,发现自己的思路开始变了,开始尝试自己去思考答案了,而且要有信心自己可以写出来答案,如果自己现在囫囵吞枣,那么笔试或者面试的时候,那些题一定也会用同样的方法对待我们。要像稻盛和夫所言:敬畏的做每一道题,把每一道题的思路想通,再做。而且开始注意边界条件和鲁棒性了。

def multiply(self, A: int, B: int) -> int:
        if A <= 0 or B <= 0: return -1
        if B == 1: return A
        return A + self.multiply(A, B-1)

在这里插入图片描述
可以看书,递归的调用,是函数的调用,每次函数调用,计算机会给函数和参数开辟额外的内存空间,所以占用的空间会比较大。相当于栈:后进先出。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值