参考: Krahets
斐波那契数列
int fibonacci(int n) {
if (n == 0) return 0; // 若求 f(0) 则直接返回 0
int[] dp = new int[n + 1]; // 状态定义:一维dp:第i个数的斐波那契数
dp[1] = 1; // 初始化 f(0), f(1)
for (int i = 2; i <= n; i++) { // 循环条件:遍历 2 ~ n
dp[i] = dp[i - 1] + dp[i - 2]; // 状态转移方程
}
return dp[n]; // 返回 f(n)
}
最优蛋糕售价
// 输入蛋糕价格列表 priceList ,求重量为 n 蛋糕的最高售价
int maxCakePrice(int n, int[] priceList) {
if (n <= 1) return priceList[n]; // 蛋糕重量 <= 1 时直接返回
int[] dp = new int[n + 1]; // 状态定义:一维dp:重量为i的蛋糕切割后的最大售价 + 初始化
for (int j = 1; j <= n; j++) { // 循环条件:遍历 1 ~ n
for (int i = 0; i < j; i++) // 选择规则:从 j 种组合中选择最高售价的组合作为 f(j)
dp[j] = Math.max(dp[j], dp[i] + priceList[j - i]); //状态转移方程
}
return dp[n];
}
爬楼梯
public int climbStairs(int n) {
int[] dp = new int[n + 1]; //状态定义:一维dp,第i层有dp[i]种爬楼梯方案
dp[0] = 1; // 初始化
dp[1] = 1;
for(int j = 2; j <= n; j++){ //循环:2~n
dp[j] = dp[j-1] + dp[j-2]; // 方程:从j-1爬上来的方案 + 从j-2爬上来的方案
}
return dp[n];
}
最大子数组和
public int maxSubArray(int[] nums) {
int[] dp = new int[nums.length]; // 1-D 代表以nums[i]结尾的最大子序列和
dp[0] = nums[0]; // 初始化
int rs = nums[0]; //结果返回的是dp数组中的最大值
for(int i = 1; i < nums.length; i++){ // 循环:1~num.length - 1
if(dp[i-1] < 0) dp[i] = nums[i]; // 方程:如果dp[i-1]<0,对最大和只有负贡献,只添加nums[i]
else dp[i] = dp[i-1] + nums[i];
rs = Math.max(rs, dp[i]);
}
return rs;
}
珠宝的最高价值
public int jewelleryValue(int[][] frame) {
int m = frame.length, n = frame[0].length;
int[][] dp = new int[m + 1][n + 1]; //2D 移动到i j 位置的最高价值
for(int i = 1; i <= m; i++){ //循环:遍历2D数组
for(int j = 1; j <= n; j++){
//方程:从上面来和从左边来的最大值 + 自身值
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]) + frame[i - 1][j - 1];
}
}
return dp[m][n];
}
小tip
这里开拓了新的一行一列来规避了首行/首列的特殊情况
解密数字
public int crackNumber(int ciphertext) {
String ciper = String.valueOf(ciphertext); //int转string
int[] dp = new int[ciper.length() + 1]; // dp代表到了第i个数字时的破译方案
dp[0] = 1; //初始化
dp[1] = 1;
for(int i = 2; i <= ciper.length(); i++){ //循环
/*
判断子串的范围: [10,25] 说明能翻译两次 其余只能翻译一次
*/
String tmp = ciper.substring(i-2, i);
if(tmp.compareTo("10") >= 0 && tmp.compareTo("26") < 0) dp[i] = dp[i-2] + dp[i - 1]; //方程
else dp[i] = dp[i - 1];
}
return dp[ciper.length()] ;
}
丑数
public int nthUglyNumber(int n) {
int[] dp = new int[n]; //记录丑数
dp[0] = 1;
int a = 0, b = 0, c = 0;
for(int i = 1; i < n; i++){
//丑数判断算法:丑数一定是由2 3 5当中某个数相乘得来,所以每次更新取最小丑数并让对应的下标++
dp[i] = Math.min(dp[a]*2,Math.min(dp[b]*3,dp[c]*5));
if(dp[i] == dp[a]*2) a++;
if(dp[i] == dp[b]*3) b++;
if(dp[i] == dp[c]*5) c++;
}
return dp[n-1];
}
统计结果概率
public double[] statisticsProbability(int num) {
double[] dp = new double[6]; //一枚骰子掷出的所有点数总和的概率
Arrays.fill(dp, 1.0/6.0);
for(int i = 2; i <= num; i++){ //从第二枚骰子开始
double[] tmp = new double[5*i + 1]; //第i枚骰子掷出的所有点数总和的概率
for(int j = 0; j < dp.length; j++){ //正向递推,注意是遍历dp算下一个dp
for(int k = 0; k < 6; k++){
tmp[j+k] += dp[j] / 6.0;
}
}
dp = tmp; //更新dp
}
return dp;
最长公共子序列
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[][] dp = new int[m+1][n+1]; //dp[i][j]代表text1[1:i]和text2[1:j]的最长子序列;
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
//如果最后一位相等 最长子序列++
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[m][n];
}
变种题
指思想是动归的题
二叉树的最大路径和
int maxSum = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
dfs(root);
return maxSum;
}
public int dfs(TreeNode root){
if(root == null) return 0;
int left = dfs(root.left);
int right = dfs(root.right);
// 一个子树内部的最大路径和
int innerMaxSum = root.val + left + right;
maxSum = Math.max(innerMaxSum,maxSum);
// 子树能向父节点“提供”的最大路径和
int outMaxSum = root.val + Math.max(Math.max(left,right),0);
return outMaxSum > 0 ? outMaxSum:0;
}