[题目解析]数学专题:130;动态规划专题:38、108、123、128 | 豆包MarsCode AI刷题

144 阅读9分钟

103.完美整数

问题描述

一个整数如果由相同的数字构成,则称为完美整数。例如:

  • 111333 是完美整数。
  • 1219101 是不完美整数。

现在,你需要计算给定区间 [x, y] 中有多少个整数是完美整数。

测试样例

样例1:

输入:x = 1 ,y = 10 输出:9

样例2:

输入:x = 2 ,y = 22 输出:10

解决方案

这个题如果暴力枚举x和y之间的数,然后判断它们是否是完美整数,这样做当x很小而y很大的时候容易超时,所以可以换一种策略。因为完美整数的个数比较少,所以不用去判断每个数是否是完美整数,而是直接枚举每一个完美整数,然后判断它是否在x和y之间。上述做法的时间复杂度为O(9n)O(9n),n为完美整数枚举的位数。

参考代码

public class Main {
    public static int solution(int x, int y) {
        // Edit your code here
        int res = x == 0 ? 1 : 0;
        for (int i = 1; i <= 9; i++) {
            int v = 0;
            for (int k = 0; k < 9; k++) {
                v = v * 10 + i;
                if (v >= x && v <= y) {
                    res++;
                } else if (v > y) {
                    break;
                }
            }
        }
        return res;
    }

    public static void main(String[] args) {
        // Add your test cases here
        
        System.out.println(solution(1, 10) == 9);
        System.out.println(solution(2, 22) == 10);
    }
}

38.补给站最优花费问题

问题描述

小U计划进行一场从地点A到地点B的徒步旅行,旅行总共需要 M 天。为了在旅途中确保安全,小U每天都需要消耗一份食物。在路程中,小U会经过一些补给站,这些补给站分布在不同的天数上,且每个补给站的食物价格各不相同。

小U需要在这些补给站中购买食物,以确保每天都有足够的食物。现在她想知道,如何规划在不同补给站的购买策略,以使她能够花费最少的钱顺利完成这次旅行。

  • M:总路程所需的天数。
  • N:路上补给站的数量。
  • p:每个补给站的描述,包含两个数字 AB,表示第 A 天有一个补给站,并且该站每份食物的价格为 B 元。

保证第0天一定有一个补给站,并且补给站是按顺序出现的。

测试样例

样例1:

输入:m = 5 ,n = 4 ,p = [[0, 2], [1, 3], [2, 1], [3, 2]] 输出:7

样例2:

输入:m = 6 ,n = 5 ,p = [[0, 1], [1, 5], [2, 2], [3, 4], [5, 1]] 输出:6

样例3:

输入:m = 4 ,n = 3 ,p = [[0, 3], [2, 2], [3, 1]] 输出:9

解决方案

为了实现最优花费,那么每次购买食物时都应该购买到目前为止所有补给站里面最便宜的食物,所以可以遍历补给站的信息,然后存储到目前为止所有补给站食物的最小价格,然后用当前天数减去之前的天数再乘上最小的食物价格,表示这几天购买的食物来自于之前最便宜的补给站,最后需要判断最后一个补给站是否到了最后一天,如果没有,那么还需要购买食物,道理和之前一样。

参考代码

public class Main {
    public static int solution(int m, int n, int[][] p) {
        // Edit your code here
        int cnt = 1, res = p[0][1], min_v = p[0][1];
        for (int i = 1; i < n; i++) {
            res += (p[i][0]  - cnt) * min_v;
            cnt = p[i][0];
            min_v = Math.min(min_v, p[i][1]);
        }
        res += (m - cnt) * min_v;
        return res;
    }

    public static void main(String[] args) {
        // Add your test cases here

        System.out.println(solution(5, 4, new int[][]{{0, 2}, {1, 3}, {2, 1}, {3, 2}}) == 7);
    }
}

108.小M的弹子游戏机挑战

问题描述

小M最近迷上了一款弹子游戏机,规则如下: 玩家可以在版面最上方任意一个位置放置弹珠。弹珠会通过得分点时为玩家赢得分数,目标是获得尽可能高的分数。 弹子游戏机的版面由两种组成要素构成:

  1. 钉子(用 -1 表示),当弹珠碰到钉子时,有可能弹射到左下或者右下的位置。
  2. 得分点(非负整数),弹珠经过得分点时可以获得对应的分数。

如果弹珠所在的格子为空(即没有钉子或者得分点),弹珠会直接往下落。

小M想知道,在一个给定的版面布局中,他能够获得的最高分数是多少。

  • n 表示版面的高度。
  • m 表示版面的宽度。
  • array 是一个 n x m 的二维数组,其中:
  • -1 表示该位置为钉子;
  • 0 表示该位置为空;
  • 正整数表示该位置为得分点,值为该得分点的分数。

测试样例

样例1:

输入:n = 3 ,m = 3 ,array = [[-1, 0, -1], [100, 0, 0], [0, 50, 70]] 输出:50

样例2:

输入:n = 4 ,m = 3 ,array = [[-1, 0, -1], [0, -1, 0], [50, 100, 70], [80, 200, 50]] 输出:130

样例3:

输入:n = 5 ,m = 5 ,array = [[0, -1, 0, -1, 0], [0, 50, -1, 50, 0], [100, 0, 0, 0, 100], [0, 100, 0, 100, 0], [50, 0, 50, 0, 50]] 输出:150

解决方案

这个题可以使用动态规划进行求解。状态表示:f[i][j]f[i][j]表示把弹珠放置在第ii行第jj列可以获得的最大分数。状态计算:1、如果当前位置为钉子,那么弹珠会弹射到左下角或者右下角,此时f[i][j]=max(f[i+1][j1],f[i+1][j+1])f[i][j]=max(f[i+1][j-1],f[i+1][j+1]);如果当前位置不是钉子,那么弹珠会往下掉,并且加上当前位置的分数,此时f[i][j]=f[i+1][j]+array[i][j]f[i][j]=f[i+1][j] + array[i][j]。那么max(f[0][j]),0<j<mmax(f[0][j]),0<j<m就是答案。上述做法的时间复杂度是O(nm)O(nm)

参考代码

public class Main {
    public static int solution(int n, int m, int[][] array) {
        // Edit your code here
        int[][] f = new int[n][m];
        for (int i = n - 1; i >= 0; i--) {
            for (int j = 0; j < m; j++) {
                if (array[i][j] == -1) {
                    if (i + 1 < n) {
                        if (j - 1 >= 0) {
                            f[i][j] = Math.max(f[i][j], f[i + 1][j - 1]);
                        }
                        if (j + 1 < m) {
                            f[i][j] = Math.max(f[i][j], f[i + 1][j + 1]);
                        }
                    }
                } else {
                    f[i][j] = (i + 1 < n ? f[i + 1][j] : 0) + array[i][j];
                }
            }
        }
        int res = 0;
        for (int j = 0; j < m; j++) {
            res = Math.max(res, f[0][j]);
        }
        return res;
    }

    public static void main(String[] args) {
        // Add your test cases here
        System.out.println(solution(3, 3, new int[][]{{-1, 0, -1}, {100, 0, 0}, {0, 50, 70}}) == 50);
        System.out.println(solution(4, 3, new int[][]{{-1, 0, -1}, {0, -1, 0}, {50, 100, 70}, {80, 200, 50}}) == 130);
    }
}

123.最优硬币组合问题

问题描述

小C有多种不同面值的硬币,每种硬币的数量是无限的。他希望知道,如何使用最少数量的硬币,凑出给定的总金额N。小C对硬币的组合方式很感兴趣,但他更希望在满足总金额的同时,使用的硬币数量尽可能少。

例如:小C有三种硬币,面值分别为 1, 2, 5。他需要凑出总金额为 18。一种最优的方案是使用三个 5 面值的硬币,一个 2 面值的硬币和一个 1 面值的硬币,总共五个硬币。

测试样例

样例1:

输入:coins = [1, 2, 5], amount = 18 输出:[5, 5, 5, 2, 1]

样例2:

输入:coins = [1, 3, 4], amount = 6 输出:[3, 3]

样例3:

输入:coins = [5], amount = 10 输出:[5, 5]

解决方案

这个问题其实就是动态规划经典问题完全背包问题的变种,硬币就是物品,总金额就是背包,每个物品的价值都是1,每个物品的数量都是无限,要求在恰好装满背包的前提下,使得背包中物品的总价值最小。状态表示:f[i]f[i]表示总金额为ii的条件下,使用的硬币的最小数量。状态计算:可以枚举最后一枚硬币,那么f[i]=min(f[icoins[k]])+1,k是硬币的下标f[i] = min(f[i-coins[k]])+1,k是硬币的下标。由于这个题还要求输出最后的方案,那么可以在状态转移的时候记录一下是从哪个状态转移过来的,或者记录一下最后用的是哪一枚硬币,最后倒推一下就可以得到最优方案。上述做法的时间复杂度为O(namount)O(n*amount),n是硬币的种类。

参考代码

import java.util.ArrayList;
import java.util.*;

public class Main {
    public static List<Integer> solution(int[] coins, int amount) {
        // Edit your code here
        final int INF = 0x3f3f3f3f;
        int[] f = new int[amount + 1];
        int[] pre = new int[amount + 1];
        for (int i = 1; i <= amount; i++) {
            f[i] = INF;
            for (int x : coins) {
                if (x <= i && f[i - x] + 1 < f[i]) {
                    f[i] = f[i - x] + 1;
                    pre[i] = x;
                }
            }
        }
        ArrayList<Integer> res = new ArrayList<>();
        if (f[amount] == INF) {
            return res;
        }
        int x = amount;
        while (x != 0) {
            res.add(pre[x]);
            x -= pre[x];
        }
        Collections.sort(res, Comparator.reverseOrder());
        return res;
    }

    public static void main(String[] args) {
        // Add your test cases here
        
        System.out.println(solution(new int[]{1, 2, 5}, 18).equals(List.of(5, 5, 5, 2, 1)));
    }
}

128.找出最长的神奇数列

问题描述

小F是一个好学的中学生,今天他学习了数列的概念。他在纸上写下了一个由 01 组成的正整数序列,长度为 n。这个序列中的 10 交替出现,且至少由 3 个连续的 01 组成的部分数列称为「神奇数列」。例如,10101 是一个神奇数列,而 1011 不是。现在,小F想知道在这个序列中,最长的「神奇数列」是哪一个。你能帮他找到吗?

如果有多个神奇数列,那么输出最先出现的一个。

测试样例

样例1:

输入:inp = "0101011101" 输出:'010101'

样例2:

输入:inp = "1110101010000" 输出:'10101010'

样例3:

输入:inp = "1010101010101010" 输出:'1010101010101010'

解决方案

遍历该序列,如果当前字符是第一个字符或者当前字符和上一个字符不相同,那么就记录这个字符串;否则,则判断记录的字符串是否长度大于等于3,同时,更新最长的神奇数列。上述做法的时间复杂度是O(n)O(n)

参考代码

public class Main {
    public static String solution(String inp) {
        // Edit your code here
        int n = inp.length();
        String res = "", tmp = "";
        for (int i = 0; i < n; i++) {
            if (i == 0 || inp.charAt(i) != inp.charAt(i - 1)) {
                tmp += inp.charAt(i);
            } else {
                if (tmp.length() >= 3 && tmp.length() > res.length()) {
                    res = tmp;
                }
                tmp = "" + inp.charAt(i);
            }
        }
        if (tmp.length() >= 3 && tmp.length() > res.length()) {
            res = tmp;
        }
        return res;
    }

    public static void main(String[] args) {
        // Add your test cases here
        
        System.out.println(solution("0101011101").equals("010101"));
    }
}