LintCode动态规划题总结

不知道什么是动态规划的,传送门在这儿:[干货]动态规划十问十答

动态规划进阶:动态规划:从新手到专家

相信看完上面两个链接的博客后,应该对于动态规划有一个新的认识和了解了。接下来就来看看LintCode上DP(下文我将以DP或者Dynamic Programming表示动态规划)的题目。

强烈推荐次博客,将动规分类总结做的很好:动态规划完整笔记

常见的动态规划题类型有好几种,有的动规是可以用滚动数组优化的,以下分别归类介绍:

1 坐标型动态规划题:

state:  f[x]表示我从起点走到坐标x,    f[x][y] 表示我从起点走到坐标x,y....
function: 研究走到x,y这个点之前的一步
initialize: 起点
answer:终点

437. Copy Books

给了一些书,让几个人去拷贝,一个人只能拷贝连续的几本书(比如拷贝了第1、2、3本),但是一个人不能跳着拷贝(比如就不能拷贝第1、3本,必须得连续的拷贝才行)。每本书都有不同的页数,翻译一页需要1分钟。

给定一个包含了页数的数组,和翻译员的人数。问你最少需要多少分钟才能把这些书翻译完。

这其实是一道坐标型动态规划题,假设动态规划的方程为dp[m][n],这就代表前m本书被前n个人拷贝所需的最少时间。接下来我们来找出动规的转移方程。为了方便起见,我们假设数组的起始index是从1开始的。

假设书的数组是[3, 2, 4],人数K=2. 那么我们需要求的就是dp[3][2],也就是前3本书被前2个人拷贝所需要的最少时间。我们从后往前倒推,不难得知dp[3][2]是有可能从dp[3][1],max(dp[2][1], pages[3]), max(dp[1][1], pages[2] + pages[3]) 演变而来的。但是这里面可以排除dp[3][1],因为如果前3本书都给一个人翻译了,那第二个人就无用武之地了,所以这样的转换是无意义的。

因此我们可以得到dp[3][2]的转移方程如下:

dp[3][2] = Min(Max(dp[2][1], pages[3]), Max(dp[1][1], pages[2] + pages[3]));

而且不难得知dp[1][n] = dp[1][3] = dp[1][2] = dp[1][[1] = pages[1]; 因为5个人翻译1本书跟1个人翻译一本书的时间是一样的,剩下的4个人没有用武之地,所以毫无意义。

也不难得知dp[n][1] = pages[1] + pages[2] + ... + pages[n]; 因为一个人翻译n本书的时间等于n本书的页数之和。

所以以上两个“不难得知”可以用于初始化dp方程。

我们把dp方程generalize一下如下所示:

DP[m][n] = MIN( MAX(DP[m-1][n-1], pages[m]), MAX(DP[m-2][n-1], pages[m-1]+pages[m]), ... , MAX(DP[1][n-1], pages[2]+pages[3]+...+pages[m]) );

数组长度是N,人数是K,则时间复杂度是O(N * N * K)

public class Solution {
    /**
     * @param pages: an array of integers
     * @param k: an integer
     * @return: an integer
     */
    public int calSum(int[] sum, int startIndex, int endIndex) {
        return sum[endIndex] - sum[startIndex];
    }
    public int copyBooks(int[] pages, int k) {
        if (pages == null || pages.length <= 0 || k < 1) {
            return 0;
        }
        
        int[][] f = new int[pages.length + 1][k + 1];
        int[] sum = new int[pages.length + 1];
        sum[0] = 0;
        
        // initialization
        f[0][0] = f[1][0] = f[0][1] = 0;
        f[1][1] = pages[0];
        for (int i = 1; i < f.length; i++) {
            f[i][1] = pages[i - 1] + f[i - 1][1];
            sum[i] = sum[i - 1] + pages[i - 1];
        }
        for (int i = 1; i < f[0].length; i++) {
            f[1][i] = f[1][1];
        }
        
        // DP function
        for (int i = 2; i <= pages.length; i++) {
            for (int j = 2; j <= k; j++) {
                f[i][j] = Integer.MAX_VALUE;
                for (int x = 1; x <= i - 1; x++) {
                    int result = Math.max(f[x][j - 1], calSum(sum, x, i));
                    f[i][j] = Math.min(result, f[i][j]);
                }
            }
        }
        
        // result
        return f[pages.length][k];
    }
}


438. Copy Books II

上一道题的变种,还是一个人只能翻译连续的几本书,不过数组变成了每个人翻译一本书所需要的时间了。

Given n books( the page number of each book is the same) and an array of integer with size k means k people to copy the book and the i th integer is the time i th person to copy one book). You must distribute the continuous id books to one people to copy. (You can give book A[1],A[2] to one people, but you cannot give book A[1], A[3] to one people, because book A[1] and A[3] is not continuous.) Return the number of smallest minutes need to copy all the books.

Given n = 4, array A = [3,2,4], .

Return 4( First person spends 3 minutes to copy book 1, Second person spends 4 minutes to copy book 2 and 3, Third person spends 4 minutes to copy book 4. )

我们用dp[i][j]表示前i本书被前j个人copy所需要的最小时间。假设的数组是[3, 2, 4],书本数K=4. 那么我们需要求的就是dp[4][3],也就是前4本书被前3个人拷贝所需要的最少时间。我们从后往前倒推,不难得知dp[4][3]是有可能从dp[4][2],max(dp[3][2], arr[3] * 1), max(dp[2][2], arr[3] * 2), max(dp[1][2], arr[3] * 3), max(dp[0][2], arr[3] * 4)演变而来的。

不难得知,dp[1][1] = arr[1] * 1, dp[2][1] =  arr[1] * 2,  dp[n][1] =  arr[1] * n

我们把dp方程generalize一下如下所示:

DP[m][n] = MIN( MAX(DP[m][n-1], arr[n] * 0), MAX(DP[m-1][n-1], arr[n] * 1), ... , MAX(DP[0][n-1], arr[n] * m) );

需要注意的是,如果发现arr[n] * m 已经大于DP[k][n-1]的时候,就不用再继续往下循环了,以此可以减少运行时间。

然后还可以用滚动数组来优化空间复杂度。

    public int copyBooksII(int n, int[] times) {
        return copyBooksWithRollingArray(n, times);
    }
    public int copyBooksWithRollingArray(int n, int[] times) {
        int len = times.length;
        // state
        int[][] dp = new int[n + 1][2];
        // initialize
        for (int i = 0; i <= n; i++) {
            dp[i][0] = times[0] * i;
        }
        // dp function
        for (int j = 1; j < len; j++) {
            for (int i = 1; i <= n; i++) {
                int a = j % 2;
                int b = 1 - a;
                dp[i][a] = Integer.MAX_VALUE;
                for (int k = 0; k <= i; k++) {
                    int tmp = Math.max(dp[i-k][b], times[j] * k);
                    dp[i][a] = Math.min(tmp, dp[i][a]);
                    if (dp[i-k][b] <= times[j] * k) {
                        break;
                    }
                }
            }
        }
        // result
        return dp[n][(len-1)%2];
    }
    public int copyBooksII2D(int n, int[] times) {
        int len = times.length;
        // state
        int[][] dp = new int[n + 1][len + 1];
        // initialize
        for (int i = 0; i <= n; i++) {
            dp[i][1] = times[0] * i;
        }
        // dp function
        for (int i = 1; i <= n; i++) {
            for (int j = 2; j <= len; j++) {
                dp[i][j] = Integer.MAX_VALUE;
                for (int k = 0; k <= i; k++) {
                    int tmp = Math.max(dp[i-k][j-1], times[j-1] * k);
                    dp[i][j] = Math.min(tmp, dp[i][j]);
                    if (dp[i-k][j-1] <= times[j-1] * k) {
                        break;
                    }
                }
            }
        }
        // result
        return dp[n][len];
    }


436. Maximal Square

这是一道二维坐标型动态规划。思路: 分析大问题的结果与小问题的相关性 f[i][j] 表示以i和j作为正方形的右下角可以扩展的最大边长

eg: 1  1  1

         1  1  1

         1  1  1 (traget)

traget 的值与3部分相关:

1. 青蓝色的正方形部分 f[i - 1][j - 1]
2. 紫粉红色 target上面的数组 up[i - 1][j] 即target上面的点 往上延伸能达到的最大长度
3. 红色的target左边的数组 left[i][j - 1]
如果 target == 1
f[i][j] = min (left[i][j - 1], up[i - 1][j], f[i - 1][j - 1]) + 1;
对于left和up数组 可以在intialization的时候用o(n^2)扫描整个矩阵实现!

优化思路1:
因为
f[i - 1][j] = left[i - 1][j]
f[i][j - 1] = up[i][j - 1]
这样 不需要额外的建立left和up数组
if (target == 1)  
f[i][j] = min (f[i - 1][j - 1], f[i][j - 1],f[i - 1][j]) + 1;

    public int maxSquare(int[][] matrix) {
        int ans = 0;
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return ans;
        }
        int n = matrix.length;
        int m = matrix[0].length;
        
        // 状态
        int[][] f = new int[n][m];
        // 初始化
        for (int i = 0; i < n; i++) {
            f[i][0] = matrix[i][0];
            ans = Math.max(f[i][0], ans);
        }
        for (int i = 0; i < m; i++) {
            f[0][i] = matrix[0][i];
            ans = Math.max(f[0][i], ans);
        }
        // 方程
        for (int i = 1; i < n; i++) {
            for (int j = 1; j < m; j++) {
                if (matrix[i][j] == 1) {
                    f[i][j] = Math.min(Math.min(f[i-1][j-1], f[i][j-1]), f[i-1][j]) + 1;
                } else {
                    f[i][j] = 0;
                }
                ans = Math.max(ans, f[i][j]);
            }
        }
        // 结果
        return ans * ans;
    }
优化思路2:
由于f[i][j]只和前3个结果相关
f[i - 1][j - 1]    f[i][j - 1]
f[i - 1][j]        f[i][j]  
故只需要保留一个2行的数组!!!
列上不能优化,因为2重循环的时候 下列的时候依赖于上列的结果,上列的结果需要保存到计算下列的时候用。
只能在行上滚动,不能行列同时滚动!!!
--------------------
state: 
f[i][j] 表示以i和j作为正方形右下角可以扩展的最大边长
function: 
if matrix[i][j] == 1
f[i % 2][j] = min(f[(i - 1) % 2 ][j], f[(i - 1) % 2][j - 1], f[i%2][j - 1]) + 1;
initialization:
f[i%2][0] = matrix[i][0]
f[0][j] = matrix[0][j]
answer:
max{f[i%2][j]}
    public int maxSquare_rollingArray(int[][] matr
  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值