递归乘法

604 阅读3分钟

考察内容:递归,二分

时间:2020-09-02 星期三

作者:guuzaa

掘金主页:🌏

题目描述 📃

题目链接 🔗,题目来自 leetcode

题目要求很简单,给定两个数 A B ,求 A*B 。不能使用 * 实现两个正整数的相乘,可以使用加号、减号和位移。

分析 💻

迭代解法

先抛开递归不谈,下面的式子很容易想到。

A×B=A+A++A=B+B++BA\times{B}=A+A+\dots+A=B+B+\dots+B

直接将 AB 或者 BA 相加即可。代码如下:

def multiply(self, A: int, B: int) -> int:
    res = 0;
    # 将B个A相加
    for i in range(1,B+1):
        res += A
        return res

思路很简单,当时问题很明显:当B很大的时候会超时。

时空复杂度分析

空间复杂是常量级别,时间复杂度是线性级别。

递归解法

递归就是不断缩减问题规模,直到达到递归出口。那么怎样才能缩减问题规模呢?

首先 A*B 好像可以化为 A // 2 * B + A // 2 * B ,分析之后发现有一个问题前面的式子在 A 是偶数的时候是成立的,但是奇数时,就不成立了。比如 3 * 4 就不等于 1 * 4 + 1 * 4。这个问题也很容易解决,做一个特判就行。当 A 是偶数的时候递归调用 multiply(A // 2 , B) + multiply(A // 2, B),奇数就递归调用 multiply(A // 2 , B) + multiply(A // 2, B) + B。这个问题迎刃而解,这也就是二分思想了。下一个问题,递归出口是什么?我们是通过不断让 A 整除2来缩减问题规模的,那么边界容易想到,当 A 缩减到 1 就直接返回 B 不就行了。所以有了下面的代码:

def multiply(self, A: int, B: int) -> int:
    if A == 1:
        return B
   
	if A % 2 == 1:  # 判断奇偶性,除了模2之外,还可以用按位与,见下面的代码
        return self.multiply(A // 2, B) + self.multiply(A // 2, B) + B
    else:
        return self.multiply(A // 2, B) + self.multiply(A // 2, B)

写完之后发现代码写麻烦了,调用了四次 self.multiply(A // 2, B)。其实只要开一个变量存储 self.multiply(A // 2, B)结果就行了,不用调用多次。代码改进如下:

def multiply(self, A: int, B: int) -> int:
    if A == 1:
        return B
   	dummy = self.multiply(A // 2, B)
	if A & 1 == 1:  # 奇数
        return dummy + dummy + B
    else:
        return dummy + dummy

题目中说可以用位移操作,既然说到了就一定能用到。

我忽略了一个知识点,n << 1 表示 n 二进制表示向右移动了一位(稍微对二进制有了解的同学,应该都知道右移的意思,这里不再展开),而它恰恰等同于 n * 2,所以上面的代码还可以再简化。

def multiply(self, A: int, B: int) -> int:
    if A == 1:
        return B
   	dummy = (self.multiply(A // 2, B)) << 1
	if A & 1 == 1:  # 奇数
        return dummy + B
    else:
        return dummy

时空复杂度分析

用到了递归,所以空间复杂度是 log 级别;二分思想,时间复杂度也是 log 级别。

总结 📕:

  • 这个题目跟快速幂有点像,都是借助二分和递归,以后找机会说一下快速幂。
  • 递归有两要素:递归出口和递归体。拿到递归题目,一定要找好这两要素。
  • 我对递归的时空复杂度分析还有些不熟,还需要继续加强这方面的训练。

全文完