LeetCode2221~2240题目解析

LeetCode 2221-2240. 题解合集

文章目录


2221. Find Triangular Sum of an Array(数组的三角和)

题目描述

给你一个下标从 0 开始的整数数组 nums,其中 nums[i] 是 0 到 9 之间(两者都包含)的一个数字。nums 的三角和 是执行以下操作以后最后剩下元素的值:nums 初始包含 n 个元素,如果 n == 1 终止操作;否则创建一个新的长度为 n - 1 的整数数组 newNums,对于满足 0 <= i < n - 1 的下标 i,newNums[i] = (nums[i] + nums[i+1]) % 10。用 newNums 替换 nums 并重复,直到只剩一个元素。返回 nums 的三角和。

示例 1:

复制代码
输入:nums = [1,2,3,4,5]
输出:8
解释:第一层 $[3,5,7,9]$ → 第二层 $[8,2,6]$ → 第三层 $[0,8]$ → 第四层 $[8]$,最终为 8。

示例 2:

复制代码
输入:nums = [5]
输出:5
解释:只有一个元素,三角和就是它本身。

约束:

  • 1 <= nums.length <= 1000

  • 0 <= nums[i] <= 9

解题思路

核心思想:按题意模拟。每一层用相邻两个元素之和的个位生成新数组,直到只剩一个元素。

步骤:

  1. 在 nums 上原地迭代:nums[i] = (nums[i] + nums[i+1]) % 10。
  2. 每层结束把有效长度 n 减 1。
  3. 直到 n == 1,返回 nums[0]。

复杂度说明:每层 O(n),共 n 层,总 O(n²);原地修改,额外空间 O(1)。

代码实现

java 复制代码
/**
 * 2221. Find Triangular Sum of an Array 数组的三角和
 *
 * 给你一个下标从 0 开始的整数数组 nums,其中 nums[i] 是 0 到 9 之间(两者都包含)的一个数字。
 * nums 的三角和是执行以下操作以后最后剩下元素的值:
 *  - nums 初始包含 n 个元素。如果 n == 1,终止操作。否则,创建一个新的长度为 n-1 的数组 newNums。
 *  - 对于满足 0 <= i < n-1 的下标 i,newNums[i] = (nums[i] + nums[i+1]) % 10。
 * 用 newNums 替换 nums,重复上述操作直到只剩 1 个元素。返回 nums 的三角和。
 *
 * 算法思路(模拟):
 * 不断按规则生成下一层数组,直到长度为 1。每层 newNums[i] = (nums[i]+nums[i+1]) % 10。
 *
 * 时间复杂度:O(n²),空间复杂度:O(n)
 */
public class Solution {

    /**
     * 计算数组的三角和
     *
     * @param nums 初始数组(元素 0~9)
     * @return 三角和
     */
    public int triangularSum(int[] nums) {
        int n = nums.length;
        while (n > 1) {
            for (int i = 0; i < n - 1; i++) {
                nums[i] = (nums[i] + nums[i + 1]) % 10; // 原地生成下一层
            }
            n--;
        }
        return nums[0];
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.triangularSum(new int[]{1, 2, 3, 4, 5})); // 预期 8
        System.out.println(s.triangularSum(new int[]{5}));             // 预期 5
    }
}

复杂度分析

  • 解法:模拟(主提交版本)
  • 时间复杂度:O(n²) --- 每层 O(n),共 n 层
  • 空间复杂度:O(1) --- 原地修改,无需新数组(若不开辟则答案为原地;顶层循环亦可按层创建,但原地实现更省)

2222. Number of Ways to Select Buildings(选择建筑的方案数)

题目描述

给你一个下标从 0 开始的二进制字符串 s,它表示一条街沿途的建筑类型:s[i] = '0' 表示办公楼,s[i] = '1' 表示餐厅。作为市政厅的官员,你需要随机选择 3 栋建筑。然而,为了确保多样性,选出来的 3 栋建筑中相邻 的两栋不能是同一类型。请你返回可以选择 3 栋建筑的有效方案数。

示例 1:

复制代码
输入:s = "001101"
输出:6
解释:合法的下标集合为 [0,2,4]、[0,3,4]、[1,2,4]、[1,3,4](对应 "010")与 [2,4,5]、[3,4,5](对应 "101"),共 6 种。

示例 2:

复制代码
输入:s = "11100"
输出:0
解释:没有任何符合题意的选择。

约束:

  • 3 <= s.length <= 10⁵

  • s[i] 为 '0' 或 '1'

解题思路

核心思想 :枚举中间建筑 + 左右侧计数。满足"相邻两栋不同"的 3 建筑子序列只有两种模式:"010" 和 "101"。

步骤:

  1. 先统计全串中 '0' 总数 total0 与 '1' 总数 total1。
  2. 从左到右遍历,维护已扫过的左侧 '0' 个数 left0、'1' 个数 left1。
  3. 对位置 j:
    • 若 s[j]=='0',它作为中间元素时模式为 "101",方案数 = 左侧 '1' 个数 × 右侧 '1' 个数 = left1 * (total1 - left1);
    • 若 s[j]=='1',模式为 "010",方案数 = left0 * (total0 - left0)。
  4. 累加到答案,更新左侧计数。

复杂度:一趟 O(n),O(1) 空间。

代码实现

java 复制代码
/**
 * 2222. Number of Ways to Select Buildings 选择建筑的方案数
 *
 * 给你一个下标从 0 开始的二进制字符串 s,它表示一条街沿途的建筑类型:s[i]='0' 表示办公楼,
 * s[i]='1' 表示餐厅。作为市政厅的官员,你需要随机选择 3 栋建筑。然而,为了确保多样性,
 * 选出来的 3 栋建筑中相邻的两栋不能是同一类型。返回可以选择 3 栋建筑的有效方案数。
 *
 * 算法思路(枚举中间 + 前缀/后缀统计):
 * 满足条件的 3 建筑子序列只有两种模式:"010" 和 "101"。
 * 对每个位置 j 作为中间建筑:
 *  - 若 s[j]=='0':左侧 '1' 个数 × 右侧 '1' 个数 得到 "101" 方案数;
 *  - 若 s[j]=='1':左侧 '0' 个数 × 右侧 '0' 个数 得到 "010" 方案数。
 * 用前缀数组统计左侧 0/1 个数,后缀统计右侧即可(或用两次遍历动态维护左手计数)。
 *
 * 时间复杂度:O(n),空间复杂度:O(1)(两次遍历)
 */
public class Solution {

    /**
     * 选择 3 栋建筑的有效方案数
     *
     * @param s 建筑类型二进制字符串
     * @return 有效方案数
     */
    public long numberOfWays(String s) {
        int n = s.length();
        long total0 = 0, total1 = 0;
        for (int i = 0; i < n; i++) {
            if (s.charAt(i) == '0') total0++;
            else total1++;
        }
        long left0 = 0, left1 = 0;
        long ans = 0;
        for (int j = 0; j < n; j++) {
            char c = s.charAt(j);
            if (c == '0') {
                // 以 0 为中间:左侧全 1 × 右侧全 1
                ans += left1 * (total1 - left1);
                left0++;
            } else {
                // 以 1 为中间:左侧全 0 × 右侧全 0
                ans += left0 * (total0 - left0);
                left1++;
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.numberOfWays("001101")); // 预期 6
        System.out.println(s.numberOfWays("11100"));  // 预期 0
        System.out.println(s.numberOfWays("101"));    // 预期 1
    }
}

复杂度分析

  • 解法:枚举中间 + 左右计数(主提交版本)
  • 时间复杂度:O(n) --- 一次遍历
  • 空间复杂度:O(1) --- 仅计数变量

2223. Sum of Scores of Built Strings(构造字符串的总得分和)

题目描述

你需要从空字符串开始构造 一个长度为 n 的字符串 s,构造的过程为每次给当前字符串前面 添加一个 字符。构造过程中得到的所有字符串编号为 1 到 n,其中长度为 i 的字符串编号为 si。si 的得分 为 si 和 sn 的最长公共前缀 的长度(注意 s == sn)。给你最终的字符串 s,返回每一个 si 的得分之和。

示例 1:

复制代码
输入:s = "babab"
输出:9
解释:s1="b" 得分 1,s2="ab" 得分 0,s3="bab" 得分 3,s4="abab" 得分 0,s5="babab" 得分 5,总和 9。

示例 2:

复制代码
输入:s = "azbazbzaz"
输出:14
解释:s2="az" 得分 2,s6="azbzaz" 得分 3,s9 得分 9,共 14。

约束:

  • 1 <= s.length <= 10⁵

  • s 由小写英文字母组成

解题思路

核心思想 :Z 函数(扩展 KMP)。si 是 s 的长度为 i 的后缀 ,其得分 = 该后缀与整个 s 的最长公共前缀(LCP)长度,这正是 Z 数组的定义。

步骤:

  1. 计算 Z 数组:z[i] = s 与从位置 i 开始的后缀的 LCP 长度(线性时间维护 [l, r] 匹配盒)。
  2. 长度为 len 的后缀对应起始位置 n - len,得分即 z[n-len];整个串自身(len = n)得分为 n。
  3. 答案 = n + sum(z[i])(对所有 i >= 1)。

复杂度:O(n) 时间、O(n) 空间,远优于对每个后缀暴力求 LCP 的 O(n²)。

代码实现

java 复制代码
/**
 * 2223. Sum of Scores of Built Strings 构造字符串的总得分和
 *
 * 你需要从空字符串开始构造一个长度为 n 的字符串 s,构造的过程为每次给当前字符串前面添加一个字符。
 * 构造过程中得到的所有字符串编号为 1 到 n,其中长度为 i 的字符串编号为 si。
 * si 的得分为 si 和 sn 的最长公共前缀的长度(注意 s == sn)。
 * 给你最终的字符串 s,返回每一个 si 的得分之和。
 *
 * 算法思路(Z 函数 / 扩展 KMP):
 * 注意到 si 是 s 的后缀(长度为 i 的后缀)。si 与 sn 的最长公共前缀长度,等价于 Z 算法中
 * 从位置 n-i 开始的后缀与整个串的最长公共前缀长度。
 * 构建 Z 数组:z[i] 表示 s 与其从 i 开始的后缀的最长公共前缀长度。
 * 答案 = sum_{len=1..n} z[n-len](其中 z[n] 视为 n,即原串自身)。
 *
 * 时间复杂度:O(n),空间复杂度:O(n)
 */
public class Solution {

    /**
     * 构造字符串的总得分和
     *
     * @param s 最终字符串
     * @return 所有 si 得分之和
     */
    public long sumScores(String s) {
        int n = s.length();
        int[] z = new int[n];
        // Z 算法:z[i] = s 与 s[i..] 的最长公共前缀长度
        int l = 0, r = 0;
        for (int i = 1; i < n; i++) {
            if (i <= r) {
                z[i] = Math.min(r - i + 1, z[i - l]);
            }
            while (i + z[i] < n && s.charAt(z[i]) == s.charAt(i + z[i])) {
                z[i]++;
            }
            if (i + z[i] - 1 > r) {
                l = i;
                r = i + z[i] - 1;
            }
        }
        long ans = n; // s 自身(长度为 n 的后缀即整个串)贡献 n
        for (int i = 1; i < n; i++) {
            ans += z[i]; // 从位置 i 开始的后缀,与 s 的最长公共前缀长度
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.sumScores("babab"));     // 预期 9
        System.out.println(s.sumScores("azbazbzaz")); // 预期 14
        System.out.println(s.sumScores("a"));         // 预期 1
    }
}

复杂度分析

  • 解法:Z 函数(主提交版本)
  • 时间复杂度:O(n) --- 线性 Z 算法
  • 空间复杂度:O(n) --- Z 数组

2224. Minimum Number of Operations to Convert Time(转化时间需要的最少操作数)

题目描述

给你两个字符串 current 和 correct,表示两个 24 小时制时间 ,格式为 "HH:MM"。在一步操作中,你可以将 current 这个时间增加 1、5、15 或 60 分钟,可以执行任意 次数。返回将 current 转化为 correct 需要的最少操作数。

示例 1:

复制代码
输入:current = "02:30", correct = "04:35"
输出:3
解释:加 60 分钟两次到 "04:30",再加 5 分钟到 "04:35",共 3 步。

示例 2:

复制代码
输入:current = "11:00", correct = "11:01"
输出:1
解释:加 1 分钟即可。

约束:

  • current 和 correct 均为合法的 24 小时制时间 "HH:MM"

  • correct 一定晚于或等于 current

解题思路

核心思想 :贪心。先转成分钟数求差值 diff,然后优先用大的增量(60 → 15 → 5 → 1)凑差值,每种增量使用 diff / 增量 次。

步骤:

  1. toMinute 把 "HH:MM" 转成 HH*60 + MM。
  2. diff = correct - current。
  3. 依次对增量 [60, 15, 5, 1]:ops += diff / inc; diff %= inc。
  4. 返回总操作数。

为什么贪心最优 :增量集合 {1,5,15,60} 中 60 是 15 的倍数、15 是 5 的倍数、5 是 1 的倍数,任意差值都可用大增量整除逼近,且不会因贪心选大而变差(本质是带进制关系的凑数问题)。

代码实现

java 复制代码
/**
 * 2224. Minimum Number of Operations to Convert Time 转化时间需要的最少操作数
 *
 * 给你两个字符串 current 和 correct,表示两个 24 小时制时间(格式 "HH:MM")。
 * 在一步操作中,你可以将 current 增加 1、5、15 或 60 分钟,执行任意次数。
 * 返回将 current 转化为 correct 需要的最少操作数。
 *
 * 算法思路(贪心):
 * 先算出两个时间相差的总分钟数 diff,然后用尽可能大的增量:
 * 先加 60,再加 15,再加 5,最后加 1,统计操作数。
 *
 * 时间复杂度:O(1),空间复杂度:O(1)
 */
public class Solution {

    /**
     * 转化时间需要的最少操作数
     *
     * @param current 当前时间 "HH:MM"
     * @param correct 目标时间 "HH:MM"
     * @return 最少操作数
     */
    public int convertTime(String current, String correct) {
        int cur = toMinute(current);
        int tar = toMinute(correct);
        int diff = tar - cur; // 题意保证 correct >= current(同一天内)
        int ops = 0;
        for (int step : new int[]{60, 15, 5, 1}) {
            ops += diff / step; // 优先用大增量贪心
            diff %= step;
        }
        return ops;
    }

    private int toMinute(String t) {
        int h = Integer.parseInt(t.substring(0, 2));
        int m = Integer.parseInt(t.substring(3, 5));
        return h * 60 + m;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.convertTime("02:30", "04:35")); // 预期 3
        System.out.println(s.convertTime("11:00", "11:01")); // 预期 1
        System.out.println(s.convertTime("00:00", "23:59")); // 预期 32(23*60+59 用 60/15/5/1 贪心)
    }
}

复杂度分析

  • 解法:贪心凑数(主提交版本)
  • 时间复杂度:O(1) --- 固定 4 种增量
  • 空间复杂度:O(1)

2225. Find Players With Zero or One Losses(找出输掉零场或一场比赛的玩家)

题目描述

给你一个整数数组 matches,其中 matches[i] = [winneri, loseri] 表示在一场比赛中 winneri 击败了 loseri。返回一个长度为 2 的列表 answer:answer[0] 是所有没有 输掉任何比赛的玩家列表;answer[1] 是所有恰好输掉一场 比赛的玩家列表。两个列表中的值都应该按递增 顺序返回。只考虑那些参与至少一场比赛的玩家。

示例 1:

复制代码
输入:matches = [[1,3],[2,3],[3,6],[5,6],[5,7],[4,5],[4,8],[4,9],[10,4],[10,9]]
输出:[[1,2,10],[4,5,7,8]]
解释:玩家 1、2、10 没有输过;4、5、7、8 各输一场。

示例 2:

复制代码
输入:matches = [[2,3],[1,3],[5,4],[6,4]]
输出:[[1,2,5,6],[]]
解释:3、4 各输两场,无人恰好输一场。

约束:

  • 1 <= matches.length <= 10⁵

  • matches[i].length == 2

  • 1 <= winneri, loseri <= 10⁵

  • winneri != loseri

  • 所有 matches[i] 互不相同

解题思路

核心思想 :哈希表统计败场数。每场比赛让败者败场 +1,胜者确保记录存在(败场数不变)。

步骤:

  1. 遍历 matches:losses.putIfAbsent(胜者, 0);losses.put(败者, 原值+1)。
  2. 遍历 losses:败场数为 0 的加入 answer[0],为 1 的加入 answer[1]。
  3. 两个列表分别排序后返回(题目要求递增)。

复杂度:O(n log n)(排序),空间 O(n)。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

/**
 * 2225. Find Players With Zero or One Losses 找出输掉零场或一场比赛的玩家
 *
 * 给你一个整数数组 matches,其中 matches[i] = [winneri, loseri] 表示在一场比赛中 winneri 击败了 loseri。
 * 返回一个长度为 2 的列表 answer:
 *  - answer[0] 是所有没有输掉任何比赛的玩家列表。
 *  - answer[1] 是所有恰好输掉一场比赛的玩家列表。
 * 两个列表中的值都应按递增顺序返回。只考虑那些参与至少一场比赛的玩家。
 *
 * 算法思路(统计败场次数 + 排序):
 * 用 Map<Integer,Integer> 统计每个玩家的败场数:胜者败场 +0,败者 +1。
 * 最后收集败场数为 0 和 1 的玩家 ID,排序输出。
 *
 * 时间复杂度:O(n log n),空间复杂度:O(n)
 */
public class Solution {

    /**
     * 找出没有输过和恰好输过一场的玩家
     *
     * @param matches 比赛结果 [胜者, 败者]
     * @return [不败玩家列表, 恰好输一场玩家列表]
     */
    public List<List<Integer>> findWinners(int[][] matches) {
        Map<Integer, Integer> losses = new HashMap<>();
        for (int[] m : matches) {
            // 胜者确保有记录(败场数保持原值)
            losses.putIfAbsent(m[0], 0);
            // 败者败场 +1
            losses.put(m[1], losses.getOrDefault(m[1], 0) + 1);
        }
        List<Integer> zero = new ArrayList<>();
        List<Integer> one = new ArrayList<>();
        for (Map.Entry<Integer, Integer> e : losses.entrySet()) {
            int cnt = e.getValue();
            if (cnt == 0) zero.add(e.getKey());
            else if (cnt == 1) one.add(e.getKey());
        }
        // 要求递增顺序输出
        zero.sort(null);
        one.sort(null);
        return Arrays.asList(zero, one);
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        // 示例 1:预期 [[1,2,10],[4,5,7,8]]
        System.out.println(s.findWinners(new int[][]{{1, 3}, {2, 3}, {3, 6}, {5, 6}, {5, 7},
                {4, 5}, {4, 8}, {4, 9}, {10, 4}, {10, 9}}));
        // 示例 2:预期 [[1,2,5,6],[]]
        System.out.println(s.findWinners(new int[][]{{2, 3}, {1, 3}, {5, 4}, {6, 4}}));
    }
}

复杂度分析

  • 解法:哈希表 + 排序(主提交版本)
  • 时间复杂度:O(n log n) --- 排序两次
  • 空间复杂度:O(n) --- 哈希表

2226. Maximum Candies Allocated to K Children(每个小孩最多能分到多少糖果)

题目描述

给你一个下标从 0 开始的整数数组 candies,每个元素表示大小为 candies[i] 的一堆糖果。你可以将每堆糖果分成任意数量的子堆 ,但无法 再将两堆合并。另给你一个整数 k,你需要将这些糖果分配给 k 个小孩,使每个小孩分到相同 数量的糖果。每个小孩可以拿走至多一堆 糖果,有些糖果可能会不被分配。返回每个小孩可以拿走的最大糖果数目。

示例 1:

复制代码
输入:candies = [5,8,6], k = 3
输出:5
解释:把 8 分成 5、3,把 6 分成 5、1,得到三堆 5 分给 3 个小孩。

示例 2:

复制代码
输入:candies = [2,5], k = 11
输出:0
解释:总共只有 7 颗糖果,无法让 11 个小孩每人至少 1 颗。

约束:

  • 1 <= candies.length <= 10⁵

  • 1 <= candies[i] <= 10⁷

  • 1 <= k <= 10¹²

解题思路

核心思想 :二分答案。设每个小孩分 x 颗,则一堆大小为 v 的糖果最多切成 v / x 份(每份 x 颗),总份数 cnt = sum(v / x);若 cnt >= k 说明 x 可行。x 越大越难满足,具有单调性,二分最大可行值。

步骤:

  1. 若糖果总数 < k,直接返回 0。
  2. 二分上界为单堆最大值,下界为 1。
  3. 对每个候选 mid 计算 cnt = sum(c / mid),cnt >= k 则 lo = mid+1,否则 hi = mid-1。

复杂度:O(n log maxCandies),空间 O(1)。

代码实现

java 复制代码
/**
 * 2226. Maximum Candies Allocated to K Children 每个小孩最多能分到多少糖果
 *
 * 给你一个下标从 0 开始的整数数组 candies,元素表示每堆糖果的大小。你可以将每堆糖果分成任意数量
 * 的子堆,但无法再将两堆合并。另给你一个整数 k,需要将这些糖果分配给 k 个小孩,使每个小孩分到
 * 相同数量的糖果,且每个小孩可以拿走至多一堆糖果,有些糖果可能不被分配。
 * 返回每个小孩可以拿走的**最大**糖果数目。
 *
 * 算法思路(二分答案):
 * 每个小孩分 x 颗糖果:一堆大小为 v 的糖果最多能分成 v / x 份(每份 x 颗),
 * 总共能分出的份数为 sum(v / x)。若该值 >= k,说明 x 可行。
 * x 具有单调性(x 越小越可行),二分查找最大可行 x。
 *
 * 时间复杂度:O(n log maxCandies),空间复杂度:O(1)
 */
public class Solution {

    /**
     * 每个小孩最多能分到的糖果数
     *
     * @param candies 每堆糖果的大小
     * @param k       小孩数量
     * @return 每个小孩能拿到的最大糖果数(无法分配返回 0)
     */
    public int maximumCandies(int[] candies, long k) {
        long sum = 0;
        int max = 0;
        for (int c : candies) {
            sum += c;
            max = Math.max(max, c);
        }
        if (sum < k) {
            return 0; // 总糖果数不足每人 1 颗
        }
        int lo = 1, hi = max, ans = 1;
        while (lo <= hi) {
            int mid = (lo + hi) >>> 1;
            long cnt = 0;
            for (int c : candies) {
                cnt += c / mid;
            }
            if (cnt >= k) { // 能分够 k 份,尝试更大
                ans = mid;
                lo = mid + 1;
            } else {
                hi = mid - 1;
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.maximumCandies(new int[]{5, 8, 6}, 3));  // 预期 5
        System.out.println(s.maximumCandies(new int[]{2, 5}, 11));    // 预期 0
        System.out.println(s.maximumCandies(new int[]{1, 2, 3, 4}, 6)); // 预期 1
    }
}

复杂度分析

  • 解法:二分答案(主提交版本)
  • 时间复杂度:O(n log maxCandies)
  • 空间复杂度:O(1)

2227. Encrypt and Decrypt Strings(加密解密字符串)

题目描述

给你一个字符数组 keys(元素互不相同)和字符串数组 values(每项长度为 2),以及字符串数组 dictionary,包含解密后所有允许的原字符串。设计并实现数据结构 Encrypter:加密时对字符串中的每个字符 c,用 values 中与 c 对应的项替换它(若 keys 中不存在该字符返回空字符串 "");解密时把字符串每相邻 2 个字符划分成子串,每个子串可映射回任意一个 对应的 key 字符(可能存在多个),统计可以由 word2 解密得到且出现在 dictionary 中的字符串数目。

示例 1:

复制代码
输入:["Encrypter", "encrypt", "decrypt"] 参数为 keys=[a,b,c,d], values=[ei,zf,ei,am], dictionary=[abcd, acbd, adbc, badc, dacb, cadb, cbda, abad],encrypt("abcd") 与 decrypt("eizfeiam")
输出:[null, "eizfeiam", 2]
解释:加密为逐字符替换;解密时 "ei" 可映射为 a 或 c 等,得到 abad、cbad、abcd、cbcd,其中 abad 与 abcd 在 dictionary 中,返回 2。

约束:

  • 1 <= keys.length == values.length <= 26

  • values[i].length == 2

  • values[i] 只含小写字母

  • keys 中字符互不相同

  • 1 <= dictionary.length <= 100

  • 1 <= dictionary[i].length <= 100

  • 所有 dictionary[i] 长度相等

  • 1 <= word1.length <= 2000

  • 1 <= word2.length <= 2000

  • 所有 word1[i] 均出现在 keys 中

解题思路

核心思想 :加密确定性 + 解密查表。每个 key 字符加密结果唯一,因此 dictionary 中每个单词的加密结果也是确定的;decrypt(word2) 统计的其实是以 word2 为加密结果且来自 dictionary 的单词个数,预处理一张"加密结果 → 出现次数"的哈希表即可。

步骤:

  1. 构造 char → String 加密映射;encrypt(w) 逐字符替换,遇到不在 keys 的字符返回 ""。
  2. 初始化时把 dictionary 每个词加密,用 Map<String,Integer> 统计每种加密结果出现次数。
  3. decrypt(word2) 直接查表返回次数,O(1)。

为什么正确 :解密过程"value 片段 → 若干 key 字符"的选择组合恰对应所有加密结果为 word2 的原词;反过来统计这些词中属于 dictionary 的数量即为所求,无需真正枚举所有解密串(组合可能爆炸)。

代码实现

java 复制代码
import java.util.HashMap;
import java.util.Map;

/**
 * 2227. Encrypt and Decrypt Strings 加密解密字符串
 *
 * 给你一个字符数组 keys(互不相同)和一个字符串数组 values(每项长度为 2),以及字符串数组 dictionary。
 * 设计并实现一个支持加密及解密字符串的数据结构 Encrypter:
 *  - 加密:对 word1 的每个字符 c,用 values[keys.indexOf(c)] 替换它;若 keys 中不存在该字符,返回 ""。
 *    注意:加密后的字符串不是唯一的(一个字符可能被 map 到相同 value),这里加密特指按 keys/values 一一替换。
 *  - 解密:统计可以由 word2 解密得到且出现在 dictionary 中的字符串数目。
 *    解密时把 word2 每两个字符作为一个 value,可映射回多个 key 字符,可能得到多个解密串。
 *
 * 算法思路:
 * 加密:直接用 char->String 的 Map 逐字符替换。
 * 解密:word2 固定按 2 字符切分,每个片段能映射到多个字符。与其枚举所有可能解密串,
 * 不如逆向思维:预处理 dictionary 中每个单词加密后的字符串,用 Map<String,Integer> 统计每种
 * 加密结果的出现次数。解密时,直接对 word2 查找该加密结果,返回出现次数即可(恰好就是
 * "可由 word2 解密得到且在 dictionary 中"的数量)。
 * 注意需要先把 dictionary 的每个词先加密(加密是确定性的:key→value 唯一)。
 *
 * 时间复杂度:初始化 O(D*L),encrypt O(L),decrypt O(1);空间复杂度:O(D*L)
 */
public class Solution {

    public static void main(String[] args) {
        char[] keys = {'a', 'b', 'c', 'd'};
        String[] values = {"ei", "zf", "ei", "am"};
        String[] dictionary = {"abcd", "acbd", "adbc", "badc", "dacb",
                "cadb", "cbda", "abad"};
        Encrypter encrypter = new Encrypter(keys, values, dictionary);
        System.out.println(encrypter.encrypt("abcd")); // 预期 eizfeiam
        System.out.println(encrypter.decrypt("eizfeiam")); // 预期 2
    }
}

class Encrypter {
    private Map<Character, String> encMap = new HashMap<>();
    private Map<String, Integer> decCnt = new HashMap<>();

    public Encrypter(char[] keys, String[] values, String[] dictionary) {
        for (int i = 0; i < keys.length; i++) {
            encMap.put(keys[i], values[i]);
        }
        // 预处理 dictionary:统计每个单词加密结果的出现次数
        for (String w : dictionary) {
            String e = encrypt(w);
            if (!e.isEmpty()) {
                decCnt.put(e, decCnt.getOrDefault(e, 0) + 1);
            }
        }
    }

    public String encrypt(String word1) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < word1.length(); i++) {
            String v = encMap.get(word1.charAt(i));
            if (v == null) {
                return ""; // keys 中不存在该字符,无法加密
            }
            sb.append(v);
        }
        return sb.toString();
    }

    public int decrypt(String word2) {
        return decCnt.getOrDefault(word2, 0); // word2 恰为某词的加密结果则计入
    }
}

复杂度分析

  • 解法:加密映射 + 解密查表(主提交版本)
  • 时间复杂度 :初始化 O(D×L),encrypt O(L),decrypt O(1)
  • 空间复杂度:O(D×L) --- 两个哈希表

2228. Users With Two Purchases Within Seven Days(7 天内两次购买的用户)

题目类型:数据库(SQL)

题目描述

表: Purchases

复制代码
+---------------+------+
| Column Name   | Type |
+---------------+------+
| purchase_id   | int  |
| user_id       | int  |
| purchase_date | date |
+---------------+------+

purchase_id 是主键(唯一值)。该表包含用户从某个零售商购买的日期的日志。

编写解决方案,获取最多 间隔 7 天进行两次购买的用户的 id(即任意两次购买日期差不超过 7 天,包含同一天)。返回按 user_id 排序的结果表。

示例 1:

复制代码
输入:Purchases 表如上(用户 2 在 03-13 与 03-20 购买,间隔 7 天;用户 7 在同一天购买两次,间隔 0 天;用户 5 仅购买一次)
输出:
+---------+
| user_id |
+---------+
| 2       |
| 7       |
+---------+

约束:

  • purchase_id 为该表主键(唯一值)

  • purchase_date 为 date 类型

  • 返回结果按 user_id 升序

解题思路

核心思想 :窗口函数 LAG 取上一笔购买日期。同一用户按日期排序后,用 DATEDIFF(当前日期 - 上一笔日期) 求相邻两次购买的间隔天数;若存在某次间隔 ≤ 7,该用户符合条件。

步骤:

  1. 对 Purchases 按 user_id 分区、purchase_date 排序,用 LAG(purchase_date, 1) OVER(...) 取上一笔日期。
  2. 计算 d = DATEDIFF(purchase_date, LAG(...))。
  3. 筛出 d <= 7 的行,DISTINCT user_id 并按 user_id 排序输出。

注意 :同一天两次购买间隔为 0,满足 ≤ 7;"至少两次购买"由 LAG 非空的记录保证。

代码实现

sql 复制代码
-- 2228. Users With Two Purchases Within Seven Days 7 天内两次购买的用户
--
-- 表: Purchases
-- +---------------+------+
-- | Column Name   | Type |
-- +---------------+------+
-- | purchase_id   | int  |
-- | user_id       | int  |
-- | purchase_date | date |
-- +---------------+------+
-- purchase_id 包含唯一值。
--
-- 编写解决方案,获取最多间隔 7 天进行两次购买的用户的 id(相邻两次购买日期差 <= 7 天,
-- 包括同一天)。返回按 user_id 排序的结果表。
--
-- 思路:按 user_id 分组并按 purchase_date 排序,用 LAG 取上一笔购买日期,
-- 用 DATEDIFF 求与上一笔的间隔天数;间隔 <= 7 的 user_id 即符合条件,去重后按 user_id 排序。

WITH t AS (
    SELECT
        user_id,
        DATEDIFF(
            purchase_date,
            LAG(purchase_date, 1) OVER (
                PARTITION BY user_id
                ORDER BY purchase_date
            )
        ) AS d
    FROM Purchases
)
SELECT DISTINCT user_id
FROM t
WHERE d <= 7
ORDER BY user_id;

复杂度分析

  • 解法:窗口函数 LAG + DATEDIFF(主提交版本)
  • 时间复杂度:O(P log P) --- 分区排序(P 为行数)
  • 空间复杂度:O§ --- 排序中间结果

2229. Check if an Array Is Consecutive(检查数组是否连贯)

题目描述

给你一个整数数组 nums,如果 nums 是一个连贯数组 则返回 true,否则返回 false。如果数组包含 [x, x + n - 1] 范围内的所有数字(包括 x 和 x + n - 1),则该数组为连贯数组;其中 x 是数组中最小的数,n 是数组的长度。

示例 1:

复制代码
输入:nums = [1,3,4,2]
输出:true
解释:最小值为 1,长度 4,范围 [1,4] 内所有值都出现。

示例 2:

复制代码
输入:nums = [1,3]
输出:false
解释:范围 [1,2] 中缺少 2。

示例 3:

复制代码
输入:nums = [3,5,4]
输出:true
解释:最小值为 3,长度 3,范围 [3,5] 恰好为 3、4、5。

约束:

  • 1 <= nums.length <= 10⁵

  • 0 <= nums[i] <= 10⁵

解题思路

核心思想:排序后检查相邻差是否为 1。连贯数组等价于"无重复且 max - min == n - 1"。

步骤:

  1. 排序(O(n log n))。
  2. 检查任意 nums[i] != nums[i-1] + 1 则返回 false(这一条同时排除了重复元素------重复会导致差值不为 1)。
  3. 全部满足返回 true。

等价写法 :用 Set 去重后,若 max - min == n - 1 && set.size() == n 也为连贯数组。

代码实现

java 复制代码
/**
 * 2229. Check if an Array Is Consecutive 检查数组是否连贯
 *
 * 给你一个整数数组 nums,如果 nums 是一个连贯数组则返回 true,否则返回 false。
 * 如果数组包含 [x, x + n - 1] 范围内的所有数字(包括端点),则该数组为连贯数组;
 * 其中 x 是数组中最小的数,n 是数组的长度。
 *
 * 算法思路:
 * 连贯数组等价于排序后相邻元素差值为 1(且无重复)。
 * 更简单的判定:max - min == n - 1 且去重后元素个数仍为 n。
 * 实现:排序后检查是否严格递增且相邻差为 1;或用 Set 判重 + max-min 判定。
 *
 * 时间复杂度:O(n log n),空间复杂度:O(1)
 */
public class Solution {

    /**
     * 判断数组是否连贯
     *
     * @param nums 整数数组
     * @return 连贯返回 true
     */
    public boolean isConsecutive(int[] nums) {
        java.util.Arrays.sort(nums);
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] != nums[i - 1] + 1) { // 非严格递增或差值不为 1 都不连贯
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.isConsecutive(new int[]{1, 3, 4, 2})); // 预期 true
        System.out.println(s.isConsecutive(new int[]{1, 3}));       // 预期 false
        System.out.println(s.isConsecutive(new int[]{3, 5, 4}));    // 预期 true
        System.out.println(s.isConsecutive(new int[]{1, 1, 2}));    // 预期 false
    }
}

复杂度分析

  • 解法:排序判差(主提交版本)
  • 时间复杂度:O(n log n)
  • 空间复杂度:O(1)(原地排序)
  • 写法二 :Set 去重 + max-min == n-1,O(n)/O(n)

2230. The Users That Are Eligible for Discount(查找可享受优惠的用户)

题目类型:数据库(SQL)

题目描述

表: Purchases

复制代码
+-------------+----------+
| Column Name | Type     |
+-------------+----------+
| user_id     | int      |
| time_stamp  | datetime |
| amount      | int      |
+-------------+----------+

(user_id, time_stamp) 是此表的主键。每一行都包含购买时间、用户 ID 与购买金额。

如果用户在时间间隔 [startDate, endDate] 内购买了至少 minAmount 数量的商品,则有资格享受折扣。两个日期应视为一天的开始(如 endDate = 2022-03-05 视为 2022-03-05 00:00:00)。编写一个解决方案查询符合折扣条件的用户 ID,按 user_id 排序返回。

示例 1:

复制代码
输入:Purchases 表如上,startDate = 2022-03-08, endDate = 2022-03-20, minAmount = 1000
输出:
+---------+
| user_id |
+---------+
| 3       |
+---------+
解释:用户 3 是唯一同时满足时间与金额条件的用户。

约束:

  • (user_id, time_stamp) 是该表主键

  • amount 为 int,time_stamp 为 datetime

  • 结果按 user_id 升序

解题思路

核心思想 :条件过滤去重输出。本题与 2205 几乎相同,但要求返回用户 ID 列表 而非数量,因此实现为存储过程 getUserIDs(startDate, endDate, minAmount)。

步骤:

  1. time_stamp BETWEEN startDate AND endDate 表达闭区间(DATE 自动按当天零点参与比较)。
  2. 叠加 amount >= minAmount 金额条件。
  3. DISTINCT user_id 去重并 ORDER BY user_id 升序输出。

代码实现

sql 复制代码
-- 2230. The Users That Are Eligible for Discount 查找可享受优惠的用户
--
-- Table: Purchases
-- +-------------+----------+
-- | Column Name | Type     |
-- +-------------+----------+
-- | user_id     | int      |
-- | time_stamp  | datetime |
-- | amount      | int      |
-- +-------------+----------+
-- (user_id, time_stamp) 是该表的主键。每一行都包含购买时间和用户 ID 以及购买的金额。
--
-- 如果用户在包含时间间隔 [startDate, endDate] 内购买了至少 minAmount 数量的商品,
-- 则有资格享受折扣。两个日期都应视为一天的开始(如 endDate = 2022-03-05 视为 2022-03-05 00:00:00)。
-- 编写一个解决方案来查询符合折扣条件的用户的 ID,以 user_id 排序返回结果表。
--
-- 思路:与 2205 类似但返回用户 ID 列表。实现为存储过程 getUserIDs(startDate, endDate, minAmount),
-- 过滤 time_stamp BETWEEN 且 amount >= minAmount,DISTINCT + ORDER BY 输出。

CREATE PROCEDURE getUserIDs(startDate DATE, endDate DATE, minAmount INT)
BEGIN
    SELECT DISTINCT user_id
    FROM Purchases
    WHERE amount >= minAmount AND time_stamp BETWEEN startDate AND endDate
    ORDER BY user_id;
END;

复杂度分析

  • 解法:条件过滤 + 去重排序(主提交版本)
  • 时间复杂度:O§ --- 单表扫描
  • 空间复杂度:O(distinct user)

2231. Largest Number After Digit Swaps by Parity(按奇偶性交换后的最大数字)

题目描述

给你一个正整数 num。你可以交换 num 中奇偶性 相同的任意两位数字(即,都是奇数或者都是偶数)。返回交换任意 次之后 num 的最大可能值。

示例 1:

复制代码
输入:num = 1234
输出:3412
解释:交换 3 和 1 得 3214,交换 2 和 4 得 3412,可证为最大。

示例 2:

复制代码
输入:num = 65875
输出:87655
解释:奇数位 5、7、5 与偶数位 6、8 各自内部交换得到最大。

约束:

  • 1 <= num <= 10⁹

解题思路

核心思想:奇偶位分别排序贪心。奇偶数位之间不能互换,因此奇数位数字可任意排列、偶数位数字可任意排列。要让整体最大,应在每个奇数位上放置剩余最大的奇数位数字、每个偶数位放置剩余最大的偶数位数字。

步骤:

  1. 把各位数字按奇偶收集到两个数组。
  2. 各自升序排序,使取"最大"时从尾部弹出。
  3. 重扫原数字各位:奇位取奇数数组剩余最大、偶位取偶数数组剩余最大。

复杂度:O(d log d)(d 为位数),空间 O(d)。

代码实现

java 复制代码
import java.util.PriorityQueue;

/**
 * 2231. Largest Number After Digit Swaps by Parity 按奇偶性交换后的最大数字
 *
 * 给你一个正整数 num。你可以交换 num 中奇偶性相同的任意两位数字(即都是奇数或都是偶数)。
 * 返回交换任意次之后 num 的最大可能值。
 *
 * 算法思路(贪心 + 优先级队列/排序):
 * 把奇偶性不同的数字分开考虑:奇数位的数字之间可以任意排列,偶数位的数字之间也可以任意排列。
 * 要让整体最大,奇数位数字降序填入奇数位置、偶数位数字降序填入偶数位置。
 * 实现:分别收集奇数位和偶数位的数字,排序后从大到小放回各自的奇数/偶数下表位置。
 *
 * 时间复杂度:O(d log d),空间复杂度:O(d)
 */
public class Solution {

    /**
     * 按奇偶性交换后得到最大数字
     *
     * @param num 正整数
     * @return 最大可能值
     */
    public int largestInteger(int num) {
        String s = String.valueOf(num);
        int n = s.length();
        int[] odd = new int[n], even = new int[n]; // 存放奇数位/偶数位数字
        int oc = 0, ec = 0;
        char[] cs = s.toCharArray();
        for (int i = 0; i < n; i++) {
            int d = cs[i] - '0';
            if (d % 2 == 0) even[ec++] = d;
            else odd[oc++] = d;
        }
        // 降序排序,方便从大到小取出
        java.util.Arrays.sort(odd, 0, oc);
        java.util.Arrays.sort(even, 0, ec);
        int oi = oc - 1, ei = ec - 1;
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            int d = cs[i] - '0';
            if (d % 2 == 0) {
                sb.append(even[ei--]); // 取当前偶数位剩余最大
            } else {
                sb.append(odd[oi--]);  // 取当前奇数位剩余最大
            }
        }
        return Integer.parseInt(sb.toString());
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.largestInteger(1234));  // 预期 3412
        System.out.println(s.largestInteger(65875)); // 预期 87655
        System.out.println(s.largestInteger(135));   // 预期 531(全为奇数)
    }
}

复杂度分析

  • 解法:奇偶位分组贪心(主提交版本)
  • 时间复杂度:O(d log d)(d 为位数,≤ 10)
  • 空间复杂度:O(d)

2232. Minimize Result by Adding Parentheses to Expression(向表达式添加括号后的最小结果)

题目描述

给你一个下标从 0 开始的字符串 expression,格式为 "num1+num2",其中 num1 和 num2 表示正整数。请你向 expression 中添加一对括号,使得添加之后 expression 仍然是一个有效的数学表达式,并且计算后可以得到最小 可能值。左括号必须 添加在 '+' 的左侧,右括号必须添加在 '+' 的右侧。返回添加一对括号后形成的表达式,且满足计算得到最小可能值;如果存在多个答案,返回任意一个。

示例 1:

复制代码
输入:expression = "247+38"
输出:"2(47+38)"
解释:2 * (47 + 38) = 170,是最小可能值。

示例 2:

复制代码
输入:expression = "12+34"
输出:"1(2+3)4"
解释:1 * (2 + 3) * 4 = 20。

示例 3:

复制代码
输入:expression = "999+999"
输出:"(999+999)"
解释:999 + 999 = 1998。

约束:

  • 3 <= expression.length <= 10

  • expression 仅由 '0' 到 '9' 与一个 '+' 组成

  • expression 的原始值和添加任一对括号后的值都符合 32 位带符号整数范围

解题思路

核心思想 :枚举括号覆盖范围。括号形式为 A(B+C)D:把加号左侧数字切成 A | B、右侧数字切成 C | D,结果为 A × (B + C) × D(A 或 D 为空串时视为 1)。

步骤:

  1. 定位 '+',得到左右数字串 left、right。
  2. 枚举切分点:左括号在 left 的第 i 位后(A=left[0:i],B=left[i:],B 非空);右括号在 right 的第 j 位后(C=right[0:j] 非空,D=right[j:])。
  3. 对每个切分计算 值 = A × (B + C) × D,记录并更新最小值及对应表达式。
  4. 返回最小表达式。

复杂度:O(n²) 枚举(n ≤ 10),常数级空间。

代码实现

java 复制代码
/**
 * 2232. Minimize Result by Adding Parentheses to Expression 向表达式添加括号后的最小结果
 *
 * 给你一个下标从 0 开始的字符串 expression,格式为 "num1+num2",其中 num1 和 num2 表示正整数。
 * 请你向 expression 中添加一对括号,使得 expression 仍然是一个有效的数学表达式,并且计算后可以
 * 得到最小可能值。左括号必须添加在 '+' 的左侧,右括号必须添加在 '+' 的右侧。
 * 返回添加一对括号后形成的表达式,且满足计算得到最小可能值;如果存在多个答案,返回任意一个。
 *
 * 算法思路(枚举括号位置):
 * 形式为 "a(b+c)d":a 是左括号前的一段数字、b 是左括号后到加号前的数字、c 是加号到右括号前的数字、
 * d 是右括号后的数字。原式 a(b+c)d = a * (b + c) * d。
 * 枚举左括号在加号左侧的位置(把加号前数字切分成 a|b),右括号在加号右侧的位置(把加号后数字切分成 c|d)。
 * 计算每个切分的值,取最小;注意 a、b、c、d 都可能为空串(空串视为 1 或空数字,实际为空时该段不存在)。
 * 注意 a 为空时相当于 a=1、d 为空时相当于 d=1。
 *
 * 时间复杂度:O(n²),空间复杂度:O(1)
 */
public class Solution {

    /**
     * 添加一对括号使表达式结果最小
     *
     * @param expression 形如 "num1+num2" 的表达式
     * @return 添加括号后的表达式
     */
    public String minimizeResult(String expression) {
        int plus = expression.indexOf('+');
        String left = expression.substring(0, plus);
        String right = expression.substring(plus + 1);
        long best = Long.MAX_VALUE;
        String bestExpr = null;
        // 枚举左括号位置:把 left 拆成 a | b(b 非空)
        for (int i = 0; i < left.length(); i++) {
            String a = left.substring(0, i);
            String b = left.substring(i);
            // 枚举右括号位置:把 right 拆成 c | d(c 非空)
            for (int j = 1; j <= right.length(); j++) {
                String c = right.substring(0, j);
                String d = right.substring(j);
                long av = a.isEmpty() ? 1 : Long.parseLong(a);
                long bv = Long.parseLong(b);
                long cv = Long.parseLong(c);
                long dv = d.isEmpty() ? 1 : Long.parseLong(d);
                long val = av * (bv + cv) * dv;
                if (val < best) {
                    best = val;
                    bestExpr = a + "(" + b + "+" + c + ")" + d;
                }
            }
        }
        return bestExpr;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.minimizeResult("247+38"));   // 预期 2(47+38)
        System.out.println(s.minimizeResult("12+34"));    // 预期 1(2+3)4
        System.out.println(s.minimizeResult("999+999"));  // 预期 (999+999)
        System.out.println(s.minimizeResult("1+1"));      // 预期 (1+1) 或 1(1+)1 之类,最小 2
    }
}

复杂度分析

  • 解法:枚举括号位置(主提交版本)
  • 时间复杂度:O(n²) --- 双重切分枚举(n ≤ 10)
  • 空间复杂度:O(1)

2233. Maximum Product After K Increments(K 次增加后的最大乘积)

题目描述

给你一个非负整数数组 nums 和一个整数 k。每次操作,你可以选择 nums 中任一 元素并将它增加 1。请你返回至多 k 次操作后能得到的 nums 的最大乘积 。由于答案可能很大,将答案对 10⁹ + 7 取余后返回。

示例 1:

复制代码
输入:nums = [0,4], k = 5
输出:20
解释:把 0 增加 5 次变成 5,乘积 5×4=20。

示例 2:

复制代码
输入:nums = [6,3,3,2], k = 2
输出:216
解释:把 3 和 2 各加 1 得 [6,4,3,3],乘积 216。

约束:

  • 1 <= nums.length <= 10⁵

  • 0 <= nums[i] <= 10⁶

  • 0 <= k <= 10⁵

解题思路

核心思想 :贪心 + 小顶堆。乘积要最大,每次 +1 应加到当前最小的元素上------给最小值加 1 的相对增幅最大,乘积增长最快。

步骤:

  1. 把 nums 全部放入小顶堆。
  2. 循环 k 次:弹出最小值 +1 后放回。
  3. 遍历堆累乘取模,返回结果。

证明直觉 :固定总增量,a×b 在 |a-b| 越小时乘积越大,因此不断"拉低"最小值(即抬高地板)是使各数趋于均衡且总乘积最大的贪心。

复杂度:O(k log n),空间 O(n)。

代码实现

java 复制代码
import java.util.PriorityQueue;

/**
 * 2233. Maximum Product After K Increments K 次增加后的最大乘积
 *
 * 给你一个非负整数数组 nums 和一个整数 k。每次操作你可以选择 nums 中任一元素并将它增加 1。
 * 返回至多 k 次操作后能得到的 nums 的最大乘积。答案可能很大,对 10^9 + 7 取余后返回。
 *
 * 算法思路(贪心 + 小顶堆):
 * 要使乘积最大,每次 +1 应该加在当前最小的元素上(加在越小的数上,相对增幅越大,乘积增长越快)。
 * 用小顶堆维护所有元素,每次弹出最小值 +1 再放回,重复 k 次,最后累乘取模。
 *
 * 时间复杂度:O(k log n),空间复杂度:O(n)
 */
public class Solution {

    private static final int MOD = 1_000_000_007;

    /**
     * 至多 k 次增加后的最大乘积(取模)
     *
     * @param nums 非负整数数组
     * @param k    最多操作次数
     * @return 最大乘积对 10^9+7 取模的结果
     */
    public int maximumProduct(int[] nums, int k) {
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        for (int x : nums) {
            pq.offer(x);
        }
        while (k-- > 0) {
            int min = pq.poll();
            pq.offer(min + 1); // 最小元素 +1 放回
        }
        long ans = 1;
        while (!pq.isEmpty()) {
            ans = ans * pq.poll() % MOD;
        }
        return (int) ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.maximumProduct(new int[]{0, 4}, 5));       // 预期 20
        System.out.println(s.maximumProduct(new int[]{6, 3, 3, 2}, 2)); // 预期 216
        System.out.println(s.maximumProduct(new int[]{1, 1}, 1));       // 预期 2
    }
}

复杂度分析

  • 解法:贪心 + 小顶堆(主提交版本)
  • 时间复杂度:O(k log n)
  • 空间复杂度:O(n)

2234. Maximum Total Beauty of the Gardens(花园的最大总美丽值)

题目描述

Alice 是 n 个花园的园丁,她想通过种花最大化所有花园的总美丽值。给你数组 flowers(flowers[i] 是第 i 个花园已种的花数,已种的花不能 移走)、newFlowers(额外可以种花的最大数目)以及整数 target、full、partial。如果一个花园有至少 target 朵花则称其完善 ,总美丽值为:完善花园数目 × full + 剩余不完善花园中花的最少数目 × partial(若无不完善花园,该部分为 0)。返回种最多 newFlowers 朵花后能得到的最大总美丽值。

示例 1:

复制代码
输入:flowers = [1,3,1,1], newFlowers = 7, target = 6, full = 12, partial = 1
输出:14
解释:种成 [3,6,2,2],1 个完善花园 × 12 + 最少数目 2 × 1 = 14。

示例 2:

复制代码
输入:flowers = [2,4,5,3], newFlowers = 10, target = 5, full = 2, partial = 6
输出:30
解释:种成 [5,4,5,5],3 个完善 × 2 + 最少 4 × 6 = 30。

约束:

  • 1 <= n <= 10⁵

  • 1 <= flowers[i] <= 10⁵

  • 1 <= newFlowers <= 10¹⁰

  • 1 <= target, full, partial <= 10⁵

解题思路

核心思想 :排序 + 枚举"完善花园数" + 前缀和二分"不完善花园能提到的高度"。最终方案形如:让最大的若干花园补到 target(完善),剩余的用余下花尽可能提升"最低高度"以增大 partial 部分。

步骤:

  1. 排序 flowers;记 pre[i] 为前 i 个之和。
  2. 枚举 i:让下标 [i, n) 共 n-i 个花园完善,所需花数 needFull = target*(n-i) - (pre[n]-pre[i]);若 needFull > newFlowers 则跳过(i 越大越省花,继续变大即可)。
  3. 剩余花 remain 用于提升前 i 个不完善花园的最小值:二分最大高度 h,使得把前 i 个中低于 h 的都补到 h 的花费 ≤ remain(用 upperBound 找第一个 ≥ h 的位置,花费 = h*p - pre[p])。
  4. 该方案的美丽值 = (n-i)*full + h*partial,与全局最大值比较。
  5. 特例:i == 0(全部完善)时美丽值 = n*full。

复杂度:O(n log n),空间 O(n)。

代码实现

java 复制代码
import java.util.Arrays;

/**
 * 2234. Maximum Total Beauty of the Gardens 花园的最大总美丽值
 *
 * Alice 是 n 个花园的园丁。给你数组 flowers,flowers[i] 是第 i 个花园里已经种的花的数目
 * (已种的花不能移走)。newFlowers 表示 Alice 额外可以种花的最大数目。target、full、partial 为整数。
 * 如果一个花园有至少 target 朵花,则该花园是"完善的"。花园的总美丽值为:
 *  - 完善花园数目 × full
 *  - 剩余不完善花园里花的最少数目 × partial(若无不完善花园,该部分为 0)
 * 返回种最多 newFlowers 朵花以后能得到的最大总美丽值。
 *
 * 算法思路(排序 + 枚举完善数量 + 前缀和二分):
 * 先把 flowers 排序。假设要让前 i 个(最小的 i 个)成为不完善花园,其余 n-i 个都补到 target 成为完善花园。
 * 在给定 i 的情况下:补全这 n-i 个完善花园需要 costFull = sum(max(0, target - f[j])) for j>=i。
 * 剩余的新花用于提升前 i 个不完善花园的最小值。为了最大化 partial 部分(最小值 × partial),
 * 需要"I 个不完善花园能一起提到的高度 h"尽量大:总可用花 available 下,最大的 h 满足
 * sum(max(0, h - f[j])) for j<i <= available,用前缀和 + 二分求最大 h。
 * 枚举 i(0..n),取 max(i*full + h*partial)。i=0 时没有不完善花园,partial 部分为 0。
 *
 * 时间复杂度:O(n log n + n log n),空间复杂度:O(n)
 */
public class Solution {

    /**
     * 最大总美丽值
     *
     * @param flowers    已有花数
     * @param newFlowers 可额外种的花数
     * @param target     完善花园标准
     * @param full       每个完善花园得分
     * @param partial    不完善最小花数得分系数(乘最少数目)
     * @return 最大总美丽值
     */
    public long maximumBeauty(int[] flowers, long newFlowers, int target, int full, int partial) {
        Arrays.sort(flowers);
        int n = flowers.length;
        long[] pre = new long[n + 1];
        for (int i = 0; i < n; i++) {
            pre[i + 1] = pre[i] + flowers[i];
        }
        long ans = 0;
        // 枚举"前 i 个花园保持不完善",其余补到 target
        for (int i = 0; i <= n; i++) {
            long needFull = 0; // 把 [i, n) 都补到 target 需要的花数
            if (i < n) {
                needFull = (long) target * (n - i) - (pre[n] - pre[i]);
            }
            if (needFull > newFlowers) {
                continue; // i 越大需要补的完善花园越少,cost 越小,继续枚举更大的 i
            }
            long remain = newFlowers - needFull;
            if (i == 0) {
                // 全部完善:总美丽 = n * full(无 partial 部分)
                ans = Math.max(ans, (long) n * full);
                continue;
            }
            // 用 remain 提升前 i 个(升序的前缀)的最小花数:二分最大高度 h
            long lo = flowers[0], hi = (long) target - 1;
            long best = -1;
            while (lo <= hi) {
                long mid = (lo + hi) >>> 1;
                // 需要把前 i 个中低于 mid 的都提到 mid
                // 找第一个 >= mid 的位置(前 i 个中)
                int p = upperBound(flowers, i, mid);
                long cost = (long) mid * p - pre[p];
                if (cost <= remain) {
                    best = mid;
                    lo = mid + 1;
                } else {
                    hi = mid - 1;
                }
            }
            if (best < 0) best = 0; // 无法提升到任何高度(remain 可能极小)
            ans = Math.max(ans, (long) (n - i) * full + best * partial);
        }
        // 处理边界:所有花园都不完善(i = n)的情况已由循环覆盖(needFull=0)
        return ans;
    }

    /** 在 arr[0..len) 中找第一个 > x > 的下标(返回数量 p 满足 arr[0..p) < = x) */
    private int upperBound(int[] arr, int len, long x) {
        int lo = 0, hi = len;
        while (lo < hi) {
            int mid = (lo + hi) >>> 1;
            if (arr[mid] <= x) lo = mid + 1;
            else hi = mid;
        }
        return lo; // 前 lo 个元素都 <= x
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.maximumBeauty(new int[]{1, 3, 1, 1}, 7, 6, 12, 1));  // 预期 14
        System.out.println(s.maximumBeauty(new int[]{2, 4, 5, 3}, 10, 5, 2, 6));  // 预期 30
        System.out.println(s.maximumBeauty(new int[]{3, 3, 3}, 0, 3, 1, 1));      // 预期 3(全完善)
    }
}

复杂度分析

  • 解法:排序 + 枚举完善数 + 前缀和二分(主提交版本)
  • 时间复杂度:O(n log n) --- 排序 + 二分
  • 空间复杂度:O(n) --- 前缀和

2235. Add Two Integers(两整数相加)

题目描述

给你两个整数 num1 和 num2,返回这两个整数的和。

示例 1:

复制代码
输入:num1 = 12, num2 = 5
输出:17

示例 2:

复制代码
输入:num1 = -10, num2 = 4
输出:-6

约束:

  • -100 <= num1, num2 <= 100

解题思路

核心思想:直接返回两数之和。

步骤 :return num1 + num2;

复杂度:O(1) 时间、O(1) 空间。

代码实现

java 复制代码
/**
 * 2235. Add Two Integers 两整数相加
 *
 * 给你两个整数 num1 和 num2,返回这两个整数的和。
 *
 * 算法思路:直接返回 num1 + num2。
 *
 * 时间复杂度:O(1),空间复杂度:O(1)
 */
public class Solution {

    /**
     * 两整数相加
     *
     * @param num1 整数 1
     * @param num2 整数 2
     * @return 两整数之和
     */
    public int sum(int num1, int num2) {
        return num1 + num2;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.sum(12, -10)); // 预期 2(12 + (-10))
        System.out.println(s.sum(12, 5));   // 预期 17
        System.out.println(s.sum(-10, 4));  // 预期 -6
    }
}

复杂度分析

  • 解法:直接相加(主提交版本)
  • 时间复杂度:O(1)
  • 空间复杂度:O(1)

2236. Root Equals Sum of Children(判断根结点是否等于子结点之和)

题目描述

给你一个二叉树 的根结点 root,该二叉树由恰好 3 个结点组成:根结点、左子结点和右子结点。如果根结点值等于两个子结点值之和,返回 true,否则返回 false。

示例 1:

复制代码
输入:root = [10,4,6]
输出:true
解释:10 = 4 + 6。

示例 2:

复制代码
输入:root = [5,3,1]
输出:false
解释:5 ≠ 3 + 1。

约束:

  • 树中恰好包含 3 个结点

  • 1 <= Node.val <= 100

解题思路

核心思想 :直接比较。题目保证树只有 3 个结点,比较 root.val 与 root.left.val + root.right.val 即可。

步骤 :return root.val == root.left.val + root.right.val;

复杂度:O(1) 时间、O(1) 空间。

代码实现

java 复制代码
/**
 * 2236. Root Equals Sum of Children 判断根结点是否等于子结点之和
 *
 * 给你一个二叉树的根结点 root,该二叉树由恰好 3 个结点组成:根结点、左子结点和右子结点。
 * 如果根结点值等于两个子结点值之和,返回 true,否则返回 false。
 *
 * 算法思路:直接比较 root.val 与左右孩子值之和。
 *
 * 时间复杂度:O(1),空间复杂度:O(1)
 */
public class Solution {

    /**
     * 判断根结点值是否等于两个孩子值之和
     *
     * @param root 恰好 3 个结点的二叉树根
     * @return 满足则返回 true
     */
    public boolean checkTree(TreeNode root) {
        return root.val == root.left.val + root.right.val;
    }

    public static void main(String[] args) {
        // 构造示例 1:[10,4,6],预期 true
        TreeNode root1 = new TreeNode(10, new TreeNode(4), new TreeNode(6));
        System.out.println(new Solution().checkTree(root1));
        // 构造示例 2:[5,3,1],预期 false
        TreeNode root2 = new TreeNode(5, new TreeNode(3), new TreeNode(1));
        System.out.println(new Solution().checkTree(root2));
    }
}

/**
 * 二叉树节点定义(力扣预置类,提交时不要粘贴本类)
 */
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode() {
    }

    TreeNode(int val) {
        this.val = val;
    }

    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

复杂度分析

  • 解法:直接判断(主提交版本)
  • 时间复杂度:O(1)
  • 空间复杂度:O(1)

2237. Count Positions on Street With Required Brightness(计算街道上满足所需亮度的位置数量)

题目描述

给你一个整数 n,一条完全笔直的街道用一条从 0 到 n - 1 的数轴表示。给你一个二维整数数组 lights,每个 lights[i] = [positioni, rangei] 表示在位置 positioni 有一盏路灯,照亮 [max(0, positioni - rangei), min(n - 1, positioni + rangei)](包含边界 )。位置 p 的亮度 定义为点亮位置 p 的路灯数量。给定大小为 n 的数组 requirement(requirement[i] 是位置 i 的最小亮度),返回亮度至少满足 requirement[i] 的位置 i 的数量。

示例 1:

复制代码
输入:n = 5, lights = [[0,1],[2,1],[3,2]], requirement = [0,2,1,4,1]
输出:4
解释:三盏灯分别照亮 [0,1]、[1,3]、[1,4];位置 0、1、2、4 的亮度 ≥ 对应要求,位置 3 亮度 2 < 4 不满足。

示例 2:

复制代码
输入:n = 1, lights = [[0,1]], requirement = [2]
输出:0
解释:位置 0 亮度 1 < 2,没有位置满足。

约束:

  • 1 <= n <= 10⁵

  • 1 <= lights.length <= 10⁵

  • 0 <= positioni < n

  • 0 <= rangei <= 10⁵

  • requirement.length == n

  • 0 <= requirement[i] <= 10⁵

解题思路

核心思想 :差分数组做区间加,前缀和还原亮度后与 requirement 比较。

步骤:

  1. 每盏灯计算照亮区间 [lo, hi],在差分数组上 diff[lo]++、diff[hi+1]--。
  2. 一趟前缀和得到每个位置的亮度 cur。
  3. cur >= requirement[i] 则计数。

复杂度:O(n + m)(m 为灯数),空间 O(n)。

代码实现

java 复制代码
/**
 * 2237. Count Positions on Street With Required Brightness 计算街道上满足所需亮度的位置数量
 *
 * 给你一个整数 n,一条完全笔直的街道用一条从 0 到 n-1 的数轴表示。给你一个二维整数数组 lights,
 * 每个 lights[i] = [positioni, rangei] 表示在位置 positioni 有一盏路灯,它照亮
 * [max(0, positioni - rangei), min(n - 1, positioni + rangei)](包含边界)。
 * 位置 p 的亮度定义为点亮位置 p 的路灯的数量。给定一个大小为 n 的数组 requirement,
 * requirement[i] 是街道上第 i 个位置的最小亮度。返回亮度至少满足 requirement[i] 的位置 i 的数量。
 *
 * 算法思路(差分数组):
 * 每盏路灯在 [L, R] 区间内贡献 +1 亮度,用差分数组 diff 实现区间加,最后前缀和还原每个位置的亮度,
 * 与 requirement 比较计数。
 *
 * 时间复杂度:O(n + m),空间复杂度:O(n)
 */
public class Solution {

    /**
     * 统计亮度满足要求的位置数量
     *
     * @param n           街道长度(位置 0..n-1)
     * @param lights      路灯 [位置, 半径]
     * @param requirement 每个位置的最小亮度
     * @return 满足要求的位置数
     */
    public int meetRequirement(int n, int[][] lights, int[] requirement) {
        int[] diff = new int[n + 1];
        for (int[] l : lights) {
            int p = l[0], r = l[1];
            int lo = Math.max(0, p - r);
            int hi = Math.min(n - 1, p + r);
            diff[lo]++;
            diff[hi + 1]--;
        }
        int ans = 0;
        int cur = 0;
        for (int i = 0; i < n; i++) {
            cur += diff[i]; // 前缀和还原位置 i 的亮度
            if (cur >= requirement[i]) {
                ans++;
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        // 示例 1:预期 4
        System.out.println(s.meetRequirement(5, new int[][]{{0, 1}, {2, 1}, {3, 2}},
                new int[]{0, 2, 1, 4, 1}));
        // 示例 2:预期 0
        System.out.println(s.meetRequirement(1, new int[][]{{0, 1}}, new int[]{2}));
        // 附加:全满足,预期 3
        System.out.println(s.meetRequirement(3, new int[][]{{1, 1}}, new int[]{0, 0, 0}));
    }
}

复杂度分析

  • 解法:差分数组 + 前缀和(主提交版本)
  • 时间复杂度:O(n + m)
  • 空间复杂度:O(n)

2238. Number of Times a Driver Was a Passenger(司机成为乘客的次数)

题目类型:数据库(SQL)

题目描述

表: Rides

复制代码
+--------------+------+
| Column Name  | Type |
+--------------+------+
| ride_id      | int  |
| driver_id    | int  |
| passenger_id | int  |
+--------------+------+

ride_id 是该表的主键。每一行都包含驾驶员的 ID 和在该趟行程中乘车的乘客的 ID。注意 driver_id != passenger_id。

编写解决方案,获取每个司机的 ID 和他们作为乘客的次数,以任意顺序返回结果表。

示例 1:

复制代码
输入:Rides 表如上(driver 7、11;7 曾作为乘客出现 2 次,11 从未作为乘客)
输出:
+-----------+-----+
| driver_id | cnt |
+-----------+-----+
| 7         | 2   |
| 11        | 0   |
+-----------+-----+

约束:

  • ride_id 是该表主键

  • driver_id != passenger_id

  • 结果可以任意顺序返回

解题思路

核心思想 :找出所有司机(driver_id 去重),左连接统计其作为乘客的出现次数。

步骤:

  1. WITH T AS (SELECT DISTINCT driver_id FROM Rides) 得到全部司机。
  2. LEFT JOIN Rides r ON t.driver_id = r.passenger_id:匹配到几行就说明座位乘客几次。
  3. GROUP BY t.driver_id + COUNT(r.passenger_id)(左连接下没当过乘客的司机计数为 0)。

注意 :同一司机不可能在一趟里既是司机又是乘客(driver_id != passenger_id),无需去重计数。

代码实现

sql 复制代码
-- 2238. Number of Times a Driver Was a Passenger 司机成为乘客的次数
--
-- 表: Rides
-- +--------------+------+
-- | Column Name  | Type |
-- +--------------+------+
-- | ride_id      | int  |
-- | driver_id    | int  |
-- | passenger_id | int  |
-- +--------------+------+
-- ride_id 是该表的主键。每一行都包含驾驶员 ID 和在 ride_id 中乘车的乘客 ID。
-- 注意 driver_id != passenger_id。
--
-- 编写解决方案,获取每个司机的 ID 和他们作为乘客的次数,以任意顺序返回结果表。
--
-- 思路:先取所有出现过 driver_id 的司机(去重),再左连接 Rides 统计该司机作为 passenger 出现的次数;
-- 没当过乘客的司机 cnt = 0。
--
-- 注意:同一司机在同一趟行程中不可能既是司机又是乘客(driver_id != passenger_id),
-- 按 passenger_id 计数即可,不需要去重。

WITH T AS (SELECT DISTINCT driver_id FROM Rides)
SELECT t.driver_id, COUNT(r.passenger_id) AS cnt
FROM
    T AS t
    LEFT JOIN Rides AS r ON t.driver_id = r.passenger_id
GROUP BY t.driver_id;

复杂度分析

  • 解法:LEFT JOIN 计数(主提交版本)
  • 时间复杂度:O§ --- 单表连接聚合
  • 空间复杂度:O(司机数)

2239. Find Closest Number to Zero(找到最接近 0 的数字)

题目描述

给你一个长度为 n 的整数数组 nums,请你返回 nums 中最接近 0 的数字。如果有多个答案,请你返回它们中的最大值。

示例 1:

复制代码
输入:nums = [-4,-2,1,4,8]
输出:1
解释:绝对值最小的是 ±1,取 1。

示例 2:

复制代码
输入:nums = [2,-1,1]
输出:1
解释:-1 和 1 同样接近 0,返回较大值 1。

约束:

  • 1 <= n <= 1000

  • -10⁵ <= nums[i] <= 10⁵

解题思路

核心思想:一趟扫描维护最优。比较绝对值:更小则更新;绝对值相等取数值更大者。

步骤:

  1. 初始化 ans = nums[0]。
  2. 遍历:|x| < |ans| 或 (|x| == |ans| && x > ans) 时更新 ans = x。

复杂度:O(n) 时间、O(1) 空间。

代码实现

java 复制代码
/**
 * 2239. Find Closest Number to Zero 找到最接近 0 的数字
 *
 * 给你一个长度为 n 的整数数组 nums,请你返回 nums 中最接近 0 的数字。
 * 如果有多个答案,请你返回它们中的最大值。
 *
 * 算法思路(一次遍历):
 * 维护当前最接近 0 的数:比较绝对值,绝对值相同时取更大值。
 *
 * 时间复杂度:O(n),空间复杂度:O(1)
 */
public class Solution {

    /**
     * 找到最接近 0 的数字
     *
     * @param nums 整数数组
     * @return 最接近 0 的数字(并列时取最大)
     */
    public int findClosestNumber(int[] nums) {
        int ans = nums[0];
        for (int x : nums) {
            int a = Math.abs(x), b = Math.abs(ans);
            if (a < b || (a == b && x > ans)) {
                ans = x; // 更接近 0;或同样接近时取更大值
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.findClosestNumber(new int[]{-4, -2, 1, 4, 8})); // 预期 1
        System.out.println(s.findClosestNumber(new int[]{2, -1, 1}));        // 预期 1
        System.out.println(s.findClosestNumber(new int[]{-3, 3}));           // 预期 3
    }
}

复杂度分析

  • 解法:一趟扫描(主提交版本)
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

2240. Number of Ways to Buy Pens and Pencils(买钢笔和铅笔的方案数)

题目描述

给你一个整数 total,表示你拥有的总钱数。同时给你两个整数 cost1 和 cost2,分别表示一支钢笔和一支铅笔的价格。你可以花费你部分或者全部的钱,去买任意数目的两种笔。请你返回购买钢笔和铅笔的不同方案数目。

示例 1:

复制代码
输入:total = 20, cost1 = 10, cost2 = 5
输出:9
解释:买 0 支钢笔可买 0~4 支铅笔(5 种);买 1 支钢笔可买 0~2 支铅笔(3 种);买 2 支钢笔 0 支铅笔(1 种),共 9 种。

示例 2:

复制代码
输入:total = 5, cost1 = 10, cost2 = 10
输出:1
解释:什么都买不起,只有买 0 支钢笔 + 0 支铅笔这一种方案。

约束:

  • 1 <= total <= 10⁶

  • 1 <= cost1, cost2 <= 10⁶

解题思路

核心思想 :枚举钢笔数量。买 i 支钢笔花费 i * cost1,剩余钱能买 0 ~ (total - i*cost1) / cost2 支铅笔,方案数为商 + 1,对 i = 0.. ⌊total/cost1⌋ 累加。

步骤:

  1. for (long pens = 0; pens * cost1 <= total; pens++)。
  2. 每种钢笔数量下累加 rest / cost2 + 1。

复杂度:O(total / cost1) 时间,O(1) 空间。

代码实现

java 复制代码
/**
 * 2240. Number of Ways to Buy Pens and Pencils 买钢笔和铅笔的方案数
 *
 * 给你一个整数 total 表示你拥有的总钱数,两个整数 cost1 和 cost2 分别表示一支钢笔和一支铅笔的价格。
 * 你可以花费部分或全部的钱,去买任意数目的两种笔。返回购买钢笔和铅笔的不同方案数目。
 *
 * 算法思路(枚举钢笔数):
 * 买 i 支钢笔花费 i * cost1,剩余 total - i*cost1 可以买铅笔:能买 0 ~ (剩余)/cost2 支铅笔,
 * 共 (剩余/cost2 + 1) 种选择。对 i 从 0 到 total/cost1 累加即可。
 *
 * 时间复杂度:O(total / cost1),空间复杂度:O(1)
 */
public class Solution {

    /**
     * 购买钢笔和铅笔的不同方案数
     *
     * @param total 总钱数
     * @param cost1 钢笔单价
     * @param cost2 铅笔单价
     * @return 方案总数
     */
    public long waysToBuyPensPencils(int total, int cost1, int cost2) {
        long ans = 0;
        for (int pens = 0; pens * cost1 <= total; pens++) {
            int rest = total - pens * cost1;
            ans += rest / cost2 + 1; // 铅笔可买 0..rest/cost2 支
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.waysToBuyPensPencils(20, 10, 5));  // 预期 9
        System.out.println(s.waysToBuyPensPencils(5, 10, 10));  // 预期 1
        System.out.println(s.waysToBuyPensPencils(7, 3, 2));    // 预期 8(0钢4种+1钢3种+2钢1种)
    }
}

复杂度分析

  • 解法:枚举钢笔数累加(主提交版本)
  • 时间复杂度:O(total / cost1)
  • 空间复杂度:O(1)

相关推荐
秋饼26 分钟前
Computer Use Agent Java 落地实战:感知-行动循环、执行器与安全围栏生产级实现
java·ai·技术分享·后端开发
Navigator_Z35 分钟前
LeetCode //C - 1317. Convert Integer to the Sum of Two No-Zero Integers
c语言·算法·leetcode
文丑颜不良啊2 小时前
LeetCode2161~2180题目解析
java·leetcode
文丑颜不良啊4 小时前
LeetCode2141~2160题目解析
java·leetcode
LabVIEW开发4 小时前
LabVIEW 连接板:28 个端子怎么排,优先级怎么定
java·算法·labview
天天被压力5 小时前
【A股分钟级高频数据完全手册 #07】北交所历史分钟K线接口:京市Pro版分时批量
java·人工智能·python
天天被压力5 小时前
【A股分钟级高频数据完全手册 #08】股票日、分时数据接口对比:普通分时与Pro版差异实测
java·人工智能·python
特立独行的猫A5 小时前
AI 时代,我为什么推荐用 cj-tauri 开发桌面应用
java·后端·python
金銀銅鐵5 小时前
从一个类名到一张类图:用反射自动生成 PlantUML
java·后端·uml