Self-Learned Algorithms - 03
This is my learning note about algorithms.
Reference
- 《小浩算法》) - 动态规划系列
70.爬楼梯
https://leetcode-cn.com/problems/climbing-stairs/
题解
- 很简单的dp题,不过在评论区找到很多有意思的解法
解法
- 递归法:容易超时,python可以加个缓存装饰器
| 1 | 
 | 
- DP1:新建一个字典或者数组来存储以前的变量,空间复杂度O(n)
| 1 | dp = {} | 
- DP2:只存储前两个元素,减少了空间,空间复杂度O(1)
| 1 | if n < 3: return n | 
- 斐波那契数列
| 1 | import math | 
- 面向测试用例编程(笑死)
| 1 | a = [1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,5702887,9227465,14930352,24157817,39088169,63245986,102334155,165580141,267914296,433494437,701408733,1134903170,1836311903] | 
- 利用排列组合C(n,m)=n!/m!(n-m)!,可参考该解法
| 1 | def fac(self, num): | 
53.最大子序和
https://leetcode-cn.com/problems/maximum-subarray/
题解
- 设定状态dp[i]为以nums[i]为结尾的连续子数组最大和
- 结果为i的情况下nums[i]必定为正数
- 如果dp[i-1]为负数,那么dp[i]=nums[i]
解法
- 一边遍历一边计算:nums[i-1]并不是数组前一项的意思,而是到前一项为止的最大子序和,和0比较是因为只要大于0,就可以相加构造最大子序和。
| 1 | for i in range(1, len(nums)): | 
- 正经dp
| 1 | dp = nums[:] | 
简化版:只用一个值保存结果
| 1 | pre = nums[0] | 
300.最长上升子序列
https://leetcode-cn.com/problems/longest-increasing-subsequence/
题解
- LIS可能是连续的,也可能是非连续的(不严格审题以为$O(N)$能解决的)
- “上升”是“严格上升”,类似于 [2, 3, 3, 6, 7]这样的子序列是不符合要求的
解法
- 动态规划:dp[i]的值代表以nums[i]为结尾的最长子序列长度,每轮计算新dp[i]时,遍历[0,i)列表区间。时间复杂度$O(N^2)$。
| 1 | if not nums: return 0 | 
- 二分查找:是否可以通过重新设计状态定义,使整个dp为一个排序列表;这样在计算每个dp[k]时,就可以通过二分法遍历[0,k)区间元素,将此部分复杂度由$O(N)$降至$O(logN)$。
| 1 | tails, res = [0] * len(nums), 0 | 
二分查找的运行时间居然比普通动态规划的快20倍,惊了!
120.三角形最小路径和
https://leetcode-cn.com/problems/triangle/
题解
- 常规动态规划题目,需要考虑一下边界的问题。从上到下和从下到上都可以做。
解法
- 运气解法(又名reinforcement learning)(笑死):随机的瞎走,走上个10000遍,只保留步数最小的那一个
| 1 | sum = 9999999 | 
- 原地修改自底而上
| 1 | for i in range(len(triangle) - 1, 0, -1): | 
- 自上而下:状态转移方程中的dp[i-1][j-1]中的j-1,当j=0时,其效果等同于dp[i-1][-1],即无穷大值float('inf');同理,j=i时dp[i-1][j]也表示无穷大值,因此不需要再进行边界判别。
| 1 | dp = [[float('inf')] * len(triangle) for _ in range(len(triangle))] | 
64.最小路径和
https://leetcode-cn.com/problems/minimum-path-sum/
题解
- 和上一道题差不多,状态转移也只有两个。
- (思考题)路径和类问题和之前的子序列类问题有何区别?
解法
- 在原数组上进行记忆
| 1 | for i in range(len(grid)): | 
- 反向动态规划:边界条件与正向相似
| 1 | x = len(grid[0]) | 
| 1 | dct = {} | 
| 1 | dct = defaultdict(lambda: float("inf")) | 
两种记忆化搜索还需要再详细思考一下
198.打家劫舍
https://leetcode-cn.com/problems/house-robber/
题解
- 失业程序员转职做小偷(?)
解法
- 动态规划
| 1 | if len(nums) == 0: return 0 | 
空间优化(奇怪的是时间减半但内存消耗增加了)
| 1 | prev = 0 |