【温故知新】动态规划之求最长公共子序列长度、零钱兑换、最大正方形(java代码)

简言

动态规划也是应该是研一选修的算法分析与设计课接触的比较多,个人感觉自己的代码实现能力还是稍微差一些,虽然对于这些场景的一个思路演示比较容易理解,关键还是得多动手操作操作。

求最长公共子序列长度

问题描述
给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。
思路
若这两个字符串没有公共子序列,则返回 0。
思路:这一题当初在算法课上也是着重讲了的,求的是公共子序列,印象中设置了三个方向来判定这个字符是否属于公共子序列,当然这里只需要求长度,那实现又稍微简单一些。
二维数组实现

public int longestCommonSubsequence(String text1, String text2)     {
        if(text1.length()==0||text2.length()==0) return 0;
        int [][]dp=new int[text1.length()+1][text2.length()+1];
        //for(int i=0;i<)
        for(int i=1;i<=text1.length();i++){
            for(int j=1;j<=text2.length();j++){
                if(i==0||j==0) dp[i][j]=0;
               if(text1.charAt(i-1)==text2.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[text1.length()][text2.length()];
    }

当然这里是可以优化的,二维数组在存储上其实是有多余的可省略的数据,如果用一维数组其实也是可以实现,只需要把三个方向的值确定好就行。
一维数组实现

public int longestCommonSubsequence(String text1, String text2)     {
        //使用一维数组也可以进行存储,因为二维数组的存储有多余的可省略的规律其中last对应dp[i][j]左上角的数,temp对应正上方的数,dp[j-1]对应左边的数
        if(text1.length()==0||text2.length()==0) return 0;
        int m=text1.length(),n=text2.length();
        int []dp=new int[n+1];
        int temp=0;
        for(int i=1;i<=m;i++){
            int last=0;//这个是初始化均为0,判定dp[j]之前的数
            for(int j=1;j<=n;j++){
                temp=dp[j];
                if(text1.charAt(i-1)==text2.charAt(j-1)) dp[j]=last+1;
                else dp[j]=Math.max(dp[j-1],temp);

                last=temp;
            }
        }
        return dp[n];
  }

零钱兑换

问题描述
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1

思路
首先可以确定的是,这种题综合性很强,背包dfs、bfs啥的好像都可以用,可以考虑使用动态规划的方法去解决,自顶向下,自下向上都是可以实现,自顶向下的思路就需要考虑金额问题,即设定F(S)为组成金额最少的硬币数,最后一枚硬币的面值是A,那么就构成转移方程:
F(S)=F(S-X) +1
我们并不知道最后一枚硬币面值是多少,就需要枚举每个硬币面值额为x(i)并选择其中的最小值,同时还要保证每个子问题最多只被计算一次。
自顶向下实现

public int coinChange(int[] coins, int amount) {
    if (amount < 1) return 0;
    return coinChange(coins, amount, new int[amount]);
  }

  private int coinChange(int[] coins, int num, int[] count) {
    if (num < 0) return -1;
    if (num == 0) return 0;
    if (count[num - 1] != 0) return count[num - 1];
    int min = Integer.MAX_VALUE;
    for (int coin : coins) {
      int num = coinChange(coins, rem - coin, count);
      if (res >= 0 && res < min)
        min = 1 + res;
    }
    count[num - 1] = (min == Integer.MAX_VALUE) ? -1 : min;
    return count[num - 1];
  }

相对于自顶向下,个人感觉自底向上的思路实现会更加通俗易懂,在这里我们仍然定义F(S)为组成金额所需最少的硬币,那在这里假设最后一枚硬币的面值是c,那么满足转移方程F(S)=F(S-C)+1直至F(0)、F(1),不考虑负值情况。
自底向上实现

public int coinChange(int[] coins, int amount) {
        if(coins.length==0) return -1;
        int[]dp = new int[amount+1];
        int max=amount+1;
        Arrays.fill(dp,max);
        dp[0]=0;
        for(int i=1;i<=amount;i++){
            for(int j=0;j<coins.length;j++){
                if(coins[j]<=i) dp[i]=Math.min(dp[i],dp[i-coins[j]]+1);
            }
        }
        return dp[amount]>amount?-1:dp[amount];
    }

最大正方形

问题描述:
在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。
思路:
这一题直接上代码吧,跟求公共子序列长度的思路很类似,只需要比较当前为1的值的左上角、左边和上边是否为1即可,得到最大正方形的边长。

public int maximalSquare(char[][] matrix) {
        int m=matrix.length;
        if(m<1) return 0;
        int n=matrix[0].length;
        int [][]dp=new int[m+1][n+1];
        int max=0;
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                if(matrix[i-1][j-1]=='1'){
                    dp[i][j]=1+Math.min(dp[i-1][j-1],Math.min(dp[i-1][j],dp[i][j-1]));
                    max=Math.max(max,dp[i][j]);
                }
            }
        }
        return max*max;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值