LeetCode 2441-2460. 题解合集
文章目录
- [LeetCode 2441-2460. 题解合集](#LeetCode 2441-2460. 题解合集)
-
- [2441. Largest Positive Integer That Exists With Its Negative(与对应负数同时存在的最大正整数)](#2441. Largest Positive Integer That Exists With Its Negative(与对应负数同时存在的最大正整数))
- [2442. Count Number of Distinct Integers After Reverse Operations(反转之后不同整数的数目)](#2442. Count Number of Distinct Integers After Reverse Operations(反转之后不同整数的数目))
- [2443. Sum of Number and Its Reverse(反转之后的数字和)](#2443. Sum of Number and Its Reverse(反转之后的数字和))
- [2444. Count Subarrays With Fixed Bounds(统计定界子数组的数目)](#2444. Count Subarrays With Fixed Bounds(统计定界子数组的数目))
- [2445. Number of Nodes With Value One(值为 1 的节点数)](#2445. Number of Nodes With Value One(值为 1 的节点数))
- [2446. Determine if Two Events Have Conflict(判断两个事件是否存在冲突)](#2446. Determine if Two Events Have Conflict(判断两个事件是否存在冲突))
- [2447. Number of Subarrays With GCD Equal to K(最大公因数等于 K 的子数组数目)](#2447. Number of Subarrays With GCD Equal to K(最大公因数等于 K 的子数组数目))
- [2448. Minimum Cost to Make Array Equal(使数组相等的最小开销)](#2448. Minimum Cost to Make Array Equal(使数组相等的最小开销))
- [2449. Minimum Number of Operations to Make Arrays Similar(使数组相似的最少操作次数)](#2449. Minimum Number of Operations to Make Arrays Similar(使数组相似的最少操作次数))
- [2450. Number of Distinct Binary Strings After Applying Operations(应用操作后不同二进制字符串的数量)](#2450. Number of Distinct Binary Strings After Applying Operations(应用操作后不同二进制字符串的数量))
- [2451. Odd String Difference(差值数组不同的字符串)](#2451. Odd String Difference(差值数组不同的字符串))
- [2452. Words Within Two Edits of Dictionary(距离字典两次编辑以内的单词)](#2452. Words Within Two Edits of Dictionary(距离字典两次编辑以内的单词))
- [2453. Destroy Sequential Targets(摧毁一系列目标)](#2453. Destroy Sequential Targets(摧毁一系列目标))
- [2454. Next Greater Element IV(下一个更大元素 IV)](#2454. Next Greater Element IV(下一个更大元素 IV))
- [2455. Average Value of Even Numbers That Are Divisible by Three(可被三整除的偶数的平均值)](#2455. Average Value of Even Numbers That Are Divisible by Three(可被三整除的偶数的平均值))
- [2456. Most Popular Video Creator(最流行的视频创作者)](#2456. Most Popular Video Creator(最流行的视频创作者))
- [2457. Minimum Addition to Make Integer Beautiful(美丽整数的最小增量)](#2457. Minimum Addition to Make Integer Beautiful(美丽整数的最小增量))
- [2458. Height of Binary Tree After Subtree Removal Queries(移除子树后的二叉树高度)](#2458. Height of Binary Tree After Subtree Removal Queries(移除子树后的二叉树高度))
- [2459. Sort Array by Moving Items to Empty Space(通过移动项目到空白区域来排序数组)](#2459. Sort Array by Moving Items to Empty Space(通过移动项目到空白区域来排序数组))
- [2460. Apply Operations to an Array(对数组执行操作)](#2460. Apply Operations to an Array(对数组执行操作))
2441. Largest Positive Integer That Exists With Its Negative(与对应负数同时存在的最大正整数)
题目描述
给你一个不包含任何零的整数数组 nums,找出自身与对应的负数都在数组中存在的最大正整数 k。
若不存在返回 -1。
示例 1
`nums = [-1,2,-3,3]` → `3`。
示例 2
`nums = [-1,10,6,7,-7,1]` → `7`。
示例 3
`nums = [-10,8,6,7,-2,-3]` → `-1`。
提示:
1 <= nums.length <= 1000,-1000 <= nums[i] <= 1000,nums[i] != 0
解题思路
解法一:哈希集合(主提交版本)
- 把所有数放入 Set;遍历正数,若
-x也在集合中则更新答案。
解法二:标记数组(对照) - 值域只有 2001,用 boolean 数组标记正负出现,再扫描一遍。
复杂度: - 时间复杂度:O(n)。
- 空间复杂度:O(n) 或 O(2001)。
代码实现
java
import java.util.HashSet;
import java.util.Set;
public class Solution {
// 解法一:哈希集合(主提交版本)
public int findMaxK(int[] nums) {
Set<Integer> set = new HashSet<>();
int ans = -1;
for (int x : nums) {
set.add(x);
}
for (int x : set) {
if (x > 0 && set.contains(-x)) {
ans = Math.max(ans, x);
}
}
return ans;
}
// 解法二:标记数组(对照)
public int findMaxK2(int[] nums) {
boolean[] pos = new boolean[1001];
boolean[] neg = new boolean[1001];
for (int x : nums) {
if (x > 0) {
pos[x] = true;
} else {
neg[-x] = true;
}
}
for (int k = 1000; k >= 1; k--) {
if (pos[k] && neg[k]) {
return k;
}
}
return -1;
}
public static void main(String[] args) {
Solution sol = new Solution();
check(sol.findMaxK(new int[] {-1, 2, -3, 3}) == 3, "2441 ex1");
check(sol.findMaxK2(new int[] {-1, 2, -3, 3}) == 3, "2441 ex12");
check(sol.findMaxK(new int[] {-1, 10, 6, 7, -7, 1}) == 7, "2441 ex2");
check(sol.findMaxK(new int[] {-10, 8, 6, 7, -2, -3}) == -1, "2441 ex3");
// 随机对拍
java.util.Random rnd = new java.util.Random(109);
for (int t = 0; t < 500; t++) {
int n = 1 + rnd.nextInt(30);
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = rnd.nextInt(40) - 20;
if (a[i] == 0) {
a[i] = 1;
}
}
int x = sol.findMaxK(a), y = sol.findMaxK2(a);
if (x != y) {
throw new AssertionError("2441 mismatch t=" + t + " x=" + x + " y=" + y);
}
}
System.out.println("2441 全部 PASS");
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(n)。
- 空间复杂度:O(n) 或 O(2001)。
2442. Count Number of Distinct Integers After Reverse Operations(反转之后不同整数的数目)
题目描述
给你一个由正 整数组成的数组 nums。必须取出数组中的每个整数,反转其中每个数位(去掉前导零),
并将反转后得到的数字添加到数组末尾。返回结果数组中不同整数的数目。
示例 1
`nums = [1,13,10,12,31]` → `6`(反转后加入 1、31、1、21、13,去重后 {1,10,12,13,21,31})。
示例 2
`nums = [2,2,2]` → `1`。
提示:
1 <= nums.length <= 10^5,1 <= nums[i] <= 10^6
解题思路
解法一:哈希集合(主提交版本)
- 每个数本身 + 反转后的数都放入 Set,返回 Set 大小。反转用除 10 取余法去掉前导零。
解法二:字符串反转(对照) - 用 StringBuilder reverse 实现数字反转,思路不同的实现。
复杂度: - 时间复杂度:O(n * L),L = 数字位数(≤7)。
- 空间复杂度:O(n)。
代码实现
java
import java.util.HashSet;
import java.util.Set;
public class Solution {
// 解法一:数学反转 + 哈希(主提交版本)
public int countDistinctIntegers(int[] nums) {
Set<Integer> set = new HashSet<>();
for (int x : nums) {
set.add(x);
int r = 0;
while (x > 0) {
r = r * 10 + x % 10;
x /= 10;
}
set.add(r);
}
return set.size();
}
// 解法二:字符串反转(对照)
public int countDistinctIntegers2(int[] nums) {
Set<Integer> set = new HashSet<>();
for (int x : nums) {
set.add(x);
set.add(Integer.parseInt(new StringBuilder(String.valueOf(x)).reverse().toString()));
}
return set.size();
}
public static void main(String[] args) {
Solution sol = new Solution();
check(sol.countDistinctIntegers(new int[] {1, 13, 10, 12, 31}) == 6, "2442 ex1");
check(sol.countDistinctIntegers2(new int[] {1, 13, 10, 12, 31}) == 6, "2442 ex12");
check(sol.countDistinctIntegers(new int[] {2, 2, 2}) == 1, "2442 ex2");
// 随机对拍
java.util.Random rnd = new java.util.Random(113);
for (int t = 0; t < 500; t++) {
int n = 1 + rnd.nextInt(30);
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = 1 + rnd.nextInt(100000);
}
int x = sol.countDistinctIntegers(a), y = sol.countDistinctIntegers2(a);
if (x != y) {
throw new AssertionError("2442 mismatch t=" + t + " x=" + x + " y=" + y);
}
}
System.out.println("2442 全部 PASS");
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(n * L),L = 数字位数(≤7)。
- 空间复杂度:O(n)。
2443. Sum of Number and Its Reverse(反转之后的数字和)
题目描述
给你一个非负 整数 num。如果存在某个非负整数 k 满足 k + reverse(k) = num,返回 true,
否则返回 false(reverse(k) 是 k 反转每个数位后得到的数字,注意反转后可能含前导零)。
示例 1
`num = 443` → `true`(172 + 271 = 443)。
示例 2
`num = 63` → `false`。
示例 3
`num = 181` → `true`(140 + 041 = 181)。
提示:
0 <= num <= 10^5
解题思路
解法一:枚举 k(主提交版本)
- 由于 k + reverse(k) = num、k 的位数不可能超过 num 的位数,枚举 k ∈ 0, num,
检查等式是否成立即可(num ≤ 1e5,枚举一次 O(num*位数) 可过)。
解法二:二分/数位构造(对照) - 无必要,直接也枚举但使用字符串反转的写法,两法对拍。
复杂度: - 时间复杂度:O(num * log num)。
- 空间复杂度:O(1)。
代码实现
java
public class Solution {
// 解法一:数学反转枚举(主提交版本)
public boolean sumOfNumberAndReverse(int num) {
for (int k = 0; k <= num; k++) {
if (k + revNum(k) == num) {
return true;
}
}
return false;
}
private int revNum(int x) {
int r = 0;
while (x > 0) {
r = r * 10 + x % 10;
x /= 10;
}
return r;
}
// 解法二:字符串反转枚举(对照)
public boolean sumOfNumberAndReverse2(int num) {
for (int k = 0; k <= num; k++) {
int r = Integer.parseInt(new StringBuilder(String.valueOf(k)).reverse().toString());
if (k + r == num) {
return true;
}
}
return false;
}
public static void main(String[] args) {
Solution sol = new Solution();
check(sol.sumOfNumberAndReverse(443) && sol.sumOfNumberAndReverse2(443), "2443 ex1");
check(!sol.sumOfNumberAndReverse(63) && !sol.sumOfNumberAndReverse2(63), "2443 ex2");
check(sol.sumOfNumberAndReverse(181) && sol.sumOfNumberAndReverse2(181), "2443 ex3");
check(sol.sumOfNumberAndReverse(0), "2443 0(k=0)");
// 随机对拍
java.util.Random rnd = new java.util.Random(127);
for (int t = 0; t < 500; t++) {
int n = rnd.nextInt(100001);
boolean x = sol.sumOfNumberAndReverse(n), y = sol.sumOfNumberAndReverse2(n);
if (x != y) {
throw new AssertionError("2443 mismatch n=" + n + " x=" + x + " y=" + y);
}
}
System.out.println("2443 全部 PASS");
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(num * log num)。
- 空间复杂度:O(1)。
2444. Count Subarrays With Fixed Bounds(统计定界子数组的数目)
题目描述
给你一个整数数组 nums 和两个整数 minK、maxK。nums 的定界子数组是满足:
- 子数组中的最小值 等于
minK; - 子数组中的最大值 等于
maxK。
返回定界子数组的数目。
示例 1
`nums = [1,3,5,2,7,5], minK = 1, maxK = 5` → `2`。
示例 2
`nums = [1,1,1,1], minK = 1, maxK = 1` → `10`。
提示:
2 <= nums.length <= 10^5,1 <= nums[i], minK, maxK <= 10^6
解题思路
解法一:端点维护双指针(主提交版本)
- 子数组不能包含任何
< minK或> maxK的元素;用bad记录最近一次越界位置。 - 记录最近一次出现
minK的位置lastMin与maxK的位置lastMax。 - 以 i 为右端点,合法左端点必须在
bad+1与min(lastMin, lastMax)之间,
数量 =max(0, min(lastMin, lastMax) - bad),累加即答案。
解法二:朴素枚举(对照,小规模) - 枚举左右端点并实时统计 min/max 校验,O(n^2)。
复杂度: - 时间复杂度:O(n)。
- 空间复杂度:O(1)。
代码实现
java
public class Solution {
// 解法一:端点维护(主提交版本)
public long countSubarrays(int[] nums, int minK, int maxK) {
long ans = 0;
int bad = -1;
int lastMin = -1, lastMax = -1;
for (int i = 0; i < nums.length; i++) {
if (nums[i] < minK || nums[i] > maxK) {
bad = i;
lastMin = lastMax = -1;
continue;
}
if (nums[i] == minK) {
lastMin = i;
}
if (nums[i] == maxK) {
lastMax = i;
}
if (lastMin != -1 && lastMax != -1) {
int leftBound = Math.min(lastMin, lastMax);
ans += Math.max(0, leftBound - bad);
}
}
return ans;
}
// 解法二:暴力枚举(对照)
public long countSubarraysBrute(int[] nums, int minK, int maxK) {
long ans = 0;
int n = nums.length;
for (int i = 0; i < n; i++) {
int mn = nums[i], mx = nums[i];
for (int j = i; j < n; j++) {
mn = Math.min(mn, nums[j]);
mx = Math.max(mx, nums[j]);
if (mn == minK && mx == maxK) {
ans++;
}
}
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
check(sol.countSubarrays(new int[] {1, 3, 5, 2, 7, 5}, 1, 5) == 2, "2444 ex1");
check(sol.countSubarraysBrute(new int[] {1, 3, 5, 2, 7, 5}, 1, 5) == 2, "2444 ex1b");
check(sol.countSubarrays(new int[] {1, 1, 1, 1}, 1, 1) == 10, "2444 ex2");
check(sol.countSubarraysBrute(new int[] {1, 1, 1, 1}, 1, 1) == 10, "2444 ex2b");
// 随机对拍
java.util.Random rnd = new java.util.Random(131);
for (int t = 0; t < 500; t++) {
int n = 2 + rnd.nextInt(40);
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = 1 + rnd.nextInt(6);
}
int mn = 1 + rnd.nextInt(5), mx = mn + rnd.nextInt(6 - mn);
long x = sol.countSubarrays(a, mn, mx), y = sol.countSubarraysBrute(a, mn, mx);
if (x != y) {
throw new AssertionError("2444 mismatch t=" + t + " x=" + x + " y=" + y);
}
}
System.out.println("2444 全部 PASS");
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(n)。
- 空间复杂度:O(1)。
2445. Number of Nodes With Value One(值为 1 的节点数)
题目描述
有一个无向树,n 个节点标记为 1...n,节点 v 的父节点是 floor(v/2)(根为 1)。最初每个节点值为 0。
对每个查询 queries[i],翻转节点 queries[i] 的子树内所有值。处理完所有查询后,返回值为 1 的节点总数。
示例 1
`n = 5, queries = [1,2,5]` → `3`。
示例 2
`n = 3, queries = [2,3,3]` → `1`。
提示:
1 <= n <= 10^5,1 <= queries.length <= 10^5,1 <= queries[i] <= n
解题思路
解法一:次数奇偶性(主提交版本)
- 子树翻转两次等于没翻,因此每个节点最终为 1 当且仅当「祖先链上被翻转次数」为奇数。
- 根 1 的次数 = 查询中 1 的出现次数;节点 v 的次数 = 父节点次数 + 查询中 v 的出现次数。
按编号从小到大遍历(父节点编号更小),累加统计奇数次节点数。
解法二:暴力子树翻转模拟(对照,小规模) - 每次查询对子树所有节点异或 1,最后统计。
复杂度: - 时间复杂度:O(n + q)。
- 空间复杂度:O(n)。
代码实现
java
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Solution {
// 解法一:奇偶次数递推(主提交版本)
public int numberOfNodes(int n, int[] queries) {
int[] flips = new int[n + 1];
for (int q : queries) {
flips[q]++;
}
int[] eff = new int[n + 1];
int ans = 0;
for (int v = 1; v <= n; v++) {
eff[v] = (eff[v / 2] + flips[v]) & 1;
if (eff[v] == 1) {
ans++;
}
}
return ans;
}
// 解法二:暴力模拟(对照,小规模)
public int numberOfNodesBrute(int n, int[] queries) {
int[] val = new int[n + 1];
for (int q : queries) {
// 翻转 q 的子树:q, 2q, 2q+1, ...
Set<Integer> sub = subtree(n, q);
for (int v : sub) {
val[v] ^= 1;
}
}
int ans = 0;
for (int v = 1; v <= n; v++) {
ans += val[v];
}
return ans;
}
private Set<Integer> subtree(int n, int root) {
Set<Integer> s = new HashSet<>();
List<Integer> level = new ArrayList<>();
level.add(root);
while (!level.isEmpty()) {
List<Integer> next = new ArrayList<>();
for (int v : level) {
s.add(v);
int l = v * 2, r = v * 2 + 1;
if (l <= n) {
next.add(l);
}
if (r <= n) {
next.add(r);
}
}
level = next;
}
return s;
}
public static void main(String[] args) {
Solution sol = new Solution();
check(sol.numberOfNodes(5, new int[] {1, 2, 5}) == 3, "2445 ex1");
check(sol.numberOfNodesBrute(5, new int[] {1, 2, 5}) == 3, "2445 ex1b");
check(sol.numberOfNodes(3, new int[] {2, 3, 3}) == 1, "2445 ex2");
check(sol.numberOfNodesBrute(3, new int[] {2, 3, 3}) == 1, "2445 ex2b");
// 随机对拍
java.util.Random rnd = new java.util.Random(137);
for (int t = 0; t < 300; t++) {
int n = 1 + rnd.nextInt(60);
int qn = 1 + rnd.nextInt(20);
int[] qs = new int[qn];
for (int i = 0; i < qn; i++) {
qs[i] = 1 + rnd.nextInt(n);
}
int x = sol.numberOfNodes(n, qs), y = sol.numberOfNodesBrute(n, qs);
if (x != y) {
throw new AssertionError("2445 mismatch t=" + t + " n=" + n + " x=" + x + " y=" + y);
}
}
System.out.println("2445 全部 PASS");
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(n + q)。
- 空间复杂度:O(n)。
2446. Determine if Two Events Have Conflict(判断两个事件是否存在冲突)
题目描述
给你两个字符串数组 event1、event2,表示同一天的两个闭区间时间段,时间按 HH:MM 格式给出
(24 小时制)。两个事件存在某个非空的交集(某些时刻两事件都包含)时视为冲突,返回是否冲突。
示例 1
`event1 = ["01:15","02:00"], event2 = ["02:00","03:00"]` → `true`。
示例 2
`event1 = ["10:00","11:00"], event2 = ["14:00","15:00"]` → `false`。
提示:
- 每个事件时间有效且
startTime <= endTime
解题思路
解法一:时间转分钟比较(主提交版本)
- 将 "HH:MM" 解析为分钟数,两闭区间 s1,e1、s2,e2 相交当且仅当
s1 <= e2 && s2 <= e1。
解法二:逐字符比较(对照) - 不转为分钟,直接按 HH:MM 字符串进行字典序比较(24 小时制下字典序与时间序一致)。
复杂度: - 时间复杂度:O(1)。
- 空间复杂度:O(1)。
代码实现
java
public class Solution {
// 解法一:转分钟比较(主提交版本)
public boolean haveConflict(String[] event1, String[] event2) {
int s1 = parse(event1[0]), e1 = parse(event1[1]);
int s2 = parse(event2[0]), e2 = parse(event2[1]);
return s1 <= e2 && s2 <= e1;
}
private int parse(String t) {
return Integer.parseInt(t.substring(0, 2)) * 60 + Integer.parseInt(t.substring(3));
}
// 解法二:字符串比较(对照)
public boolean haveConflict2(String[] event1, String[] event2) {
return event1[0].compareTo(event2[1]) <= 0 && event2[0].compareTo(event1[1]) <= 0;
}
public static void main(String[] args) {
Solution sol = new Solution();
check(sol.haveConflict(new String[] {"01:15", "02:00"}, new String[] {"02:00", "03:00"}), "2446 ex1");
check(sol.haveConflict2(new String[] {"01:15", "02:00"}, new String[] {"02:00", "03:00"}), "2446 ex12");
check(!sol.haveConflict(new String[] {"10:00", "11:00"}, new String[] {"14:00", "15:00"}), "2446 ex2");
check(!sol.haveConflict2(new String[] {"10:00", "11:00"}, new String[] {"14:00", "15:00"}), "2446 ex22");
check(sol.haveConflict(new String[] {"01:00", "02:00"}, new String[] {"01:20", "03:00"}), "2446 ex3");
// 随机对拍
java.util.Random rnd = new java.util.Random(139);
for (int t = 0; t < 500; t++) {
String[] e1 = randEvent(rnd), e2 = randEvent(rnd);
boolean x = sol.haveConflict(e1, e2), y = sol.haveConflict2(e1, e2);
if (x != y) {
throw new AssertionError("2446 mismatch t=" + t);
}
}
System.out.println("2446 全部 PASS");
}
private static String[] randEvent(java.util.Random rnd) {
int h1 = rnd.nextInt(24), m1 = rnd.nextInt(60);
int h2 = rnd.nextInt(24), m2 = rnd.nextInt(60);
String t1 = String.format("%02d:%02d", h1, m1);
String t2 = String.format("%02d:%02d", h2, m2);
if (t1.compareTo(t2) > 0) {
String tmp = t1;
t1 = t2;
t2 = tmp;
}
return new String[] {t1, t2};
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(1)。
- 空间复杂度:O(1)。
2447. Number of Subarrays With GCD Equal to K(最大公因数等于 K 的子数组数目)
题目描述
给你一个整数数组 nums 和一个整数 k,统计 nums 的子数组中元素的最大公因数等于 k 的子数组数目。
示例 1
`nums = [9,3,1,2,6,3], k = 3` → `4`。
示例 2
`nums = [4], k = 7` → `0`。
提示:
1 <= nums.length <= 1000,1 <= nums[i], k <= 10^9
解题思路
解法一:gcd 集合递推(主提交版本)
- 遍历每个起点 i,维护「以 i 为起点的所有子数组 gcd 的集合」:
gcd(x, v)随长度增加而递减,
集合大小 O(log A)。以每个起点新的 gcd = gcd(前一状态集合元素, numsi)。 - 总复杂度 O(n log A)。
解法二:暴力枚举(对照) - 对每个起点向后扩展 gcd,等于 k 时计数,O(n^2)。
复杂度: - 时间复杂度:O(n log A)(主)或 O(n^2)(暴力)。
- 空间复杂度:O(log A)。
代码实现
java
public class Solution {
// 解法一:gcd 集合递推(主提交版本)
public int subarrayGCD(int[] nums, int k) {
int ans = 0;
java.util.Map<Integer, Integer> prev = new java.util.HashMap<>();
for (int x : nums) {
java.util.Map<Integer, Integer> cur = new java.util.HashMap<>();
if (x % k == 0) {
// 新的子数组起点 x
cur.put(x, cur.getOrDefault(x, 0) + 1);
for (java.util.Map.Entry<Integer, Integer> e : prev.entrySet()) {
int ng = gcd(e.getKey(), x);
cur.put(ng, cur.getOrDefault(ng, 0) + e.getValue());
}
ans += cur.getOrDefault(k, 0);
}
prev = cur;
}
return ans;
}
// 解法二:暴力(对照)
public int subarrayGCD2(int[] nums, int k) {
int ans = 0;
for (int i = 0; i < nums.length; i++) {
int g = 0;
for (int j = i; j < nums.length; j++) {
g = gcd(g, nums[j]);
if (g == k) {
ans++;
} else if (g < k) {
break; // gcd 只会更小,提前退出
}
}
}
return ans;
}
private static int gcd(int a, int b) {
while (b != 0) {
int t = a % b;
a = b;
b = t;
}
return a;
}
public static void main(String[] args) {
Solution sol = new Solution();
check(sol.subarrayGCD(new int[] {9, 3, 1, 2, 6, 3}, 3) == 4, "2447 ex1");
check(sol.subarrayGCD2(new int[] {9, 3, 1, 2, 6, 3}, 3) == 4, "2447 ex12");
check(sol.subarrayGCD(new int[] {4}, 7) == 0, "2447 ex2");
check(sol.subarrayGCD2(new int[] {4}, 7) == 0, "2447 ex22");
// 随机对拍
java.util.Random rnd = new java.util.Random(149);
for (int t = 0; t < 500; t++) {
int n = 1 + rnd.nextInt(30);
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = 1 + rnd.nextInt(100);
}
int k = 1 + rnd.nextInt(10);
int x = sol.subarrayGCD(a, k), y = sol.subarrayGCD2(a, k);
if (x != y) {
throw new AssertionError("2447 mismatch t=" + t + " x=" + x + " y=" + y);
}
}
System.out.println("2447 全部 PASS");
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(n log A)(主)或 O(n^2)(暴力)。
- 空间复杂度:O(log A)。
2448. Minimum Cost to Make Array Equal(使数组相等的最小开销)
题目描述
给你两个下标从 0 开始的数组 nums 和 cost,分别包含 n 个正整数。可以执行任意次操作:
将 nums 中任意元素增加或减小 1。对第 i 个元素执行一次操作的开销是 cost[i]。
返回使 nums 中所有元素相等的最少总开销。
示例 1
`nums=[1,3,5,2], cost=[2,3,1,14]` → `8`(全部变成 2)。
示例 2
`nums=[2,2,2,2,2], cost=[4,2,8,1,3]` → `0`。
提示:
1 <= n <= 10^5,1 <= nums[i], cost[i] <= 10^6,测试保证输出不超过 2^53-1
解题思路
解法一:带权中位数(主提交版本)
- 目标值 x 的代价函数
f(x) = sum(cost[i] * |nums[i] - x|),是凸函数,
最优 x 在「带权中位数」处:按 nums 排序后,找到前缀 cost 总和首次 ≥ 总 cost 一半的位置。 - 这里直接枚举候选项取 min:排序后前缀扫描求从最小值出发的代价,再向右滑动更新 O(n log n)。
解法二:三分/暴力枚举(对照) - 由于值域较大,采用对去重后的候选值暴力计算并比较(O(n^2))仅小规模对拍。
复杂度: - 时间复杂度:O(n log n)。
- 空间复杂度:O(n)。
代码实现
java
import java.util.Arrays;
public class Solution {
// 解法一:排序 + 带权中位数扫描(主提交版本)
public long minCost(int[] nums, int[] cost) {
int n = nums.length;
Integer[] idx = new Integer[n];
for (int i = 0; i < n; i++) {
idx[i] = i;
}
Arrays.sort(idx, (a, b) -> Integer.compare(nums[a], nums[b]));
long total = 0;
for (long c : cost) {
total += c;
}
// 找到带权中位数位置
long half = (total + 1) / 2;
long acc = 0;
int pick = -1;
for (int i = 0; i < n; i++) {
acc += cost[idx[i]];
if (acc >= half) {
pick = nums[idx[i]];
break;
}
}
// 计算以 pick 为目标的总代价
long ans = 0;
for (int i = 0; i < n; i++) {
ans += (long) Math.abs(nums[i] - pick) * cost[i];
}
return ans;
}
// 解法二:暴力枚举全部值为目标(对照,小规模)
public long minCostBrute(int[] nums, int[] cost) {
int lo = Integer.MAX_VALUE, hi = Integer.MIN_VALUE;
for (int x : nums) {
lo = Math.min(lo, x);
hi = Math.max(hi, x);
}
long best = Long.MAX_VALUE;
for (int p = lo; p <= hi; p++) {
long cur = 0;
for (int i = 0; i < nums.length; i++) {
cur += (long) Math.abs(nums[i] - p) * cost[i];
}
best = Math.min(best, cur);
}
return best;
}
public static void main(String[] args) {
Solution sol = new Solution();
check(sol.minCost(new int[] {1, 3, 5, 2}, new int[] {2, 3, 1, 14}) == 8, "2448 ex1");
check(sol.minCostBrute(new int[] {1, 3, 5, 2}, new int[] {2, 3, 1, 14}) == 8, "2448 ex1b");
check(sol.minCost(new int[] {2, 2, 2, 2, 2}, new int[] {4, 2, 8, 1, 3}) == 0, "2448 ex2");
check(sol.minCostBrute(new int[] {2, 2, 2, 2, 2}, new int[] {4, 2, 8, 1, 3}) == 0, "2448 ex2b");
// 随机对拍
java.util.Random rnd = new java.util.Random(151);
for (int t = 0; t < 300; t++) {
int n = 1 + rnd.nextInt(12);
int[] a = new int[n], c = new int[n];
for (int i = 0; i < n; i++) {
a[i] = 1 + rnd.nextInt(20);
c[i] = 1 + rnd.nextInt(10);
}
long x = sol.minCost(a, c), y = sol.minCostBrute(a, c);
if (x != y) {
throw new AssertionError("2448 mismatch t=" + t + " x=" + x + " y=" + y
+ " a=" + Arrays.toString(a));
}
}
System.out.println("2448 全部 PASS");
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(n log n)。
- 空间复杂度:O(n)。
2449. Minimum Number of Operations to Make Arrays Similar(使数组相似的最少操作次数)
题目描述
给你两个正整数数组 nums 和 target(等长)。一次操作:选两个不同下标 i、j,令
nums[i] += 2 且 nums[j] -= 2。两个数组每个元素出现频率相等则称相似。返回使 nums 与 target
相似的最少操作次数(测试保证有解)。
示例 1
`nums=[8,12,6], target=[2,14,10]` → `2`。
示例 2
`nums=[1,2,5], target=[4,1,3]` → `1`。
示例 3
`nums=[1,1,1,1,1], target=[1,1,1,1,1]` → `0`。
提示:
1 <= n <= 10^5,1 <= nums[i], target[i] <= 10^6
解题思路
解法一:奇偶分组贪心(主提交版本)
- 每次操作 ±2 不改变元素奇偶性,因此 nums 与 target 中奇数和偶数必须分别对应。
- 把 nums 与 target 各自按奇偶分成两组并排序;匹配时同一组内从小到大配对,
需要的「上升总量」=sum(max(0, target2[i] - nums2[i]))(下降由别的元素承担)。 - 一次操作同时给一个元素 +2、另一个 -2,因此操作数 = 上升总量 / 2。
解法二:朴素双数组匹配(对照) - 显式模拟匹配过程:同样分组排序,统计正差和,除以 2。
复杂度: - 时间复杂度:O(n log n)。
- 空间复杂度:O(n)。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
// 解法一:奇偶分组贪心(主提交版本)
public long makeSimilar(int[] nums, int[] target) {
List<Integer> no = new ArrayList<>(), ne = new ArrayList<>();
List<Integer> to = new ArrayList<>(), te = new ArrayList<>();
for (int x : nums) {
if ((x & 1) == 1) {
no.add(x);
} else {
ne.add(x);
}
}
for (int x : target) {
if ((x & 1) == 1) {
to.add(x);
} else {
te.add(x);
}
}
no.sort(Integer::compareTo);
ne.sort(Integer::compareTo);
to.sort(Integer::compareTo);
te.sort(Integer::compareTo);
long inc = 0;
for (int i = 0; i < no.size(); i++) {
if (to.get(i) > no.get(i)) {
inc += to.get(i) - no.get(i);
}
}
for (int i = 0; i < ne.size(); i++) {
if (te.get(i) > ne.get(i)) {
inc += te.get(i) - ne.get(i);
}
}
return inc / 2;
}
// 解法二:直接统计(对照,思路等价写法不同)
public long makeSimilar2(int[] nums, int[] target) {
Arrays.sort(nums);
Arrays.sort(target);
// 由于奇偶必须分开对应,先按奇偶稳定分组再求差
int n = nums.length;
int[] n2 = new int[n], t2 = new int[n];
int po = 0, pe = 0, po2 = 0, pe2 = 0;
for (int x : nums) {
if ((x & 1) == 1) {
n2[po++] = x;
} else {
n2[n - 1 - (pe++)] = x;
}
}
for (int x : target) {
if ((x & 1) == 1) {
t2[po2++] = x;
} else {
t2[n - 1 - (pe2++)] = x;
}
}
long inc = 0;
for (int i = 0; i < n; i++) {
if (t2[i] > n2[i]) {
inc += t2[i] - n2[i];
}
}
return inc / 2;
}
public static void main(String[] args) {
Solution sol = new Solution();
check(sol.makeSimilar(new int[] {8, 12, 6}, new int[] {2, 14, 10}) == 2, "2449 ex1");
check(sol.makeSimilar2(new int[] {8, 12, 6}, new int[] {2, 14, 10}) == 2, "2449 ex12");
check(sol.makeSimilar(new int[] {1, 2, 5}, new int[] {4, 1, 3}) == 1, "2449 ex2");
check(sol.makeSimilar(new int[] {1, 1, 1, 1, 1}, new int[] {1, 1, 1, 1, 1}) == 0, "2449 ex3");
// 随机对拍:从 target 反向生成合法 nums
java.util.Random rnd = new java.util.Random(157);
for (int t = 0; t < 300; t++) {
int n = 2 + rnd.nextInt(10);
int[] tg = new int[n];
for (int i = 0; i < n; i++) {
tg[i] = 1 + rnd.nextInt(30);
}
int[] ns = tg.clone();
for (int i = 0; i < n; i++) {
ns[i] += (rnd.nextBoolean() ? 2 : -2) * (1 + rnd.nextInt(5));
if (ns[i] < 1) {
ns[i] = tg[i]; // 保证合法(奇偶一致)
}
}
// 确保奇偶一致:修正
for (int i = 0; i < n; i++) {
if ((ns[i] & 1) != (tg[i] & 1)) {
ns[i] += 1;
}
}
long x = sol.makeSimilar(ns, tg), y = sol.makeSimilar2(ns, tg);
if (x != y) {
throw new AssertionError("2449 mismatch t=" + t + " x=" + x + " y=" + y);
}
}
System.out.println("2449 全部 PASS");
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(n log n)。
- 空间复杂度:O(n)。
2450. Number of Distinct Binary Strings After Applying Operations(应用操作后不同二进制字符串的数量)
题目描述
给定一个_二进制_字符串 s 和一个正整数 k。可以应用操作任意次:从 s 中选择任何大小为 k 的
子字符串,将其所有字符翻转。返回可以获得的不同 字符串的数量,对 10^9+7 取模。
示例 1
`s = "1001", k = 3` → `4`。
示例 2
`s = "10110", k = 5` → `2`。
提示:
1 <= k <= s.length <= 10^5,s[i]是 '0' 或 '1'
解题思路
解法一:线性基/矩阵秩(主提交版本,数学推导)
- 翻转固定长度 k 的连续段,可视为在 GF(2) 上对布尔向量做线性变换。所有可达字符串构成的
仿射空间大小 = 2^rank。已知结论(官方题解):若 k 与 n 满足一定条件,秩 = n 或 n-1 或特殊。
简单实现:用「翻转差分数组」思路,只考虑长度为 k 的段的操作组合,差分变换的秩:
对每个位置 i,定义翻转该段为对差分数组的两位取反;互相独立的翻转组合数通过并查集/线性基计算。 - 这里采用通用可通行解法:把长度 k 的翻转段看成 n-k+1 个线性方程作用在 n 位上,
用并查集/线性基计算可达空间维数,答案 = 2^rank % MOD。注意 n ≤ 1e5 时直接线性基 O(n*bits) 可行。
实际更简单:翻转段操作等价于在 GF(2) 上生成空间,其秩 = n - (k 与 n 的某种关系);
标准结论:rank = n(若 k 为奇数)或 n-1(若 k 为偶数且 n-k 为偶数时)等。为稳妥直接算。
解法二:小规模暴力 BFS(对照) - 小规模时枚举所有可达状态(位掩码翻转),返回集合大小,用于对拍。
复杂度: - 时间复杂度:O(n * bits) 或 O(n)。
- 空间复杂度:O(n)。
代码实现
java
import java.util.HashSet;
import java.util.Set;
public class Solution {
private static final int MOD = 1000000007;
// 解法一:线性基求秩(主提交版本)
public int countDistinctStrings(String s, int k) {
int n = s.length();
int m = n - k + 1; // 翻转段数
int[] basis = new int[k]; // 线性基,向量长度 k(n 位由 m 个段向量的线性组合决定)
// 把每个候选「整体翻转」视为 m 个向量用 GF(2) 线性基求秩的几何解释:
// 更稳定做法:直接计算可达状态的维度------已知二阶差分技巧:
// 定义 diff[i] = s[i] ^ s[i-1],翻转 [l, l+k-1] 等价于 diff[l] 与 diff[l+k] 变号。
// diff 数组长度 n+1,可翻转的独立二元组 (l, l+k) 形成若干链;每条链长度 L 的自由度 = L-1
// (首尾可被固定),但两端 diff[0]、diff[n] 不合法(越界)时调整。官方解法秩计算复杂。
// 回到实现:用并查集把位置 i 与 i+k 连边(表示二者构成的可翻转对),
// 连通块的自由度 = 块大小(奇数环的部分)------由于我们处理的是差分数组的「翻转两位」,
// 实际上每次操作翻转两个 diff 位。
// 定义数组 d[0..n],翻转 [l, l+k-1] 使 d[l] 与 d[l+k] 异或 1。
// 可操作的边 = 所有 (l, l+k),0<=l<=n-k。把这些对(下标 l 与 l+k)用并查集连接,
// 每个连通分量大小为 sz:分量内每对边缘相互影响,等价于向量数 = sz-1(任取一点为基准)。
// rank = sum(sz-1) 对所有连通分量 = n+1 - 连通分量个数。
int N = n + 1;
int[] parent = new int[N];
for (int i = 0; i < N; i++) {
parent[i] = i;
}
for (int l = 0; l + k <= n; l++) {
union(parent, l, l + k);
}
Set<Integer> roots = new HashSet<>();
for (int i = 0; i < N; i++) {
roots.add(find(parent, i));
}
int rank = N - roots.size(); // n+1 - comps
// 但差分数组首位 d[0] 与末位 d[n] 是固定(虚拟)位吗?实际上 d[0] 对应 s[0](xor 0),
// d[n] 对应翻转后的 s[n-1]、无实际约束------原串信息只有 n 位。最终可达字符串数 = 2^有效秩,
// 有效秩需仔细对齐:达空间维数 = rank(上面算的)------验证对 k=n:l=0 唯一对 (0,n),
// comps = n,rank = n+1-n = 1?但 k=n 时只有整体翻转,可达 2 个字符串 → 秩应为 1 ✅。
return (int) pow2(rank);
}
private void union(int[] p, int a, int b) {
int ra = find(p, a), rb = find(p, b);
if (ra != rb) {
p[ra] = rb;
}
}
private int find(int[] p, int x) {
while (p[x] != x) {
p[x] = p[p[x]];
x = p[x];
}
return x;
}
private long pow2(int e) {
long r = 1, a = 2;
while (e > 0) {
if ((e & 1) == 1) {
r = r * a % MOD;
}
a = a * a % MOD;
e >>= 1;
}
return r;
}
// 解法二:暴力 BFS(对照,小规模)
public int countDistinctStringsBrute(String s, int k) {
Set<String> seen = new HashSet<>();
java.util.ArrayDeque<String> q = new java.util.ArrayDeque<>();
seen.add(s);
q.add(s);
int n = s.length();
while (!q.isEmpty()) {
String cur = q.poll();
for (int l = 0; l + k <= n; l++) {
StringBuilder sb = new StringBuilder(cur);
for (int i = l; i < l + k; i++) {
sb.setCharAt(i, sb.charAt(i) == '0' ? '1' : '0');
}
String ns = sb.toString();
if (seen.add(ns)) {
q.add(ns);
}
}
}
return seen.size();
}
public static void main(String[] args) {
Solution sol = new Solution();
check(sol.countDistinctStrings("1001", 3) == 4, "2450 ex1");
check(sol.countDistinctStringsBrute("1001", 3) == 4, "2450 ex1b");
check(sol.countDistinctStrings("10110", 5) == 2, "2450 ex2");
check(sol.countDistinctStringsBrute("10110", 5) == 2, "2450 ex2b");
// 随机对拍(小规模)
java.util.Random rnd = new java.util.Random(163);
for (int t = 0; t < 200; t++) {
int n = 1 + rnd.nextInt(9);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
sb.append(rnd.nextBoolean() ? '1' : '0');
}
int k = 1 + rnd.nextInt(n);
int x = sol.countDistinctStrings(sb.toString(), k);
int y = sol.countDistinctStringsBrute(sb.toString(), k);
if (x != y) {
throw new AssertionError("2450 mismatch s=" + sb + " k=" + k + " x=" + x + " y=" + y);
}
}
System.out.println("2450 全部 PASS");
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(n * bits) 或 O(n)。
- 空间复杂度:O(n)。
2451. Odd String Difference(差值数组不同的字符串)
题目描述
给你一个字符串数组 words(长度都相同 n)。每个字符串可转为长度 n-1 的差值整数数组
difference[i][j] = words[i][j+1] - words[i][j](按字母位置差值)。除了一个字符串外其余差值数组
都相同。返回差值数组不同的那个字符串。
示例 1
`words = ["adc","wzy","abc"]` → `"abc"`。
示例 2
`words = ["aaa","bob","ccc","ddd"]` → `"bob"`。
提示:
3 <= words.length <= 100,2 <= n <= 20,只含小写字母
解题思路
解法一:哈希统计差值模式(主提交版本)
- 把每个字符串的差值数组编码为 String(用逗号分隔),统计出现次数,返回出现 1 次的字符串。
解法二:直接找不同(对照) - 取前两个的差值,若相等则找与它不同的;否则比较哪个与第三个相同。
复杂度: - 时间复杂度:O(n * L)。
- 空间复杂度:O(n)。(n 为 words 数量)
代码实现
java
import java.util.HashMap;
import java.util.Map;
public class Solution {
// 解法一:哈希统计(主提交版本)
public String oddString(String[] words) {
Map<String, Integer> cnt = new HashMap<>();
Map<String, String> pos = new HashMap<>();
for (String w : words) {
String key = encode(w);
cnt.put(key, cnt.getOrDefault(key, 0) + 1);
pos.put(key, w);
}
for (Map.Entry<String, Integer> e : cnt.entrySet()) {
if (e.getValue() == 1) {
return pos.get(e.getKey());
}
}
return "";
}
private String encode(String w) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i < w.length(); i++) {
if (sb.length() > 0) {
sb.append(',');
}
sb.append(w.charAt(i) - w.charAt(i - 1));
}
return sb.toString();
}
// 解法二:先测前两个(对照)
public String oddString2(String[] words) {
String d0 = diff(words[0]), d1 = diff(words[1]), d2 = diff(words[2]);
String other = d0.equals(d1) ? d0 : (d0.equals(d2) ? d0 : d1);
for (String w : words) {
if (!diff(w).equals(other)) {
return w;
}
}
return "";
}
private String diff(String w) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i < w.length(); i++) {
if (sb.length() > 0) {
sb.append(',');
}
sb.append(w.charAt(i) - w.charAt(i - 1));
}
return sb.toString();
}
public static void main(String[] args) {
Solution sol = new Solution();
check(sol.oddString(new String[] {"adc", "wzy", "abc"}).equals("abc"), "2451 ex1");
check(sol.oddString2(new String[] {"adc", "wzy", "abc"}).equals("abc"), "2451 ex12");
check(sol.oddString(new String[] {"aaa", "bob", "ccc", "ddd"}).equals("bob"), "2451 ex2");
check(sol.oddString2(new String[] {"aaa", "bob", "ccc", "ddd"}).equals("bob"), "2451 ex22");
// 随机对拍
java.util.Random rnd = new java.util.Random(167);
for (int t = 0; t < 300; t++) {
int n = 3 + rnd.nextInt(6);
int len = 2 + rnd.nextInt(6);
// 构造 n-1 个相同 + 1 个不同的字符串
StringBuilder base = new StringBuilder();
for (int i = 0; i < len; i++) {
base.append((char) ('a' + rnd.nextInt(5)));
}
String[] ws = new String[n];
int oddPos = rnd.nextInt(n);
String odd = "";
// 保证 odd 的差值数组与 base 不同
do {
StringBuilder sb = new StringBuilder();
for (int j = 0; j < len; j++) {
sb.append((char) ('a' + rnd.nextInt(5)));
}
odd = sb.toString();
} while (diffOf(odd).equals(diffOf(base.toString())));
for (int i = 0; i < n; i++) {
ws[i] = (i == oddPos) ? odd : base.toString();
}
String x = sol.oddString(ws), y = sol.oddString2(ws);
if (!x.equals(y) || !x.equals(odd)) {
throw new AssertionError("2451 mismatch t=" + t + " x=" + x + " y=" + y + " odd=" + odd);
}
}
System.out.println("2451 全部 PASS");
}
private static String diffOf(String w) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i < w.length(); i++) {
if (sb.length() > 0) {
sb.append(',');
}
sb.append(w.charAt(i) - w.charAt(i - 1));
}
return sb.toString();
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(n * L)。
- 空间复杂度:O(n)。(n 为 words 数量)
2452. Words Within Two Edits of Dictionary(距离字典两次编辑以内的单词)
题目描述
给你两个字符串数组 queries 和 dictionary(所有单词长度相同,只含小写字母)。
一次编辑:把任意一个字母修改成任何其他字母。返回 queries 中满足「不超过两次编辑内与 dictionary
中某个字符串相同」的单词,顺序与输入一致。
示例 1
`queries=["word","note","ants","wood"], dictionary=["wood","joke","moat"]` → `["word","note","wood"]`。
示例 2
`queries=["yes"], dictionary=["not"]` → `[]`。
提示:
1 <= queries.length, dictionary.length <= 100,1 <= n <= 100
解题思路
解法一:按位差异计数(主提交版本)
- 对 queries 的每个 w,遍历 dictionary 每个 d 统计对应位置不同数 ≤ 2 即可。
O(Q * D * L) = 100100 100 = 1e6,直接过。
解法二:剪枝 + 提前退出(对照) - 相同结构但用 char 数组先拷贝、位差异计数,写法不同。
复杂度: - 时间复杂度:O(Q * D * L)。
- 空间复杂度:O(1)。
代码实现
java
import java.util.ArrayList;
import java.util.List;
public class Solution {
// 解法一:直接计数(主提交版本)
public List<String> twoEditWords(String[] queries, String[] dictionary) {
List<String> ans = new ArrayList<>();
for (String q : queries) {
for (String d : dictionary) {
int diff = 0;
for (int i = 0; i < q.length(); i++) {
if (q.charAt(i) != d.charAt(i)) {
diff++;
if (diff > 2) {
break;
}
}
}
if (diff <= 2) {
ans.add(q);
break;
}
}
}
return ans;
}
// 解法二:优化写法(对照)
public List<String> twoEditWords2(String[] queries, String[] dictionary) {
List<String> ans = new ArrayList<>();
char[][] dict = new char[dictionary.length][];
for (int i = 0; i < dictionary.length; i++) {
dict[i] = dictionary[i].toCharArray();
}
for (String q : queries) {
char[] qc = q.toCharArray();
boolean ok = false;
for (char[] d : dict) {
int diff = 0;
for (int i = 0; i < qc.length; i++) {
if (qc[i] != d[i]) {
diff++;
}
}
if (diff <= 2) {
ok = true;
break;
}
}
if (ok) {
ans.add(q);
}
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
List<String> r1 = sol.twoEditWords(new String[] {"word", "note", "ants", "wood"},
new String[] {"wood", "joke", "moat"});
check(r1.toString().equals("[word, note, wood]"), "2452 ex1 " + r1);
check(sol.twoEditWords2(new String[] {"word", "note", "ants", "wood"},
new String[] {"wood", "joke", "moat"}).size() == 3, "2452 ex12");
check(sol.twoEditWords(new String[] {"yes"}, new String[] {"not"}).isEmpty(), "2452 ex2");
// 随机对拍
java.util.Random rnd = new java.util.Random(173);
for (int t = 0; t < 300; t++) {
int qn = 1 + rnd.nextInt(8), dn = 1 + rnd.nextInt(8);
int len = 1 + rnd.nextInt(6);
String[] qs = new String[qn], ds = new String[dn];
for (int i = 0; i < qn; i++) {
qs[i] = randStr(rnd, len);
}
for (int i = 0; i < dn; i++) {
ds[i] = randStr(rnd, len);
}
List<String> x = sol.twoEditWords(qs, ds), y = sol.twoEditWords2(qs, ds);
if (!x.equals(y)) {
throw new AssertionError("2452 mismatch t=" + t + " x=" + x + " y=" + y);
}
}
System.out.println("2452 全部 PASS");
}
private static String randStr(java.util.Random rnd, int len) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++) {
sb.append((char) ('a' + rnd.nextInt(6)));
}
return sb.toString();
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(Q * D * L)。
- 空间复杂度:O(1)。
2453. Destroy Sequential Targets(摧毁一系列目标)
题目描述
给你一个下标从 0 开始的数组 nums(正整数,数轴上目标的位置)和一个整数 space。
给机器输入 nums[i],它会摧毁所有位置在 nums[i] + c * space(c 为非负整数)的目标。
想要摧毁尽可能多的目标,返回在摧毁数目最多前提下 nums[i] 的最小值。
示例 1
`nums=[3,7,8,1,1,5], space=2` → `1`。
示例 2
`nums=[1,3,5,2,4,6], space=2` → `1`。
示例 3
`nums=[6,2,5], space=100` → `2`。
提示:
1 <= nums.length <= 10^5,1 <= nums[i] <= 10^9,1 <= space <= 10^9
解题思路
解法一:同余分组(主提交版本)
- 输入参数 x 摧毁的目标是与其模 space 同余的所有 numsi(因为 x + c*space)。
- 按
nums[i] % space分组计数;取计数最大的组中最小元素。
解法二:TreeMap 统计(对照) - 用有序结构记录每组最小元素,逻辑相同、实现不同。
复杂度: - 时间复杂度:O(n)。
- 空间复杂度:O(n)。
代码实现
java
import java.util.HashMap;
import java.util.Map;
public class Solution {
// 解法一:同余分组(主提交版本)
public int destroyTargets(int[] nums, int space) {
Map<Integer, Integer> cnt = new HashMap<>();
Map<Integer, Integer> minOf = new HashMap<>();
for (int x : nums) {
int key = ((x % space) + space) % space;
cnt.put(key, cnt.getOrDefault(key, 0) + 1);
minOf.put(key, Math.min(minOf.getOrDefault(key, Integer.MAX_VALUE), x));
}
int bestCnt = -1, bestVal = Integer.MAX_VALUE;
for (Map.Entry<Integer, Integer> e : cnt.entrySet()) {
if (e.getValue() > bestCnt || (e.getValue() == bestCnt && minOf.get(e.getKey()) < bestVal)) {
bestCnt = e.getValue();
bestVal = minOf.get(e.getKey());
}
}
return bestVal;
}
// 解法二:统计后扫描(对照)
public int destroyTargets2(int[] nums, int space) {
Map<Integer, Integer> cnt = new HashMap<>();
for (int x : nums) {
int key = ((x % space) + space) % space;
cnt.put(key, cnt.getOrDefault(key, 0) + 1);
}
int bestCnt = -1, bestVal = Integer.MAX_VALUE;
for (int x : nums) {
int key = ((x % space) + space) % space;
int c = cnt.get(key);
if (c > bestCnt || (c == bestCnt && x < bestVal)) {
bestCnt = c;
bestVal = x;
}
}
return bestVal;
}
public static void main(String[] args) {
Solution sol = new Solution();
check(sol.destroyTargets(new int[] {3, 7, 8, 1, 1, 5}, 2) == 1, "2453 ex1");
check(sol.destroyTargets2(new int[] {3, 7, 8, 1, 1, 5}, 2) == 1, "2453 ex12");
check(sol.destroyTargets(new int[] {1, 3, 5, 2, 4, 6}, 2) == 1, "2453 ex2");
check(sol.destroyTargets(new int[] {6, 2, 5}, 100) == 2, "2453 ex3");
// 随机对拍
java.util.Random rnd = new java.util.Random(179);
for (int t = 0; t < 500; t++) {
int n = 1 + rnd.nextInt(30);
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = 1 + rnd.nextInt(100);
}
int sp = 1 + rnd.nextInt(20);
int x = sol.destroyTargets(a, sp), y = sol.destroyTargets2(a, sp);
if (x != y) {
throw new AssertionError("2453 mismatch t=" + t + " x=" + x + " y=" + y);
}
}
System.out.println("2453 全部 PASS");
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(n)。
- 空间复杂度:O(n)。
2454. Next Greater Element IV(下一个更大元素 IV)
题目描述
给你一个下标从 0 开始的非负整数数组 nums。nums[j] 称为 nums[i] 的第二大整数,若:
j > i;2)nums[j] > nums[i];3) 恰好存在一个k满足i < k < j且nums[k] > nums[i]。
返回answer,answer[i]是第二大整数,不存在则为 -1。
示例 1
`nums = [2,4,0,9,6]` → `[9,6,6,-1,-1]`。
示例 2
`nums = [3,3]` → `[-1,-1]`。
提示:
1 <= nums.length <= 10^5,0 <= nums[i] <= 10^9
解题思路
解法一:单调栈 + 小根堆(主提交版本)
- 从左到右扫描,用单调递减栈维护「只被弹出的元素」:当
nums[i]大于栈顶时,栈顶元素
找到了第一个更大元素(就是 numsi),把它们移入一个小根堆(存「已经找到一个更大元素的候选下标」,
堆按 nums 值排序)。 - 每次遇到新元素 numsi,先处理堆:凡堆顶值 < numsi 的,说明 numsi 是它的第二个更大元素
(第一个已被它们弹出它们时遇到的那个元素找到------准确说:堆顶元素在之前已被某个更大元素弹出,
现在遇到更大的 numsi 即是第二个更大),记录答案并弹出。 - 然后处理单调栈(负责找「第一个更大元素」)。
解法二:暴力枚举(对照,小规模) - 对每个 i 向右找第一个更大、再找第二个更大。
复杂度: - 时间复杂度:O(n log n)。
- 空间复杂度:O(n)。
代码实现
java
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.PriorityQueue;
public class Solution {
// 解法一:单调栈 + 小根堆(主提交版本)
public int[] secondGreaterElement(int[] nums) {
int n = nums.length;
int[] ans = new int[n];
Arrays.fill(ans, -1);
Deque<Integer> stack = new ArrayDeque<>(); // 单调递减栈,存下标
PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> Integer.compare(nums[a], nums[b]));
for (int i = 0; i < n; i++) {
// 先处理已经找到第一个更大元素的候选:nums[i] 是它们的第二个更大元素
while (!pq.isEmpty() && nums[pq.peek()] < nums[i]) {
ans[pq.poll()] = nums[i];
}
// 找第一个更大元素:弹出所有小于 nums[i] 的
while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
pq.offer(stack.pop()); // 找到了第一个更大元素(nums[i])
}
stack.push(i);
}
return ans;
}
// 解法二:暴力(对照)
public int[] secondGreaterElementBrute(int[] nums) {
int n = nums.length;
int[] ans = new int[n];
Arrays.fill(ans, -1);
for (int i = 0; i < n; i++) {
int first = -1, second = -1;
for (int j = i + 1; j < n; j++) {
if (nums[j] > nums[i]) {
if (first == -1) {
first = j;
} else {
second = j;
break;
}
}
}
if (second != -1) {
ans[i] = nums[second];
}
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
check(Arrays.toString(sol.secondGreaterElement(new int[] {2, 4, 0, 9, 6})).equals("[9, 6, 6, -1, -1]"),
"2454 ex1");
check(Arrays.toString(sol.secondGreaterElement(new int[] {3, 3})).equals("[-1, -1]"), "2454 ex2");
// 随机对拍
java.util.Random rnd = new java.util.Random(181);
for (int t = 0; t < 500; t++) {
int n = 1 + rnd.nextInt(12);
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = rnd.nextInt(30);
}
int[] x = sol.secondGreaterElement(a), y = sol.secondGreaterElementBrute(a);
if (!Arrays.equals(x, y)) {
throw new AssertionError("2454 mismatch t=" + t + " x=" + Arrays.toString(x)
+ " y=" + Arrays.toString(y) + " a=" + Arrays.toString(a));
}
}
System.out.println("2454 全部 PASS");
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(n log n)。
- 空间复杂度:O(n)。
2455. Average Value of Even Numbers That Are Divisible by Three(可被三整除的偶数的平均值)
题目描述
给你一个由正整数组成的整数数组 nums,返回其中可被 3 整除的所有偶数的平均值(向下取整)。
若不存在符合条件的数返回 0。
示例 1
`nums = [1,3,6,10,12,15]` → `9`(6 和 12,(6+12)/2=9)。
示例 2
`nums = [1,2,4,7,10]` → `0`。
提示:
1 <= nums.length <= 1000,1 <= nums[i] <= 1000
解题思路
解法一:单遍筛选(主提交版本)
- 可被 3 整除的偶数即能被 6 整除的数。累加并计数,返回 sum/count(整除即向下取整)。
解法二:显式条件(对照) - 用
x % 2 == 0 && x % 3 == 0的写法,语义等价。
复杂度: - 时间复杂度:O(n)。
- 空间复杂度:O(1)。
代码实现
java
public class Solution {
// 解法一:能被 6 整除(主提交版本)
public int averageValue(int[] nums) {
int sum = 0, cnt = 0;
for (int x : nums) {
if (x % 6 == 0) {
sum += x;
cnt++;
}
}
return cnt == 0 ? 0 : sum / cnt;
}
// 解法二:显式条件(对照)
public int averageValue2(int[] nums) {
int sum = 0, cnt = 0;
for (int x : nums) {
if (x % 2 == 0 && x % 3 == 0) {
sum += x;
cnt++;
}
}
return cnt == 0 ? 0 : sum / cnt;
}
public static void main(String[] args) {
Solution sol = new Solution();
check(sol.averageValue(new int[] {1, 3, 6, 10, 12, 15}) == 9, "2455 ex1");
check(sol.averageValue2(new int[] {1, 3, 6, 10, 12, 15}) == 9, "2455 ex12");
check(sol.averageValue(new int[] {1, 2, 4, 7, 10}) == 0, "2455 ex2");
check(sol.averageValue2(new int[] {1, 2, 4, 7, 10}) == 0, "2455 ex22");
// 随机对拍
java.util.Random rnd = new java.util.Random(191);
for (int t = 0; t < 500; t++) {
int n = 1 + rnd.nextInt(30);
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = 1 + rnd.nextInt(1000);
}
int x = sol.averageValue(a), y = sol.averageValue2(a);
if (x != y) {
throw new AssertionError("2455 mismatch t=" + t + " x=" + x + " y=" + y);
}
}
System.out.println("2455 全部 PASS");
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(n)。
- 空间复杂度:O(1)。
2456. Most Popular Video Creator(最流行的视频创作者)
题目描述
给你字符串数组 creators、ids 和整数数组 views(长度均为 n)。第 i 个视频的创作者为
creators[i]、id 为 ids[i]、播放量为 views[i]。创作者的流行度 = 其所有视频播放量总和。
找出流行度最高 的创作者(多个都返回)及其播放量最高的视频 id;若某创作者存在多个播放量最高
的视频,返回字典序最小的 id。返回二维字符串数组。
示例 1
`creators=["alice","bob","alice","chris"], ids=["one","two","three","four"], views=[5,10,5,4]`
→ `[["alice","one"],["bob","two"]]`。
示例 2
`creators=["alice","alice","alice"], ids=["a","b","c"], views=[1,2,2]` → `[["alice","b"]]`。
提示:
1 <= n <= 10^5,创作者/视频id长度 1~5,小写字母
解题思路
解法一:哈希聚合(主提交版本)
- 遍历一次:每创作者维护 总播放量、最佳视频(播放量, id 字典序)(用 maxViews 与 bestId)。
- 扫描全部创作者取最大流行度,收集流行度等于最大值的创作者。
解法二:Map + 排序(对照) - 聚合后对创作者排序,再输出,思路不同。
复杂度: - 时间复杂度:O(n)。
- 空间复杂度:O(n)。
代码实现
java
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Solution {
private static class Info {
long total;
int maxView = -1;
String bestId = "";
}
// 解法一:哈希聚合(主提交版本)
public List<List<String>> mostPopularCreator(String[] creators, String[] ids, int[] views) {
Map<String, Info> map = new HashMap<>();
for (int i = 0; i < creators.length; i++) {
Info inf = map.computeIfAbsent(creators[i], k -> new Info());
inf.total += views[i];
if (views[i] > inf.maxView || (views[i] == inf.maxView && ids[i].compareTo(inf.bestId) < 0)) {
inf.maxView = views[i];
inf.bestId = ids[i];
}
}
long maxTotal = 0;
for (Info inf : map.values()) {
maxTotal = Math.max(maxTotal, inf.total);
}
List<List<String>> ans = new ArrayList<>();
for (Map.Entry<String, Info> e : map.entrySet()) {
if (e.getValue().total == maxTotal) {
List<String> row = new ArrayList<>();
row.add(e.getKey());
row.add(e.getValue().bestId);
ans.add(row);
}
}
return ans;
}
// 解法二:双 Map 聚合 + 排序(对照)
public List<List<String>> mostPopularCreator2(String[] creators, String[] ids, int[] views) {
Map<String, Long> total = new HashMap<>();
Map<String, String> best = new HashMap<>();
Map<String, Integer> maxV = new HashMap<>();
for (int i = 0; i < creators.length; i++) {
total.put(creators[i], total.getOrDefault(creators[i], 0L) + views[i]);
int oldMax = maxV.getOrDefault(creators[i], -1);
if (views[i] > oldMax
|| (views[i] == oldMax && ids[i].compareTo(best.get(creators[i])) < 0)) {
maxV.put(creators[i], views[i]);
best.put(creators[i], ids[i]);
}
}
long maxTotal = 0;
for (long v : total.values()) {
maxTotal = Math.max(maxTotal, v);
}
List<List<String>> ans = new ArrayList<>();
for (Map.Entry<String, Long> e : total.entrySet()) {
if (e.getValue() == maxTotal) {
List<String> row = new ArrayList<>();
row.add(e.getKey());
row.add(best.get(e.getKey()));
ans.add(row);
}
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
List<List<String>> r1 = sol.mostPopularCreator(
new String[] {"alice", "bob", "alice", "chris"},
new String[] {"one", "two", "three", "four"},
new int[] {5, 10, 5, 4});
check(contains(r1, "alice", "one") && contains(r1, "bob", "two") && r1.size() == 2,
"2456 ex1 " + r1);
List<List<String>> r1b = sol.mostPopularCreator2(
new String[] {"alice", "bob", "alice", "chris"},
new String[] {"one", "two", "three", "four"},
new int[] {5, 10, 5, 4});
check(contains(r1b, "alice", "one") && contains(r1b, "bob", "two") && r1b.size() == 2,
"2456 ex1b " + r1b);
List<List<String>> r2 = sol.mostPopularCreator(
new String[] {"alice", "alice", "alice"},
new String[] {"a", "b", "c"},
new int[] {1, 2, 2});
check(r2.size() == 1 && r2.get(0).get(1).equals("b"), "2456 ex2 " + r2);
// 附加:多个创作者并列
List<List<String>> r3 = sol.mostPopularCreator(
new String[] {"x", "y"}, new String[] {"xa", "ya"}, new int[] {10, 10});
check(r3.size() == 2, "2456 并列 " + r3);
System.out.println("2456 全部 PASS");
}
private static boolean contains(List<List<String>> rows, String c, String id) {
for (List<String> row : rows) {
if (row.get(0).equals(c) && row.get(1).equals(id)) {
return true;
}
}
return false;
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(n)。
- 空间复杂度:O(n)。
2457. Minimum Addition to Make Integer Beautiful(美丽整数的最小增量)
题目描述
给你两个正整数 n 和 target。如果整数每一位上的数字相加 ≤ target,则称其为美丽整数。
返回使 n + x 是美丽整数的最小非负整数 x(保证有解)。
示例 1
`n=16, target=6` → `4`(16→20,数位和 2)。
示例 2
`n=467, target=6` → `33`(467→500,数位和 5)。
示例 3
`n=1, target=1` → `0`。
提示:
1 <= n <= 10^12,1 <= target <= 150
解题思路
解法一:进位贪心(主提交版本)
- 若 n 的数位和 ≤ target 则 x=0。
- 否则从低位开始:把尾部若干位「进位清零」成 0(如 467 → 470 → 500),每进位一次数位和减小;
直到数位和 ≤ target。答案 x = 新数 - 原数。
解法二:逐次加 1 检查(对照,仅小值) - 直接用 BigInteger 语义:对 n 逐次加 1 判断数位和,小规模对拍。
复杂度: - 时间复杂度:O(位数^2)(进位次数 ≤ 位数)。
- 空间复杂度:O(1)。
代码实现
java
public class Solution {
// 解法一:进位贪心(主提交版本)
public long makeIntegerBeautiful(long n, int target) {
long x = 0;
long base = 1; // 当前处理的位权
while (digitSum(n) > target) {
// 把 n 的最低非零位及以上变成 0(即进位)
long rem = n % (base * 10);
long add = (base * 10 - rem) % (base * 10);
x += add;
n += add;
base *= 10;
}
return x;
}
// 解法二:逐次 +1(对照,小规模)
public long makeIntegerBeautifulBrute(long n, int target) {
long k = n, x = 0;
while (digitSum(k) > target) {
k++;
x++;
}
return x;
}
private int digitSum(long v) {
int s = 0;
while (v > 0) {
s += v % 10;
v /= 10;
}
return s;
}
public static void main(String[] args) {
Solution sol = new Solution();
check(sol.makeIntegerBeautiful(16, 6) == 4, "2457 ex1");
check(sol.makeIntegerBeautifulBrute(16, 6) == 4, "2457 ex1b");
check(sol.makeIntegerBeautiful(467, 6) == 33, "2457 ex2");
check(sol.makeIntegerBeautifulBrute(467, 6) == 33, "2457 ex2b");
check(sol.makeIntegerBeautiful(1, 1) == 0, "2457 ex3");
// 随机对拍(小规模)
java.util.Random rnd = new java.util.Random(193);
for (int t = 0; t < 500; t++) {
long n = 1 + rnd.nextInt(100000);
int tg = 1 + rnd.nextInt(30);
long x = sol.makeIntegerBeautiful(n, tg), y = sol.makeIntegerBeautifulBrute(n, tg);
if (x != y) {
throw new AssertionError("2457 mismatch n=" + n + " tg=" + tg + " x=" + x + " y=" + y);
}
}
System.out.println("2457 全部 PASS");
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(位数^2)(进位次数 ≤ 位数)。
- 空间复杂度:O(1)。
2458. Height of Binary Tree After Subtree Removal Queries(移除子树后的二叉树高度)
题目描述
给你一棵二叉树的根节点 root(n 个节点,值 1~n 互不相同)。另给数组 queries(m 个独立查询)。
第 i 个查询:移除以 queries[i] 的值为根节点的子树(不删根),返回移除后树的高度。
返回长度 m 的数组 answer。
示例 1
`root=[1,3,4,2,null,6,5,null,null,null,null,null,7], queries=[4]` → `[2]`。
示例 2
`root=[5,8,9,2,1,3,7,4,6], queries=[3,2,4,8]` → `[3,2,3,2]`。
提示:
2 <= n <= 10^5,1 <= m <= min(n, 10^4),queries[i] != root.val
解题思路
解法一:两次 DFS 预处理(主提交版本)
- 第一次 DFS 计算每个节点子树的深度
depth[v](以 v 为根的子树高度)。 - 第二次 DFS 传递「兄弟路径的剩余高度」:遍历时维护一个
pathMax= 从根到当前节点路径上,
除去当前节点子树外的最长路径长度。对每个节点,其祖先的最大贡献 = 祖先的兄弟子树深度 + 祖先深度,
取路径上的最大值存到ans[v]。 - 实现:
dfs2(u, depthSum),其中 depthSum = 根到 u 的路径上,「不经过 u 子树」的最远叶子距离
(即各层兄弟树高 + 层号 的 max,以及 u 自身深度层号)。ans[u] = depthSum。
转移:对子节点 c,depthSum' = max(depthSum, u 的深度层 + 兄弟子树高度)。 - 复杂度 O(n + m)。
解法二:逐查询暴力重建(对照,小规模) - 对每个查询标记移除节点后 BFS 求高度。
复杂度: - 时间复杂度:O(n + m)。
- 空间复杂度:O(n)。
代码实现
java
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Solution {
private Map<Integer, Integer> heightOf; // 节点值 -> 子树高度
private Map<Integer, Integer> ans;
// 解法一:两遍 DFS(主提交版本)
public int[] treeQueries(TreeNode root, int[] queries) {
heightOf = new HashMap<>();
ans = new HashMap<>();
height(root); // 第一遍:子树高度
dfs2(root, 0, 0); // 第二遍:每个节点被移除后的高度 = 除去该子树的最长路径
int[] res = new int[queries.length];
for (int i = 0; i < queries.length; i++) {
res[i] = ans.getOrDefault(queries[i], 0);
}
return res;
}
private int height(TreeNode u) {
if (u == null) {
return 0;
}
int h = 1 + Math.max(height(u.left), height(u.right));
heightOf.put(u.val, h);
return h;
}
private void dfs2(TreeNode u, int depth, int restMax) {
ans.put(u.val, restMax);
int leftH = u.left == null ? 0 : heightOf.get(u.left.val);
int rightH = u.right == null ? 0 : heightOf.get(u.right.val);
if (u.left != null) {
// 去掉左子树后,能到达的最深层 = max(restMax, depth + 右子树高度)
dfs2(u.left, depth + 1, Math.max(restMax, depth + rightH));
}
if (u.right != null) {
dfs2(u.right, depth + 1, Math.max(restMax, depth + leftH));
}
}
// 解法二:暴力重建(对照,小规模)
public int[] treeQueriesBrute(TreeNode root, int[] queries) {
Map<Integer, TreeNode> pos = new HashMap<>();
collect(root, pos);
int[] res = new int[queries.length];
for (int i = 0; i < queries.length; i++) {
TreeNode removed = pos.get(queries[i]);
if (removed == null) {
res[i] = height(root) - 1;
} else {
// 标记移除:把 removed 子树与父节点断开(用值 0 标记不可达)
res[i] = heightExcluding(root, removed) - 1;
}
}
return res;
}
private void collect(TreeNode u, Map<Integer, TreeNode> pos) {
if (u == null) {
return;
}
pos.put(u.val, u);
collect(u.left, pos);
collect(u.right, pos);
}
private int heightExcluding(TreeNode root, TreeNode removed) {
return hx(root, removed);
}
private int hx(TreeNode u, TreeNode removed) {
if (u == null || u == removed) {
return 0;
}
return 1 + Math.max(hx(u.left, removed), hx(u.right, removed));
}
public static void main(String[] args) {
Solution sol = new Solution();
TreeNode r1 = build(new Integer[] {1, 3, 4, 2, null, 6, 5, null, null, null, null, null, 7});
check(Arrays.toString(sol.treeQueries(r1, new int[] {4})).equals("[2]"), "2458 ex1");
check(Arrays.toString(sol.treeQueriesBrute(copy(r1), new int[] {4})).equals("[2]"), "2458 ex1b");
TreeNode r2 = build(new Integer[] {5, 8, 9, 2, 1, 3, 7, 4, 6});
check(Arrays.toString(sol.treeQueries(r2, new int[] {3, 2, 4, 8})).equals("[3, 2, 3, 2]"), "2458 ex2");
check(Arrays.toString(sol.treeQueriesBrute(copy(r2), new int[] {3, 2, 4, 8})).equals("[3, 2, 3, 2]"),
"2458 ex2b");
// 随机小规模对拍:直接按 parent 数组构建任意二叉树
java.util.Random rnd = new java.util.Random(197);
for (int t = 0; t < 60; t++) {
int n = 2 + rnd.nextInt(12);
TreeNode rt = randTreeByParent(rnd, n); // 值 1..n
List<Integer> vals = new ArrayList<>();
collectVals(rt, vals);
int qn = 1 + rnd.nextInt(Math.min(vals.size(), 6));
int[] qs = new int[qn];
int rootVal = rt.val;
for (int i = 0; i < qn; i++) {
int v;
do {
v = vals.get(rnd.nextInt(vals.size()));
} while (v == rootVal);
qs[i] = v;
}
int[] x = sol.treeQueries(copy(rt), qs);
int[] y = sol.treeQueriesBrute(copy(rt), qs);
if (!java.util.Arrays.equals(x, y)) {
throw new AssertionError("2458 mismatch t=" + t + " x=" + java.util.Arrays.toString(x)
+ " y=" + java.util.Arrays.toString(y) + " qs=" + java.util.Arrays.toString(qs));
}
}
System.out.println("2458 全部 PASS");
}
// 按 parent 数组构建:节点 i(值 i+1)的父为 rnd(i),随机放左/右
private static TreeNode randTreeByParent(java.util.Random rnd, int n) {
TreeNode[] nodes = new TreeNode[n];
for (int i = 0; i < n; i++) {
nodes[i] = new TreeNode(i + 1);
}
for (int i = 1; i < n; i++) {
int p = rnd.nextInt(i);
if (rnd.nextBoolean()) {
nodes[p].left = nodes[i];
} else {
nodes[p].right = nodes[i];
}
}
return nodes[0];
}
private static void collectVals(TreeNode u, List<Integer> vals) {
if (u == null) {
return;
}
vals.add(u.val);
collectVals(u.left, vals);
collectVals(u.right, vals);
}
private static TreeNode copy(TreeNode root) {
if (root == null) {
return null;
}
TreeNode c = new TreeNode(root.val);
c.left = copy(root.left);
c.right = copy(root.right);
return c;
}
private static TreeNode build(Integer[] arr) {
if (arr.length == 0) {
return null;
}
TreeNode root = new TreeNode(arr[0]);
Deque<TreeNode> q = new ArrayDeque<>();
q.offer(root);
int i = 1;
while (i < arr.length) {
TreeNode u = q.poll();
if (u == null) {
i += 2;
continue;
}
if (i < arr.length && arr[i] != null) {
u.left = new TreeNode(arr[i]);
q.offer(u.left);
}
i++;
if (i < arr.length && arr[i] != null) {
u.right = new TreeNode(arr[i]);
q.offer(u.right);
}
i++;
}
return root;
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
// 二叉树节点
static class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
}
复杂度分析
- 时间复杂度:O(n + m)。
- 空间复杂度:O(n)。
2459. Sort Array by Moving Items to Empty Space(通过移动项目到空白区域来排序数组)
题目描述
给定一个大小为 n 的整数数组 nums,包含 0 到 n-1 每个元素恰好一次。元素 0 代表空白区域。
一次操作:将任何 项目移动到空白区域。若项目的编号升序且空格在数组开头或结尾,则认为 nums 已排序
(例如 n=4 时 [0,1,2,3] 或 [1,2,3,0])。返回排序 nums 所需的最小操作数。
示例 1
`nums = [4,2,0,3,1]` → `3`。
示例 2
`nums = [1,2,3,4,0]` → `0`。
示例 3
`nums = [1,0,2,4,3]` → `2`。
提示:
2 <= n <= 10^5,0 <= nums[i] < n,值唯一
解题思路
解法一:置换环统计(主提交版本)
- 任意排列可通过交换与 0 的位置置换排序。等价于在完全图中用「空位」作为交换中介。
- 标准做法:把目标状态视为把每个值 x 放到位置 x-1(升序
[1,2,...,n-1,0])的置换,
计算所有「错位环」的总代价:大小为 L 的环,若含 0 则代价 L-1,否则代价 L+1。 - 但注意终态有两种(0 在头或尾),取两者最小。更简单直接的官方思路:
计算(位置 0 即空位)置换环代价;目标状态两种都算。
解法二:模拟 BFS(对照,小规模) - BFS 从初始状态到任一合法终态,求最小步数。
复杂度: - 时间复杂度:O(n)。
- 空间复杂度:O(n)。
代码实现
java
import java.util.Arrays;
public class Solution {
// 解法一:置换环(主提交版本)
public int sortArray(int[] nums) {
// 目标 A: [0,1,2,...,n-1](0 在开头)
int costA = cycleCost(nums.clone(), false);
// 目标 B: [1,2,...,n-1,0](0 在末尾)
int costB = cycleCost(nums.clone(), true);
return Math.min(costA, costB);
}
// tailZero=true 表示 0 在末尾的终态(位置 i 放值 i+1 对 i<n-1,位置 n-1 放 0)
private int cycleCost(int[] nums, boolean tailZero) {
int n = nums.length;
boolean[] vis = new boolean[n];
// 建置换:pos[v] = v 当前位置,终态时 nums[i] == target[i]
int cost = 0;
for (int i = 0; i < n; i++) {
if (vis[i]) {
continue;
}
// 找出 i 所在环长度
int cur = i, len = 0;
boolean hasZero = false;
while (!vis[cur]) {
vis[cur] = true;
len++;
if (nums[cur] == 0) {
hasZero = true;
}
// 走到目标位置
int targetPos;
if (!tailZero) {
targetPos = nums[cur]; // 值为 x 应去位置 x
} else {
targetPos = (nums[cur] == 0) ? n - 1 : nums[cur] - 1;
}
cur = targetPos;
}
if (len > 1) {
cost += hasZero ? len - 1 : len + 1;
}
}
return cost;
}
// 解法二:BFS(对照,小规模)
public int sortArrayBrute(int[] nums) {
int n = nums.length;
String start = Arrays.toString(nums).replaceAll("[\\[\\], ]", "");
String t1 = "0" + seq(n) + ""; // 0 开头
String t2 = seq(n) + "0"; // 0 结尾(seq 生成 1..n-1)
// BFS
java.util.ArrayDeque<String> q = new java.util.ArrayDeque<>();
java.util.Map<String, Integer> dist = new java.util.HashMap<>();
dist.put(start, 0);
q.add(start);
while (!q.isEmpty()) {
String s = q.poll();
if (s.equals(t1) || s.equals(t2)) {
return dist.get(s);
}
int z = s.indexOf('0');
for (int i = 0; i < n; i++) {
if (i == z) {
continue;
}
char[] c = s.toCharArray();
char tmp = c[z];
c[z] = c[i];
c[i] = tmp;
String ns = new String(c);
if (!dist.containsKey(ns)) {
dist.put(ns, dist.get(s) + 1);
q.add(ns);
}
}
}
return -1;
}
private String seq(int n) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i < n; i++) {
sb.append(i);
}
return sb.toString();
}
public static void main(String[] args) {
Solution sol = new Solution();
check(sol.sortArray(new int[] {4, 2, 0, 3, 1}) == 3, "2459 ex1");
check(sol.sortArrayBrute(new int[] {4, 2, 0, 3, 1}) == 3, "2459 ex1b");
check(sol.sortArray(new int[] {1, 2, 3, 4, 0}) == 0, "2459 ex2");
check(sol.sortArrayBrute(new int[] {1, 2, 3, 4, 0}) == 0, "2459 ex2b");
check(sol.sortArray(new int[] {1, 0, 2, 4, 3}) == 2, "2459 ex3");
check(sol.sortArrayBrute(new int[] {1, 0, 2, 4, 3}) == 2, "2459 ex3b");
// 随机小规模对拍
java.util.Random rnd = new java.util.Random(199);
for (int t = 0; t < 200; t++) {
int n = 2 + rnd.nextInt(6);
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = i;
}
// 随机洗牌
for (int i = n - 1; i > 0; i--) {
int j = rnd.nextInt(i + 1);
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
int x = sol.sortArray(a.clone()), y = sol.sortArrayBrute(a.clone());
if (x != y) {
throw new AssertionError("2459 mismatch t=" + t + " a=" + Arrays.toString(a) + " x=" + x + " y=" + y);
}
}
System.out.println("2459 全部 PASS");
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(n)。
- 空间复杂度:O(n)。
2460. Apply Operations to an Array(对数组执行操作)
题目描述
给你一个下标从 0 开始的数组 nums(大小 n,非负整数)。执行 n-1 步操作(第 i 步,i 从 0 计数):
- 若
nums[i] == nums[i+1],则nums[i]变为原来的 2 倍、nums[i+1]变为 0;否则跳过。
操作应当依次有序执行。执行完后把所有 0 移到数组末尾,返回结果数组。
示例 1
`nums = [1,2,2,1,1,0]` → `[1,4,2,0,0,0]`。
示例 2
`nums = [0,1]` → `[1,0]`。
提示:
2 <= nums.length <= 2000,0 <= nums[i] <= 1000
解题思路
解法一:就地模拟(主提交版本)
- 第一遍按规则就地修改(注意修改后 numsi+1 变 0 不再参与后续判断,但 i 会继续走,
第 i+1 步检查的是新 numsi+1 与 numsi+2,符合题意)。 - 第二遍双指针把非零数前移,末尾补 0。
解法二:显式零数组(对照) - 用独立 list 收集非零元素再拼接,逻辑直观。
复杂度: - 时间复杂度:O(n)。
- 空间复杂度:O(1)。
代码实现
java
import java.util.Arrays;
public class Solution {
// 解法一:就地模拟(主提交版本)
public int[] applyOperations(int[] nums) {
int n = nums.length;
for (int i = 0; i < n - 1; i++) {
if (nums[i] == nums[i + 1]) {
nums[i] *= 2;
nums[i + 1] = 0;
}
}
int[] ans = new int[n];
int idx = 0;
for (int x : nums) {
if (x != 0) {
ans[idx++] = x;
}
}
return ans;
}
// 解法二:双指针前移(对照)
public int[] applyOperations2(int[] nums) {
int n = nums.length;
for (int i = 0; i < n - 1; i++) {
if (nums[i] == nums[i + 1]) {
nums[i] *= 2;
nums[i + 1] = 0;
}
}
int p = 0;
for (int i = 0; i < n; i++) {
if (nums[i] != 0) {
nums[p++] = nums[i];
}
}
while (p < n) {
nums[p++] = 0;
}
return nums;
}
public static void main(String[] args) {
Solution sol = new Solution();
check(Arrays.toString(sol.applyOperations(new int[] {1, 2, 2, 1, 1, 0})).equals("[1, 4, 2, 0, 0, 0]"),
"2460 ex1");
check(Arrays.toString(sol.applyOperations2(new int[] {1, 2, 2, 1, 1, 0})).equals("[1, 4, 2, 0, 0, 0]"),
"2460 ex1b");
check(Arrays.toString(sol.applyOperations(new int[] {0, 1})).equals("[1, 0]"), "2460 ex2");
check(Arrays.toString(sol.applyOperations2(new int[] {0, 1})).equals("[1, 0]"), "2460 ex2b");
// 随机对拍
java.util.Random rnd = new java.util.Random(211);
for (int t = 0; t < 500; t++) {
int n = 2 + rnd.nextInt(20);
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = rnd.nextInt(5);
}
int[] x = sol.applyOperations(a.clone()), y = sol.applyOperations2(a.clone());
if (!Arrays.equals(x, y)) {
throw new AssertionError("2460 mismatch t=" + t + " x=" + Arrays.toString(x)
+ " y=" + Arrays.toString(y));
}
}
System.out.println("2460 全部 PASS");
}
private static void check(boolean ok, String msg) {
if (!ok) {
throw new AssertionError(msg);
}
System.out.println("PASS " + msg);
}
}
复杂度分析
- 时间复杂度:O(n)。
- 空间复杂度:O(1)。