本篇为Datawhale组队学习计划第21期LeetCode精选题目组Task08学习笔记。
初学,时间有点仓促,很多解法没有详细分析,未来可能会修改,见谅。
Datawhale学习文档:
https://github.com/datawhalechina/team-learning-program/tree/master/LeetCodeTencent
(昨天忘记改标题了)
062 不同路径
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/unique-paths
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
思路
参考https://leetcode-cn.com/problems/unique-paths/solution/dong-tai-gui-hua-by-powcai-2/
看作一道数学题,机器人一共要向右走m-1步和向下走n-1步,并且两种走法互斥。要在m+n-2次选择中,确定m-1个向右的时机,路径
C
m
+
n
−
2
m
−
1
C_{m+n-2}^{m-1}
Cm+n−2m−1(或者n-1次向下,结果相同)。
主要考察还是动态规划:
转移方程 dp[i][j] = dp[i-1][j] + dp[i][j-1]
并设dp[i][0] = dp[0][j] = 1
根据题解思路优化,还可以只记录dp[i-1][j]和dp[i][j-1]的数据(和“互斥”的思想也有类似)。
Python 实现
(动态规划仅放原始版本,优化代码请看原链接)
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
return comb(m + n - 2, m- 1)
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
dp = [[1]*n] + [[1]+[0] * (n-1) for _ in range(m-1)]
#print(dp)
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[-1][-1]
# 作者:powcai
# 链接:https://leetcode-cn.com/problems/unique-paths/solution/dong-tai-gui-hua-by-powcai-2/
# 来源:力扣(LeetCode)
# 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
070 爬楼梯
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/climbing-stairs
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
- 1 阶 + 1 阶 + 1 阶
- 1 阶 + 2 阶
- 2 阶 + 1 阶
(数学题,还记得是斐波那契数列)
f(n)=f(n-1)+f(n-2),对f(n),最后一步可能是一级或两级,所以将前面两项加起来得到
纯数学方法:斐波那契数列公式推导,可参考百度百科。
动态规划:dp(n)=dp(n-1)+dp(n-2)
初始化dp[0]=1,dp[1]=1。
关于dp[0]的赋值,有人认为从题目n为正整数出发,不应考虑dp[0],直接dp[1] = 1,dp[2] = 2,range(3,n+2)递推,以符合dp[i]的定义。个人觉得数列本身(0),1,1,2,…,结合递推公式可以认为dp[2] = 2和dp[0]=1是等价的,顺手就行,也不必为dp[0]强行赋予意义。
关于dp[0]的其他讨论可以移步精选题解的评论区。此外,这篇还讨论了3,4,5…步的情况的扩展:https://leetcode-cn.com/problems/climbing-stairs/solution/70-pa-lou-ti-dong-tai-gui-hua-jing-dian-a3na7/
Python实现
class Solution:
def climbStairs(self, n: int) -> int:
dp = [0] * (n + 1)
dp[0] = 1;
dp[1] = 1;
for i in range(2,n+1): # 注意(2,n+1)或(3,n+2)
dp[i] = dp[i - 1] + dp[i - 2];
return dp[n];
078 子集
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/subsets
给你一个整数数组 nums ,返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。
示例:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
输入:nums = [0]
输出:[[],[0]]
提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
思路
回溯。题解中有两篇关于回溯题目合集https://leetcode-cn.com/problems/subsets/solution/hui-su-suan-fa-by-powcai-5/
两种做法:对每个数字,都有选、不选两种;
https://leetcode-cn.com/problems/subsets/solution/shou-hua-tu-jie-zi-ji-hui-su-fa-xiang-jie-wei-yun-/
其实回溯算法关键在于:不合适就退回上一步
然后通过约束条件, 减少时间复杂度.
回溯与递归:
深度优先搜索是递归实现的,是要搜索整个二叉树的,在这个搜索的基础上,再加点回溯/剪枝的操作就是这一类排列组合的题了,是这个关系
(官方题解下的评论https://leetcode-cn.com/problems/subsets/solution/zi-ji-by-leetcode-solution/601898)
Python实现
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
res = []
n = len(nums)
def helper(i, tmp): # 辅助函数
res.append(tmp)
for j in range(i, n):
helper(j + 1,tmp + [nums[j]] )
helper(0, [])
return res
# 作者:powcai
#链接:https://leetcode-cn.com/problems/subsets/solution/hui-su-suan-fa-by-powcai-5/
#来源:力扣(LeetCode)
#著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
补充知识
斐波那契数列第n项的求解方法:
- n比较小的时候,可以直接使用过递归法求解,不做任何记忆化操作,时间复杂度是O(2^n),存在很多冗余计算。
- 一般情况下,我们使用「记忆化搜索」或者「迭代」的方法,实现这个转移方程,时间复杂度和空间复杂度都可以做到O(n)。
- 为了优化空间复杂度,我们可以不用保存f(x−2)之前的项,我们只用三个变量来维护f(x)、f(x−1)和f(x−2),你可以理解成是把「滚动数组思想」应用在了动态规划中,也可以理解成是一种递推,这样随着n的不断增大O(n)可能已经不能满足我们的需要了,我们可以用「矩阵快速幂」的方法把算法加速到O(logn)。
- 我们也可以把n代入斐波那契数列的通项公式计算结果,但是如果我们用浮点数计算来实现,可能会产生精度误差。
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/climbing-stairs/solution/pa-lou-ti-by-leetcode-solution/