动态规划刷题篇(一)

大家好鸭!这里是动态规划刷题频道,如果你也在学习动态规划,不妨一起来做做吧,题目选自LeetCode中等难度。

目录

👌Example 1(最长回文子串) 

👌Example 2(整数拆分)

👌Example 3(单词拆分)

👌Example 4 (打家劫舍)

👌Example 5 (最小化目标值与所选元素的差)


👌Example 1(最长回文子串) 

 给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:

输入:s = "cbbd"
输出:"bb"

LeetCode链接:最长回文子串

 👱‍♀️1.确定状态

在一个长度为n的字符串内,如果你知道第二个字符~第n-1个字符是回文串,且第1个字符与第n个字符相同,那么该字符串是一个回文串。如果第一个字符与第n个字符不相等,那么该字符串一定不是回文串。那么子字符串是不是回文串是我们要思考的问题。

状态:dp[i][j]用来记录起始下标为i,结尾下标为j的字符串是否是回文字符串。

情况1:如果i和j相等,那么只有一个字符,dp[i][j] = true

情况2:如果str[i] == str[j] && j-i == 1,dp[i][j] = true(相邻的字符串相同)

情况3:如果str[i] == str[j] && dp[i+1][j-1] == true

情况4:如果str[i] != str[j] dp[i][j] = false

🥞2.状态转移方程

dp[i][j] ={str[i] == str[j] &&(i == j || j-i == 1 || dp[i+1][j-1])} 

🙈4.计算顺序

在遍历的过程中,比如要计算dp[1][5],如果下标为1的字符和下标为5的字符相同,那么我要知道下标为2~下标为4的字符串是不是回文子串,那么先要知道dp[2][4]是什么情况,如果i从0开始遍历当你计算到dp[1][5]时,dp[2][4]还没有被计算出来。如果i从n-1开始计算遍历,dp[2][4]能够先计算出来,就可以直接使用了。

class Solution {
    public String longestPalindrome(String s) {
          if(s == null){
            return "";
        }

        int low = 0;//记录最长回文子串的起始位置
        int high = 0;//记录最长回文子串的末位置

        int n = s.length();
        char[] str = s.toCharArray();
      
        boolean[][] dp = new boolean[n][n];

        for(int i=n-1; i>=0; i--)//遍历顺序要从后往前
        {
            for(int j=i; j<n; j++){
               
               if(str[i] == str[j]){

                   if( i == j || j-i == 1){//起始位置和连续两个字符相同的情况
                       dp[i][j] = true;
                   }else if(dp[i+1][j-1]){
                       dp[i][j] = true;
                   }

               }

               if(dp[i][j] && j-i>high-low){
                   high = j;
                   low = i;
               }

            }
        }

        return s.substring(low,high+1);
    }
}

 解法2:(有兴趣的可以研究一下)

class Solution {
    public String longestPalindrome(String s) {
        int[] record = new int[2];//记录最长回文子串的起始位置

        char[] str = s.toCharArray();//字符串转字符数组
        int strSz = s.length();

        for(int i=0; i<strSz; i++){
            i = FindLongPalindrome(str,i,strSz,record);
        }
        
        return s.substring(record[0],record[1]+1);
     }

     public int FindLongPalindrome(char[] str,int low, int n,int[] record){
         int high = low;
         
         //找到回文子串的中心low~high
         while(high<n-1 && str[high+1] == str[low]){
             high++;
         }

         int ans = high;

        //寻找以low~high为中心的回文子串,向两边寻找
        while(low>0 && high<n-1 && str[low-1] == str[high+1]){
            low--;
            high++;
        }

        //记录最大长度
        if(high-low > record[1]-record[0]){
            record[0] = low;
            record[1] = high;
        }

        return ans;
     }
}

👌Example 2(整数拆分)

给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

返回 你可以获得的最大乘积 

示例 1:

输入: n = 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:

输入: n = 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

LeetCode链接:整数拆分

⌛1.确定状态

知道n的大小,n可以由k个数的和组成,存在一个数是组成k的最后一个数m,如果我们知道组成(n-m)的k个数的最大乘积是dp[n-m],我们就可以知道组成n的k个数的乘积是dp[n-m]*m。组成n的方式有很多种,我们只要选择出一个最大的即可。

状态:dp[i]表示组成i的k个数的最大乘积

🌯2.转移方程

dp[i] =max{dp[j]*(i-j),j*(i-j)}

✨3.初始条件及边界情况

dp[2] = 1;

😊4.计算顺序

d[2] dp[3]...dp[n]

class Solution {
    public int integerBreak(int n) {
        int[] dp = new int[n+1];
        dp[2] = 1;
        
        for(int i=3; i<=n; i++){

            for(int j=1;j<i;j++){
                dp[i] = Math.max(dp[j]*(i-j),dp[i]);
                dp[i] = Math.max(dp[i],j*(i-j));
            }
            
        }

        return dp[n];
    }
}

👌Example 3(单词拆分)

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

示例 1:

输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。
示例 2:

输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。

LeetCode链接:单词拆分

🐢1.确定状态

如果我们知道了s字符串中最后一个单词可以由wordDict字典中的单词组成,如果知道了剩余的字符串能够由wordDict字典中的单词组成,则字符串s就可以由字典中的单词组成,否则不能由字典中的单词组成。

状态:dp[i]表示字符串s中前i个字符是否能由字典中的单词组成,能组成为true,不能为false。

👶2.状态转移方程

dp[i] = {wordDict.contains(s.substring(j,i)) && dp[j]} j<i;如果前j个字符能够被组成,那么看剩余的字符是否能够被组成。

😅3.初始条件及边界情况

dp[0] = true

🎉4.计算顺序

dp[1] dp[2] ...dp[n]

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        // 可以类比于完全背包问题
        int n = s.length();

        boolean[] dp = new boolean[n+1];
        dp[0] = true;

        for(int i=1; i<=n; i++){

            for(int j=0; j<i; j++){

                if(wordDict.contains(s.substring(j,i))
                && dp[j]) 
                {
                    dp[i] = true;
                    break;
                }
            }

        }

        return dp[n];
    }
}

👌Example 4 (打家劫舍)

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 。

LeetCode链接:打家劫舍

🌏1.确定状态

偷窃到第n号房屋时,可能会存在两种情况,偷或不偷。如果偷了第n号房屋,那么第n-1号房屋一定是不能偷的,如果我们知道了只有n-2号房屋,能够偷的最大价值。那么我们就能计算出偷n号房子能够获取的最大金额。如果不偷,且我们知道有n-1号房屋能够偷的最大价值,我们就能知道不偷n号房子能够达到的最大价值。

状态:dp[i]表示偷到第i号房能够获取的最大金额。

🥫2.转移方程

dp[i] = max{dp[i-1],dp[i-2]+nums[i-1]}

🚖3.初始条件及边界情况

dp[1] = nums[0]

😁4.计算顺序

dp[2] dp[3] ... dp[n]

class Solution {
    public int rob(int[] nums) {
        int n = nums.length;

        if(n == 0 || nums == null){
            return 0;
        }

        int[] dp = new int[n+1];

        //初始条件
        dp[1] = nums[0];

        for(int i=2; i<=n; i++){
            dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i-1]);
        }

        return dp[n];
    }
}

👌Example 5 (最小化目标值与所选元素的差)

给你一个大小为 m x n 的整数矩阵 mat 和一个整数 target 。

从矩阵的 每一行 中选择一个整数,你的目标是 最小化 所有选中元素之 和 与目标值 target 的 绝对差 。

返回 最小的绝对差 。

a 和 b 两数字的 绝对差 是 a - b 的绝对值。

LeetCode链接:最小化目标值与所选元素的差

😍1.确定状态

转化为01背包问题,每一行当中的物品不能重复放入背包。例如第一行中有{1,2,3}。能放1或2或3。我们只要统计放入其中一个进背包,能够装满背包的情况。

状态:dp[i][k]放入第i行中的物品,是否能够填满容量为k的背包,能装满为true,不能装满为false。

🚄2.转移方程

dp[i][k] = dp[i-1][k-record]

说明:record为放入物品的大小

✨3.初始条件及边界情况

dp[0][0] = true

👰4.计算顺序 

dp[1][0] dp[1][1] dp[1][2]...dp[1][t-1]

..

dp[m][0] dp[m][1] dp[m][2]...dp[m][t-1]

class Solution {
    public int minimizeTheDifference(int[][] mat, int target) {

        //装化为01背包问题
        int m = mat.length;
        int n = mat[0].length;
        int x = Math.max(m,n);
        int t = 70 * x;

        boolean[][] dp = new boolean[m+1][t];
        dp[0][0] = true;

        for(int i=1; i<=m; i++){
            for(int j=0; j<n; j++){
                int record = mat[i-1][j];
                for(int k=record; k<t; k++){
                    if(dp[i-1][k-record]){
                        dp[i][k] = true;
                    }
                }
            }
        }

        int ret = Integer.MAX_VALUE;
        for(int i=0; i<t; i++){
            if(dp[m][i]){
                ret = Math.min(ret, Math.abs(i-target));
            }
        }

        return ret;
    }
}
  • 20
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南 栀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值