面试算法总结------动态规划

动态规划

动态规划中最重要的两点就是 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个盘子中 总共有多少中不同的方法(当盘子允许为空时有多少种不同的算法 以及当盘子不允许为空时有多少种不同的算法)

  1. 首先当盘子允许为空的时候 就是常规的动态规划问题
    定义状态方程 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) 每一个盘子都不是空盘的数量之和)

  2. 当盘子不允许为空的时候 需要借助允许为空的状态转移方程
    即当有m个苹果要放到k个盘子中去 并且k个盘子均不能为空的时候
    F(m,k)=F(m-k,k) if m>=k F(m,k)=0 if m<k

  3. 当有了上述两个子问题的解法之后 问题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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值