【题解】—— LeetCode一周小结13

news2025/1/16 10:57:36

【题解】—— 每日一道题目栏


上接:【题解】—— LeetCode一周小结12

25.零钱兑换 II

题目链接:518. 零钱兑换 II

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。

题目数据保证结果符合 32 位带符号整数。

示例 1:

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

输出:4

解释:有四种方式可以凑成总金额:

5=5

5=2+2+1

5=2+1+1+1

5=1+1+1+1+1

示例 2:

输入:amount = 3, coins = [2]

输出:0

解释:只用面额 2 的硬币不能凑成总金额 3 。

示例 3:

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

输出:1

提示:

1 <= coins.length <= 300

1 <= coins[i] <= 5000

coins 中的所有值 互不相同

0 <= amount <= 5000

题解:
方法:完全背包

        使用动态规划解决零钱兑换问题,采用完全背包的思想。

        设计一维数组f,其中f[j]表示凑出金额j的组合数。

        初始化数组f,将f[0]设置为1,表示凑出金额0的组合数为1。

        通过状态转移方程更新数组f的值,即f[j] += f[j - val],其中val为硬币的面值,表示凑出金额j的组合数等于凑出金额j - val的组合数加上当前硬币的组合数。

        最终返回f[cnt],即使用所有硬币凑出金额cnt的组合数。

class Solution {
    // 使用动态规划解决零钱兑换问题(完全背包)
    public int change(int cnt, int[] cs) {
        int[] f = new int[cnt + 1]; // 定义一维数组f,表示凑出金额j的组合数
        f[0] = 1; // 初始化凑出金额0的组合数为1

        // 动态规划状态转移过程,更新数组f的值
        for (int val : cs) { // 遍历硬币面值数组
            for (int j = val; j <= cnt; j++) { // 遍历金额范围
                f[j] += f[j - val]; // 更新组合数
            }
        }

        return f[cnt]; // 返回使用所有硬币凑出目标金额的组合数
    }
}

方法:完全背包 (优化)

class Solution {
    // 使用动态规划解决零钱兑换问题(完全背包,一维优化)
    public int change(int cnt, int[] cs) {
        int[] f = new int[cnt + 1]; // 定义一维数组f,表示凑出金额j的组合数
        f[0] = 1; // 初始化凑出金额0的组合数为1

        // 动态规划状态转移过程,更新数组f的值
        for (int val : cs) { // 遍历硬币面值数组
            for (int j = val; j <= cnt; j++) { // 遍历金额范围
                f[j] += f[j - val]; // 更新组合数
            }
        }

        return f[cnt]; // 返回使用所有硬币凑出目标金额的组合数
    }
}


26.设计可以求最短路径的图类

题目链接:2642. 设计可以求最短路径的图类

给你一个有 n 个节点的 有向带权 图,节点编号为 0 到 n - 1 。图中的初始边用数组 edges 表示,其中 edges[i] = [fromi, toi, edgeCosti] 表示从 fromi 到 toi 有一条代价为 edgeCosti 的边。

请你实现一个 Graph 类:

  • Graph(int n, int[][] edges) 初始化图有 n 个节点,并输入初始边。
  • addEdge(int[] edge) 向边集中添加一条边,其中 edge = [from, to, edgeCost] 。数据保证添加这条边之前对应的两个节点之间没有有向边。
  • int shortestPath(int node1, int node2) 返回从节点 node1 到 node2 的路径 最小 代价。如果路径不存在,返回 -1 。一条路径的代价是路径中所有边代价之和。

示例 1:

在这里插入图片描述

输入:

[“Graph”, “shortestPath”, “shortestPath”, “addEdge”, “shortestPath”]

[[4, [[0, 2, 5], [0, 1, 2], [1, 2, 1], [3, 0, 3]]], [3, 2], [0, 3],
[[1, 3, 4]], [0, 3]]

输出:

[null, 6, -1, null, 6]

解释:

Graph g = new Graph(4, [[0, 2, 5], [0, 1, 2], [1, 2, 1], [3, 0, 3]]);

g.shortestPath(3, 2); // 返回 6 。从 3 到 2 的最短路径如第一幅图所示:3 -> 0 -> 1 -> 2
,总代价为 3 + 2 + 1 = 6 。

g.shortestPath(0, 3); // 返回 -1 。没有从 0 到 3 的路径。

g.addEdge([1, 3, 4]); // 添加一条节点 1 到节点 3 的边,得到第二幅图。

g.shortestPath(0, 3); // 返回 6 。从 0 到 3 的最短路径为 0 -> 1 -> 3 ,总代价为 2 + 4
= 6 。

提示:

1 <= n <= 100

0 <= edges.length <= n * (n - 1)

edges[i].length == edge.length == 3

0 <= fromi, toi, from, to, node1, node2 <= n - 1

1 <= edgeCosti, edgeCost <= 106

图中任何时候都不会有重边和自环。

调用 addEdge 至多 100 次。

调用 shortestPath 至多 100 次。

题解:
方法:Dijkstra

        直接把边addEdge加入图中即可。

        对于 shortestPath,用 Dijkstra 算法计算从起点 start 到终点 end 的最短路长度。

邻接矩阵建图 + 朴素 Dijkstra

class Graph {
    private static final int INF = Integer.MAX_VALUE / 2; // 防止更新最短路时加法溢出

    private final int[][] g; // 邻接矩阵表示图的边信息

    // 构造方法,初始化图的邻接矩阵并添加边
    public Graph(int n, int[][] edges) {
        g = new int[n][n]; // 初始化邻接矩阵
        for (int[] row : g) {
            Arrays.fill(row, INF); // 初始化邻接矩阵的值为无穷大
        }
        for (int[] e : edges) {
            addEdge(e); // 添加一条边(题目保证没有重边)
        }
    }

    // 添加一条边到邻接矩阵
    public void addEdge(int[] e) {
        g[e[0]][e[1]] = e[2]; // 添加一条边(题目保证这条边之前不存在)
    }

    // 求解从起点到终点的最短路径长度
    public int shortestPath(int start, int end) {
        int n = g.length;
        int[] dis = new int[n]; // 起点到各个点的最短路径长度
        Arrays.fill(dis, INF); // 初始化为无穷大
        dis[start] = 0; // 起点到自身的距离为0
        boolean[] vis = new boolean[n]; // 标记节点是否被访问过
        while (true) {
            int x = -1; // 最近的未访问节点
            for (int i = 0; i < n; i++) {
                if (!vis[i] && (x < 0 || dis[i] < dis[x])) {
                    x = i;
                }
            }
            if (x < 0 || dis[x] == INF) { // 所有从起点能到达的点都被更新了
                return -1; // 终点无法到达
            }
            if (x == end) { // 找到终点,提前退出
                return dis[x]; // 返回终点的最短路径长度
            }
            vis[x] = true; // 标记节点为已访问
            for (int y = 0; y < n; y++) {
                dis[y] = Math.min(dis[y], dis[x] + g[x][y]); // 更新最短路径长度
            }
        }
    }
}

邻接表建图 + 堆优化 Dijkstra

class Graph {
    private final List<int[]>[] g; // 邻接表表示图的边信息

    // 构造方法,初始化图的邻接表并添加边
    public Graph(int n, int[][] edges) {
        g = new ArrayList[n]; // 初始化邻接表
        Arrays.setAll(g, i -> new ArrayList<>()); // 初始化邻接表的数组
        for (int[] e : edges) {
            addEdge(e); // 添加一条边(题目保证没有重边)
        }
    }

    // 添加一条边到邻接表
    public void addEdge(int[] e) {
        g[e[0]].add(new int[]{e[1], e[2]}); // 添加一条边(题目保证这条边之前不存在)
    }

    // 求解从起点到终点的最短路径长度
    public int shortestPath(int start, int end) {
        int[] dis = new int[g.length]; // 起点到各个点的最短路径长度
        Arrays.fill(dis, Integer.MAX_VALUE); // 初始化为无穷大
        dis[start] = 0; // 起点到自身的距离为0
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> (a[0] - b[0])); // 使用最小堆存储节点,按路径长度排序
        pq.offer(new int[]{0, start}); // 将起点加入优先队列
        while (!pq.isEmpty()) {
            int[] p = pq.poll(); // 弹出当前路径长度最短的节点
            int d = p[0]; // 当前路径长度
            int x = p[1]; // 当前节点
            if (x == end) { // 到达终点,返回最短路径长度
                return d;
            }
            if (d > dis[x]) { // x 之前出堆过,无需更新邻居的最短路
                continue;
            }
            for (int[] e : g[x]) {
                int y = e[0]; // 邻居节点
                int w = e[1]; // 边的权重
                if (d + w < dis[y]) { // 如果通过当前节点 x 到达邻居节点 y 的路径更短
                    dis[y] = d + w; // 更新最短路径长度
                    pq.offer(new int[]{dis[y], y}); // 将邻居节点加入优先队列
                }
            }
        }
        return -1; // 无法到达终点
    }
}

题单:Dijkstra 算法

2642. 设计可以求最短路径的图类
1514. 概率最大的路径
1631. 最小体力消耗路径
1368. 使网格图至少有一条有效路径的最小代价
1786. 从第一个节点出发到最后一个节点的受限路径数
1976. 到达目的地的方案数
2662. 前往目标的最小代价
2045. 到达目的地的第二短时间
882. 细分图中的可到达节点
2203. 得到要求路径的最小带权子图
2577. 在网格图中访问一个格子的最少时间
2699. 修改图中的边权
2093. 前往目标城市的最小费用
2473. 购买苹果的最低成本
2714. 找到最短路径的 K 次跨越
2737. 找到最近的标记节点
LCP 35. 电动车游城市

方法:Floyd

        创建一个Graph类表示图结构,使用邻接矩阵存储图的边权重信息,并使用Floyd-Warshall算法求解任意两点之间的最短路径长度。

        在构造方法中,初始化邻接矩阵,并根据边的信息更新图的边权重。然后,利用Floyd-Warshall算法计算任意两点之间的最短路径长度。

        使用INF表示无穷大,防止加法溢出,初始化邻接矩阵的对角线为0,其他位置为INF。

        构造方法中使用两层循环遍历所有节点对,并通过中间节点k更新从节点i到节点j的最短路径长度。

        shortestPath方法用于返回从指定起点到终点的最短路径长度,如果路径不存在则返回-1。

        addEdge方法用于添加一条边,并根据新添加的边更新图的最短路径长度。

class Graph {
    private static final int INF = Integer.MAX_VALUE / 3; // 防止更新最短路时加法溢出

    private final int[][] f; // 邻接矩阵表示图的边权重信息

    // 构造方法,初始化邻接矩阵,并使用Floyd-Warshall算法计算任意两点之间的最短路径长度
    public Graph(int n, int[][] edges) {
        f = new int[n][n]; // 初始化邻接矩阵
        for (int i = 0; i < n; i++) {
            Arrays.fill(f[i], INF); // 初始化邻接矩阵为无穷大
            f[i][i] = 0; // 对角线上的元素为0
        }
        for (int[] e : edges) {
            f[e[0]][e[1]] = e[2]; // 添加一条边(题目保证没有重边和自环)
        }
        for (int k = 0; k < n; k++) {
            for (int i = 0; i < n; i++) {
                if (f[i][k] == INF) {
                    continue;
                }
                for (int j = 0; j < n; j++) {
                    f[i][j] = Math.min(f[i][j], f[i][k] + f[k][j]); // 使用中间节点k更新最短路径长度
                }
            }
        }
    }

    // 添加一条边,并根据新添加的边更新图的最短路径长度
    public void addEdge(int[] e) {
        int x = e[0], y = e[1], w = e[2], n = f.length;
        if (w >= f[x][y]) { // 如果新添加的边权重大于等于已有的边权重,则无需更新
            return;
        }
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                f[i][j] = Math.min(f[i][j], f[i][x] + w + f[y][j]); // 更新最短路径长度
            }
        }
    }

    // 返回从指定起点到终点的最短路径长度,如果路径不存在则返回-1
    public int shortestPath(int start, int end) {
        int ans = f[start][end]; // 获取起点到终点的最短路径长度
        return ans < INF ? ans : -1; // 如果路径长度小于INF,则返回路径长度,否则返回-1
    }
}


27.统计将重叠区间合并成组的方案数

题目链接:2580. 统计将重叠区间合并成组的方案数

给你一个二维整数数组 ranges ,其中 ranges[i] = [starti, endi] 表示 starti 到 endi 之间(包括二者)的所有整数都包含在第 i 个区间中。

你需要将 ranges 分成 两个 组(可以为空),满足:

  • 每个区间只属于一个组。

  • 两个有 交集 的区间必须在 同一个 组内。

如果两个区间有至少 一个 公共整数,那么这两个区间是 有交集 的。

  • 比方说,区间 [1, 3] 和 [2, 5] 有交集,因为 2 和 3 在两个区间中都被包含。
    请你返回将 ranges 划分成两个组的 总方案数 。由于答案可能很大,将它对 109 + 7 取余 后返回。

示例 1:

输入:ranges = [[6,10],[5,15]]

输出:2

解释:

两个区间有交集,所以它们必须在同一个组内。

所以有两种方案:

  • 将两个区间都放在第 1 个组中。
  • 将两个区间都放在第 2 个组中。

示例 2:

输入:ranges = [[1,3],[10,20],[2,5],[4,8]]

输出:4

解释:

区间 [1,3] 和 [2,5] 有交集,所以它们必须在同一个组中。

同理,区间 [2,5] 和 [4,8] 也有交集,所以它们也必须在同一个组中。

所以总共有 4 种分组方案:

  • 所有区间都在第 1 组。
  • 所有区间都在第 2 组。
  • 区间 [1,3] ,[2,5] 和 [4,8] 在第 1 个组中,[10,20] 在第 2 个组中。
  • 区间 [1,3] ,[2,5] 和 [4,8] 在第 2 个组中,[10,20] 在第 1 个组中。

提示:

1 <= ranges.length <= 105

ranges[i].length == 2

0 <= starti <= endi <= 109

题解:
方法:合并区间

        首先对ranges按照区间的起点进行升序排序,这样可以保证后面的区间起点始终不小于前面的区间起点。

        初始化ans为1,用于记录合法区间的数量。初始化maxR为-1,用于记录当前能够合并的区间的最大右边界。

        遍历排序后的ranges数组,对于每个区间p:

        如果当前区间的起点p[0]大于maxR,说明当前区间无法与之前的区间合并,此时将ans乘以2并取模1_000_000_007,表示新增加一个独立的区间。

        更新maxR为当前区间的右边界p[1],表示将当前区间合并到已有的区间中。

        遍历结束后,返回ans作为结果。

注释已添加在代码中。

class Solution {
    public int countWays(int[][] ranges) {
        // 对ranges按照区间的起点进行升序排序
        Arrays.sort(ranges, (a, b) -> a[0] - b[0]);
        // 初始化ans为1,用于记录合法区间的数量
        int ans = 1;
        // 初始化maxR为-1,用于记录当前能够合并的区间的最大右边界
        int maxR = -1;
        // 遍历排序后的ranges数组
        for (int[] p : ranges) {
            // 如果当前区间的起点p[0]大于maxR,说明当前区间无法与之前的区间合并
            if (p[0] > maxR) {
                // 将ans乘以2并取模1_000_000_007,表示新增加一个独立的区间
                ans = ans * 2 % 1_000_000_007;
            }
            // 更新maxR为当前区间的右边界p[1],表示将当前区间合并到已有的区间中
            maxR = Math.max(maxR, p[1]);
        }
        // 返回ans作为结果
        return ans;
    }
}

题单:合并区间

难度1
56. 合并区间
55. 跳跃游戏
2963. 统计好分割方案的数目
2584. 分割数组使乘积互质
2585. 寻找最大长度的未覆盖区间
难度2
2587. 跳跃游戏 II
2588. 视频拼接
2589. 灌溉花园的最少水龙头数目


28.访问完所有房间的第一天

题目链接:1997. 访问完所有房间的第一天

你需要访问 n 个房间,房间从 0 到 n - 1 编号。同时,每一天都有一个日期编号,从 0 开始,依天数递增。你每天都会访问一个房间。

最开始的第 0 天,你访问 0 号房间。给你一个长度为 n 且 下标从 0 开始 的数组 nextVisit 。在接下来的几天中,你访问房间的 次序 将根据下面的 规则 决定:

  • 假设某一天,你访问 i 号房间。
  • 如果算上本次访问,访问 i 号房间的次数为 奇数 ,那么 第二天 需要访问 nextVisit[i] 所指定的房间,其中 0 <= nextVisit[i] <= i 。
  • 如果算上本次访问,访问 i 号房间的次数为 偶数 ,那么 第二天 需要访问 (i + 1) mod n 号房间。

请返回你访问完所有房间的第一天的日期编号。题目数据保证总是存在这样的一天。由于答案可能很大,返回对 109+ 7 取余后的结果。

示例 1:

输入:nextVisit = [0,0]

输出:2

解释:

  • 第 0 天,你访问房间 0 。访问 0 号房间的总次数为 1 ,次数为奇数。

    下一天你需要访问房间的编号是 nextVisit[0] = 0

  • 第 1 天,你访问房间 0 。访问 0 号房间的总次数为 2 ,次数为偶数。

    下一天你需要访问房间的编号是 (0 + 1) mod 2 = 1

  • 第 2 天,你访问房间 1 。这是你第一次完成访问所有房间的那天。

示例 2:

输入:nextVisit = [0,0,2]

输出:6

解释:

你每天访问房间的次序是 [0,0,1,0,0,1,2,…] 。

第 6 天是你访问完所有房间的第一天。

示例 3:

输入:nextVisit = [0,1,2,0]

输出:6

解释:

你每天访问房间的次序是 [0,0,1,1,2,2,3,…] 。

第 6 天是你访问完所有房间的第一天。

提示:

n == nextVisit.length

2 <= n <= 105

0 <= nextVisit[i] <= i

题解:
方法:前缀和优化 DP
        利用前缀和数组 s[] 存储到达每个房间时已经停留的总天数。通过动态规划的方式,从第一个房间开始遍历到倒数第二个房间,计算到达下一个房间所需的天数,并将结果存储在前缀和数组中。最终返回到达最后一个房间所需的天数。

class Solution {
    public int firstDayBeenInAllRooms(int[] nextVisit) {
        final long MOD = 1_000_000_007; // 取模的值
        int n = nextVisit.length; // 房间数量
        long[] s = new long[n]; // 用于存储前缀和的数组

        // 循环计算前缀和
        for (int i = 0; i < n - 1; i++) {
            int j = nextVisit[i]; // 下一个要访问的房间
            s[i + 1] = (s[i] * 2 - s[j] + 2 + MOD) % MOD; // 计算当前房间所需的天数并取模,避免负数
            // s[i] 表示到达房间 i 时已经停留的总天数
            // s[i] * 2 表示离开房间 i 时已经停留的总天数(因为要走一趟再回来)
            // s[j] 表示到达下一个房间 j 时已经停留的总天数
            // (s[i] * 2 - s[j] + 2) 表示到达下一个房间需要的天数
            // 加 2 是因为无论如何都需要一天时间移动到下一个房间
            // 取模避免结果溢出
        }

        return (int) s[n - 1]; // 返回到达最后一个房间所需的天数
    }
}

29.元素和最小的山形三元组 I

题目链接:2908. 元素和最小的山形三元组 I

给你一个下标从 0 开始的整数数组 nums 。

如果下标三元组 (i, j, k) 满足下述全部条件,则认为它是一个 山形三元组 :

  • i < j < k
  • nums[i] < nums[j] 且 nums[k] < nums[j]

请你找出 nums 中 元素和最小 的山形三元组,并返回其 元素和 。如果不存在满足条件的三元组,返回 -1 。

示例 1:

输入:nums = [8,6,1,5,3]

输出:9

解释:三元组 (2, 3, 4) 是一个元素和等于 9 的山形三元组,因为:

  • 2 < 3 < 4

  • nums[2] < nums[3] 且 nums[4] < nums[3]

这个三元组的元素和等于 nums[2] + nums[3] + nums[4] = 9 。可以证明不存在元素和小于 9 的山形三元组。

示例 2:

输入:nums = [5,4,8,7,10,2]

输出:13

解释:三元组 (1, 3, 5) 是一个元素和等于 13 的山形三元组,因为:

  • 1 < 3 < 5

  • nums[1] < nums[3] 且 nums[5] < nums[3]

这个三元组的元素和等于 nums[1] + nums[3] + nums[5] = 13 。可以证明不存在元素和小于 13 的山形三元组。

示例 3:

输入:nums = [6,5,4,3,4,5]

输出:-1

解释:可以证明 nums 中不存在山形三元组。

题解:
方法:O(n) 前后缀分解
        首先从右往左计算数组的后缀最小值,然后从左往右遍历数组,寻找满足一定条件的山形结构。在遍历过程中,维护前缀的最小值,并判断当前元素是否构成山形结构,如果是,则更新答案。最后返回答案,若无满足条件的山形结构,则返回-1。

class Solution {
    public int minimumSum(int[] nums) {
        int n = nums.length;
        int[] suf = new int[n]; // 后缀最小值
        suf[n - 1] = nums[n - 1];
        
        // 计算后缀最小值
        for (int i = n - 2; i > 1; i--) {
            suf[i] = Math.min(suf[i + 1], nums[i]);
        }

        int ans = Integer.MAX_VALUE; // 初始答案为整型最大值
        int pre = nums[0]; // 前缀最小值

        // 寻找山形结构
        for (int j = 1; j < n - 1; j++) {
            if (pre < nums[j] && nums[j] > suf[j + 1]) { // 如果当前元素构成山形
                ans = Math.min(ans, pre + nums[j] + suf[j + 1]); // 更新答案
            }
            pre = Math.min(pre, nums[j]); // 更新前缀最小值
        }
        
        return ans == Integer.MAX_VALUE ? -1 : ans; // 返回最终答案,若无满足条件的山形结构则返回-1
    }
}

30.需要添加的硬币的最小数量

题目链接:2952. 需要添加的硬币的最小数量

给你一个下标从 0 开始的整数数组 coins,表示可用的硬币的面值,以及一个整数 target 。

如果存在某个 coins 的子序列总和为 x,那么整数 x 就是一个 可取得的金额 。

返回需要添加到数组中的 任意面值 硬币的 最小数量 ,使范围 [1, target] 内的每个整数都属于 可取得的金额 。

数组的 子序列 是通过删除原始数组的一些(可能不删除)元素而形成的新的 非空 数组,删除过程不会改变剩余元素的相对位置。

示例 1:

输入:coins = [1,4,10], target = 19

输出:2

解释:需要添加面值为 2 和 8 的硬币各一枚,得到硬币数组 [1,2,4,8,10] 。

可以证明从 1 到 19 的所有整数都可由数组中的硬币组合得到,且需要添加到数组中的硬币数目最小为 2 。

示例 2:

输入:coins = [1,4,10,5,7,19], target = 19

输出:1

解释:只需要添加一枚面值为 2 的硬币,得到硬币数组 [1,2,4,5,7,10,19] 。

可以证明从 1 到 19 的所有整数都可由数组中的硬币组合得到,且需要添加到数组中的硬币数目最小为 1 。

示例 3:

输入:coins = [1,1,1], target = 20

输出:3

解释:

需要添加面值为 4 、8 和 16 的硬币各一枚,得到硬币数组 [1,1,1,4,8,16] 。

可以证明从 1 到 20 的所有整数都可由数组中的硬币组合得到,且需要添加到数组中的硬币数目最小为 3 。

提示:

1 <= target <= 105

1 <= coins.length <= 105

1 <= coins[i] <= target

题解:
方法:贪心
        归纳法

        首先对硬币面额进行排序,然后从能够表示的最小金额 1 开始,逐步增加能够表示的金额范围,直到达到目标金额为止。在每次循环中,尽可能地使用已有的硬币,如果当前没有合适的硬币可用,则必须添加当前能够表示的金额,然后将能够表示的金额范围扩大为原来的两倍。最终返回所添加的硬币数量,即为最少需要添加的硬币数量。

class Solution {
    public int minimumAddedCoins(int[] coins, int target) {
        Arrays.sort(coins); // 对硬币面额进行排序

        int ans = 0; // 记录添加的硬币数量
        int s = 1; // 当前能够表示的金额范围
        int i = 0; // 当前遍历到的硬币面额的索引

        // 当能够表示的金额范围小于或等于目标金额时,继续循环
        while (s <= target) {
            // 如果还有硬币可用并且当前硬币的面额不超过能够表示的金额范围
            if (i < coins.length && coins[i] <= s) {
                s += coins[i++]; // 使用当前硬币,更新能够表示的金额范围
            } else {
                s *= 2; // 如果没有合适的硬币可用,则必须添加 s 这个金额
                ans++; // 记录添加的硬币数量
            }
        }

        return ans; // 返回添加的硬币数量
    }
}

31. 验证二叉树的前序序列化

题目链接:331. 验证二叉树的前序序列化

序列化二叉树的一种方法是使用 前序遍历 。当我们遇到一个非空节点时,我们可以记录下这个节点的值。如果它是一个空节点,我们可以使用一个标记值记录,例如 #。

在这里插入图片描述

例如,上面的二叉树可以被序列化为字符串 “9,3,4,#,#,1,#,#,2,#,6,#,#”,其中 # 代表一个空节点。

给定一串以逗号分隔的序列,验证它是否是正确的二叉树的前序序列化。编写一个在不重构树的条件下的可行算法。

保证 每个以逗号分隔的字符或为一个整数或为一个表示 null 指针的 ‘#’ 。

你可以认为输入格式总是有效的

例如它永远不会包含两个连续的逗号,比如 “1,3” 。
注意:不允许重建树。

示例 1:

输入: preorder = “9,3,4,#,#,1,#,#,2,#,6,#,#”

输出: true

示例 2:

输入: preorder = “1,#”

输出: false

示例 3:

输入: preorder = “9,#,#,1”

输出: false

提示:

1 <= preorder.length <= 10……4^

preorder 由以逗号 “,” 分隔的 [0,100] 范围内的整数和 “#” 组成

题解:
方法:
        

class Solution {
    public boolean isValidSerialization(String preorder) {
        int n = preorder.length();
        int i = 0;
        Deque<Integer> stack = new LinkedList<Integer>();
        stack.push(1);
        while (i < n) {
            if (stack.isEmpty()) {
                return false;
            }
            if (preorder.charAt(i) == ',') {
                i++;
            } else if (preorder.charAt(i) == '#'){
                int top = stack.pop() - 1;
                if (top > 0) {
                    stack.push(top);
                }
                i++;
            } else {
                // 读一个数字
                while (i < n && preorder.charAt(i) != ',') {
                    i++;
                }
                int top = stack.pop() - 1;
                if (top > 0) {
                    stack.push(top);
                }
                stack.push(2);
            }
        }
        return stack.isEmpty();
    }
}

在这里插入图片描述

下接:【题解】—— LeetCode一周小结14


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1560362.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【产品经理】全面解读“数字孪生”

理解数字孪生 随着互联网技术的深入发展&#xff0c;数字孪生被越来越多地提及&#xff0c;那么数字孪生到底是什么&#xff1f;数字孪生&#xff0c;翻译自英文“Digital Twin”&#xff0c;最早在2002年&#xff0c;被从事产品生命周期管理PLM的Michael Grieves教授&#xf…

护眼台灯哪个牌子好?性价比高的护眼台灯推荐

现在生活节奏越来越快&#xff0c;夜间学习、工作已经成为了很多学生党、办公族不可避免的一件事&#xff0c;很多人在劣质的光源下眼睛会出现各种问题。尤其是桌前的那一盏台灯&#xff0c;很多人认为台灯亮度只要够亮就不会伤眼了。 其实不然&#xff0c;要知道光线中的成分…

vant checkbox 复选框 样式改写

修改前 修改后 基于 vant&#xff1a; 4.8.3 unocss: 0.53.4 <van-checkbox-group v-model"query.zczb" shape"square" class"text-16 w-100% flex flex-wrap"><template v-for"item in registerCapitalOption"><v…

AI绘画教程:Midjourney使用方法与技巧从入门到精通

文章目录 一、《AI绘画教程&#xff1a;Midjourney使用方法与技巧从入门到精通》二、内容介绍三、作者介绍&#x1f324;️粉丝福利 一、《AI绘画教程&#xff1a;Midjourney使用方法与技巧从入门到精通》 一本书读懂Midjourney绘画&#xff0c;让创意更简单&#xff0c;让设计…

WPF上使用MaterialDesign框架---下载与配置

一、介绍&#xff1a; Material Design语言的一些重要功能包括 系统字体Roboto的升级版本 &#xff0c;同时颜色更鲜艳&#xff0c;动画效果更突出。杜拉特还简要谈到了新框架的一些变化。谷歌的想法是让谷歌平台上的开发者掌握这个新框架&#xff0c;从而让所有应用就有统一的…

二.音视频编辑-媒体组合-播放

引言 当涉及到音视频编辑时&#xff0c;媒体资源的提取和组合是至关重要的环节。在iOS平台上&#xff0c;AVFoundation框架提供了丰富而强大的功能&#xff0c;使得媒体资源的操作变得轻松而高效。从原始的媒体中提取片段&#xff0c;然后将它们巧妙地组合成一个完整的作品&am…

Adaboost集成学习 | Matlab实现基于SVM-Adaboost支持向量机结合Adaboost集成学习时间序列预测(股票价格预测)

目录 效果一览基本介绍模型设计程序设计参考资料效果一览 基本介绍 Adaboost集成学习 | 基于SVM-Adaboost支持向量机结合Adaboost集成学习时间序列预测(股票价格预测)基于SVM(支持向量机)和AdaBoost集成学习的时间序列预测(如股票价格预测)是一种结合了两种强大机器学习算…

2_2.Linux中的远程登录服务

# 一.Openssh的功能 # 1.sshd服务的用途# #作用&#xff1a;可以实现通过网络在远程主机中开启安全shell的操作 Secure SHell >ssh ##客户端 Secure SHell daemon >sshd ##服务端 2.安装包# openssh-server 3.主配置文件# /etc/ssh/sshd_conf 4.…

浏览器工作原理与实践--编译器和解释器:V8是如何执行一段JavaScript代码的

前面我们已经花了很多篇幅来介绍JavaScript是如何工作的&#xff0c;了解这些内容能帮助你从底层理解JavaScript的工作机制&#xff0c;从而能帮助你更好地理解和应用JavaScript。 今天这篇文章我们就继续“向下”分析&#xff0c;站在JavaScript引擎V8的视角&#xff0c;来分析…

ROS2从入门到精通1-2:详解ROS2服务通信机制与自定义服务

目录 0 专栏介绍1 服务通信模型2 服务模型实现(C)3 服务模型实现(Python)4 自定义服务5 话题、服务通信的异同 0 专栏介绍 本专栏旨在通过对ROS2的系统学习&#xff0c;掌握ROS2底层基本分布式原理&#xff0c;并具有机器人建模和应用ROS2进行实际项目的开发和调试的工程能力。…

蓝桥备赛——贪心

题干 AC Code n, w = map(int, input().split()) # n种类, w核载重 a = [] # [[weight1, value1], [weight2, value2], ...] for _ in range(n):a.append(list(map(int, input().split()))) a.sort(key=lambda x: x[1] / x[0], reverse=True)maxVal = 0for i in a:if i[0…

可视化图表:K线图,快速搞清价格波动。

2023-08-21 21:20贝格前端工场 Hi&#xff0c;我是贝格前端工场的老司机&#xff0c;本文分享可视化图表设计的K线图设计&#xff0c;欢迎老铁持续关注我们。 一、K线图的含义 K线图&#xff08;K Line Chart&#xff09;是一种常用于股票、期货等金融市场的可视化图表&…

【详细讲解WebView的使用与后退键处理】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

前端对数据进行分组和计数处理

js对数组数据的处理&#xff0c;添加属性&#xff0c;合并表格数据。 let data[{id:1,group_id:111},{id:2,group_id:111},{id:3,group_id:111},{id:4,group_id:222},{id:5,group_id:222} ]let tempDatadata; tempDatatempData.reduce((arr,item)>{let findarr.find(i>i…

webpack搭建开发环境

webpack搭建开发环境 一.webpack开发模式二.webpack打包模式三.webpack打包模式应用四.Webpack 前端注入环境变量五.Webpack 开发环境调错 source map六. Webpack 设置解析别名路径七.优化-CDN的使用八.多页面打包九.优化-分割公共代码一.webpack开发模式 作用:启动 Web 服务…

原生js实现循环滚动效果

原生js实现如下图循环滚动效果 核心代码 <div class"scroll"><div class"blist" id"scrollContainer"><div class"bitem"></div>......<div class"bitem"></div></div> </di…

Web框架开发-Form组件和ajax实现注册

一、注册相关的知识点 1、Form组件 我们一般写Form的时候都是把它写在views视图里面,那么他和我们的视图函数也不影响,我们可以吧它单另拿出来,在应用下面建一个forms.py的文件来存放 2、局部钩子函数 1 2 3 4 5 6 7 # 局部钩子函数 def clean_username(self): userna…

Servlet Response的常用方法 缓存和乱码处理

前言 Servlet Response相关的信息&#xff0c;在service方法中使用的是HttpServletResponse&#xff0c;它继承自ServletResponse&#xff0c;扩展了Http协议相关的内容&#xff0c;下面简单记录一下它的基本用法。 一、response组成内容 以下是一个常见response响应的内容&…

【二叉树】Leetcode 114. 二叉树展开为链表【中等】

二叉树展开为链表 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同…

Linux(CentOS7)配置系统服务以及开机自启动

目录 前言 两种方式 /etc/systemd/system/ 进入 /etc/systemd/system/ 文件夹 创建 nginx.service 文件 重新加载 systemd 配置文件 ​编辑 配置开机自启 /etc/init.d/ 进入 /etc/init.d/ 文件夹 创建 mysql 文件 编写脚本内容 添加/删除系统服务 配置开机自启 …