九章算法高級班筆記5.動態規劃(上)

大綱:

cs3k.com

  1. 滾動數組
  • House Robber I/II
  • Maximal Square
  1. 記憶化搜索
  • Longest Increasing Subsequence
  • Coin in a line I/II/III

 

什麼是動態規劃? cs3k.com

動態規劃是解決重複子問題的一種方法。

動態規劃四要素

cs3k.com

  1. 狀態 State
  • 靈感,創造力,存儲小規模問題的結果
  • 最優解/Maximum/Minimum
  • Yes/No 存在不存在知足條件的答案
  • Count(*) 有多少個可行解
  1. 方程 Function
  • 狀態之間的聯繫,怎麼經過小的狀態,來求得大的狀態
  1. 初始化 Intialization
  • 最極限的小狀態是什麼, 起點
  1. 答案 Answer
  • 最大的那個狀態是什麼,終點

狀態的六大問題:

cs3k.com

cs3k.com

  1. 座標型15%
    jump game: 棋盤,格子
     f[i]表明從起點走到i座標
  2. 序列型30%
    f[i]表明前i個元素總和,i=0表示不取任何元素
  3. 雙序列型30%
  4. 劃分型10%
  5. 揹包型10%
  6. 區間型5%

滾動數組優化

f[i] = max(f[i-1], f[i-2] + A[i

cs3k.com

]);

轉換爲java

f[i%2] = max(f[(i-1)%2]和 f[(i-2)%2])

House Robber

cs3k.com

cs3k.com

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.算法

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.數組

Given [3, 8, 4], return 8.ide

這是一道典型的序列型的動態規劃。
f[i]表明路過完第i家的時候, 最多能有多少錢。
通常求什麼,f[i]就是什麼。
對於i=n的時候,咱們有兩個選擇, 就是搶或者不搶:wordpress

搶的話,最後是i=n-2的錢數加上第n家的錢數
不搶的話, 錢數等於i=n-1的錢數優化

說着感受沒啥問題,可是總以爲隱隱約約哪裏不對勁, 漏了什麼狀況, 因而乎我看了個博客http://blog.csdn.net/zsy112371/article/details/52541925上面用此段代碼作解釋this

帶碼引用 https://discuss.leetcode.com/topic/11082/java-o-n-solution-space-o-1:spa

public int rob(int[] num) {  
    int[][] dp = new int[num.length + 1][2];  
    for (int i = 1; i <= num.length; i++) {  
        dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]);  
        dp[i][1] = num[i - 1] + dp[i - 1][0];  
    }  
    return Math.max(dp[num.length][0], dp[num.length][1]);  
}

解釋引用自博客http://blog.csdn.net/zsy112371/article/details/52541925.net

稍微解釋一下這個代碼,dp這個二維數組表明盜竊前i棟房子帶來的最大收益,其中第二維一共有兩個選擇分別是0和1。0表明不盜竊第i棟房子,1表明盜竊第i棟房子。換句話就是說,dp[i][0]表明盜竊前i棟房子的最大收益,可是不包括第i棟房子的(由於沒有盜竊第i棟房子),而dp[i][0]表明盜竊前i棟房子的最大收益,其中包括了第i棟房子的(由於第i棟房子被盜竊了)。
其實對一棟房子來講,結果無非是兩種,被盜竊和沒被竊。因此說,纔會有以前分0和1兩種狀況進行討論。若是第i棟房子沒被盜竊的話,那麼dp[i][0] = dp[i-1][0]和dp[i-1][1]中的最大值。這個比較好理解,若是第i棟房子沒被竊,那麼最大總收益dp[i][0]必定和dp[i-1][0],dp[i-1][1]這兩個之中最大的相同。而倘若第i棟房子被竊,那麼dp[i][1]必定等於第num[i-1](注意,這裏的i是從1開始的,因此i-1正好對應num中的第i項,由於num中的index是從0開始的)+dp[i-1][0],由於第i棟房子已經被竊了,第i-1棟房子確定不能被竊,不然會觸發警報,因此咱們只能取dp[i-1][0]即第i-1棟房子沒被竊狀況的最大值。循環結束,最後返回dp[num.length][0]和dp[nums.length][1]較大的一項。
結束來自http://blog.csdn.net/zsy112371/article/details/52541925 的引用。3d

咱們走幾個小栗子:

數組[80, 7, 9, 90, 87]按上面的作法獲得每步的結果, 選i和不選i的結果

80      7      9      90      87
不選i   0     (80)    80      89     80+90
選i   (80)     7     (89)   (80+90) (87+89)

換點數字和順序:

80      9      7       3      60
不選i   0     (80)    80      87      87
選i   (80)     9     (87)   (80+3) (87+60)

咱們發現, 對於每個i, 影響後面能用到的, 只有選i和不選i兩個決策結果中比較大的那個, 因此咱們能夠只存一個, f[i]表明路過完第i家的時候, 最多能有多少錢. 就是如下算法:

public class Solution {
    /**
     * @param A: An array of non-negative integers.
     * return: The maximum amount of money you can rob tonight
     */
    //---方法一---
    public long houseRobber(int[] A) {
        // write your code here
        int n = A.length;
        if(n == 0)
            return 0;
        long []res = new long[n+1];
        
        
        res[0] = 0;
        res[1] = A[0];
        for(int i = 2; i <= n; i++) {
            res[i] = Math.max(res[i-1], res[i-2] + A[i-1]);
        }
        return res[n];
    }

以後呢, 咱們發現, 對於某個狀態c,假設它是奇數位的狀態, 它前面有奇狀態a和偶狀態b:

...  a       b       c  ...
 ... 奇1     偶1      奇2  ...

它的狀態只和它前面的一個奇狀態和一個偶狀態有關, 因此咱們能夠只存三個變量:

奇1 和 偶1 推出 奇2

但是因爲在奇2狀態結果一出現的時候, 奇1結果就沒啥用了, 能夠被當即替換, 因此咱們就能夠只存一對奇偶的狀態, 即兩個變量, 以下:

public long houseRobber(int[] A) {
        // write your code here
        int n = A.length;
        if(n == 0)
            return 0;
        long []res = new long[2];
        
        
        res[0] = 0;
        res[1] = A[0];
        for(int i = 2; i <= n; i++) {
            res[i%2] = Math.max(res[(i-1)%2], res[(i-2)%2] + A[i-1]);
        }
        return res[n%2];
    }
}

這就是滾動數組, 或者叫作滾動指針的空間優化.

House Robber II

cs3k.com

After robbing those houses on that street, the thief has found himself a new place for his thievery so that he will not get too much attention. This time, all houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile, the security system for these houses remain the same as for those in the previous street.

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

Example
nums = [3,6,4], return 6

如今呢, 咱們若是選3了的話, 4很差搞。

若是選4了的話呢, 3很差搞。
這就變成了一個循環數組問題, 循環數組問題有三種方法能夠解:

  1. 取反
  2. 分裂
  3. 倍增

這裏咱們用分裂的方法, 把數組

[3, 6, 4]

分紅, 選3的:

[3, 6]

和不選3的:

[6, 4]

而後把這兩個非循環數組分別用上面的方法求解.
我猜這多是雙序列動規吧…

public class Solution {
    public int houseRobber2(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }
        if (nums.length == 1) {
            return nums[0];
        }
        return Math.max(robber1(nums, 0, nums.length - 2), robber1(nums, 1, nums.length - 1));
    }
    public int robber1(int[] nums, int st, int ed) {
        int []res = new int[2];
        if(st == ed) 
            return nums[ed];
        if(st+1 == ed)
            return Math.max(nums[st], nums[ed]);
        res[st%2] = nums[st];
        res[(st+1)%2] = Math.max(nums[st], nums[st+1]);
        
        for(int i = st+2; i <= ed; i++) {
            res[i%2] = Math.max(res[(i-1)%2], res[(i-2)%2] + nums[i]);
            
        }
        return res[ed%2];
    }
}

Maximal Square

cs3k.com

Given a 2D binary matrix filled with 0’s and 1’s, find the largest square containing all 1’s and return its area.

Example
For example, given the following matrix:

1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0

Return 4.

長方形每每選左上和右下角的角點開始, 正方形通常選右下角.

這道題比較直白的作法是這樣的:

for x = 0 ~ n
 for y = 0 ~ m
  for a = 1 ~ min(m,n)
   for (x = x ~ x+a)
    for (y = y ~ y+1)

時間複雜度是O( m * n * min(m,n) * m * n)
這個時間複雜度, 真是不小. 接着咱們想一想, 發現呢, (2,3)這個點爲右下角點的square的大小, 其實和(1,2)有關, 它最大是(1,2)的squre的邊長再加一
那麼呢, 咱們能夠把正方形分紅pic5.1圖中的幾個部分:
Evernote Snapshot 20171031 162349
對於一個點A, 咱們須要看看它左上的點最大的正方形邊長, 而後驗證它左邊連續是1的點的長度和上面連續是1的點的長度, 即

1. for向左都是1的長度
2. for向上都是1的長度
3. for左上點的最大正方形的邊長

1, 2和3的值中的最小值+1就是結果

其中1和2能夠用預處理過得矩陣left[][]和up[][]優化.
這是狀態方程是:

if matrix[i][j] == 1
    f[i][j] = min(LEFT[i][j-1], UP[i-1][j], f[i-1][j-1]) + 1;
if matrix[i][j] == 0
    f[i][j] = 0

接着呢, 咱們發現, 其實left和up矩陣是不須要的. 如圖pic5.2:
Evernote Snapshot 20171031 164120

咱們發現, A爲右下角點的鉛筆畫大正方形A全爲1須要的條件是:

綠色的B, 黑色的C和粉色的D全都爲1

加上

而且A本身的最右下角點爲1

因此咱們只須要在左點, 上點和左上點各自最大正方形的邊長取最小值, 再加1就好了. 狀態方程變成:

if matrix[i][j] == 1
     f[i][j] = min(f[i - 1][j], f[i][j-1], f[i-1][j-1]) + 1;
if matrix[i][j] == 0
     f[i][j] = 0
public class Solution {
    /**
     * @param matrix: a matrix of 0 and 1
     * @return: an integer
     */
    public int maxSquare(int[][] matrix) {
        // write your code here
        int ans = 0;
        int n = matrix.length;
        int m;
        if(n > 0)
            m = matrix[0].length;
        else 
            return ans;
        int [][]res = new int [n][m];
        for(int i = 0; i < n; i++){
            res[i][0] = matrix[i][0];
            ans = Math.max(res[i][0] , ans);
            for(int j = 1; j < m; j++) {                 if(i > 0) {
                    if(matrix[i][j] > 0) {
                        res[i][j] = Math.min(res[i - 1][j],Math.min(res[i][j-1], res[i-1][j-1])) + 1;
                    } else {
                        res[i][j] = 0;
                    }
                    
                }
                else {
                    res[i][j] = matrix[i][j];
                }
                ans = Math.max(res[i][j], ans);
            }
        }
        return ans*ans;
    }
}

那動態規劃中什麼要初始化呢?

cs3k.com

動態方程不能求的要初始化

滾動數組優化

須要多少個狀態, 就存多少個.

求第i個狀態, 若是隻與i-1有關, 那就只須要兩個數組. 用previous數組, 推now數組.
因此上一個解法中, j能夠都模2.

那i爲何不能模呢?
由於若是i模了的話, 下一行左邊開始的時候, 存的仍是上一行最右邊末尾的值, 不行噠~ 狀態方程變爲:

if matrix[i][j] == 1
    f[i%2][j] = min(f[(i - 1)%2][j], f[i%2][j-1], f[(i-1)%2][j-1]) + 1;
if matrix[i][j] == 0
    f[i%2][j] = 0

其中要注意初始化和答案也要記得模:

初始化 Intialization:

f[i%2][0] = matrix[i][0];
f[0][j] = matrix[0][j];

答案 Answer

max{f[i%2][j]}

Follow Up Maximal Square II

cs3k.com

Given a 2D binary matrix filled with 0’s and 1’s, find the largest square which diagonal is all 1 and others is 0.

Notice

Only consider the main diagonal situation.

For example, given the following matrix:

1 0 1 0 0
1 0 0 1 0
1 1 0 0 1
1 0 0 1 0
Return 9

這時候up和left數組就不能省啦.

public class Solution {
    /**
     * @param matrix a matrix of 0 and 1
     * @return an integer
     */
    public int maxSquare2(int[][] matrix) {
        // write your code here
        int n = matrix.length;
        if (n == 0)
            return 0;

        int m = matrix[0].length;
        if (m == 0)
            return 0;

        int[][] f = new int[n][m];
        int[][] u = new int[n][m];
        int[][] l = new int[n][m];

        int length = 0;
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j) {                 if (matrix[i][j] == 0) {                     f[i][j] = 0;                     u[i][j] = l[i][j] = 1;                     if (i > 0)
                        u[i][j] = u[i - 1][j] + 1;
                    if (j > 0)
                        l[i][j] = l[i][j - 1] + 1;
                } else {
                    u[i][j] = l[i][j] = 0;
                    if (i > 0 && j > 0)
                        f[i][j] = Math.min(f[i - 1][j - 1], Math.min(u[i - 1][j], l[i][j - 1])) + 1;
                    else 
                        f[i][j] = 1;
                }
                length = Math.max(length, f[i][j]);
            }
        return length * length;
    }
}

一維的叫滾動數組, 二位的叫滾動矩陣, 二維動態規劃空間優化的特色是:

  1. f[i][j] = 由f[i-1]行 來決定狀態,
  2. 第i行跟 i-1行以前毫無關係,
  3. 因此狀態轉變爲
    f[i%2][j] = 由f[(i-1)%2]行來決定狀態

記憶化搜索

cs3k.com

  1. 記憶搜索本質是:動態規劃
  • 動態規劃就是解決了重複計算的搜索
  1. 動態規劃的實現方式:
  • 循環(從小到大遞推)
  • 記憶化搜索(從大到小搜索)
  1. 記憶化搜索
  • 畫搜索樹
  • 萬金油搜索能夠解決一切問題
    記憶化搜索能夠解決一切動態規劃的問題
    動態規劃均可以用記憶化搜索解決, 可是記憶化搜索不必定是最優解

Longest Increasing Continuous Subsequence

cs3k.com

Give an integer array,find the longest increasing continuous subsequence in this array.

An increasing continuous subsequence:

Can be from right to left or from left to right.
Indices of the integers in the subsequence should be continuous.
Notice

O(n) time and O(1) extra space.

Example
For [5, 4, 2, 1, 3], the LICS is [5, 4, 2, 1], return 4.

For [5, 1, 2, 3, 4], the LICS is [1, 2, 3, 4], return 4.

這道題:

若是a[i] > a[i-1]時,f[i] = f[i-1] + 1
若是a[i] < a[i-1]呢,f[i] = 1

Longest Increasing Continuous subsequence II

cs3k.com

Give you an integer matrix (with row size n, column size m),find the longest increasing continuous subsequence in this matrix. (The definition of the longest increasing continuous subsequence here can start at any row or column and go up/down/right/left any direction).

Example
Given a matrix:

[
[1 ,2 ,3 ,4 ,5],
[16,17,24,23,6],
[15,18,25,22,7],
[14,19,20,21,8],
[13,12,11,10,9]
]
return 25

相似與滑雪問題
這道題很容易想到二維dp, 記錄f[i][j] 是以i,j爲結尾的LIS。可是因爲咱們走的方向不只僅是從左往右和從上往下, 還可能從右往左和從下往上, 因此

1. 轉化方程式非順序性的(順序指的是從左往右,從上往下)
2. 初始化的狀態在哪兒是肯定的

這樣的問題, 咱們只能dfs加記憶化搜索.
每一個位置最長的LIS記錄在一個矩陣dp[][] 裏面, 同時用一個flag[][] 矩陣記錄下遍歷過沒有. dp和flag矩陣能夠寫在一塊兒, 可是最好不要, 由於dp表明當前最長長度, flag表明遍歷過沒有, 意義不一樣.
這道題的時間複雜度是O(n*n), 由於有flag, 每一個點最多遍歷一次.

public class Solution {
    /**
     * @param A an integer matrix
     * @return  an integer
     */
    int [][]dp;
    int [][]flag ;
    int n ,m;
    public int longestIncreasingContinuousSubsequenceII(int[][] A) {
        // Write your code here
        if(A.length == 0)
            return 0;
        n = A.length;
         m  = A[0].length;
        int ans= 0;
        dp = new int[n][m];
        flag = new int[n][m];
        
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) { 
                dp[i][j] = search(i, j, A);
                ans = Math.max(ans, dp[i][j]);
            }
        }
        return ans;
    }
    int []dx = {1,-1,0,0};
    int []dy = {0,0,1,-1};
    
    int search(int x, int y, int[][] A)   {
        if(flag[x][y] != 0)    
            return dp[x][y];
        
        int ans = 1; 
        int nx , ny;
        for(int i = 0; i < 4; i++) {
            nx = x + dx[i];
            ny = y + dy[i];
            if(0<= nx && nx < n && 0<= ny && ny < m ) {                 if( A[x][y] > A[nx][ny]) {
                    ans = Math.max(ans,  search( nx, ny, A) + 1);
                }
            }
        }
        flag[x][y] = 1;
        dp[x][y] = ans;
        return ans;
    }
}

何時用記憶化搜索呢?

cs3k.com

  1. 狀態轉移特別麻煩, 不是順序性
  2. 初始化狀態不是特別容易找到

這時候咱們用萬能的dfs加memorization
enter image description here

那怎麼根據DP四要素轉化爲記憶化搜索呢?

  1. State:
  • dp[x][y] 以x,y做爲結尾的最長子序列
  1. Function:
  • 遍歷x,y 上下左右四個格子
    dp[x][y] = dp[nx][ny] + 1
         (if a[x][y] > a[nx][ny])
  1. Intialize:
  • dp[x][y] 是極小值時,初始化爲1
  1. Answer:
  • dp[x][y]中最大值

enter image description here

記憶化搜索的博弈類問題

cs3k.com

Coins in a Line

There are n coins in a line. Two players take turns to take one or two coins from right side until there are no more coins left. The player who take the last coin wins.

Could you please decide the first play will win or lose?

Example
n = 1, return true.

n = 2, return true.

n = 3, return false.

n = 4, return true.

n = 5, return true.

棋盤和玩遊戲的問題必定是博弈類問題.

這個問題我直接就想到除以三看餘數, 可是老師說這個方法很差想…而且沒有通用性. 我以爲我可能思惟詭異吧, 好多我本身能繞一個小時把本身都繞暈了的題, 老師說這個你們都應該能想到吧…

來來來, 不能用貪心咱們就走個小栗子看看,o表明一個硬幣

ooooo
        先手拿       1 /        \ 2
                   oooo        ooo
        後手拿    1/    \2     1/  \2
                ooo    oo     oo    o
        先手拿 先手輸  先手贏  先手贏 先手贏

其中剩兩個石頭的狀況有兩次,咱們能夠判斷一次而後存下來以備後用。

首先, 咱們知道輪到先手, 剩4個,2個或1個石頭的時候,是贏的. 而3個石頭的時候, 是輸的。因此對於5個石頭的狀況, 咱們要保證先手不管在對方拿了1個剩四個仍是拿了2個剩3個的狀況下,都有機會贏。
能夠概括爲如下方法:

  1. State:
  • dp[i] 如今還剩i個硬幣,如今先手取硬幣的人最後輸贏情況
  1. Function:
  • dp[i] = (dp[i-2]&& dp[i-3]) || (dp[i-3]&& dp[i-4] )
  1. Intialize:
  • dp[0] = false
  • dp[1] = true
  • dp[2] = true
  • dp[3] = false
  1. Answer:
  • dp[n]

此外還有另外一個方法:

  1. State:
  • dp[i] 如今還剩i個硬幣,如今當前取硬幣的人最後輸贏情況
  1. Function:
  • dp[i] = true (dp[i-1]==false 或者 dp[i-2]==false)
  1. Intialize:
  • dp[0] = false
  • dp[1] = true
  • dp[2] = true
  1. Answer:
  • dp[n]
public class Solution {
    /**
     * @param n: an integer
     * @return: a boolean which equals to true if the first player will win
     */
    public boolean firstWillWin(int n) {
        // write your code here
        int []dp = new int[n+1];
        
        return MemorySearch(n, dp);
        
    }
    boolean MemorySearch(int n, int []dp) { // 0 is empty, 1 is false, 2 is true
        if(dp[n] != 0) {
            if(dp[n] == 1)
                return false;
            else
                return true;
        }
        if(n <= 0) {
            dp[n] = 1;
        } else if(n == 1) {
            dp[n] = 2;
        } else if(n == 2) {
            dp[n] = 2;
        } else if(n == 3) {
            dp[n] = 1;
        } else {
            if((MemorySearch(n-2, dp) && MemorySearch(n-3, dp)) || 
                (MemorySearch(n-3, dp) && MemorySearch(n-4, dp) )) {
                dp[n] = 2;
            } else {
                dp[n] = 1;
            }
        }
        if(dp[n] == 2) 
            return true;
        return false;
    }
}

// 方法二 StackOverflow
public class Solution {
    /**
     * @param n: an integer
     * @return: a boolean which equals to true if the first player will win
     */
    public boolean firstWillWin(int n) {
        // write your code here
        boolean []dp = new boolean[n+1];
        boolean []flag = new boolean[n+1];
        return MemorySearch(n, dp, flag);
    }
    boolean MemorySearch(int i, boolean []dp, boolean []flag) {
        if(flag[i] == true) {
            return dp[i];
        }
        if(i == 0) {
            dp[i] = false;
        } else if(i == 1) {
            dp[i] = true;
        } else if(i == 2) {
            dp[i] = true;
        } else {
            dp[i] = !MemorySearch(i-1, dp, flag) || !MemorySearch(i-2, dp, flag);
        }
        flag[i] =true;
        return dp[i];
    }
}

//方法三
public class Solution {
    /**
     * @param n: an integer
     * @return: a boolean which equals to true if the first player will win
     */
    public boolean firstWillWin(int n) {
        // write your code here
        if (n == 0)
            return false;
        else if (n == 1)
            return true;
        else if (n == 2)
            return true;
            
        boolean []dp = new boolean[n+1];
        dp[0] = false;
        dp[1] = true;
        dp[2] = true;
        for (int i = 3; i <= n; i++) 
            dp[i] = !dp[i - 1] || !dp[i - 2];
            
        return dp[n];
    }
}

Coins in a Line II

There are n coins with different value in a line. Two players take turns to take one or two coins from left side until there are no more coins left. The player who take the coins with the most value wins.

Could you please decide the first player will win or lose?

Have you met this question in a real interview? Yes
Example
Given values array A = [1,2,2], return true.

Given A = [1,2,4], return false.

來來來, 走個栗子:

[5,1,2,10] 
        先手拿       1(5) /        \ 2(6) 
                    [1,2,10]       [2,10]  
        後手拿   1(1)/    \2(3)  1(2)/  \2(12)
              [2,10]  [10]        [10] []

每次拿硬幣的原則不是當前手拿的最多, 而是加上剩下能拿的總共最多。

f[i]先手還剩i個硬幣:

f[5] 左 = 5 + min(f[2],f[1])
     右 = 6 + min(f[1],f[0])
  1. State:
  • dp[i] 如今還剩i個硬幣,如今先手取硬幣的人最後最多取硬幣價值
  1. Function:
  • i 是全部硬幣數目
  • coin[n-i] 表示倒數第i個硬幣
  • dp[i] = max(min(dp[i-2], dp[i-3])+coin[n-i] ),(min(dp[i-3],dp[i-4])+coin[n-i]+coin[n-i+1] )
  1. Intialize:
  • dp[0] = 0
  • dp[1] = coin[i-1]
  • dp[2] = coin[i-2] + coin[i-1]
  • dp[3] = coin[i-2] + coin[i-3]
  1. Answer:
  • dp[n] > sum/2

另外一種方法:

  1. State:
  • dp[i] 如今還剩i個硬幣,如今當前取硬幣的人最後最多取硬幣價值
  1. Function:
  • i 是全部硬幣數目
  • sum[i] 是後i個硬幣的總和
  • dp[i] = max(sum[i]-dp[i-1], sum[i] – dp[i-2])
  1. Intialize:
  • dp[0] = 0
  • dp[1] = coin[i-1]
  • dp[2] = coin[i-2] + coin[i-1]
  1. Answer:
  • dp[n]
public class Solution {
    /**
     * @param values: an array of integers
     * @return: a boolean which equals to true if the first player will win
     */
    public boolean firstWillWin(int[] values) {
        // write your code here
        int n = values.length;
        int[] sum = new int[n + 1];
        for (int i = 1; i <= n; ++i)
            sum[i] = sum[i -  1] + values[n - i];

        int[] dp = new int[n + 1];
        dp[1] = values[n - 1];
        for (int i = 2; i <= n; ++i)             dp[i] = Math.max(sum[i] - dp[i - 1], sum[i] - dp[i - 2]);                      return dp[n]  > sum[n] / 2;
    }
}

// 方法一
import java.util.*;

public class Solution {
    /**
     * @param values: an array of integers
     * @return: a boolean which equals to true if the first player will win
     */
    public boolean firstWillWin(int[] values) {
        // write your code here
        int []dp = new int[values.length + 1];
        boolean []flag =new boolean[values.length + 1];
        int sum = 0;
        for(int now : values) 
            sum += now;
        
        return sum < 2*MemorySearch(values.length, dp, flag, values);     }     int MemorySearch(int n, int []dp, boolean []flag, int []values) {          if(flag[n] == true)             return dp[n];         flag[n] = true;         if(n == 0)  {             dp[n] = 0;           } else if(n == 1) {             dp[n] = values[values.length-1];         } else if(n == 2) {             dp[n] = values[values.length-1] + values[values.length-2];          } else if(n == 3){             dp[n] = values[values.length-2] + values[values.length-3];          } else {             dp[n] = Math.max(                 Math.min(MemorySearch(n-2, dp, flag,values) , MemorySearch(n-3, dp, flag, values)) + values[values.length-n],                 Math.min(MemorySearch(n-3, dp, flag, values), MemorySearch(n-4, dp, flag, values)) + values[values.length-n] + values[values.length - n + 1]                 );         }              return dp[n];     }     } // 方法二 public class Solution {     /**      * @param values: an array of integers      * @return: a boolean which equals to true if the first player will win      */     public boolean firstWillWin(int[] values) {         // write your code here         int n = values.length;         int []dp = new int[n + 1];         boolean []flag =new boolean[n + 1];         int []sum = new int[n+1];         int allsum = values[n-1];         sum[n-1] = values[n-1];         for(int i = n-2; i >= 0; i--) { 
            sum[i] += sum[i+1] + values[i];
            allsum += values[i];
        }
        return allsum/2 < MemorySearch(0, n, dp, flag, values, sum);
    }
    int MemorySearch(int i, int n, int []dp, boolean []flag, int []values, int []sum) {
        if(flag[i] == true)
            return dp[i];
        flag[i] = true;
        if(i == n)  {
            dp[n] = 0;  
        } else if(i == n-1) {
            dp[i] = values[i];
        } else if(i == n-2) {
            dp[i] = values[i] + values[i + 1]; 
        } else {
            dp[i] = sum[i] -
                Math.min(MemorySearch(i+1, n, dp, flag, values, sum) , MemorySearch(i+2, n, dp, flag, values, sum));
        }
        return dp[i];
    }
   
}

Coins in a Line III

cs3k.com

There are n coins in a line. Two players take turns to take a coin from one of the ends of the line until there are no more coins left. The player with the larger amount of money wins.

Could you please decide the first player will win or lose?

Example
Given array A = [3,2,2], return true.

Given array A = [1,2,4], return true.

Given array A = [1,20,4], return false.

方法一:

  1. State:
  • dp[i][j] 如今還第i到第j的硬幣,如今先手取硬幣的人最後最多取硬幣價值
  1. Function:
  • left = min(dp[i+2][j], dp[i+1][j-1])+coin[i] [i+1,j]
  • right = min(dp[i][j-2], dp[i+1][j-1])+coin[j] [i,j-1]
  • dp[i][j] = max(left, right).
  1. Intialize:
  • dp[i][i] = coin[i],
  • dp[i][i+1] = max(coin[i],coin[i+1]),
  1. Answer:
  • dp[0][n-1] > sum/2

方法二:

  1. State:
  • dp[i][j] 如今還第i到第j的硬幣,如今當前取硬幣的人最後最多取硬幣價值
  1. Function:
  • sum[i][j]第i到第j的硬幣價值總和
  • dp[i][j] = max(sum[i][j] – dp[i+1][j], sum[i][j] – dp[i][j-1]);
  1. Intialize:
  • dp[i][i] = coin[i],
  1. Answer:
  • dp[0][n-1]
public class Solution {
    /**
     * @param values: an array of integers
     * @return: a boolean which equals to true if the first player will win
     */
    public boolean firstWillWin(int[] values) {
        // write your code here
        
        int n = values.length;
        int[] sum = new int[n + 1];
        sum[0] = 0;
        for (int i = 1; i <= n; ++i)
            sum[i] = sum[i - 1] + values[i - 1];
            
        // s[i][j] = sum[j + 1] -  sum[i];
        
        int[][] dp = new int[n][n];
        for (int i = 0; i < n; ++i)
            dp[i][i] = values[i];
            
        for (int len = 2; len <= n; ++len) {
            for (int i = 0; i < n; ++i) {                 int j = i + len - 1;                 if (j >= n)
                    continue;
                int s = sum[j + 1] - sum[i];
                dp[i][j] = Math.max(s - dp[i + 1][j], s - dp[i][j - 1]);
            }
        }
        
        return dp[0][n - 1]  > sum[n] / 2;
    }
}

// 方法一
import java.util.*;

public class Solution {
    /**
     * @param values: an array of integers
     * @return: a boolean which equals to true if the first player will win
     */
    public boolean firstWillWin(int[] values) {
        // write your code here
        int n = values.length;
        int [][]dp = new int[n + 1][n + 1];
        boolean [][]flag =new boolean[n + 1][n + 1];
        
        int sum = 0;
        for(int now : values) 
            sum += now;
        
        return sum < 2*MemorySearch(0,values.length - 1, dp, flag, values);     }     int MemorySearch(int left, int right, int [][]dp, boolean [][]flag, int []values) {                  if(flag[left][right])                return dp[left][right];         flag[left][right] = true;         if(left > right) {
            dp[left][right] = 0;
        } else if (left == right) {
            dp[left][right] = values[left];
        } else if(left + 1 == right) {
            dp[left][right] = Math.max(values[left], values[right]);
        } else {
            int  pick_left = Math.min(MemorySearch(left + 2, right, dp, flag, values), MemorySearch(left + 1, right - 1, dp, flag, values)) + values[left];
            int  pick_right = Math.min(MemorySearch(left, right - 2, dp, flag, values), MemorySearch(left + 1, right - 1, dp, flag, values)) + values[right];
            dp[left][right] = Math.max(pick_left, pick_right);    
        }
        return dp[left][right];   
    }
}

// 方法二
import java.util.*;
public class Solution {
    /**
     * @param values: an array of integers
     * @return: a boolean which equals to true if the first player will win
     */
    public boolean firstWillWin(int[] values) {
        // write your code here
        int n = values.length;
        int [][]dp = new int[n + 1][n + 1];
        boolean [][]flag =new boolean[n + 1][n + 1];
        int[][] sum = new int[n + 1][n + 1];
        for (int i = 0; i < n; i++) {
            for (int j = i; j < n; j++) {
                sum[i][j] = i == j ? values[j] : sum[i][j-1] + values[j];
            }
        }
        int allsum = 0;
        for(int now : values) 
            allsum += now;
        
        return allsum < 2*MemorySearch(0,values.length - 1, dp, flag, values, sum);     }     int MemorySearch(int left, int right, int [][]dp, boolean [][]flag, int []values, int [][]sum) {         if(flag[left][right])                return dp[left][right];                      flag[left][right] = true;         if(left > right) {
            dp[left][right] = 0;
        } else if (left == right) {
            dp[left][right] = values[left];
        } else if(left + 1 == right) {
            dp[left][right] = Math.max(values[left], values[right]);
        } else {
            int cur = Math.min(MemorySearch(left+1, right, dp, flag, values, sum), MemorySearch(left,right-1, dp, flag, values, sum));
            dp[left][right] = sum[left][right] - cur;
        }
        return dp[left][right];   
    }
}

何時用記憶化搜索?

cs3k.com

  1. 狀態轉移特別麻煩,不是順序性。
  • Longest Increasing continuous Subsequence 2D
    • 遍歷x,y 上下左右四個格子 dp[x][y] = dp[nx][ny]
  • Coins in a Line III
    • dp[i][j] = sum[i][j] – min(dp[i+1][j], dp[i][j-1]);
  1. 初始化狀態不是很容易找到
  • Stone Game
    • 初始化dp[i][i] = 0
  • Longest Increasing continuous Subsequence 2D
    • 初始化極小值
  1. 從大到小
相關文章
相關標籤/搜索