面试准备------动态规划问题

动态规划

1. Coin Change

问题:

输入一组硬币值 以及需要兑换的总数 输出可兑换的最少硬币数量 不可兑换输出-1

示例:

Input: coins = [1, 2, 5], amount = 11
Output: 3 
Explanation: 11 = 5 + 5 + 1

解析:
构造一个dp数组 长度为amount+1 每个位置上存放当前amount 能够兑换的最少的硬币数量 初始dp[0]=0 其余初始值为INTEGER.MAX

递推公式为 dp[i]=Math.min(dp[i],dp[i-coins[j]+1)

关键点:

使用dp存储 每个位置上取值 dp[0]=0 是为了使当前amount 恰好等于当前coins值时 为dp[当前amount]赋值为1
对于dp[i] 
当i取值从1到amount时
首先将dp[i] 赋值为Integer.MAX_VALUE
然后遍历coins 如果当前的amount大于等于当前的coins(等于的时候说明只需要当前coins一个就可以兑换)比较当前的dp[i] 和dp[amount-coins[j]]+1 的大小  

代码:

class Solution {
    public int coinChange(int[] coins, int amount) {
           int[]dp=new int[amount+1];
        dp[0]=0;
        for(int i=1;i<=amount;i++){
           
            dp[i] = Integer.MAX_VALUE;
            for(int j=0;j<coins.length;j++){
                if(i>=coins[j]&&dp[i-coins[j]]!=Integer.MAX_VALUE){
                    dp[i]=Math.min(dp[i],dp[i-coins[j]]+1);
                    
                }
    
            }
        }
        return dp[amount]==Integer.MAX_VALUE?-1:dp[amount];
    }
}
2. Coin Change 2

问题

输入一组硬币 以及需要兑换的总值  输出一共有多少种兑换的方式

示例

Input: amount = 5, coins = [1, 2, 5]
Output: 4
Explanation: there are four ways to make up the amount:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

解析

同样适用dp数组 只不过在此使用二维数组 dp[coins+1][amount+1]    其中dp[i][j] 代表使用coins中前i种硬币 兑换 amount=j能够有多少种兑换的方法
递推公式为 dp[i][j]=dp[i-1][j]+(dp[i-1][j-amount[i-1]]+dp[i-1][j-2*amount[i-1]]+......)
仔细观察可得 dp[i-1][j-amount[i-1]]+dp[i-1][j-2*amount[i-1]]+......=dp[i][j-amount[i-1]]

代码

class Solution {
    public int change(int amount, int[] coins) {
       int[][] dp=new int[coins.length+1][amount+1];
       dp[0][0]=1;
       for(int i=1;i<=coins.length;i++){
         dp[i][0]=1;
         for(int j=1;j<=amount;j++){
           dp[i][j]=dp[i-1][j]+(j-coins[i-1]>=0?dp[i][j-coins[i-1]]:0);
            }
        }
        return dp[coins.length][amount];
         }
       }

仔细观察上述表达式 在递归的过程中只和 dp[i-1] [j] 以及dp[i][j-coins[i-1]]有关 其实可以压缩为一维数组

class Solution {
    public int change(int amount, int[] coins) { 
    int []dp=new int[amount+1];
    dp[0]=1;
    for(int coin:coins){
      for(int j=coin;j<=amount;j++){
        dp[j]=dp[j]+dp[j-coin]
        
      }
    }
    return dp[amount];
    }
    }
3.最小路径和

问题:

Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path

示例:

[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
Output: 7
Explanation: Because the path 1→3→1→1→1 minimizes the sum.

解析:

使用dp[][]数组 初始化dp[0][i] =dp[0][i-1]+grid[0][i]  初始化 dp[j][0] =dp[j-1]+grid[j][0]
递推公式 dp[i][j]=Math.min(dp[i-1][j],dp[i],[j-1])+grid[i][j]
因为dp[i-1][j] 在一维数组中可以用自身表示 所以可以 将其简化为一维数组 dp[i]=Math.min(dp[i],dp[i-1])+grid[i][j]

代码:

class Solution {
    public int minPathSum(int[][] grid) {
        // int row= grid.length;
        // if(row==0){
        //     return 0;
        // }
        // int col=grid[0].length;
        // int[][]dp=new int[row][col];
        // dp[0][0]=grid[0][0];
        // for(int i=1;i<col;i++){
        //     dp[0][i]=dp[0][i-1]+grid[0][i];
        // }
        // for(int j=1;j<row;j++){
        //     dp[j][0]=dp[j-1][0]+grid[j][0];
        // }
        // for(int i=1;i<row;i++){
        //     for(int j=1;j<col;j++){
        //         dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];
        //     }
        // }
        // return dp[row-1][col-1];
        }
        }
一维数组
class Solution {
    public int minPathSum(int[][] grid) {
   int row=grid.length;
        int col=grid[0].length;
        int[]dp=new int[col];
        dp[0]=grid[0][0];
        for(int i=1;i<col;i++){
            dp[i]=dp[i-1]+grid[0][i];
        }
        for(int i =1;i<row;i++){
            dp[0]=dp[0]+grid[i][0];
            for(int j=1;j<col;j++ ){
                dp[j]=Math.min(dp[j],dp[j-1])+grid[i][j];
            }            
        }
        return dp[col-1];
        }
        }
4.Unique Path & Unique Path 2

问题:

对于一个m行 n列的矩阵 只能向下或者向右走 总共有多少种不同的走法到达右下角

解析

此题和最短路径和是一摸一样的解法 只是在初始化第一行的时候初始值全部为1

代码:

class Solution {
    public int uniquePaths(int m, int n) {
        // int[][]dp= new int[m][n];
        // for(int i=0;i<n;i++){
        //     dp[0][i]=1;
        // }
        // for(int j=0;j<m;j++){
        //     dp[j][0]=1;
        // }
        // for(int i=1;i<m;i++){
        //     for(int j=1;j<n;j++){
        //         dp[i][j]=dp[i-1][j]+dp[i][j-1];
        //     }
        // }
        // return dp[m-1][n-1];
        
        int[]dp=new int[n];
        for(int i=0;i<n;i++){
            dp[i]=1;
        }
        for(int i= 1;i<m;i++){
            dp[0]=1;
            for(int j= 1;j<n;j++){
                dp[j]=dp[j]+dp[j-1];
            }
        }
        return dp[n-1];
        
    }
}
Unique Path 2 解析 
对于Unique Path 2 主要的区别是增加了一个阻碍矩阵 某些位置不可通过
这样使得初始化的时候不仅要观察阻碍矩阵对应位置是否可通过 同时还要确保该位置的前一位置可通过

代码

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        // int m=obstacleGrid.length;
        // int n=obstacleGrid[0].length;
        // int[][] dp=new int[m][n];
        // dp[0][0]=obstacleGrid[0][0]==1?0:1;
        // for(int i=1;i<n;i++){
        //     dp[0][i]=obstacleGrid[0][i]==1?0:1;
        //     dp[0][i]=(dp[0][i-1]==1&&dp[0][i]==1)?1:0;
        // }
        // for(int j=1;j<m;j++){
        //     dp[j][0]=obstacleGrid[j][0]==1?0:1;
        //     dp[j][0]=(dp[j][0]==1&&dp[j-1][0]==1)?1:0;
        // }
        // for(int i=1;i<m;i++){
        //     for(int j=1;j<n;j++){
        //         dp[i][j]=dp[i-1][j]+dp[i][j-1];
        //         dp[i][j]=obstacleGrid[i][j]==1?0:dp[i][j];
        //     }
        // }
        // return dp[m-1][n-1];
        
        int m=obstacleGrid.length;
        int n=obstacleGrid[0].length;
        int[]dp=new int[n];
        dp[0]=obstacleGrid[0][0]==1?0:1;
        for(int i=1;i<n;i++){
            dp[i]=(dp[i-1]==1&&obstacleGrid[0][i]==0)?1:0;
        }
        for(int i=1;i<m;i++){
            dp[0]=(dp[0]==1 && obstacleGrid[i][0]==0)?1:0;
            for(int j=1;j<n;j++){
                dp[j]=(obstacleGrid[i][j]==0)?(dp[j-1]+dp[j]):0;
            }
        }
        return dp[n-1];
    }
}
5. 最长递增子序列

问题:

在一个数组中寻找最长的递增子序列

示例:

Input: [10,9,2,5,3,7,101,18]
Output: 4 
Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4. 

解析1

可以创建一个数组int[]result=new int[nums.length]  以每一个元素i分别作为比较对象
遍历其之前的所有元素 如果元素i大于元素j 则表示其可以作为元素j之后的递增序列的一员 因此在元素j的基础上可以长度加1  同时与当前长度比较取较长的一个.

代码:

  if(nums.length==0){
            return 0;
        }
        int[]result=new int[nums.length];
        int max=1;
        for(int i=0;i<nums.length;i++){
            result[i]=1;
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j]){
                    result[i]=Math.max(result[i],result[j]+1);
                    max=Math.max(max,result[i]);
                }
            }
        }
        return max;

解析2:

以[10,9,2,5,3,7,101,18] 为例 遍历数组 在结果数组中寻找第一个比该值大的元素并替换 
可以使用二分查找binarySearch(int[] nums,int target)寻找一个数组中第一个比target大的位置  

代码

 public int lengthOfLIS(int[] nums) {
        int []result=new int[nums.length];
        for(int i=0;i<nums.length;i++){
            result[i]=Integer.MAX_VALUE;
        }
        for(int i=0;i<nums.length;i++){
            int pos=binarySearch(result,nums[i]);
            result[pos]=nums[i];
                
        }
        for(int j=nums.length-1;j>=0;j--){
            if(result[j]!=Integer.MAX_VALUE){
                return j+1;
            }
        }
        return 0;
        
        
    }
    private int binarySearch(int[] nums,int target){
        int begin=0;
        int end=nums.length-1;
        while(begin<end){
            int mid=(end-begin)/2+begin;
            if(nums[mid]<target){
                begin=mid+1;
            }else if(nums[mid]>target){
                end=mid;
            }else{
                return mid;
            }
        }
        return end;
        
 
    }
6.最长公共子序列
7.回溯法

对于回溯方法而言 ,最典型的应用场景是不易顺次的通过多重循环得到最终的结果(因为很难确定内层循环的次数),此时可以通过回溯的方法 首先确定外层的一个取值 然后逐层推进
例如

 Letter Combinations of a Phone Number
每个按键代表若干个字母 给定若干个按键  确定所有字母的组合
此时最先想到的就是逐层遍历 但是因为层数不确定 所以无法穷尽循环,可以回溯的方法 对于每一层的操作都是相同的 即 给定前一层的结果以及该层所需要遍历的内容 然后进行拼接的操作 如果到达终止条件返回 如果不到达终止条件 则继续递归下去
class Solution {
     Map<String, String> phone = new HashMap<String, String>() {{
        put("2", "abc");
        put("3", "def");
        put("4", "ghi");
        put("5", "jkl");
        put("6", "mno");
        put("7", "pqrs");
        put("8", "tuv");
        put("9", "wxyz");
  }};
        List<String> result =new ArrayList<>();
    public void backUpon(String pre,String cur){
        if(cur.length()==0){
            result.add(pre);
        }else{
        String nOperate=cur.substring(0,1);
        String nTarget=phone.get(nOperate);
        for(int i=0;i<nTarget.length();i++){
            String tmp=pre+nTarget.charAt(i);
            backUpon(tmp,cur.substring(1));
            
        }
        }
    }
    public List<String> letterCombinations(String digits) {
        if(digits.length()!=0){
            backUpon("",digits);
        }        
        return result;
        
      
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值