九章算法高级班笔记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

]);

转换为

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.

这是一道典型的序列型的动态规划。
f[i]代表路过完第i家的时候, 最多能有多少钱。
一般求什么,f[i]就是什么。
对于i=n的时候,我们有两个选择, 就是抢或者不抢:

抢的话,最后是i=n-2的钱数加上第n家的钱数
不抢的话, 钱数等于i=n-1的钱数

说着感觉没啥问题,但是总觉得隐隐约约哪里不对劲, 漏了什么情况, 于是乎我看了个博客http://blog.csdn.net/zsy112371/article/details/52541925上面用此段代码做解释

带码引用 https://discuss.leetcode.com/topic/11082/java-o-n-solution-space-o-1:

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

稍微解释一下这个代码,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 的引用。

我们走几个小栗子:

数组[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. 从大到小

猜你喜欢

转载自www.cnblogs.com/jiuzhangsuanfa/p/9895692.html