LeetCode 2221-2240. 题解合集
文章目录
- [LeetCode 2221-2240. 题解合集](#LeetCode 2221-2240. 题解合集)
-
- [2221. Find Triangular Sum of an Array(数组的三角和)](#2221. Find Triangular Sum of an Array(数组的三角和))
- [2222. Number of Ways to Select Buildings(选择建筑的方案数)](#2222. Number of Ways to Select Buildings(选择建筑的方案数))
- [2223. Sum of Scores of Built Strings(构造字符串的总得分和)](#2223. Sum of Scores of Built Strings(构造字符串的总得分和))
- [2224. Minimum Number of Operations to Convert Time(转化时间需要的最少操作数)](#2224. Minimum Number of Operations to Convert Time(转化时间需要的最少操作数))
- [2225. Find Players With Zero or One Losses(找出输掉零场或一场比赛的玩家)](#2225. Find Players With Zero or One Losses(找出输掉零场或一场比赛的玩家))
- [2226. Maximum Candies Allocated to K Children(每个小孩最多能分到多少糖果)](#2226. Maximum Candies Allocated to K Children(每个小孩最多能分到多少糖果))
- [2227. Encrypt and Decrypt Strings(加密解密字符串)](#2227. Encrypt and Decrypt Strings(加密解密字符串))
- [2228. Users With Two Purchases Within Seven Days(7 天内两次购买的用户)](#2228. Users With Two Purchases Within Seven Days(7 天内两次购买的用户))
- [2229. Check if an Array Is Consecutive(检查数组是否连贯)](#2229. Check if an Array Is Consecutive(检查数组是否连贯))
- [2230. The Users That Are Eligible for Discount(查找可享受优惠的用户)](#2230. The Users That Are Eligible for Discount(查找可享受优惠的用户))
- [2231. Largest Number After Digit Swaps by Parity(按奇偶性交换后的最大数字)](#2231. Largest Number After Digit Swaps by Parity(按奇偶性交换后的最大数字))
- [2232. Minimize Result by Adding Parentheses to Expression(向表达式添加括号后的最小结果)](#2232. Minimize Result by Adding Parentheses to Expression(向表达式添加括号后的最小结果))
- [2233. Maximum Product After K Increments(K 次增加后的最大乘积)](#2233. Maximum Product After K Increments(K 次增加后的最大乘积))
- [2234. Maximum Total Beauty of the Gardens(花园的最大总美丽值)](#2234. Maximum Total Beauty of the Gardens(花园的最大总美丽值))
- [2235. Add Two Integers(两整数相加)](#2235. Add Two Integers(两整数相加))
- [2236. Root Equals Sum of Children(判断根结点是否等于子结点之和)](#2236. Root Equals Sum of Children(判断根结点是否等于子结点之和))
- [2237. Count Positions on Street With Required Brightness(计算街道上满足所需亮度的位置数量)](#2237. Count Positions on Street With Required Brightness(计算街道上满足所需亮度的位置数量))
- [2238. Number of Times a Driver Was a Passenger(司机成为乘客的次数)](#2238. Number of Times a Driver Was a Passenger(司机成为乘客的次数))
- [2239. Find Closest Number to Zero(找到最接近 0 的数字)](#2239. Find Closest Number to Zero(找到最接近 0 的数字))
- [2240. Number of Ways to Buy Pens and Pencils(买钢笔和铅笔的方案数)](#2240. Number of Ways to Buy Pens and Pencils(买钢笔和铅笔的方案数))
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
解题思路
核心思想:按题意模拟。每一层用相邻两个元素之和的个位生成新数组,直到只剩一个元素。
步骤:
- 在
nums上原地迭代:nums[i] = (nums[i] + nums[i+1]) % 10。 - 每层结束把有效长度
n减 1。 - 直到
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"。
步骤:
- 先统计全串中
'0'总数total0与'1'总数total1。 - 从左到右遍历,维护已扫过的左侧
'0'个数left0、'1'个数left1。 - 对位置
j:- 若
s[j]=='0',它作为中间元素时模式为"101",方案数 = 左侧'1'个数 × 右侧'1'个数 =left1 * (total1 - left1); - 若
s[j]=='1',模式为"010",方案数 =left0 * (total0 - left0)。
- 若
- 累加到答案,更新左侧计数。
复杂度:一趟 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 数组的定义。
步骤:
- 计算 Z 数组:
z[i]=s与从位置i开始的后缀的 LCP 长度(线性时间维护[l, r]匹配盒)。 - 长度为
len的后缀对应起始位置n - len,得分即z[n-len];整个串自身(len = n)得分为n。 - 答案 =
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 / 增量 次。
步骤:
toMinute把"HH:MM"转成HH*60 + MM。diff = correct - current。- 依次对增量
[60, 15, 5, 1]:ops += diff / inc; diff %= inc。 - 返回总操作数。
为什么贪心最优 :增量集合 {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,胜者确保记录存在(败场数不变)。
步骤:
- 遍历
matches:losses.putIfAbsent(胜者, 0);losses.put(败者, 原值+1)。 - 遍历
losses:败场数为0的加入answer[0],为1的加入answer[1]。 - 两个列表分别排序后返回(题目要求递增)。
复杂度: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 越大越难满足,具有单调性,二分最大可行值。
步骤:
- 若糖果总数
< k,直接返回0。 - 二分上界为单堆最大值,下界为 1。
- 对每个候选
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 的单词个数,预处理一张"加密结果 → 出现次数"的哈希表即可。
步骤:
- 构造
char → String加密映射;encrypt(w)逐字符替换,遇到不在keys的字符返回""。 - 初始化时把
dictionary每个词加密,用Map<String,Integer>统计每种加密结果出现次数。 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),
encryptO(L),decryptO(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,该用户符合条件。
步骤:
- 对
Purchases按user_id分区、purchase_date排序,用LAG(purchase_date, 1) OVER(...)取上一笔日期。 - 计算
d = DATEDIFF(purchase_date, LAG(...))。 - 筛出
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"。
步骤:
- 排序(O(n log n))。
- 检查任意
nums[i] != nums[i-1] + 1则返回false(这一条同时排除了重复元素------重复会导致差值不为 1)。 - 全部满足返回
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)。
步骤:
time_stamp BETWEEN startDate AND endDate表达闭区间(DATE自动按当天零点参与比较)。- 叠加
amount >= minAmount金额条件。 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⁹
解题思路
核心思想:奇偶位分别排序贪心。奇偶数位之间不能互换,因此奇数位数字可任意排列、偶数位数字可任意排列。要让整体最大,应在每个奇数位上放置剩余最大的奇数位数字、每个偶数位放置剩余最大的偶数位数字。
步骤:
- 把各位数字按奇偶收集到两个数组。
- 各自升序排序,使取"最大"时从尾部弹出。
- 重扫原数字各位:奇位取奇数数组剩余最大、偶位取偶数数组剩余最大。
复杂度: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)。
步骤:
- 定位
'+',得到左右数字串left、right。 - 枚举切分点:左括号在
left的第i位后(A=left[0:i],B=left[i:],B非空);右括号在right的第j位后(C=right[0:j]非空,D=right[j:])。 - 对每个切分计算
值 = A × (B + C) × D,记录并更新最小值及对应表达式。 - 返回最小表达式。
复杂度: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 的相对增幅最大,乘积增长最快。
步骤:
- 把
nums全部放入小顶堆。 - 循环
k次:弹出最小值+1后放回。 - 遍历堆累乘取模,返回结果。
证明直觉 :固定总增量,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 部分。
步骤:
- 排序
flowers;记pre[i]为前i个之和。 - 枚举
i:让下标[i, n)共n-i个花园完善,所需花数needFull = target*(n-i) - (pre[n]-pre[i]);若needFull > newFlowers则跳过(i越大越省花,继续变大即可)。 - 剩余花
remain用于提升前i个不完善花园的最小值:二分最大高度h,使得把前i个中低于h的都补到h的花费 ≤remain(用upperBound找第一个 ≥ h 的位置,花费 =h*p - pre[p])。 - 该方案的美丽值 =
(n-i)*full + h*partial,与全局最大值比较。 - 特例:
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 比较。
步骤:
- 每盏灯计算照亮区间
[lo, hi],在差分数组上diff[lo]++、diff[hi+1]--。 - 一趟前缀和得到每个位置的亮度
cur。 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 去重),左连接统计其作为乘客的出现次数。
步骤:
WITH T AS (SELECT DISTINCT driver_id FROM Rides)得到全部司机。LEFT JOIN Rides r ON t.driver_id = r.passenger_id:匹配到几行就说明座位乘客几次。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⁵
解题思路
核心思想:一趟扫描维护最优。比较绝对值:更小则更新;绝对值相等取数值更大者。
步骤:
- 初始化
ans = nums[0]。 - 遍历:
|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⌋ 累加。
步骤:
for (long pens = 0; pens * cost1 <= total; pens++)。- 每种钢笔数量下累加
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)