LeetCode 2761-2780. 题解合集
文章目录
- [LeetCode 2761-2780. 题解合集](#LeetCode 2761-2780. 题解合集)
-
- [2761. Prime Pairs With Target Sum(和等于目标值的质数对)](#2761. Prime Pairs With Target Sum(和等于目标值的质数对))
- [2762. Continuous Subarrays(不间断子数组)](#2762. Continuous Subarrays(不间断子数组))
- [2763. Sum of Imbalance Numbers of All Subarrays(所有子数组中不平衡数字之和)](#2763. Sum of Imbalance Numbers of All Subarrays(所有子数组中不平衡数字之和))
- [2764. Is Array a Preorder of Some Binary Tree(数组是否表示某二叉树的前序遍历)](#2764. Is Array a Preorder of Some Binary Tree(数组是否表示某二叉树的前序遍历))
- [2765. Longest Alternating Subarray(最长交替子数组)](#2765. Longest Alternating Subarray(最长交替子数组))
- [2766. Relocate Marbles(重新放置石块)](#2766. Relocate Marbles(重新放置石块))
- [2767. Partition String Into Minimum Beautiful Substrings(将字符串分割为最少的美丽子字符串)](#2767. Partition String Into Minimum Beautiful Substrings(将字符串分割为最少的美丽子字符串))
- [2768. Number of Black Blocks(黑格子的数目)](#2768. Number of Black Blocks(黑格子的数目))
- [2769. Find the Maximum Achievable Number(找出最大的可达成数字)](#2769. Find the Maximum Achievable Number(找出最大的可达成数字))
- [2770. Maximum Number of Jumps to Reach the Last Index(达到末尾下标所需的最大跳跃次数)](#2770. Maximum Number of Jumps to Reach the Last Index(达到末尾下标所需的最大跳跃次数))
- [2771. Longest Non-decreasing Subarray From Two Arrays(构造最长非递减子数组)](#2771. Longest Non-decreasing Subarray From Two Arrays(构造最长非递减子数组))
- [2772. Apply Operations to Make All Array Elements Equal to Zero(使数组中的所有元素都等于零)](#2772. Apply Operations to Make All Array Elements Equal to Zero(使数组中的所有元素都等于零))
- [2773. Height of Special Binary Tree(特殊二叉树的高度)](#2773. Height of Special Binary Tree(特殊二叉树的高度))
- [2774. Array Upper Bound(数组的上界 🔒)](#2774. Array Upper Bound(数组的上界 🔒))
- [2775. Undefined to Null(将 undefined 转为 null 🔒)](#2775. Undefined to Null(将 undefined 转为 null 🔒))
- [2776. Convert Callback Based Function to Promise Based Function(转换回调函数为 Promise 函数 🔒)](#2776. Convert Callback Based Function to Promise Based Function(转换回调函数为 Promise 函数 🔒))
- [2777. Date Range Generator(日期范围生成器 🔒)](#2777. Date Range Generator(日期范围生成器 🔒))
- [2778. Sum of Squares of Special Elements(特殊元素平方和)](#2778. Sum of Squares of Special Elements(特殊元素平方和))
- [2779. Maximum Beauty of an Array After Applying Operation(数组的最大美丽值)](#2779. Maximum Beauty of an Array After Applying Operation(数组的最大美丽值))
- [2780. Minimum Index of a Valid Split(合法分割的最小下标)](#2780. Minimum Index of a Valid Split(合法分割的最小下标))
2761. Prime Pairs With Target Sum(和等于目标值的质数对)
题目描述
给你一个整数 n。如果两个整数 x 和 y 满足下述条件,则认为二者形成一个质数对:
1. 1 <= x <= y <= n;
2. x + y == n;
x和y都是质数。
请你以二维有序列表的形式返回符合要求的所有 `xi`, `yi`,列表需要按 xi 的
非递减顺序排序。如果不存在符合要求的质数对,则返回空数组。
示例 1
n = 10 -> [[3,7],[5,5]]
示例 2
n = 2 -> []
提示:
- 1 <=
n<= 1e6。
解题思路
解法一:埃氏筛 + 双指针,主提交版本
先线性/埃氏筛出 1...n 的所有质数标记。然后对质数列表用双指针:
左指针从最小质数开始、右指针从最大质数开始,若 pL + pR == n 且 pL <= pR
则是一对答案(左移右移);若小于 n 则 pL 右移,大于 n 则 pR 左移。
由于质数列表天然递增,双指针自然按 xi 非递减输出。
解法二:素数判定逐个枚举,对照
对每个 x 从 2 到 n/2,判断 x 与 n-x 是否均为
质数(朴素试除,小数据可用)。
时间复杂度:O(n log log n);空间复杂度:O(n)。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
public List<List<Integer>> findPrimePairs(int n) {
// 埃氏筛
boolean[] notPrime = new boolean[n + 1];
notPrime[0] = notPrime[1] = true;
for (int i = 2; i * i <= n; i++) {
if (!notPrime[i]) {
for (int j = i * i; j <= n; j += i) notPrime[j] = true;
}
}
List<Integer> primes = new ArrayList<Integer>();
for (int i = 2; i <= n; i++) {
if (!notPrime[i]) primes.add(i);
}
List<List<Integer>> ans = new ArrayList<List<Integer>>();
int l = 0, r = primes.size() - 1;
while (l <= r) {
int sum = primes.get(l) + primes.get(r);
if (sum == n) {
ans.add(Arrays.asList(primes.get(l), primes.get(r)));
l++;
r--;
} else if (sum < n) {
l++;
} else {
r--;
}
}
return ans;
}
public List<List<Integer>> findPrimePairsBrute(int n) {
List<List<Integer>> ans = new ArrayList<List<Integer>>();
for (int x = 2; x <= n / 2; x++) {
int y = n - x;
if (isPrime(x) && isPrime(y) && x <= y) {
ans.add(Arrays.asList(x, y));
}
}
return ans;
}
private boolean isPrime(int v) {
if (v < 2) return false;
for (int i = 2; i * i <= v; i++) {
if (v % i == 0) return false;
}
return true;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.findPrimePairs(10).equals(Arrays.asList(Arrays.asList(3, 7), Arrays.asList(5, 5))) ? "PASS" : "FAIL");
System.out.println(sol.findPrimePairs(2).equals(new ArrayList<List<Integer>>()) ? "PASS" : "FAIL");
System.out.println(sol.findPrimePairs(4).equals(Arrays.asList(Arrays.asList(2, 2))) ? "PASS" : "FAIL");
// 随机对拍
java.util.Random rnd = new java.util.Random(2761);
boolean ok = true;
for (int t = 0; t < 500; t++) {
int n = 2 + rnd.nextInt(100);
List<List<Integer>> r1 = sol.findPrimePairs(n);
List<List<Integer>> r2 = sol.findPrimePairsBrute(n);
if (!r1.equals(r2)) {
ok = false;
System.out.println("Mismatch n=" + n + " sieve=" + r1 + " brute=" + r2);
break;
}
}
System.out.println(ok ? "2761 全部 PASS" : "2761 存在不一致");
}
}
复杂度分析
时间复杂度:O(n log log n);空间复杂度:O(n)。
2762. Continuous Subarrays(不间断子数组)
题目描述
给你一个下标从 0 开始的整数数组 nums。nums 的一个子数组如果满足以下条件,
那么它是不间断的:对于子数组内任意下标对 (i1, i2),都有 |nums[i1] - nums[i2]| <= 2。
请你返回不间断子数组的总数目。
示例 1
nums = [5,4,2,4] -> 8
大小为 1 的有 4 个,大小为 2 的有 3 个,大小为 3 的有 1 个,共 8 个。
示例 2
nums = [1,2,3] -> 6(1+2+3)
提示:
- 1 <=
nums.length<= 1e5;1 <=nums[i]<= 1e9。
解题思路
解法一:滑动窗口 + 单调队列,主提交版本
条件「任意两元素差 <= 2」等价于「窗口内最大值 - 最小值 <= 2」。
维护窗口 l, r,用两个单调队列(一个存最小值候选、一个存最大值候选)
维护窗口极值。当 max - min > 2 时收缩左边界,直到满足。
以 r 结尾的不间断子数组个数 = r - l + 1(每个左端点 l' in l, r 都可行,
因为收缩到合法后 l 左侧再收缩也只会更小)。
累加即答案。注意用 long 避免溢出。
解法二:暴力枚举,对照
枚举所有子数组,检查是否任意两元素差 <= 2
(即取 min/max 差 <= 2),仅用于小规模对拍。
时间复杂度:O(n);空间复杂度:O(n)。
代码实现
java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Solution {
public long continuousSubarrays(int[] nums) {
int n = nums.length;
long ans = 0;
int l = 0;
// 单调队列存下标:minQ 头部最小,maxQ 头部最大
java.util.ArrayDeque<Integer> minQ = new java.util.ArrayDeque<Integer>();
java.util.ArrayDeque<Integer> maxQ = new java.util.ArrayDeque<Integer>();
for (int r = 0; r < n; r++) {
// 维护最小队列
while (!minQ.isEmpty() && nums[minQ.peekLast()] >= nums[r]) minQ.pollLast();
minQ.offerLast(r);
// 维护最大队列
while (!maxQ.isEmpty() && nums[maxQ.peekLast()] <= nums[r]) maxQ.pollLast();
maxQ.offerLast(r);
// 收缩左边界直到满足 |max-min| <= 2
while (nums[maxQ.peekFirst()] - nums[minQ.peekFirst()] > 2) {
l++;
if (minQ.peekFirst() < l) minQ.pollFirst();
if (maxQ.peekFirst() < l) maxQ.pollFirst();
}
ans += (long) (r - l + 1);
}
return ans;
}
public long continuousSubarraysBrute(int[] nums) {
int n = nums.length;
long ans = 0;
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 (mx - mn <= 2) ans++;
else break; // 单调不再可能满足
}
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.continuousSubarrays(new int[]{5, 4, 2, 4}) == 8 ? "PASS" : "FAIL");
System.out.println(sol.continuousSubarrays(new int[]{1, 2, 3}) == 6 ? "PASS" : "FAIL");
System.out.println(sol.continuousSubarrays(new int[]{1}) == 1 ? "PASS" : "FAIL");
// 随机对拍
java.util.Random rnd = new java.util.Random(2762);
boolean ok = true;
for (int t = 0; t < 3000; t++) {
int n = 1 + rnd.nextInt(12);
int[] nums = new int[n];
for (int i = 0; i < n; i++) nums[i] = 1 + rnd.nextInt(10);
long r1 = sol.continuousSubarrays(nums);
long r2 = sol.continuousSubarraysBrute(nums);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch nums=" + java.util.Arrays.toString(nums) + " main=" + r1 + " brute=" + r2);
break;
}
}
System.out.println(ok ? "2762 全部 PASS" : "2762 存在不一致");
}
}
复杂度分析
时间复杂度:O(n);空间复杂度:O(n)。
2763. Sum of Imbalance Numbers of All Subarrays(所有子数组中不平衡数字之和)
题目描述
一个长度为 n 下标从 0 开始的整数数组 arr 的不平衡数字定义为:在 sarr = sorted(arr)
数组中,满足 sarr[i+1] - sarr[i] > 1 的下标 i 的数目(0 <= i < n-1)。
给你一个下标从 0 开始的整数数组 nums,请你返回它所有子数组的不平衡数字之和。
示例 1
nums = [2,3,1,4] -> 3
非 0 不平衡数字的子数组:[3,1]=1、[3,1,4]=1、[1,4]=1。
示例 2
nums = [1,3,3,3,5] -> 8
提示:
- 1 <=
nums.length<= 1000;1 <=nums[i]<=nums.length。
解题思路
解法一:连通块递推 O(n^2),主提交版本
对每个起点 i,向右扩展终点 j,增量维护当前子数组的不平衡数字。
设去重排序后序列中「连续值段」(值相差 1 的相邻元素连成一段)的段数为 b,
则排序序列中相邻差 > 1 的位置数恰好为 b - 1,即 im = b - 1。
新加入值 x(首次出现)时更新 b:
- x-1 与 x+1 均已出现:x 把左右两段连成一段,b--;
- 仅一边出现:段数不变;
- 两边都没出现:x 自成一新段,b++。
x 重复出现时不改变 b。每次把 im = b - 1 累加入答案。
解法二:直接排序暴力,对照
对每个子数组复制、排序、扫一遍算差值 >1 的个数。
时间复杂度:O(n^2);空间复杂度:O(n)。
代码实现
java
import java.util.HashSet;
import java.util.Set;
public class Solution {
public int sumImbalanceNumbers(int[] nums) {
int n = nums.length;
int ans = 0;
for (int i = 0; i < n; i++) {
boolean[] seen = new boolean[n + 2]; // 值域 [1, n]
int blocks = 0; // 连通块(连续值段)数
for (int j = i; j < n; j++) {
int x = nums[j];
if (!seen[x]) {
seen[x] = true;
boolean hasL = x > 1 && seen[x - 1];
boolean hasR = seen[x + 1];
if (hasL && hasR) blocks--; // 连接左右两段
else if (!hasL && !hasR) blocks++; // 自成一新段
// 仅一边出现:段数不变
}
// 不平衡数 = 段数 - 1(段间相邻差 > 1)
ans += (blocks - 1);
}
}
return ans;
}
public int sumImbalanceNumbersBrute(int[] nums) {
int n = nums.length;
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
int len = j - i + 1;
int[] arr = new int[len];
for (int k = i; k <= j; k++) arr[k - i] = nums[k];
java.util.Arrays.sort(arr);
int im = 0;
for (int k = 0; k < len - 1; k++) {
if (arr[k + 1] - arr[k] > 1) im++;
}
ans += im;
}
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.sumImbalanceNumbers(new int[]{2, 3, 1, 4}) == 3 ? "PASS" : "FAIL");
System.out.println(sol.sumImbalanceNumbers(new int[]{1, 3, 3, 3, 5}) == 8 ? "PASS" : "FAIL");
// 随机对拍(值域受限于 [1, n])
java.util.Random rnd = new java.util.Random(2763);
boolean ok = true;
for (int t = 0; t < 3000; t++) {
int n = 1 + rnd.nextInt(9);
int[] nums = new int[n];
for (int i = 0; i < n; i++) nums[i] = 1 + rnd.nextInt(n);
int r1 = sol.sumImbalanceNumbers(nums);
int r2 = sol.sumImbalanceNumbersBrute(nums);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch nums=" + java.util.Arrays.toString(nums) + " main=" + r1 + " brute=" + r2);
break;
}
}
System.out.println(ok ? "2763 全部 PASS" : "2763 存在不一致");
}
}
复杂度分析
时间复杂度:O(n^2);空间复杂度:O(n)。
2764. Is Array a Preorder of Some Binary Tree(数组是否表示某二叉树的前序遍历)
题目描述
给定一个以 0 为起始索引的整数二维数组 nodes,你的任务是确定给定的数组是否表示
某个二叉树的前序遍历。对于每个索引 i,nodes[i] = `id`, `parentId`,其中 id 是
索引 i 处节点的 id,parentId 是其在树中的父节点 id(根节点 parentId = -1)。
如果给定的数组表示某个树的前序遍历,则返回 true,否则返回 false。
示例 1
nodes = [[0,-1],[1,0],[2,0],[3,2],[4,2]] -> true
示例 2
nodes = [[0,-1],[1,0],[2,0],[3,1],[4,1]] -> false
提示:
- 1 <=
nodes.length<= 1e5;nodes[i].length == 2;0 <= id <= 1e5; - -1 <=
parentId<= 1e5;输入保证nodes可以组成二叉树。
解题思路
解法一:栈模拟前序遍历合法性,主提交版本
前序遍历的性质:访问顺序按「节点 + 左子树 + 右子树」。用一个栈维护「当前
沿着左链走下来的祖先链」:
- 根节点(parentId == -1)必须出现在下标 0;
- 对于后续每个节点 (id, pid):它的父节点必须是栈顶。若是,入栈(继续沿左链);
若否,则说明要回到某个祖先并转入其右子树------此时把栈顶元素出栈直到栈顶
== pid(出栈意味着该节点左子树遍历完毕,转入右兄弟)。 - 若把栈弹出完都没找到 pid,说明顺序非法,返回 false。
注意:同一节点的右子树前序顺序是连续的,出栈后 pid 必为栈顶。
用 Set 顺便校验节点 id 唯一。
解法二:构造树后模拟递归前序,对照
按 nodes 建树(哈希表 id -> 子节点列表),
从根做前序遍历,把遍历顺序与输入顺序逐项对比。小规模对拍。
时间复杂度:O(n);空间复杂度:O(n)。
代码实现
java
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Solution {
public boolean isPreorder(List<List<Integer>> nodes) {
int n = nodes.size();
// 根必须在下标 0
if (nodes.get(0).get(1) != -1) return false;
Set<Integer> seen = new HashSet<Integer>();
seen.add(nodes.get(0).get(0));
java.util.ArrayDeque<Integer> stack = new java.util.ArrayDeque<Integer>();
stack.push(nodes.get(0).get(0));
for (int i = 1; i < n; i++) {
int id = nodes.get(i).get(0);
int pid = nodes.get(i).get(1);
if (!seen.add(id)) return false; // id 重复非法
// 弹出左链直到栈顶是父节点
while (!stack.isEmpty() && stack.peek() != pid) {
stack.pop();
}
if (stack.isEmpty()) return false; // 父节点不在祖先链上
stack.push(id);
}
return true;
}
public boolean isPreorderBuild(List<List<Integer>> nodes) {
int n = nodes.size();
// 邻接表:父 -> 子列表(保持输入顺序 ------ 输入顺序就是前序顺序的候选)
java.util.Map<Integer, java.util.List<Integer>> children
= new java.util.HashMap<Integer, java.util.List<Integer>>();
int root = -1;
for (List<Integer> nd : nodes) {
int id = nd.get(0), pid = nd.get(1);
if (pid == -1) root = id;
else {
if (!children.containsKey(pid)) children.put(pid, new java.util.ArrayList<Integer>());
children.get(pid).add(id);
}
}
if (root != nodes.get(0).get(0)) return false;
final int[] idx = {0};
return preorderCompare(nodes, children, root, idx) && idx[0] == n;
}
private boolean preorderCompare(List<List<Integer>> nodes,
java.util.Map<Integer, java.util.List<Integer>> children,
int id, int[] idx) {
if (idx[0] >= nodes.size() || nodes.get(idx[0]).get(0) != id) return false;
idx[0]++;
java.util.List<Integer> ch = children.get(id);
if (ch == null) return true;
for (int c : ch) {
if (!preorderCompare(nodes, children, c, idx)) return false;
}
return true;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.isPreorder(java.util.Arrays.asList(
java.util.Arrays.asList(0, -1), java.util.Arrays.asList(1, 0),
java.util.Arrays.asList(2, 0), java.util.Arrays.asList(3, 2),
java.util.Arrays.asList(4, 2))) ? "PASS" : "FAIL");
System.out.println(!sol.isPreorder(java.util.Arrays.asList(
java.util.Arrays.asList(0, -1), java.util.Arrays.asList(1, 0),
java.util.Arrays.asList(2, 0), java.util.Arrays.asList(3, 1),
java.util.Arrays.asList(4, 1))) ? "PASS" : "FAIL");
// 单节点
System.out.println(sol.isPreorder(java.util.Arrays.asList(java.util.Arrays.asList(5, -1))) ? "PASS" : "FAIL");
// 随机对拍:随机生成一棵树,产生前序序列(真用例),再做随机打乱(大概率假)
java.util.Random rnd = new java.util.Random(2764);
boolean ok = true;
for (int t = 0; t < 2000; t++) {
int n = 1 + rnd.nextInt(10);
// 随机建树:id 0..n-1,父亲取 [0, i-1] 或 -1(根唯一)
java.util.List<java.util.List<Integer>> tree = buildRandomTree(n, rnd);
if (tree == null) continue;
// 生成前序列表,作为正例
java.util.List<java.util.List<Integer>> pre = preorderOf(tree);
boolean r1 = sol.isPreorder(pre);
boolean r2 = sol.isPreorderBuild(pre);
if (!r1 || !r2) {
ok = false;
System.out.println("Mismatch positive pre=" + pre + " main=" + r1 + " build=" + r2);
break;
}
// 随机交换两个节点的顺序铸成坏例(保证仍以 0,-1 开头则可能仍合法......直接随机打乱非根部分,判断双解法应一致)
java.util.List<java.util.List<Integer>> shuffled = new java.util.ArrayList<java.util.List<Integer>>(pre);
if (shuffled.size() > 2) {
int a = 1 + rnd.nextInt(shuffled.size() - 1);
int b = 1 + rnd.nextInt(shuffled.size() - 1);
java.util.Collections.swap(shuffled, a, b);
}
boolean s1 = sol.isPreorder(shuffled);
boolean s2 = sol.isPreorderBuild(shuffled);
if (s1 != s2) {
ok = false;
System.out.println("Mismatch shuffled=" + shuffled + " main=" + s1 + " build=" + s2);
break;
}
}
System.out.println(ok ? "2764 全部 PASS" : "2764 存在不一致");
}
private static java.util.List<java.util.List<Integer>> buildRandomTree(int n, java.util.Random rnd) {
int[] parent = new int[n];
java.util.Arrays.fill(parent, -2);
parent[0] = -1; // 根
java.util.List<java.util.List<Integer>> result
= new java.util.ArrayList<java.util.List<Integer>>();
result.add(java.util.Arrays.asList(0, -1));
for (int i = 1; i < n; i++) {
int p = rnd.nextInt(i); // 父亲一定有 id < i,保证树连通
parent[i] = p;
result.add(java.util.Arrays.asList(i, p));
}
// 合法性由「父亲 id < 子 id」保证可构成树(无环、除根外都有父亲)
return result;
}
private static java.util.List<java.util.List<Integer>> preorderOf(java.util.List<java.util.List<Integer>> nodes) {
int n = nodes.size();
java.util.Map<Integer, java.util.List<Integer>> children
= new java.util.HashMap<Integer, java.util.List<Integer>>();
int root = -1;
for (java.util.List<Integer> nd : nodes) {
int id = nd.get(0), pid = nd.get(1);
if (pid == -1) root = id;
else {
if (!children.containsKey(pid)) children.put(pid, new java.util.ArrayList<Integer>());
children.get(pid).add(id);
}
}
java.util.List<java.util.List<Integer>> pre = new java.util.ArrayList<java.util.List<Integer>>();
dfsPre(nodes, children, root, pre);
return pre;
}
private static void dfsPre(java.util.List<java.util.List<Integer>> nodes,
java.util.Map<Integer, java.util.List<Integer>> children,
int id, java.util.List<java.util.List<Integer>> pre) {
int pid = -2;
for (java.util.List<Integer> nd : nodes) {
if (nd.get(0) == id) { pid = nd.get(1); break; }
}
pre.add(java.util.Arrays.asList(id, pid));
java.util.List<Integer> ch = children.get(id);
if (ch == null) return;
for (int c : ch) dfsPre(nodes, children, c, pre);
}
}
复杂度分析
时间复杂度:O(n);空间复杂度:O(n)。
2765. Longest Alternating Subarray(最长交替子数组)
题目描述
给你一个下标从 0 开始的整数数组 nums。如果 nums 中长度为 m 的子数组 s 满足以下
条件,我们称它是一个交替子数组:
m> 1;s1= s0 + 1;- 数组
s与 `s0`, `s1`, `s0`, `s1`, ... 一样,即s1-s0= 1,s2-s1= -1,
s3-s2= 1,s4-s3= -1,以此类推。
请你返回nums中所有交替子数组中最长的长度;若不存在交替子数组,返回 -1。
示例 1
nums = [2,3,4,3,4] -> 4([3,4,3,4])
示例 2
nums = [4,5,6] -> 2([4,5] 或 [5,6])
提示:
- 2 <=
nums.length<= 100;1 <=nums[i]<= 1e4。
解题思路
解法一:枚举起点扩展,主提交版本
枚举每个起点 i,要求 numsi+1 - numsi == 1(交替起点必须是 +1 上升),
之后差值在 +1 / -1 之间切换,一直扩展直到不满足,记录长度。
注意交替子数组起点处必须是 s1 = s0 + 1。
解法二:暴力枚举所有子数组,对照
枚举每个 (l, r) 完整检查交替条件。
时间复杂度:O(n^2);空间复杂度:O(1)。
代码实现
java
public class Solution {
public int alternatingSubarray(int[] nums) {
int n = nums.length;
int ans = -1;
for (int i = 0; i < n - 1; i++) {
// 起点必须是 +1 上升
if (nums[i + 1] - nums[i] != 1) continue;
int len = 2;
int diff = -1; // 下一个期望差值
int j = i + 1;
while (j + 1 < n && nums[j + 1] - nums[j] == diff) {
len++;
diff = -diff;
j++;
}
ans = Math.max(ans, len);
}
return ans;
}
public int alternatingSubarrayBrute(int[] nums) {
int n = nums.length;
int ans = -1;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
// 检查 [i, j] 是否交替:s1=s0+1,之后 ±1 交替
boolean ok = true;
if (nums[i + 1] - nums[i] != 1) continue;
long diff = 1;
for (int k = i + 1; k <= j && ok; k++) {
if (nums[k] - nums[k - 1] != diff) ok = false;
diff = -diff;
}
if (ok) ans = Math.max(ans, j - i + 1);
}
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.alternatingSubarray(new int[]{2, 3, 4, 3, 4}) == 4 ? "PASS" : "FAIL");
System.out.println(sol.alternatingSubarray(new int[]{4, 5, 6}) == 2 ? "PASS" : "FAIL");
System.out.println(sol.alternatingSubarray(new int[]{1, 1, 1}) == -1 ? "PASS" : "FAIL");
// 随机对拍
java.util.Random rnd = new java.util.Random(2765);
boolean ok = true;
for (int t = 0; t < 3000; t++) {
int n = 2 + rnd.nextInt(14);
int[] nums = new int[n];
for (int i = 0; i < n; i++) nums[i] = 1 + rnd.nextInt(8);
int r1 = sol.alternatingSubarray(nums);
int r2 = sol.alternatingSubarrayBrute(nums);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch nums=" + java.util.Arrays.toString(nums) + " main=" + r1 + " brute=" + r2);
break;
}
}
System.out.println(ok ? "2765 全部 PASS" : "2765 存在不一致");
}
}
复杂度分析
时间复杂度:O(n^2);空间复杂度:O(1)。
2766. Relocate Marbles(重新放置石块)
题目类型:JavaScript
题目描述
给你一个下标从 0 开始的整数数组 nums ,表示一些石块的初始位置。再给你两个长度** 相等** 下标从 0 开始的整数数组 moveFrom 和 moveTo 。
在 moveFrom.length 次操作内,你将改变石块的位置。在第 i 次操作中,你将位置在 moveFrom[i] 的所有石块移到位置 moveTo[i] 。
完成这些操作后,请你按升序返回所有 有 石块的位置。
注意:
-
如果一个位置至少有一个石块,我们称这个位置 有 石块。
-
一个位置可能会有多个石块。
示例 1:
输入:nums = [1,6,7,8], moveFrom = [1,7,2], moveTo = [2,9,5]
输出:[5,6,8,9]
解释:一开始,石块在位置 1,6,7,8 。
第 i = 0 步操作中,我们将位置 1 处的石块移到位置 2 处,位置 2,6,7,8 有石块。
第 i = 1 步操作中,我们将位置 7 处的石块移到位置 9 处,位置 2,6,8,9 有石块。
第 i = 2 步操作中,我们将位置 2 处的石块移到位置 5 处,位置 5,6,8,9 有石块。
最后,至少有一个石块的位置为 [5,6,8,9] 。
示例 2:
输入: nums = [1,1,3,3], moveFrom = [1,3], moveTo = [2,2]
输出:[2]
解释:一开始,石块在位置 [1,1,3,3] 。
第 i = 0 步操作中,我们将位置 1 处的石块移到位置 2 处,有石块的位置为 [2,2,3,3] 。
第 i = 1 步操作中,我们将位置 3 处的石块移到位置 2 处,有石块的位置为 [2,2,2,2] 。
由于 2 是唯一有石块的位置,我们返回 [2] 。
提示:
-
1 <= nums.length <= 105 -
1 <= moveFrom.length <= 105 -
moveFrom.length == moveTo.length -
1 <= nums[i], moveFrom[i], moveTo[i] <= 109 -
测试数据保证在进行第
i步操作时,moveFrom[i]处至少有一个石块。
解题思路
每次把某一位置上的全部石子移到另一位置,求最终有石子的位置。同一位置可堆叠,数组模拟移动需要反复查找。
用集合保存有石子的坐标:移出时删除起点,移入时加入终点。最后排序输出。
代码实现
typescript
function relocateMarbles(nums: number[], moveFrom: number[], moveTo: number[]): number[] {
const pos: Set<number> = new Set(nums);
for (let i = 0; i < moveFrom.length; i++) {
pos.delete(moveFrom[i]);
pos.add(moveTo[i]);
}
return [...pos].sort((a, b) => a - b);
}
复杂度分析
- 时间复杂度 :
O(n),n为操作次数,哈希集合读写均摊常数。 - 空间复杂度 :
O(k),k为有石块的位置数。
2767. Partition String Into Minimum Beautiful Substrings(将字符串分割为最少的美丽子字符串)
题目描述
给你一个二进制字符串 s,你需要将字符串分割成一个或者多个子字符串,使每个子字符串都是美丽的。
如果一个字符串满足以下条件,则它是美丽的:
- 它不包含前导 0;
- 它是 5 的幂的二进制表示。
返回分割后的子字符串的最少数目;若无法分割,返回 -1。
示例 1
s = "1011" -> 2(["101","1"],101=5^1 二进制,1=5^0 二进制)
示例 2
s = "111" -> 3(["1","1","1"])
示例 3
s = "0" -> -1
提示:
- 1 <=
s.length<= 15;s[i]为 '0' 或 '1'。
解题思路
解法一:DP 最小分割,主提交版本
5 的幂(二进制长度不超过 15)数量有限,先离线枚举所有 5 的幂的二进制表示,
存成集合 good。用 dpi 表示分割 s0...i-1 所需的最少美丽子串数,
枚举最后一段 j, i-1:若该段无前导 0 且其数值属于 good,则
dpi = min(dpi, dpj + 1)。答案为 dpn(不可达为 -1)。
解法二:DFS 回溯,对照
从位置 pos 出发,枚举所有以 pos 开头的美丽子串并递归,
取最小段数,记忆化。小规模对拍。
时间复杂度:O(n^2);空间复杂度:O(n)。
代码实现
java
import java.util.HashSet;
import java.util.Set;
public class Solution {
public int minimumBeautifulSubstrings(String s) {
int n = s.length();
// 预计算所有长度 <= n 的 5 的幂的二进制值
Set<Integer> good = new HashSet<Integer>();
long p = 1;
while (true) {
String bin = Long.toBinaryString(p);
if (bin.length() > n) break;
good.add((int) p);
p *= 5;
if (p > (1L << 40)) break;
}
int INF = Integer.MAX_VALUE / 2;
int[] dp = new int[n + 1];
java.util.Arrays.fill(dp, INF);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
if (s.charAt(j) == '0') continue; // 前导 0
String sub = s.substring(j, i);
// 数值解析(长度 <= 15,int 安全)
int val = 0;
for (int k = j; k < i; k++) val = (val << 1) | (s.charAt(k) - '0');
if (good.contains(val)) {
if (dp[j] + 1 < dp[i]) dp[i] = dp[j] + 1;
}
}
}
return dp[n] >= INF ? -1 : dp[n];
}
public int minimumBeautifulSubstringsDfs(String s) {
int n = s.length();
Set<Integer> good = new HashSet<Integer>();
long p = 1;
while (true) {
if (Long.toBinaryString(p).length() > n) break;
good.add((int) p);
p *= 5;
}
int[] memo = new int[n + 1];
java.util.Arrays.fill(memo, -2);
int res = dfs(s, 0, good, memo);
return res >= Integer.MAX_VALUE / 2 ? -1 : res;
}
private int dfs(String s, int pos, Set<Integer> good, int[] memo) {
if (pos == s.length()) return 0;
if (memo[pos] != -2) return memo[pos];
int best = Integer.MAX_VALUE / 2;
int val = 0;
for (int i = pos; i < s.length(); i++) {
if (i == pos && s.charAt(i) == '0') break; // 前导 0 后面也都不行
val = (val << 1) | (s.charAt(i) - '0');
if (good.contains(val)) {
int rest = dfs(s, i + 1, good, memo);
if (rest + 1 < best) best = rest + 1;
}
}
memo[pos] = best;
return best;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.minimumBeautifulSubstrings("1011") == 2 ? "PASS" : "FAIL");
System.out.println(sol.minimumBeautifulSubstrings("111") == 3 ? "PASS" : "FAIL");
System.out.println(sol.minimumBeautifulSubstrings("0") == -1 ? "PASS" : "FAIL");
System.out.println(sol.minimumBeautifulSubstrings("1") == 1 ? "PASS" : "FAIL");
// 随机对拍(长度小)
java.util.Random rnd = new java.util.Random(2767);
boolean ok = true;
for (int t = 0; t < 3000; t++) {
int len = 1 + rnd.nextInt(12);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++) sb.append(rnd.nextBoolean() ? '1' : '0');
String s = sb.toString();
int r1 = sol.minimumBeautifulSubstrings(s);
int r2 = sol.minimumBeautifulSubstringsDfs(s);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch s=" + s + " dp=" + r1 + " dfs=" + r2);
break;
}
}
System.out.println(ok ? "2767 全部 PASS" : "2767 存在不一致");
}
}
复杂度分析
时间复杂度:O(n^2);空间复杂度:O(n)。
2768. Number of Black Blocks(黑格子的数目)
题目描述
给你两个整数 m 和 n,表示一个下标从 0 开始的 m x n 的网格图。
给你一个下标从 0 开始的二维整数矩阵 coordinates,其中 coordinates[i] = `x`, `y`
表示坐标为 `x`, `y` 的格子是黑色的,其他格子为白色。
一个块定义为网格图中 2 x 2 的一个子矩阵:左上角格子 `x`, `y` 的块覆盖
`x`,`y`、`x`+1,`y`、`x`,`y`+1、`x`+1,`y`+1 四个格子(0 <= x < m-1,0 <= y < n-1)。
返回长度为 5 的数组 arr,arr[i] 表示恰好包含 i 个黑色格子的块的数目。
示例 1
m=3, n=3, coordinates=[[0,0]] -> [3,1,0,0,0]
示例 2
m=3, n=3, coordinates=[[0,0],[1,1],[0,2]] -> [0,2,2,0,0]
提示:
- 2 <=
m,n<= 1e5;0 <=coordinates.length<= 1e4;坐标两两不同。
解题思路
解法一:黑点定位 + 覆盖块统计,主提交版本
黑格数量最多 1e4,而块数量可能达到 (m-1)(n-1) 巨大,因此只统计受黑格影响的块。
每个黑格 (x, y) 最多属于 4 个块,块左上角为 (x-1,y-1)、(x-1,y)、(x,y-1)、(x,y),
对每个在网格内的左上角,用 Map 记录该块被多少个黑格覆盖。
总块数 total = (m-1) (n-1)。没有被任何黑格覆盖的块黑格数为 0,
arr0 = total - 有黑格的块数。
对每个被覆盖的块,黑格数即覆盖它的黑格数量,对应 arrcount++。
解法二:直接建网格,对照
m, n 小时直接 boolean 网格 + 扫描所有块。
时间复杂度:O(coordinates.length * 4 + |map|);空间复杂度:O(coordinates.length)。
代码实现
java
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class Solution {
public long[] countBlackBlocks(int m, int n, int[][] coordinates) {
Map<Long, Integer> cover = new HashMap<Long, Integer>();
for (int[] c : coordinates) {
int x = c[0], y = c[1];
// 左上角候选
for (int dx = 0; dx <= 1; dx++) {
for (int dy = 0; dy <= 1; dy++) {
int tx = x - dx, ty = y - dy;
if (tx >= 0 && tx <= m - 2 && ty >= 0 && ty <= n - 2) {
long key = (long) tx * n + ty;
cover.put(key, cover.getOrDefault(key, 0) + 1);
}
}
}
}
long[] ans = new long[5];
long coveredBlocks = 0;
for (int cnt : cover.values()) {
ans[cnt]++;
coveredBlocks++;
}
long total = (long) (m - 1) * (n - 1);
ans[0] = total - coveredBlocks;
return ans;
}
public long[] countBlackBlocksSim(int m, int n, int[][] coordinates) {
boolean[][] black = new boolean[m][n];
for (int[] c : coordinates) black[c[0]][c[1]] = true;
long[] ans = new long[5];
for (int i = 0; i < m - 1; i++) {
for (int j = 0; j < n - 1; j++) {
int cnt = 0;
if (black[i][j]) cnt++;
if (black[i + 1][j]) cnt++;
if (black[i][j + 1]) cnt++;
if (black[i + 1][j + 1]) cnt++;
ans[cnt]++;
}
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(java.util.Arrays.equals(
sol.countBlackBlocks(3, 3, new int[][]{{0, 0}}),
new long[]{3, 1, 0, 0, 0}) ? "PASS" : "FAIL");
System.out.println(java.util.Arrays.equals(
sol.countBlackBlocks(3, 3, new int[][]{{0, 0}, {1, 1}, {0, 2}}),
new long[]{0, 2, 2, 0, 0}) ? "PASS" : "FAIL");
// 随机对拍
java.util.Random rnd = new java.util.Random(2768);
boolean ok = true;
for (int t = 0; t < 2000; t++) {
int m = 2 + rnd.nextInt(5);
int n = 2 + rnd.nextInt(5);
java.util.Set<Long> used = new HashSet<Long>();
int k = rnd.nextInt(m * n + 1);
int[][] coords = new int[k][2];
for (int i = 0; i < k; i++) {
int x, y;
do {
x = rnd.nextInt(m);
y = rnd.nextInt(n);
} while (!used.add((long) x * n + y));
coords[i][0] = x;
coords[i][1] = y;
}
long[] r1 = sol.countBlackBlocks(m, n, coords);
long[] r2 = sol.countBlackBlocksSim(m, n, coords);
if (!java.util.Arrays.equals(r1, r2)) {
ok = false;
System.out.println("Mismatch m=" + m + " n=" + n + " coords="
+ java.util.Arrays.deepToString(coords) + " main=" + java.util.Arrays.toString(r1)
+ " sim=" + java.util.Arrays.toString(r2));
break;
}
}
System.out.println(ok ? "2768 全部 PASS" : "2768 存在不一致");
}
}
复杂度分析
时间复杂度:O(coordinates.length * 4 + |map|);空间复杂度:O(coordinates.length)。
2769. Find the Maximum Achievable Number(找出最大的可达成数字)
题目描述
给你两个整数 num 和 t。如果整数 x 可以在执行下述操作不超过 t 次的情况下变为与
num 相等,则称其为可达成数字:每次操作将 x 的值增加或减少 1,同时可以选择将
num 的值增加或减少 1。
返回所有可达成数字中的最大值 x。
示例 1
num = 4, t = 1 -> 6(x 减 1 的同时 num 加 1,一次后相等)
示例 2
num = 3, t = 2 -> 7(两次:x 减 2、num 加 2)
提示:
- 1 <=
num,t<= 50。
解题思路
解法一:数学公式,主提交版本
每次操作 x 与 num 的相对距离最多缩小 2(x 往 num 靠近 1、num 也往 x 靠近 1)。
因此 t 次操作最多让初始差 d = x - num 从任意大缩小到 0,即 t 次操作能消除的
最大距离为 2t,x 最大可取 num + 2t。
解法二:模拟,对照
从一个大值开始做 t 次操作逼近 num,或直接二分查找最大的 x
使得 t 次内可达 num(检查 x - num <= 2t 且同奇偶性放宽到不等式即可)。
时间复杂度:O(1);空间复杂度:O(1)。
代码实现
java
public class Solution {
public int theMaximumAchievableX(int num, int t) {
return num + 2 * t;
}
public int theMaximumAchievableXSim(int num, int t) {
int x = num + 2 * t; // 这就是最大值;模拟验证:
// 用二分验证:最大 x 满足 (x - num) <= 2*t(x > num 时每次相对距离 -2)
int lo = num, hi = num + 2 * t;
while (lo < hi) {
int mid = (lo + hi + 1) / 2;
if (mid - num <= 2 * t) lo = mid;
else hi = mid - 1;
}
return lo;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.theMaximumAchievableX(4, 1) == 6 ? "PASS" : "FAIL");
System.out.println(sol.theMaximumAchievableX(3, 2) == 7 ? "PASS" : "FAIL");
System.out.println(sol.theMaximumAchievableX(1, 50) == 101 ? "PASS" : "FAIL");
// 随机对拍
java.util.Random rnd = new java.util.Random(2769);
boolean ok = true;
for (int t = 0; t < 3000; t++) {
int num = 1 + rnd.nextInt(50);
int tt = 1 + rnd.nextInt(50);
int r1 = sol.theMaximumAchievableX(num, tt);
int r2 = sol.theMaximumAchievableXSim(num, tt);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch num=" + num + " t=" + tt + " main=" + r1 + " sim=" + r2);
break;
}
}
System.out.println(ok ? "2769 全部 PASS" : "2769 存在不一致");
}
}
复杂度分析
时间复杂度:O(1);空间复杂度:O(1)。
2770. Maximum Number of Jumps to Reach the Last Index(达到末尾下标所需的最大跳跃次数)
题目描述
给你一个下标从 0 开始、由 n 个整数组成的数组 nums 和一个整数 target。
你的初始位置在下标 0。在一步操作中,你可以从下标 i 跳跃到任意满足下述条件的下标 j:
- 0 <=
i<j<n; - -
target<=nums[j]-nums[i]<=target。
返回到达下标n-1 处所需的最大跳跃次数;若无法到达,返回 -1。
示例 1
nums = [1,3,6,4,1,2], target = 2 -> 3
示例 2
nums = [1,3,6,4,1,2], target = 3 -> 5
示例 3
nums = [1,3,6,4,1,2], target = 0 -> -1
提示:
- 2 <=
nums.length<= 1000;-1e9 <=nums[i]<= 1e9;0 <=target<= 2e9。
解题思路
解法一:DP 最长路,主提交版本
dpi 表示从 0 到 i 的最大跳跃次数。初始化 dp0 = 0,其余为 -inf。
对每个 i,枚举 j > i:若 |numsj-numsi| <= target 则 dpj = max(dpj, dpi+1)。
答案 dpn-1(-inf 则 -1)。
注意用 long 计算差值避免溢出(nums 差可达 2e9,target <= 2e9)。
解法二:记忆化搜索,对照
dfs(i) 表示从 0 到 i 的最大跳跃次数,
递归枚举前驱。小规模对拍。
时间复杂度:O(n^2);空间复杂度:O(n)。
代码实现
java
import java.util.Arrays;
public class Solution {
public int maximumJumps(int[] nums, int target) {
int n = nums.length;
int NEG = Integer.MIN_VALUE / 2;
int[] dp = new int[n];
Arrays.fill(dp, NEG);
dp[0] = 0;
for (int i = 0; i < n; i++) {
if (dp[i] == NEG) continue;
for (int j = i + 1; j < n; j++) {
long diff = (long) nums[j] - nums[i];
if (diff >= -target && diff <= target) {
if (dp[i] + 1 > dp[j]) dp[j] = dp[i] + 1;
}
}
}
return dp[n - 1] == NEG ? -1 : dp[n - 1];
}
public int maximumJumpsMemo(int[] nums, int target) {
int n = nums.length;
int[] memo = new int[n];
Arrays.fill(memo, -2); // -2 未计算
int res = dfs(nums, target, n - 1, memo);
return res < 0 ? -1 : res; // 不可达统一返回 -1
}
private int dfs(int[] nums, int target, int i, int[] memo) {
if (i == 0) return 0;
if (memo[i] != -2) return memo[i];
int best = -1; // 不可达
for (int p = 0; p < i; p++) {
long diff = (long) nums[i] - nums[p];
if (diff >= -target && diff <= target) {
int prev = dfs(nums, target, p, memo);
if (prev >= 0 && prev + 1 > best) best = prev + 1;
}
}
memo[i] = best;
return best;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.maximumJumps(new int[]{1, 3, 6, 4, 1, 2}, 2) == 3 ? "PASS" : "FAIL");
System.out.println(sol.maximumJumps(new int[]{1, 3, 6, 4, 1, 2}, 3) == 5 ? "PASS" : "FAIL");
System.out.println(sol.maximumJumps(new int[]{1, 3, 6, 4, 1, 2}, 0) == -1 ? "PASS" : "FAIL");
// 随机对拍
java.util.Random rnd = new java.util.Random(2770);
boolean ok = true;
for (int t = 0; t < 2000; t++) {
int n = 2 + rnd.nextInt(9);
int[] nums = new int[n];
for (int i = 0; i < n; i++) nums[i] = rnd.nextInt(21) - 10;
int target = rnd.nextInt(8);
int r1 = sol.maximumJumps(nums, target);
int r2 = sol.maximumJumpsMemo(nums, target);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch nums=" + Arrays.toString(nums) + " target=" + target
+ " dp=" + r1 + " memo=" + r2);
break;
}
}
System.out.println(ok ? "2770 全部 PASS" : "2770 存在不一致");
}
}
复杂度分析
时间复杂度:O(n^2);空间复杂度:O(n)。
2771. Longest Non-decreasing Subarray From Two Arrays(构造最长非递减子数组)
题目描述
给你两个下标从 0 开始的整数数组 nums1 和 nums2,长度均为 n。
定义另一个长度为 n 的数组 nums3:对于每个下标 i,你可以将 nums1[i] 或 nums2[i]
的值赋给 nums3[i]。你的任务是使用最优策略为 nums3 赋值,以最大化 nums3 中
最长非递减子数组的长度。
示例 1
nums1 = [2,3,1], nums2 = [1,2,1] -> 2
示例 2
nums1 = [1,3,2,1], nums2 = [2,2,3,4] -> 4
示例 3
nums1 = [1,1], nums2 = [2,2] -> 2
提示:
- 1 <=
n<= 1e5;1 <= nums1i, nums2i <= 1e9。
解题思路
解法一:DP 双状态,主提交版本
dp0i:以 nums3i = nums1i 结尾的最长非递减子数组长度;
dp1i:以 nums3i = nums2i 结尾的最长非递减子数组长度。
转移时枚举上一位置选的源 (a, b) in {(0,0),(0,1),(1,0),(1,1)}:
若 numsSrci >= numsPrevprei 则可延续。初始 dp00 = dp10 = 1。
答案 = max over i (dp0i, dp1i)。
解法二:DFS 记忆化,对照
dfs(i, src) 返回以位置 i 选源 src 结尾的最长长度,
递归选择 i+1 时满足非递减。小规模对拍。
时间复杂度:O(n);空间复杂度:O(n)。
代码实现
java
public class Solution {
public int maxNonDecreasingLength(int[] nums1, int[] nums2) {
int n = nums1.length;
int[] dp0 = new int[n]; // 以 nums1[i] 结尾
int[] dp1 = new int[n]; // 以 nums2[i] 结尾
dp0[0] = dp1[0] = 1;
int ans = 1;
for (int i = 1; i < n; i++) {
dp0[i] = dp1[i] = 1;
if (nums1[i] >= nums1[i - 1]) dp0[i] = Math.max(dp0[i], dp0[i - 1] + 1);
if (nums1[i] >= nums2[i - 1]) dp0[i] = Math.max(dp0[i], dp1[i - 1] + 1);
if (nums2[i] >= nums1[i - 1]) dp1[i] = Math.max(dp1[i], dp0[i - 1] + 1);
if (nums2[i] >= nums2[i - 1]) dp1[i] = Math.max(dp1[i], dp1[i - 1] + 1);
ans = Math.max(ans, Math.max(dp0[i], dp1[i]));
}
return ans;
}
public int maxNonDecreasingLengthMemo(int[] nums1, int[] nums2) {
int n = nums1.length;
int[][] memo = new int[n][2];
for (int[] row : memo) java.util.Arrays.fill(row, -1);
int ans = 0;
for (int i = 0; i < n; i++) {
ans = Math.max(ans, dfs(nums1, nums2, i, 0, memo));
ans = Math.max(ans, dfs(nums1, nums2, i, 1, memo));
}
return ans;
}
private int dfs(int[] a, int[] b, int i, int src, int[][] memo) {
if (memo[i][src] != -1) return memo[i][src];
int val = src == 0 ? a[i] : b[i];
int best = 1;
if (i + 1 < a.length) {
if (a[i + 1] >= val) best = Math.max(best, dfs(a, b, i + 1, 0, memo) + 1);
if (b[i + 1] >= val) best = Math.max(best, dfs(a, b, i + 1, 1, memo) + 1);
}
memo[i][src] = best;
return best;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.maxNonDecreasingLength(new int[]{2, 3, 1}, new int[]{1, 2, 1}) == 2 ? "PASS" : "FAIL");
System.out.println(sol.maxNonDecreasingLength(new int[]{1, 3, 2, 1}, new int[]{2, 2, 3, 4}) == 4 ? "PASS" : "FAIL");
System.out.println(sol.maxNonDecreasingLength(new int[]{1, 1}, new int[]{2, 2}) == 2 ? "PASS" : "FAIL");
// 随机对拍
java.util.Random rnd = new java.util.Random(2771);
boolean ok = true;
for (int t = 0; t < 3000; t++) {
int n = 1 + rnd.nextInt(9);
int[] a = new int[n], b = new int[n];
for (int i = 0; i < n; i++) {
a[i] = 1 + rnd.nextInt(6);
b[i] = 1 + rnd.nextInt(6);
}
int r1 = sol.maxNonDecreasingLength(a, b);
int r2 = sol.maxNonDecreasingLengthMemo(a, b);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch a=" + java.util.Arrays.toString(a) + " b="
+ java.util.Arrays.toString(b) + " dp=" + r1 + " memo=" + r2);
break;
}
}
System.out.println(ok ? "2771 全部 PASS" : "2771 存在不一致");
}
}
复杂度分析
时间复杂度:O(n);空间复杂度:O(n)。
2772. Apply Operations to Make All Array Elements Equal to Zero(使数组中的所有元素都等于零)
题目描述
给你一个下标从 0 开始的整数数组 nums 和一个正整数 k。
你可以对数组执行下述操作任意次:从数组中选出长度为 k 的任一子数组,并将子数组中
每个元素都减去 1。如果你可以使数组中的所有元素都等于 0,返回 true;否则返回 false。
示例 1
nums = [2,2,3,1,1,0], k = 3 -> true
示例 2
nums = [1,3,1,1], k = 2 -> false
提示:
- 1 <=
k<=nums.length<= 1e5;0 <=nums[i]<= 1e6。
解题思路
解法一:贪心 + 差分、扫描线,主提交版本
从左到右处理:下标 i 的元素只能被「以 i 开头或更早开头、覆盖 i 的长度 k 区间」
的减操作影响。由于区间起点若在 i 之前且尚未结束,其贡献已在途;而起点为 i 的
区间必须在此时决定开几次。贪心:若当前累计被减量 cur < numsi,则需要在
i, i+k-1 内补 numsi - cur 次操作(这是唯一能减到 numsi 的手段,因为
之后的区间起点 >= i+1 不再覆盖 i)。若 cur > numsi 则已过度,非法。
用差分数组 add 记录「区间结束后的减少量」。检查每个位置是否恰好归零,且
最后一个 k 窗口不能越界(若 i + k > n 且还需要减则非法)。
实现:cur 为当前生效中的减量;addi 记录在位置 i 结束的区间贡献(负向)。
解法二:暴力模拟,对照
直接对每个 i 若 numsi > 0 则对 i, i+k-1 整体减
numsi(若越界则失败),最后检查全 0。仅用于小规模对拍。
时间复杂度:O(n);空间复杂度:O(n)。
代码实现
java
public class Solution {
public boolean checkArray(int[] nums, int k) {
int n = nums.length;
int[] diff = new int[n + 1]; // diff[i] 表示「结束于 i 的区间」带来的减少量
int cur = 0; // 当前位置累计被减量
for (int i = 0; i < n; i++) {
cur += diff[i];
if (cur > nums[i]) return false; // 减多了
if (cur == nums[i]) continue;
int need = nums[i] - cur;
int end = i + k - 1;
if (end >= n) return false; // 需要减但区间越界,无法归零
cur += need; // 从 i 起多减 need 次
diff[end + 1] -= need; // end 之后结束
}
return true;
}
public boolean checkArraySim(int[] nums, int k) {
int n = nums.length;
int[] arr = nums.clone();
for (int i = 0; i < n; i++) {
if (arr[i] == 0) continue;
if (i + k > n) return false;
int dec = arr[i];
for (int j = i; j < i + k; j++) {
arr[j] -= dec;
if (arr[j] < 0) return false;
}
}
for (int v : arr) {
if (v != 0) return false;
}
return true;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.checkArray(new int[]{2, 2, 3, 1, 1, 0}, 3) ? "PASS" : "FAIL");
System.out.println(!sol.checkArray(new int[]{1, 3, 1, 1}, 2) ? "PASS" : "FAIL");
System.out.println(sol.checkArray(new int[]{0, 0, 0}, 1) ? "PASS" : "FAIL");
System.out.println(!sol.checkArray(new int[]{1, 1}, 3) ? "PASS" : "FAIL");
// 随机对拍
java.util.Random rnd = new java.util.Random(2772);
boolean ok = true;
for (int t = 0; t < 5000; t++) {
int n = 1 + rnd.nextInt(8);
int[] nums = new int[n];
for (int i = 0; i < n; i++) nums[i] = rnd.nextInt(4);
int k = 1 + rnd.nextInt(n);
boolean r1 = sol.checkArray(nums, k);
boolean r2 = sol.checkArraySim(nums, k);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch nums=" + java.util.Arrays.toString(nums) + " k=" + k
+ " diff=" + r1 + " sim=" + r2);
break;
}
}
System.out.println(ok ? "2772 全部 PASS" : "2772 存在不一致");
}
}
复杂度分析
时间复杂度:O(n);空间复杂度:O(n)。
2773. Height of Special Binary Tree(特殊二叉树的高度)
题目描述
给定一棵具有 n 个节点的特殊二叉树的根节点 root。特殊二叉树的节点编号从 1 到 n。
假设这棵树有 k 个叶子,顺序为 b1 < b2 < ... < bk。这棵树的叶子节点有一个特殊属性:
- 如果
i<k,则bi的右子节点为b(i+1);否则为b1。 - 如果
i> 1,则bi的左子节点为b(i-1);否则为bk。
返回给定树的高度。二叉树的高度指从根节点到任何其他节点的最长路径的长度。
示例 1
root = [1,2,3,null,null,4,5] -> 2
示例 2
root = [1,2] -> 1
示例 3
root = [1,2,3,null,null,4,null,5,6] -> 3
提示:
- 2 <=
n<= 1e4;1 <=node.val<=n;node.val唯一。
解题思路
解法一:DFS 深度优先 + 防环剪枝,主提交版本
叶子节点成环形链表(bi 左连 b(i-1)、右连 b(i+1))。若直接对二叉树 DFS 会因
叶子的交叉引用无限循环。观察:只有真正的「树边」(父 -> 子)能增加深度,
叶子之间的环引用需跳过:
- 左子边:若 root.left.right == root,说明 root.left 是 root 的叶子左邻
b(i-1)(环引用:b(i-1).right == bi == root),跳过不递归; - 右子边:若 root.right.left == root,说明 root.right 是 root 的叶子右邻
b(i+1)(环引用:b(i+1).left == bi == root),跳过不递归。
其余情况为真树边,深度 +1 继续。
解法二:迭代 DFS + 显式栈,对照
显式栈模拟同样剪枝,两者结果应一致。
时间复杂度:O(n);空间复杂度:O(h)(h 为树高)。
代码实现
java
public class Solution {
private int ans;
public int heightOfTree(TreeNode root) {
ans = 0;
dfs(root, 0);
return ans;
}
private void dfs(TreeNode root, int d) {
ans = Math.max(ans, d);
// 左子若为叶子左环引用(root.left.right == root),跳过
if (root.left != null && root.left.right != root) {
dfs(root.left, d + 1);
}
// 右子若为叶子右环引用(root.right.left == root),跳过
if (root.right != null && root.right.left != root) {
dfs(root.right, d + 1);
}
}
public int heightOfTreeIter(TreeNode root) {
int res = 0;
java.util.ArrayDeque<TreeNode> stack = new java.util.ArrayDeque<TreeNode>();
java.util.ArrayDeque<Integer> depths = new java.util.ArrayDeque<Integer>();
stack.push(root);
depths.push(0);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
int d = depths.pop();
res = Math.max(res, d);
if (node.left != null && node.left.right != node) {
stack.push(node.left);
depths.push(d + 1);
}
if (node.right != null && node.right.left != node) {
stack.push(node.right);
depths.push(d + 1);
}
}
return res;
}
public static void main(String[] args) {
Solution sol = new Solution();
// 示例 1: [1,2,3,null,null,4,5] -> 叶子 2、4、5
TreeNode n1 = new TreeNode(1);
TreeNode n2 = new TreeNode(2);
TreeNode n3 = new TreeNode(3);
TreeNode n4 = new TreeNode(4);
TreeNode n5 = new TreeNode(5);
n1.left = n2;
n1.right = n3;
n3.left = n4;
n3.right = n5;
// 叶子顺序 b1=2, b2=4, b3=5
n2.right = n4; // b1.右 = b2
n4.right = n5; // b2.右 = b3
n5.right = n2; // b3.右 = b1
n2.left = n5; // b1.左 = bk = b3
n4.left = n2; // b2.左 = b1
n5.left = n4; // b3.左 = b2
System.out.println(sol.heightOfTree(n1) == 2 ? "PASS" : "FAIL");
System.out.println(sol.heightOfTreeIter(n1) == 2 ? "PASS" : "FAIL");
// 示例 2: [1,2] 单叶子
TreeNode e1 = new TreeNode(1);
TreeNode e2 = new TreeNode(2);
e1.left = e2;
e2.left = e2; // 单叶子:左 = bk = 自己
e2.right = e2; // 单叶子:右 = b1 = 自己
System.out.println(sol.heightOfTree(e1) == 1 ? "PASS" : "FAIL");
System.out.println(sol.heightOfTreeIter(e1) == 1 ? "PASS" : "FAIL");
// 示例 3: [1,2,3,null,null,4,null,5,6] -> 叶子 2、5、6
TreeNode r = new TreeNode(1);
TreeNode l2 = new TreeNode(2);
TreeNode r3 = new TreeNode(3);
r.left = l2;
r.right = r3;
TreeNode n44 = new TreeNode(4);
r3.left = n44;
TreeNode n55 = new TreeNode(5);
TreeNode n66 = new TreeNode(6);
n44.left = n55;
n44.right = n66;
// 叶子顺序 b1=2, b2=5, b3=6
l2.right = n55; // b1.右 = b2
n55.right = n66; // b2.右 = b3
n66.right = l2; // b3.右 = b1
l2.left = n66; // b1.左 = bk = b3
n55.left = l2; // b2.左 = b1
n66.left = n55; // b3.左 = b2
System.out.println(sol.heightOfTree(r) == 3 ? "PASS" : "FAIL");
System.out.println(sol.heightOfTreeIter(r) == 3 ? "PASS" : "FAIL");
// 随机对拍:随机完全二叉树形状 + 叶子成环,两种写法应一致
java.util.Random rnd = new java.util.Random(2773);
boolean ok = true;
for (int t = 0; t < 2000; t++) {
int n = 2 + rnd.nextInt(14);
TreeNode[] nodes = new TreeNode[n];
for (int i = 0; i < n; i++) nodes[i] = new TreeNode(i + 1);
// 完全二叉树形状:节点 i 的左右孩子 2i+1 / 2i+2
for (int i = 0; i < n; i++) {
if (2 * i + 1 < n) nodes[i].left = nodes[2 * i + 1];
if (2 * i + 2 < n) nodes[i].right = nodes[2 * i + 2];
}
// 找叶子(无孩子)
java.util.List<TreeNode> leaves = new java.util.ArrayList<TreeNode>();
for (TreeNode nd : nodes) {
if (nd.left == null && nd.right == null) leaves.add(nd);
}
int k = leaves.size();
for (int i = 0; i < k; i++) {
TreeNode cur = leaves.get(i);
TreeNode next = leaves.get((i + 1) % k); // 右 = b(i+1)
TreeNode prev = leaves.get((i - 1 + k) % k); // 左 = b(i-1)
cur.left = prev;
cur.right = next;
}
int r1 = sol.heightOfTree(nodes[0]);
int r2 = sol.heightOfTreeIter(nodes[0]);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch n=" + n + " main=" + r1 + " iter=" + r2);
break;
}
}
System.out.println(ok ? "2773 全部 PASS" : "2773 存在不一致");
}
}
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(n);空间复杂度:O(h)(h 为树高)。
2774. Array Upper Bound(数组的上界 🔒)
题目类型:JavaScript
题目描述
请你编写代码实现一个数组方法,任何数组都可以调用 upperBound() 方法,并返回给定目标数字的最后一个索引。nums 是一个可能包含重复数字的按升序排序的数组。如果在数组中找不到目标数字,则返回-1。
示例 1:
输入:nums = [3,4,5], target = 5
输出:2
解释:目标值的最后一个索引是 2
示例 2:**
输入:nums = [1,4,5], target = 2
输出:-1
解释:因为数组中没有数字 2,所以返回 -1。
示例 3:**
输入:nums = [3,4,6,6,6,6,7], target = 6
输出:5
解释:目标值的最后一个索引是 5
提示:
-
1 <= nums.length <= 104 -
-104 <= nums[i], target <= 104 -
nums按升序排序。
**进阶:**你能编写一个时间复杂度为 O(log n) 的算法吗?
解题思路
有序数组上求 t a r g e t target target 最后一次出现的下标。从右线性扫描可以得到答案,题目同时允许对数时间。
二分出第一个大于 t a r g e t target target 的位置,其前一位若等于 t a r g e t target target 即为右边界,否则不存在。
代码实现
typescript
declare global {
interface Array<T> {
upperBound(target: number): number;
}
}
Array.prototype.upperBound = function (target: number) {
let left = 0;
let right = this.length;
while (left < right) {
const mid = (left + right) >> 1;
if (this[mid] > target) {
right = mid;
} else {
left = mid + 1;
}
}
return left > 0 && this[left - 1] == target ? left - 1 : -1;
};
// [3,4,5].upperBound(5); // 2
// [1,4,5].upperBound(2); // -1
// [3,4,6,6,6,6,7].upperBound(6) // 5
typescript
declare global {
interface Array<T> {
upperBound(target: number): number;
}
}
Array.prototype.upperBound = function (target: number) {
return this.lastIndexOf(target);
};
// [3,4,5].upperBound(5); // 2
// [1,4,5].upperBound(2); // -1
// [3,4,6,6,6,6,7].upperBound(6) // 5
复杂度分析
- 时间复杂度 :
O(log n),数组已排序,二分查找。 - 空间复杂度 :
O(1),仅指针。
2775. Undefined to Null(将 undefined 转为 null 🔒)
题目类型:JavaScript
题目描述
给定一个深层嵌套的对象或数组 obj ,并创建该对象 obj 的副本,将其中的任何 undefined 值替换为 null 。
当使用 JSON.stringify() 将对象转换为 JSON 字符串时,undefined 值与 null 值的处理方式不同。该函数有助于确保序列化数据不会出现意外错误。
示例 1:**
输入:obj = {"a": undefined, "b": 3}
输出:{"a": null, "b": 3}
解释:obj.a 的值已从 undefined 更改为 null 。
示例 2:**
输入:obj = {"a": undefined, "b": ["a", undefined]}
输出:{"a": null,"b": ["a", null]}
解释:obj.a 和 obj.b[1] 的值已从 undefined 更改为 null 。
提示:
-
obj是一个有效的JSON对象或数组 -
2 <= JSON.stringify(obj).length <= 105
解题思路
把对象或数组中的 u n d e f i n e d undefined undefined 换成 n u l l null null,并递归处理嵌套结构。序列化时 u n d e f i n e d undefined undefined 会被丢键,不能靠 J S O N JSON JSON 往返。
遍历每个键:值仍是对象则先递归,再把仍然等于 u n d e f i n e d undefined undefined 的槽位写成 n u l l null null。数组下标同样被 f o r ⋯ i n for\cdots in for⋯in 覆盖。
代码实现
typescript
function undefinedToNull(obj: Record<any, any>): Record<any, any> {
for (const key in obj) {
if (typeof obj[key] === 'object') {
obj[key] = undefinedToNull(obj[key]);
}
if (obj[key] === undefined) {
obj[key] = null;
}
}
return obj;
}
/**
* undefinedToNull({"a": undefined, "b": 3}) // {"a": null, "b": 3}
* undefinedToNull([undefined, undefined]) // [null, null]
*/
复杂度分析
- 时间复杂度 :
O(n),深度优先遍历对象所有节点。 - 空间复杂度 :
O(d),d为嵌套深度(递归栈)。
2776. Convert Callback Based Function to Promise Based Function(转换回调函数为 Promise 函数 🔒)
题目类型:JavaScript
题目描述
编写一个函数,接受另一个函数 fn ,并将基于回调函数的函数转换为基于 Promise 的函数。
promisify 函数接受一个函数 fn ,fn 将回调函数作为其第一个参数,并且还可以接受其他额外的参数。
promisfy 返回一个新函数,新函数会返回一个 Promise 对象。当回调函数被成功调用时,新函数返回的 Promise 对象应该使用原始函数的结果进行解析;当回调函数被调用出现错误时,返回的 Promise 对象应该被拒绝并携带错误信息。最终返回的基于 Promise 的函数应该接受额外的参数作为输入。
以下是一个可以传递给 promisify 的函数示例:
function sum(callback, a, b) {
if (a < 0 || b < 0) {
const err = Error('a and b must be positive');
callback(undefined, err);
} else {
callback(a + b);
}
}
这是基于 Promise 的等效代码:
async function sum(a, b) {
if (a < 0 || b < 0) {
throw Error('a and b must be positive');
} else {
return a + b;
}
}
示例 1:**
输入:
fn = (callback, a, b, c) => {
return callback(a * b * c);
}
args = [1, 2, 3]
输出:{"resolved": 6}
解释:
const asyncFunc = promisify(fn);
asyncFunc(1, 2, 3).then(console.log); // 6
fn 以回调函数作为第一个参数和 args 作为其余参数进行调用。当使用 (1, 2, 3) 调用时,基于 Promise 的 fn 将解析为值 6。
示例 2:**
输入:
fn = (callback, a, b, c) => {
callback(a * b * c, "Promise Rejected");
}
args = [4, 5, 6]
输出:{"rejected": "Promise Rejected"}
解释:
const asyncFunc = promisify(fn);
asyncFunc(4, 5, 6).catch(console.log); // "Promise Rejected"
fn 以回调函数作为第一个参数和 args 作为其余参数进行调用。在回调函数的第二个参数中,接受一个错误消息,因此当调用 fn 时,Promise 被拒绝并携带回调函数中提供的错误消息。请注意,不管将什么作为回调函数的第一个参数传递都无关紧要。
提示:
-
1 <= args.length <= 100 -
0 <= args[i] <= 104
解题思路
把先接收 n e x t ( d a t a , e r r o r ) next(data, error) next(data,error) 的回调函数改写成返回 Promise 的函数。直接调用原函数无法把成功与失败接到 t h e n / c a t c h then/catch then/catch。
返回的异步函数构造 Promise,把 r e s o l v e / r e j e c t resolve/reject resolve/reject 包进 n e x t next next,再把其余参数传给原 f n fn fn。有 e r r o r error error 则拒绝,否则兑现 d a t a data data。
代码实现
typescript
type CallbackFn = (next: (data: number, error: string) => void, ...args: number[]) => void;
type Promisified = (...args: number[]) => Promise<number>;
function promisify(fn: CallbackFn): Promisified {
return async function (...args) {
return new Promise((resolve, reject) => {
fn(
(data, error) => {
if (error) {
reject(error);
} else {
resolve(data);
}
},
...args,
);
});
};
}
/**
* const asyncFunc = promisify(callback => callback(42));
* asyncFunc().then(console.log); // 42
*/
复杂度分析
- 时间复杂度 :
O(1),包装回调为 Promise。 - 空间复杂度 :
O(1),仅创建一个 Promise。
2777. Date Range Generator(日期范围生成器 🔒)
题目类型:JavaScript
题目描述
现给定起始日期 start 、结束日期 end 和正整数 step ,返回一个生成器对象,该生成器对象按照从 start 到 end(包括 start 和 end )的范围生成日期。
step 的值表示连续生成的日期之间的天数间隔。
所有日期都以字符串格式 YYYY-MM-DD 表示。
示例 1:**
输入:start = "2023-04-01", end = "2023-04-04", step = 1
输出:["2023-04-01","2023-04-02","2023-04-03","2023-04-04"]
解释:
const g = dateRangeGenerator(start, end, step);
g.next().value // '2023-04-01'
g.next().value // '2023-04-02'
g.next().value // '2023-04-03'
g.next().value // '2023-04-04'
示例 2:**
输入:start = "2023-04-10", end = "2023-04-20", step = 3
输出:["2023-04-10","2023-04-13","2023-04-16","2023-04-19"]
解释:
const g = dateRangeGenerator(start, end, step);
g.next().value // '2023-04-10'
g.next().value // '2023-04-13'
g.next().value // '2023-04-16'
g.next().value // '2023-04-19'
示例 3:**
输入:start = "2023-04-10", end = "2023-04-10", step = 1
输出:["2023-04-10"]
解释:
const g = dateRangeGenerator(start, end, step);
g.next().value // '2023-04-10'
提示:
-
new Date(start) <= new Date(end) -
start和end的日期格式是YYYY-MM-DD -
0 <= 结束日期与开始日期之间的天数差 <= 1500 -
1 <= step <= 1000
解题思路
按固定步长产出闭区间内的日期字符串。手写儒略日加减容易在月末出错,而 D a t e Date Date 的 s e t D a t e setDate setDate 会自动进位。
从起始日走到结束日(含),每次 y i e l d yield yield ISO 日期,再把日份加上 s t e p step step。生成器按需暂停,不必预先展开整个区间。
代码实现
typescript
function* dateRangeGenerator(start: string, end: string, step: number): Generator<string> {
const startDate = new Date(start);
const endDate = new Date(end);
let currentDate = startDate;
while (currentDate <= endDate) {
yield currentDate.toISOString().slice(0, 10);
currentDate.setDate(currentDate.getDate() + step);
}
}
/**
* const g = dateRangeGenerator('2023-04-01', '2023-04-04', 1);
* g.next().value; // '2023-04-01'
* g.next().value; // '2023-04-02'
* g.next().value; // '2023-04-03'
* g.next().value; // '2023-04-04'
* g.next().done; // true
*/
复杂度分析
- 时间复杂度 :
O(1)每次next(),按起始与步长推进日期。 - 空间复杂度 :
O(1),生成器只保存当前日期。
2778. Sum of Squares of Special Elements(特殊元素平方和)
题目描述
给你一个下标从 1 开始、长度为 n 的整数数组 nums。
对 nums 中的元素 nums[i] 而言,如果 n 能够被 i 整除,即 n % i == 0,
则认为 nums[i] 是一个特殊元素。返回 nums 中所有特殊元素的平方和。
示例 1
nums = [1,2,3,4] -> 21(特殊元素下标 1、2、4:1²+2²+4²=21)
示例 2
nums = [2,7,1,19,18,3] -> 63(下标 1、2、3、6:2²+7²+1²+3²=63)
提示:
- 1 <=
nums.length==n<= 50;1 <=nums[i]<= 50。
解题思路
解法一:直接枚举,主提交版本
遍历下标 i(1-based),若 n % i == 0 则累加 numsi-1 * numsi-1。
解法二:枚举 n 的因子,对照
先求出 n 的所有因子(含 1 与 n),
只对这些下标求和,结果相同。
时间复杂度:O(n);空间复杂度:O(1)。
代码实现
java
public class Solution {
public int sumOfSquares(int[] nums) {
int n = nums.length;
int ans = 0;
for (int i = 1; i <= n; i++) { // 1-based 下标
if (n % i == 0) {
ans += nums[i - 1] * nums[i - 1];
}
}
return ans;
}
public int sumOfSquaresFactors(int[] nums) {
int n = nums.length;
int ans = 0;
for (int i = 1; i <= n; i++) {
if (n % i == 0) {
ans += nums[i - 1] * nums[i - 1];
}
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.sumOfSquares(new int[]{1, 2, 3, 4}) == 21 ? "PASS" : "FAIL");
System.out.println(sol.sumOfSquares(new int[]{2, 7, 1, 19, 18, 3}) == 63 ? "PASS" : "FAIL");
System.out.println(sol.sumOfSquares(new int[]{5}) == 25 ? "PASS" : "FAIL");
// 随机对拍
java.util.Random rnd = new java.util.Random(2778);
boolean ok = true;
for (int t = 0; t < 3000; t++) {
int n = 1 + rnd.nextInt(50);
int[] nums = new int[n];
for (int i = 0; i < n; i++) nums[i] = 1 + rnd.nextInt(50);
int r1 = sol.sumOfSquares(nums);
int r2 = sol.sumOfSquaresFactors(nums);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch n=" + n + " main=" + r1 + " fac=" + r2);
break;
}
}
System.out.println(ok ? "2778 全部 PASS" : "2778 存在不一致");
}
}
复杂度分析
时间复杂度:O(n);空间复杂度:O(1)。
2779. Maximum Beauty of an Array After Applying Operation(数组的最大美丽值)
题目描述
给你一个下标从 0 开始的整数数组 nums 和一个非负整数 k。
在一步操作中,你可以对每个下标最多执行一次:将 nums[i] 替换为范围
`nums[i]`-`k`, `nums[i]`+`k` 内的任一整数。
数组的美丽值定义为数组中由相等元素组成的最长子序列的长度。
执行任意次操作后,返回数组可能取得的最大美丽值。
示例 1
nums = [4,6,1,2], k = 2 -> 3(全变为 4)
示例 2
nums = [1,1,1,1], k = 10 -> 4
提示:
- 1 <=
nums.length<= 1e5;0 <=nums[i],k<= 1e5。
解题思路
解法一:排序 + 滑动窗口,主提交版本
所有元素最终能变成同一个值 v 等价于:每个 numsi 满足 |numsi - v| <= k,
即所有相关元素必须落在长度 2k 的区间 v-k, v+k 内。
排序后,能统一为同一值的元素是排序数组中的一段连续区间 l, r,
满足 numsr - numsl <= 2k(此时可选 v = (numsl+numsr)/2 这类中点)。
用滑动窗口求满足 numsr - numsl <= 2k 的最长区间长度。
解法二:枚举目标值 + 前缀计数,对照
值域较小(<= 2e5),开计数数组,
对每个可能的目标 v(min, max 内),统计落在 v-k, v+k 内元素的最多数量。
用差分/前缀和。小规模对拍。
时间复杂度:O(n log n);空间复杂度:O(log n)(排序栈)。
代码实现
java
import java.util.Arrays;
public class Solution {
public int maximumBeauty(int[] nums, int k) {
Arrays.sort(nums);
int n = nums.length;
int ans = 0;
int l = 0;
for (int r = 0; r < n; r++) {
while (nums[r] - nums[l] > 2 * (long) k) {
l++;
}
ans = Math.max(ans, r - l + 1);
}
return ans;
}
public int maximumBeautyCnt(int[] nums, int k) {
int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
for (int v : nums) {
min = Math.min(min, v);
max = Math.max(max, v);
}
int LO = Math.max(0, min - k);
int HI = max + k;
int size = HI - LO + 1 + 2;
int[] diff = new int[size + 2];
for (int v : nums) {
int a = v - k - LO;
int b = v + k - LO;
if (a < 0) a = 0;
if (b >= size) b = size - 1;
diff[a]++;
diff[b + 1]--;
}
int ans = 0, cur = 0;
for (int i = 0; i < size; i++) {
cur += diff[i];
ans = Math.max(ans, cur);
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.maximumBeauty(new int[]{4, 6, 1, 2}, 2) == 3 ? "PASS" : "FAIL");
System.out.println(sol.maximumBeauty(new int[]{1, 1, 1, 1}, 10) == 4 ? "PASS" : "FAIL");
System.out.println(sol.maximumBeauty(new int[]{5}, 0) == 1 ? "PASS" : "FAIL");
System.out.println(sol.maximumBeauty(new int[]{1, 10}, 5) == 2 ? "PASS" : "FAIL"); // 1->6,10->6
// 随机对拍
java.util.Random rnd = new java.util.Random(2779);
boolean ok = true;
for (int t = 0; t < 3000; t++) {
int n = 1 + rnd.nextInt(12);
int[] nums = new int[n];
for (int i = 0; i < n; i++) nums[i] = rnd.nextInt(30);
int k = rnd.nextInt(10);
int r1 = sol.maximumBeauty(nums, k);
int r2 = sol.maximumBeautyCnt(nums, k);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch nums=" + Arrays.toString(nums) + " k=" + k
+ " main=" + r1 + " cnt=" + r2);
break;
}
}
System.out.println(ok ? "2779 全部 PASS" : "2779 存在不一致");
}
}
复杂度分析
时间复杂度:O(n log n);空间复杂度:O(log n)(排序栈)。
2780. Minimum Index of a Valid Split(合法分割的最小下标)
题目描述
如果在长度为 m 的整数数组 arr 中超过一半的元素值为 x,那么我们称 x 是支配元素。
给你一个下标从 0 开始长度为 n 的整数数组 nums,数据保证它含有一个支配元素。
你需要在下标 i 处将 nums 分割成两个数组 nums0...`i` 和 nums`i`+1...`n`-1。
如果一个分割满足:0 <= i < n-1 且两个子数组的支配元素相同,则称它是合法的。
返回一个合法分割的最小下标;如果合法分割不存在,返回 -1。
示例 1
nums = [1,2,2,2] -> 2(分割 [1,2,2] / [2])
示例 2
nums = [2,1,3,1,1,1,7,1,2,1] -> 4
示例 3
nums = [3,3,3,3,7,2,2] -> -1
提示:
- 1 <=
nums.length<= 1e5;1 <=nums[i]<= 1e9;nums有且只有一个支配元素。
解题思路
解法一:统计 + 前缀频次,主提交版本
先扫描一遍找出整个数组的支配元素 d(频次 > n/2)。
再从左到右第二次扫描,维护 d 在左边出现的次数 cntL;
位置 i 合法当且仅当:
- 左边长度 lenL = i+1,cntL * 2 > lenL(d 是左半边支配元素);
- 右边长度 lenR = n-i-1,cntR = totalCnt - cntL,cntR * 2 > lenR。
返回第一个满足的 i。
解法二:Boyer-Moore 找支配 + 遍历,对照
用摩尔投票找候选、验证频次,
再遍历求答案,与主解区别只在找支配元素的方式。
时间复杂度:O(n);空间复杂度:O(1)。
代码实现
java
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Solution {
public int minimumIndex(List<Integer> nums) {
int n = nums.size();
// 找支配元素:Boyer-Moore
int cand = -1, cnt = 0;
for (int v : nums) {
if (cnt == 0) cand = v;
cnt += (v == cand) ? 1 : -1;
}
int total = 0;
for (int v : nums) {
if (v == cand) total++;
}
// 若不存在支配元素(题面保证存在,防御性判断)
if (total * 2 <= n) return -1;
int cntL = 0;
for (int i = 0; i < n - 1; i++) {
if (nums.get(i) == cand) cntL++;
int lenL = i + 1;
int lenR = n - i - 1;
int cntR = total - cntL;
if (cntL * 2 > lenL && cntR * 2 > lenR) return i;
}
return -1;
}
public int minimumIndexMap(List<Integer> nums) {
int n = nums.size();
Map<Integer, Integer> totalMap = new HashMap<Integer, Integer>();
for (int v : nums) totalMap.put(v, totalMap.getOrDefault(v, 0) + 1);
int cand = -1, total = 0;
for (Map.Entry<Integer, Integer> e : totalMap.entrySet()) {
if (e.getValue() > total) {
total = e.getValue();
cand = e.getKey();
}
}
int cntL = 0;
for (int i = 0; i < n - 1; i++) {
if (nums.get(i) == cand) cntL++;
int lenL = i + 1;
int lenR = n - i - 1;
int cntR = total - cntL;
if (cntL * 2 > lenL && cntR * 2 > lenR) return i;
}
return -1;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.minimumIndex(java.util.Arrays.asList(1, 2, 2, 2)) == 2 ? "PASS" : "FAIL");
System.out.println(sol.minimumIndex(java.util.Arrays.asList(2, 1, 3, 1, 1, 1, 7, 1, 2, 1)) == 4 ? "PASS" : "FAIL");
System.out.println(sol.minimumIndex(java.util.Arrays.asList(3, 3, 3, 3, 7, 2, 2)) == -1 ? "PASS" : "FAIL");
System.out.println(sol.minimumIndex(java.util.Arrays.asList(1, 1)) == 0 ? "PASS" : "FAIL");
// 随机对拍:构造含支配元素的数组
java.util.Random rnd = new java.util.Random(2780);
boolean ok = true;
for (int t = 0; t < 3000; t++) {
int n = 1 + rnd.nextInt(10);
int[] arr = new int[n];
int d = 1 + rnd.nextInt(5);
int cntD = n / 2 + 1; // 保证支配
java.util.List<Integer> pool = new java.util.ArrayList<Integer>();
for (int i = 0; i < n; i++) pool.add(rnd.nextInt(5) + 1);
// 覆盖前 cntD 个为 d
for (int i = 0; i < cntD; i++) pool.set(i, d);
java.util.Collections.shuffle(pool, rnd);
int r1 = sol.minimumIndex(pool);
int r2 = sol.minimumIndexMap(pool);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch pool=" + pool + " main=" + r1 + " map=" + r2);
break;
}
}
System.out.println(ok ? "2780 全部 PASS" : "2780 存在不一致");
}
}
复杂度分析
时间复杂度:O(n);空间复杂度:O(1)。