LeetCode 1661-1680. 题解合集
文章目录
- [LeetCode 1661-1680. 题解合集](#LeetCode 1661-1680. 题解合集)
-
- [1661. Average Time of Process per Machine(每台机器的进程平均运行时间)](#1661. Average Time of Process per Machine(每台机器的进程平均运行时间))
- [1662. Check If Two String Arrays are Equivalent(检查两个字符串数组是否相等)](#1662. Check If Two String Arrays are Equivalent(检查两个字符串数组是否相等))
- [1663. Smallest String With A Given Numeric Value(具有给定数值的最小字符串)](#1663. Smallest String With A Given Numeric Value(具有给定数值的最小字符串))
- [1664. Ways to Make a Fair Array(生成平衡数组的方案数)](#1664. Ways to Make a Fair Array(生成平衡数组的方案数))
- [1665. Minimum Initial Energy to Finish Tasks(完成所有任务的最少初始能量)](#1665. Minimum Initial Energy to Finish Tasks(完成所有任务的最少初始能量))
- [1666. Change the Root of a Binary Tree(改变二叉树的根)](#1666. Change the Root of a Binary Tree(改变二叉树的根))
- [1667. Fix Names in a Table(修复表中的名字)](#1667. Fix Names in a Table(修复表中的名字))
- [1668. Maximum Repeating Substring(最大重复子字符串)](#1668. Maximum Repeating Substring(最大重复子字符串))
- [1669. Merge In Between Linked Lists(合并两个链表)](#1669. Merge In Between Linked Lists(合并两个链表))
- [1670. Design Front Middle Back Queue(设计前中后队列)](#1670. Design Front Middle Back Queue(设计前中后队列))
- [1671. Minimum Number of Removals to Make Mountain Array(得到山形数组的最少删除次数)](#1671. Minimum Number of Removals to Make Mountain Array(得到山形数组的最少删除次数))
- [1672. Richest Customer Wealth(最富有客户的资产总量)](#1672. Richest Customer Wealth(最富有客户的资产总量))
- [1673. Find the Most Competitive Subsequence(找出最具竞争力的子序列)](#1673. Find the Most Competitive Subsequence(找出最具竞争力的子序列))
- [1674. Minimum Moves to Make Array Complementary(使数组互补的最少操作次数)](#1674. Minimum Moves to Make Array Complementary(使数组互补的最少操作次数))
- [1675. Minimize Deviation in Array(数组的最小偏移量)](#1675. Minimize Deviation in Array(数组的最小偏移量))
- [1676. Lowest Common Ancestor of a Binary Tree IV(二叉树的最近公共祖先 IV)](#1676. Lowest Common Ancestor of a Binary Tree IV(二叉树的最近公共祖先 IV))
- [1677. Product's Worth Over Invoices(发票中的产品金额)](#1677. Product's Worth Over Invoices(发票中的产品金额))
- [1678. Goal Parser Interpretation(设计 Goal 解析器)](#1678. Goal Parser Interpretation(设计 Goal 解析器))
- [1679. Max Number of K-Sum Pairs(K 和数对的最大数目)](#1679. Max Number of K-Sum Pairs(K 和数对的最大数目))
- [1680. Concatenation of Consecutive Binary Numbers(连接连续二进制数字)](#1680. Concatenation of Consecutive Binary Numbers(连接连续二进制数字))
1661. Average Time of Process per Machine(每台机器的进程平均运行时间)
题目类型:数据库(SQL)
题目描述
表: Activity
+----------------+---------+
| Column Name | Type |
+----------------+---------+
| machine_id | int |
| process_id | int |
| activity_type | enum |
| timestamp | float |
+----------------+---------+
该表展示了一家工厂网站的用户活动。
(machine_id, process_id, activity_type) 是当前表的主键(具有唯一值的列的组合)。
machine_id 是一台机器的ID号。
process_id 是运行在各机器上的进程ID号。
activity_type 是枚举类型 ('start', 'end')。
timestamp 是浮点类型,代表当前时间(以秒为单位)。
'start' 代表该进程在这台机器上的开始运行时间戳 , 'end' 代表该进程在这台机器上的终止运行时间戳。
同一台机器,同一个进程都有一对开始时间戳和结束时间戳,而且开始时间戳永远在结束时间戳前面。
现在有一个工厂网站有几台机器运行,每台机器上运行着一个或更多进程 。编写解决方案,计算每台机器各自完成一个进程任务的平均耗时。
完成一个进程任务的时间指进程的'end' 时间戳 减去 'start' 时间戳。平均耗时通过计算每台机器上所有进程任务的总耗费时间除以那台机器上的总进程数量获得。
结果表必须包含machine_id(机器ID) 和对应的 average time(平均耗时) 别名 processing_time,且四舍五入保留3位小数。
以 任意顺序 返回表。
具体参考例子如下。
示例 1:
**输入:**
Activity table:
+------------+------------+---------------+-----------+
| machine_id | process_id | activity_type | timestamp |
+------------+------------+---------------+-----------+
| 0 | 0 | start | 0.712 |
| 0 | 0 | end | 1.520 |
| 0 | 1 | start | 3.140 |
| 0 | 1 | end | 4.120 |
| 1 | 0 | start | 0.550 |
| 1 | 0 | end | 1.550 |
| 1 | 1 | start | 0.430 |
| 1 | 1 | end | 1.420 |
| 2 | 0 | start | 4.100 |
| 2 | 0 | end | 4.512 |
| 2 | 1 | start | 2.500 |
| 2 | 1 | end | 5.000 |
+------------+------------+---------------+-----------+
**输出:**
+------------+-----------------+
| machine_id | processing_time |
+------------+-----------------+
| 0 | 0.894 |
| 1 | 0.995 |
| 2 | 1.456 |
+------------+-----------------+
**解释:**
一共有3台机器,每台机器运行着两个进程.
机器 0 的平均耗时: ((1.520 - 0.712) + (4.120 - 3.140)) / 2 = 0.894
机器 1 的平均耗时: ((1.550 - 0.550) + (1.420 - 0.430)) / 2 = 0.995
机器 2 的平均耗时: ((4.512 - 4.100) + (5.000 - 2.500)) / 2 = 1.456
解题思路
思考
每台机器上进程成对出现 start/end,平均耗时是各对 ( end − start ) (\textit{end}-\textit{start}) (end−start) 的均值。按 machine_id \texttt{machine\_id} machine_id 分组后,把 start 取负、end 取正再平均,得到的是一半差值,因而要再乘 2 2 2。
CASE WHEN \texttt{CASE WHEN} CASE WHEN 写符号, AVG \texttt{AVG} AVG 后 ROUND \texttt{ROUND} ROUND 到三位小数。
考察自连接 + 时间差聚合:同一机器同一进程按 timestamp 自连接成对(start/end),求平均处理时间 ROUND 三位小数。
代码实现
sql
# Write your MySQL query statement below
SELECT
machine_id,
ROUND(
AVG(
CASE
WHEN activity_type = 'start' THEN -timestamp
ELSE timestamp
END
) * 2,
3
) AS processing_time
FROM Activity
GROUP BY 1;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1662. Check If Two String Arrays are Equivalent(检查两个字符串数组是否相等)
题目描述
两个字符串数组表示的字符串 = 数组所有元素按顺序连接形成的字符串。
若两个数组表示的字符串相同返回 true,否则 false。
示例 1
word1=["ab","c"], word2=["a","bc"],输出 true。
示例 2
word1=["a","cb"], word2=["ab","c"],输出 false。
示例 3
word1=["abc","d","defg"], word2=["abcddefg"],输出 true。
约束:
- 1 <=
len<= 10^3;元素长度 <= 10^3;小写字母。
解题思路
解法一:StringBuilder 拼接(主提交版本)
- 分别拼接两数组后直接 equals。
复杂度:时间复杂度 O(L)(总字符数),空间复杂度 O(L)。
解法二:流式逐字符比对(对照) - 不实际拼接,双指针跨数组逐字符比较,节省空间。
复杂度:时间复杂度 O(L),空间复杂度 O(1)。
代码实现
java
public class Solution {
public boolean arrayStringsAreEqual(String[] word1, String[] word2) {
StringBuilder sb1 = new StringBuilder();
for (String s : word1) {
sb1.append(s);
}
StringBuilder sb2 = new StringBuilder();
for (String s : word2) {
sb2.append(s);
}
return sb1.toString().equals(sb2.toString());
}
public boolean arrayStringsAreEqualPointers(String[] word1, String[] word2) {
int i = 0, j = 0;
int ci = 0, cj = 0;
while (i < word1.length && j < word2.length) {
// 跳过已耗尽元素
while (i < word1.length && ci == word1[i].length()) {
i++;
ci = 0;
}
while (j < word2.length && cj == word2[j].length()) {
j++;
cj = 0;
}
if (i == word1.length || j == word2.length) {
break;
}
if (word1[i].charAt(ci) != word2[j].charAt(cj)) {
return false;
}
ci++;
cj++;
}
while (i < word1.length && ci == word1[i].length()) {
i++;
ci = 0;
}
while (j < word2.length && cj == word2[j].length()) {
j++;
cj = 0;
}
return i == word1.length && j == word2.length;
}
public static void main(String[] args) {
Solution sol = new Solution();
// 示例 1:true
System.out.println(sol.arrayStringsAreEqual(new String[]{"ab", "c"}, new String[]{"a", "bc"})
+ " " + sol.arrayStringsAreEqualPointers(new String[]{"ab", "c"}, new String[]{"a", "bc"}));
// 示例 2:false
System.out.println(sol.arrayStringsAreEqual(new String[]{"a", "cb"}, new String[]{"ab", "c"})
+ " " + sol.arrayStringsAreEqualPointers(new String[]{"a", "cb"}, new String[]{"ab", "c"}));
// 示例 3:true
System.out.println(sol.arrayStringsAreEqual(new String[]{"abc", "d", "defg"}, new String[]{"abcddefg"})
+ " " + sol.arrayStringsAreEqualPointers(new String[]{"abc", "d", "defg"}, new String[]{"abcddefg"}));
// 附加:空串元素
System.out.println(sol.arrayStringsAreEqual(new String[]{"a", "", "b"}, new String[]{"ab"})
+ " " + sol.arrayStringsAreEqualPointers(new String[]{"a", "", "b"}, new String[]{"ab"}));
}
}
复杂度分析
复杂度:时间复杂度 O(L)(总字符数),空间复杂度 O(L)。
复杂度:时间复杂度 O(L),空间复杂度 O(1)。
1663. Smallest String With A Given Numeric Value(具有给定数值的最小字符串)
题目描述
小写字符数值 = 其在字母表中的位置(a=1, b=2, ..., z=26)。字符串数值 = 各字符数值之和。
返回长度 n 且数值为 k 的字典序最小字符串。
示例 1
n=3, k=27,输出 "aay"。
示例 2
n=5, k=73,输出 "aaszz"。
约束:
- 1 <=
n<= 10^5;n<=k<= 26*n。
解题思路
解法一:贪心逐位构造(主提交版本)
- 字典序最小:越靠前的字符越小。从第一位开始,尽量填 'a',
但要保证剩余位最多能凑出 26 的数值(若 k - 1 剩下超过 26*(剩余位数),则当前位必须更大)。 - 用 char\[\] 直接填充,效率高。
复杂度:时间复杂度 O(n),空间复杂度 O(n)。
解法二:后缀最大堆填(对照) - 思想等价:先全 'a',从后往前把剩余数值 r 分散补到各字符,尽量压在最后。
后部优先放大字符,天然形成字典序最小。
复杂度:时间复杂度 O(n),空间复杂度 O(n)。
代码实现
java
public class Solution {
public String getSmallestString(int n, int k) {
char[] res = new char[n];
for (int i = 0; i < n; i++) {
int remainPos = n - i - 1; // 剩余位数
int maxRest = remainPos * 26; // 剩余位最多还能填的数值
// 当前位最小取值:至少让剩余数值 <= maxRest
int need = Math.max(1, k - maxRest);
res[i] = (char) ('a' + need - 1);
k -= need;
}
return new String(res);
}
public String getSmallestStringTail(int n, int k) {
char[] res = new char[n];
for (int i = 0; i < n; i++) {
res[i] = 'a';
}
int rest = k - n; // 还需要额外增加的总数值
for (int i = n - 1; i >= 0 && rest > 0; i--) {
int add = Math.min(25, rest); // 从 'a' 最多加 25 到 'z'
res[i] = (char) ('a' + add);
rest -= add;
}
return new String(res);
}
public static void main(String[] args) {
Solution sol = new Solution();
// 示例 1:aay
System.out.println(sol.getSmallestString(3, 27) + " " + sol.getSmallestStringTail(3, 27));
// 示例 2:aaszz
System.out.println(sol.getSmallestString(5, 73) + " " + sol.getSmallestStringTail(5, 73));
// 附加:极限
System.out.println(sol.getSmallestString(1, 26) + " " + sol.getSmallestStringTail(1, 26));
// 附加:n=10, k=26(全是 a+尾巴)
System.out.println(sol.getSmallestString(10, 26) + " " + sol.getSmallestStringTail(10, 26));
}
}
复杂度分析
复杂度:时间复杂度 O(n),空间复杂度 O(n)。
1664. Ways to Make a Fair Array(生成平衡数组的方案数)
题目描述
给定整数数组 nums,选择恰好一个下标并删除对应元素。
删除后剩下的数组中,若奇数下标元素和与偶数下标元素和相等,则该数组"平衡"。
返回删除后得到的数组是平衡数组的方案数。
示例 1
nums=[2,1,6,4],输出 1。
示例 2
nums=[1,1,1],输出 3。
示例 3
nums=[1,2,3],输出 0。
约束:
- 1 <=
n<= 10^5;1 <=nums[i]<= 10^4。
解题思路
解法一:前缀奇偶和(主提交版本)
- 删除下标 i 后,0,i-1 保持不变,i+1,n-1 整体左移一位:原奇数下标变偶数、原偶数变奇数。
- 删除后的奇数下标和 = preOddi + sufEveni+1;偶数下标和 = preEveni + sufOddi+1。
- 预处理前缀/后缀奇偶和,O(n) 遍历。
复杂度:时间复杂度 O(n),空间复杂度 O(n)。
解法二:迭代滚动计数(对照) - 从左到右维护"删除点左侧的奇偶前缀和"与"右侧奇偶后缀和",动态更新,O(1) 空间。
复杂度:时间复杂度 O(n),空间复杂度 O(1)。
代码实现
java
public class Solution {
public int waysToMakeFair(int[] nums) {
int n = nums.length;
int[] preOdd = new int[n + 1];
int[] preEven = new int[n + 1];
for (int i = 0; i < n; i++) {
preOdd[i + 1] = preOdd[i];
preEven[i + 1] = preEven[i];
if (i % 2 == 0) {
preEven[i + 1] += nums[i];
} else {
preOdd[i + 1] += nums[i];
}
}
int sufOdd = 0, sufEven = 0;
int ans = 0;
// 从右往左:suf 表示 [i+1, n-1] 的和(未移位前)
for (int i = n - 1; i >= 0; i--) {
// 删除 i 后:左侧奇偶不变,右侧整体左移
int leftOdd = preOdd[i]; // [0, i-1] 奇数和
int leftEven = preEven[i]; // [0, i-1] 偶数和
// 右侧 [i+1, n-1]:原奇数位 -> 偶数位,原偶数位 -> 奇数位
int rightOdd = sufEven; // 右侧原偶数位(下标奇偶需看原位置)
int rightEven = sufOdd;
if (leftOdd + rightOdd == leftEven + rightEven) {
ans++;
}
// 把 nums[i] 纳入后缀
if (i % 2 == 0) {
sufEven += nums[i];
} else {
sufOdd += nums[i];
}
}
return ans;
}
public int waysToMakeFairRolling(int[] nums) {
int n = nums.length;
int Lodd = 0, Leven = 0; // 删除点左侧奇偶和(原下标)
int Rodd = 0, Reven = 0; // 删除点右侧奇偶和(原下标)
for (int i = 0; i < n; i++) {
if (i % 2 == 0) {
Reven += nums[i];
} else {
Rodd += nums[i];
}
}
int ans = 0;
for (int i = 0; i < n; i++) {
// 从右侧移除 nums[i]
if (i % 2 == 0) {
Reven -= nums[i];
} else {
Rodd -= nums[i];
}
// 删除 i 后:奇数下标和 = Lodd + Reven(右侧偶数位变奇);偶数下标和 = Leven + Rodd
if (Lodd + Reven == Leven + Rodd) {
ans++;
}
// 加入左侧
if (i % 2 == 0) {
Leven += nums[i];
} else {
Lodd += nums[i];
}
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
// 示例 1:1
System.out.println(sol.waysToMakeFair(new int[]{2, 1, 6, 4}) + " " + sol.waysToMakeFairRolling(new int[]{2, 1, 6, 4}));
// 示例 2:3
System.out.println(sol.waysToMakeFair(new int[]{1, 1, 1}) + " " + sol.waysToMakeFairRolling(new int[]{1, 1, 1}));
// 示例 3:0
System.out.println(sol.waysToMakeFair(new int[]{1, 2, 3}) + " " + sol.waysToMakeFairRolling(new int[]{1, 2, 3}));
// 附加:单元素
System.out.println(sol.waysToMakeFair(new int[]{5}) + " " + sol.waysToMakeFairRolling(new int[]{5}));
}
}
复杂度分析
复杂度:时间复杂度 O(n),空间复杂度 O(n)。
复杂度:时间复杂度 O(n),空间复杂度 O(1)。
1665. Minimum Initial Energy to Finish Tasks(完成所有任务的最少初始能量)
题目描述
任务 tasks[i] = `actuali`, `minimumi`:完成第 i 个任务耗费 actuali 能量,开始前需要至少 minimumi 能量。
可按任意顺序完成任务,返回完成所有任务的最少初始能量。
示例 1
tasks=[[1,2],[2,4],[4,8]],输出 8。
示例 2
tasks=[[1,3],[2,4],[10,11],[10,12],[8,9]],输出 32。
示例 3
tasks=[[1,7],[2,8],[3,9],[4,10],[5,11],[6,12]],输出 27。
约束:
- 1 <= tasks.length <= 10^5;1 <= actuali <= minimumi <= 10^4。
解题思路
解法一:贪心排序(主提交版本)
- 关键观察:若在能量 t 时执行某任务,完成后能量为 t - actuali,要求 t >= minimumi。
两个任务 a、b 顺序 (a 后 b) 需要初始 >= max(min_a, actual_a + min_b);
顺序 (b 后 a) 需要初始 >= max(min_b, actual_b + min_a)。
比较二者:若 min_a + actual_b > min_b + actual_a,则应先做 a。
即按 (minimumi - actuali) 降序(差值大的先做)。 - 排完序后模拟累加求峰值。
复杂度:时间复杂度 O(n log n),空间复杂度 O(log n)。
解法二:二分答案(对照) - 对初始能量二分,check(mid) 按贪心顺序模拟能否完成全部任务。
复杂度:时间复杂度 O(n log n log MAX),空间复杂度 O(log n)。
代码实现
java
import java.util.Arrays;
import java.util.Comparator;
public class Solution {
public int minimumEffort(int[][] tasks) {
// 按 (minimum - actual) 降序排序------消耗与门槛差越大越先做
Arrays.sort(tasks, new Comparator<int[]>() {
@Override
public int compare(int[] a, int[] b) {
return (b[1] - b[0]) - (a[1] - a[0]);
}
});
long energy = 0; // 峰值
long cur = 0; // 当前剩余
for (int[] t : tasks) {
if (cur < t[1]) {
energy += t[1] - cur;
cur = t[1];
}
cur -= t[0];
}
return (int) energy;
}
public int minimumEffortBinary(int[][] tasks) {
Arrays.sort(tasks, new Comparator<int[]>() {
@Override
public int compare(int[] a, int[] b) {
return (b[1] - b[0]) - (a[1] - a[0]);
}
});
int lo = 0, hi = 0;
for (int[] t : tasks) {
hi += t[0] + t[1];
}
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (canFinish(tasks, mid)) {
hi = mid;
} else {
lo = mid + 1;
}
}
return lo;
}
private boolean canFinish(int[][] tasks, long init) {
long cur = init;
for (int[] t : tasks) {
if (cur < t[1]) {
return false;
}
cur -= t[0];
}
return true;
}
public static void main(String[] args) {
Solution sol = new Solution();
// 示例 1:8
System.out.println(sol.minimumEffort(new int[][]{{1, 2}, {2, 4}, {4, 8}})
+ " " + sol.minimumEffortBinary(new int[][]{{1, 2}, {2, 4}, {4, 8}}));
// 示例 2:32
System.out.println(sol.minimumEffort(new int[][]{{1, 3}, {2, 4}, {10, 11}, {10, 12}, {8, 9}})
+ " " + sol.minimumEffortBinary(new int[][]{{1, 3}, {2, 4}, {10, 11}, {10, 12}, {8, 9}}));
// 示例 3:27
System.out.println(sol.minimumEffort(new int[][]{{1, 7}, {2, 8}, {3, 9}, {4, 10}, {5, 11}, {6, 12}})
+ " " + sol.minimumEffortBinary(new int[][]{{1, 7}, {2, 8}, {3, 9}, {4, 10}, {5, 11}, {6, 12}}));
}
}
复杂度分析
复杂度:时间复杂度 O(n log n),空间复杂度 O(log n)。
复杂度:时间复杂度 O(n log n log MAX),空间复杂度 O(log n)。
1666. Change the Root of a Binary Tree(改变二叉树的根)
题目描述
给定二叉树根 root 和叶子节点 leaf,修改 tree 使 leaf 成为新根。
对 leaf 到 root 路径上除 root 外的每个节点 cur:
- 若
cur有左子节点,则该子节点变为cur的右子节点(保证cur至多一个非路径子节点); cur的原父节点变为cur的左子节点。
返回新树的根,并正确设置(含新根)每个节点的parent指针。
示例 1
root=[3,5,1,6,2,0,8,null,null,7,4], leaf=7,
输出 [7,2,null,5,4,3,6,null,null,null,1,null,null,0,8]。
示例 2
leaf=0,输出 [0,1,null,3,8,5,null,null,null,6,2,null,null,7,4]。
约束:
- 节点数 2, 100;值唯一;
leaf存在于树中。
解题思路
解法一:从 leaf 向上迭代(主提交版本)
- prev 记录已翻转出的"新父"。对当前节点 cur:
① cur.parent 置为 prev;
② 若 cur.left 存在且不为 prev(也不是 cur 的原父------原父马上会成为左子),移到 cur.right;
③ cur.left 置为原父 parent(原父变左子);
④ prev = cur,cur 上移到 parent。 - 循环直到 cur == root:root.parent 置 null,返回原 leaf。
复杂度:时间复杂度 O(h),空间复杂度 O(1)。
解法二:递归(对照) - 自底向上递归同样操作。
复杂度:时间复杂度 O(h),空间复杂度 O(h)。
代码实现
java
public class Solution {
static class Node {
public int val;
public Node left;
public Node right;
public Node parent;
Node(int val) {
this.val = val;
}
}
public Node flipBinaryTree(Node root, Node leaf) {
Node cur = leaf;
Node prev = null;
while (cur != root) {
Node parent = cur.parent;
cur.parent = prev;
// 原左子(非 prev、非原父)移到右子
if (cur.left != null && cur.left != prev && cur.left != parent) {
cur.right = cur.left;
}
// 原父变为左子(若原左子恰为 prev 则被覆盖,因 prev 已是新父,不影响结构)
cur.left = parent;
prev = cur;
cur = parent;
}
return leaf;
}
public Node flipBinaryTreeRecursive(Node root, Node leaf) {
flip(leaf, null);
root.parent = null;
return leaf;
}
private void flip(Node cur, Node prev) {
Node parent = cur.parent;
cur.parent = prev;
if (cur.left != null && cur.left != prev && cur.left != parent) {
cur.right = cur.left;
}
cur.left = parent;
if (parent != null) {
flip(parent, cur);
}
}
public static void main(String[] args) {
Solution sol = new Solution();
// 构造示例树 [3,5,1,6,2,0,8,null,null,7,4]
Node n6 = new Node(6);
Node n7 = new Node(7);
Node n4 = new Node(4);
Node n2 = new Node(2);
n2.left = n7;
n2.right = n4;
n7.parent = n2;
n4.parent = n2;
Node n5 = new Node(5);
n5.left = n6;
n5.right = n2;
n6.parent = n5;
n2.parent = n5;
Node n0 = new Node(0);
Node n8 = new Node(8);
Node n1 = new Node(1);
n1.left = n0;
n1.right = n8;
n0.parent = n1;
n8.parent = n1;
Node root = new Node(3);
root.left = n5;
root.right = n1;
n5.parent = root;
n1.parent = root;
Node newRoot = sol.flipBinaryTree(root, n7);
// 期望结构:7.left=2, 2.left=5, 2.right=4, 5.left=3, 5.right=6, 1 悬挂在 3.right 下
System.out.println(newRoot.val + " parent=" + (newRoot.parent == null ? "null" : newRoot.parent.val));
System.out.println(newRoot.left.val); // 2
System.out.println(newRoot.left.left.val); // 5
System.out.println(newRoot.left.right.val); // 4
System.out.println(newRoot.left.left.left.val); // 3
System.out.println(newRoot.left.left.right.val); // 6
System.out.println(newRoot.parent == null ? "ok-root" : "bad-root");
}
}
复杂度分析
复杂度:时间复杂度 O(h),空间复杂度 O(1)。
复杂度:时间复杂度 O(h),空间复杂度 O(h)。
1667. Fix Names in a Table(修复表中的名字)
题目类型:数据库(SQL)
题目描述
表: Users
+----------------+---------+
| Column Name | Type |
+----------------+---------+
| user_id | int |
| name | varchar |
+----------------+---------+
user_id 是该表的主键(具有唯一值的列)。
该表包含用户的 ID 和名字。名字仅由小写和大写字符组成。
编写解决方案,修复名字,使得只有第一个字符是大写的,其余都是小写的。
返回按 user_id 排序的结果表。
返回结果格式示例如下。
示例 1:**
**输入:**
Users table:
+---------+-------+
| user_id | name |
+---------+-------+
| 1 | aLice |
| 2 | bOB |
+---------+-------+
**输出:**
+---------+-------+
| user_id | name |
+---------+-------+
| 1 | Alice |
| 2 | Bob |
+---------+-------+
解题思路
思考
姓名需首字母大写、其余小写。用 UPPER ( LEFT ( n a m e , 1 ) ) \texttt{UPPER}(\texttt{LEFT}(name,1)) UPPER(LEFT(name,1)) 与 LOWER ( SUBSTRING ( n a m e , 2 ) ) \texttt{LOWER}(\texttt{SUBSTRING}(name,2)) LOWER(SUBSTRING(name,2)) 拼接,再按 user_id \texttt{user\_id} user_id 排序。
考察CONCAT + 首字母大写:用 UPPER(LEFT(name,1)) + LOWER(SUBSTRING(name,2))重排用户表姓名,按 user_id 排序输出。
代码实现
sql
SELECT
user_id,
CONCAT(UPPER(LEFT(name, 1)), LOWER(SUBSTRING(name, 2))) AS name
FROM users
ORDER BY user_id;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1668. Maximum Repeating Substring(最大重复子字符串)
题目描述
字符串 word 连续重复 k 次形成的字符串若是 sequence 的子串,则 word 的重复值为 k。
返回 word 在 sequence 中的最大重复值;若 word 不是 sequence 的子串返回 0。
示例 1
sequence="ababc", word="ab",输出 2。
示例 2
sequence="ababc", word="ba",输出 1。
示例 3
sequence="ababc", word="ac",输出 0。
约束:
- 1 <=
len<= 100;仅含小写字母。
解题思路
解法一:拼接递增尝试(主提交版本)
- 从 k=1 开始把 word 重复 k 次得到 candidate,若 sequence.contains(candidate) 则继续增大 k。
- 第一个不满足的 k - 1 即答案。
复杂度:时间复杂度 O(n * (n/m)) 约 O(n^2/m),空间复杂度 O(k*m)。
解法二:DP 连续匹配计数(对照) - dpi 表示以 sequence 下标 i 结尾的连续 word 重复次数:若以 i 为结尾的 m 个字符等于 word,
且 i-m 位置可用,则 dpi = dpi-m + 1,否则 1。取全局最大。
复杂度:时间复杂度 O(n*m),空间复杂度 O(n)。
代码实现
java
public class Solution {
public int maxRepeating(String sequence, String word) {
int k = 0;
StringBuilder sb = new StringBuilder(word);
while (sequence.contains(sb.toString())) {
k++;
sb.append(word);
}
return k;
}
public int maxRepeatingDp(String sequence, String word) {
int n = sequence.length();
int m = word.length();
int[] dp = new int[n + 1];
int ans = 0;
for (int i = m; i <= n; i++) {
if (sequence.substring(i - m, i).equals(word)) {
dp[i] = dp[i - m] + 1;
ans = Math.max(ans, dp[i]);
}
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
// 示例 1:2
System.out.println(sol.maxRepeating("ababc", "ab") + " " + sol.maxRepeatingDp("ababc", "ab"));
// 示例 2:1
System.out.println(sol.maxRepeating("ababc", "ba") + " " + sol.maxRepeatingDp("ababc", "ba"));
// 示例 3:0
System.out.println(sol.maxRepeating("ababc", "ac") + " " + sol.maxRepeatingDp("ababc", "ac"));
// 附加:aa 中 a 重复
System.out.println(sol.maxRepeating("aa", "a") + " " + sol.maxRepeatingDp("aa", "a"));
}
}
复杂度分析
复杂度:时间复杂度 O(n * (n/m)) 约 O(n^2/m),空间复杂度 O(km)。
复杂度:时间复杂度 O(nm),空间复杂度 O(n)。
1669. Merge In Between Linked Lists(合并两个链表)
题目描述
给定 list1(n 个节点)与 list2(m 个节点)。删除 list1 中下标 a 到 b 的节点,
把 list2 接在被删除位置。返回结果链表头。
示例 1
list1=[10,1,13,6,9,5], a=3, b=4, list2=[1000000,1000001,1000002],输出 [10,1,13,1000000,1000001,1000002,5]。
示例 2
list1=[0,1,2,3,4,5,6], a=2, b=5, list2=[1000000,...,1000004],输出 [0,1,1000000,...,1000004,6]。
约束:
- 3 <= list1.length <= 10^4;1 <= list2.length <= 10^4;0 <=
a<=b< list1.length。
解题思路
解法一:双指针定位(主提交版本)
- 走到第 a-1 个节点 prev,记录第 b+1 个节点 tail;
- prev.next = list2 头;遍历 list2 到尾后接 tail。
复杂度:时间复杂度 O(n + m),空间复杂度 O(1)。
解法二:递归(对照) - 用计数器在递归中定位并拼接(演示函数式写法)。
复杂度:时间复杂度 O(n + m),空间复杂度 O(n)(递归栈)。
代码实现
java
public class Solution {
static class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
}
}
public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
ListNode prev = list1;
for (int i = 0; i < a - 1; i++) {
prev = prev.next;
}
ListNode tail = prev;
for (int i = 0; i <= b - a + 1; i++) {
tail = tail.next;
}
// tail 指向 b+1 节点
prev.next = list2;
ListNode cur = list2;
while (cur.next != null) {
cur = cur.next;
}
cur.next = tail;
return list1;
}
public ListNode mergeInBetweenRecursive(ListNode list1, int a, int b, ListNode list2) {
Helper h = new Helper();
return h.build(list1, a, b, list2, 0);
}
static class Helper {
ListNode build(ListNode node, int a, int b, ListNode list2, int idx) {
if (node == null) {
return null;
}
if (idx == a - 1) {
ListNode tmp = node;
for (int i = 0; i <= b - a + 1; i++) {
tmp = tmp.next;
}
ListNode tail = tmp;
node.next = list2;
ListNode c = list2;
while (c.next != null) {
c = c.next;
}
c.next = tail;
return node;
}
node.next = build(node.next, a, b, list2, idx + 1);
return node;
}
}
private static ListNode build(int[] arr) {
ListNode head = new ListNode(arr[0]);
ListNode cur = head;
for (int i = 1; i < arr.length; i++) {
cur.next = new ListNode(arr[i]);
cur = cur.next;
}
return head;
}
private static String toStr(ListNode head) {
StringBuilder sb = new StringBuilder();
while (head != null) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(head.val);
head = head.next;
}
return sb.toString();
}
public static void main(String[] args) {
Solution sol = new Solution();
// 示例 1:[10,1,13,1000000,1000001,1000002,5]
ListNode l1 = build(new int[]{10, 1, 13, 6, 9, 5});
ListNode l2 = build(new int[]{1000000, 1000001, 1000002});
System.out.println(toStr(sol.mergeInBetween(l1, 3, 4, l2)));
// 示例 2:[0,1,1000000,1000001,1000002,1000003,1000004,6]
ListNode l3 = build(new int[]{0, 1, 2, 3, 4, 5, 6});
ListNode l4 = build(new int[]{1000000, 1000001, 1000002, 1000003, 1000004});
System.out.println(toStr(sol.mergeInBetween(l3, 2, 5, l4)));
// 递归对照
ListNode l5 = build(new int[]{10, 1, 13, 6, 9, 5});
ListNode l6 = build(new int[]{1000000, 1000001, 1000002});
System.out.println(toStr(sol.mergeInBetweenRecursive(l5, 3, 4, l6)));
}
}
复杂度分析
复杂度:时间复杂度 O(n + m),空间复杂度 O(1)。
复杂度:时间复杂度 O(n + m),空间复杂度 O(n)(递归栈)。
1670. Design Front Middle Back Queue(设计前中后队列)
题目描述
设计 FrontMiddleBack 类,支持前、中、后三个位置的 push 与 pop:
pushFront/pushMiddle/pushBack(intval)popFront/popMiddle/popBack():删除并返回该位置元素;空队列返回 -1。- 当有两个中间位置时,选择靠前的位置(如 1,2,3,4,5
pushMiddle(6) -> 1,2,6,3,4,5;
从 1,2,3,4,5,6popMiddle-> 返回 3)。
示例
pushFront(1); pushBack(2); pushMiddle(3); pushMiddle(4);
popFront()=1; popMiddle()=3; popMiddle()=4; popBack()=2; popFront()=-1。
约束:
- 最多 1000 次操作。
解题思路
解法一:双端队列 + 平衡(主提交版本)
- 用两个 ArrayDeque 模拟左右两半,左半的"中间侧"始终不小于右半的"中间侧"(保持左 >= 右)。
- 每次操作后做 balance:若左 > 右 + 1,把左尾移到右头;若右 > 左,把右头移到左尾。
- pushMiddle 即 push 到左尾;popMiddle 即 pop 左尾(左 > 右或相等时都是中间位置确实在左尾)。
注意当整体长度为偶数且左 == 右时,中间偏左的元素位于左尾,正确。
复杂度:均摊 O(1),空间 O(n)。
解法二:ArrayList 模拟(对照) - 用 ArrayList,在中间位置插入/删除即 index = size()/2(偶数长度取前中间)。
适合操作少(1000 次)的场景。
复杂度:时间复杂度 O(n)(数组搬移),空间 O(n)。
代码实现
java
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
public class Solution {
static class FrontMiddleBackQueue {
private Deque<Integer> left = new ArrayDeque<>();
private Deque<Integer> right = new ArrayDeque<>();
public FrontMiddleBackQueue() {
}
public void pushFront(int val) {
left.addFirst(val);
balance();
}
public void pushMiddle(int val) {
left.addLast(val);
balance();
}
public void pushBack(int val) {
right.addLast(val);
balance();
}
public int popFront() {
if (left.isEmpty() && right.isEmpty()) {
return -1;
}
int res;
if (!left.isEmpty()) {
res = left.removeFirst();
} else {
res = right.removeFirst();
}
balance();
return res;
}
public int popMiddle() {
if (left.isEmpty() && right.isEmpty()) {
return -1;
}
int res;
if (left.size() == right.size()) {
// 偶数长度中间偏左:在右半头部
res = right.removeFirst();
} else {
// 奇数长度中间:在左半尾部
res = left.removeLast();
}
balance();
return res;
}
public int popBack() {
if (left.isEmpty() && right.isEmpty()) {
return -1;
}
int res;
if (!right.isEmpty()) {
res = right.removeLast();
} else {
res = left.removeLast();
}
balance();
return res;
}
private void balance() {
if (left.size() > right.size() + 1) {
right.addFirst(left.removeLast());
} else if (right.size() > left.size()) {
left.addLast(right.removeFirst());
}
}
}
static class FrontMiddleBackQueueList {
private List<Integer> list = new ArrayList<>();
public FrontMiddleBackQueueList() {
}
public void pushFront(int val) {
list.add(0, val);
}
public void pushMiddle(int val) {
list.add(list.size() / 2, val);
}
public void pushBack(int val) {
list.add(val);
}
public int popFront() {
if (list.isEmpty()) {
return -1;
}
return list.remove(0);
}
public int popMiddle() {
if (list.isEmpty()) {
return -1;
}
return list.remove((list.size() - 1) / 2);
}
public int popBack() {
if (list.isEmpty()) {
return -1;
}
return list.remove(list.size() - 1);
}
}
public static void main(String[] args) {
// 示例验证
FrontMiddleBackQueue q = new FrontMiddleBackQueue();
q.pushFront(1); // [1]
q.pushBack(2); // [1,2]
q.pushMiddle(3); // [1,3,2]
q.pushMiddle(4); // [1,4,3,2]
System.out.println(q.popFront()); // 1
System.out.println(q.popMiddle()); // 3
System.out.println(q.popMiddle()); // 4
System.out.println(q.popBack()); // 2
System.out.println(q.popFront()); // -1
System.out.println("---");
FrontMiddleBackQueueList q2 = new FrontMiddleBackQueueList();
q2.pushFront(1);
q2.pushBack(2);
q2.pushMiddle(3);
q2.pushMiddle(4);
System.out.println(q2.popFront());
System.out.println(q2.popMiddle());
System.out.println(q2.popMiddle());
System.out.println(q2.popBack());
System.out.println(q2.popFront());
}
}
复杂度分析
复杂度:均摊 O(1),空间 O(n)。
复杂度:时间复杂度 O(n)(数组搬移),空间 O(n)。
1671. Minimum Number of Removals to Make Mountain Array(得到山形数组的最少删除次数)
题目描述
山形数组 arr:arr.length >= 3 且存在峰值下标 i(0 < i < n-1),
满足 arr[0] < ... < arr[i] > arr[i+1] > ... > arr[n-1](严格递增后严格递减)。
给定数组 nums,返回变成山形数组的最少删除次数。
示例 1
nums=[1,3,1],输出 0。
示例 2
nums=[2,1,1,5,6,2,3,1],输出 3(保留 [1,5,6,3,1])。
约束:
- 3 <=
n<= 1000;题目保证删除一些元素后一定能得到山形数组。
解题思路
解法一:LIS 双方向 DP(主提交版本)
- 最长保留 = 以某 i 为峰的最长严格递增前缀长度 + 最长严格递减后缀长度 - 1。
- 用 O(n^2) 的经典 LIS(严格递增),分别算 lefti、righti。
最少删除 = n - maxLen。
复杂度:时间复杂度 O(n^2),空间复杂度 O(n)。
解法二:二分 LIS 优化(对照) - 用 patience/greedy 二分求严格 LIS 的长度数组(O(n log n)),再取峰。
复杂度:时间复杂度 O(n log n),空间复杂度 O(n)。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
public int minimumMountainRemovals(int[] nums) {
int n = nums.length;
int[] left = new int[n];
int[] right = new int[n];
Arrays.fill(left, 1);
Arrays.fill(right, 1);
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
left[i] = Math.max(left[i], left[j] + 1);
}
}
}
for (int i = n - 1; i >= 0; i--) {
for (int j = n - 1; j > i; j--) {
if (nums[j] < nums[i]) {
right[i] = Math.max(right[i], right[j] + 1);
}
}
}
int best = 0;
for (int i = 1; i < n - 1; i++) {
if (left[i] >= 2 && right[i] >= 2) {
best = Math.max(best, left[i] + right[i] - 1);
}
}
return n - best;
}
public int minimumMountainRemovalsBinary(int[] nums) {
int n = nums.length;
int[] left = lisLen(nums, true);
int[] right = lisLen(nums, false);
int best = 0;
for (int i = 1; i < n - 1; i++) {
if (left[i] >= 2 && right[i] >= 2) {
best = Math.max(best, left[i] + right[i] - 1);
}
}
return n - best;
}
private int[] lisLen(int[] nums, boolean forward) {
int n = nums.length;
int[] len = new int[n];
List<Integer> tails = new ArrayList<>();
if (forward) {
for (int i = 0; i < n; i++) {
// 严格递增:>= 则替换
int pos = lowerBound(tails, nums[i]);
if (pos == tails.size()) {
tails.add(nums[i]);
} else {
tails.set(pos, nums[i]);
}
len[i] = tails.size();
}
} else {
for (int i = n - 1; i >= 0; i--) {
int pos = lowerBound(tails, nums[i]);
if (pos == tails.size()) {
tails.add(nums[i]);
} else {
tails.set(pos, nums[i]);
}
len[i] = tails.size();
}
}
return len;
}
private int lowerBound(List<Integer> tails, int val) {
int lo = 0, hi = tails.size();
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (tails.get(mid) < val) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
public static void main(String[] args) {
Solution sol = new Solution();
// 示例 1:0
System.out.println(sol.minimumMountainRemovals(new int[]{1, 3, 1})
+ " " + sol.minimumMountainRemovalsBinary(new int[]{1, 3, 1}));
// 示例 2:3
System.out.println(sol.minimumMountainRemovals(new int[]{2, 1, 1, 5, 6, 2, 3, 1})
+ " " + sol.minimumMountainRemovalsBinary(new int[]{2, 1, 1, 5, 6, 2, 3, 1}));
// 附加
System.out.println(sol.minimumMountainRemovals(new int[]{9, 8, 1, 7, 6, 5, 4, 3, 2, 1})
+ " " + sol.minimumMountainRemovalsBinary(new int[]{9, 8, 1, 7, 6, 5, 4, 3, 2, 1}));
}
}
复杂度分析
复杂度:时间复杂度 O(n^2),空间复杂度 O(n)。
复杂度:时间复杂度 O(n log n),空间复杂度 O(n)。
1672. Richest Customer Wealth(最富有客户的资产总量)
题目描述
给定 m x n 整数网格 accounts,accounts[i][j] 是第 i 位客户在第 j 家银行的资产。
返回资产总量最大的客户的资产总量。
示例 1
accounts=[[1,2,3],[3,2,1]],输出 6。
示例 2
accounts=[[1,5],[7,3],[3,5]],输出 10。
示例 3
accounts=[[2,8,7],[7,1,3],[1,9,5]],输出 17。
约束:
- 1 <=
m,n<= 50;1 <= accountsij <= 100。
解题思路
解法一:逐行求和取最大(主提交版本)
- 对每行求和,维护最大值。
复杂度:时间复杂度 O(m*n),空间复杂度 O(1)。
解法二:流式 API(对照) - 用 IntStream 求和演示函数式写法。
复杂度:时间复杂度 O(m*n),空间复杂度 O(1)。
代码实现
java
import java.util.Arrays;
public class Solution {
public int maximumWealth(int[][] accounts) {
int max = 0;
for (int[] row : accounts) {
int sum = 0;
for (int v : row) {
sum += v;
}
max = Math.max(max, sum);
}
return max;
}
public int maximumWealthStream(int[][] accounts) {
int max = 0;
for (int[] row : accounts) {
max = Math.max(max, Arrays.stream(row).sum());
}
return max;
}
public static void main(String[] args) {
Solution sol = new Solution();
// 示例 1:6
System.out.println(sol.maximumWealth(new int[][]{{1, 2, 3}, {3, 2, 1}})
+ " " + sol.maximumWealthStream(new int[][]{{1, 2, 3}, {3, 2, 1}}));
// 示例 2:10
System.out.println(sol.maximumWealth(new int[][]{{1, 5}, {7, 3}, {3, 5}})
+ " " + sol.maximumWealthStream(new int[][]{{1, 5}, {7, 3}, {3, 5}}));
// 示例 3:17
System.out.println(sol.maximumWealth(new int[][]{{2, 8, 7}, {7, 1, 3}, {1, 9, 5}})
+ " " + sol.maximumWealthStream(new int[][]{{2, 8, 7}, {7, 1, 3}, {1, 9, 5}}));
}
}
复杂度分析
复杂度:时间复杂度 O(m*n),空间复杂度 O(1)。
1673. Find the Most Competitive Subsequence(找出最具竞争力的子序列)
题目描述
给定整数数组 nums 和正整数 k,返回长度为 k 且最具竞争力的 nums 子序列。
竞争力定义:在两个长度相同子序列第一个不同位置上,数字较小者更具竞争力。
示例 1
nums=[3,5,2,6], k=2,输出 [2,6]。
示例 2
nums=[2,4,3,3,5,4,9,6], k=4,输出 [2,3,3,4]。
约束:
- 1 <=
nums.length<= 10^5;0 <=nums[i]<= 10^9;1 <=k<=nums.length。
解题思路
解法一:单调栈(主提交版本)
- 维护单调递增栈。遍历每个 x:栈不空且栈顶 > x 且"剩余元素数 + 栈大小 > k"(仍能凑够 k 个),
则弹栈。最后入栈。 - 结束取前 k 个。
复杂度:时间复杂度 O(n),空间复杂度 O(k)。
解法二:贪心选最小起点(对照) - 每次在合法窗口内选最小元素作为下一位置起点(窗口右端须留够剩余位数)。
用稀疏表/单调队列优化可 O(n),此处给出 O(nk) 朴素对照。
复杂度:时间复杂度 O(nk),空间复杂度 O(k)(对照)。
代码实现
java
import java.util.ArrayDeque;
import java.util.Deque;
public class Solution {
public int[] mostCompetitive(int[] nums, int k) {
Deque<Integer> stack = new ArrayDeque<>();
int n = nums.length;
for (int i = 0; i < n; i++) {
int x = nums[i];
// 可弹性:栈大小 - 1 + 剩余元素数 >= k
while (!stack.isEmpty() && stack.peek() > x && stack.size() - 1 + (n - i) >= k) {
stack.pop();
}
if (stack.size() < k) {
stack.push(x);
}
}
int[] res = new int[k];
for (int i = k - 1; i >= 0; i--) {
res[i] = stack.pop();
}
return res;
}
public int[] mostCompetitiveNaive(int[] nums, int k) {
int n = nums.length;
int[] res = new int[k];
int start = 0;
for (int i = 0; i < k; i++) {
int end = n - (k - i); // 该位置可取的最大起点(留够剩余)
int bestIdx = start;
for (int j = start + 1; j <= end; j++) {
if (nums[j] < nums[bestIdx]) {
bestIdx = j;
}
}
res[i] = nums[bestIdx];
start = bestIdx + 1;
}
return res;
}
private static String join(int[] a) {
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < a.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(a[i]);
}
return sb.append("]").toString();
}
public static void main(String[] args) {
Solution sol = new Solution();
// 示例 1:[2,6]
System.out.println(join(sol.mostCompetitive(new int[]{3, 5, 2, 6}, 2))
+ " " + join(sol.mostCompetitiveNaive(new int[]{3, 5, 2, 6}, 2)));
// 示例 2:[2,3,3,4]
System.out.println(join(sol.mostCompetitive(new int[]{2, 4, 3, 3, 5, 4, 9, 6}, 4))
+ " " + join(sol.mostCompetitiveNaive(new int[]{2, 4, 3, 3, 5, 4, 9, 6}, 4)));
// 附加:k = n
System.out.println(join(sol.mostCompetitive(new int[]{1, 2, 3}, 3))
+ " " + join(sol.mostCompetitiveNaive(new int[]{1, 2, 3}, 3)));
}
}
复杂度分析
复杂度:时间复杂度 O(n),空间复杂度 O(k)。
复杂度:时间复杂度 O(n*k),空间复杂度 O(k)(对照)。
1674. Minimum Moves to Make Array Complementary(使数组互补的最少操作次数)
题目描述
偶长数组 nums 若对所有 i 有 nums[i] + nums[n-1-i] == 同一值,则数组互补。
每次操作可把任意一个元素替换为 1 到 limit 之间的另一个整数。
返回使数组互补的最少操作次数。
示例 1
nums=[1,2,4,3], limit=4,输出 1。
示例 2
nums=[1,2,2,1], limit=2,输出 2。
示例 3
nums=[1,2,1,2], limit=2,输出 0。
约束:
n为偶数且 2 <=n<= 10^5;1 <=nums[i]<=limit<= 10^5。
解题思路
解法一:差分数组 + 区间覆盖(主提交版本)
- 每对 (a, b)(a <= b)若目标和为 s,操作次数为:
0 次:s == a+b;1 次:s in 1+a, limit+b 且 s != a+b(改一个);
2 次:其余 s in 2, 2\*limit。 - 用差分数组 diff2...2\*limit 表示每个可能和的目标覆盖,最后前缀和得到各 s 的总代价,取最小。
复杂度:时间复杂度 O(n + limit),空间复杂度 O(limit)。
解法二:暴力枚举目标和(对照) - 枚举 s 从 2 到 2*limit,对每对求代价求和,取最小(适合小数据验证)。
复杂度:时间复杂度 O(limit * n/2),空间复杂度 O(1)(对照)。
代码实现
java
import java.util.Arrays;
public class Solution {
public int minMoves(int[] nums, int limit) {
int n = nums.length;
int[] diff = new int[2 * limit + 3];
for (int i = 0; i < n / 2; i++) {
int a = nums[i], b = nums[n - 1 - i];
int s = a + b;
// 2 次操作区间:[2, s-1] 与 [s+1, 2*limit]
// 1 次操作区间:[min(a,b)+1, max(a,b)+limit]
diff[2] += 2;
diff[2 * limit + 1] -= 2;
// 从 2 次降为 1 次:区间 [lo, hi] 减 1
int lo = Math.min(a, b) + 1;
int hi = Math.max(a, b) + limit;
diff[lo] -= 1;
diff[hi + 1] += 1;
// 特殊点 s:再减 1(0 次)
diff[s] -= 1;
diff[s + 1] += 1;
}
int ans = n;
int cur = 0;
for (int s = 2; s <= 2 * limit; s++) {
cur += diff[s];
ans = Math.min(ans, cur);
}
return ans;
}
public int minMovesBrute(int[] nums, int limit) {
int n = nums.length;
int ans = n;
for (int s = 2; s <= 2 * limit; s++) {
int cost = 0;
for (int i = 0; i < n / 2; i++) {
int a = nums[i], b = nums[n - 1 - i];
if (a + b == s) {
continue;
}
// 能否一次操作达成?
int l = Math.min(a, b) + 1;
int r = Math.max(a, b) + limit;
if (s >= l && s <= r) {
cost += 1;
} else {
cost += 2;
}
}
ans = Math.min(ans, cost);
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
// 示例 1:1
System.out.println(sol.minMoves(new int[]{1, 2, 4, 3}, 4) + " " + sol.minMovesBrute(new int[]{1, 2, 4, 3}, 4));
// 示例 2:2
System.out.println(sol.minMoves(new int[]{1, 2, 2, 1}, 2) + " " + sol.minMovesBrute(new int[]{1, 2, 2, 1}, 2));
// 示例 3:0
System.out.println(sol.minMoves(new int[]{1, 2, 1, 2}, 2) + " " + sol.minMovesBrute(new int[]{1, 2, 1, 2}, 2));
// 附加
System.out.println(sol.minMoves(new int[]{1, 1, 1, 1}, 1) + " " + sol.minMovesBrute(new int[]{1, 1, 1, 1}, 1));
}
}
复杂度分析
复杂度:时间复杂度 O(n + limit),空间复杂度 O(limit)。
复杂度:时间复杂度 O(limit * n/2),空间复杂度 O(1)(对照)。
1675. Minimize Deviation in Array(数组的最小偏移量)
题目描述
对数组 nums 的任意元素可执行任意次两类操作:
- 偶数除以 2;奇数乘 2。
数组偏移量 = 任意两元素的最大差值。返回经过某些操作后能获得的最小偏移量。
示例 1
nums=[1,2,3,4],输出 1。
示例 2
nums=[4,1,5,20,3],输出 3。
示例 3
nums=[2,10,8],输出 3。
约束:
- 2 <=
n<= 5*10^4;1 <=nums[i]<= 10^9。
解题思路
解法一:大根堆(主提交版本)
- 关键转化:奇数乘 2 只会让数增大(成为偶数),故先把所有奇数乘 2,使所有数都是偶数;
之后只允许"偶数除以 2"操作(把数调小)。 - 维护当前最大值(大根堆),每次把最大值除以 2(若为偶数),更新最小偏移;
直到最大值变为奇数无法再除。
复杂度:时间复杂度 O(n log n * log maxV),空间复杂度 O(n)。
解法二:排序 + 枚举截断(对照) - 把所有数向上扩展成"可行值集合"(奇数可乘 2),枚举下限 L 取每个数可行值中 >= L 的最小值,
求 (max - L) 最小值。用排序 + 指针维护。
复杂度:O(n log n * per-number-domain),仅演示思路。
代码实现
java
import java.util.PriorityQueue;
public class Solution {
public int minimumDeviation(int[] nums) {
PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> b - a);
int min = Integer.MAX_VALUE;
for (int v : nums) {
int cur = v;
if (cur % 2 == 1) {
cur *= 2; // 奇数乘 2 变成偶数(只可能到达更大的值)
}
pq.offer(cur);
min = Math.min(min, cur);
}
int ans = Integer.MAX_VALUE;
while (true) {
int max = pq.poll();
ans = Math.min(ans, max - min);
if (max % 2 == 1) {
// 最大值已是奇数不能再除
break;
}
max /= 2;
min = Math.min(min, max);
pq.offer(max);
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
// 示例 1:1
System.out.println(sol.minimumDeviation(new int[]{1, 2, 3, 4}));
// 示例 2:3
System.out.println(sol.minimumDeviation(new int[]{4, 1, 5, 20, 3}));
// 示例 3:3
System.out.println(sol.minimumDeviation(new int[]{2, 10, 8}));
// 附加
System.out.println(sol.minimumDeviation(new int[]{1} ));
}
}
复杂度分析
复杂度:时间复杂度 O(n log n * log maxV),空间复杂度 O(n)。
复杂度:O(n log n * per-number-domain),仅演示思路。
1676. Lowest Common Ancestor of a Binary Tree IV(二叉树的最近公共祖先 IV)
题目描述
给定二叉树根 root 和 TreeNode 数组 nodes,返回 nodes 中所有节点的最近公共祖先(LCA)。
nodes 中所有节点都存在且值互不相同,树中所有值也互不相同。
示例 1
root=[3,5,1,6,2,0,8,null,null,7,4], nodes=[4,7],输出 2。
示例 2
nodes=[1],输出 1。
示例 3
nodes=[7,6,2,4],输出 5。
示例 4
nodes=[0..8] 全部,输出 3。
约束:
- 节点数 1, 10\^4;
nodes[i]均存在且互不相同。
解题思路
解法一:目标集合 + 后序(主提交版本)
- 把 nodes 的值放入集合 target。
- 后序遍历:若当前节点在 target 中返回节点;否则若左右都非空返回当前节点,否则返回非空侧。
复杂度:时间复杂度 O(n),空间复杂度 O(h)。
解法二:父指针路径交集(对照) - 建父指针映射,从每个 nodesi 向上标记父链,取最先的交点。
- 实现为"对第一个节点建链集合,再沿第二个节点向上找首个交集",推广到全部节点:
迭代合并(找到前两个的 LCA 后再与第三个求 LCA...)。
复杂度:时间复杂度 O(n + k*h),空间复杂度 O(n)。
代码实现
java
import java.util.HashSet;
import java.util.Set;
public class Solution {
static class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int val) {
this.val = val;
}
}
private Set<Integer> target;
private TreeNode ans;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode[] nodes) {
target = new HashSet<>();
for (TreeNode t : nodes) {
target.add(t.val);
}
ans = null;
dfs(root);
return ans;
}
private boolean dfs(TreeNode node) {
if (node == null) {
return false;
}
boolean inLeft = dfs(node.left);
boolean inRight = dfs(node.right);
boolean self = target.contains(node.val);
if ((inLeft && inRight) || (self && (inLeft || inRight))) {
if (ans == null) {
ans = node;
}
return true;
}
return self || inLeft || inRight;
}
public static void main(String[] args) {
Solution sol = new Solution();
// 构建示例树:[3,5,1,6,2,0,8,null,null,7,4]
TreeNode n6 = new TreeNode(6);
TreeNode n7 = new TreeNode(7);
TreeNode n4 = new TreeNode(4);
TreeNode n2 = new TreeNode(2);
n2.left = n7;
n2.right = n4;
TreeNode n5 = new TreeNode(5);
n5.left = n6;
n5.right = n2;
TreeNode n0 = new TreeNode(0);
TreeNode n8 = new TreeNode(8);
TreeNode n1 = new TreeNode(1);
n1.left = n0;
n1.right = n8;
TreeNode root = new TreeNode(3);
root.left = n5;
root.right = n1;
// 示例 1:nodes=[4,7] -> 2
TreeNode r1 = sol.lowestCommonAncestor(root, new TreeNode[]{n4, n7});
System.out.println(r1 == null ? "null" : r1.val);
// 示例 3:nodes=[7,6,2,4] -> 5
TreeNode r3 = sol.lowestCommonAncestor(root, new TreeNode[]{n7, n6, n2, n4});
System.out.println(r3 == null ? "null" : r3.val);
// 示例 4:全部 -> 3
TreeNode r4 = sol.lowestCommonAncestor(root, new TreeNode[]{n0, n1, n2, root, n4, n5, n6, n7, n8});
System.out.println(r4 == null ? "null" : r4.val);
}
}
复杂度分析
复杂度:时间复杂度 O(n),空间复杂度 O(h)。
复杂度:时间复杂度 O(n + k*h),空间复杂度 O(n)。
1677. Product's Worth Over Invoices(发票中的产品金额)
题目类型:数据库(SQL)
题目描述
Product 表:
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| product_id | int |
| name | varchar |
+-------------+---------+
product_id 是这张表的主键
表中含有产品 id 、产品名称。产品名称都是小写的英文字母,产品名称都是唯一的
Invoice 表:
+-------------+------+
| Column Name | Type |
+-------------+------+
| invoice_id | int |
| product_id | int |
| rest | int |
| paid | int |
| canceled | int |
| refunded | int |
+-------------+------+
invoice_id 发票 id ,是这张表的主键
product_id 产品 id
rest 应缴款项
paid 已支付金额
canceled 已取消金额
refunded 已退款金额
要求写一个SQL查询,对于所有产品,返回每个产品的产品名称,以及全部发票累计的总应缴款项、总已支付金额、总已取消金额、总已退款金额。
查询结果按 product_name 排序
示例:
Product 表:
+------------+-------+
| product_id | name |
+------------+-------+
| 0 | ham |
| 1 | bacon |
+------------+-------+
Invoice table:
+------------+------------+------+------+----------+----------+
| invoice_id | product_id | rest | paid | canceled | refunded |
+------------+------------+------+------+----------+----------+
| 23 | 0 | 2 | 0 | 5 | 0 |
| 12 | 0 | 0 | 4 | 0 | 3 |
| 1 | 1 | 1 | 1 | 0 | 1 |
| 2 | 1 | 1 | 0 | 1 | 1 |
| 3 | 1 | 0 | 1 | 1 | 1 |
| 4 | 1 | 1 | 1 | 1 | 0 |
+------------+------------+------+------+----------+----------+
Result 表:
+-------+------+------+----------+----------+
| name | rest | paid | canceled | refunded |
+-------+------+------+----------+----------+
| bacon | 3 | 3 | 3 | 3 |
| ham | 2 | 4 | 5 | 3 |
+-------+------+------+----------+----------+
- bacon 的总应缴款项为 1 + 1 + 0 + 1 = 3
- bacon 的总已支付金额为 1 + 0 + 1 + 1 = 3
- bacon 的总已取消金额为 0 + 1 + 1 + 1 = 3
- bacon 的总已退款金额为 1 + 1 + 1 + 0 = 3
- ham 的总应缴款项为 2 + 0 = 2
- ham 的总已支付金额为 0 + 4 = 4
- ham 的总已取消金额为 5 + 0 = 5
- ham 的总已退款金额为 0 + 3 = 3
解题思路
思考
按产品汇总发票的 rest/paid/canceled/refunded,没有发票的产品也应出现且金额为 0 0 0。
从 Product \texttt{Product} Product 左连接 Invoice \texttt{Invoice} Invoice,按 product_id \texttt{product\_id} product_id 分组求和,用 IFNULL \texttt{IFNULL} IFNULL 把空和变成 0 0 0,再按名称排序。
考察LEFT JOIN + 条件聚合:发票表左连接产品表,按产品分组用 SUM(CASE ...)计算 2020 年销售额(rest 加权),无发票产品补 0。
代码实现
sql
# Write your MySQL query statement below
SELECT
name,
IFNULL(SUM(rest), 0) AS rest,
IFNULL(SUM(paid), 0) AS paid,
IFNULL(SUM(canceled), 0) AS canceled,
IFNULL(SUM(refunded), 0) AS refunded
FROM
Product
LEFT JOIN Invoice USING (product_id)
GROUP BY product_id
ORDER BY name;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1678. Goal Parser Interpretation(设计 Goal 解析器)
题目描述
解析 command 字符串:"G" -> "G","()" -> "o","(al)" -> "al",按原顺序拼接。
command 由 "G"、 "()" 和 "(al)" 按某种顺序组成。
示例 1
command="G()(al)",输出 "Goal"。
示例 2
command="G()()()()(al)",输出 "Gooooal"。
示例 3
command="(al)G(al)()()G",输出 "alGalooG"。
约束:
- 1 <= command.length <= 100。
解题思路
解法一:单指针扫描(主提交版本)
- 遇到 'G' 输出 "G";遇到 '(' 看下一字符:')' 输出 "o",'a' 则输出 "al" 并跳 4 位。
复杂度:时间复杂度 O(n),空间复杂度 O(1)(不计输出)。
解法二:String replace(对照) - 直接用 replaceAll 替换两次,直观。
复杂度:时间复杂度 O(n),空间复杂度 O(n)。
代码实现
java
public class Solution {
public String interpret(String command) {
StringBuilder sb = new StringBuilder();
int i = 0;
int n = command.length();
while (i < n) {
char c = command.charAt(i);
if (c == 'G') {
sb.append('G');
i++;
} else { // '('
if (command.charAt(i + 1) == ')') {
sb.append('o');
i += 2;
} else {
sb.append("al");
i += 4;
}
}
}
return sb.toString();
}
public String interpretReplace(String command) {
return command.replace("()", "o").replace("(al)", "al");
}
public static void main(String[] args) {
Solution sol = new Solution();
// 示例 1:Goal
System.out.println(sol.interpret("G()(al)") + " " + sol.interpretReplace("G()(al)"));
// 示例 2:Gooooal
System.out.println(sol.interpret("G()()()()(al)") + " " + sol.interpretReplace("G()()()()(al)"));
// 示例 3:alGalooG
System.out.println(sol.interpret("(al)G(al)()()G") + " " + sol.interpretReplace("(al)G(al)()()G"));
}
}
复杂度分析
复杂度:时间复杂度 O(n),空间复杂度 O(1)(不计输出)。
复杂度:时间复杂度 O(n),空间复杂度 O(n)。
1679. Max Number of K-Sum Pairs(K 和数对的最大数目)
题目描述
每一步操作中,从数组中选出和为 k 的两个整数并移出。
返回可以执行的最大操作数。
示例 1
nums=[1,2,3,4], k=5,输出 2。
示例 2
nums=[3,1,3,4,3], k=6,输出 1。
约束:
- 1 <=
nums.length<= 10^5;1 <=nums[i]<= 10^9;1 <=k<= 10^9。
解题思路
解法一:哈希表计数(主提交版本)
- 遍历每个 x:若 k - x 已有计数,则配对并减计数;否则 x 入表。
复杂度:时间复杂度 O(n),空间复杂度 O(n)。
解法二:排序 + 双指针(对照) - 排序后左右指针向中间配对。
复杂度:时间复杂度 O(n log n),空间复杂度 O(log n)。
代码实现
java
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class Solution {
public int maxOperations(int[] nums, int k) {
Map<Integer, Integer> cnt = new HashMap<>();
int ans = 0;
for (int x : nums) {
int need = k - x;
Integer c = cnt.get(need);
if (c != null && c > 0) {
cnt.put(need, c - 1);
ans++;
} else {
cnt.put(x, cnt.getOrDefault(x, 0) + 1);
}
}
return ans;
}
public int maxOperationsSort(int[] nums, int k) {
Arrays.sort(nums);
int ans = 0;
int lo = 0, hi = nums.length - 1;
while (lo < hi) {
int sum = nums[lo] + nums[hi];
if (sum == k) {
ans++;
lo++;
hi--;
} else if (sum < k) {
lo++;
} else {
hi--;
}
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
// 示例 1:2
System.out.println(sol.maxOperations(new int[]{1, 2, 3, 4}, 5)
+ " " + sol.maxOperationsSort(new int[]{1, 2, 3, 4}, 5));
// 示例 2:1
System.out.println(sol.maxOperations(new int[]{3, 1, 3, 4, 3}, 6)
+ " " + sol.maxOperationsSort(new int[]{3, 1, 3, 4, 3}, 6));
// 附加:k/2 相同数字
System.out.println(sol.maxOperations(new int[]{2, 2, 2, 2}, 4)
+ " " + sol.maxOperationsSort(new int[]{2, 2, 2, 2}, 4));
}
}
复杂度分析
复杂度:时间复杂度 O(n),空间复杂度 O(n)。
复杂度:时间复杂度 O(n log n),空间复杂度 O(log n)。
1680. Concatenation of Consecutive Binary Numbers(连接连续二进制数字)
题目描述
将 1 到 n 的二进制表示连接起来,返回连接结果对应的十进制数字对 10^9+7 取模的结果。
示例 1
n=1,输出 1。
示例 2
n=3,输出 27("1"+"10"+"11" = "11011")。
示例 3
n=12,输出 505379714。
约束:
- 1 <=
n<= 10^5。
解题思路
解法一:位运算递推(主提交版本)
- 记 ans 为当前拼接值。拼接数字 x 的二进制长度 len = bitLength(x),
ans = (ans * 2^len + x) % MOD。 - 用循环快速幂(或直接 Math.pow 不精确,用二分幂)求 2^len % MOD。
复杂度:时间复杂度 O(n log n)(每次快速幂),空间 O(1)。
解法二:预计算 2 的幂优化(对照) - 维护 shift 步长:当 x 是 2 的幂时,len 增加 1,shift = shift * 2 % MOD;
ans = (ans * shift + x) % MOD,O(n)。
复杂度:时间复杂度 O(n),空间 O(1)。
代码实现
java
public class Solution {
private static final int MOD = 1000000007;
public int concatenatedBinary(int n) {
long ans = 0;
for (int i = 1; i <= n; i++) {
int len = Integer.toBinaryString(i).length();
ans = (ans * pow2(len) + i) % MOD;
}
return (int) ans;
}
private long pow2(int len) {
long res = 1, base = 2;
while (len > 0) {
if ((len & 1) == 1) {
res = res * base % MOD;
}
base = base * base % MOD;
len >>= 1;
}
return res;
}
public int concatenatedBinaryFast(int n) {
long ans = 0;
long shift = 1; // 2^len
for (int i = 1; i <= n; i++) {
if ((i & (i - 1)) == 0) { // i 是 2 的幂,位数 +1
shift = shift * 2 % MOD;
}
ans = (ans * shift + i) % MOD;
}
return (int) ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
// 示例 1:1
System.out.println(sol.concatenatedBinary(1) + " " + sol.concatenatedBinaryFast(1));
// 示例 2:27
System.out.println(sol.concatenatedBinary(3) + " " + sol.concatenatedBinaryFast(3));
// 示例 3:505379714
System.out.println(sol.concatenatedBinary(12) + " " + sol.concatenatedBinaryFast(12));
// 附加
System.out.println(sol.concatenatedBinary(4) + " " + sol.concatenatedBinaryFast(4));
}
}
复杂度分析
复杂度:时间复杂度 O(n log n)(每次快速幂),空间 O(1)。
复杂度:时间复杂度 O(n),空间 O(1)。