遞歸和動態規划 算法視頻QQ_1603159172
從Triangle這個問題說起:
題目:
Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.
Example:
Given the following triangle:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).
四種解法
遇到這道題,老師說開始先給出暴力解法之后進行優化是可以的,不一定一開始就給出最優解,想出來一個暴力解法覺得傻就不說話。。。
- DFS:Traverse
其中時間復雜度為O(2^{n}), 其中n為Triangle中的層數(122*2…)進去n次,每次調用2次自己,所以O(2^{n-1}),近似為O(2^{n}), 注意使用Traverse的方法的時候,結果是當作函數參數傳遞的。
- DFS:Divide and Conquer
時間復雜度依然為O(2^{n}), 其中n為Triangle中的點數,同使用Traverse的方法不同的是,Divide and Conquer 的結果是作為函數返回值而不是函數參數,要不然沒法conquer。
–DFS:Divide and Conquer 加 memorization
時間復雜度為O(n^{2}),其中n為Triangle的層數。
其中注意,如果求最小值的時候,初始化往往是int或其類型的最大值;反之如果求最大值,初始化往往為最小值
記憶法的分治時間復雜度計算:
a=一共多少個點
b=每個點處理的時間復雜度
c=每個點訪問幾次
分治法的總時間復雜度=a*b*c
–Traditional Dynamic Programming
// version 0: top-down public class Solution { /** * @param triangle: a list of lists of integers. * @return: An integer, minimum path sum. */ public int minimumTotal(int[][] triangle) { if (triangle == null || triangle.length == 0) { return -1; } if (triangle[0] == null || triangle[0].length == 0) { return -1; } // state: f[x][y] = minimum path value from 0,0 to x,y int n = triangle.length; int[][] f = new int[n][n]; // initialize f[0][0] = triangle[0][0]; for (int i = 1; i < n; i++) { f[i][0] = f[i - 1][0] + triangle[i][0]; f[i][i] = f[i - 1][i - 1] + triangle[i][i]; } // top down for (int i = 1; i < n; i++) { for (int j = 1; j < i; j++) { f[i][j] = Math.min(f[i - 1][j], f[i - 1][j - 1]) + triangle[i][j]; } } // answer int best = f[n - 1][0]; for (int i = 1; i < n; i++) { best = Math.min(best, f[n - 1][i]); } return best; } } //Version 1: Bottom-Up public class Solution { /** * @param triangle: a list of lists of integers. * @return: An integer, minimum path sum. */ public int minimumTotal(int[][] triangle) { if (triangle == null || triangle.length == 0) { return -1; } if (triangle[0] == null || triangle[0].length == 0) { return -1; } // state: f[x][y] = minimum path value from x,y to bottom int n = triangle.length; int[][] f = new int[n][n]; // initialize for (int i = 0; i < n; i++) { f[n - 1][i] = triangle[n - 1][i]; } // bottom up for (int i = n - 2; i >= 0; i--) { for (int j = 0; j <= i; j++) { f[i][j] = Math.min(f[i + 1][j], f[i + 1][j + 1]) + triangle[i][j]; } } // answer return f[0][0]; } }
此題中的時間復雜度的a為O(n^{2}), b和c均為O(1), 最后的結果為O(n^{2})。
討論:
算法視頻QQ_1603159172
動態規划和分治
1.動態規划是一種算法思想, 是高於算法的. 而分治的記憶化搜索是實現動態規划的一種手段.
2.那么什么是動態規划呢?
-就感覺上來說, 動態規划的是"一層一層來", 基於前一個狀態推出現在的狀態.
3.動態規划為什么會快呢?
-因為減少了很多不必要的重復計算.
4.動態規划和分治的區別?
-動態規划約等於分治+記憶化, 因為有了記憶化, 所以算過的直接用就行, 就不用再算一遍了.
5.動態規划有方向性,不回頭,不繞圈兒,無循環依賴。
什么時候使用動態規划:
動態規划適合把暴力時間復雜度為指數型的問題轉化為多項式的復雜度,即O(2^{n})或O(n!) 轉化為O(n^{2})
1.求最大最小的問題
2.判斷可不可行,存不存在
3.統計方案個數
什么時候不用動態規划:
- 本來時間復雜度就在O(n^{2})或者O(n^{3})的問題繼續優化,因為不怎么能優化…(100%不用DP)• 動態規划擅長與優化指數級別復雜度(2^n,n!)到多項式級別復雜度(n^2,n^3)
• 不擅長優化n^3到n^2
- 求!!所有的,具體的!!方案而不是方案個數,要用DFS而不是DP(99%不用DP), 除了N皇后
- 輸入數據是一個集合而非序列(70~80%不用DP),除了背包問題。
動態規划的實現方式
算法視頻QQ_1603159172
多重循環的兩種方式:
- 自底向上
- 自頂向下兩種方法沒有太大優劣區別
思維模式一個正向,一個逆向
自底向上代碼如下:
時間復雜度: O(n^{2})
空間復雜度:O(n^{2}),看開了一個多大的數組就是結果,莫想的太復雜…
自頂向上代碼如下:
時間復雜度依舊: O(n^{2})
空間復雜度依舊:O(n^{2}),依舊看開了一個多大的數組就是結果,依舊莫想的太復雜…
動態規划的四點要素
1.狀態:即定義,中間的狀態
2.方程:即從前一種狀態推出現在的狀態.
3.初始化:極限小的狀態,即為起點.
4.答案:終點
遞歸三要素:
1. 定義(狀態)
• 接受什么參數
• 做了什么事
• 返回什么值
2. 拆解(方程)
• 如何將參數變小
3. 出口(初始化)
• 什么時候可以直接 return
狀態的六大問題:
1.坐標型15%
2.序列型30%
3.雙序列型30%
4.划分型10%
5.背包型10%
5.區間型5%
坐標型動態規划
算法視頻QQ_1603159172
初始化的小技巧
沒有左上和右上的提出來先算,也可以說成二維DP問題, [i,0]和[0,i]先初始化,即初始化第0行和第0列.
Unique Paths
A robot is located at the top-left corner of a m x n grid.
The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid.
public class Solution { public int uniquePaths(int m, int n) { if (m == 0 || n == 0) { return 1; } int[][] sum = new int[m][n]; for (int i = 0; i < m; i++) { sum[i][0] = 1; } for (int i = 0; i < n; i++) { sum[0][i] = 1; } for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { sum[i][j] = sum[i - 1][j] + sum[i][j - 1]; } } return sum[m - 1][n - 1]; } }
Minimum Path Sum
算法視頻QQ_1603159172
Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.
public class Solution { public int minPathSum(int[][] grid) { if (grid == null || grid.length == 0 || grid[0].length == 0) { return 0; } int M = grid.length; int N = grid[0].length; int[][] sum = new int[M][N]; sum[0][0] = grid[0][0]; for (int i = 1; i < M; i++) { sum[i][0] = sum[i - 1][0] + grid[i][0]; } for (int i = 1; i < N; i++) { sum[0][i] = sum[0][i - 1] + grid[0][i]; } for (int i = 1; i < M; i++) { for (int j = 1; j < N; j++) { sum[i][j] = Math.min(sum[i - 1][j], sum[i][j - 1]) + grid[i][j]; } } return sum[M - 1][N - 1]; } }
接龍型DP
算法視頻QQ_1603159172
LISLongest Increasing Subsequence
Given a sequence of integers, find the longest increasing subsequence (LIS).
You code should return the length of the LIS.
不要用貪心法, 你能想到的貪心法都是錯的
面試不會考貪心法
貪心法沒有通用性
起點不確定,終點也不確定,一個小人, 只能往高跳,最多踩多少個木樁:
public class Solution { /** * @param nums: The integer array * @return: The length of LIS (longest increasing subsequence) */ public int longestIncreasingSubsequence(int[] nums) { int []f = new int[nums.length]; int max = 0; for (int i = 0; i < nums.length; i++) { f[i] = 1; for (int j = 0; j < i; j++) { if (nums[j] f[j] + 1 ? f[i] : f[j] + 1; } } if (f[i] > max) { max = f[i]; } } return max; } }