343. 整数拆分
思路:题目要求给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。例如10可以拆分成4和6而6又可以继续拆分成3,3也可以继续拆分,那么实际上是可以用DFS解题的,但是除了DFS还有没有更简单的方法呢?显然动态规划是个不错的选择,因为就刚才那个例子,反过来3的拆分会影响6,6的拆分会影响10。接下来就可以定义动态方程了->
定义dp[i]为正整数i能活得的最大乘积,给定一个j<i,i可以拆分成j和i-j,j可以继续拆分也可以不拆分,取一个最大的即可,所以有:
dp[i] = Math.max(dp[i],Math.max(dp[i-j]*j,j*(i-j)));
所以接下来遍历i和j即可,完整的代码如下:
public int integerBreak(int n) {
int[] dp = new int[n+1];
dp[1] = 1;
for(int i=2;i<n+1;i++){
for(int j=1;j<i;j++){
dp[i] = Math.max(dp[i],Math.max(dp[i-j]*j,j*(i-j)));
}
}
return dp[n];
}
类似的还有279. 完全平方数题:
279. 完全平方数题
思路:这一题的要求是给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。类似的,我们也可以用动态规划解题,先定义动态规划方程:
dp[i] = Math.min(dp[i],dp[i-j*j]+1);
其中i-j*j>=0,也就是j<=Math.sqrt(i)。这一题有一点和上一题不一样,因为我们要求的是最小的分割个数,所以一开始dp数组需要初始化,否则一开始dp数组都是0就已经是最小的,这样就会导致更新的错误,所以我们将dp数组初始化为最大值即可,完整的代码如下:
public int numSquares(int n) {
int[] dp = new int[n+1];
dp[1] = 1;
for(int i=2;i<=n;i++){
dp[i] = Integer.MAX_VALUE;
for(int j=1;j<=Math.sqrt(i);j++){
dp[i] = Math.min(dp[i],dp[i-j*j]+1);
}
}
return dp[n];
}
其中dp[i]的初始化也可以为i,因为最差的情况就是全部分割成1,这也是最大的值。
接下来来一个稍微难一点的:
91. 解码方法
思路:题目:
一条包含字母 A-Z 的消息通过以下方式进行了编码:
'A' -> 1
'B' -> 2
...
'Z' -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。
首先,如果我们从头开始分割,那么我们分割的时候每次需要考虑的是我们是分割一个字符还是两个呢?显然这两种我们是都需要考虑的,也就是说我们对于每个字符串,我们首先考虑分割一个的,然后再考虑分割两个的,然后剩下的字串按照相同的方式分割,那么这就是递归了。
(方法1:递归法 )在分割的时候,并不是所有的字符串一定都可以分成两位数的,因为最大的两位数也不可以超过26,所以每次分割的时候需要提前判断一下是否满足条件。->
首先我们定义递归的出口:
if (s.length() == start) {
return 1;
}
//以0位开始的数是不存在的
if (s.charAt(start) == '0') {
return 0;
}
一开始我写这个递归的出口时当s.length() == start时返回的是0,导致最后的错误,这里一旦是0,后面的累加将都会是0,无法正确的递归,递归的出口也是记录分割方法个数(结束)的就一种形式,所以不可以写0。
所以完整的代码如下:
public int numDecodings(String s) {
if (s == null || s.length() == 0) {
return 0;
}
return digui(s, 0);
}
//递归的套路,加一个index控制递归的层次
public int digui(String s, int start) {
//递归的第一步,应该是加终止条件,避免死循环。
if (s.length() == start) {
return 1;
}
//以0位开始的数是不存在的
if (s.charAt(start) == '0') {
return 0;
}
//递归的递推式应该是如果index的后两位小于等于26,
// digui(s, start) = digui(s, start+1)+digui(s, start+2)
// 否则digui(s, start) = digui(s, start+1)
int ans1 = digui(s, start + 1);
int ans2 = 0;
if (start < s.length() - 1) {
int ten = (s.charAt(start) - '0') * 10;
int one = (s.charAt(start + 1) - '0');
if (ten + one <= 26) {
ans2 = digui(s, start + 2);
}//ans1 = digui(s, start + 1);不能放else里,因为动态规划里有dp[len] = 1;(预先在最开始加了一个一)所以ans1 = digui(s, start + 1);先得放外面迭代一次。
}
return ans1 + ans2;
}
(方法2:动态规划)
从我写的注释也可以看出,这一题也适合用动态规划解题,我们定义dp[i]为字符串s的前i个字符组合成编码方法的总数。首先初始化动态规划数组:
int one = Integer.valueOf(s.substring(i - 1, i));
if (one != 0) {
dp[i] += dp[i - 1];
}
if (s.charAt(i - 2) == '0') {
continue;
}
int two = Integer.valueOf(s.substring(i - 2, i));
if (two <= 26) {
dp[i] += dp[i - 2];
}
对dp数组的初始化也是非常关键的,dp[0]相当于上一题递归的出口,所以设置为1,初始化如下:
dp[0] = 1;
dp[1] = s.charAt(0) == '0' ? 0 : 1;
完整的代码如下:
public int numDecodings(String s) {
if (s == null || s.length() == 0) {
return 0;
}
int n = s.length();
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = s.charAt(0) == '0' ? 0 : 1;
for (int i = 2; i <= n; i++) {
int one = Integer.valueOf(s.substring(i - 1, i));
if (one != 0) {
dp[i] += dp[i - 1];
}
if (s.charAt(i - 2) == '0') {
continue;
}
int two = Integer.valueOf(s.substring(i - 2, i));
if (two <= 26) {
dp[i] += dp[i - 2];
}
}
return dp[n];
}