Leetcode 腾讯50题 day8
今天的题解我分享了一些使用Python做算法题的小技巧,涉及到
- 组合数求解
math.comb()
- 递归调用的记忆化存储装饰器
@lru_cache
- 求解字符串排列组合的包
itertools
如果有使用Python做算法题的同学可以看看我的题解中的分享。另外,练习算法的目的还是在锻炼思维与编码能力,这些函数只是巧劲,有时候并不能有效降低时间复杂度,不过这些函数笔试的时候一般都可以使用(math.comb函数未必),掌握了会在写代码的过程中更加手到擒来。
No.62 不同路径
题目描述
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
输入:m = 3, n = 7
输出:28
我的思路
在自己模拟的时候可以感觉到答案与输入的数字之间存在着某种规律,所以可以研究一下是否真的有规律。
以7行3列为例,要从(1,1)走到(3,7),且只能向右或向下前进,因此每一步行走对于目标位置来说都是有效的,即都离目标点更近了一步,因此走到目标点的所需的步长对于每组输入来说都是确定的,在这个例子里,所需的步长是8,即(7-1)+(3-1)=8。也就是说,不管用什么方法走,只要向右移动6格,且向下移动2格,那么左上角的点就会移动到右下角。而不同走法的区别就在于什么时候向下和什么时候向右。因此,这是一个数学的组合数问题,求C82就是答案,C82意味着从必须要走的8个格子中选取两个,这两个是向下走的,那么另外6个就是向右走的,因此就可求出答案。
实际上读题的时候看到这类走路的题目时感觉大概有三种思路可以做,广度优先搜索、动态规划和数学规律,是否真的可以用这些方法需要经过自己验证再写代码,而如果是比较容易计算的数学规律,在验证出来后是很好写代码的,所以我优先选择思考是否有数学规律可以解决这道题,进而发现了可以用组合数来做。
一些分享
另外再分享我在实现代码时的一些发现。下列的代码是我自己根据题目实现的组合数的计算代码。在查阅相关资料时发现,python的math
包已经实现了组合数,可以使用math.comb(8, 2)
来计算C82。但要注意,这个函数仅在python版本>=3.8
的版本中支持,我也尝试了下leetcode目前是支持这个函数的,注意力扣中国给出的版本链接没有更新,其中显示的并不是leetcode最新的各语言的运行环境,可以查看leetcode官网看当前支持的语言版本,下图显示的是官网截的部分语言的版本,注意mysql是8.0版本,和5+版本区别较大,大家刷sql题要注意。另外,python的math包还包括了求阶乘、最大公因数、ceil和floor这些函数,都很常用,一般笔试也都可以使用。
代码
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
a = m + n - 2
b = min(m-1, n-1)
top = 1
bottom = 1
l = b
for i in range(l):
top *= a
a -= 1
bottom *= b
b -= 1
return top // bottom
No.70 爬楼梯
题目描述
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
我的思路
每道题上手都可以先通过手算试试题目的过程是怎么样的。这道题可以看出对于n阶台阶,只能由n-1阶台阶上一步,或者n-2阶台阶上两步,才能走到,也就是
f
(
n
)
=
f
(
n
−
1
)
+
f
(
n
−
2
)
f(n) = f(n-1) + f(n-2)
f(n)=f(n−1)+f(n−2)
因此可以通过求斐波那契数列的递归方法求解。但是如果直接用递归的方法实现,会产生大量重复的计算,比如计算f(10),就会计算2次f(8),4次f(7),8次f(6)…因此时间复杂度会变成O(2n),为了避免重复计算,可以将算过的结果保存起来,在python里可以使用@lru_cache
这个装饰器,这个装饰器使用了LRU缓存算法,将函数算过的结果缓存下来,可以避免重复计算。如果不用递归的话也可以通过数组递推求解,两种实现方法时间复杂度都为O(n)。
另外,leetcode官方题解中还提出了一个矩阵快速幂,有线性代数基础的同学可以学习一下矩阵快速幂算法,速度更快,为O(logN)
一些分享
关于@lru_cache
这个装饰器我也是在做leetcode周赛的时候,每次结束之后都会看看排名靠前的ak大佬们是如何解决的,因为我主要用python做所以会挑一些同样用python的解答看一看,才发现了lru_cache这个宝藏,也许这不是最优解,但是不论笔试还是周赛,在做得对、时间复杂度小以外,写代码的速度也是很重要的一点,递归的解法通常看起来直观而且码字速度快,但往往会因为重复调用而爆时,这个时候lru_cache就尤为有用。可以看官方链接了解其更多用法。
代码
class Solution:
@lru_cache
def climbStairs(self, n: int) -> int:
if n==1:
return 1
elif n==2:
return 2
else:
return self.climbStairs(n-1) + self.climbStairs(n-2)
No.78 子集
题目描述
给你一个整数数组 nums ,返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
我的思路
回溯法的题解比较多,可见leetcode官方题解
学习回溯法是这个题目的关键,我这里举的方法是一个偷懒的做法,不过是笔试中可以使用的,建议大家在练习完回溯法之后再用调包的方法实现一下,熟练掌握python的排列组合调用。
我这里分享python中的可以求排列组合的一个包itertools
,其中涉及排列组合的包括如下四个函数
其中
- product用来计算笛卡尔积
- permutations计算全排列
- combinations计算组合
- combinations_with_replacement计算有放回的组合
代码
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
res = []
from itertools import combinations
for i in range(len(nums)+1):
res = res + list(combinations(nums, i))
return res