LeetCode 1381-1400. 题解合集
文章目录
- [LeetCode 1381-1400. 题解合集](#LeetCode 1381-1400. 题解合集)
-
- [1381. Design a Stack With Increment Operation(设计一个支持增量操作的栈)](#1381. Design a Stack With Increment Operation(设计一个支持增量操作的栈))
- [1382. Balance a Binary Search Tree(将二叉搜索树变平衡)](#1382. Balance a Binary Search Tree(将二叉搜索树变平衡))
- [1383. Maximum Performance of a Team(最大的团队表现值)](#1383. Maximum Performance of a Team(最大的团队表现值))
- [1384. Total Sales Amount by Year(按年度列出销售总额 🔒)](#1384. Total Sales Amount by Year(按年度列出销售总额 🔒))
- [1385. Find the Distance Value Between Two Arrays(两个数组间的距离值)](#1385. Find the Distance Value Between Two Arrays(两个数组间的距离值))
- [1386. Cinema Seat Allocation(安排电影院座位)](#1386. Cinema Seat Allocation(安排电影院座位))
- [1387. Sort Integers by The Power Value(将整数按权重排序)](#1387. Sort Integers by The Power Value(将整数按权重排序))
- [1388. Pizza With 3n Slices(3n 块披萨)](#1388. Pizza With 3n Slices(3n 块披萨))
- [1389. Create Target Array in the Given Order(按既定顺序创建目标数组)](#1389. Create Target Array in the Given Order(按既定顺序创建目标数组))
- [1390. Four Divisors(四因数)](#1390. Four Divisors(四因数))
- [1391. Check if There is a Valid Path in a Grid(检查网格中是否存在有效路径)](#1391. Check if There is a Valid Path in a Grid(检查网格中是否存在有效路径))
- [1392. Longest Happy Prefix(最长快乐前缀)](#1392. Longest Happy Prefix(最长快乐前缀))
- [1393. Capital Gain/Loss(股票的资本损益)](#1393. Capital Gain/Loss(股票的资本损益))
- [1394. Find Lucky Integer in an Array(找出数组中的幸运数字)](#1394. Find Lucky Integer in an Array(找出数组中的幸运数字))
- [1395. Count Number of Teams(统计作战单位数)](#1395. Count Number of Teams(统计作战单位数))
- [1396. Design Underground System(设计地铁系统)](#1396. Design Underground System(设计地铁系统))
- [1397. Find All Good Strings(找到所有好字符串)](#1397. Find All Good Strings(找到所有好字符串))
- [1398. Customers Who Bought Products A and B but Not C(购买了产品A和产品B却没有购买产品C的顾客)](#1398. Customers Who Bought Products A and B but Not C(购买了产品A和产品B却没有购买产品C的顾客))
- [1399. Count Largest Group(统计最大组的数目)](#1399. Count Largest Group(统计最大组的数目))
- [1400. Construct K Palindrome Strings(构造 K 个回文字符串)](#1400. Construct K Palindrome Strings(构造 K 个回文字符串))
1381. Design a Stack With Increment Operation(设计一个支持增量操作的栈)
题目描述
请你设计一个支持对其元素进行增量操作的栈。
实现自定义栈类 CustomStack:
- CustomStack(
intmaxSize):用maxSize初始化对象,maxSize是栈中最多能容纳的元素数量。 voidpush(intx):如果栈还未增长到maxSize,就将x添加到栈顶。intpop():弹出栈顶元素,并返回栈顶的值,或栈为空时返回 -1。voidinc(intk,intval):栈底的k个元素的值都增加val。如果栈中元素总数小于k,
则栈中的所有元素都增加val。
示例
["CustomStack","push","push","pop","push","push","push","increment","increment","pop","pop","pop","pop"]
[[3],[1],[2],[],[2],[3],[4],[5,100],[2,100],[],[],[],[]]
- 输出 :`null`,`null`,`null`,2,`null`,`null`,`null`,`null`,`null`,103,202,201,-1
约束: - 1 <=
maxSize<= 1000;1 <= 调用次数 <= 1000。
解题思路
解法一:数组栈 + 直接增量(主提交版本)
用 int\[\] stack 模拟栈。inc(k, val) 对底部 min(k, size) 个元素直接逐项增加。
复杂度:push/pop O(1),inc O(min(k, size));空间 O(maxSize)。
由于总调用次数 ≤ 1000、maxSize ≤ 1000,直写版本完全可接受。
解法二:延迟增量标记(对照)
addk 表示「栈底到下标 k 的元素各自需要累加的额外增量」。
inc(k, val) 只做 addmin(k,size)-1 += val;pop 时把顶层标记下传并清零
(addsize-2 += addsize-1),实现 push/pop/inc 均 O(1)。
空间 O(maxSize)。
代码实现
java
public class Solution {
public static class CustomStack {
private final int[] stack;
private final int maxSize;
private int size;
public CustomStack(int maxSize) {
this.maxSize = maxSize;
stack = new int[maxSize];
size = 0;
}
public void push(int x) {
if (size < maxSize) {
stack[size++] = x;
}
}
public int pop() {
if (size == 0) {
return -1;
}
return stack[--size];
}
public void increment(int k, int val) {
int n = Math.min(k, size);
for (int i = 0; i < n; i++) {
stack[i] += val;
}
}
}
public static class CustomStackLazy {
private final int[] stack;
private final int[] add;
private final int maxSize;
private int size;
public CustomStackLazy(int maxSize) {
this.maxSize = maxSize;
stack = new int[maxSize];
add = new int[maxSize];
size = 0;
}
public void push(int x) {
if (size < maxSize) {
stack[size++] = x;
}
}
public int pop() {
if (size == 0) {
return -1;
}
if (size > 1) {
add[size - 2] += add[size - 1]; // 顶层增量下传给下面的元素
}
int res = stack[size - 1] + add[size - 1];
add[size - 1] = 0; // 清空该位置,为将来 push 的元素准备
size--;
return res;
}
public void increment(int k, int val) {
int n = Math.min(k, size);
if (n > 0) {
add[n - 1] += val; // 底部 n 个元素共享这份增量
}
}
}
private static void runCase(CustomStack stk) {
stk.push(1);
stk.push(2);
System.out.println(stk.pop()); // 2
stk.push(2);
stk.push(3);
stk.push(4); // 满,不入
stk.increment(5, 100); // [101,102,103]
stk.increment(2, 100); // [201,202,103]
System.out.println(stk.pop()); // 103
System.out.println(stk.pop()); // 202
System.out.println(stk.pop()); // 201
System.out.println(stk.pop()); // -1
}
private static void runCaseLazy(CustomStackLazy stk) {
stk.push(1);
stk.push(2);
System.out.println(stk.pop()); // 2
stk.push(2);
stk.push(3);
stk.push(4); // 满,不入
stk.increment(5, 100); // [101,102,103]
stk.increment(2, 100); // [201,202,103]
System.out.println(stk.pop()); // 103
System.out.println(stk.pop()); // 202
System.out.println(stk.pop()); // 201
System.out.println(stk.pop()); // -1
}
public static void main(String[] args) {
runCase(new CustomStack(3));
runCaseLazy(new CustomStackLazy(3));
}
}
复杂度分析
复杂度:push/pop O(1),inc O(min(k, size));空间 O(maxSize)。
空间 O(maxSize)。
1382. Balance a Binary Search Tree(将二叉搜索树变平衡)
题目描述
给你一棵二叉搜索树,请你返回一棵平衡后的二叉搜索树,新生成的树应该与原来的树有着相同的节点值。
如果有多种构造方法,请你返回任意一种。
如果一棵二叉搜索树中,每个节点的两棵子树高度差不超过 1,我们就称这棵二叉搜索树是平衡的。
示例 1
root = [1,null,2,null,3,null,4,null,null] -> [2,1,3,null,null,null,4]
示例 2
root = [2,1,3] -> [2,1,3]
约束:
- 树节点数在 1, 10\^4 内;节点值互不相同。
解题思路
解法一:中序取值 + 二分重建(主提交版本)
BST 的中序遍历得到升序序列,然后每次取中点作为根递归建树,
保证左右子树节点数差 ≤ 1,树高 O(log n)。
复杂度:O(n),空间 O(n)。
解法二:构造时选择中位下标(对照)
同样中序收集,但递归时用 (lo + hi) >>> 1 选根、左闭右开区间避免重复,
逻辑与解法一等价,用于对照展示不同写法。
代码实现
java
import java.util.ArrayList;
import java.util.List;
public class Solution {
public static 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;
}
}
public TreeNode balanceBST(TreeNode root) {
List<Integer> vals = new ArrayList<>();
inOrder(root, vals);
return build(vals, 0, vals.size() - 1);
}
private void inOrder(TreeNode node, List<Integer> vals) {
if (node == null) {
return;
}
inOrder(node.left, vals);
vals.add(node.val);
inOrder(node.right, vals);
}
private TreeNode build(List<Integer> vals, int lo, int hi) {
if (lo > hi) {
return null;
}
int mid = (lo + hi) >>> 1;
TreeNode node = new TreeNode(vals.get(mid));
node.left = build(vals, lo, mid - 1);
node.right = build(vals, mid + 1, hi);
return node;
}
public TreeNode balanceBSTHalfOpen(TreeNode root) {
List<Integer> vals = new ArrayList<>();
inOrder(root, vals);
return buildHalfOpen(vals, 0, vals.size());
}
private TreeNode buildHalfOpen(List<Integer> vals, int lo, int hi) {
if (lo >= hi) {
return null;
}
int mid = lo + (hi - lo) / 2;
TreeNode node = new TreeNode(vals.get(mid));
node.left = buildHalfOpen(vals, lo, mid);
node.right = buildHalfOpen(vals, mid + 1, hi);
return node;
}
private static TreeNode buildTree(Integer... vals) {
if (vals.length == 0 || vals[0] == null) {
return null;
}
TreeNode root = new TreeNode(vals[0]);
java.util.LinkedList<TreeNode> q = new java.util.LinkedList<>();
q.offer(root);
int i = 1;
while (!q.isEmpty()) {
TreeNode cur = q.poll();
if (i < vals.length && vals[i] != null) {
cur.left = new TreeNode(vals[i]);
q.offer(cur.left);
}
i++;
if (i < vals.length && vals[i] != null) {
cur.right = new TreeNode(vals[i]);
q.offer(cur.right);
}
i++;
}
return root;
}
private static int height(TreeNode node) {
if (node == null) {
return 0;
}
return 1 + Math.max(height(node.left), height(node.right));
}
private static boolean balanced(TreeNode node) {
if (node == null) {
return true;
}
int l = height(node.left);
int r = height(node.right);
return Math.abs(l - r) <= 1 && balanced(node.left) && balanced(node.right);
}
private static void printLevel(TreeNode root) {
java.util.List<Integer> out = new ArrayList<>();
java.util.LinkedList<TreeNode> q = new java.util.LinkedList<>();
q.offer(root);
out.add(root.val);
while (!q.isEmpty()) {
TreeNode cur = q.poll();
if (cur.left != null) {
out.add(cur.left.val);
q.offer(cur.left);
} else {
out.add(null);
}
if (cur.right != null) {
out.add(cur.right.val);
q.offer(cur.right);
} else {
out.add(null);
}
}
// 去尾部 null
while (!out.isEmpty() && out.get(out.size() - 1) == null) {
out.remove(out.size() - 1);
}
System.out.println(out);
}
public static void main(String[] args) {
Solution s = new Solution();
TreeNode t1 = buildTree(1, null, 2, null, 3, null, 4, null, null);
TreeNode r1 = s.balanceBST(t1);
System.out.println("balanced=" + balanced(r1));
printLevel(r1);
TreeNode t2 = buildTree(2, 1, 3);
TreeNode r2 = s.balanceBST(t2);
System.out.println("balanced=" + balanced(r2));
printLevel(r2);
TreeNode r3 = s.balanceBSTHalfOpen(buildTree(1, null, 2, null, 3, null, 4, null, null));
System.out.println("balanced=" + balanced(r3));
printLevel(r3);
}
}
复杂度分析
复杂度:O(n),空间 O(n)。
1383. Maximum Performance of a Team(最大的团队表现值)
题目描述
给定两个整数 n 和 k,以及两个长度为 n 的整数数组 speed 和 efficiency。
现有 n 名工程师,编号从 1 到 n。其中 speedi 和 efficiencyi 分别代表第 i 位工程师的速度和效率。
从这 n 名工程师中最多选择 k 名不同的工程师,使其组成的团队具有最大的团队表现值。
团队表现值的定义为:一个团队中「所有工程师速度的和」乘以他们「效率值中的最小值」。
请你返回该团队的最大团队表现值,由于答案可能很大,请你返回结果对 10^9 + 7 取余后的结果。
示例 1
n = 6, speed = [2,10,3,1,5,8], efficiency = [5,4,3,9,7,2], k = 2 -> 60
示例 2
n = 6, speed 同上, efficiency 同上, k = 3 -> 68
示例 3
n = 6, speed 同上, efficiency 同上, k = 4 -> 72
约束:
- 1 <=
k<=n<= 10^5;1 <= speedi, efficiencyi <= 10^8。
解题思路
解法一:按效率降序 + 小根堆(主提交版本)
把工程师按 efficiency 降序排列。遍历时维护「当前已选团队的最小效率」= 当前工程师效率 e,
因为后续效率只会更小。用一个小根堆维护已选 k 个速度中最小的,超员时弹出最小速度,
保持 sumSpeed 为「效率 >= e 的工程师中可选的最大速度之和」,每次更新答案 sumSpeed * e。
复杂度:O(n log k),空间 O(n)。
解法二:朴素枚举子集(对照)
对工程师子集逐一枚举(2^n - 1),计算表现值取最大。仅适用于极小 n,
作为正确性对照。复杂度 O(2^n * n)。
代码实现
java
import java.util.Arrays;
import java.util.PriorityQueue;
public class Solution {
private static final int MOD = 1_000_000_007;
public int maxPerformance(int n, int[] speed, int[] efficiency, int k) {
int[][] eng = new int[n][2];
for (int i = 0; i < n; i++) {
eng[i][0] = speed[i];
eng[i][1] = efficiency[i];
}
Arrays.sort(eng, (a, b) -> b[1] - a[1]);
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
long sumSpeed = 0;
long best = 0;
for (int[] e : eng) {
sumSpeed += e[0];
minHeap.offer(e[0]);
if (minHeap.size() > k) {
sumSpeed -= minHeap.poll();
}
best = Math.max(best, sumSpeed * e[1]);
}
return (int) (best % MOD);
}
public int maxPerformanceBrute(int n, int[] speed, int[] efficiency, int k) {
long best = 0;
int full = 1 << n;
for (int mask = 1; mask < full; mask++) {
int cnt = Integer.bitCount(mask);
if (cnt > k) {
continue;
}
long sum = 0;
long minEff = Long.MAX_VALUE;
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) != 0) {
sum += speed[i];
minEff = Math.min(minEff, efficiency[i]);
}
}
best = Math.max(best, sum * minEff);
}
return (int) (best % MOD);
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.maxPerformance(6, new int[]{2, 10, 3, 1, 5, 8},
new int[]{5, 4, 3, 9, 7, 2}, 2)); // 60
System.out.println(s.maxPerformance(6, new int[]{2, 10, 3, 1, 5, 8},
new int[]{5, 4, 3, 9, 7, 2}, 3)); // 68
System.out.println(s.maxPerformance(6, new int[]{2, 10, 3, 1, 5, 8},
new int[]{5, 4, 3, 9, 7, 2}, 4)); // 72
System.out.println(s.maxPerformanceBrute(6, new int[]{2, 10, 3, 1, 5, 8},
new int[]{5, 4, 3, 9, 7, 2}, 2)); // 60
System.out.println(s.maxPerformanceBrute(6, new int[]{2, 10, 3, 1, 5, 8},
new int[]{5, 4, 3, 9, 7, 2}, 3)); // 68
System.out.println(s.maxPerformanceBrute(6, new int[]{2, 10, 3, 1, 5, 8},
new int[]{5, 4, 3, 9, 7, 2}, 4)); // 72
}
}
复杂度分析
复杂度:O(n log k),空间 O(n)。
1384. Total Sales Amount by Year(按年度列出销售总额 🔒)
题目类型:数据库(SQL)
题目描述
Product 表:
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| product_id | int |
| product_name | varchar |
+---------------+---------+
product_id 是这张表的主键(具有唯一值的列)。
product_name 是产品的名称。
Sales 表:
+---------------------+---------+
| Column Name | Type |
+---------------------+---------+
| product_id | int |
| period_start | date |
| period_end | date |
| average_daily_sales | int |
+---------------------+---------+
product_id 是这张表的主键(具有唯一值的列)。
period_start 和 period_end 是该产品销售期的起始日期和结束日期,且这两个日期包含在销售期内。
average_daily_sales 列存储销售期内该产品的日平均销售额。
销售日期范围为2018年到2020年。
编写解决方案,找出每个产品每年的总销售额,并包含 product_id , product_name , report_year 以及 total_amount 。
返回结果并按 product_id 和 report_year** 排序**。
返回结果格式如下例所示。
示例 1:**
`**输入:**
Product` table:
+------------+--------------+
| product_id | product_name |
+------------+--------------+
| 1 | LC Phone |
| 2 | LC T-Shirt |
| 3 | LC Keychain |
+------------+--------------+
`Sales` table:
+------------+--------------+-------------+---------------------+
| product_id | period_start | period_end | average_daily_sales |
+------------+--------------+-------------+---------------------+
| 1 | 2019-01-25 | 2019-02-28 | 100 |
| 2 | 2018-12-01 | 2020-01-01 | 10 |
| 3 | 2019-12-01 | 2020-01-31 | 1 |
+------------+--------------+-------------+---------------------+
**输出:**
+------------+--------------+-------------+--------------+
| product_id | product_name | report_year | total_amount |
+------------+--------------+-------------+--------------+
| 1 | LC Phone | 2019 | 3500 |
| 2 | LC T-Shirt | 2018 | 310 |
| 2 | LC T-Shirt | 2019 | 3650 |
| 2 | LC T-Shirt | 2020 | 10 |
| 3 | LC Keychain | 2019 | 31 |
| 3 | LC Keychain | 2020 | 31 |
+------------+--------------+-------------+--------------+
**解释:**
LC Phone 在 2019-01-25 至 2019-02-28 期间销售,该产品销售时间总计35天。销售总额 35*100 = 3500。
LC T-shirt 在 2018-12-01 至 2020-01-01 期间销售,该产品在2018年、2019年、2020年的销售时间分别是31天、365天、1天,2018年、2019年、2020年的销售总额分别是31*10=310、365*10=3650、1*10=10。
LC Keychain 在 2019-12-01 至 2020-01-31 期间销售,该产品在2019年、2020年的销售时间分别是:31天、31天,2019年、2020年的销售总额分别是31*1=31、31*1=31。
解题思路
思考
销售额按年拆开,区间可能跨越 2018 2018 2018-- 2020 2020 2020。用三年日历与销售区间按年份重叠连接,当年天数等于「区间在该年的结束日序减开始日序再加一」,再乘日均销量。闰年 2020 2020 2020 单独按 366 366 366 天计算。
考察递归/数字表展开年份:把每条销售记录展开到涉及的每个年份,再按 product_id、年份分组求和。
代码实现
sql
# Write your MySQL query statement below
SELECT
s.product_id,
p.product_name,
y.YEAR AS report_year,
s.average_daily_sales * (
IF(YEAR(s.period_end) > y.YEAR, y.days_of_year, DAYOFYEAR(s.period_end)) - IF(
YEAR(s.period_start) < y.YEAR,
1,
DAYOFYEAR(s.period_start)
) + 1
) AS total_amount
FROM
Sales AS s
INNER JOIN (
SELECT
'2018' AS YEAR,
365 AS days_of_year
UNION ALL
SELECT
'2019' AS YEAR,
365 AS days_of_year
UNION ALL
SELECT
'2020' AS YEAR,
366 AS days_of_year
) AS y
ON YEAR(s.period_start) <= y.YEAR AND YEAR(s.period_end) >= y.YEAR
INNER JOIN Product AS p ON p.product_id = s.product_id
ORDER BY s.product_id, y.YEAR;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1385. Find the Distance Value Between Two Arrays(两个数组间的距离值)
题目描述
给你两个整数数组 arr1、arr2 和一个整数 d,请你返回两个数组之间的距离值。
「距离值」定义为符合此距离要求的元素数目:对于元素 arr1i,不存在任何元素 arr2j
满足 |arr1i - arr2j| <= d。
示例 1
arr1 = [4,5,8], arr2 = [10,9,1,8], d = 2 -> 2
示例 2
arr1 = [1,4,2,3], arr2 = [-4,-3,6,10,20,30], d = 3 -> 2
示例 3
arr1 = [2,1,100,3], arr2 = [-5,-2,10,-3,7], d = 6 -> 1
约束:
- 1 <= arr1.length, arr2.length <= 500;-10^3 <= arr1i, arr2j <= 10^3。
解题思路
解法一:排序 + 二分(主提交版本)
对 arr2 排序。对每个 arr1i,二分查找 arr2 中第一个 >= arr1i - d 的位置,
检查该位置值与 arr1i 的差是否 <= d,以及前一个位置,两处都不满足
|arr1i - arr2j| <= d 才计数。复杂度:O(n1 * log n2)。
解法二:朴素双重循环(对照)
直接对每个 arr1i 遍历全部 arr2j 检查距离。复杂度 O(n1 * n2)。
代码实现
java
import java.util.Arrays;
public class Solution {
public int findTheDistanceValue(int[] arr1, int[] arr2, int d) {
Arrays.sort(arr2);
int cnt = 0;
for (int x : arr1) {
int idx = lowerBound(arr2, x - d);
boolean ok = true;
if (idx < arr2.length && Math.abs(x - arr2[idx]) <= d) {
ok = false;
}
if (idx > 0 && Math.abs(x - arr2[idx - 1]) <= d) {
ok = false;
}
if (ok) {
cnt++;
}
}
return cnt;
}
private int lowerBound(int[] a, int x) {
int lo = 0, hi = a.length;
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (a[mid] < x) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
public int findTheDistanceValueNaive(int[] arr1, int[] arr2, int d) {
int cnt = 0;
for (int x : arr1) {
boolean ok = true;
for (int y : arr2) {
if (Math.abs(x - y) <= d) {
ok = false;
break;
}
}
if (ok) {
cnt++;
}
}
return cnt;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.findTheDistanceValue(new int[]{4, 5, 8}, new int[]{10, 9, 1, 8}, 2)); // 2
System.out.println(s.findTheDistanceValue(new int[]{1, 4, 2, 3}, new int[]{-4, -3, 6, 10, 20, 30}, 3)); // 2
System.out.println(s.findTheDistanceValue(new int[]{2, 1, 100, 3}, new int[]{-5, -2, 10, -3, 7}, 6)); // 1
System.out.println(s.findTheDistanceValueNaive(new int[]{4, 5, 8}, new int[]{10, 9, 1, 8}, 2)); // 2
System.out.println(s.findTheDistanceValueNaive(new int[]{1, 4, 2, 3}, new int[]{-4, -3, 6, 10, 20, 30}, 3)); // 2
System.out.println(s.findTheDistanceValueNaive(new int[]{2, 1, 100, 3}, new int[]{-5, -2, 10, -3, 7}, 6)); // 1
}
}
复杂度分析
1386. Cinema Seat Allocation(安排电影院座位)
题目描述
如上图所示,电影院的观影厅中有 n 行座位,行编号从 1 到 n,且每一行内总共有 10 个座位,
列编号从 1 到 10。给定一个二维数组 reservedSeats,其中 reservedSeats[i] = rowi, seati
表示第 rowi 行的座位 seati 已经被预定。
四人小组必须被安排在同一排的四个座位上。该小组可以坐在以下座位块之一:
- 座位 2, 3, 4, 5
- 座位 4, 5, 6, 7
- 座位 6, 7, 8, 9
只有当该块中的所有座位都没有被预订时,才能使用该块。每个座位最多只能分配给一个小组。
返回一个整数,表示可以分配的最大四人小组数量。
示例 1
n = 3, reservedSeats = [[1,2],[1,3],[1,8],[2,6],[3,1],[3,10]] -> 4
示例 2
n = 2, reservedSeats = [[2,1],[1,8],[2,6]] -> 2
示例 3
n = 4, reservedSeats = [[4,3],[1,4],[4,6],[1,7]] -> 4
约束:
- 1 <=
n<= 10^9;0 <=reservedSeats.length<= 10^4。
解题思路
解法一:位掩码 + 分段判断(主提交版本)
每行最多容纳 2 个小组(左块 2-5 与右块 6-9),当中间块 4-7 空时也能在 2-5/6-9
被占的情况下放 1 个。用 int mask 记录每行被占的座位(座位 2...9 映射到位 0...7),
判断块空:左块 (2,3,4,5) 是否全空、右块 (6,7,8,9) 是否全空、中块 (4,5,6,7) 是否全空。
对只有预订的行单独计算,全空的行每行给 2。
复杂度:O®,R 为有预订的行数;空间 O®。
解法二:按行分组布尔判断(对照)
用 Map<row, boolean10> 记录预订,逐行用三个块的原语判断,逻辑直白。
复杂度同解法一。
代码实现
java
import java.util.HashMap;
import java.util.Map;
public class Solution {
// 座位 2..9 映射到位 0..7(seat-2)
// 左块 [2,5] = 位 0..3;右块 [6,9] = 位 4..7;中块 [4,7] = 位 2..5
private static final int LEFT = 0b00001111; // 座位 2,3,4,5
private static final int RIGHT = 0b11110000; // 座位 6,7,8,9
private static final int MID = 0b00111100; // 座位 4,5,6,7
public int maxNumberOfFamilies(int n, int[][] reservedSeats) {
Map<Integer, Integer> rowMask = new HashMap<>();
for (int[] r : reservedSeats) {
int row = r[0];
int seat = r[1];
if (seat == 1 || seat == 10) {
continue; // 边座不影响任何块
}
int bit = 1 << (seat - 2);
rowMask.put(row, rowMask.getOrDefault(row, 0) | bit);
}
long total = 2L * n; // 最理想:每行 2 个(含全空行)
for (int mask : rowMask.values()) {
int rowMax = 0;
if ((mask & LEFT) == 0) {
rowMax++; // 可以用左块
}
if ((mask & RIGHT) == 0) {
rowMax++; // 可以用右块
}
if (rowMax == 0 && (mask & MID) == 0) {
rowMax = 1; // 左右都被占但中间空,能放一组
}
total -= (2 - rowMax);
}
return (int) total;
}
public int maxNumberOfFamiliesBool(int n, int[][] reservedSeats) {
Map<Integer, boolean[]> rows = new HashMap<>();
for (int[] r : reservedSeats) {
boolean[] a = rows.computeIfAbsent(r[0], k -> new boolean[11]);
a[r[1]] = true;
}
long total = 2L * n;
for (boolean[] a : rows.values()) {
boolean left = !(a[2] || a[3] || a[4] || a[5]);
boolean right = !(a[6] || a[7] || a[8] || a[9]);
boolean mid = !(a[4] || a[5] || a[6] || a[7]);
int rowMax = 0;
if (left) {
rowMax++;
}
if (right) {
rowMax++;
}
if (rowMax == 0 && mid) {
rowMax = 1;
}
total -= (2 - rowMax);
}
return (int) total;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.maxNumberOfFamilies(3, new int[][]{{1, 2}, {1, 3}, {1, 8}, {2, 6}, {3, 1}, {3, 10}})); // 4
System.out.println(s.maxNumberOfFamilies(2, new int[][]{{2, 1}, {1, 8}, {2, 6}})); // 2
System.out.println(s.maxNumberOfFamilies(4, new int[][]{{4, 3}, {1, 4}, {4, 6}, {1, 7}})); // 4
System.out.println(s.maxNumberOfFamiliesBool(3, new int[][]{{1, 2}, {1, 3}, {1, 8}, {2, 6}, {3, 1}, {3, 10}})); // 4
System.out.println(s.maxNumberOfFamiliesBool(2, new int[][]{{2, 1}, {1, 8}, {2, 6}})); // 2
System.out.println(s.maxNumberOfFamiliesBool(4, new int[][]{{4, 3}, {1, 4}, {4, 6}, {1, 7}})); // 4
}
}
复杂度分析
复杂度:O®,R 为有预订的行数;空间 O®。
1387. Sort Integers by The Power Value(将整数按权重排序)
题目描述
我们将整数 x 的权重定义为按照下述规则将 x 变成 1 所需要的步数:
- 如果
x是偶数,那么x=x/ 2; - 如果
x是奇数,那么x= 3 *x+ 1。
比方说,x=3 的权重为 7(3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1)。
给你三个整数lo、hi和k。你的任务是将区间 `lo`, `hi` 之间的整数按照它们的权重升序排序,
如果大于等于 2 个整数有相同的权重,那么按照数字自身的数值升序排序。
请你返回区间 `lo`, `hi` 之间的整数按权重排序后的第k个数。
注意,题目保证对于任意整数x(lo<=x<=hi),它变成 1 所需要的步数是一个 32 位有符号整数。
示例 1
lo = 12, hi = 15, k = 2 -> 13
示例 2
lo = 1, hi = 1, k = 1 -> 1
示例 3
lo = 7, hi = 11, k = 4 -> 7
约束:
- 1 <=
lo<=hi<= 1000;1 <=k<=hi-lo+ 1。
解题思路
解法一:记忆化权重 + 排序(主提交版本)
定义 getPower(x) 递归求权重,用 Map 记忆化避免重复计算(Collatz 过程可能超出 x 范围,
但可安全用 long,并在递归中记忆)。
把 lo, hi 的所有数按 (weight, value) 排序取第 k-1 个。
复杂度:O((hi-lo) * log(hi-lo) + 总步数),空间 O(记忆化规模)。
解法二:数组桶 + 稳定归并(对照)
权重范围有界(≤ 大约 200),先用桶按权重收集数字(桶内自然升序),
再从小权重到大权重依次遍历取第 k 个,等价于排序但省去显式排序。
复杂度:O(总步数 + (hi-lo))。
代码实现
java
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Solution {
private final Map<Long, Integer> memo = new HashMap<>();
public int getKth(int lo, int hi, int k) {
List<int[]> list = new ArrayList<>();
for (int x = lo; x <= hi; x++) {
list.add(new int[]{power(x), x});
}
list.sort((a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]);
return list.get(k - 1)[1];
}
private int power(long x) {
if (x == 1) {
return 0;
}
Integer cached = memo.get(x);
if (cached != null) {
return cached;
}
int res;
if ((x & 1) == 0) {
res = 1 + power(x / 2);
} else {
res = 1 + power(3 * x + 1);
}
memo.put(x, res);
return res;
}
public int getKthBucket(int lo, int hi, int k) {
// 权重上界经验值 ~ 215(Collatz 到 1 的最大步数),取 400 安全
List<List<Integer>> buckets = new ArrayList<>();
for (int i = 0; i < 400; i++) {
buckets.add(new ArrayList<>());
}
for (int x = lo; x <= hi; x++) {
buckets.get(power(x)).add(x);
}
int cnt = 0;
for (int w = 0; w < 400; w++) {
for (int x : buckets.get(w)) {
cnt++;
if (cnt == k) {
return x;
}
}
}
return -1;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.getKth(12, 15, 2)); // 13
System.out.println(s.getKth(1, 1, 1)); // 1
System.out.println(s.getKth(7, 11, 4)); // 7
System.out.println(s.getKth(10, 20, 5)); // 11
System.out.println(s.getKthBucket(12, 15, 2)); // 13
System.out.println(s.getKthBucket(1, 1, 1)); // 1
System.out.println(s.getKthBucket(7, 11, 4)); // 7
System.out.println(s.getKthBucket(10, 20, 5)); // 11
}
}
复杂度分析
复杂度:O((hi-lo) * log(hi-lo) + 总步数),空间 O(记忆化规模)。
复杂度:O(总步数 + (hi-lo))。
1388. Pizza With 3n Slices(3n 块披萨)
题目描述
给你一个披萨,它由 3n 块不同大小的部分组成,现在你和你的朋友们需要按照如下规则来分披萨:
- 你挑选任意一块披萨。
- Alice 将会挑选你所选择的披萨逆时针方向的下一块披萨。
- Bob 将会挑选你所选择的披萨顺时针方向的下一块披萨。
- 重复上述过程直到没有披萨剩下。
每一块披萨的大小按顺时针方向由循环数组 slices 表示。
请你返回你可以获得的披萨大小总和的最大值。
示例 1
slices = [1,2,3,4,5,6] -> 10
示例 2
slices = [8,9,8,6,1,1] -> 16
示例 3
slices = [4,1,2,5,8,3,1,9,7] -> 21
约束:
- 1 <= slices.length <= 500;slices.length 为 3 的倍数;1 <= slicesi <= 1000。
解题思路
解法一:环形不相邻选择 DP(主提交版本)
每人每轮取一块,共 3n 块、3 人参与,最终你恰好得到 n 块。
由于 Alice/Bob 会拿走你选中块的左右相邻块,等价于:在环形数组中选 n 个互不相邻的元素
使和最大。环形问题拆成两个线性 DP:选 0 号(则不能选最后一块)与不选 0 号。
dpij 在前 i 块中选 j 块且不相邻的最大值。
复杂度:O(n^2),空间 O(n) 滚动。
解法二:三维循环 DP(对照)
直接在环上用 dpijborrow 表示是否取了 0 号块,状态更直观。
复杂度同为 O(n^2)。
代码实现
java
import java.util.Arrays;
public class Solution {
public int maxSizeSlices(int[] slices) {
int n = slices.length;
int take = n / 3;
// 情况 A:选第 0 块 -> 区间 [0, n-2] 线性选 take 块
int a = linear(slices, 0, n - 2, take);
// 情况 B:不选第 0 块 -> 区间 [1, n-1] 线性选 take 块
int b = linear(slices, 1, n - 1, take);
return Math.max(a, b);
}
// 在 slices[lo..hi](线性、不含环)中选 k 个不相邻元素的最大和
private int linear(int[] slices, int lo, int hi, int k) {
int len = hi - lo + 1;
if (len < k) {
return 0;
}
int[] dp = new int[k + 1]; // 滚动:dp[j] = 当前处理到某位置时选 j 个的最大和
int[] prev = new int[k + 1];
// dp[i][j] = max(dp[i-1][j], dp[i-2][j-1] + slices[i])
// 用两层滚动:prev2 表示 i-2 行,prev1 表示 i-1 行
int[] prev2 = new int[k + 1];
int[] prev1 = new int[k + 1];
for (int i = lo; i <= hi; i++) {
int[] cur = new int[k + 1];
for (int j = 1; j <= k; j++) {
cur[j] = Math.max(prev1[j], prev2[j - 1] + slices[i]);
}
prev2 = prev1;
prev1 = cur;
}
return prev1[k];
}
public int maxSizeSlices3D(int[] slices) {
int n = slices.length;
int k = n / 3;
// dp0[i][j]:考虑前 i 块(0..i-1)、选了 j 块、且第 0 块未选的最大和
// dp1[i][j]:上述情形但第 0 块已选
int[][] dp0 = new int[n + 1][k + 1];
int[][] dp1 = new int[n + 1][k + 1];
for (int[] row : dp0) {
Arrays.fill(row, Integer.MIN_VALUE / 4);
}
for (int[] row : dp1) {
Arrays.fill(row, Integer.MIN_VALUE / 4);
}
dp0[1][0] = 0;
dp1[1][1] = slices[0];
for (int i = 2; i <= n; i++) {
for (int j = 0; j <= k; j++) {
// 不取 i-1
dp0[i][j] = Math.max(dp0[i][j], dp0[i - 1][j]);
dp1[i][j] = Math.max(dp1[i][j], dp1[i - 1][j]);
if (j >= 1) {
// 取 i-1:前一块 i-2 不能取
if (i - 2 >= 1) {
dp0[i][j] = Math.max(dp0[i][j], dp0[i - 2][j - 1] + slices[i - 1]);
dp1[i][j] = Math.max(dp1[i][j], dp1[i - 2][j - 1] + slices[i - 1]);
}
}
}
}
// 选了第 0 块则最后一块 n-1 不能选;未选第 0 块则可以(i=n 即含最后一块)
int res = Math.max(dp0[n][k], dp1[n - 1][k]);
if (n >= 1) {
res = Math.max(res, dp0[n][k]);
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.maxSizeSlices(new int[]{1, 2, 3, 4, 5, 6})); // 10
System.out.println(s.maxSizeSlices(new int[]{8, 9, 8, 6, 1, 1})); // 16
System.out.println(s.maxSizeSlices(new int[]{4, 1, 2, 5, 8, 3, 1, 9, 7})); // 21
System.out.println(s.maxSizeSlices3D(new int[]{1, 2, 3, 4, 5, 6})); // 10
System.out.println(s.maxSizeSlices3D(new int[]{8, 9, 8, 6, 1, 1})); // 16
System.out.println(s.maxSizeSlices3D(new int[]{4, 1, 2, 5, 8, 3, 1, 9, 7})); // 21
}
}
复杂度分析
复杂度:O(n^2),空间 O(n) 滚动。
1389. Create Target Array in the Given Order(按既定顺序创建目标数组)
题目描述
给你两个整数数组 nums 和 index。你需要按照以下规则创建目标数组:
- 目标数组
target最初为空。 - 按从左到右的顺序依次读取
nums[i]和index[i],在target数组中的下标index[i]处插入值nums[i]。 - 重复上一步,直到在
nums和index中都没有要读取的元素。
请你返回目标数组。题目保证数字插入位置总是存在。
示例 1
nums = [0,1,2,3,4], index = [0,1,2,2,1] -> [0,4,1,3,2]
示例 2
nums = [1,2,3,4,0], index = [0,1,2,3,0] -> [0,1,2,3,4]
示例 3
nums = [1], index = [0] -> [1]
约束:
- 1 <=
nums.length,index.length<= 100;index[i]<=i。
解题思路
解法一:ArrayList 插入(主提交版本)
利用 List.add(pos, val) 在指定位置插入并自动后移后续元素。
复杂度:O(n^2)(插入时移动 O(n)),空间 O(n)。
解法二:从后往前倒序放置(对照)
逆向思维:最后一个插入的元素落在最终位置。从后往前处理第 i 步,
把值从后往前依次向后挪动。本质同解法一,但直接操作 int\[\]。
复杂度:O(n^2)。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
public int[] createTargetArray(int[] nums, int[] index) {
List<Integer> target = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
target.add(index[i], nums[i]);
}
int[] res = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
res[i] = target.get(i);
}
return res;
}
public int[] createTargetArrayArray(int[] nums, int[] index) {
int n = nums.length;
int[] res = new int[n];
int size = 0;
for (int i = 0; i < n; i++) {
// 把 [index[i], size) 的元素整体后移一位
for (int j = size; j > index[i]; j--) {
res[j] = res[j - 1];
}
res[index[i]] = nums[i];
size++;
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(Arrays.toString(s.createTargetArray(new int[]{0, 1, 2, 3, 4}, new int[]{0, 1, 2, 2, 1}))); // [0,4,1,3,2]
System.out.println(Arrays.toString(s.createTargetArray(new int[]{1, 2, 3, 4, 0}, new int[]{0, 1, 2, 3, 0}))); // [0,1,2,3,4]
System.out.println(Arrays.toString(s.createTargetArray(new int[]{1}, new int[]{0}))); // [1]
System.out.println(Arrays.toString(s.createTargetArrayArray(new int[]{0, 1, 2, 3, 4}, new int[]{0, 1, 2, 2, 1}))); // [0,4,1,3,2]
System.out.println(Arrays.toString(s.createTargetArrayArray(new int[]{1, 2, 3, 4, 0}, new int[]{0, 1, 2, 3, 0}))); // [0,1,2,3,4]
System.out.println(Arrays.toString(s.createTargetArrayArray(new int[]{1}, new int[]{0}))); // [1]
}
}
复杂度分析
复杂度:O(n^2)(插入时移动 O(n)),空间 O(n)。
复杂度:O(n^2)。
1390. Four Divisors(四因数)
题目描述
给你一个整数数组 nums,请你返回该数组中恰有四个因数的这些整数的各因数之和。
如果数组中不存在满足题意的整数,则返回 0。
示例 1
nums = [21,4,7] -> 32(21 有 4 个因数 1,3,7,21;4 有 3 个;7 有 2 个)
示例 2
nums = [21,21] -> 64(两个 21 各自贡献 32)
示例 3
nums = [1,2,3,4,5] -> 0
约束:
- 1 <=
nums.length<= 10^4;1 <=nums[i]<= 10^5。
解题思路
解法一:试除法统计(主提交版本)
对每个 num 枚举 i 从 1 到 sqrt(num),找到所有约数;若约数个数恰好为 4 则累加约数和。
复杂度:O(V * sqrt(maxNum)),V 为数组长度。
解法二:预筛每个数的约数和(对照)
用类埃筛思路预处理 up to max 内所有数的约数个数与约数和:
for d in 1...max: for m = d...max step d: divisorCountm++, divisorSumm += d。
之后直接查表。复杂度 O(max * log max)。
代码实现
java
import java.util.Arrays;
public class Solution {
public int sumFourDivisors(int[] nums) {
int ans = 0;
for (int num : nums) {
int cnt = 0;
int sum = 0;
for (int i = 1; (long) i * i <= num; i++) {
if (num % i == 0) {
cnt++;
sum += i;
if (i != num / i) {
cnt++;
sum += num / i;
}
}
}
if (cnt == 4) {
ans += sum;
}
}
return ans;
}
private static final int MAXN = 100000;
public int sumFourDivisorsSieve(int[] nums) {
int[] cnt = new int[MAXN + 1];
long[] sum = new long[MAXN + 1];
for (int d = 1; d <= MAXN; d++) {
for (int m = d; m <= MAXN; m += d) {
cnt[m]++;
sum[m] += d;
}
}
int ans = 0;
for (int num : nums) {
if (cnt[num] == 4) {
ans += sum[num];
}
}
return ans;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.sumFourDivisors(new int[]{21, 4, 7})); // 32
System.out.println(s.sumFourDivisors(new int[]{21, 21})); // 64
System.out.println(s.sumFourDivisors(new int[]{1, 2, 3, 4, 5})); // 0
System.out.println(s.sumFourDivisorsSieve(new int[]{21, 4, 7})); // 32
System.out.println(s.sumFourDivisorsSieve(new int[]{21, 21})); // 64
System.out.println(s.sumFourDivisorsSieve(new int[]{1, 2, 3, 4, 5})); // 0
}
}
复杂度分析
复杂度:O(V * sqrt(maxNum)),V 为数组长度。
1391. Check if There is a Valid Path in a Grid(检查网格中是否存在有效路径)
题目描述
给你一个 m x n 的网格 grid。网格里的每个单元都代表一条街道。grid[i][j] 的街道可以是:
- 1 表示连接左单元格和右单元格的街道。
- 2 表示连接上单元格和下单元格的街道。
- 3 表示连接左单元格和下单元格的街道。
- 4 表示连接右单元格和下单元格的街道。
- 5 表示连接左单元格和上单元格的街道。
- 6 表示连接右单元格和上单元格的街道。
你最开始从左上角的单元格 (0,0) 开始出发,网格中的「有效路径」是指从左上方的单元格 (0,0) 开始、
一直到右下方的 (m-1,n-1) 结束的路径。该路径必须只沿着街道走。
注意:你不能变更街道。如果网格中存在有效的路径,则返回true,否则返回false。
示例 1
grid = [[2,4,3],[6,5,2]] -> true
示例 2
grid = [[1,2,1],[1,2,1]] -> false
示例 3
grid = [[1,1,2]] -> false
约束:
m,n<= 300。
解题思路
解法一:方向图 BFS(主提交版本)
把每种街道看作无向边的集合:1={左,右}, 2={上,下}, 3={左,下}, 4={右,下},
5={左,上}, 6={右,上}。从 (0,0) BFS,走到邻居时必须「当前格的出口方向」与
「邻居格的入口方向」恰好互补。用 dir 0/1/2/3 表示 左/右/上/下,互补对 (0,1),(2,3)。
复杂度:O(m * n),空间 O(m * n)。
解法二:DFS 递归(对照)
同样的方向连通性判断,用 DFS 递归实现,逻辑一致。
复杂度:O(m * n),递归栈空间 O(m * n)。
代码实现
java
import java.util.LinkedList;
public class Solution {
private static final int[][] SHAPE = {
{}, // 0 占位
{0, 1}, // 1: 左、右
{2, 3}, // 2: 上、下
{0, 3}, // 3: 左、下
{1, 3}, // 4: 右、下
{0, 2}, // 5: 左、上
{1, 2} // 6: 右、上
};
// 四个方向 {左, 右, 上, 下},互补:左<->右(0<->1),上<->下(2<->3)
private static final int[] DR = {0, 0, -1, 1};
private static final int[] DC = {-1, 1, 0, 0};
public boolean hasValidPath(int[][] grid) {
int m = grid.length, n = grid[0].length;
boolean[][] vis = new boolean[m][n];
LinkedList<int[]> q = new LinkedList<>();
q.offer(new int[]{0, 0});
vis[0][0] = true;
while (!q.isEmpty()) {
int[] cur = q.poll();
int r = cur[0], c = cur[1];
if (r == m - 1 && c == n - 1) {
return true;
}
for (int dir : SHAPE[grid[r][c]]) {
int nr = r + DR[dir];
int nc = c + DC[dir];
if (nr < 0 || nr >= m || nc < 0 || nc >= n || vis[nr][nc]) {
continue;
}
// 检查互补方向是否在 neighbor 的街道中
int opp = (dir ^ 1);
if (containsDir(grid[nr][nc], opp)) {
vis[nr][nc] = true;
q.offer(new int[]{nr, nc});
}
}
}
return false;
}
private boolean containsDir(int shape, int dir) {
for (int d : SHAPE[shape]) {
if (d == dir) {
return true;
}
}
return false;
}
public boolean hasValidPathDfs(int[][] grid) {
int m = grid.length, n = grid[0].length;
boolean[][] vis = new boolean[m][n];
return dfs(grid, 0, 0, vis);
}
private boolean dfs(int[][] grid, int r, int c, boolean[][] vis) {
int m = grid.length, n = grid[0].length;
if (vis[r][c]) {
return false;
}
vis[r][c] = true;
if (r == m - 1 && c == n - 1) {
return true;
}
for (int dir : SHAPE[grid[r][c]]) {
int nr = r + DR[dir];
int nc = c + DC[dir];
if (nr < 0 || nr >= m || nc < 0 || nc >= n) {
continue;
}
if (containsDir(grid[nr][nc], dir ^ 1)) {
if (dfs(grid, nr, nc, vis)) {
return true;
}
}
}
return false;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.hasValidPath(new int[][]{{2, 4, 3}, {6, 5, 2}})); // true
System.out.println(s.hasValidPath(new int[][]{{1, 2, 1}, {1, 2, 1}})); // false
System.out.println(s.hasValidPath(new int[][]{{1, 1, 2}})); // false
System.out.println(s.hasValidPath(new int[][]{{3, 4, 3, 4}, {2, 2, 2, 2}, {6, 5, 6, 5}})); // true
System.out.println(s.hasValidPathDfs(new int[][]{{2, 4, 3}, {6, 5, 2}})); // true
System.out.println(s.hasValidPathDfs(new int[][]{{1, 2, 1}, {1, 2, 1}})); // false
System.out.println(s.hasValidPathDfs(new int[][]{{1, 1, 2}})); // false
}
}
复杂度分析
复杂度:O(m * n),空间 O(m * n)。
复杂度:O(m * n),递归栈空间 O(m * n)。
1392. Longest Happy Prefix(最长快乐前缀)
题目描述
如果一个字符串是「快乐前缀」,那么它既是原字符串的「非空」前缀,同时也是原字符串的
后缀(不包含自身)。给定一个字符串 s,返回 s 的最长快乐前缀。如果不存在这样的前缀,
返回空字符串 ""。
示例 1
s = "level" -> "l"
示例 2
s = "ababab" -> "abab"
示例 3
s = "leetcodeleet" -> "leet"
约束:
- 1 <=
s.length<= 10^5;s只包含小写英文字母。
解题思路
解法一:KMP 前缀函数(主提交版本)
求 s 的 next 数组:nexti 是 s0...i 的「最长相等真前后缀」长度。
答案即 nextn-1,若为 0 返回空串。
复杂度:O(n),空间 O(n)。
解法二:滚动哈希(对照)
从长到短枚举前缀长度 len,同时维护前缀哈希与后缀哈希,相等即答案。
用 31 进制 + 大模数,为避免冲突可双模。复杂度 O(n)。
代码实现
java
public class Solution {
public String longestPrefix(String s) {
int n = s.length();
int[] next = new int[n];
for (int i = 1, j = 0; i < n; i++) {
while (j > 0 && s.charAt(i) != s.charAt(j)) {
j = next[j - 1];
}
if (s.charAt(i) == s.charAt(j)) {
j++;
}
next[i] = j;
}
int len = next[n - 1];
return s.substring(0, len);
}
public String longestPrefixHash(String s) {
int n = s.length();
long mod = 1_000_000_007L;
long base = 31;
long pow = 1;
long pre = 0, suf = 0;
int best = 0;
for (int i = 0; i < n - 1; i++) {
pre = (pre * base + (s.charAt(i) - 'a' + 1)) % mod;
suf = (suf + (long) (s.charAt(n - 1 - i) - 'a' + 1) * pow) % mod;
pow = pow * base % mod;
if (pre == suf) {
best = i + 1;
}
}
return s.substring(0, best);
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.longestPrefix("level")); // l
System.out.println(s.longestPrefix("ababab")); // abab
System.out.println(s.longestPrefix("leetcodeleet")); // leet
System.out.println(s.longestPrefix("a")); // ""
System.out.println(s.longestPrefixHash("level")); // l
System.out.println(s.longestPrefixHash("ababab")); // abab
System.out.println(s.longestPrefixHash("leetcodeleet")); // leet
System.out.println(s.longestPrefixHash("a")); // ""
}
}
复杂度分析
复杂度:O(n),空间 O(n)。
1393. Capital Gain/Loss(股票的资本损益)
题目类型:数据库(SQL)
题目描述
Stocks 表:
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| stock_name | varchar |
| operation | enum |
| operation_day | int |
| price | int |
+---------------+---------+
(stock_name, operation_day) 是这张表的主键(具有唯一值的列的组合)
operation 列使用的是一种枚举类型,包括:('Sell','Buy')
此表的每一行代表了名为 stock_name 的某支股票在 operation_day 这一天的操作价格。
此表可以保证,股票的每个"卖出"操作在前某一天都有相应的"买入"操作。并且,股票的每个"买入"操作在即将到来的某一天都有相应的"卖出"操作。
编写解决方案报告每只股票的 资本损益。
股票的 **资本利得/损失 **是指一次或多次买卖该股票后的总收益或损失。
以 任意顺序 返回结果表。
结果格式如下所示。
示例 1:
`**输入:**
Stocks` 表:
+---------------+-----------+---------------+--------+
| stock_name | operation | operation_day | price |
+---------------+-----------+---------------+--------+
| Leetcode | Buy | 1 | 1000 |
| Corona Masks | Buy | 2 | 10 |
| Leetcode | Sell | 5 | 9000 |
| Handbags | Buy | 17 | 30000 |
| Corona Masks | Sell | 3 | 1010 |
| Corona Masks | Buy | 4 | 1000 |
| Corona Masks | Sell | 5 | 500 |
| Corona Masks | Buy | 6 | 1000 |
| Handbags | Sell | 29 | 7000 |
| Corona Masks | Sell | 10 | 10000 |
+---------------+-----------+---------------+--------+
**输出:**
+---------------+-------------------+
| stock_name | capital_gain_loss |
+---------------+-------------------+
| Corona Masks | 9500 |
| Leetcode | 8000 |
| Handbags | -23000 |
+---------------+-------------------+
**解释:**
Leetcode 股票在第一天以1000美元的价格买入,在第五天以9000美元的价格卖出。资本收益=9000-1000=8000美元。
Handbags 股票在第17天以30000美元的价格买入,在第29天以7000美元的价格卖出。资本损失=7000-30000=-23000美元。
Corona Masks 股票在第1天以10美元的价格买入,在第3天以1010美元的价格卖出。在第4天以1000美元的价格再次购买,在第5天以500美元的价格出售。最后,它在第6天以1000美元的价格被买走,在第10天以10000美元的价格被卖掉。资本损益是每次('Buy'->'Sell')操作资本收益或损失的和=(1010-10)+(500-1000)+(10000-1000)=1000-500+9000=9500美元。
解题思路
思考
按股票汇总资本损益:卖出加价、买入减价。对 stock_name \textit{stock\_name} stock_name 分组后, S U M ( I F ( operation = 'Buy' , − price , price ) ) \mathrm{SUM}(\mathrm{IF}(\textit{operation}=\texttt{'Buy'},-\textit{price},\textit{price})) SUM(IF(operation='Buy',−price,price)) 一次求出净额。
考察按股票分组 + CASE WHEN:买入减、卖出加,最终 SUM 得到每股资本损益。
代码实现
sql
# Write your MySQL query statement below
SELECT
stock_name,
SUM(IF(operation = 'Buy', -price, price)) AS capital_gain_loss
FROM Stocks
GROUP BY 1;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1394. Find Lucky Integer in an Array(找出数组中的幸运数字)
题目描述
在整数数组中,如果一个整数的出现频次和它的数值大小相等,我们就称这个整数为「幸运数」。
给你一个整数数组 arr,请你从中找出并返回一个幸运数:
- 如果数组中存在多个幸运数,只需返回最大的那个。
- 如果数组中不含幸运数,则返回 -1。
示例 1
arr = [2,2,3,4] -> 2
示例 2
arr = [1,2,2,3,3,3] -> 3
示例 3
arr = [2,2,2,3,3] -> -1
约束:
- 1 <=
arr.length<= 500;1 <=arr[i]<= 500。
解题思路
解法一:哈希计数 + 扫描(主提交版本)
用 Map 统计每个数字频次,然后遍历 map 键值,取 freq == value 的最大 value。
复杂度:O(n),空间 O(n)。
解法二:频次数组(对照,值域已知)
值域 ≤ 500,用 int501 计数,从大到小找第一个 freqi == i 即为最大幸运数。
复杂度:O(n + V),空间 O(V)。
代码实现
java
import java.util.HashMap;
import java.util.Map;
public class Solution {
public int findLucky(int[] arr) {
Map<Integer, Integer> cnt = new HashMap<>();
for (int v : arr) {
cnt.put(v, cnt.getOrDefault(v, 0) + 1);
}
int best = -1;
for (Map.Entry<Integer, Integer> e : cnt.entrySet()) {
if (e.getKey().equals(e.getValue())) {
best = Math.max(best, e.getKey());
}
}
return best;
}
public int findLuckyArray(int[] arr) {
int[] cnt = new int[501];
for (int v : arr) {
cnt[v]++;
}
for (int i = 500; i >= 1; i--) {
if (cnt[i] == i) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.findLucky(new int[]{2, 2, 3, 4})); // 2
System.out.println(s.findLucky(new int[]{1, 2, 2, 3, 3, 3})); // 3
System.out.println(s.findLucky(new int[]{2, 2, 2, 3, 3})); // -1
System.out.println(s.findLuckyArray(new int[]{2, 2, 3, 4})); // 2
System.out.println(s.findLuckyArray(new int[]{1, 2, 2, 3, 3, 3})); // 3
System.out.println(s.findLuckyArray(new int[]{2, 2, 2, 3, 3})); // -1
}
}
复杂度分析
复杂度:O(n),空间 O(n)。
复杂度:O(n + V),空间 O(V)。
1395. Count Number of Teams(统计作战单位数)
题目描述
n 名士兵站成一排。每个士兵都有一个独一无二的评分 rating。
从中选出 3 个士兵组成一个作战单位,规则如下:
- 从队伍中选出下标分别为
i、j、k的 3 名士兵,他们的评分分别为 ratingi、ratingj、ratingk; - 作战单位需满足:ratingi < ratingj < ratingk 或者 ratingi > ratingj > ratingk,
其中 0 <=i<j<k<n。
请你返回按上述条件组建的作战单位的方案数。
示例 1
rating = [2,5,3,4,1] -> 3
示例 2
rating = [2,1,3] -> 0
示例 3
rating = [1,2,3,4] -> 4
约束:
- 3 <=
n<= 1000;rating 中各元素互不相同;1 <= ratingi <= 10^5。
解题思路
解法一:枚举中间元素(主提交版本)
固定 j 为中间士兵,统计 j 左侧比 ratingj 小的个数 leftLess、左侧比它大的个数 leftGreater、
右侧比它小的 rightLess、右侧比它大的 rightGreater。
递增三元组数 += leftLess * rightGreater;
递减三元组数 += leftGreater * rightLess。
复杂度:O(n^2),空间 O(1)。
解法二:三重循环暴力(对照)
直接枚举 (i, j, k) 判断 rating 单调性。复杂度 O(n^3),
仅作为正确性对照(n 较小时)。
代码实现
java
public class Solution {
public int numTeams(int[] rating) {
int n = rating.length;
int ans = 0;
for (int j = 1; j < n - 1; j++) {
int leftLess = 0, leftGreater = 0;
for (int i = 0; i < j; i++) {
if (rating[i] < rating[j]) {
leftLess++;
} else {
leftGreater++;
}
}
int rightLess = 0, rightGreater = 0;
for (int k = j + 1; k < n; k++) {
if (rating[k] < rating[j]) {
rightLess++;
} else {
rightGreater++;
}
}
ans += leftLess * rightGreater + leftGreater * rightLess;
}
return ans;
}
public int numTeamsBrute(int[] rating) {
int n = rating.length;
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
for (int k = j + 1; k < n; k++) {
if ((rating[i] < rating[j] && rating[j] < rating[k])
|| (rating[i] > rating[j] && rating[j] > rating[k])) {
ans++;
}
}
}
}
return ans;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.numTeams(new int[]{2, 5, 3, 4, 1})); // 3
System.out.println(s.numTeams(new int[]{2, 1, 3})); // 0
System.out.println(s.numTeams(new int[]{1, 2, 3, 4})); // 4
System.out.println(s.numTeamsBrute(new int[]{2, 5, 3, 4, 1})); // 3
System.out.println(s.numTeamsBrute(new int[]{2, 1, 3})); // 0
System.out.println(s.numTeamsBrute(new int[]{1, 2, 3, 4})); // 4
}
}
复杂度分析
复杂度:O(n^2),空间 O(1)。
1396. Design Underground System(设计地铁系统)
题目描述
地铁系统跟踪不同车站之间的乘客出行时间,并使用这一数据来计算从一站到另一站的平均时间。
实现 UndergroundSystem 类:
voidcheckIn(intid,StringstationName,intt):通行卡 ID 等于 id 的乘客,在时间t,
从stationName站进入。乘客一次只能从一个站进入。voidcheckOut(intid,StringstationName,intt):通行卡 ID 等于 id 的乘客,在时间t,
从stationName站离开。doublegetAverageTime(StringstartStation,StringendStation):返回从startStation站到
endStation站的平均时间。平均时间会根据截至目前所有从startStation站直接到达endStation站
的行程进行计算。从startStation到endStation的行程时间与反向行程时间可能不同。
在调用getAverageTime之前,至少有一名乘客从startStation站到达endStation站。
你可以假设对checkIn和checkOut方法的所有调用都是符合逻辑的。如果一名乘客在时间 t1 进站、
时间 t2 出站,那么 t1 < t2。所有时间都按时间顺序发生。
示例 1
checkIn(45,"Leyton",3); checkIn(32,"Paradise",8); checkIn(27,"Leyton",10);
checkOut(45,"Waterloo",15); checkOut(27,"Waterloo",20); checkOut(32,"Cambridge",22);
getAverageTime("Paradise","Cambridge") -> 14.0
getAverageTime("Leyton","Waterloo") -> 11.0
checkIn(10,"Leyton",24); checkOut(10,"Waterloo",38);
getAverageTime("Leyton","Waterloo") -> 12.0
约束:
- 调用次数 <= 2 * 10^4;站名由小写字母组成。
解题思路
解法一:双哈希表(主提交版本)
passengers: Map<id, station, time> 记录在途乘客;
route: Map<startStation + "#" + endStation, totalTime, count> 累计行程总时长与次数。
checkIn 记录起点,checkOut 计算耗时并累加进 route,getAverageTime 用总时长/次数。
复杂度:各操作 O(1),空间 O(n)。
解法二:嵌套 Map 结构(对照)
route 用 Map<startStation, Map<endStation, long\[\]>> 组织,逻辑等价,
展示另一种组织方式。复杂度同解法一。
代码实现
java
import java.util.HashMap;
import java.util.Map;
public class Solution {
public static class UndergroundSystem {
// id -> [startStation, startTime]
private final Map<Integer, String[]> passengers;
private final Map<Integer, Integer> startTimes;
// "start#end" -> {totalTime, count}
private final Map<String, long[]> routes;
public UndergroundSystem() {
passengers = new HashMap<>();
startTimes = new HashMap<>();
routes = new HashMap<>();
}
public void checkIn(int id, String stationName, int t) {
passengers.put(id, new String[]{stationName, ""});
startTimes.put(id, t);
}
public void checkOut(int id, String stationName, int t) {
String[] info = passengers.get(id);
String start = info[0];
int startT = startTimes.get(id);
String key = start + "#" + stationName;
long[] acc = routes.getOrDefault(key, new long[]{0, 0});
acc[0] += (t - startT);
acc[1]++;
routes.put(key, acc);
passengers.remove(id);
startTimes.remove(id);
}
public double getAverageTime(String startStation, String endStation) {
long[] acc = routes.get(startStation + "#" + endStation);
return (double) acc[0] / acc[1];
}
}
public static class UndergroundSystemNested {
// id -> [startStation, startTime]
private final Map<Integer, String[]> passengers;
private final Map<String, Map<String, long[]>> routes;
public UndergroundSystemNested() {
passengers = new HashMap<>();
routes = new HashMap<>();
}
public void checkIn(int id, String stationName, int t) {
passengers.put(id, new String[]{stationName, String.valueOf(t)});
}
public void checkOut(int id, String stationName, int t) {
String[] pinfo = passengers.get(id);
String start = pinfo[0];
int startT = Integer.parseInt(pinfo[1]);
Map<String, long[]> inner = routes.getOrDefault(start, new HashMap<>());
long[] acc = inner.getOrDefault(stationName, new long[]{0, 0});
acc[0] += (t - startT);
acc[1]++;
inner.put(stationName, acc);
routes.put(start, inner);
passengers.remove(id);
}
public double getAverageTime(String startStation, String endStation) {
long[] acc = routes.get(startStation).get(endStation);
return (double) acc[0] / acc[1];
}
}
public static void main(String[] args) {
UndergroundSystem us = new UndergroundSystem();
us.checkIn(45, "Leyton", 3);
us.checkIn(32, "Paradise", 8);
us.checkIn(27, "Leyton", 10);
us.checkOut(45, "Waterloo", 15);
us.checkOut(27, "Waterloo", 20);
us.checkOut(32, "Cambridge", 22);
System.out.println(us.getAverageTime("Paradise", "Cambridge")); // 14.0
System.out.println(us.getAverageTime("Leyton", "Waterloo")); // 11.0
us.checkIn(10, "Leyton", 24);
System.out.println(us.getAverageTime("Leyton", "Waterloo")); // 11.0
us.checkOut(10, "Waterloo", 38);
System.out.println(us.getAverageTime("Leyton", "Waterloo")); // 12.0
UndergroundSystemNested usn = new UndergroundSystemNested();
usn.checkIn(10, "Leyton", 3);
usn.checkOut(10, "Paradise", 8);
System.out.println(usn.getAverageTime("Leyton", "Paradise")); // 5.0
usn.checkIn(5, "Leyton", 10);
usn.checkOut(5, "Paradise", 16);
System.out.println(usn.getAverageTime("Leyton", "Paradise")); // 5.5
usn.checkIn(2, "Leyton", 21);
usn.checkOut(2, "Paradise", 30);
System.out.println(usn.getAverageTime("Leyton", "Paradise")); // 6.666... -> 约 6.666666666666667
}
}
复杂度分析
复杂度:各操作 O(1),空间 O(n)。
1397. Find All Good Strings(找到所有好字符串)
题目描述
给你两个长度为 n 的字符串 s1 和 s2,以及一个字符串 evil。请你返回好字符串的数目。
好字符串的定义为:它的长度为 n,字典序大于等于 s1,字典序小于等于 s2,且不包含 evil 为子字符串。
由于答案可能很大,请你返回答案对 10^9 + 7 取余的结果。
示例 1
n = 2, s1 = "aa", s2 = "da", evil = "b" -> 51
示例 2
n = 8, s1 = "leetcode", s2 = "leetgoes", evil = "leet" -> 0
示例 3
n = 2, s1 = "gx", s2 = "gz", evil = "x" -> 2
约束:
- 1 <=
n<= 500;1 <= evil.length <= 50;s1/s2长度n且全为小写字母。
解题思路
解法一:数位 DP + KMP 自动机(主提交版本)
计算 f(bound) = 长度 n、字典序 <= bound、不含 evil 的字符串个数。
答案 = f(s2) - f(s1) + isValid(s1)。
DP 状态:(pos, matched, tight),matched 为当前后缀与 evil 的最长匹配长度(KMP 自动机转移)。
复杂度:O(n * |evil| * 26)。空间 O(n * |evil|)。
解法二:朴素模拟统计(对照)
枚举从 s1 到 s2 的所有字符串判断是否包含 evil(字典序扫描),
仅适用于极小 n,作为正确性对照。
代码实现
java
public class Solution {
private static final int MOD = 1_000_000_007;
public int findGoodStrings(int n, String s1, String s2, String evil) {
int m = evil.length();
// KMP next
int[] next = new int[m];
for (int i = 1, j = 0; i < m; i++) {
while (j > 0 && evil.charAt(i) != evil.charAt(j)) {
j = next[j - 1];
}
if (evil.charAt(i) == evil.charAt(j)) {
j++;
}
next[i] = j;
}
int[][] go = new int[m][26];
for (int state = 0; state < m; state++) {
for (int ch = 0; ch < 26; ch++) {
int j = state;
char c = (char) ('a' + ch);
while (j > 0 && c != evil.charAt(j)) {
j = next[j - 1];
}
if (c == evil.charAt(j)) {
j++;
}
go[state][ch] = j;
}
}
long f1 = countLeq(n, s1, evil, m, go);
long f2 = countLeq(n, s2, evil, m, go);
long validS1 = isValid(s1, evil) ? 1 : 0;
long ans = (f2 - f1 + validS1) % MOD;
if (ans < 0) {
ans += MOD;
}
return (int) ans;
}
private boolean isValid(String s, String evil) {
return s.indexOf(evil) < 0;
}
// 字典序 <= bound 且不含 evil 的长度为 n 的字符串个数
private long countLeq(int n, String bound, String evil, int m, int[][] go) {
long[][][] memo = new long[n + 1][m + 1][2];
for (long[][] a : memo) {
for (long[] b : a) {
java.util.Arrays.fill(b, -1);
}
}
return dp(0, 0, 1, n, bound, evil, m, go, memo);
}
private long dp(int pos, int matched, int tight, int n, String bound, String evil,
int m, int[][] go, long[][][] memo) {
if (matched == m) {
return 0; // 已包含 evil
}
if (pos == n) {
return 1;
}
if (memo[pos][matched][tight] != -1) {
return memo[pos][matched][tight];
}
long res = 0;
int maxCh = (tight == 1) ? (bound.charAt(pos) - 'a') : 25;
for (int ch = 0; ch <= maxCh; ch++) {
int nxt = go[matched][ch];
if (nxt == m) {
continue;
}
int ntight = (tight == 1 && ch == maxCh) ? 1 : 0;
res = (res + dp(pos + 1, nxt, ntight, n, bound, evil, m, go, memo)) % MOD;
}
memo[pos][matched][tight] = res;
return res;
}
public int findGoodStringsBrute(int n, String s1, String s2, String evil) {
long[] low = new long[n];
long[] high = new long[n];
for (int i = 0; i < n; i++) {
low[i] = s1.charAt(i) - 'a';
high[i] = s2.charAt(i) - 'a';
}
int cnt = 0;
long[] cur = low.clone();
while (true) {
// 判断 cur >= low 且 <= high 且不含 evil
boolean ok = true;
StringBuilder sb = new StringBuilder();
for (long x : cur) {
sb.append((char) ('a' + x));
}
if (sb.indexOf(evil) >= 0) {
ok = false;
}
if (ok && le(cur, high)) {
cnt++;
}
// 累加 1(按 26 进制)
int i = n - 1;
while (i >= 0 && cur[i] == 25) {
cur[i] = 0;
i--;
}
if (i < 0) {
break;
}
cur[i]++;
if (!le(cur, high)) {
break;
}
}
return cnt % MOD;
}
private boolean le(long[] a, long[] b) {
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return a[i] < b[i];
}
}
return true;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.findGoodStrings(2, "aa", "da", "b")); // 51
System.out.println(s.findGoodStrings(8, "leetcode", "leetgoes", "leet")); // 0
System.out.println(s.findGoodStrings(2, "gx", "gz", "x")); // 2
System.out.println(s.findGoodStrings(3, "aaa", "aab", "aa")); // 0?校验:aaa/aab 均含 aa
System.out.println(s.findGoodStringsBrute(2, "aa", "da", "b")); // 51
System.out.println(s.findGoodStringsBrute(2, "gx", "gz", "x")); // 2
System.out.println(s.findGoodStringsBrute(3, "aaa", "aab", "aa")); // 0
}
}
复杂度分析
复杂度:O(n * |evil| * 26)。空间 O(n * |evil|)。
1398. Customers Who Bought Products A and B but Not C(购买了产品A和产品B却没有购买产品C的顾客)
题目类型:数据库(SQL)
题目描述
Customers 表:
+---------------------+---------+
| Column Name | Type |
+---------------------+---------+
| customer_id | int |
| customer_name | varchar |
+---------------------+---------+
customer_id 是这张表中具有唯一值的列。
customer_name 是顾客的名称。
Orders 表:
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| order_id | int |
| customer_id | int |
| product_name | varchar |
+---------------+---------+
order_id 是这张表中具有唯一值的列。
customer_id 是购买了名为 "product_name" 产品顾客的id。
请你编写解决方案,报告购买了产品 "A" ,"B" 但没有购买产品 "C" 的客户的 customer_id 和 customer_name,因为我们想推荐他们购买这样的产品。
返回按 customer_id 排序 的结果表。
返回结果格式如下所示。
示例 1:
**输入:**
Customers table:
+-------------+---------------+
| customer_id | customer_name |
+-------------+---------------+
| 1 | Daniel |
| 2 | Diana |
| 3 | Elizabeth |
| 4 | Jhon |
+-------------+---------------+
Orders table:
+------------+--------------+---------------+
| order_id | customer_id | product_name |
+------------+--------------+---------------+
| 10 | 1 | A |
| 20 | 1 | B |
| 30 | 1 | D |
| 40 | 1 | C |
| 50 | 2 | A |
| 60 | 3 | A |
| 70 | 3 | B |
| 80 | 3 | D |
| 90 | 4 | C |
+------------+--------------+---------------+
**输出:**
+-------------+---------------+
| customer_id | customer_name |
+-------------+---------------+
| 3 | Elizabeth |
+-------------+---------------+
**解释:**
只有 customer_id 为 3 的顾客购买了产品 A 和产品 B ,却没有购买产品 C 。
解题思路
思考
买过 A A A、 B B B 却从未买 C C C 的顾客。客户左连订单后按人分组,用条件求和: S U M ( product = A ) > 0 \mathrm{SUM}(\textit{product}=A)>0 SUM(product=A)>0 且 B B B 同样,且 C C C 的和为 0 0 0。
考察HAVING 条件聚合:按顾客分组,用 SUM 判断买过 A、买过 B、没买过 C 三个条件同时成立。
代码实现
sql
# Write your MySQL query statement below
SELECT customer_id, customer_name
FROM
Customers
LEFT JOIN Orders USING (customer_id)
GROUP BY 1
HAVING SUM(product_name = 'A') > 0 AND SUM(product_name = 'B') > 0 AND SUM(product_name = 'C') = 0
ORDER BY 1;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1399. Count Largest Group(统计最大组的数目)
题目描述
给定一个整数 n。我们需要根据数字的数位和将 1 到 n 的数字分组。
例如,数字 14 和 5 属于同一组(1+4=5=5),而数字 13 和 3 属于不同组(1+3=4 vs 3)。
返回最大组的数字数量,即元素数量最多的组。
示例 1
n = 13 -> 4(数位和为 1..4 的各组分别有 2 个数字,共 4 个组并列最大)
示例 2
n = 2 -> 2(组 [1]、[2] 各 1 个数字,大小均为 1,共 2 组)
示例 3
n = 15 -> 6(数位和为 1..5 的各组均为 2 个,共 5 组?校验:1..15 数位和 1..6,
6 组各 2 个 -> 6)
约束:
- 1 <=
n<= 10^4。
解题思路
解法一:哈希计数(主提交版本)
遍历 1...n,用 Map<Integer, Integer> 统计每个数位和的频次,再取最大值并统计
达到该最大值的组数。复杂度:O(n * digits),空间 O(1)(数位和 ≤ 36)。
解法二:数组计数(对照)
数位和上限为 9 * 4 = 36(n ≤ 10^4),直接用 int46 计数并扫描,逻辑更简洁。
复杂度:O(n),空间 O(1)。
代码实现
java
import java.util.HashMap;
import java.util.Map;
public class Solution {
public int countLargestGroup(int n) {
Map<Integer, Integer> cnt = new HashMap<>();
for (int x = 1; x <= n; x++) {
int sum = digitSum(x);
cnt.put(sum, cnt.getOrDefault(sum, 0) + 1);
}
int max = 0;
for (int v : cnt.values()) {
max = Math.max(max, v);
}
int res = 0;
for (int v : cnt.values()) {
if (v == max) {
res++;
}
}
return res;
}
private int digitSum(int x) {
int s = 0;
while (x > 0) {
s += x % 10;
x /= 10;
}
return s;
}
public int countLargestGroupArray(int n) {
int[] cnt = new int[46];
for (int x = 1; x <= n; x++) {
cnt[digitSum(x)]++;
}
int max = 0;
for (int v : cnt) {
max = Math.max(max, v);
}
int res = 0;
for (int v : cnt) {
if (v == max) {
res++;
}
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.countLargestGroup(13)); // 4
System.out.println(s.countLargestGroup(2)); // 2
System.out.println(s.countLargestGroup(15)); // 6
System.out.println(s.countLargestGroup(24)); // 5
System.out.println(s.countLargestGroupArray(13)); // 4
System.out.println(s.countLargestGroupArray(2)); // 2
System.out.println(s.countLargestGroupArray(15)); // 6
System.out.println(s.countLargestGroupArray(24)); // 5
}
}
复杂度分析
达到该最大值的组数。复杂度:O(n * digits),空间 O(1)(数位和 ≤ 36)。
复杂度:O(n),空间 O(1)。
1400. Construct K Palindrome Strings(构造 K 个回文字符串)
题目描述
给你一个字符串 s 和一个整数 k。请你用 s 字符串中所有字符构造 k 个非空回文串。
如果你可以用 s 中所有字符构造 k 个回文字符串,那么请你返回 True,否则返回 False。
示例 1
s = "annabelle", k = 2 -> true(如 "anna" + "elble")
示例 2
s = "leetcode", k = 3 -> false
示例 3
s = "true", k = 4 -> true(每个字符单独一个回文串)
示例 4
s = "yzyzyzyzyzyzyzy", k = 2 -> true
示例 5
s = "cr", k = 7 -> false(字符不够分 7 个非空串)
约束:
- 1 <=
s.length<= 10^5;1 <=k<= 10^5。
解题思路
解法一:奇数字符计数(主提交版本)
回文串中心可以有且仅有一个奇数字符。要构造 k 个非空回文串:
- 要求 k <= s.length();
- 设 odd 为出现次数为奇数的字符种数,每个奇数种字符都必须单独占用一个回文串中心,
因此还需要 k >= odd。
满足这两个条件即可。复杂度:O(n),空间 O(26)。
解法二:频次数组 + 条件校验(对照)
用 int26 统计并校验,逻辑与解法一一致但完全避免使用 Map。
复杂度:O(n),空间 O(1)。
代码实现
java
import java.util.HashMap;
import java.util.Map;
public class Solution {
public boolean canConstruct(String s, int k) {
if (k > s.length()) {
return false;
}
Map<Character, Integer> cnt = new HashMap<>();
for (char c : s.toCharArray()) {
cnt.put(c, cnt.getOrDefault(c, 0) + 1);
}
int odd = 0;
for (int v : cnt.values()) {
if ((v & 1) == 1) {
odd++;
}
}
return k >= odd;
}
public boolean canConstructArray(String s, int k) {
if (k > s.length()) {
return false;
}
int[] cnt = new int[26];
for (char c : s.toCharArray()) {
cnt[c - 'a']++;
}
int odd = 0;
for (int v : cnt) {
if ((v & 1) == 1) {
odd++;
}
}
return k >= odd;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.canConstruct("annabelle", 2)); // true
System.out.println(s.canConstruct("leetcode", 3)); // false
System.out.println(s.canConstruct("true", 4)); // true
System.out.println(s.canConstruct("yzyzyzyzyzyzyzy", 2)); // true
System.out.println(s.canConstruct("cr", 7)); // false
System.out.println(s.canConstructArray("annabelle", 2)); // true
System.out.println(s.canConstructArray("leetcode", 3)); // false
System.out.println(s.canConstructArray("true", 4)); // true
System.out.println(s.canConstructArray("yzyzyzyzyzyzyzy", 2)); // true
System.out.println(s.canConstructArray("cr", 7)); // false
}
}
复杂度分析
满足这两个条件即可。复杂度:O(n),空间 O(26)。
复杂度:O(n),空间 O(1)。