动态规划刷题篇(二)

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

目录

👉Example 1(三角形最小路径和)

👉Example 2(交错字符串)

👉Example 3 (解决智力问题)

👉Example 4(数字1的个数)


👉Example 1(三角形最小路径和)

给定一个三角形 triangle ,找出自顶向下的最小路径和。

每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。

示例 1:

输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出:11
解释:如下面简图所示:
   2
  3 4
 6 5 7
4 1 8 3
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
示例 2:

输入:triangle = [[-10]]
输出:-10

LeetCode链接:三角形最小路径和

💎1.确定状态

状态:dp[i][j]表示走到第i行第j列的最小和。

情况1:走到第i行第1列,只能从第i-1行第1列走到

情况2:走到第i行第m(第i行的最后一列)列,只能从i-1行m-1列走到

情况3:走到中间既可以从i-1行j-1列走到,也可以从i-1行j列走到

😜2.状态转移方程

dp[i][0] = dp[i-1][0]+data  (j = 0)

dp[i][m] = dp[i-1][m-1]+data  (j = m)

dp[i][j] =min{dp[i-1][j],dp[i-1][j-1]}+data;

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

dp[0][0] = trangle.get(0).get(0)

🚎4.计算顺序

dp[1][0] dp[1][1]

dp[2][0] dp[2][1] dp[2][2]

....

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

自上至下:

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        //自上到下,获取最大行和列
        int n = triangle.size();
        int m = triangle.get(n-1).size();
        int min = Integer.MAX_VALUE;
        
        int[][] dp = new int[n][m];
        dp[0][0] = triangle.get(0).get(0);//初始化

        for(int i=1; i<n; i++){
            List<Integer> row = triangle.get(i);
            dp[i][0] = row.get(0)+dp[i-1][0];

            for(int j=1; j<row.size(); j++){
               if(j == row.size()-1){
                   dp[i][j] = dp[i-1][j-1]+row.get(j);
                }
                else{
                    dp[i][j] = Math.min(dp[i-1][j],dp[i-1][j-1])+row.get(j);
                }
            }

        }

        //找最后一行的最大值
        for(int num:dp[m-1]){
            if(num<min){
                min = num;
            }
        }
        
        return min;
    }
}

自下至上:

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        //自上到下,获取最大行和列
        int n = triangle.size();
        int m = triangle.get(n-1).size();
        
        int[] dp = new int[m+1];

        for(int i=n-1;i >=0; i--){
            List<Integer> row = triangle.get(i);

            for(int j=0; j<row.size(); j++){
                dp[j] = Math.min(dp[j],dp[j+1])+row.get(j);
            }

        }

        return dp[0];
    }
}

👉Example 2(交错字符串)

给定三个字符串 s1、s2、s3,请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。

两个字符串 s 和 t 交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串:

s = s1 + s2 + ... + sn
t = t1 + t2 + ... + tm
|n - m| <= 1
交错 是 s1 + t1 + s2 + t2 + s3 + t3 + ... 或者 t1 + s1 + t2 + s2 + t3 + s3 + ...
注意:a + b 意味着字符串 a 和 b 连接。

LeetCode链接:交错字符串

👓1.确定状态

dp[i][j]:str1字符串中0~i个字符+str2字符串中0~j个字符是否能拼出str3[i+j],能拼出为true,不能拼出为false。

🛴2.状态转移方程

情况1:当i=0时,只用str2拼str3的部分

dp[0][j] = dp[0][j-1] && str2[j-1] == str3[j-1]

情况2:当j=0时,只用str1拼str3的部分

dp[i][0] = dp[i-1][0] && str1[i-1][j-1]

情况3:str1部分+str2部分拼str3的部分

dp[i][j] = (dp[i-1][j] && str1[i-1] == str3[i-1+j])

|| (dp[i][j-1] && str2[j-1] == str3[j-1+i])

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

初始条件:dp[0][0] = true 

📜4.计算顺序

dp[0][0] dp[0][1]..

dp[1][0] dp[1][1]..

...

class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {
        char[] str1 = s1.toCharArray();
        char[] str2 = s2.toCharArray();
        char[] str3 = s3.toCharArray();

        int len1 = s1.length();
        int len2 = s2.length();
        int len3 = s3.length();

        if(len3 != len1 + len2){
            return false;
        }

        boolean[][] dp = new boolean[len1+1][len2+1];
        dp[0][0] = true;

        //只用str2时
        for(int i =1; i<= len2; i++){
            if(dp[0][i-1] == true && str2[i-1] == str3[i-1]){
                dp[0][i] = true;
            }
        }

        //只用str1时
        for(int i=1; i<=len1; i++){
            if(dp[i-1][0] == true && str1[i-1] == str3[i-1]){
                dp[i][0] = true;
            }
        }
        
        for(int i=1; i<=len1; i++){
            for(int j=1; j<=len2; j++){
                dp[i][j] = (dp[i-1][j] && str1[i-1] == str3[i-1+j])
                            ||(dp[i][j-1] && str2[j-1] == str3[j-1+i]);
            }
        }

        return dp[len1][len2];
    }
}

👉Example 3 (解决智力问题)

给你一个下标从 0 开始的二维整数数组 questions ,其中 questions[i] = [pointsi, brainpoweri] 。

这个数组表示一场考试里的一系列题目,你需要 按顺序 (也就是从问题 0 开始依次解决),针对每个问题选择 解决 或者 跳过 操作。解决问题 i 将让你 获得  pointsi 的分数,但是你将 无法 解决接下来的 brainpoweri 个问题(即只能跳过接下来的 brainpoweri 个问题)。如果你跳过问题 i ,你可以对下一个问题决定使用哪种操作。

比方说,给你 questions = [[3, 2], [4, 3], [4, 4], [2, 5]] :
如果问题 0 被解决了, 那么你可以获得 3 分,但你不能解决问题 1 和 2 。
如果你跳过问题 0 ,且解决问题 1 ,你将获得 4 分但是不能解决问题 2 和 3 。
请你返回这场考试里你能获得的 最高 分数。

LintCode链接:解决智力问题

我觉得我智力有问题,解决不了智力问题🙄。开始想着前推后,超时了。后面看别人的题解,是后推前🤢。

1.确定状态

对于每一题,可能存在两种情况,要么写要么不写。到底写不写💢, 我们采用逆向思维,从后往前写,如果我们写了这一题,就能拿到这一题的分,如果我们知道了他下一次跳题能够得到的最大分,那么我们就可以知道写这一题能够拿到的最大分。如果不写这一题,那么获取的最大分就是写上一题或不写上一题获取的分。

1 1 dp[0] = 7;

2 2 dp[1] = 7;

3 3 dp[2] = 5;

4 4 dp[3] = 5;

5 5 dp[4] = 5

从第五题开始,选择写或不写,因为第五题是最后一题,没有下一题,也没有下一次跳题的最大分,这时你肯定选择写,因为写能够获得到五分。现在看第四题,有下一题,但是没有下一题跳题,如果写该题,那么只能拿到四分,因为写了这一题就不能写第五题,写第五题分更高,那么肯定不写这一题。现在看第三题,也没有下一次跳题,如果写了这题,四五题都不能写,才拿三分,不划算,不写,所以还是写第五题划算。现在看第二题,有下一次跳题,写了这二题还可以写第五题,何乐而不为,所以写第二题能够拿到七分。现在看第一题,写第一题就不能写二题,但是可以写第三题或第四题或第五题,能够获得的最大分为6,没有七分划算,那就不写。

dp[i]:从第i题开始写,写到第n题的最大分。

2.状态转移方程

有下一次跳题:

dp[i] = max{dp[i+1], dp[i+questions[i][1]+1]+questions[i][0]}

没有下一次跳题

dp[i] = max{dp[i+1], questions[i][0]}

3.初始条件及边界情况

最后一题没有下一题,会存在越界。

dp[n] = 0

4.计算顺序

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

class Solution {
    public long mostPoints(int[][] questions) {
        if(questions == null){
            return 0;
        }

        int n = questions.length;
        
        long[] dp = new long[n+1];
        dp[n] = 0;

        for(int i=n-1; i>=0; i--){

            if(i+questions[i][1]+1<n){
                dp[i] = Math.max(dp[i+1],questions[i][0]+dp[i+questions[i][1]+1]);
            }else{
                dp[i] = Math.max(dp[i+1],questions[i][0]);
            }

        }

        return dp[0];
    }
}

👉Example 4(数字1的个数)

给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。

LeetCode链接:数字1的个数 

 1.确定状态

该题需要采用分块的计算方法。

我们可以很快的计算出0~9中1的个数,只有1中含有1,那么我们如何计算出0~99中1的个数。

0~9中1的个数我们已经知道了,那么我们考虑如何计算10~99中1的个数,可以先计算十位是1的情况,有10 11 12 13 14 1 5 16 17 18 19,我们暂且不考虑个位上1有多少个,那么有10个数十位含有1,现在我们考虑各位是1的情况。

1(0,1,2,3,4,5,6,7,8,9)

2(0,1,2,3,4,5,6,7,8,9)

3(0,1,2,3,4,5,6,7,8,9)

...

9(0,1,2,3,4,5,6,7,8,9)

十位上1的个数我们已经确定了,且已经知道了0~9中1的个数,那么我们就可以知道各位上有1的情况有多少种,设0~9种1的个数为A[0],则0~99中1的个数为

A[1] = 9*A[0]+A[0]+10

A[1] = 10 * A[0]+10

同理也可以计算出0~999中1的个数,同样固定百位是1,百位为1的个数有100个,现在考虑后两位是1的情况,因为我们已经知道0~99中1的个数,计算100~999中的个数

1(0,1 ,2,3,4,...,99)

2(0,1,2,3,4,...,99)

3(0,1,2,3,4,...,99)

...

9(0,1,2,3,4,...99)

设0~999中1的个数为A[2]

A[2] = 9 * A[1] + A[1] +10^2

A[2] = 10 * A[1] + 10^2

同理,我们可以通过该种方法递推出A[3],A[4]...。

例子:计算12345中1的个数

通过上面的递推我们可以很快的计算出0~9999中1的个数,那么10000~12345中1的个数如何进行计算呢?同样我们采用递推的方法。我们先计算0~5中1的个数,显而易见只有1个,设dp[0] = 1,接下来计算45中1的个数,因为我们已经计算出了0~5中1的个数,所有我们只要计算0~40中1的个数即可,同样看十位是1的情况。

10 11 12 13 14 15 16 17 18 19 

看个位中存在1的情况

0(1,2,3,4,5,6,7,8,9)

1(1,2,3,4,5,6,7,8,9)

2(1,2,3,4,5,6,7,8,9)

3(1,2,3,4,5,6,7,8,9)

dp[1] = 4*A[0]+10+dp[0]

同理可得

dp[2] = 3*A[2]+100+dp[1]

dp[3] = 2*A[3] + 1000+dp[2]

....

状态:dp[i]表示该数后i+1位中1的个数

2.转移方程

如果后i+1位中的最高位是0:

dp[i] = dp[i-1]

如果i+1位中的最高位是1,rest = (i+1)位数中最高位是1的个数,比如123,百位是1的数有24个。

dp[i] = rest + A[i-1]+dp[i-1]

其他情况:

dp[i] = num*A[i-1]+dp[i-1]+10^i

3.初始化及边界情况

若最后i+1位是0

dp[0] = 0;

否则

dp[0] = 1

4.计算顺序

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

class Solution {
    public int countDigitOne(int n) {
        //将n转化为字符串
        String s = String.valueOf(n);
        int len = s.length();
        char[] ch = s.toCharArray();

        if(len == 1)
        {
            return n>0?1:0;
        }
        
        int[] A = new int[len-1];
        A[0] = 1;

        //计算除最高位部分1的个数
        for(int i=1; i<len-1; i++)
        {
            A[i] = 10*A[i-1]+(int)Math.pow(10,i);
        }
        
        //计算余数中1的个数
        int[] dp = new int[len];

        if(ch[len-1] == '0'){
            dp[0] = 0;
        }else{
            dp[0] = 1;
        }

        for(int i=1; i<len; i++)
        {
            char chTemp = ch[len-i-1];
            //高位为0
            if(chTemp == '0'){
                dp[i] = dp[i-1];
                continue;
            }

            //高位为1
            if(chTemp == '1'){
                int rest = Integer.parseInt(s.substring(len-i,len))+1;
                dp[i] = rest + dp[i-1]+A[i-1];
            }else{
                int temp = chTemp-'0';
                dp[i] = dp[i-1]+temp*A[i-1]+(int)Math.pow(10,i);
            }

        }

        return dp[len-1];
    }
}

能力有限⌛,如有错还请大佬指教。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

南 栀

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

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

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

打赏作者

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

抵扣说明:

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

余额充值