九章算法筆記 9.動態規劃 Dynamic Programming

遞歸和動態規劃 cs3k.com

從Triangle這個問題提及:java

題目:

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:數組

[ide

[2],函數

[3,4],優化

[6,5,7],spa

[4,1,8,3]code

]blog

The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).

四種解法

遇到這道題,老師說開始先給出暴力解法以後進行優化是能夠的,不必定一開始就給出最優解,想出來一個暴力解法以爲傻就不說話。。。

  • DFS:Traverse

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})。

討論: 

cs3k.com

動態規劃和分治

1.動態規劃是一種算法思想, 是高於算法的. 而分治的記憶化搜索是實現動態規劃的一種手段.
2.那麼什麼是動態規劃呢?
-就感受上來講, 動態規劃的是"一層一層來", 基於前一個狀態推出如今的狀態.
3.動態規劃爲何會快呢?
-由於減小了不少沒必要要的重複計算.
4.動態規劃和分治的區別?
-動態規劃約等於分治+記憶化, 由於有了記憶化, 因此算過的直接用就行, 就不用再算一遍了.
5.動態規劃有方向性,不回頭,不繞圈兒,無循環依賴。

何時使用動態規劃:

動態規劃適合把暴力時間複雜度爲指數型的問題轉化爲多項式的複雜度,即O(2^{n})或O(n!) 轉化爲O(n^{2})

1.求最大最小的問題

2.判斷可不可行,存不存在

3.統計方案個數

何時不用動態規劃:

  1. 原本時間複雜度就在O(n^{2})或者O(n^{3})的問題繼續優化,由於不怎麼能優化…(100%不用DP)• 動態規劃擅長與優化指數級別複雜度(2^n,n!)到多項式級別複雜度(n^2,n^3)

    • 不擅長優化n^3到n^2

  2. 求!!全部的,具體的!!方案而不是方案個數,要用DFS而不是DP(99%不用DP), 除了N皇后
  3. 輸入數據是一個集合而非序列(70~80%不用DP),除了揹包問題。

動態規劃的實現方式

cs3k.com

這裏寫圖片描述

多重循環的兩種方式:

  • 自底向上
  • 自頂向下兩種方法沒有太大優劣區別

    思惟模式一個正向,一個逆向

    自底向上代碼以下:

    這裏寫圖片描述

    時間複雜度: 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%

座標型動態規劃

cs3k.com

這裏寫圖片描述

初始化的小技巧

沒有左上和右上的提出來先算,也能夠說成二維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

cs3k.com

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

cs3k.com

LISLongest Increasing Subsequence

Given a sequence of integers, find the longest increasing subsequence (LIS).

You code should return the length of the LIS.

不要用貪心法, 你能想到的貪心法都是錯的

面試不會考貪心法

貪心法沒有通用性

起點不肯定,終點也不肯定,一個小人, 只能往高跳,最多踩多少個木樁:

enter image description here

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; } }
相關文章
相關標籤/搜索