LeetCode1381~1400题目解析

LeetCode 1381-1400. 题解合集

文章目录


1381. Design a Stack With Increment Operation(设计一个支持增量操作的栈)

题目描述

请你设计一个支持对其元素进行增量操作的栈。

实现自定义栈类 CustomStack:

  • CustomStack(int maxSize):用 maxSize 初始化对象,maxSize 是栈中最多能容纳的元素数量。
  • void push(int x):如果栈还未增长到 maxSize,就将 x 添加到栈顶。
  • int pop():弹出栈顶元素,并返回栈顶的值,或栈为空时返回 -1。
  • void inc(int k, int val):栈底的 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)。


题目描述

给你一棵二叉搜索树,请你返回一棵平衡后的二叉搜索树,新生成的树应该与原来的树有着相同的节点值。

如果有多种构造方法,请你返回任意一种。

如果一棵二叉搜索树中,每个节点的两棵子树高度差不超过 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 类:

  • void checkIn(int id, String stationName, int t):通行卡 ID 等于 id 的乘客,在时间 t,
    从 stationName 站进入。乘客一次只能从一个站进入。
  • void checkOut(int id, String stationName, int t):通行卡 ID 等于 id 的乘客,在时间 t,
    从 stationName 站离开。
  • double getAverageTime(String startStation, String endStation):返回从 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 个非空回文串:

  1. 要求 k <= s.length();
  2. 设 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)。

相关推荐
开开心心_Every1 小时前
快速隐藏程序工具自定义快捷键一键切换
java·开发语言·前端·spring·智能手机·word·intellij-idea
萧瑟余晖2 小时前
Spring Batch核心模型与快速上手详解
java·spring·batch
缓慢更新2 小时前
AI 辅助生成单测:从接口定义到用例的流水线
java·服务器·前端·网络·数据库·人工智能
LMT流氓兔4 小时前
第五单元——第十六课:给书店助手加上网页界面
java·开发语言·jvm·人工智能·oracle·langchain·1024程序员节
Tisfy5 小时前
LeetCode 3882.网格图中最小异或路径:动态规划
算法·leetcode·动态规划
wangbing11255 小时前
平台介绍-modbus设备调试
java
西索斯coding5 小时前
gpt-image-2.5-sunburst 和 gemini-3.1-flash-image 差多少?60 组项目素材的成功成本评测框架
java·linux·gpt·ai
A boy CDEF girl6 小时前
算法题专栏:优选算法
数据结构·算法·leetcode
wuminyu6 小时前
Java字节码学习总结:字节码如何加载、解析和执行
java·linux·c语言·jvm·c++