轻松搞定动态规划算法题

动态规划

写在前面

本人对动态规划的理解是我们知道初始值的一些状态,去求未来的值。而初始值到未来值的变化是有统一的公式的,类似于数学归纳法。

这里说一下,说懒了的dp要点;

  1. 原问题到自问题的拆分,怎么把原问题拆分成自问题。
  2. 初始状态,边界状态,我们要根据它来递推到我们最终状态
  3. 状态转移方程,关键
  4. 退出状态,结果状态。也就是你状态转移方程的退出条件。

LeetCode 70 爬楼梯

记得这道题是up校招的时候猫眼电影的二面面试问我的,那时候up没刷过题,给出了一个n方的方式。记忆满深刻的。但是代码没写出来就给了思路,所以错过了猫眼。不过hr小姐姐还是蛮好看的。

思路:

  1. 其实这道题主要是理解一个累加的思路,就是不论你想获取n阶中的哪阶的方法,它的方法始终可以通过 n-1阶和n-2阶迈1步或者两步得到,所以可以得到递推公式:f(n) = f(n-1)+f(n-2);
  2. n-1 和 n-2 最终回溯肯定可以通过 第 1阶和第2阶得到

代码:

public int climbStairs(int n) {
        if(n==0) return 0;
        if(n==1) return 1;
        int[] dp = new int[n];
        dp[0] = 1;
        dp[1] = 2;
        for(int i=2;i<n;i++){
            dp[i] = dp[i-1]+dp[i-2];
        }
        return dp[n-1];
    }

LeetCode 198 打家劫舍

思路:

  1. 我们从第一家开始遍历,来讨论偷不偷这家。 对于第一家我们没有参照对象肯定要偷。到了第二家,我们偷的最大值等于什么? 肯定是第一家和第二家哪个钱多偷哪个?
  2. 到了第三家,这时候我们偷的方式存在两种,第一次偷的钱+ 第三家的钱, 和第二次偷的钱,比较哪个最大偷哪个。
  3. 那么就得到了状态转移方程:dp[i] =Math.max(dp[i-2]+nums[i],dp[i-1]);
  4. 结束状态就是dp[n];

代码:

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

LeetCode 53. 最大子序和

思路:

  1. 这里注意的是求连续子序列的最大值,连续就说明了一定要挨着,那么我们从第0个数算起,最大子序组就是它本身,第1个数呢? 如果第0个大于0 那么第一个就是它本身+第0个,如果小于0,那就是第一个本身。
  2. 1 里面求得是已i为结尾得最大子序和。我们还需要一个 值来记录全局最大值,也就是全局最大子序和

代码:

public int maxSubArray(int[] nums) {
        int size = nums.length;
        int dp[] = new int[size];
        dp[0] =  nums[0];
        int res = dp[0];
        for(int i =1; i<size;i++){
            dp[i] = Math.max(dp[i-1]+nums[i],nums[i]);
            res = Math.max(dp[i],res);
        }
        return res;
    }

LeetCode 322. 零钱兑换

思路:

  1. 最先想到的是贪心,但是不行,只适用于倍数钱币;
  2. 回溯方法也可以,但是会超时。
  3. dp, 确定状态:应该表示固定金钱最少需要几个其它金钱组成;状态转移方程的确定: 考虑dp[i]可以怎么得到? 像爬楼梯是通过 dp[i-1] dp[i-2]的和,这题不行。 上面其余两种方程也不行的。 回到实际应用场景,比如我们求dp[11] ,假设我们的方程是 dp[x]+y ,这里的dp[x]相当于x金钱它组合的最少方案,我们先不考虑它具体是多少,那么我们想让dp[11]最小,因为dp[x]是定值,那么我们让y最小,y=0 不符合题意,那可以让y等于1,怎么让y等于1? 那就从x能通过拿取一个硬币变成y。也就是dp[y] = min (dp[y-硬币1],dp[y-硬币2],。。。)+1; 也就是能转移到y的值 一定是y-硬币集合。

代码:

 public int coinChange(int[] coins, int amount) {

        //定义dp状态数组,数组下标表示钱数,数组value表示钞票张数
        int dp[] = new int[amount+1];
        //初始化状态数组
        Arrays.fill(dp,-1);
        dp[0] = 0;
     	//遍历总金钱
        for(int i = 1; i<=amount;i++){
            //遍历硬币数组
            for(int j =0 ; j<coins.length;j++){
                //如果当前总钱数大于当前所选硬币,并且总钱数-当前硬币的值已经存在dp数组,
                //我们才继续执行,否则剪枝叶
                if(i-coins[j]>=0 && dp[i-coins[j]]!=-1){
                    if(dp[i]== -1 || dp[i] > (dp[i-coins[j]]+1)){
                        //状态转移方程
                         dp[i] = dp[i-coins[j]]+1;
                    }
                }
            }
        }
        return dp[amount];
    }

LeetCode 120. 三角形最小路径和

思路:

  1. ​ 这道题是很经典的bp题,而其bp思路也很明显,状态用二维数组表示dp【】【】, 表示到达该点的最小路径。加入我们从顶往下来看,那么第一层的【2】就可以转换成dp[0,0]=2. 再往下,对于[3,4]中的3 它的最小肯定是dp[0,0]+3,同理4也一样。 再看第三层的5,它可以从3 或者4到达,但是最小的原则,就是min(dp[i-1,j],dp[i-1,j-1]). 那么转移方程就出来了, dp[i,j] = min(dp[i-1,j],dp[i-1,j-1])+c[i,j]; 退出状态就是最后一排dp的最小值。
  2. 从顶往下的情况递推公式dp[i,j] = min(dp[i-1,j],dp[i-1,j-1])+c[i,j];再特殊情况不适用,因为边界的值只有一个选择,所以我们可以考虑从底往上走,这时候不需要考虑边界,因为上面肯定比下面少。这个时候的转移方程就是dp[i,j] = min(dp[i+1,j],dp[i+1,j+1])+c[i,j],包含了所有情况。

代码:

public int minimumTotal(List<List<Integer>> triangle) {
        if(triangle.size()==0) return 0;
        int row = triangle.size();
        int column = triangle.get(row-1).size();
        int[][] dp = new int [row][column];
        //初始化最后一行的dp值
        for(int i =0; i<column;i++){
            dp[row-1][i] = triangle.get(row-1).get(i);
        }
        //从倒数第二行开始
        for(int i = triangle.size()-2;i>=0;i--){
            for(int j =0 ; j<triangle.get(i).size();j++){
                //遍历每一列,计算dp值
                dp[i][j] = Math.min(dp[i+1][j],dp[i+1][j+1])+triangle.get(i).get(j);
            }
        }
        return dp[0][0];
    }

LeetCode 300 最长递增子序列

思路:

  1. 状态定义:这是最难的,既然是求最长子序列,那么就关心长度了。 dp[i]就表示第i个数字的最大长度可以不?这样不行,因为你没法和dp[i+1]产生联系,因为你不知道你nums[i+1]到底是不是比dp[i]中的大还是小。那么就修改成以第i个数为结尾的最长数值。
  2. 状态转移方程: dp[i]= dp[j]+1; 如果nums[i] 大于nums[j];
  3. 结束状态: 利用res 记录每个dp的最大值,遍历结束返回res 。

代码:

public int lengthOfLIS(int[] nums) {
        if(nums == null || nums.length == 0) return 0;
        int dp[] = new int[nums.length];
        dp[0] =1;
        int res = 1;
    //遍历每一个数
        for(int i = 1; i<nums.length;i++){
            //遍历比它小的dp值
            for(int j = 0; j<i;j++){
                if(nums[i]>nums[j]){
                    dp[i] = Math.max(dp[i],dp[j]+1);
                }
            }
            //如果没找到比它小的值,赋予默认值1
            if(dp[i]==0) dp[i]=1;
            res = Math.max(res,dp[i]);
        }
        return res;
    }

Tips: 可以利用贪心+二分查找优化成nlongN的时间复杂度。 第一层for 循环 放入数组,第二次循环在已知排好序的数组里面二分查找,待插入元素的位置。

LeetCode 64 最小路径和

思路:

  1. 状态定义:dp[i,j]表示到达坐标 i, j点的最少步数;
  2. 转移方程: dp[i,j] = grid[i,j] +min(dp[i-1,j],dp[j,j-1])
  3. 初始状态dp[0,0] = grid[0,0];
  4. 结束状态 dp[n-1,n-1];

代码:

public int minPathSum(int[][] grid) {
        int row = grid.length;
        int col = grid[0].length;
        int dp[][] = new int[row][col];
        dp[0][0] = grid[0][0];
        int x_offset[] = {-1,0};
        int y_offset[] = {0,-1};
        for(int i =0; i<row;i++){
            for(int j = 0; j<col;j++){
                if(dp[i][j] != 0) continue;
                for(int k =0; k<2;k++){
                    int xNew = i+x_offset[k];
                    int yNew = j+y_offset[k];
                    if(xNew<0 || xNew>=row || yNew<0||yNew>=col){
                        continue;
                    }else{
                        if(dp[i][j] == 0){
                            dp[i][j] = dp[xNew][yNew]+grid[i][j];
                        }else{
                             dp[i][j] = Math.min(dp[i][j],dp[xNew][yNew]+grid[i][j]);
                        }
                       
                    }
                }
            }
        }
        return dp[row-1][col-1];
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值