dp[i] 代表以 i 结尾的arithmetic slice的个数。因此有下列关系
dp[i]=dp[i−1]+1,ifA[i]−A[i−1]=A[i−1]−A[i−2]
dp[i]=0,ifA[i]−A[i−1]≠A[i−1]−A[i−2]
public int numberOfArithmeticSlices(int[] A) {
int len = A.length;
if(len < 3) return 0;
int result = 0;
int[] dp = new int[len];
for(int i = 1; i < len-1; i++) {
if(A[i+1] - A[i] == A[i] - A[i-1]) {
dp[i+1] = dp[i] + 1;
result += dp[i+1];
} else {
dp[i+1] = 0;
}
}
return result;
}
给一个范围n,对方心里想了某个数字(范围在[1,n],具体大小你并不知道)。你每次猜一个数字,对方会告诉你猜得大了还是猜的小了或者猜对了。如果这次没有猜对,你需要支付你猜的这个数字的大小。问,给定一个范围n时,最少支付多少才能保证一定猜到这个数字呢?
这个问题有点绕,然而却是一个典型的minmax问题。可以先从简单的例子来想。
在一个范围[m,n]中猜数字,
1. 如果只有一个数,比如[5,5],那么一次就会猜中,就不用支付,因此最少支付0。
2. 如果范围里有两个数,比如[5,6],你猜5如果错了,你就知道是6了,需要支付5;你猜6如果错了,同样一定会知道答案,但是需要支付6。相比之下,支付5就可以一定猜出来。
3. 如果范围里有3个数,比如[5,6,7],你猜5,那么[6,7]中猜6一定知道答案,共支付11;你猜6,[5,5]和[7,7]不用支付就可以知道答案,共支付6;你猜7,那么[5,6]需要支付5,共支付12;比较11,6,12可知,最少支付为6。
4. 一般的情况,我们不管范围里有几个数,用 dp[i][j] 来表示在 [i,j] 范围内猜数字,最难猜到的数字让我们支付了多少(最少支付了多少可以保证猜到这个范围内的任何数字)。这相当于,如果我们猜任何一个 k∈[i,j] ,一定会得到需要猜的数字的是在 [i,k−1] 这个范围还是在 [k+1,j] 这个范围,相应再去找 dp[i][k−1] , dp[k+1][j] 来看这两个范围的保底花费。由于我们并不知道这个数字在哪个范围,因此要取最大的来保证我们一定可以猜出来最难猜的那个数字。接下来就是dp的递推式了
public int getMoneyAmount(int n) {
int dp[][] = new int[n+1][n+1];
for(int j = 1; j <= n; j++) {
for(int i = 1; i + j <= n; i++) {
dp[i][i+j] = Integer.MAX_VALUE;
for(int k = i; k <= i+j; k++) {
dp[i][i+j] = Math.min(dp[i][i+j], k + Math.max(dp[i][k-1], (k+1 > i+j)?0:dp[k+1][i+j]));
}
}
}
return dp[1][n];
}
给定两个字符串S,T。S中有多少个不同的子序列恰好等于T
这个题目很容易理解错,题目想问的并不是S和T有多少个不同的公共子序列。举一个例子
S=”rabbbit”, T=”rabbit”,则返回3。原因是:
S的三个子序列rab-bit , ra-bbit,rabb-it恰好等于T,因此返回3。
用 i∈[1,S.len] 和 j∈[1,T.len] 分别代表当前指向S和T的位置。用 dp[j][i] 代表S的前 i 个字母组成的子串中有多少个子序列恰好等于T的前 j 个字母组成的子串。则有,
当 S[i]=T[j] , dp[j][i]=dp[j−1][i−1]+dp[j][i−1]
当 S[i]≠T[j] , dp[j][i]=dp[j][i−1]
以上的递推式的意思是:
1. S增加一个字母,但是和T的当前字母不相等时,S不会增加新的子序列和T相等,因此 dp[j][i]=dp[j][i−1]
2. S增加一个字母,和T的当前字母相等时,S会增加 dp[j−1][i−1] 个子序列和T相等(任何一个相等的情况在末尾加上同一个字母依然符合条件),再计算上之前的序列得到 dp[j][i]=dp[j−1][i−1]+dp[j][i−1]
public int numDistinct(String s, String t) {
int lens = s.length();
int lent = t.length();
int[][] dp = new int[lent+1][lens+1];
for(int i = 0; i <= lens; i++)
dp[0][i] = 1;
for(int j = 1; j <= lent; j++) {
for(int i = j; i <= lens; i++) {
if(s.charAt(i-1) == t.charAt(j-1))
dp[j][i] = dp[j-1][i-1]+dp[j][i-1];
else
dp[j][i] = dp[j][i-1];
}
}
return dp[lent][lens];
}
一个三角形从顶向下寻找一条和最小的路径
public int minimumTotal(List> triangle) {
int layer = triangle.size();
if(layer == 0) return 0;
int[][] dp = new int[layer][layer];
int min = triangle.get(0).get(0);
dp[0][0] = min;
for(int i = 1; i < layer; i++) {
int curmin = Integer.MAX_VALUE;
for(int j = 0; j < i+1; j++) {
int left = (j-1 >= 0)? dp[i-1][j-1]: Integer.MAX_VALUE;
int right = (j < i)? dp[i-1][j]: Integer.MAX_VALUE;
if(i == 2 && j == 2) System.out.println("lr"+left+" "+right);
dp[i][j] = Math.min(left, right) + triangle.get(i).get(j);
curmin = Math.min(curmin, dp[i][j]);
}
min = curmin;
}
return min;
}
public int minimumTotal(List> triangle) {
int layer = triangle.size();
int[] dp = new int[layer];
if(layer == 0) return 0;
//initialiaze
for(int i = 0; i < layer; i++)
dp[i] = triangle.get(layer-1).get(i);
for(int j = layer-2; j >= 0; j--) {
for(int i = 0; i < j+1; i++)
dp[i] = Math.min(dp[i], dp[i+1])+triangle.get(j).get(i);
}
return dp[0];
}
给定一个n,求在[0, 10n )之间的所有数中,数里每一个数字都不一样的数的个数。
比如n=2时,返回91。即为除去100个数中除去{11,22,33…99}的数量。
dp[i]代表i位数字里每个数字都不一样的个数。则dp[1]=10. dp[2]=81.
dp[3]=dp[2]x8, dp[4]=dp[3]x7…直到dp[大于等于11]=0。
原因是,对于一个各个位都不同的两位数xy,由于是两位数那么第一位肯定不是0,添加上一个第三位i组成不同的三位数xyi,则i的选择有9种。根据乘法原理,dp[3]=dp[2]x8。其他的同理。
注意问题的拆分。
public class Solution {
public int countNumbersWithUniqueDigits(int n) {
int[] dp = new int[11];
dp[1] = 9;
for(int i = 2; i <= 10; i++)
dp[i] = dp[i-1]*(11-i);
int sum = 0;
for(int i = 1; i <= n; i++)
sum += dp[i];
return sum+1;
}
}
每一个数字都可以写成若干个完全平方数的和,比如12=4+4+4。给定一个数字n,求它最少可以表示为几个完全平方数的和。比如n=12, return 3.
dp[i] 表示数字 i 最少可以表示为的完全平方数的和的个数。那么 dp[i]=min(dp[i],dp[i−square]+1) ,其中 square 是每一个小于 i 的完全平方数。
这道题不具有贪心选择性,不能认为挑选完全平方数时越大越好。
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*j <= i; j++) {
dp[i] = Math.min(1+dp[i-j*j], dp[i]);
}
}
return dp[n];
}