简言
动态规划也是应该是研一选修的算法分析与设计课接触的比较多,个人感觉自己的代码实现能力还是稍微差一些,虽然对于这些场景的一个思路演示比较容易理解,关键还是得多动手操作操作。
求最长公共子序列长度
问题描述:
给定两个字符串 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;
}