动态规划

我觉得动态规划主要有两点:

1. 当前这个点要与不要
2. 由小推向大(也就是所谓子问题),以空间换时间。

01背包

例:有5个物品,w = [2,2,6,5,4],v = [6,3,5,4,6],背包的容量为10。

1. 一个物品一个物品的慢慢放
2. 拿到一个物品到底是放还是不放

当前物品不放,则价值为上一次放的情况;如果放了,则价值为把前i-1个物品放入(当前容量-这个物品容量)的价值加当前物品的价值。显然,谁大要谁。

012345678910
000000000000
100666666666
200
300
400
500

初始化:i个物品装入容量0的背包中,价值肯定0;0个物品放入容量j的背包,价值也是0 。
dp[1][?]处放第一个物品,放不下,价值是放0物品时得0;放进去价值是6;
dp[2][?]处放第二个物品,放不下,价值和上一行的一样;?处放得下,放,价值为前(2-1)个物品放入(2-2)容量中,得,0+3 = 3;不放,就是上一次的6,;所以?处不放新物品。
以此类推。。。
最后放到第5个物品,便能得到最大价值。

      /**
     * @param c : 表示背包容量
     * @param n : 表示商品个数
     * @param w : 表示商品重量
     * @param p : 表示商品价值
     *
     */
    public static int KnapSack(int c,int n,int w[],int p[]){
        //c[i][m] 表示前i件物品放入容量为m的背包时的最大价值
        int[][] dp = new int[n+1][c+1];

        for(int i=0;i<n+1;i++){
            dp[i][0] = 0;
        }
        for(int j=0;j<c+1;j++){
            dp[0][j] = 0;
        }
        for(int i=1;i<n+1;i++){
            for(int j=1;j<c+1;j++){
                if(w[i-1] <= j){   //放得下,谁大要谁
                    dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-w[i-1]]+p[i-1]);

                }else{  //放不下,就是上一次的情况
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[n][c];
    }

最长公共子序列

例:给定两个字符串 “abcbdb”和 “acbbabdbb”,返回这两个字符串的最长公共子序列的长度。
1. 从字符串开头慢慢比
2. 当前字符相等了,公共长度则为上一次的加1;不等了,要么不要字符1要么不要字符2,显然,谁的结果长要谁。

“”acbbabdbb
0123456789
“”00000000000
a10111111111
b201
c301
b401
d501
b601

初始化:有一方为空,公共长度都是0;
dp[1][?]处第一个字符的比较结果,a==a,则长度为:上一次(即左上对角线)[0][0]+1 = 1; a != c,不要a(即上一行)则长度为0;不要c(即左一列)则长度为1。所以公共长度为1
dp[2][?]处第二个字符的比较结果,b != a,不要b(即上一行),长度为1;不要a(即左一列)长度为0 。所以公共长度为1 。
以此类推。。。
最后字符比较完,便能得到最长结果。

public int longestCommonSubsequence(String str1, String str2) {
        if (str1 == null || str2 == null || str1.length() == 0 || str2.length() == 0)
            return 0;
        int[][] dp = new int[str1.length() + 1][str2.length() + 1];
        for (int i = 1; i <= str1.length(); i++) {
            for (int j = 1; j <= str2.length(); j++) {
                if (str1.charAt(i - 1) == str2.charAt(j - 1))	//当前相等
                    dp[i][j] = dp[i - 1][j - 1] + 1;		//上一次加这一个
                else												//不等
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);	//谁长要谁
            }
        }
        return dp[str1.length()][str2.length()];
    }

编辑距离

例:给你两个单词 word1 = “horse”和word2 = “ros”,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

1. 从字符串开头慢慢比
2. 当前字符相等了,则编辑距离和上一次一样;不等了,则进行如下的三种编辑,显然,谁小要谁。

你可以对一个单词进行如下三种操作:

  1. 插入一个字符
  2. 删除一个字符
  3. 替换一个字符
“”horse
012345
“”0012345
r111?
o221
s331

初始化:一个字符编辑成一个字符串,这个简单。
dp[1][?]处第一个字符的编辑距离,r != h,则进行编辑:不要r(即上一行)(对于另一个字符串而言就是加上h,效果一样)则编辑距离为1+1=2;不要h(即左一列)则编辑距离为1+1=2。替换,编辑距离为上一次加1,即0+1 = 1 。所以编辑距离为1,替换一次就行。
以此类推。。。
最后字符比较完,便能得到最小编辑距离。

public int minDistance(String word1, String word2) {
        int n1 = word1.length();
        int n2 = word2.length();

        int[][] dp = new int[n1 + 1][n2 + 1];
        // 第一行
        for (int j = 1; j <= n2; j++) dp[0][j] = dp[0][j - 1] + 1;
        // 第一列
        for (int i = 1; i <= n1; i++) dp[i][0] = dp[i - 1][0] + 1;

        for (int i = 1; i <= n1; i++) {
            for (int j = 1; j <= n2; j++) {
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) 
                	dp[i][j] = dp[i - 1][j - 1];		
                else{
                    int min = Math.min(dp[i][j - 1], dp[i - 1][j]);	//删或者说加
                    dp[i][j] = Math.min(dp[i - 1][j - 1], min ) + 1;	//替换
                }
            }
        }
        return dp[n1][n2];  
    }

打家劫舍

例:如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。房屋存放金额[1,2,3,1],计算不触动警报装置的情况下 ,能够偷窃到的最高金额。

1. 一间一间慢慢偷
2. 当前房屋到底偷还是不偷。

当前房屋偷了,则价值上上一次的价值加这一次的价值;如果不偷,则价值为上一次的价值。显然,谁大要谁。

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

零钱兑换

例:给定不同面额的硬币 coins = [1, 2, 5]和一个总金额 amount = 11。计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

1. 一块钱一块钱的慢慢换
2. 当前这个硬币到底要不要。

当前硬币不要,则个数没变化;如果要,则个数变为兑换(要兑换的钱减去当前这个面额)的个数加1。显然,谁小要谁。

public static int coinChange(int[] coins, int amount) {
        //dp[i]表示钱数为i时最小硬币数
        int dp[] = new int[amount + 1];
        //初始化钱数为i时的硬币数
        Arrays.fill(dp,amount +1);
        dp[0] = 0;
        //钱数为0时的最小硬币数也为0
        for(int i = 1;i<=amount;i++){
            for(int coin : coins){
                if(i >= coin){
                    dp[i] = Math.min(dp[i],dp[i-coin]+1);
                }
            }
        }
        return dp[amount]> amount ? -1:dp[amount];
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值