动态规划
动态规划精讲1-题目列表
问题的特点
最优子结构:原问题的最优解可以通过子问题的最优解推出。一般使用状态转移方程描述子问题之间、子问题和原问题之间的转化关系。
重复子问题:求解不同子问题时需要进行大量相同的运算。动态规划中可采用记忆化递归或递推的方法减少重复问题的计算。如果子问题之间无重复,使用动态规划不能减少重复计算,效率和普通递归相同。
和其他算法的区别
分治
基本思想是减小子问题规模,分别求解子问题,然后综合子问题的解得出最终结果。分治法能够高效解决有最优子结构,但是没有重复子问题的问题。
贪心
最优子结构
原问题最优解中一定包含某个子问题最优解,但不一定包含上一步子问题的最优解,因此需要记录之前所有子问题的最优解
每一步的最优解都一定包含上一步子问题的最优解,因此无需记录上一步之前的最优解
重复子问题
大量重复子问题
只要处理每步贪心策略选择出的一个子问题
结果正确性
动态规划的本质是穷举法(需要考虑所有子问题,但可以避免重复的计算),因此结果一定正确
求得的是贪心意义上的最优解,不能保证正确
算法复杂度
复杂度高
复杂度低
解题思路
线性动态规划
线性动态规划的主要特点是状态的推导是按照问题规模从小到大依次递推的,较大规模的问题的解依赖较小规模的问题的解。 线性动态规划解决的问题主要是单串,双串,矩阵上的问题,因为问题规模可以用位置表示,并且位置的大小就是问题规模的大小。因此从前往后推位置就相当于从小到大推问题规模。
最长上升子序列 / 最长递增子序列(LIS): Longest Increasing Subsequence 最长连续序列(LCS): Longest Consecutive Sequence 最长连续递增序列(LCIS): Longest Continuous Increasing Subsequence 最长公共子序列(LCS): Longest Common Subsequence
子串:一定连续 子序列:可以不连续
1 2 3 4 dp[n] = [0 , n]区间上问题的解 dp[n] = f(dp[n - 1 ], dp[n - 2 ], ..., dp[0 ])
线性DP-LIS
最长上升子序列
状态定义: \(dp[i]\) 表示以\(nums[i]\) 结尾的最长上升子序列的长度。
状态转移: \[dp[i] = \left \{
\begin{aligned}
s.t. \quad j \in [0, i - 1],\; nums[j] < nums[i] \\
max(dp[i],\; dp[j] + 1) \\
\end{aligned} \right.\]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public int lengthOfLIS (int [] nums) { if (nums == null || nums.length == 0 ){ return 0 ; } int len = nums.length; int max_len = 1 ; int [] dp = new int [len]; Arrays.fill(dp, 1 ); for (int i = 1 ; i < len; i++){ for (int j = 0 ; j < i; j++){ if (nums[j] < nums[i]){ dp[i] = Math.max(dp[i], dp[j] + 1 ); } } max_len = Math.max(max_len, dp[i]); } return max_len; } }
最长递增子序列的个数
状态定义: \(dp[i]\) 表示以\(nums[i]\) 结尾的最长递增子序列的长度; \(count[i]\) 表示以\(nums[i]\) 结尾的最长递增子序列(长度相同)的组合个数。
转移方程: \[dp[i] = \left \{ \begin{aligned}
s.t. \quad j \in [0, i - 1],\; nums[i] > nums[j] \\
max(dp[i],\; dp[j] + 1) \\
\end{aligned} \right.\] \[count[i] = \left \{ \begin{aligned}
s.t. \quad j \in [0, i - 1],\; nums[i] > nums[j] \\
count[j] \quad dp[i] < dp[j] + 1 \\
sum(count[i],\; count[j]) \quad dp[i] = dp[j] + 1 \\
\end{aligned} \right.\]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Solution { public int findNumberOfLIS (int [] nums) { if (nums == null || nums.length < 1 ){ return 0 ; } int len = nums.length; int [] dp = new int [len]; int [] count = new int [len]; Arrays.fill(dp, 1 ); Arrays.fill(count, 1 ); int max = 1 ; for (int i = 0 ; i < len; i++){ for (int j = 0 ; j < i; j++){ if (nums[j] < nums[i]){ if (dp[j] + 1 > dp[i]){ dp[i] = dp[j] + 1 ; count[i] = count[j]; } else if (dp[j] + 1 == dp[i]){ count[i] += count[j]; } } } max = Math.max(max, dp[i]); } int res = 0 ; for (int i = 0 ; i < len; i++){ if (dp[i] == max){ res += count[i]; } } return res; } }
俄罗斯套娃信封问题
排序 + LIS,状态定义和转移方程同第300题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public int maxEnvelopes (int [][] envelopes) { if (envelopes == null || envelopes.length == 0 || envelopes[0 ].length == 0 ){ return 0 ; } int len = envelopes.length; Arrays.sort(envelopes, (o1, o2) -> { if (o1[0 ] != o2[0 ]){ return o1[0 ] - o2[0 ]; } else { return o2[1 ] - o1[1 ]; } }); int res = 1 ; int [] dp = new int [len + 1 ]; Arrays.fill(dp, 1 ); for (int i = 0 ; i < len; i++){ for (int j = 0 ; j < i; j++){ if (envelopes[i][1 ] > envelopes[j][1 ]){ dp[i] = Math.max(dp[i], dp[j] + 1 ); } } res = Math.max(res, dp[i]); } return res; } }
线性DP-最大子数组和
最大子序和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public int maxSubArray_1 (int [] nums) { if (nums == null || nums.length == 0 ){ return 0 ; } int res = Integer.MIN_VALUE, tmp = 0 ; for (int num : nums){ tmp += num; res = Math.max(res, tmp); tmp = tmp < 0 ? 0 : tmp; } return res; } public int maxSubArray_1 (int [] nums) { int pre = 0 , res = nums[0 ]; for (int num : nums){ pre = Math.max(pre + num, num); res = Math.max(res, pre); } return res; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Solution { class Status { public int lSum, rSum, mSum, iSum; public Status (int lSum, int rSum, int mSum, int iSum) { this .lSum = lSum; this .rSum = rSum; this .mSum = mSum; this .iSum = iSum; } } public Status getInfo (int [] arr, int l, int r) { if (l == r){ return new Status(arr[l], arr[l], arr[l], arr[l]); } int mid = (l + r) / 2 ; Status lSub = getInfo(arr, l, mid); Status rSub = getInfo(arr, mid + 1 , r); return pushUp(lSub, rSub); } public Status pushUp (Status l, Status r) { int lSum = Math.max(l.lSum, l.iSum + r.lSum); int rSum = Math.max(r.rSum, r.iSum + l.rSum); int mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum + r.lSum); int iSum = l.iSum + r.iSum; return new Status(lSum, rSum, mSum, iSum); } public int maxSubArray (int [] nums) { return getInfo(nums, 0 , nums.length - 1 ).mSum; } }
乘积最大子数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int maxProduct (int [] nums) { int res, max, min; res = max = min = nums[0 ]; for (int i = 1 ; i < nums.length; i++){ if (nums[i] < 0 ){ int tmp = max; max = min; min = tmp; } max = Math.max(max * nums[i], nums[i]); min = Math.min(min * nums[i], nums[i]); res = Math.max(res, max); } return res; } }
环形子数组的最大和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { public int maxSubarraySumCircular (int [] A) { int len = A.length; if (len == 1 ) return A[0 ]; int sum = A[0 ], total = A[0 ], maxSum = A[0 ]; for (int i = 1 ; i < len; i++){ if (sum >= 0 ){ sum += A[i]; } else { sum = A[i]; } maxSum = Math.max(sum, maxSum); total += A[i]; } sum = A[1 ]; int minSum = A[1 ]; for (int i = 2 ; i < len - 1 ; i++){ if (sum <= 0 ){ sum += A[i]; } else { sum = A[i]; } minSum = Math.min(minSum, sum); } return Math.max(maxSum, total - minSum); } }
最大子矩阵
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution { public int [] getMaxMatrix(int [][] matrix) { int row = matrix.length; int col = matrix[0 ].length; int [] vector = new int [col]; int [] res = new int [4 ]; int maxSum = matrix[0 ][0 ]; for (int i = 0 ; i < row; i++){ for (int k = 0 ; k < col; k++) vector[k] = 0 ; for (int j = i; j < row; j++){ for (int k = 0 ; k < col; k++) vector[k] += matrix[j][k]; int sum = 0 ; int start = 0 ; for (int c = 0 ; c < col; c++){ if (sum > 0 ){ sum += vector[c]; } else { sum = vector[c]; start = c; } if (sum > maxSum){ maxSum = sum; res[0 ] = i; res[1 ] = start; res[2 ] = j; res[3 ] = c; } } } } return res; } }
线性DP-打家劫舍
前缀和
区间动态规划
背包问题
状态压缩
计数问题
矩阵快速幂
数位动态规划
try 2
wow
try 3
wowow