考察内容:递归,二分
时间:2020-09-02 星期三
作者:guuzaa
掘金主页:🌏
题目描述 📃
题目链接 🔗,题目来自 leetcode。
题目要求很简单,给定两个数 A B ,求 A*B 。不能使用 * 实现两个正整数的相乘,可以使用加号、减号和位移。
分析 💻
迭代解法
先抛开递归不谈,下面的式子很容易想到。
直接将 A 个 B 或者 B 个 A 相加即可。代码如下:
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 级别。
总结 📕:
- 这个题目跟快速幂有点像,都是借助二分和递归,以后找机会说一下快速幂。
- 递归有两要素:递归出口和递归体。拿到递归题目,一定要找好这两要素。
- 我对递归的时空复杂度分析还有些不熟,还需要继续加强这方面的训练。
全文完