动态规划习题_70、 爬楼梯_198、打家劫舍_213.、打家劫舍 II_母牛生产_信件错排(***)_64、最小路径和

70、 爬楼梯(爬一爬二)

假设你正在爬楼梯。需要 n 步你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:

输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1.  1 步 + 1 步
2.  2 步
示例 2:

输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1.  1 步 + 1 步 + 1 步
2.  1 步 + 2 步
3.  2 步 + 1 步

到达n的“前一步”走法可以是:从n-1处爬1阶楼梯,或者从n-2处爬2阶楼梯。
必须从头走到尾才算一种走法,每一步的选择只是走法的一部分,而不是一个新的走法。从后向前思考比较正确。
S(n) = S(n-1) + S(n-2)

class Solution {
    public int climbStairs(int n) {
        if(n <= 0)
            return 0;
        if(n == 1)
            return 1;
        if(n == 2)
            return 2;
        int a = 1;
        int b = 2;
        int res = 0;
        for(int i=3;i<=n;i++){
            res = a + b;
            a = b;
            b = res;
        }
        return res;
    }
}

下面递归会超时!!!

class Solution {
    public int climbStairs(int n) {
        if(n <= 0)
            return 0;
        if(n == 1)
            return 1;
        if(n == 2)
            return 2;
       return climbStairs(n-1) + climbStairs(n-2); 
    }
}

198、打家劫舍(抢不不抢)

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例 1:

输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:

输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 。
class Solution {
    public int rob(int[] nums) {
        int len = nums.length;
        if(len <= 0)
            return 0;
        if(len == 1)
            return nums[0];
        if(len == 2)
            return Math.max(nums[0],nums[1]);
        int norob = nums[0];//不抢第二家(那就抢第一家)
        int rob = nums[1];//抢第二家(那就不抢第一家)
        for(int i=2;i<len;i++){
            int tmp = norob;
            norob = Math.max(norob,rob);//不抢第三家  (i+1家)
            rob = nums[i] + tmp;//抢第三家  (i+1家)
        }
        return Math.max(norob,rob);
    }
}

不抢第三家 ==》就是抢和不抢第二家的最大值。
抢第三家===》就是第三家的值加上不抢第二家的值。需要临时变量保存一个变量。


213.、打家劫舍 II

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例 1:

输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:

输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。
class Solution {

    public int robCycle(int[] nums,int low,int high){
        int len = high - low + 1;
        if(len == 1)
            return nums[low];
        if(len == 2)
            return Math.max(nums[low],nums[high]);
        int norob = nums[low];
        int rob = nums[low+1];
        for(int i=low+2;i<=high;i++){//注意:i的初始值和最后的值
            int tmp = norob;
            norob = Math.max(norob,rob);
            rob = nums[i] + tmp;//注意:这里是tmp
        }
        return Math.max(norob,rob);

    }
    public int rob(int[] nums) {
        if(nums.length == 0)
            return 0;
        if(nums.length == 1)
            return nums[0];
        int res1 = robCycle(nums,0,nums.length-2);
        int res2 = robCycle(nums,1,nums.length-1);
        return Math.max(res1,res2);
    }
}

母牛生产(今年的数量,去年的数量,新增的数量)

程序员代码面试指南-P181

题目描述:假设农场中成熟的母牛每年都会生 1 头小母牛,并且永远不会死。第一年有 1 只小母牛,从第二年开始,母牛开始生小母牛。每只小母牛 3 年之后成熟又可以生小母牛。给定整数 N,求 N 年后牛的数量。
新增的数量是距今3年前的牛的数量
f[i]代表第i年母牛的数量,那么f[i]=f[i-1]+f[i-3],意思是:
第i年的母牛头数 = 去年的木牛头数 + 新生的小牛头数(只有距第i年3年前的牛才能生小牛)。
写代码的时候i=1开始比较方便!


信件错排(考虑三封信件之间的关系)

题目描述:有 N 个 信 和 信封,它们被打乱,求错误装信方式的数量。
定义一个数组 dp 存储错误方式数量dp[i] 表示前 i 个信和信封的错误方式数量。假设第 i 个信装到第 j 个信封里面,而第 j 个信装到第 k 个信封里面。根据 i 和 k 是否相等,有两种情况:

i==k,交换 i 和 k 的信后,它们的信和信封在正确的位置,但是其余 i-2 封信有 dp[i-2] 种错误装信的方式。由于 j 有 i-1 种取值,因此共有 (i-1)*dp[i-2] 种错误装信方式。
i != k,交换 i 和 j 的信后,第 i 个信和信封在正确的位置,其余 i-1 封信有 dp[i-1] 种错误装信方式。由于 j 有 i-1 种取值,因此共有 (i-1)*dp[i-1] 种错误装信方式。
综上所述,错误装信数量方式数量为:
dp[ i ] = (i - 1) * dp[ i - 2] + (i - 1) * dp[ i - 1];
dp[N] 即为所求。


64、最小路径和


给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例:

输入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
class Solution {
    public int minPathSum(int[][] grid) {
        if(grid == null)
            return 0;
        int m = grid.length;        
        int n = grid[0].length;
        int[][] dp = new int[m][n];
        dp[0][0] = grid[0][0];
        for(int i=1;i<m;i++){
            dp[i][0] = dp[i-1][0] + grid[i][0];  
        }
        for(int j=1;j<n;j++){
            dp[0][j] = dp[0][j-1] + grid[0][j];
        }
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){

                dp[i][j] = Math.min(dp[i][j-1],dp[i-1][j]) + grid[i][j];
            }
        }
        return dp[m-1][n-1];
    }
}

注意:先把第0行和第0列进行初始化(第一行只能从左一直往右走,而第一列只能从上一直往下走,并且将经过的点累加起来。)
状态转移方程为dp[i][j] = min{dp[i - 1][j], dp[i][j - 1]} + m[i][j].可以用一个二维的dp矩阵来求解.对于dp矩阵,第一行和第一列只会有一种走法,就是从左到右或者从上到下的累加,所以可以先进行初始化,然后其他元素就可以用过转移方程一个一个填充.
边界条件:if(m==null||m.length==0||m[0]==null||m[0].length==0)
优化:使用一个一维数组在进行这个迭代过程。

class Solution {
    public int minPathSum(int[][] grid) {
        if(grid == null)
            return 0;
        int row = grid.length;
        int col = grid[0].length;

        int[] dp = new int[col];
        dp[0] = grid[0][0];

        //计算第一行的最短路径
        for(int j=1;j<col;j++){
            dp[j] = dp[j-1] + grid[0][j];
        }
        //计算除了第一行的其他最小路径和
        for(int i=1;i<row;i++){
            //dp[0]代表每一行最左边的dp,后一行的dp覆盖前一行的dp
            dp[0] = dp[0] + grid[i][0];
            for(int j=1;j<col;j++){               
                dp[j] = Math.min(dp[j-1],dp[j]) + grid[i][j];                
            }
        }
        return dp[col-1];
    }
}

本题中的压缩空间的方法几乎可以应用到所有的二维动态规划的题目,通过一个数组滚动更新的方式无疑节省了大量的空间。没有优化之前,求某个位置动态规划值的过程是在矩阵中进行两次寻址,程序的常数时间也得到了一定程度的加速,但是空间压缩的方法是有局限性的,如果本题改成打印具有最小路径和的路径,就不能使用空间压缩的方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章