动态规划
动态规划中最重要的两点就是 1.确定状态的定义,即将一个问题准确的定义为某一个状态方程 2.确定状态转移方程的定义 即将上述定义的状态方程与其之前的状态进行关联 3.将状态转移过程中的中间变量进行保存 避免多次重复运算
1. 0,1背包问题
给定一个容量为C的背包,给定需要在背包中装填的物体的质量 w,以及物体的价值v ,使得装填的物体在不超过背包容积的情况下 得到的物体价值最大
0,1 背包问题有一个特点就是放入背包中的物体只能放入一次,不可以重复放入
1.首先定义状态
F(C,n) 表示将n个物体放入到容积为C的背包中 使得物体的价值最大
2.定义状态转移方程
F(C,n) = F(C,n-1) if C< w[n-1] (当容积小于第n个物体的时候 当前能承载的最大价值 和 只在n-1个物体中进行选取是一样的)
F(C,n)=Math.max(F(C-w[n-1],n-1)+v[n-1],F(C,n-1)) (当前的容积大于第n个物体的时候 则需要比较将第n个放入和忽略第n个二者哪个大)
public int getMax(int[] weights,int[]values,int C ){
int[][]dp=new int[C+1][weights.length+1]
for(int i=1;i<=C;i++){
for(int j=1;j<=weight.length;j++){
if(C<weights[j-1]){
dp[i][j]=dp[i][j-1]
} else{
dp[i][j]=Math.max(dp[i-weight[j-1]][j-1]+values[j-1],dp[i][j-1])
}
}
return dp[C][weights.length];
}
}
2. 0,1背包问题的变种 分割等和子集(leetcode 416)
输出对于C个数 是否可以得到两个等和的子数组
其实可以转化一下 对于容积为C的背包 给背包填充物体的质量为w 是否能够在这堆物体中将背包完全填满
同样的解题思路 定义状态方程
F(C,n) 对于n个物体放入容积为c的背包 背包是否被填满
F(c,n) = F(c,n-1) if c<w[n-1]
F(c,n)=F(c,n-1) | F(c-w[n-1],n-1) if c>=w[n-1]
(对于0,1背包问题 选择了某一个元素之后 剩下的情况只能是之前的元素进行处理)
public boolean canPartition(int[] nums) {
int total=0;
for(int i=0;i<nums.length;i++){
total+=nums[i];
}
if(total%2!=0){
return false;
}
int split=total/2;
boolean [][] dp=new boolean[split+1][nums.length+1];
for(int i=0;i<=nums.length;i++){
dp[0][i]=true;
}
for(int i=1;i<=split;i++){
for(int j=1;j<=nums.length;j++){
dp[i][j]=dp[i][j-1];
if(i>=nums[j-1]){
dp[i][j]|=dp[i-nums[j-1]][j-1];
}
}
}
return dp[split][nums.length];
}
另一个变种问题 和分割等和子集相似 给定一个数组 数组中每一个元素都是一个石头的重量 每一次从中任意选取两个如果两个值相等则将这两个数字删去 如果两个不相等 x1 < x2 那么将x1删去 并将x2-x1 添加到数组中 这样进行若干次操作 直到数组中剩下一个石头或者不剩下石头 那么剩下的石头最小的重量等于几 (leetcode 1049. Last Stone Weight II )
其实这道题 可以转化为将一个数组分成两个相等数组问题 如果能够将这个数组分割成为两个等和的数组 那么最后一定完全不剩下 如果不能的话 那么求解totalSum/2 的背包最大能够承载多少重量的石头 L1 那么另一组重量为 totalSum-L1 所以整体差值为 totalSum-2*L1
public int lastStoneWeightII(int[] stones) {
int totalSum=0;
for(int stone:stones){
totalSum+=stone;
}
int sum=totalSum/2;
int[][]dp=new int[sum+1][stones.length+1];
for(int i=1;i<=sum;i++){
for(int j=1;j<=stones.length;j++){
if(i<stones[j-1]){
dp[i][j]=dp[i][j-1];
}else{
dp[i][j]=Math.max(dp[i][j-1],dp[i-stones[j-1]][j-1]+stones[j-1]);
}
}
}
return totalSum-2*dp[sum][stones.length];
}
3.完全背包问题 找零钱 1
完全背包问题和0,1 背包问题的区别在于 某一元素可以无限的使用 因此 在状态转移方程中 c>=w[n-1]之后 即便选择了当前n的元素 剩下的参与处理的元素依旧是当前的n个 而不是之前的n-1个
给出需要兑换零钱的数量 C 以及用来兑换的零钱的种类w 找出有多少种兑换的方法
状态方程为:
F(C,n) 表示在零钱数量为C 使用n种零钱进行找零的时候 有多少种方法
状态转移方程为:
F(C,n) =F(C,n-1) if(C<w[n-1])
F(C,n)=F(C,n-1)+F(C-w[n-1],n)
int[][]dp=new int[amount+1][coins.length+1];
for(int i=0;i<=coins.length;i++){
dp[0][i]=1;
}
for(int i=1;i<=amount;i++){
for(int j=1;j<=coins.length;j++){
if(i<coins[j-1]){
dp[i][j]=dp[i][j-1];
}else{
dp[i][j]=dp[i][j-1]+dp[i-coins[j-1]][j];
}
}
}
return dp[amount][coins.length];
4. 完全背包问题 找零钱2
给出需要找零的零钱数量C 以及用来找零的零钱w 得到最终可以用来兑换的零钱数量中的最小值 如果不可以用来兑换 则返回-1
依旧是上述的套路
首先确定状态方程
F(C,n) 表示在零钱数量为C以及用来找零的零钱为n种的时候 可以用来兑换的零钱数量的最小值
状态转移方程为
F(c,n) =F(c,n-1) if(c<w[n-1])
F(c,n)= Math.min(F(c,n-1),1+F(c-w[n-1],n))
这里要注意的是 如果想要沿用上述的代码框架 因为出现了Math.min的操作 所以在初始化的时候很重要 这里采用的初始化方法为将无法兑换的所有可能都初始化为 Integer.MAX_VALUE/2(其实初始化为一个很大的数就可以了 但是如果初始化为Integer.MAX_VALUE 会出现加一越界的情况)
public int coinChange(int[] coins, int amount) {
int[][]dp=new int[amount+1][coins.length+1];
for(int i=0;i<=coins.length;i++){
dp[0][i]=0;
}
for(int j=1;j<=amount;j++){
dp[j][0]=Integer.MAX_VALUE/2;
}
for(int i=1;i<=amount;i++){
for(int j=1;j<=coins.length;j++){
if(i<coins[j-1]){
dp[i][j]=dp[i][j-1];
}else{
dp[i][j]=Math.min(dp[i-coins[j-1]][j]+1,dp[i][j-1]);
}
}
}
return dp[amount][coins.length]==Integer.MAX_VALUE/2?-1:dp[amount][coins.length];
}
5. 完全背包问题 整数划分
就是将一个整数划分为若干个小于等于它的整数相加的形式 求总共划分的可能性有多少
例如 4可以有如下的划分
4
3 1
2 2
2 1 1
1 1 1 1
依旧是老套路 典型的完全背包问题
状态方程为
F(C,n) 表示使用n个数字 对C进行划分可以有几种划分结果
状态转移方程为
F(C,n)=F(C,n-1) if(C<n)
F(C,n) =F(C,n-1)+F(C-n,n)
但是这里有一个可以优化的地方就是 如果C小于n 那么对于大于c的那部分都是不起作用的 所以可以直接 F(C,n)=F(c,c) if c<n
public static int getResult2(int m,int n){
int[][]result=new int[m+1][n+1];
for(int i=1;i<=n;i++){
result[0][i]=1;
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(i<j){
result[i][j]=result[i][i];
}else{
result[i][j]=result[i][j-1]+result[i-j][j];
}
}
}
return result[m][n];
}
6. 字符串的编辑距离
这时字符串中最常考的一种题目之一 同时也是经典的动态规划题目,一定不可以再写错了
对于给定的两个字符串 通过任意删除,插入 替换操作使得两个字符串变成完全一致所操作的最短的步骤
同样的也是分两步走 首先定义状态方程
F(m,n) 表示将str1 的前m个字符转化为str前n个字符所操作的最短步骤
F(m,n) = F(m-1,n-1) if str1[m-1]==str2[n-1]
F(m,n)=Math.min(F(m-1,n)+1,F(m,n-1)+1,F(m-1,n-1)+1 ) if str[m-1]!=str2[n-1]
public int minDistance(String word1, String word2) {
int len1=word1.length();
int len2=word2.length();
int[][]result=new int[len1+1][len2+1];
for(int i=0;i<=len1;i++){
result[i][0]=i;
}
for(int j=0;j<=len2;j++){
result[0][j]=j;
}
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
if(word1.charAt(i-1)==word2.charAt(j-1)){
result[i][j]=result[i-1][j-1];
}else{
result[i][j]=Math.min(result[i-1][j],Math.min(result[i][j-1],result[i-1][j-1]))+1;
}
}
}
return result[len1][len2];
}
一般情况是根据ditui
6.2 Delete operation for two strings
6.3 DP结合重现 不仅仅是得到数量
class Solution {
public String shortestCommonSupersequence(String str1, String str2) {
StringBuilder sb = new StringBuilder();
String lcs = lcs(str1, str2);
p(lcs);
int i = 0, j = 0;
for (char c : lcs.toCharArray()) {
while (str1.charAt(i) != c) sb.append(str1.charAt(i++));
while (str2.charAt(j) != c) sb.append(str2.charAt(j++));
sb.append(c);
i++; j++;
}
p(sb.toString());
sb.append(str1.substring(i)).append(str2.substring(j));
return sb.toString();
}
String lcs(String s1, String s2) {
int n = s1.length();
int m = s2.length();
String[][] dp = new String[n+1][m+1];
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++) {
if (i == 0 || j == 0) {
dp[i][j] = "";
continue;
}
if (s1.charAt(i-1) == s2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1] + s1.charAt(i-1);
} else {
if (dp[i][j-1].length() < dp[i-1][j].length()) {
dp[i][j] = dp[i-1][j];
} else {
dp[i][j] = dp[i][j-1];
}
}
}
}
return dp[n][m];
}
private void p(String s) {
System.out.println(s);
}
}
字符串正则表达式
给定一个字符串 以及一个正则表达式匹配项 在正则表达式中 ?代表匹配任意的一个字符,*代表匹配0个和多个任意的字符
返回字符串和正则表达式是否匹配
class Solution {
public boolean isMatch(String s, String p) {
boolean[][] dp=new boolean[s.length()+1][p.length()+1];
dp[0][0]=true;
for(int i=1;i<=p.length();i++){
if(p.charAt(i-1)=='*'&&dp[0][i-1]==true){
dp[0][i]=true;
}
}
for(int i=1;i<=s.length();i++){
for(int j=1;j<=p.length();j++){
if(p.charAt(j-1)=='?'||s.charAt(i-1)==p.charAt(j-1)){
dp[i][j]=dp[i-1][j-1];
}
if(p.charAt(j-1)=='*'){
dp[i][j]=(dp[i-1][j]||dp[i][j-1]||dp[i-1][j-1]);
}
}
}
return dp[s.length()][p.length()];
}
}
对于给定的字符串 首先如果通配符中出现* 并且其前一位为true 则该位置也为true,在匹配过程中分为两种情况 如果 (p.charAt(j-1)==’?’||s.charAt(i-1)==p.charAt(j-1) 则当前i,j匹配,如果当前为 * 则可以分如下几种情况 1)把匹配为空 则 dp[i][j]=dp[i][j-1] ,把 * 匹配为当p.charAt(i) 并且只匹配这一个,则dp[i][j]=dp[i]-1[j-1] 如果把 匹配为p.charAt(i) 并且不仅仅匹配当前位置 则dp[i][j]=dp[i-1][j]
7. word break
对于给定一个字符串 能够做出一个分割 使得分割后的子串 全部都包含在数组之中 如果可以则返回true 如果不可以返回false
首先还是定义状态方程
F(n) 表示 字符串第n个字符及之前的字符可以做出有效分割
状态转移方程为
F(n)|=(F(n-i)&判定条件(n-i,n) ) for i in (0,n)
public boolean wordBreak(String s, List<String> wordDict) {
boolean []dp=new boolean[s.length()+1];
dp[0]=true;
for(int i=1;i<=s.length();i++){
for(int j=0;j<i;j++){
if(wordDict.contains(s.substring(j,i))&&dp[j]){
dp[i]=true;
break;
}
}
}
return dp[s.length()];
}
8. SubSet && SubSet ii 全排列问题(回溯问题)
对于subSet的问题可以和 全排列问题一起记
二者的关键就是要设定一个正确的返回值标记 对于固定长度的返回情况 可以通过比较当前的tmp的size和目标大小的关系确定返回
对于没有固定长度的返回情况 比如subset 可能有很多大小的返回值 这时候可以循环处理不同大小的返回值 例如 一个长度为5的array
subset的长度可能为 1,2,3,4,5 可以在主函数中使用循环 每次回溯一个固定长度的subset
然后在backTrack函数中使用循环保证每一个可能都被遍历到
public class Solution {
ArrayList<ArrayList<Integer>> result=new ArrayList<>();
public ArrayList<ArrayList<Integer>> subsets(int[] S) {
Arrays.sort(S);
ArrayList<Integer> tmp=new ArrayList<>();
for(int i=0;i<=S.length;i++){
backTrack(S,i,0,tmp);
}
return result;
}
public void backTrack(int[]S,int totalNum,int index,ArrayList<Integer> tmp){
if(totalNum==0){
result.add(new ArrayList<>(tmp));
return;
}
for(int i=index;i<S.length;i++){
tmp.add(S[i]);
backTrack(S,totalNum-1,i+1,tmp);
tmp.remove(tmp.size()-1);
}
}
8.2 N皇后问题(回溯问题)
对于N皇后问题这类输出不是传统回溯问题输出的问题 可以先将整体架构写出来 然后再慢慢的根据具体问题微调架构中的代码
首先搭建整体的代码框架,当输入时char数组的时候方便对局部数据做先修改后改回的操作 因此
public void backTrack(char[][] matrix,int rowIndex)
根据终止条件 即 matrix.length == rowIndex 将 matrix转化为List< String> 这里 可以另外构造一个函数 (注意char数组类型可以直接new 生成string)
然后对于其他情况 从第一列开始循环 如果当前的rowIndex 的第i列满足需求 这里如果之前所有行都没有在当前列i有皇后 同时 行差的绝对值不等于列差的绝对值(在一条斜线上) 那么满足需求
class Solution {
List<List<String>> result=new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
char[][]matrix=new char[n][n];
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
matrix[i][j]='.';
}
}
backTrack(matrix,0);
return result;
}
public List<String> construct(char[][]matrix){
List<String>res=new ArrayList<>();
for(int i=0;i<matrix.length;i++){
String s=new String(matrix[i]);
res.add(s);
}
return res;
}
public boolean valid(char[][]matrix,int rowIndex,int colIndex){
for(int i=0;i<rowIndex;i++){
for(int j=0;j<matrix.length;j++){
if(matrix[i][j]=='Q'&&(j==colIndex || (rowIndex-i)==Math.abs(j-colIndex))){
return false;
}
}
}
return true;
}
public void backTrack(char[][]matrix,int rowIndex){
if(rowIndex==matrix.length){
result.add(construct(matrix));
return;
}
for(int i=0;i<matrix.length;i++){
if(valid(matrix,rowIndex,i)){
matrix[rowIndex][i]='Q';
backTrack(matrix,rowIndex+1);
matrix[rowIndex][i]='.';
}
}
}
}
8.m(m大于等于1 小于等于50)个苹果 n (n大于等于1 小于等于50)个梨 放入k(k大于等于1 小于等于50)个盘子中 其中苹果和梨不能同时出现在一个盘子里 请问有多少种不同的装法 假设碗足够大 能够装50个苹果或者50个梨 同时碗和碗之间也没有区别 因此当M=N=1 K=3时 只有一种装法
首先阐明该题的两个子问题 即 将m个苹果放入到k个盘子中 总共有多少中不同的方法(当盘子允许为空时有多少种不同的算法 以及当盘子不允许为空时有多少种不同的算法)
-
首先当盘子允许为空的时候 就是常规的动态规划问题
定义状态方程 F(m,k)表示有m个苹果以及k个盘子的时候 总共有多少种不同的放法
定义状态转移方程 F(m,k)=F(m,m) if m<k F(m,k)=f(m-k,k)+f(m,k-1) if m>=k (即 如果m比k小 则最多使用到m个盘子 多余的盘子不会提供更多的解 如果m比k大 则解的数量等于 f(m,k-1) 即至少空余一个盘子的数量 以及 f(m-k,k) 每一个盘子都不是空盘的数量之和) -
当盘子不允许为空的时候 需要借助允许为空的状态转移方程
即当有m个苹果要放到k个盘子中去 并且k个盘子均不能为空的时候
F’(m,k)=F(m-k,k) if m>=k F’(m,k)=0 if m<k -
当有了上述两个子问题的解法之后 问题8也就迎刃而解了 分别循环遍历 使得总体的空盘子数为0,1,2,3,… 则使用的盘子数为 k,k-1,k-2… 即 将k,k-1,k-2…个盘子分为两个部分一部分用来装m个苹果 且不可为空 另一部分装n个梨且不可以为空 二者的乘积为该状态下的所有可能 再将全部状态累加即可得最终结果
9.字符串的最大公共子串和最大公共子序列 最长上升子序列
最长公共子序列 状态转移方程为
dp[i][j]= 0 if i=0 || j==0
dp[i][j] =dp[i-1][j-1] if s.charAt(i-1)==s.charAt(j-1)
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1])
public int minDistance(String word1, String word2) {
if (word1.length()==0){
return word2.length();
}
if(word2.length()==0){
return word1.length();
}
int[][]dp=new int[word1.length()+1][word2.length()+1];
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]);
}
}
}
如果想要得到公共子序列的取值 则
int i=word1.length();
int j=word2.length();
StringBuffer buffer=new StringBuffer()
while(i>0 && j>0){
if(word1.charAt(i-1)==word2.charAt(j-1)){
buffer.append(word1.charAt(i-1));
i--;
j--
}else{
if(dp[i-1][j]>dp[i][j-1]){
i--;
} else{
j--;
}
}
return sb.reverse().tostring();
}
最长公共子串
public static int lcs(String str1, String str2) {
int len1 = str1.length();
int len2 = str2.length();
int result = 0; //记录最长公共子串长度
int c[][] = new int[len1+1][len2+1];
for (int i = 0; i <= len1; i++) {
for( int j = 0; j <= len2; j++) {
if(i == 0 || j == 0) {
c[i][j] = 0;
} else if (str1.charAt(i-1) == str2.charAt(j-1)) {
c[i][j] = c[i-1][j-1] + 1;
result = max(c[i][j], result);
} else {
c[i][j] = 0;
}
}
}
return result;
}
最长上升子序列
public int getNum(int[] nums){
if(nums.length==0){
return 0;
}
int[]dp=new int[nums.length];
int max=0;
dp[0]=1;
for(int i=1;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);
}
}
max=Math.max(max,dp[i])
}
return max;
}
10. 丑数问题
该题是一个简单的动态规划问题 和上述题目不同的是 该题的状态转移方程略有区别
F(m) = min(F(i)*2,min(F(j)*3,F(k)5)) (其中i,j,k 注意不一定是相同的值 即每一个数都独立的维护该值的指针,当2,3,5中任意一个值被使用之后 对应的指针向后移动一位 即第n个丑数是之前的某一个丑数2 和某一个丑数乘以3 以及某一个丑数乘以5之后比较求最小的结果)
public int GetUglyNumber_Solution(int index) {
if(index==0||index==1){
return index;
}
int[]dp=new int[index];
dp[0]=1;
int index2=0;
int index3=0;
int index5=0;
for(int i=1;i<index;i++){
dp[i]=Math.min(dp[index2]*2,Math.min(dp[index3]*3,dp[index5]*5));
if(dp[i]==dp[index2]*2){
index2++;
}
if(dp[i]==dp[index3]*3){
index3++;
}
if(dp[i]==dp[index5]*5){
index5++;
}
}
return dp[index-1];
}
11. 小偷偷房子问题
这一系列问题 可以总结为 在一个长度为N的数组中 数组每一个位置值对应访问该位置的收益 但是不能同时访问距离间隔小于k的位置 访问一遍之后求最大的收益
11.1 hourse Robber
给定一个数组 每一个位置代表偷这个位置可以得到的金钱 但是不能偷连续的位置 则最大收益为
public int rob(int[] nums) {
if(nums.length==0){
return 0;
}
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];
}
11.2 上一个题做一个延伸
如果从一个直线 变成了一个环 (其实和直线唯一的区别就是分两次做 第一次整体数组不包括最后一个 第二次整体数组不包括第一个)
这个时候需要注意初始化的值以及dp数组中的index和nums中的index的关系
public int rob(int[] nums) {
if(nums.length==0){
return 0;
}
if(nums.length==1){
return nums[0];
}
if(nums.length==2){
return Math.max(nums[0],nums[1]);
}
int[]dp=new int[nums.length-1];
dp[0]=nums[0];
dp[1]=Math.max(nums[0],nums[1]);
for(int i=2;i<nums.length-1;i++){
dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i]);
}
int[]dp2=new int[nums.length];
dp2[0]=0;
dp2[1]=nums[1];
for(int i=2;i<nums.length;i++){
dp2[i]=Math.max(dp2[i-1],dp2[i-2]+nums[i]);
}
return Math.max(dp[nums.length-2],dp2[nums.length-1]);
}
11.3 再进一步的延伸 如果当前有n个位置 可以在这n个位置任意一个位置安放一个广告 但是相邻的k个位置只能够存在一个广告(可以全位置都不放广告) 总共有多少种放广告的方法
就是比如3个slot _ _ _
gap是1
你可以这么放
x _ x.
x _ _.
_ x _.
_ _ x
_ _ _
一共5种
public static int AdNum(int []slots,int k){
if(slots.length==0){
return 0;
}
int[]dp=new int[slots.length];
dp[0]=2;
for(int i=1;i<slots.length;i++){
if(i-k-1<0){
dp[i]=dp[i-1]+1;
}else {
dp[i]=dp[i-1]+dp[i-k-1];
}
}
return dp[slots.length-1];
}
和robber一样对于每一个位置也是存在放置广告和不放置广告两个选择 只不过间隔是不确定的 如果对于位置i 选择不放置广告 那么有dp[i-1]种不同的方案 如果选择在位置i放置广告那么 有dp[i-k-1]种不同的选择 如果i-k-1 小于0 则只有一种选择 即 之前全部为不放置 在这个位置放置。
字符串正则表达式匹配
N个木块问题
N个木块摆放 后一列是木块数要大于等于前一列 总共有多少种摆放方式
public static int getTotalNum(int N){
int result=0;
int[][]dp=new int[N+1][N+1];
for(int i=1;i<=N;i++){
dp[i][i]=1;
}
for(int i=1;i<=N;i++) {
for (int j = 1; j < i; j++) {
for (int w = 1; w <= j; w++) {
if (w <= i-j) {
dp[j][i] += dp[w][i - j];
}
}
}
}
for(int i=1;i<=N;i++){
result+=dp[i][N];
}
return result;
}
leetcode 239 矩阵中最长递增路径
class Solution {
public int longestIncreasingPath(int[][] matrix) {
int max=0;
if(matrix.length==0){
return 0;
}
int[][]dp=new int[matrix.length][matrix[0].length];
for(int i=0;i<matrix.length;i++){
for(int j=0;j<matrix[0].length;j++){
max=Math.max(max,dfs(matrix,i,j,Integer.MIN_VALUE,dp));
}
}
return max;
}
public int dfs(int[][]matrix,int i,int j,int preTmp,int[][]dp){
if(i<0||i>=matrix.length||j<0||j>=matrix[0].length||preTmp>=matrix[i][j]){ #因为要控制递增 可以将前一个取值传递给下一次 这样就避免了边界的判断
return 0;
}
if(dp[i][j]!=0){ # 如果之前计算过该值 那么之后可以直接用 不用重复计算 因为每一次计算i,j位置都会得到i,j位置的最长路径
return dp[i][j];
}
dp[i][j]=Math.max(1,dfs(matrix,i-1,j,matrix[i][j],dp)+1);
dp[i][j]=Math.max(dp[i][j],dfs(matrix,i+1,j,matrix[i][j],dp)+1);
dp[i][j]=Math.max(dp[i][j],dfs(matrix,i,j-1,matrix[i][j],dp)+1);
dp[i][j]=Math.max(dp[i][j],dfs(matrix,i,j+1,matrix[i][j],dp)+1);
return dp[i][j];
}
}
击鼓传花问题
有N个人 其中一个人叫小明 剩下的人没有姓名,开始时由小明发球,经过K(K>=3) 次传导,问最后球依旧传到小明手里的所有可能的传导次数为
例如 由3个人 经过3次传导 最后传到小明手里的所有可能为 2种
public static int getTotalNum(int N,int K){
int[][] result=new int[K+1][2];
result[1][0]=0;
result[1][1]=1;
for(int i=2;i<=K;i++){
result[i][1]=result[i-1][0];
result[i][0]=result[i-1][1]*(N-1)+result[i-1][0]*(N-2);
}
return result[K][1];
}
情况可以分为两种 球在小明手里 和球不在小明手里 如果球在小明手里 那么下一次他可以传给K个人 第i次球在小明手里只有可能是第i-1次球不在小明手里的次数 而第i次球不在小明手里 可能是第i-1次球不在小明手里次数乘上(N-2) 因为第i-1次球不在小明手里 同时这一次传球不能传给自己也不能传给小明 加上 第i-1次球在小明手里乘上 N-1