最大子序和
int preMax = nums[0];
int res = preMax;
for (int i = 1; i < nums.length; i++) {
//前一位置是负数,对本位置没贡献
preMax = Math.max(preMax,preMax + nums[i]);
res = Math.max(res,preMax);
}
return res;
爬楼梯
public int climbStairs(int n) {
int[] dp = new int[n + 1];
dp[0] = 1;
if (n >= 1) {
dp[1] = 1;
}
if (n >= 2) {
dp[2] = 1;
}
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
买卖股票的最佳时机
public int maxProfit(int[] prices) {
int minPrice = prices[0];
int curMaxProfit = 0;
int res = 0;
for (int i = 1; i < prices.length; i++) {
minPrice = Math.min(minPrice,prices[i]);
curMaxProfit = prices[i] - minPrice;
res = Math.max(curMaxProfit,res);
}
return res;
}
最长回文子串
public String longestPalindrome(String s) {
char[] chars = s.toCharArray();
boolean[][] dp = new boolean[s.length()][s.length()];
int maxLen = 0, begin = 0;
for (int right = 0; right < s.length(); right++) {
for (int left = 0; left < right; left++) {
if (chars[left] == chars[right]) {
//两个字符相等时长度为1、2、3时必为回文字符串
if (right - left < 3) {
dp[left][right] = true;
} else {
dp[left][right] = dp[left + 1][right - 1];
}
if (dp[left][right] && right - left + 1 > maxLen) {
maxLen = right - left + 1;
begin = left;
}
}
}
}
return s.substring(begin,begin + maxLen);
}
跳跃游戏
public boolean canJump(int[] nums) {
int maxFar = 0;
//只算到倒数第二位,因为最后一位不用跳
for (int i = 0; i < nums.length - 1; i++) {
//最远距离能否到达当前位置
if (maxFar >= i) {
//能够到达就比较最远距离和当前位置能跳到的位置,如果当前位置能跳到的位置比最远距离大就更新
maxFar = Math.max(maxFar,i + nums[i]);
}
}
//最远距离能否到达最后一位
return maxFar >= nums.length - 1;
}
public boolean canJump1(int[] nums) {
if (nums == null) {
return false;
}
boolean[] dp = new boolean[nums.length];
dp[0] = true;
for (int right = 1; right < nums.length; right++) {
for (int left = 0; left < right; left++) {
// 如果能跳到位置left,就看从left开始跳,能否跳到right
if (dp[left] && left +nums[left] >= right) {
//能的话表明right是能达的
dp[right] = true;
break;
}
}
}
return dp[nums.length - 1];
}
最小路径和
public int minPathSum(int[][] grid) {
int row = grid.length;
int col = grid[0].length;
int[][] dp = new int[row][col];
//第一行
dp[0][0] = grid[0][0];
for (int i = 1; i < col; i++) {
dp[0][i] = dp[0][i - 1] + grid[0][i];
}
//第一列
for (int i = 1; i < row; i++) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for (int i = 1; i < row; i++) {
for (int j = 1; j < col; j++) {
dp[i][j] = grid[i][j] + Math.min(dp[i - 1][j],dp[i][j - 1]);
}
}
return dp[row - 1][col - 1];
}
路径总和
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
//第一行
for (int i = 0; i < n; i++) {
dp[0][i] = 1;
}
//第一列
for (int i = 0; i < m; i++) {
dp[i][0] = 1;
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
解码方法
public int numDecodings1(String s) {
int[] dp = new int[s.length() + 1];
char[] chars = s.toCharArray();
if (chars[0] == '0') {
return 0;
}
//没有字符和只有一个字符只有一种编码方法
dp[0] = 1;
dp[1] = 1;
//dp[0]表示空字符串 dp[1]表示第一个字符 dp[2]表示前二个字符
for (int i = 1; i < s.length(); i++) {
//当前数字为0
if (chars[i] == '0') {
//前面的是1或2才能一起编码 dp[i] = dp[i - 2] 10 20
if (chars[i - 1] == '1' || chars[i - 1] == '2') {
//由于s[1]指第二个下标,对应为dp[2],所以dp的下标要比s大1,故为dp[i+1]
dp[i + 1] = dp[i - 1];
//否则编码方法为0 00
} else {
dp[i + 1] = 0;
}
//前一位数字是1则可以自己编码也可以一起编码,这个if已经排除了当前数字是0的情况 所以只会组成11-19的数 12
} else if (chars[i - 1] == '1') {
dp[i + 1] = dp[i] + dp[i - 1];
//前一位数字是2则当前数字
} else if (chars[i - 1] == '2') {
int one = chars[i] - '0';
//当前数字要大于等于1小于等于6才能一起编码 21
if (one >= 1 && one <= 6) {
dp[i + 1] = dp[i] + dp[i - 1];
//否则只能单独编码 27
} else {
dp[i + 1] = dp[i];
}
//前一位数字不是1、2 当前数字也不是0,即只能单独编码 37
} else {
dp[i + 1] = dp[i];
}
}
return dp[s.length()];
}
public int numDecodings2(String s) {
int[] dp = new int[s.length() + 1];
char[] chars = s.toCharArray();
if (chars[0] == '0') {
return 0;
}
//没有字符只有一种编码方法
dp[0] = 1;
dp[1] = 1;
//dp[0]表示空字符串 dp[1]表示第一个字符 dp[2]表示前二个字符
for (int i = 1; i < s.length(); i++) {
//自己能单独解码,i是1-9
//两位数字组成1-9 大于26不包含10的倍数:第一个if执行,第二个if不执行 02 27
//两位数字组成11-19 21-26:第一个和第二个if都执行 12 22
//两位数字组成10 20:第一个if不执行,第二个if执行
//两位数字组成0 大于26的10的倍数:两个if都不执行 00 30
//总体数据范围是0 1-9 10 11-19 20 21-26 >26(不包含10的整数倍)
if (chars[i] != '0') {
dp[i + 1] = dp[i];
}
int sum = (chars[i-1] -'0')* 10 + chars[i] - '0';
if (sum >= 10 && sum <= 26) {
dp[i + 1] += dp[i - 1];
}
}
return dp[s.length()];
}
public int numDecodings3(String s) {
int[] dp = new int[s.length() + 1];
char[] chars = s.toCharArray();
if (chars[0] == '0') {
return 0;
}
//没有字符只有一种编码方法
dp[0] = 1;
dp[1] = 1;
//dp[0]表示空字符串 dp[1]表示第一个字符 dp[2]表示前二个字符
for (int i = 1; i < s.length(); i++) {
int sum = (chars[i-1] -'0')* 10 + chars[i] - '0';
//自己可以编码也能和前一位编码10 < a < 20 20 < a < 27
if ((sum > 10 && sum < 20) || (sum > 20 && sum < 27)) {
dp[i+1] = dp[i] + dp[i - 1];
//只能和前一位编码10或20
} else if (sum == 10 || sum == 20) {
dp[i+1] = dp[i-1];
//只能自己编码 02或27
} else if (sum < 10 && sum > 0 || sum > 26 && sum % 10 != 0) {
dp[i+1] = dp[i];
}//不能编码 00 或 30
else {
dp[i+1] = 0;
}
}
return dp[s.length()];
}
单词拆分
public boolean wordBreak(String s, List<String> wordDict) {
/**
* 单词就是物品,字符串s就是背包,单词能否组成字符串s,就是问物品能不能把背包装满。
* 拆分时可以重复使用字典中的单词,说明就是一个完全背包!
*/
HashSet<String> set = new HashSet<>(wordDict);
int length = s.length();
boolean[] dp = new boolean[length];
for (int right = 0; right < length; right++) {
//先看0到right在不在list里
if (set.contains(s.substring(0,right + 1))) {
dp[right] = true;
continue;
}
//在看1到right的所有子串在不在list里 例如right=7时 看1到7的所有子串eetcode etcode tcode code ode de e是否在list里
//从后往前遍历效率会高点
for (int left = right; left > 0; left--) {
//code能被拆分,再看leet是不是在list里 leet在list里 所以leetcode都在list里可以被拆分
if (dp[right] = dp[left-1] && wordDict.contains(s.substring(left,right + 1))) {
dp[right] = true;
break;
}
}
}
return dp[length - 1];
}
最大乘积连续子数组
public int maxProduct(int[] nums) {
int[][] dp = new int[nums.length][2];
//最小值
dp[0][0] = nums[0];
//最大值
dp[0][1] = nums[0];
int res = dp[0][1];
for (int i = 1; i < nums.length; i++) {
//正数
if (nums[i] > 0) {
//当前最大值为当前数 * 之前最大值,如果之前最大值为负数,当前最大值为当前数
dp[i][1] = Math.max(nums[i] * dp[i-1][1],nums[i]);
//当前最小值为当前数 * 之前最小值,如果之前最小值为正数,当前最小值为当前数
dp[i][0] = Math.min(nums[i] * dp[i-1][0],nums[i]);
//负数
} else if (nums[i] < 0) {
//当前最大值为当前数 * 之前的最小值,如果之前最小值为正数,当前最大值为当前数
dp[i][1] = Math.max(nums[i] * dp[i-1][0],nums[i]);
//当前最小值为当前数 * 之前最大值,如果之前最大值为负数,当前最小值为当前数
dp[i][0] = Math.min(nums[i] * dp[i-1][1],nums[i]);
}
//更新结果
res = Math.max(res,dp[i][1]);
}
return res;
}
打家劫舍
public int rob(int[] nums) {
int length = nums.length;
int[] dp = new int[length];
//当前位置偷,前一位置不能偷
//当前位置不偷,前一位置可偷
if (length > 1) {
dp[0] = nums[0];
}
if (length > 2) {
dp[1] = Math.max(dp[0],nums[1]);
}
if (length < 3) {
return dp[length-1];
}
for (int i = 2; i < length; i++) {
dp[i] = Math.max(dp[i-1],dp[i-2] + nums[i]);
}
return dp[length-1];
}
最大正方形
public int maximalSquare(char[][] matrix) {
int r = matrix.length;
int c = matrix[0].length;
int[][] dp = new int[r][c];
int res = 0;
//第一行
for (int i = 0; i < c; i++) {
if (matrix[0][i] == '1') {
dp[0][i] = 1;
}
res = Math.max(dp[0][i],res);
}
//第一列
for (int i = 0; i < r; i++) {
if (matrix[i][0] == '1') {
dp[i][0] = 1;
}
res = Math.max(dp[i][0],res);
}
for (int i = 1; i < r; i++) {
for (int j = 1; j < c; j++) {
if (matrix[i][j] == '1') {
int min = Math.min(dp[i-1][j],dp[i][j-1]);
dp[i][j] = Math.min(min,dp[i-1][j-1]) + 1;
res = Math.max(dp[i][j],res);
}
}
}
return res * res;
}
完全平方数
public int numSquares(int n) {
int[] dp = new int[n + 1];
Arrays.fill(dp,n+1);
//base case 整数0需要0个完全平方数
dp[0] = 0;
//背包容量
for (int bagCap = 1; bagCap <= n; bagCap++) {
//物品重量
for (int weight = 1; weight * weight <= bagCap; weight++) {
//当前整数需要的最少完全平方个数 = (当前整数 - 一个完全平方数)需要的最少平方数 + 1
//+1是减去的那个完全平方数 当前整数减一个完全平方数之后的数有可能是由同一个完全平方数取多次组成的,即一个物品取多次,完全背包
dp[bagCap] = Math.min(dp[bagCap],dp[bagCap- weight * weight] + 1) ;
}
}
return dp[n];
}
最长递增子序列
public int lengthOfLIS(int[] nums) {
int length = nums.length;
int[] dp = new int[length];
Arrays.fill(dp,1);
int res = 0;
for (int right = 0; right < length; right++) {
for (int left = 0; left < right; left++) {
//当前数字比right小就表明以right结尾的最长递增子序列是当前数字结尾的最长递增子序列+1
//当前位置的最长递增子序列等于结尾比当前位置小的最长递增子序列+1
//例如10,9,2,5,3,7,101,18
//当前位置7的最长递增子序列是以2结尾的最长递增子序列+1 以5结尾的最长递增子序列+1 以3结尾的最长递增子序列+1
//以2结尾的最长递增子序列是1 1+1=2 以5结尾的最长递增子序列是2 2+1=3 以3结尾的最长递增子序列是2 2+1=3,所以以7结尾的最长递增子序列是3
if (nums[left] < nums[right]) {
dp[right] = Math.max(dp[right],dp[left] + 1);
}
}
res = Math.max(res,dp[right]);
}
return res;
}
最佳买卖股票时机含冷冻期
public int maxProfit1(int[] prices) {
int length = prices.length;
int[][] dp = new int[length][3];
if (length < 2) {
return 0;
}
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[0][2] = 0;
for (int i = 1; i < length; i++) {
//前一天卖出了股票的最大收益,然后当天不卖不买就变成了当天没卖股票的最大收益
//第i天不持有股票的情况有两种:1、第i-1天也不持有股票 2、第i-1天因为卖了股票而不持有股票
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][2]);
//前一天没卖股票的最大收益今天买了股票就变成了当天持有股票的最大收益,前一天没卖今天才能买,这里体现了冷冻期
//第i天持有股票有两种情况:1、第i-1天也持有股票 2、第i-1天不持有股票而且不是因为卖了股票才不持有股票的,第i天买入股票
dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);
//前一天持有股票的最大收益今天卖了股票就变成了当天卖了股票的最大收益
//第i天是冷冻期只有一种情况,第i-1天持有股票且当天卖出
dp[i][2] = dp[i-1][1] + prices[i];
}
return Math.max(dp[length-1][0],dp[length-1][2]);
}
零钱兑换
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp,amount+1);
dp[0] = 0;
int res = Integer.MAX_VALUE;
for (int bagCap = 1; bagCap <= amount; bagCap++) {
for (int weightIndex = 0; weightIndex < coins.length; weightIndex++) {
if (coins[weightIndex] > bagCap) {
break;
}
dp[bagCap] = Math.min(dp[bagCap],dp[bagCap-coins[weightIndex]] + 1);
}
res = Math.min(res,dp[bagCap]);
}
return res;
}
回文子串
public int countSubstrings(String s) {
int length = s.length();
char[] chars = s.toCharArray();
int res = 0;
boolean[][] dp = new boolean[length][length];
for (int right = 0; right < length; right++) {
for (int left = 0; left <= right; left++) {
if (chars[left] == chars[right]) {
if (right - left < 3) {
dp[left][right] = true;
} else {
dp[left][right] = dp[left+1][right-1];
}
if (dp[left][right]) {
res++;
}
}
}
}
return res;
}
分割等和子集
public boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if (sum % 2 != 0) {
return false;
}
sum = sum/2;
int length = nums.length;
boolean[][] dp = new boolean[length][sum + 1];
//先看第一件物品
if (nums[0] <= sum) {
dp[0][nums[0]] = true;
}
for (int bagCap = 1; bagCap <= sum; bagCap++) {
for (int weightIndex = 1; weightIndex < length; weightIndex++) {
//装不下直接继承上一个结果
if (nums[weightIndex] > bagCap) {
dp[weightIndex][bagCap] = dp[weightIndex-1][bagCap];
//装得下:
// 不装就继承上一个结果dp[weightIndex-1][bagCap]
// 装就背包容量减少的结果dp[weightIndex-1][bagCap-nums[weightIndex]]
} else {
dp[weightIndex][bagCap] = dp[weightIndex-1][bagCap] || dp[weightIndex-1][bagCap-nums[weightIndex]];
}
}
}
return dp[length-1][sum];
}
目标和
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int num : nums) {
sum += num;
}
int length = nums.length;
int amount = (sum + target)/2;
//目标数大于数组和肯定凑不了
if (target > sum || (sum + target)%2 != 0) {
return 0;
}
int[][] dp = new int[length+1][amount+1];
dp[0][0] = 1;
for (int i = 1; i <= length; i++) {
dp[i][0] = dp[i-1][0];
}
for (int bagCap = 1; bagCap <= amount; bagCap++) {
for (int weightIndex = 1; weightIndex <= length; weightIndex++) {
//装得下总方案数是不装的方案数+装的方案数
if (nums[weightIndex-1] <= bagCap) {
dp[weightIndex][bagCap] = dp[weightIndex-1][bagCap] + dp[weightIndex-1][bagCap-nums[weightIndex-1]];
//装不下总方案数是不装的方案数
} else {
dp[weightIndex][bagCap] = dp[weightIndex-1][bagCap];
}
}
}
return dp[length][amount];
}
数字翻译成字符串
String s = String.valueOf(num);
int length = s.length();
int[] dp = new int[length];
char[] chars = s.toCharArray();
dp[0] = 1;
if (length == 1) {
return 1;
}
int ten = (chars[0] - '0')*10 + (chars[1] - '0');
if (ten >= 0 && ten <= 25) {
dp[1] = 2;
} else {
dp[1] = 1;
}
for (int i = 2; i < length; i++) {
int sum = (chars[i-1] - '0')*10 + (chars[i] - '0');
//能自己编码,也能和前一位一起编码
if (sum >= 10 && sum <= 25) {
dp[i] = dp[i-1] + dp[i-2];
//只能自己编码,不能和前一位一起编码
} else {
dp[i] = dp[i-1];
}
}
return dp[length-1];
剪绳子
public int cuttingRope(int n) {
int[] dp = new int[n+1];
dp[2] = 1;
for (int right = 3; right <= n; right++) {
//在left剪一刀
for (int left = 1; left <= right; left++) {
dp[right] = Math.max(dp[right],Math.max(left * (right - left),left * dp[right - left]));
}
}
return dp[n];
}
剪绳子||
public int cuttingRope1(int n) {
int res = 1;
if (n == 2) {
return 1;
}
if (n == 3) {
return 2;
}
if (n == 4) {
return 4;
}
while (n > 4) {
res *= 3;
n -= 3;
res %= 1000000007;
}
return (res * n) % 1000000007;
}
\
丑数
public int nthUglyNumber(int n) {
int a = 0, b = 0, c = 0;
int[] dp = new int[n];
dp[0] = 1;
for (int i = 1; i < n; i++) {
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];
}
n个骰子的点数
public double[] dicesProbability(int n) {
int[][] dp = new int[n+1][6 * n + 1];
double[] res = new double[5 * n + 1];
for (int i = 1; i <= 6; i++) {
dp[1][i] = 1;
}
//多少个骰子
for (int dice = 2; dice <= n; dice++) {
//出现的点数
for (int count = dice; count <= 6 * dice; count++) {
//要凑的数 例如要求2个骰子点数7出现的次数是由1个骰子点数6出现的次数加上一个点数为1的骰子凑出来的
for (int i = 1; i <= 6; i++) {
//出现的点数要大于要凑的数 例如出现的点数为3 只能由2和1凑出3 不能由3凑出3因为没有点数0
if (count <= i) {
continue;
}
dp[dice][count] += dp[dice-1][count-i];
}
}
}
for (int i = n; i <= 6 * n; i++) {
//Math.pow(6,n)是出现的总次数 两个骰子出现的点数为6的2次方即36
res[i-n] = dp[n][i]/(Math.pow(6,n));
}
return res;
}