动归:用数组空间换时间、状态转移

动归个人理解

  • 一般来说求i到j的范围数据,需要参考前面的,必须new int[][]维护dp数组;
  • 如果只是单纯的求xx最大值,就只需要维护一个int maxXxx来作为状态转移即可
  • 一般递归都能改成dp,就像是递归的方法名必须要明确其含义(输入xx和yy,来得到zz),dp数组的含义也必须明确(dp[i][j]代表例如:i天买入,j天卖出的收益)
  • dp[][]数组的状态转移方程,所依赖的位置必须事先已经遍历过,否则可能因为new数组时默认赋值而产生问题(力扣5题)

力扣5最长回文子串

题目描述

超经典的一道题,用二维数组维护状态转移方程,有一个细节点就是到底维护dp[][]的右上角还是左下角

状态转移方程

dp[i][j]表示i到j是回文串,为true = 当前arr[i]等于arr[j] 内层dp[i+1][j-1]为true 长度小于3( j-i<=2 )

题解

  • 因为
  • 所以这是错的,状态转移参考的是dp数组默认的false
  • 这是对的

最终实现

class Solution {
    public String longestPalindrome(String s) {
        if(s == null || s.length() <= 1){return s;}
        char[] arr = s.toCharArray();
        int len = s.length();
        //保留最长回文串的前后指针
        int left = 0;
        int right = 0;
        //dp保存i到j是否是回文串:状态转移方程是:i+1~~j-1也是true
        boolean[][] dp = new boolean[len][len];//默认都是false
        for(int i = 0 ; i < len ; i++){
            dp[i][i] = true;//自身是一个回文串
        }
        //i到j是否是回文串
        for(int j = 1 ; j < len ; j++){//左i
            for(int i = 0 ; i < j ;i++){//右j
                //首尾不同 不是回文串
                if(arr[i] != arr[j]){continue;}
                //首尾相同,要参考内部是不是回文串
                if( j-i <= 2 || dp[i+1][j-1] ){//长度小于3 or 内层(dp左下角是true)
                    dp[i][j] = true;
                    if(right-left < j-i){
                        right = j;
                        left = i;
                    }
                }
            }
        }
        return s.substring(left,right+1);
    }
}

同程秋招

趁着回忆出来记录一下,不过感觉这个题状态转移方程没那么强的像动归,更像是一个普通的字符串问题

题目描述

若输入字符串s = “level” ,返回"l",即最长相同前后缀。(前缀:l、le、lev、leve)(后缀:e、ev、eve、evel)(不包括s自身)

若输入字符串s = “ababab” 则返回"abab"

题解

public class Solution {

  public static void main(String[] args) {
    Solution solution = new Solution();
    String   level    = solution.longestPrefix("ababab");
    System.out.println(level);

  }


  public String longestPrefix (String s) {
    //最长、既是前缀、也是后缀
    //1.空串
    if(s == null || s.length() == 0){
      return "";
    }
    int len = s.length();
    char[] arr =  s.toCharArray();
    //长度1一定是
    if(len == 1){
      return s;
    }
    //题目说不能包括s自身,所以特殊情况
    if(len == 2 && arr[0] == arr[1]){
      return "" + arr[0];
    }

    //指定一个跨度
    int gap = 1;
    String dest = "";
    //前gap 和 后gap是否相同
    while(gap < len){
      //前缀
      String leftString = s.substring(0,gap);
      //后缀
      String rightString = s.substring(len-gap,len);
      gap++;//gap扩大
      if( !leftString.equals(rightString) ){
        continue;//不能用break
      }else{
        dest = leftString;
      }
    }
    return dest;
  }
}

力扣55跳跃游戏

题目描述

状态转移方程

最大能达到的角标 = 遍历到的i位置 + i位置上的跳跃距离

题解

class Solution {
    public boolean canJump(int[] nums) {
        if(nums == null){return true;}
        
        int remote = 0;//最远的角标(状态转移方程:= Math.max)
        for(int i = 0 ; i < nums.length; i++){
            //跳不到i这来
            if(remote < i){return false;}
            //当前角标 + 跳跃长度
            remote = Math.max(remote, i+nums[i]);
            if(remote >= nums.length - 1){return true;}
        }
        return false;
    }
}

力扣139单词拆分

题目描述

状态转移方程

dp[x]表示x位置之前都能被字典匹配 = 当前角标i + 用String::startsWith(String word ; int offset)匹配到的字符串长度

题解

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        if("".equals(s) || s == null){return true;}
        int len = s.length();
        //创建一个dp数组,boolean类型,保证第一个为true(最开始就能匹配)
        //然后用这个 被匹配到的字典长度+原来角标———再去匹配
        //每次匹配都会去遍历wordDict字典
        boolean[] dp = new boolean[len + 1];//因为最后是判断dp[len] == true,保证指针不越界
        dp[0] = true;//认为字符串s前面还拼接了一个"",dp的长度位len+1,0号位是""一定能被匹配,dp[1]对应的是s的0号位
        for(int i = 0 ; i < len ; i++){
            if( !dp[i] ){continue;}
            //i位置,遍历匹配字典
            for(String word : wordDict){
                //不越界 && s从i开始能匹配上word
                if( i + word.length() <= len && s.startsWith(word,i) ){
                    dp[i + word.length()] = true; 
                }
            }
        }
        return dp[len];
    }

}

力扣62不同路径

题目描述

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

题解1:递归

太暴力了,容易爆栈

class Solution {
    public int uniquePaths(int m, int n) {
        if(m == 1 || n == 1){return 1;}
        return uniquePaths(m-1,n) + uniquePaths(m,n-1);
    }
}

题解2:dp

状态转移方程

dp[i][j] = dp[i-1][j] + dp[i][j-1];左边路径数 + 上边路径数

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];
    }
}

力扣64最小路径和

上面这道题的延申

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

状态转移方程

维护一个dp[i][j]表示该位置上的路径 = Math.min( 上or左路径 ) + 自身路径grid[i][j]

题解

其实可以发现这里不用新建一个dp[][],可以直接在原有的grid上进行修改

class Solution {
    public int minPathSum(int[][] grid) {
        int row = grid.length;
        int len = grid[0].length;
        int[][] dp = new int[row][len];
        dp[0][0] = grid[0][0];
        for(int i = 1 ; i < row ; i++){
            dp[i][0] = grid[i][0] + dp[i-1][0];
        }
        for(int j = 1 ; j < len ; j++){
            dp[0][j] = grid[0][j] + dp[0][j-1];
        }
        for(int i = 1 ; i < row ; i++){
            for(int j = 1 ; j < len ; j++){
                dp[i][j] = Math.min( dp[i-1][j] , dp[i][j-1])  +   grid[i][j];
            }
        }
        return dp[row-1][len-1];
    }
}

力扣121买卖股票最佳时机

简单题

状态转移方程

因为我们只是求最大值,所以不用维护dp[][],直接维护一个maxProfit用Math.max()来更新最大值即可

题解

class Solution {
    public int maxProfit(int[] prices) {
        int len = prices.length;
        int maxProfit = 0;
        for(int i = 0 ; i < len-1 ; i++){
            for(int j = i+1 ; j < len ; j++){
                maxProfit = Math.max(maxProfit, prices[j]-prices[i]);
            }
        }
      return maxProfit;

    }
}

青蛙跳台、斐波那契、爬楼梯

随便参考一个,用一维dp[]来保存前两位的值

递归

递归老问题,容易超时、爆栈

class Solution {
    public int climbStairs(int n) {
        if(n == 1 ) return 1;
        if(n == 2 ) return 2;
        return climbStairs(n-1) + climbStairs(n-2);
    }
}

动归

class Solution {
    public int climbStairs(int n) {
        if(n == 1)return 1;
        if(n == 2)return 2;
        int[] dp = new int[n+1];
        dp[1] = 1;
        dp[2] = 2;
        for(int i = 3 ; i <= n ; i++){
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[n];
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
动态规划是一种常见的算法思想,用于求解一些具有重叠子问题和最优子结构性质的问题。在动态规划过程中,状态转移是关键步骤之一,因为它决定了问题的求解方式和复杂度。为了检测前跟踪状态转移数,可以采用以下方法: 1. 定义状态:首先需要明确问题的状态,通常是由若干个变量表示的,如二维数组、三维数组等。定义好状态之后,可以考虑如何利用状态转移方程求解问题。 2. 设计状态转移方程:状态转移方程是将当前状态转移到下一状态的函数,通常是通过前面的状态推导出当前状态。在设计状态转移方程时,需要考虑问题的最优子结构性质,即当前状态的最优解可以由前面的状态的最优解推导得出。 3. 实现状态转移方程:在实现状态转移方程时,需要注意状态之间的依赖关系,以及状态转移的顺序。通常情况下,状态转移是按照某种顺序进行的,如从前往后、从上往下等。 4. 检测前跟踪状态转移数:在实现状态转移方程时,可以通过记录状态转移次数来检测前跟踪状态转移数。具体地,可以在状态转移方程中增加一个计数器,记录每次状态转移的次数。最后,统计所有状态转移次数,即可得到前跟踪状态转移数。 需要注意的是,前跟踪状态转移数的大小与问题的规模和复杂度有关,通常情况下,前跟踪状态转移数越大,问题的复杂度越高,求解时间空间复杂度也会增加。因此,在进行动态规划求解时,需要合理设计状态状态转移方程,以减少前跟踪状态转移数,提高算法效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值