class Solution {
public int fib(int n) {
if (n<=1){
return n;
}
int[] dp = new int[n+1];
dp[0] = 0;
dp[1] = 1;
for (int i=2;i<=n;i++){
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
}
class Solution {
//方法一:斐波那契数组
public int climbStairs(int n) {
int[] dp = new int[n+2];
dp[0]=0;
dp[1]=1;
dp[2]=2;
if(n<=2){return n;}
for (int i=3;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
}
class Solution {
public int minCostClimbingStairs(int[] cost) {
int[] dp = new int[cost.length+1];
dp[0]=0;
dp[1]=0;
for(int i=2;i<=cost.length;i++){
dp[i] = Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
}
return dp[cost.length];
}
}
class Solution {
public int integerBreak(int n) {
int[] dp = new int[n+1];//dp[i]代表分解数字i最大乘积
dp[2]=1;
for (int i = 3;i<=n;i++){
for(int j = 1;j<=i/2;j++){
//把数i 分成j和j-i 如果j-i可以再分 或者j-i不能再分
dp[i] = Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j]));
}
}
return dp[n];
}
}
class Solution {
public int numTrees(int n) {
int[] dp = new int[n+1];
dp[0] = 1;
dp[1] = 1;
for (int i=2;i<=n;i++){
for(int j=0;j<i;j++){
dp[i] += dp[j]*dp[i-j-1];
}
}
return dp[n];
}
}
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];//记录到每个点的路径总数
//初始化
for(int i = 0;i<m;i++){
dp[i][0]=1;
}
for(int j = 0;j<n;j++){
dp[0][j]=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];
}
}
我说句题外话,就是何时使用【回溯】,何时使用【动态规划】,用大白话说,就是:
首先看取值范围,递归回溯一维数组,100就是深度的极限了(何况本题是100²)
如果是求走迷宫的【路径】,必然是回溯;如果是走迷宫的【路径的条数】,必然是dp--------(这个竟然屡试不爽!!!!)
与T62相比 只要遇到障碍就为0 具体体现在初始化和递推公式
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[][] dp = new int[m][n];
if (obstacleGrid[0][0] == 1 || obstacleGrid[m-1][n-1] == 1){
return 0;//特殊情况 起点和终点有障碍
}
//初始化 在没有遇到障碍前 左一列 上一行均为1
for (int i=0;i<m && obstacleGrid[i][0] ==0;i++){
dp[i][0]=1;
}
for (int j=0;j<n && obstacleGrid[0][j] ==0;j++){
dp[0][j]=1;
}
//递归公式
for (int i = 1;i<m;i++){
for(int j=1;j<n;j++){
if(obstacleGrid[i][j]==0){
dp[i][j]= dp[i-1][j]+ dp[i][j-1];
}else{
dp[i][j] = 0;
}
}
}
return dp[m-1][n-1];
}
}
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
思路分析
就是数组和的一半, target=sum/2
每一个元素的数值既是重量,也是价值
dp[j]表示 背包总容量(所能装的总重量)是j,dp[j]表示 背包总容量(所能装的总重量)是j
代码实现
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
int target = 0;
for (int i=0;i<nums.length;i++){
sum = sum + nums[i];
}
if( sum%2!=0){return false;}//奇数
else{target=sum/2;}
int[] dp = new int[target+1];
for(int i =0;i<nums.length;i++){//遍历物品
for(int j = target;j>=nums[i];j--){
dp[j] = Math.max(dp[j],dp[j-nums[i]]+nums[i]);
}
}
return dp[target] == target;
}
}
class Solution {
//尽量把石头分为两堆质量相同
public int lastStoneWeightII(int[] stones) {
int sum = 0;
for (int i=0;i<stones.length;i++){
sum += stones[i];
}
int target = sum/2;//最大重量
int[] dp = new int[target+1];
for (int i=0;i<stones.length;i++){
for(int j = target;j>=stones[i];j--){
dp[j] = Math.max(dp[j],dp[j-stones[i]]+stones[i]);//得到一半石头的最大重量
}
}
return sum - 2 * dp[target];
}
}
dp[j] += dp[j - nums[i]]
难点二:关于求出x后的一些细节
target>sum(绝对值)
x不是整数,则无法实现
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for(int i=0;i<nums.length;i++){
sum += nums[i];
}
//记加法为x,减法为y,有x+y=sum,x-y=target;
//x= (sum+target)/2
if ((sum+target)%2!=0){return 0;} //若无法平分 则不存在
if ((target<0&&sum<-target) || (target>0&&sum<target)){return 0;}//target比sum大 不存在
int size = (sum+target)/2; //背包容量
if (size<0){size = -size;} //负数转正
int[] dp = new int[size+1];
dp[0] = 1;
for(int i=0;i<nums.length;i++){
for(int j = size;j>=nums[i];j--){
dp[j] += dp[j-nums[i]];
}
}
return dp[size];
}
}
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int[][] dp = new int[m+1][n+1];
for(String str:strs){//遍历物品
int zeroNum = 0;
int oneNum = 0;
for (char c: str.toCharArray()){
if(c=='1'){oneNum++;}
else{zeroNum++;}
}//计算物品重量即1和0的个数
for(int i = m;i >= zeroNum;i--){
for(int j = n; j >= oneNum;j--){
dp[i][j] = Math.max(dp[i][j],dp[i-zeroNum][j-oneNum]+1);
}
}
}
return dp[m][n];
}
}
问题描述
有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
解决方案
对于纯完全背包问题,其for循环的先后循环是可以颠倒的!
如果问装满背包有几种方式的话? 遍历顺序
组合数,(1,1,2)和(2,1,1)算一种情况,那就必须是 先物品后背包
排列数,必须是 先背包后物品
代码实现
//先遍历物品,再遍历背包
private static void testCompletePack(){
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWeight = 4;
int[] dp = new int[bagWeight + 1];
for (int i = 0; i < weight.length; i++){ // 遍历物品
for (int j = weight[i]; j <= bagWeight; j++){ // 遍历背包容量
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
for (int maxValue : dp){
System.out.println(maxValue + " ");
}
}
//先遍历背包,再遍历物品
private static void testCompletePackAnotherWay(){
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWeight = 4;
int[] dp = new int[bagWeight + 1];
for (int i = 1; i <= bagWeight; i++){ // 遍历背包容量
for (int j = 0; j < weight.length; j++){ // 遍历物品
if (i - weight[j] >= 0){
dp[i] = Math.max(dp[i], dp[i - weight[j]] + value[j]);
}
}
}
for (int maxValue : dp){
System.out.println(maxValue + " ");
}
}
class Solution {
public int change(int amount, int[] coins) {
int[] dp = new int[amount+1];
dp[0] = 1;
for(int i=0;i<coins.length;i++){
for(int j = coins[i];j<=amount;j++){//j从coins【i】开始
dp[j] += dp[j-coins[i]];
}
}
return dp[amount];
}
}
class Solution {
public int combinationSum4(int[] nums, int target) {
int[] dp = new int[target+1];
dp[0] = 1;
for(int i=0;i<=target;i++){
for(int j=0;j<nums.length;j++){
if(i>=nums[j]){
dp[i] += dp[i-nums[j]];
}
}
}
return dp[target];
}
}
class Solution {
public int climbStairs(int n) {
int[] dp = new int[n+1];
int[] weight = {1,2};
dp[0] = 1;
for(int i =1;i<=n;i++){//跟顺序无关,先包后物品
for(int j=0;j<weight.length;j++){
if(i>=weight[j]){
dp[i] += dp[i-weight[j]];
}
}
}
return dp[n];
}
}
class Solution {
//1. dp[j] 凑到金额j所需的最少硬币个数
//2. dp[0] = 0 其他为最大值
//3. dp[j] = min (dp[j-coins[i]] + 1, dp[j])
//4. 先硬币后总金额
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount+1];
dp[0] = 0;
for (int i = 1;i<dp.length;i++){
dp[i] = Integer.MAX_VALUE;
}
for (int i = 0;i<coins.length;i++){
for (int j = coins[i];j <= amount;j++){
if (dp[j-coins[i]]!= Integer.MAX_VALUE){
dp[j] = Math.min( dp[j-coins[i]] + 1 , dp[j]);
}
}
}
if (dp[amount] == Integer.MAX_VALUE){return -1;}
return dp[amount];
}
}
class Solution {
public int numSquares(int n) {
int[] dp = new int[n+1];//和为j的完全平方数的最少数量
dp[0] = 0;
for (int i=1;i<dp.length;i++){
dp[i] = Integer.MAX_VALUE;
}//初始化
for (int i = 1;i*i<=n;i++){//遍历物品
for (int j = i*i;j<=n;j++){
if (dp[j-i*i]!=Integer.MAX_VALUE){
dp[j] = Math.min(dp[j-i*i]+1,dp[j]);
}
}
}
return dp[n];
}
}
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
boolean[] dp = new boolean[s.length()+1];
dp[0] = true;
for (int j = 1;j<=s.length();j++){
for(String word:wordDict){
int length = word.length();
if (j>=length && dp[j-length]==true && word.equals(s.substring(j-length,j))){
dp[j] = true;
break;
}
}
}
return dp[s.length()];
}
}
class Solution {
public int rob(int[] nums) {
if (nums.length==1){return nums[0];}
int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = Math.max(nums[0],nums[1]);
for (int i = 2;i<nums.length;i++){
dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[nums.length-1];
}
}
class Solution {
//打家劫舍变形
public int rob(int[] nums) {
int len = nums.length;
if (len == 1){
return nums[0];
}
if (len == 2){
return Math.max(nums[0],nums[1]);
}
int left = robHelper(nums,0,len-1);//不考虑最后一个
int right = robHelper(nums,1,len);//不考虑第一个
return Math.max(left,right);
}
public int robHelper(int[] nums,int start,int end){
int[] dp = new int[end-start];
dp[0] = nums[start];
dp[1] = Math.max(nums[start],nums[start+1]);
for(int i=2;i<end-start;i++){
if(start==0){
dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
}
if(start==1){
dp[i] = Math.max(dp[i-2]+nums[i+1],dp[i-1]);
}
}
return dp[end-start-1];
}
}
class Solution {
public int rob(TreeNode root) {
int[] result = robHelper(root);
return Math.max(result[0],result[1]);
}
public int[] robHelper(TreeNode root){
int[] dp = new int[2];
if (root==null){return dp;}
int[] left = robHelper(root.left); //左
int[] right = robHelper(root.right); //右
//处理中
dp[0] = Math.max(left[0],left[1]) + Math.max(right[0],right[1]);//不偷该节点
dp[1] = root.val + left[0] + right[0];
return dp;
}
}
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int[][] dp = new int[len][2];
dp[0][0] = - prices[0];
dp[0][1] = 0;
int res = 0;
for(int i=1;i<len;i++){
dp[i][0] = Math.max(dp[i-1][0],-prices[i]);//持有股票
dp[i][1] = Math.max(dp[i-1][0]+prices[i],0);//不持有股票
res = Math.max(res,dp[i][1]);
}
return res;
}
}
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int[][] dp = new int[len][2];
dp[0][0] = -prices[0];//持有股票
dp[0][1] = 0;//不持有股票
for(int i=1;i<len;i++){
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
dp[i][1] = Math.max(dp[i-1][0] + prices[i], dp[i-1][1]);
}
return dp[len-1][1];
}
}
分四种情况讨论,第一次持有,不持有,第二次持有,不持有
初始化:第二次持有 初始化为 -prices[0]
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int[][] dp = new int[len][4];
dp[0][0] = - prices[0];//第一次持有
dp[0][1] = 0; //第一次不持有
dp[0][2] = - prices[0];
dp[0][3] = 0;
for(int i=1;i<len;i++){
dp[i][0] = Math.max(dp[i-1][0],-prices[i]);
dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
dp[i][2] = Math.max(dp[i-1][2],dp[i-1][1]-prices[i]);
dp[i][3] = Math.max(dp[i-1][3],dp[i-1][2] + prices[i]);
}
return dp[len-1][3];
}
}
为了方便 此题引入状态0,表示不操作,前面的题目也可以引入,但是过于简单,笔者没有引入
class Solution {
public int maxProfit(int k, int[] prices) {
//为了不需要分类讨论,引入多一个状态量0
//状态0表示不操作
//奇数:持有
//偶数:不持有
int len = prices.length;
if(len<2) return 0;
int[][] dp = new int[len][2*k+1];
//初始化
for(int i=1;i<2*k+1;i+=2){
dp[0][i] = -prices[0];
}
//递推
for(int i=1;i<len;i++){
for(int j=1;j<2*k+1;j+=2){
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-1]-prices[i]);
dp[i][j+1] = Math.max(dp[i-1][j+1],dp[i-1][j] + prices[i]);
}
}
return dp[len-1][2*k];
}
}
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
if(len<2) return 0;
int[][] dp = new int[len][4];//不操作/买入/卖出/冷冻期
dp[0][1] = -prices[0];
for(int i=1;i<len;i++){
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][3]);
dp[i][1] = Math.max(dp[i-1][1],Math.max(dp[i-1][3],dp[i-1][0])-prices[i]);
dp[i][2] = dp[i-1][1]+prices[i];
dp[i][3] = dp[i-1][2];
}
return Math.max(dp[len-1][0],Math.max(dp[len-1][2],dp[len-1][3]));
}
}
操作两个序列时,往往dp[i][j] 表示的是i-1的序列和j-1的序列。且要注意序列连续和不连续带来的公式区别
思路分析
dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);注意这里不是要dp[i] 与 dp[j] + 1进行比较,而是我们要取dp[j] + 1的最大值。
初始化必须都是1,不要理所当然的认为0,还是要严谨点
代码实现
class Solution {
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp,1);
for(int i=0;i<nums.length;i++){
for(int j=0;j<i;j++){
if(nums[i] > nums[j]){
dp[i] = Math.max(dp[i],dp[j]+1);
}
}
}
int res =0;
for(int i = 0;i<nums.length;i++){
res = Math.max(res,dp[i]);
}
return res;
}
}
class Solution {
//题718 找最长重复子数组,但是此题不需要连续,只要满足相对顺序就好,因此当元素不等时需要进一步判断
//dp[i][j] 0到i-1 text1的字符串 和 0到j-1 text2的字符串 的最长公共子序列
//为了方便初始化 本题统一初始化 为 len+1
public int longestCommonSubsequence(String text1, String text2) {
int lenA = text1.length();
int lenB = text2.length();
int res = 0;
int[][] dp = new int[lenA+1][lenB+1];
for(int i = 1;i<=lenA;i++){
char charA = text1.charAt(i-1);//取字符 注意此处需要减去1
for(int j = 1; j<=lenB;j++){
char charB = text2.charAt(j-1);
if(charA==charB){//若两个字符相等 则在减1的基础上加1
dp[i][j] = dp[i-1][j-1] + 1;
}
else{//若字符不等 则取左上元素的最大值
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
}
if(res<dp[i][j]){
res = dp[i][j];
}
}
}
return res;
}
}
本题说是求绘制的最大连线数,其实就是求两个字符串的最长公共子序列的长度!
class Solution {
//本质是求最长公共子序列
public int maxUncrossedLines(int[] nums1, int[] nums2) {
int lenA = nums1.length;
int lenB = nums2.length;
int res = 0;
int[][] dp = new int[lenA+1][lenB+1];
for(int i = 1;i<=lenA;i++){
for(int j = 1;j <= lenB; j++){
if(nums1[i-1]==nums2[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]);
}
if(res<dp[i][j]){
res = dp[i][j];
}
}
}
return res;
}
}
class Solution {
//参考题300:简单化
//由于是连续的 只需要考虑前一个 而不需要遍历全部
public int findLengthOfLCIS(int[] nums) {
int n = nums.length;
int result = 0;
if (n == 1){
return 1;
}
int[] dp = new int[n];//dp[i]:以下标i为结尾的连续递增的子序列长度为dp[i]。
for(int i = 0;i<n;i++){
dp[i] = 1;
}
for(int i = 1;i<n;i++){
if(nums[i]>nums[i-1]){
dp[i] = dp[i-1] + 1;
}
if(result<dp[i]){
result = dp[i];
}
}
return result;
}
}
class Solution {
public int maxSubArray(int[] nums) {
if(nums.length == 0) return 0;
int[] dp = new int[nums.length];
dp[0] = nums[0];
int res = nums[0];
for(int i=1;i<nums.length;i++){
dp[i] = Math.max(dp[i-1]+nums[i],nums[i]);//取不取前面的
if(res<dp[i]){
res = dp[i];
}
}
return res;
}
}```
### T718. 最长重复子数组 (与编辑距离章节可以对比)**

- **思路分析**
==注意此时求得是连续的,所以当nums1[i]与nums2[j]不相同时,不能给其赋值,与后面的编辑距离有区别===
- **代码实现**
```java
class Solution {
public int findLength(int[] nums1, int[] nums2) {
int len1 = nums1.length;
int len2 = nums2.length;
int[][] dp = new int[len1+1][len2+1];//i-1 的nums1 和 j-1的nums2的最长重复子数组
//初始化
for(int i=0;i<len1+1;i++){
dp[i][0] = 0;
}
for(int j=1;j<len2+1;j++){
dp[0][j] = 0;
}
int res = 0;
for(int i=1;i<len1+1;i++){
for(int j=1;j<len2+1;j++){
if(nums1[i-1] == nums2[j-1]){
dp[i][j] = dp[i-1][j-1] + 1;
}//不同时,由于是连续数组,所以必须不能取
res = Math.max(dp[i][j],res);
}
}
return res;
}
}
class Solution {
public boolean isSubsequence(String s, String t) {
int lenA = s.length();
int lenB = t.length();
int[][] dp = new int[lenA+1][lenB+1];// i-1 结尾s和 j-1结尾t 相同子序列的长度
for(int i = 1;i<=lenA;i++){
char sChar = s.charAt(i-1);
for(int j = 1;j<=lenB;j++){
char tChar = t.charAt(j-1);
if(sChar==tChar){
dp[i][j] = dp[i-1][j-1] + 1;
}
else{
dp[i][j] = dp[i][j-1];
}
}
}
if(dp[lenA][lenB] == lenA){
return true;//当相同子序列长度等于s 则说明s是t的子序列
}
else{return false;}
}
}
class Solution {
//递推公式中相等的情况 与之前不同
public int numDistinct(String s, String t) {
//表示i-1结尾的字符串s中出现j-1结尾的字符串t的个数
int[][] dp = new int[s.length()+1][t.length()+1];
//初始化
for(int i = 0;i<=s.length();i++){
dp[i][0] = 1;//假设t为空字符串 出现个数为1
}
for(int j = 1;j<=t.length();j++){
dp[0][j] = 0; //当s为空,t不为空,注意dp[0][0]=1
}
//递推公式
for(int i =1;i<=s.length();i++){
for(int j =1;j<=t.length();j++){
if(s.charAt(i-1) == t.charAt(j-1)){
dp[i][j] = dp[i-1][j-1] + dp[i-1][j];//相等时 分为两部分
//一部分是s取i元素(此时相当于原来s和t都少取一个元素的情况) 一部分是s不取i元素
}
else{
dp[i][j] = dp[i-1][j];//此时相当于s不取i元素
}
}
}
return dp[s.length()][t.length()];
}
}
class Solution {
public int minDistance(String word1, String word2) {
int len1 = word1.length();
int len2 = word2.length();
int[][] dp = new int[len1+1][len2+1];
for(int i=0;i<len1+1;i++){
dp[i][0] = i;
}
for(int j=1;j<len2+1;j++){
dp[0][j] = j;
}
for(int i=1;i<len1+1;i++){
for(int j=1;j<len2+1;j++){
if(word1.charAt(i-1) == word2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1];
}else{
dp[i][j] = Math.min(dp[i-1][j-1]+2,Math.min(dp[i-1][j],dp[i][j-1])+1);
}
}
}
return dp[len1][len2];
}
}
class Solution {
//1143.最长公共子序列
//dp[i][j] i-1字符串word1 和 j-1字符串word2 的最长公共子序列
//步数 = 多余的字符
public int minDistance(String word1, String word2) {
int lenA = word1.length();
int lenB = word2.length();
int[][] dp = new int[lenA+1][lenB+1];
int res = 0;
for(int i = 1;i<=word1.length();i++){
for(int j = 1;j<= word2.length();j++){
if(word1.charAt(i-1) == word2.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]);
}
if (res<dp[i][j]){
res = dp[i][j];
}
}
}
return lenA+lenB-2*res;
}
}
class Solution {
//dp[i][j] i-1 结尾的单词word1 变成 j-1结尾单词word2 的最少操作数
//四种操作
//当字符相等, 字符不等 增删换
public int minDistance(String word1, String word2) {
int lenA = word1.length();
int lenB = word2.length();
int[][] dp = new int[lenA+1][lenB+1];
//初始化
for(int i = 0;i<=lenA;i++){
dp[i][0] = i;
}
for(int j = 1;j<=lenB;j++){
dp[0][j] = j;
}
//递推公式
for(int i = 1;i<=lenA;i++){
for(int j = 1;j<=lenB;j++){
if(word1.charAt(i-1)==word2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1];
}
else{
dp[i][j] = Math.min(dp[i-1][j-1],Math.min(dp[i-1][j],dp[i][j-1])) + 1;
//换,删,增三种操作
}
}
}
return dp[lenA][lenB];
}
}
分清楚是否连续,能否删除某些元素
难点1:dp的含义 dp[i][j] i到j的字符串是否是回文串
难点2:递推公式的推导,分情况讨论s[i]与s[j] 以及i与j
s[i]与s[j]不等 直接false
相等,看i与j差多少 小于等于1,大于1
难点3:遍历顺序 从递推公式可以看出i是从后到前
class Solution {
public int countSubstrings(String s) {
boolean[][] dp = new boolean[s.length()][s.length()];
int res=0;
for(int i = s.length()-1;i>=0;i--){//从后往前遍历
for(int j = i;j<s.length();j++){
//不相等为false 默认false不需要操作
if(s.charAt(i)==s.charAt(j)){//相等时 进行判断
if(j-i<=1){//二者小于1 aa,a
dp[i][j]=true;}
else{//二者差大于1, abccba 判断 bccb
dp[i][j]=dp[i+1][j-1];
}
}
}
}
for(int i = 0;i<s.length();i++){
for(int j=0;j<s.length();j++){
if(dp[i][j]){
res++;
}
}
}
return res;
}
}
class Solution {
public int longestPalindromeSubseq(String s) {
int len = s.length();
int res = 0;
if (len == 1){return 1;}
int[][] dp = new int[len][len];
for(int i=0;i<len;i++){
dp[i][i]=1;
}
for(int i = len-1;i>=0;i--){
for(int j = i+1;j<len;j++){
if(s.charAt(i)==s.charAt(j)){
dp[i][j] = dp[i+1][j-1] + 2; // 字符相同
}
else{
dp[i][j] = Math.max(dp[i+1][j],dp[i][j-1]); //字符不同
}
if(res<dp[i][j]){
res = dp[i][j];
}
}
}
return res;
}
}
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int maxValue(TreeNode root, int k) {
int[] dp = maxHelper(root,k);
int res = Integer.MIN_VALUE;
for(int i=0;i<=k;i++){
res = Math.max(dp[i],res);
}
return res;
}
//返回的是一个dp数组:dp[i] i表示染色节点数目,表示该节点连接i个染色节点的价值;0表示不染色
//后序遍历,自下而上,分为染色和不染色
public static int[] maxHelper(TreeNode root,int k){
int[] dp = new int[k+1];
if(root==null) return dp;
//获取左右节点状态
int[] left = maxHelper(root.left,k);
int[] right = maxHelper(root.right,k);
//处理当前节点
//不染色:左最大+右最大
int maxLeft = Integer.MIN_VALUE;
int maxRight = Integer.MIN_VALUE;
for(int i=0;i<k+1;i++){
maxLeft = Math.max(maxLeft,left[i]);
maxRight = Math.max(maxRight,right[i]);
}
dp[0] = maxLeft + maxRight;
//染色:左边j个 右边i-1-j个
for(int i=1;i<=k;i++){
for(int j=0;j<i;j++){
dp[i] = Math.max(left[j] + right[i-j-1] + root.val,dp[i]);
}
}
//返回dp数组
return dp;
}
}