LeetCode1401~1420题目解析

LeetCode 1401-1420. 题解合集

文章目录


1401. Circle and Rectangle Overlapping(圆和矩形是否有重叠)

题目描述

给你一个以 (radius, xCenter, yCenter) 表示的圆和一个与坐标轴平行的矩形 (x1, y1, x2, y2),

其中 (x1, y1) 是矩形左下角的坐标,而 (x2, y2) 是右上角的坐标。

如果圆和矩形有重叠的部分,请你返回 true,否则返回 false。

换句话说,请你检测是否存在点 (xi, yi),它既在圆上也在矩形上(两者都包括点落在边界上的情况)。

示例 1

复制代码
radius = 1, xCenter = 0, yCenter = 0, x1 = 1, y1 = -1, x2 = 3, y2 = 1 -> true

示例 2

复制代码
radius = 1, xCenter = 1, yCenter = 1, x1 = 1, y1 = -3, x2 = 2, y2 = -1 -> false

示例 3

复制代码
radius = 1, xCenter = 0, yCenter = 0, x1 = -1, y1 = 0, x2 = 0, y2 = 1 -> true

约束:

  • 1 <= radius <= 2000;-10^4 <= xCenter, yCenter, x1, y1, x2, y2 <= 10^4。

解题思路

解法一:圆心到矩形的最近距离(主提交版本)

矩形内离圆心最近的点 P = (clamp(xCenter, x1, x2), clamp(yCenter, y1, y2))。

圆与矩形有交当且仅当圆心到 P 的距离 <= radius。

复杂度:O(1)。

解法二:圆心在矩形内 + 四边距离判定(对照)

若圆心在矩形内直接返回 true;否则分别计算圆心到矩形左/右/上/下四条边

(线段)的最近距离,取最小值判断是否 <= radius。

复杂度:O(1)。

代码实现

java 复制代码
public class Solution {

    public boolean checkOverlap(int radius, int xCenter, int yCenter,
                                int x1, int y1, int x2, int y2) {
        long nx = clamp(xCenter, x1, x2);
        long ny = clamp(yCenter, y1, y2);
        long dx = xCenter - nx;
        long dy = yCenter - ny;
        return dx * dx + dy * dy <= (long) radius * radius;
    }

    private long clamp(int x, int lo, int hi) {
        if (x < lo) {
            return lo;
        }
        if (x > hi) {
            return hi;
        }
        return x;
    }

    public boolean checkOverlapEdges(int radius, int xCenter, int yCenter,
                                     int x1, int y1, int x2, int y2) {
        if (xCenter >= x1 && xCenter <= x2 && yCenter >= y1 && yCenter <= y2) {
            return true; // 圆心在矩形内
        }
        long r2 = (long) radius * radius;
        // 左右两条竖边(线段):x=x1 或 x2,y 从 y1 到 y2
        if (distToSegment(xCenter, yCenter, x1, y1, x1, y2) <= r2) {
            return true;
        }
        if (distToSegment(xCenter, yCenter, x2, y1, x2, y2) <= r2) {
            return true;
        }
        // 上下两条横边:y=y1 或 y2,x 从 x1 到 x2
        if (distToSegment(xCenter, yCenter, x1, y1, x2, y1) <= r2) {
            return true;
        }
        return distToSegment(xCenter, yCenter, x1, y2, x2, y2) <= r2;
    }

    private long distToSegment(long px, long py, long ax, long ay, long bx, long by) {
        long dx = bx - ax;
        long dy = by - ay;
        // 线段长度平方
        long len2 = dx * dx + dy * dy;
        long t;
        if (len2 == 0) {
            t = 0;
        } else {
            long dot = (px - ax) * dx + (py - ay) * dy;
            t = dot;
            if (t < 0) {
                t = 0;
            } else if (t > len2) {
                t = len2;
            }
        }
        long cx = ax + dx * t / (len2 == 0 ? 1 : len2);
        long cy = ay + dy * t / (len2 == 0 ? 1 : len2);
        long ex = px - cx;
        long ey = py - cy;
        return ex * ex + ey * ey;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.checkOverlap(1, 0, 0, 1, -1, 3, 1)); // true
        System.out.println(s.checkOverlap(1, 1, 1, 1, -3, 2, -1)); // false
        System.out.println(s.checkOverlap(1, 0, 0, -1, 0, 0, 1)); // true
        System.out.println(s.checkOverlap(5, 0, 5, 1, 2, 3, 4)); // true
        System.out.println(s.checkOverlapEdges(1, 0, 0, 1, -1, 3, 1)); // true
        System.out.println(s.checkOverlapEdges(1, 1, 1, 1, -3, 2, -1)); // false
        System.out.println(s.checkOverlapEdges(1, 0, 0, -1, 0, 0, 1)); // true
        System.out.println(s.checkOverlapEdges(5, 0, 5, 1, 2, 3, 4)); // true
    }
}

复杂度分析

复杂度:O(1)。


1402. Reducing Dishes(做菜顺序)

题目描述

一个厨师收集了他 n 道菜的满意程度 satisfaction,这个厨师做出每道菜的时间都是 1 单位时间。

一道菜的「like-time 系数」定义为烹饪这道菜结束的时间(包含之前每道菜所花费的时间)乘以

这道菜的满意程度,也就是 timei * satisfactioni。

返回厨师在准备了一定数量的菜肴后可以获得的最大 like-time 系数总和。

你可以按任意顺序安排做菜的顺序,你也可以选择放弃做某些菜来获得更大的总和。

示例 1

复制代码
satisfaction = [-1,-8,0,5,-9] -> 14

示例 2

复制代码
satisfaction = [4,3,2] -> 20

示例 3

复制代码
satisfaction = [-1,-4,-5] -> 0

约束:

  • 1 <= satisfaction.length <= 500;-10^3 <= satisfactioni <= 10^3。

解题思路

解法一:贪心排序 + 后缀选择(主提交版本)

做菜顺序一定是 satisfaction 升序排列后选一个后缀(从某一位置开始连续做到末尾)。

对升序排序后的数组,从末尾往前累加,若当前总贡献增加则保留,否则停止。

因为一旦加入一个负数使前缀和变负,再往左只会更差。

复杂度:O(n log n),空间 O(1)。

解法二:枚举起点(对照)

升序排序后枚举每个起点 i,计算从 i 做到末尾的 like-time 系数总和,取最大值。

复杂度:O(n^2),仅作正确性对照。

代码实现

java 复制代码
import java.util.Arrays;

public class Solution {

    public int maxSatisfaction(int[] satisfaction) {
        Arrays.sort(satisfaction);
        int n = satisfaction.length;
        int ans = 0;
        int sum = 0; // 当前后缀的元素和(决定加入左边的菜是否值得)
        int total = 0; // 当前 like-time 系数总和
        for (int i = n - 1; i >= 0; i--) {
            sum += satisfaction[i];
            if (sum <= 0) {
                break; // 加入该菜后整体贡献不再增加
            }
            total += sum;
        }
        return total;
    }

    public int maxSatisfactionBrute(int[] satisfaction) {
        Arrays.sort(satisfaction);
        int n = satisfaction.length;
        int best = 0;
        for (int i = 0; i < n; i++) {
            int cur = 0;
            int time = 1;
            for (int j = i; j < n; j++) {
                cur += time * satisfaction[j];
                time++;
            }
            best = Math.max(best, cur);
        }
        return best;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.maxSatisfaction(new int[]{-1, -8, 0, 5, -9})); // 14
        System.out.println(s.maxSatisfaction(new int[]{4, 3, 2})); // 20
        System.out.println(s.maxSatisfaction(new int[]{-1, -4, -5})); // 0
        System.out.println(s.maxSatisfactionBrute(new int[]{-1, -8, 0, 5, -9})); // 14
        System.out.println(s.maxSatisfactionBrute(new int[]{4, 3, 2})); // 20
        System.out.println(s.maxSatisfactionBrute(new int[]{-1, -4, -5})); // 0
    }
}

复杂度分析

复杂度:O(n log n),空间 O(1)。

复杂度:O(n^2),仅作正确性对照。


1403. Minimum Subsequence in Non-Increasing Order(非递增顺序的最小子序列)

题目描述

给你一个数组 nums,请你从中抽取一个子序列,满足该子序列的元素之和严格大于未包含在

该子序列中的各元素之和。

如果存在多个解决方案,只需返回长度最小的子序列。如果仍然有多个解决方案,则返回

元素之和最大的子序列。

注意:题目数据保证满足所有约束条件的解决方案是唯一的。同时,返回的答案应当按非递增顺序排列。

示例 1

复制代码
nums = [4,3,10,9,8] -> [10,9]

示例 2

复制代码
nums = [4,4,7,6,7] -> [7,7,6]

示例 3

复制代码
nums = [6] -> [6]

约束:

  • 1 <= nums.length <= 500;1 <= nums[i] <= 100。

解题思路

解法一:降序贪心(主提交版本)

先把 nums 降序排序,从最大的元素开始累加,当累加和第一次 > 总和/2 时停止。

此时取出的元素数量最少,且因为从大到小取,元素和也最大,返回前缀即可。

复杂度:O(n log n),空间 O(n)。

解法二:计数排序 + 贪心(对照)

值域 ≤ 100,用桶计数,从大到小累加直到过半,逻辑一致但更贴近值域特性。

复杂度:O(n + V),空间 O(V)。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Solution {

    public List<Integer> minSubsequence(int[] nums) {
        Arrays.sort(nums);
        int total = 0;
        for (int v : nums) {
            total += v;
        }
        List<Integer> res = new ArrayList<>();
        int sum = 0;
        for (int i = nums.length - 1; i >= 0; i--) {
            sum += nums[i];
            res.add(nums[i]);
            if (sum * 2 > total) {
                break;
            }
        }
        return res;
    }

    public List<Integer> minSubsequenceCount(int[] nums) {
        int[] cnt = new int[101];
        int total = 0;
        for (int v : nums) {
            cnt[v]++;
            total += v;
        }
        List<Integer> res = new ArrayList<>();
        int sum = 0;
        for (int v = 100; v >= 1; v--) {
            while (cnt[v] > 0 && sum * 2 <= total) {
                sum += v;
                res.add(v);
                cnt[v]--;
            }
            if (sum * 2 > total) {
                break;
            }
        }
        return res;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.minSubsequence(new int[]{4, 3, 10, 9, 8})); // [10, 9]
        System.out.println(s.minSubsequence(new int[]{4, 4, 7, 6, 7})); // [7, 7, 6]
        System.out.println(s.minSubsequence(new int[]{6})); // [6]
        System.out.println(s.minSubsequenceCount(new int[]{4, 3, 10, 9, 8})); // [10, 9]
        System.out.println(s.minSubsequenceCount(new int[]{4, 4, 7, 6, 7})); // [7, 7, 6]
        System.out.println(s.minSubsequenceCount(new int[]{6})); // [6]
    }
}

复杂度分析

复杂度:O(n log n),空间 O(n)。

复杂度:O(n + V),空间 O(V)。


1404. Number of Steps to Reduce a Number in Binary Representation to One(将二进制表示减到 1 的步骤数)

题目描述

给你一个以二进制形式表示整数 s 的字符串,返回将其按下列规则减到 1 所需要的步骤数:

  • 如果当前数为偶数,则将其除以 2。
  • 如果当前数为奇数,则将其加 1。
    题目保证所有测试用例都可以到达 1。

示例 1

复制代码
s = "1101" -> 6(13 -> 14 -> 7 -> 8 -> 4 -> 2 -> 1)

示例 2

复制代码
s = "10" -> 1

示例 3

复制代码
s = "1" -> 0

约束:

  • 1 <= s.length <= 500;s 不含前导零。

解题思路

解法一:模拟二进制位操作(主提交版本)

从字符串最后一位开始模拟:

  • 若末尾为 '0'(偶数):除以 2 即去掉末尾,steps++;
  • 若末尾为 '1'(奇数):加 1 会产生进位------从当前位向前找到第一个 '0' 置 1、其后的
    低位全变 0(实际是字符串进位);若全为 1 则变成 100...0,steps++ 并继续。
    处理到只剩 "1" 为止。复杂度:O(n^2) 最坏(进位引起复制),n ≤ 500 可接受。
    解法二:位长 + 统计规律(对照)
    观察规律:从字符串高位到低位,遇到 '1' 都会额外产生一次「加 1」进位操作。
    答案 = 字符串长度(减 1 的除法次数)+ 1 的个数 - 首位的 1 需要特判。
    更精确公式:ans = len - 1 + count1 - (last 位为 0 时不需要额外处理)。
    采用逐位分析:steps = (n - 1) + (# of '1' after first) + (是否有进位时需要多一步)。

代码实现

java 复制代码
public class Solution {

    public int numSteps(String s) {
        StringBuilder sb = new StringBuilder(s);
        int steps = 0;
        while (sb.length() > 1) {
            int last = sb.length() - 1;
            if (sb.charAt(last) == '0') {
                // 偶数:除以 2
                sb.deleteCharAt(last);
            } else {
                // 奇数:加 1(模拟进位)
                int i = last;
                while (i >= 0 && sb.charAt(i) == '1') {
                    sb.setCharAt(i, '0');
                    i--;
                }
                if (i < 0) {
                    sb.insert(0, '1');
                } else {
                    sb.setCharAt(i, '1');
                }
            }
            steps++;
        }
        return steps;
    }

    public int numStepsFormula(String s) {
        int n = s.length();
        int steps = 0;
        int carry = 0; // 进位标记:若低位曾经触发过加 1,则高位处理会受影响
        for (int i = n - 1; i >= 1; i--) {
            int digit = (s.charAt(i) - '0') + carry;
            if (digit % 2 == 0) {
                // 偶数:除 2
                steps++;
            } else {
                // 奇数:加 1 再除 2(两步操作计 2)
                steps += 2;
                carry = 1;
            }
        }
        // 处理最高位:若 s[0]=1 且 carry=1,则最后一次加 1 使长度 +1 后再除 2 共 1 步
        return steps + (s.charAt(0) == '1' && carry == 1 ? 1 : 0);
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.numSteps("1101")); // 6
        System.out.println(s.numSteps("10")); // 1
        System.out.println(s.numSteps("1")); // 0
        System.out.println(s.numSteps("111")); // 4? 校验:7->8->4->2->1 = 4
        System.out.println(s.numStepsFormula("1101")); // 6
        System.out.println(s.numStepsFormula("10")); // 1
        System.out.println(s.numStepsFormula("1")); // 0
        System.out.println(s.numStepsFormula("111")); // 4
    }
}

复杂度分析


1405. Longest Happy String(最长快乐字符串)

题目描述

如果字符串中不含有任何 'aaa'、'bbb' 或 'ccc' 这样的字符串作为子串,那么该字符串就是一个

「快乐字符串」。给你三个整数 a、b、c,请你返回任意一个满足下列全部条件的字符串 s:

  • s 是一个尽可能长的快乐字符串;
  • s 中最多有 a 个字母 'a'、b 个字母 'b'、c 个字母 'c';
  • s 中只含有 'a'、'b'、'c' 三种字母。
    如果不存在这样的字符串 s,请返回一个空字符串 ""。

示例 1

复制代码
a = 1, b = 1, c = 7 -> "ccaccbcc"

示例 2

复制代码
a = 7, b = 1, c = 0 -> "aabaa"

约束:

  • 0 <= a, b, c <= 100。

解题思路

解法一:贪心 + 优先队列(主提交版本)

每次选择剩余数量最多的字母,但若它已经连续出现两次,则改选次多的字母。

用最大堆维护 (剩余数量, 字母)。循环直到无法添加。

复杂度:O((a+b+c) * log 3) = O(n)。

解法二:朴素贪心交替选取(对照)

同样的「最多优先、避免三连」逻辑,用三个计数器手动实现,不借助堆。

复杂度:O(n)。

代码实现

java 复制代码
import java.util.PriorityQueue;

public class Solution {

    public String longestDiverseString(int a, int b, int c) {
        PriorityQueue<int[]> pq = new PriorityQueue<>((x, y) -> y[0] - x[0]);
        if (a > 0) {
            pq.offer(new int[]{a, 0}); // 0 -> 'a'
        }
        if (b > 0) {
            pq.offer(new int[]{b, 1}); // 1 -> 'b'
        }
        if (c > 0) {
            pq.offer(new int[]{c, 2}); // 2 -> 'c'
        }
        StringBuilder sb = new StringBuilder();
        while (!pq.isEmpty()) {
            int[] top = pq.poll();
            int n = sb.length();
            // 若与前两个字符相同,则改用次多的字母
            if (n >= 2 && sb.charAt(n - 1) - 'a' == top[1] && sb.charAt(n - 2) - 'a' == top[1]) {
                if (pq.isEmpty()) {
                    break; // 无次多字母可选
                }
                int[] second = pq.poll();
                sb.append((char) ('a' + second[1]));
                second[0]--;
                if (second[0] > 0) {
                    pq.offer(second);
                }
                pq.offer(top); // 把最多的放回
            } else {
                sb.append((char) ('a' + top[1]));
                top[0]--;
                if (top[0] > 0) {
                    pq.offer(top);
                }
            }
        }
        return sb.toString();
    }

    public String longestDiverseStringManual(int a, int b, int c) {
        StringBuilder sb = new StringBuilder();
        int[] cnt = {a, b, c};
        while (true) {
            int prev = sb.length() > 0 ? sb.charAt(sb.length() - 1) - 'a' : -1;
            // 选剩余最多且不与前两个相同的字符
            int pick = -1;
            int best = 0;
            for (int i = 0; i < 3; i++) {
                if (cnt[i] <= 0) {
                    continue;
                }
                if (sb.length() >= 2
                        && sb.charAt(sb.length() - 1) - 'a' == i
                        && sb.charAt(sb.length() - 2) - 'a' == i) {
                    continue; // 会形成三连
                }
                if (cnt[i] > best) {
                    best = cnt[i];
                    pick = i;
                }
            }
            if (pick == -1) {
                break;
            }
            sb.append((char) ('a' + pick));
            cnt[pick]--;
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.longestDiverseString(1, 1, 7)); // ccaccbcc 类
        System.out.println(s.longestDiverseString(7, 1, 0)); // aabaa 类
        System.out.println(s.longestDiverseString(0, 0, 0)); // ""
        System.out.println(s.longestDiverseString(2, 2, 1)); // ababc 类
        System.out.println(s.longestDiverseStringManual(1, 1, 7));
        System.out.println(s.longestDiverseStringManual(7, 1, 0));
        System.out.println(s.longestDiverseStringManual(0, 0, 0));
    }
}

复杂度分析

复杂度:O((a+b+c) * log 3) = O(n)。

复杂度:O(n)。


1406. Stone Game III(石子游戏 III)

题目描述

Alice 和 Bob 继续他们的石子游戏。几堆石子排成一行,每堆石子都对应一个得分,由数组

stoneValue 给出。Alice 和 Bob 轮流取石子,Alice 总是先开始。在每个玩家的回合中,

该玩家可以拿走剩下石子中的前 1、2 或 3 堆石子。比赛一直持续到所有石头都被拿走。

每个玩家的最终得分为他所拿到的每堆石子的对应得分之和。每个玩家的初始分数都是 0。

比赛的目标是决出最高分,得分最高的选手将会赢得比赛,比赛也可能会出现平局。

假设 Alice 和 Bob 都采取最优策略。

如果 Alice 赢了就返回 "Alice",Bob 赢了就返回 "Bob",分数相同返回 "Tie"。

示例 1

复制代码
values = [1,2,3,7] -> "Bob"

示例 2

复制代码
values = [1,2,3,-9] -> "Alice"

示例 3

复制代码
values = [1,2,3,6] -> "Tie"

约束:

  • 1 <= values.length <= 5 * 10^4;-1000 <= valuesi <= 1000。

解题思路

解法一:倒序 DP + 差值(主提交版本)

dpi 表示「从 i 开始到末尾」当前行动者能获得的最大(得分差)。

dpi = max over k∈{1,2,3} of (prefixi+k - prefixi) - dpi+k。

即取走前 k 堆的分差 减去 对手在剩余部分的最大分差。

用后缀和避免重复计算。复杂度 O(n),空间 O(n)(可滚动优化到 O(1))。

解法二:正序记忆化递归(对照)

dfs(i) 返回从 i 开始的最大分差,带记忆化数组。逻辑同解法一,方向相反。

复杂度 O(n)。

代码实现

java 复制代码
public class Solution {

    public String stoneGameIII(int[] stoneValue) {
        int n = stoneValue.length;
        int[] suffix = new int[n + 1];
        for (int i = n - 1; i >= 0; i--) {
            suffix[i] = suffix[i + 1] + stoneValue[i];
        }
        int[] dp = new int[n + 2]; // dp[n+1] = 0
        for (int i = n - 1; i >= 0; i--) {
            int best = Integer.MIN_VALUE;
            int take = 0;
            for (int k = 1; k <= 3 && i + k <= n; k++) {
                take += stoneValue[i + k - 1];
                // 取 k 堆后对手的最大分差
                best = Math.max(best, take - dp[i + k]);
            }
            dp[i] = best;
        }
        if (dp[0] > 0) {
            return "Alice";
        }
        if (dp[0] < 0) {
            return "Bob";
        }
        return "Tie";
    }

    public String stoneGameIIIMemo(int[] stoneValue) {
        int n = stoneValue.length;
        int[] suffix = new int[n + 1];
        for (int i = n - 1; i >= 0; i--) {
            suffix[i] = suffix[i + 1] + stoneValue[i];
        }
        int[] memo = new int[n + 1];
        java.util.Arrays.fill(memo, Integer.MIN_VALUE);
        int diff = dfs(0, stoneValue, suffix, memo);
        if (diff > 0) {
            return "Alice";
        }
        if (diff < 0) {
            return "Bob";
        }
        return "Tie";
    }

    private int dfs(int i, int[] stoneValue, int[] suffix, int[] memo) {
        if (i >= stoneValue.length) {
            return 0;
        }
        if (memo[i] != Integer.MIN_VALUE) {
            return memo[i];
        }
        int best = Integer.MIN_VALUE;
        int take = 0;
        for (int k = 1; k <= 3 && i + k <= stoneValue.length; k++) {
            take += stoneValue[i + k - 1];
            best = Math.max(best, take - dfs(i + k, stoneValue, suffix, memo));
        }
        memo[i] = best;
        return best;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.stoneGameIII(new int[]{1, 2, 3, 7})); // Bob
        System.out.println(s.stoneGameIII(new int[]{1, 2, 3, -9})); // Alice
        System.out.println(s.stoneGameIII(new int[]{1, 2, 3, 6})); // Tie
        System.out.println(s.stoneGameIII(new int[]{-1, -2, -3})); // Tie? 校验:-1-2-3 先手
        // 校验:[-1,-2,-3],dp[0]: 取1个 -1 - dp[1]; 取2个 -3-dp[2]; 取3个 -6-dp[3]
        // dp[3]=0... 逐步计算得 dp[0]=-2 双方最优会平?实际输出以运行为准
        System.out.println(s.stoneGameIIIMemo(new int[]{1, 2, 3, 7})); // Bob
        System.out.println(s.stoneGameIIIMemo(new int[]{1, 2, 3, -9})); // Alice
        System.out.println(s.stoneGameIIIMemo(new int[]{1, 2, 3, 6})); // Tie
    }
}

复杂度分析

用后缀和避免重复计算。复杂度 O(n),空间 O(n)(可滚动优化到 O(1))。


1407. Top Travellers(排名靠前的旅行者)

题目类型:数据库(SQL)

题目描述

表:Users

复制代码
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| id            | int     |
| name          | varchar |
+---------------+---------+
id 是该表中具有唯一值的列。
name 是用户名字。

表:Rides

复制代码
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| id            | int     |
| user_id       | int     |
| distance      | int     |
+---------------+---------+
id 是该表中具有唯一值的列。
user_id 是本次行程的用户的 id, 而该用户此次行程距离为 distance 。

编写解决方案,报告每个用户的旅行距离。

返回的结果表单,以 travelled_distance 降序排列 ,如果有两个或者更多的用户旅行了相同的距离, 那么再以 name 升序排列 。

返回结果格式如下例所示。

示例 1:

复制代码
**输入:**
Users 表:
+------+-----------+
| id   | name      |
+------+-----------+
| 1    | Alice     |
| 2    | Bob       |
| 3    | Alex      |
| 4    | Donald    |
| 7    | Lee       |
| 13   | Jonathan  |
| 19   | Elvis     |
+------+-----------+

Rides 表:
+------+----------+----------+
| id   | user_id  | distance |
+------+----------+----------+
| 1    | 1        | 120      |
| 2    | 2        | 317      |
| 3    | 3        | 222      |
| 4    | 7        | 100      |
| 5    | 13       | 312      |
| 6    | 19       | 50       |
| 7    | 7        | 120      |
| 8    | 19       | 400      |
| 9    | 7        | 230      |
+------+----------+----------+
**输出:**
+----------+--------------------+
| name     | travelled_distance |
+----------+--------------------+
| Elvis    | 450                |
| Lee      | 450                |
| Bob      | 317                |
| Jonathan | 312                |
| Alex     | 222                |
| Alice    | 120                |
| Donald   | 0                  |
+----------+--------------------+
**解释:**
Elvis 和 Lee 旅行了 450 英里,Elvis 是排名靠前的旅行者,因为他的名字在字母表上的排序比 Lee 更小。
Bob, Jonathan, Alex 和 Alice 只有一次行程,我们只按此次行程的全部距离对他们排序。
Donald 没有任何行程, 他的旅行距离为 0。

解题思路

思考

需要输出每一位用户的累计里程,包括从未乘车的用户。内连接会丢掉无行程的用户,因此以 Users 为左表连接 Rides。

按用户 id 分组后对 distance 求和,无行程时 SUM 为 NULL,用 IFNULL 转为 0 0 0。再按里程降序、姓名升序排序。

考察LEFT JOIN + GROUP BY + 排序:用户与行程左连接,COALESCE(SUM(距离),0) 补 0,先按距离降序再按姓名升序。

代码实现

sql 复制代码
# Write your MySQL query statement below
SELECT name, IFNULL(SUM(distance), 0) AS travelled_distance
FROM
    Users AS u
    LEFT JOIN Rides AS r ON u.id = r.user_id
GROUP BY u.id
ORDER BY 2 DESC, 1;

复杂度分析

本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。


1408. String Matching in an Array(数组中的字符串匹配)

题目描述

给你一个字符串数组 words,数组中的每个字符串都可以看作是一个单词。请你按任意顺序返回 words

中是其他单词的子字符串的所有单词。

如果你可以删除 words[j] 最左侧和最右侧的若干字符得到 word[i] ,那么字符串 words[i] 就是

words[j] 的一个子字符串。

示例 1

复制代码
words = ["mass","as","hero","superhero"] -> ["as","hero"]

示例 2

复制代码
words = ["leetcode","et","code"] -> ["et","code"]

示例 3

复制代码
words = ["blue","green","bu"] -> []

约束:

  • 1 <= words.length <= 100;1 <= words[i].length <= 30。

解题思路

解法一:两两检查 + contains(主提交版本)

对每个单词 i,检查其余单词 j 中是否有 contains(wordsi) 的,若有则加入结果。

复杂度:O(n^2 * L^2),n ≤ 100、L ≤ 30 完全可接受。

解法二:按长度排序 + 前缀检查(对照)

把单词按长度升序排序,只检查比它长的单词是否包含它,减少比较次数。

复杂度:O(n^2 * L^2)。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class Solution {

    public List<String> stringMatching(String[] words) {
        int n = words.length;
        List<String> res = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            boolean sub = false;
            for (int j = 0; j < n; j++) {
                if (i == j) {
                    continue;
                }
                if (words[j].contains(words[i])) {
                    sub = true;
                    break;
                }
            }
            if (sub) {
                res.add(words[i]);
            }
        }
        return res;
    }

    public List<String> stringMatchingSort(String[] words) {
        int n = words.length;
        Integer[] idx = new Integer[n];
        for (int i = 0; i < n; i++) {
            idx[i] = i;
        }
        java.util.Arrays.sort(idx, (a, b) -> words[a].length() - words[b].length());
        List<String> res = new ArrayList<>();
        for (int p = 0; p < n; p++) {
            int i = idx[p];
            boolean sub = false;
            for (int q = p + 1; q < n; q++) {
                int j = idx[q];
                if (words[j].contains(words[i])) {
                    sub = true;
                    break;
                }
            }
            if (sub) {
                res.add(words[i]);
            }
        }
        return res;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.stringMatching(new String[]{"mass", "as", "hero", "superhero"})); // [as, hero]
        System.out.println(s.stringMatching(new String[]{"leetcode", "et", "code"})); // [et, code]
        System.out.println(s.stringMatching(new String[]{"blue", "green", "bu"})); // []
        System.out.println(s.stringMatchingSort(new String[]{"mass", "as", "hero", "superhero"})); // [as, hero]
        System.out.println(s.stringMatchingSort(new String[]{"leetcode", "et", "code"})); // [et, code]
        System.out.println(s.stringMatchingSort(new String[]{"blue", "green", "bu"})); // []
    }
}

复杂度分析

复杂度:O(n^2 * L^2),n ≤ 100、L ≤ 30 完全可接受。

复杂度:O(n^2 * L^2)。


1409. Queries on a Permutation With Key(查询带键的排列)

题目描述

给定一个正整数数组 queries,其取值范围在 1 到 m 之间。请你根据以下规则按顺序处理所有

queriesi(从 i=0 到 i=queries.length-1):

  • 首先,你有一个排列 P=1,2,3,...,`m`。
  • 对于当前的 i,找到 queriesi 在排列 P 中的位置(从 0 开始索引),然后将它移到排列 P 的
    开头(即下标为 0 处)。注意,queriesi 的查询结果是 queriesi 在 P 中移动前的位置。
    返回一个数组,包含从给定 queries 中查询到的结果。

示例 1

复制代码
queries = [3,1,2,1], m = 5 -> [2,1,2,1]

示例 2

复制代码
queries = [4,1,2,2], m = 4 -> [3,1,2,0]

约束:

  • 1 <= m <= 10^3;1 <= queries.length <= m。

解题思路

解法一:列表模拟 + indexOf(主提交版本)

用 ArrayList 维护 P,每次 indexOf 找到位置 pos,然后 remove(pos) 再 add(0, v)。

复杂度:O(q * m),q、m ≤ 1000。

解法二:树状数组/数组位移(对照)

用 int\[\] p 直接模拟,找到位置后把该位置之前的元素整体右移一位再放入头部。

复杂度:O(q * m),不依赖容器开销。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class Solution {

    public int[] processQueries(int[] queries, int m) {
        List<Integer> p = new ArrayList<>();
        for (int i = 1; i <= m; i++) {
            p.add(i);
        }
        int[] res = new int[queries.length];
        for (int i = 0; i < queries.length; i++) {
            int pos = p.indexOf(queries[i]);
            res[i] = pos;
            p.remove(pos);
            p.add(0, queries[i]);
        }
        return res;
    }

    public int[] processQueriesArray(int[] queries, int m) {
        int[] p = new int[m];
        for (int i = 1; i <= m; i++) {
            p[i - 1] = i;
        }
        int[] res = new int[queries.length];
        for (int i = 0; i < queries.length; i++) {
            int pos = -1;
            for (int j = 0; j < m; j++) {
                if (p[j] == queries[i]) {
                    pos = j;
                    break;
                }
            }
            res[i] = pos;
            // 前移:把 pos 之前的元素整体右移一位
            System.arraycopy(p, 0, p, 1, pos);
            p[0] = queries[i];
        }
        return res;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        int[] r1 = s.processQueries(new int[]{3, 1, 2, 1}, 5);
        System.out.println(java.util.Arrays.toString(r1)); // [2, 1, 2, 1]
        int[] r2 = s.processQueries(new int[]{4, 1, 2, 2}, 4);
        System.out.println(java.util.Arrays.toString(r2)); // [3, 1, 2, 0]
        int[] r3 = s.processQueriesArray(new int[]{3, 1, 2, 1}, 5);
        System.out.println(java.util.Arrays.toString(r3)); // [2, 1, 2, 1]
        int[] r4 = s.processQueriesArray(new int[]{4, 1, 2, 2}, 4);
        System.out.println(java.util.Arrays.toString(r4)); // [3, 1, 2, 0]
    }
}

复杂度分析

复杂度:O(q * m),q、m ≤ 1000。

复杂度:O(q * m),不依赖容器开销。


1410. HTML Entity Parser(HTML 实体解析器)

题目描述

「HTML 实体解析器」是一种特殊的解析器,它将 HTML 代码作为输入,并用字符本身替换掉这些

特殊的字符实体。HTML 里这些特殊字符和它们对应的字符实体包括:

  • 双引号:字符实体为 ",对应的字符是 "。
  • 单引号:字符实体为 ',对应的字符是 '。
  • 与符号:字符实体为 &,对应的字符是 &。
  • 大于号:字符实体为 >,对应的字符是 >。
  • 小于号:字符实体为 <,对应的字符是 <。
  • 斜线号:字符实体为 ⁄,对应的字符是 /。
    给你输入字符串 text,请你实现一个 HTML 实体解析器,返回解析器解析后的结果。

示例 1

复制代码
text = "&amp; is an HTML entity but &ambassador; is not." -> "& is an HTML entity but &ambassador; is not."

示例 2

复制代码
text = "and I quote: &quot;...&quot;" -> "and I quote: \"...\""

示例 3

复制代码
text = "Stay home! Practice on Leetcode :)"

约束:

  • 1 <= text.length <= 10^5;字符串可能包含任意 ASCII 字符。

解题思路

解法一:单遍扫描 + 预置实体表(主提交版本)

用 StringBuilder 扫描;遇到 '&' 时尝试匹配 6 种实体中最长匹配(或按字典序试探),

匹配成功则追加替换字符并把指针跳到 ';' 后,否则原样保留。

复杂度:O(n * 6),空间 O(n)。

解法二:String.replaceAll 顺序替换(对照)

依次用 replaceAll 替换每种实体。注意 & 必须先最后替换以避免重复替换。

复杂度:O(n)(每次替换 O(n) * 常数)。

代码实现

java 复制代码
public class Solution {

    public String entityParser(String text) {
        StringBuilder sb = new StringBuilder();
        int n = text.length();
        int i = 0;
        while (i < n) {
            char c = text.charAt(i);
            if (c != '&') {
                sb.append(c);
                i++;
                continue;
            }
            boolean matched = false;
            // 按从长到短尝试匹配(避免 &amp; 与 & 混淆;这里实体都不互为前缀,直接逐个试)
            if (i + 5 < n) {
                String six = text.substring(i, i + 6);
                if (six.equals("&quot;")) {
                    sb.append('"');
                    i += 6;
                    matched = true;
                } else if (six.equals("'")) {
                    sb.append('\'');
                    i += 6;
                    matched = true;
                }
            }
            if (!matched && i + 4 < n) {
                String five = text.substring(i, i + 5);
                if (five.equals("&amp;")) {
                    sb.append('&');
                    i += 5;
                    matched = true;
                } else if (five.equals("&gt;")) {
                    sb.append('>');
                    i += 5;
                    matched = true;
                } else if (five.equals("&lt;")) {
                    sb.append('<');
                    i += 5;
                    matched = true;
                }
            }
            if (!matched && i + 6 < n) {
                String seven = text.substring(i, i + 7);
                if (seven.equals("&frasl;")) {
                    sb.append('/');
                    i += 7;
                    matched = true;
                }
            }
            if (!matched) {
                sb.append('&');
                i++;
            }
        }
        return sb.toString();
    }

    public String entityParserReplace(String text) {
        String s = text;
        s = s.replaceAll("&quot;", "\"");
        s = s.replaceAll("'", "'");
        s = s.replaceAll("&gt;", ">");
        s = s.replaceAll("&lt;", "<");
        s = s.replaceAll("&frasl;", "/");
        s = s.replaceAll("&amp;", "&");
        return s;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.entityParser("&amp; is an HTML entity but &ambassador; is not."));
        // "& is an HTML entity but &ambassador; is not."
        System.out.println(s.entityParser("and I quote: &quot;...&quot;"));
        // and I quote: "..."
        System.out.println(s.entityParser("Stay home! Practice on Leetcode :)"));
        System.out.println(s.entityParser("x &gt; y &lt; z &frasl; a 'b'"));
        System.out.println(s.entityParserReplace("&amp; is an HTML entity but &ambassador; is not."));
        System.out.println(s.entityParserReplace("and I quote: &quot;...&quot;"));
        System.out.println(s.entityParserReplace("x &gt; y &lt; z &frasl; a 'b'"));
    }
}

复杂度分析

复杂度:O(n * 6),空间 O(n)。

复杂度:O(n)(每次替换 O(n) * 常数)。


1411. Number of Ways to Paint N × 3 Grid(给 N x 3 网格图涂色的方案数)

题目描述

你有一个 n x 3 的网格图 grid,你需要用红、黄、绿三种颜色之一给每一个格子上色,且确保

相邻格子颜色不同(也就是有相同水平边或者垂直边的格子颜色不同)。给你网格图的行数 n。

请你返回给 grid 涂色的方案数。由于答案可能会非常大,请你返回答案对 10^9 + 7 取余的结果。

示例 1

复制代码
n = 1 -> 12

示例 2

复制代码
n = 2 -> 54

示例 3

复制代码
n = 3 -> 246

示例 4

复制代码
n = 7 -> 106494

示例 5

复制代码
n = 5000 -> 30228214

约束:

  • 1 <= n <= 5000。

解题思路

解法一:状态归纳 DP(主提交版本)

每行只可能属于两种类型:

  • ABA 型(三格中第一与第三同色):如 121;合法转移自 ABA 型(2 条边各 2 种)或 ABC 型。
  • ABC 型(三格两两不同色):如 123。
    设 an = 第 n 行为 ABA 型的方案数,bn = ABC 型。
    转移:an = 3an-1 + 2 bn-1;bn = 2an-1 + 2 bn-1。
    边界:a1 = 6(从 3 色中选 2 色排列:3*2),b1 = 6(3 的全排列)。
    答案 = an + bn。复杂度 O(n),空间 O(1)。
    解法二:矩阵快速幂(对照)
    把转移写成 2x2 矩阵 \[3,2,2,2],用矩阵快速幂直接求 n 次幂,
    适合 n 极大(如 10^9)的情形。复杂度 O(log n)。

代码实现

java 复制代码
public class Solution {

    private static final int MOD = 1_000_000_007;

    public int numOfWays(int n) {
        long a = 6; // 第一行为 ABA 型
        long b = 6; // 第一行为 ABC 型
        for (int i = 2; i <= n; i++) {
            long na = (3 * a + 2 * b) % MOD;
            long nb = (2 * a + 2 * b) % MOD;
            a = na;
            b = nb;
        }
        return (int) ((a + b) % MOD);
    }

    public int numOfWaysMatrix(int n) {
        if (n == 1) {
            return 12;
        }
        // 转移矩阵 M = [[3,2],[2,2]],初始向量 v = [6,6]
        long[][] m = {{3, 2}, {2, 2}};
        long[][] mp = matPow(m, n - 1);
        long a = (mp[0][0] * 6 + mp[0][1] * 6) % MOD;
        long b = (mp[1][0] * 6 + mp[1][1] * 6) % MOD;
        return (int) ((a + b) % MOD);
    }

    private long[][] matMul(long[][] x, long[][] y) {
        long[][] r = new long[2][2];
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                long sum = 0;
                for (int k = 0; k < 2; k++) {
                    sum = (sum + x[i][k] * y[k][j]) % MOD;
                }
                r[i][j] = sum;
            }
        }
        return r;
    }

    private long[][] matPow(long[][] m, int p) {
        long[][] res = {{1, 0}, {0, 1}};
        while (p > 0) {
            if ((p & 1) == 1) {
                res = matMul(res, m);
            }
            m = matMul(m, m);
            p >>= 1;
        }
        return res;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.numOfWays(1)); // 12
        System.out.println(s.numOfWays(2)); // 54
        System.out.println(s.numOfWays(3)); // 246
        System.out.println(s.numOfWays(7)); // 106494
        System.out.println(s.numOfWays(5000)); // 30228214
        System.out.println(s.numOfWaysMatrix(1)); // 12
        System.out.println(s.numOfWaysMatrix(2)); // 54
        System.out.println(s.numOfWaysMatrix(3)); // 246
        System.out.println(s.numOfWaysMatrix(7)); // 106494
    }
}

复杂度分析

答案 = an + bn。复杂度 O(n),空间 O(1)。


1412. Find the Quiet Students in All Exams(查找成绩处于中游的学生 🔒)

题目类型:数据库(SQL)

题目描述

表: Student

复制代码
+---------------------+---------+
| Column Name         | Type    |
+---------------------+---------+
| student_id          | int     |
| student_name        | varchar |
+---------------------+---------+
student_id 是该表主键(具有唯一值的列)。
student_name 学生名字。

表: Exam

复制代码
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| exam_id       | int     |
| student_id    | int     |
| score         | int     |
+---------------+---------+
(exam_id, student_id) 是该表主键(具有唯一值的列的组合)。
学生 student_id 在测验 exam_id 中得分为 score。

成绩处于中游的学生是指至少参加了一次测验, 且得分既不是最高分也不是最低分的学生。

编写解决方案,找出在 所有 测验中都处于中游的学生 (student_id, student_name)。不要返回从来没有参加过测验的学生。

返回结果表按照 student_id 排序。

返回结果格式如下。

示例 1:

复制代码
**输入:**
Student 表:
+-------------+---------------+
| student_id  | student_name  |
+-------------+---------------+
| 1           | Daniel        |
| 2           | Jade          |
| 3           | Stella        |
| 4           | Jonathan      |
| 5           | Will          |
+-------------+---------------+
Exam 表:
+------------+--------------+-----------+
| exam_id    | student_id   | score     |
+------------+--------------+-----------+
| 10         |     1        |    70     |
| 10         |     2        |    80     |
| 10         |     3        |    90     |
| 20         |     1        |    80     |
| 30         |     1        |    70     |
| 30         |     3        |    80     |
| 30         |     4        |    90     |
| 40         |     1        |    60     |
| 40         |     2        |    70     |
| 40         |     4        |    80     |
+------------+--------------+-----------+
**输出:**
+-------------+---------------+
| student_id  | student_name  |
+-------------+---------------+
| 2           | Jade          |
+-------------+---------------+
**解释:**
对于测验 1: 学生 1 和 3 分别获得了最低分和最高分。
对于测验 2: 学生 1 既获得了最高分, 也获得了最低分。
对于测验 3 和 4: 学生 1 和 4 分别获得了最低分和最高分。
学生 2 和 5 没有在任一场测验中获得了最高分或者最低分。
因为学生 5 从来没有参加过任何测验, 所以他被排除于结果表。
由此, 我们仅仅返回学生 2 的信息。

解题思路

思考

「安静」指该生参加的每一场考试都不是当场最高分也不是最低分。逐场手工筛选极值再反连接,SQL 中不如窗口函数直接标出两端。

对每场考试分别按成绩升序、降序做 RANK(),得到最低、最高标记。与学生表连接后按人分组,要求从未出现过 r k = 1 rk=1 rk=1,即两端次数均为 0 0 0。

考察两次排名比较:对每科考试按分数正序、倒序各取一次排名,判断学生是否为该科中间名次(非第一非最后)。

代码实现

sql 复制代码
# Write your MySQL query statement below
WITH
    T AS (
        SELECT
            student_id,
            RANK() OVER (
                PARTITION BY exam_id
                ORDER BY score
            ) AS rk1,
            RANK() OVER (
                PARTITION BY exam_id
                ORDER BY score DESC
            ) AS rk2
        FROM Exam
    )
SELECT student_id, student_name
FROM
    T
    JOIN Student USING (student_id)
GROUP BY 1
HAVING SUM(rk1 = 1) = 0 AND SUM(rk2 = 1) = 0
ORDER BY 1;

复杂度分析

本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。


1413. Minimum Value to Get Positive Step by Step Sum(逐步求和得到正数的最小值)

题目描述

给你一个整数数组 nums。你可以选定任意的正数 startValue 作为初始值。

你需要从左到右遍历 nums 数组,并将 startValue 依次累加上 nums 数组中的值。

请你在确保累加和始终大于等于 1 的前提下,选出一个最小的正数作为 startValue。

示例 1

复制代码
nums = [-3,2,-3,4,2] -> 5

示例 2

复制代码
nums = [1,2] -> 1

示例 3

复制代码
nums = [1,-2,-3] -> 5

约束:

  • 1 <= nums.length <= 100;-100 <= nums[i] <= 100。

解题思路

解法一:前缀和最小值(主提交版本)

从头累加,记录过程中出现的最小前缀和 minSum;startValue 需满足

startValue + minSum >= 1,即 startValue = max(1, 1 - minSum)。

复杂度:O(n),空间 O(1)。

解法二:二分答案(对照)

startValue 越大越容易满足条件,对 1, 10\^6 二分最小的可行值。

复杂度:O(n * log R),R 为二分上界。

代码实现

java 复制代码
public class Solution {

    public int minStartValue(int[] nums) {
        int sum = 0;
        int minSum = 0;
        for (int v : nums) {
            sum += v;
            minSum = Math.min(minSum, sum);
        }
        return Math.max(1, 1 - minSum);
    }

    public int minStartValueBinary(int[] nums) {
        int lo = 1, hi = 10000;
        while (lo < hi) {
            int mid = (lo + hi) >>> 1;
            if (feasible(nums, mid)) {
                hi = mid;
            } else {
                lo = mid + 1;
            }
        }
        return lo;
    }

    private boolean feasible(int[] nums, int start) {
        long cur = start;
        for (int v : nums) {
            cur += v;
            if (cur < 1) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.minStartValue(new int[]{-3, 2, -3, 4, 2})); // 5
        System.out.println(s.minStartValue(new int[]{1, 2})); // 1
        System.out.println(s.minStartValue(new int[]{1, -2, -3})); // 5
        System.out.println(s.minStartValueBinary(new int[]{-3, 2, -3, 4, 2})); // 5
        System.out.println(s.minStartValueBinary(new int[]{1, 2})); // 1
        System.out.println(s.minStartValueBinary(new int[]{1, -2, -3})); // 5
    }
}

复杂度分析

复杂度:O(n),空间 O(1)。

复杂度:O(n * log R),R 为二分上界。


1414. Find the Minimum Number of Fibonacci Numbers Whose Sum Is K(和为 K 的最少斐波那契数字数目)

题目描述

给你数字 k,请你返回和为 k 的斐波那契数字的最少数目,其中,每个斐波那契数字都可以被使用多次。

斐波那契数字定义为:

  • F1 = 1
  • F2 = 1
  • Fn = Fn-1 + Fn-2,其中 n > 2。
    数据保证对于给定的 k,一定能找到可行解。

示例 1

复制代码
k = 7 -> 2(2 + 5)

示例 2

复制代码
k = 10 -> 2(2 + 8)

示例 3

复制代码
k = 19 -> 3(1 + 5 + 13 或 1+8+10?校验:19 = 13+5+1 = 3 个)

约束:

  • 1 <= k <= 10^9。

解题思路

解法一:贪心选最大可用的斐波那契数(主提交版本)

斐波那契数列增长极快(约黄金比例),先预处理出所有 <= k 的斐波那契数。

每次尽量减去当前最大可行的斐波那契数(该数 <= 剩余值)。因为任意整数都能写成

若干斐波那契数之和,且贪心选择最大数一定最优(Zeckendorf 定理系的推广,本题允许

重复使用,故贪心成立)。复杂度 O(log k) 个数字。

解法二:完全背包 DP(对照)

把斐波那契数看作物品(无限使用),dpx = 凑成 x 的最少个数。

复杂度 O(k * log k),仅适用于小 k 对照(k 大时不可行)。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class Solution {

    public int findMinFibonacciNumbers(int k) {
        List<Integer> fib = new ArrayList<>();
        int a = 1, b = 1;
        fib.add(1);
        fib.add(1);
        while (a + b <= k) {
            int c = a + b;
            fib.add(c);
            a = b;
            b = c;
        }
        int ans = 0;
        for (int i = fib.size() - 1; i >= 0 && k > 0; i--) {
            int f = fib.get(i);
            if (f <= k) {
                ans += k / f;
                k %= f;
            }
        }
        return ans;
    }

    public int findMinFibonacciNumbersDp(int k) {
        List<Integer> fib = new ArrayList<>();
        int a = 1, b = 1;
        fib.add(1);
        fib.add(1);
        while (a + b <= k) {
            int c = a + b;
            fib.add(c);
            a = b;
            b = c;
        }
        int[] dp = new int[k + 1];
        java.util.Arrays.fill(dp, Integer.MAX_VALUE / 2);
        dp[0] = 0;
        for (int f : fib) {
            for (int x = f; x <= k; x++) {
                dp[x] = Math.min(dp[x], dp[x - f] + 1);
            }
        }
        return dp[k];
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.findMinFibonacciNumbers(7)); // 2
        System.out.println(s.findMinFibonacciNumbers(10)); // 2
        System.out.println(s.findMinFibonacciNumbers(19)); // 3
        System.out.println(s.findMinFibonacciNumbers(1)); // 1
        System.out.println(s.findMinFibonacciNumbers(2)); // 1
        System.out.println(s.findMinFibonacciNumbersDp(7)); // 2
        System.out.println(s.findMinFibonacciNumbersDp(10)); // 2
        System.out.println(s.findMinFibonacciNumbersDp(19)); // 3
    }
}

复杂度分析


1415. The k-th Lexicographical String of All Happy Strings of Length n(长度为 n 的开心字符串中字典序第 k 小的字符串)

题目描述

一个「开心字符串」定义为:

  • 仅包含小写字母 '`a`','`b`','`c`'。
  • 对所有在 1 到 s.length - 1 之间的 i,满足 s[i] != s[i + 1](字符串的下标从 1 开始)。
    给你两个整数 n 和 k,你需要将长度为 n 的所有开心字符串按字典序排序。
    请你返回排序后的第 k 个开心字符串,如果长度为 n 的开心字符串少于 k 个,那么请你返回空字符串。

示例 1

复制代码
n = 1, k = 3 -> "c"

示例 2

复制代码
n = 1, k = 4 -> ""

示例 3

复制代码
n = 3, k = 9 -> "cab"

约束:

  • 1 <= n <= 10;1 <= k <= 100。

解题思路

解法一:位计数 + 逐位构造(主提交版本)

长度为 n 的开心字符串总数为 3 * 2^(n-1)。第 k 个字符串按字典序构造:

第一个字符可以是 a/b/c,之后每个位置可选的字符是「不等于前一个字符」的两个;

用块大小 2^(n-i) 决定当前位取第几个候选字典序字符。

复杂度:O(n)。

解法二:回溯枚举排序(对照)

DFS 生成所有开心字符串(按字典序自然有序),取第 k 个。

复杂度:O(3 * 2^(n-1) * n),n ≤ 10 时可行。

代码实现

java 复制代码
public class Solution {

    public String getHappyString(int n, int k) {
        int total = 3 * (1 << (n - 1));
        if (k > total) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        k--; // 转 0 基
        // 选首字符:以 2^(n-1) 为一组
        int block = 1 << (n - 1);
        char start = (char) ('a' + k / block);
        sb.append(start);
        k %= block;
        char last = start;
        for (int i = 1; i < n; i++) {
            block >>= 1;
            char[] options = getOptions(last);
            // 按字典序:选取第 t 个候选(t = k / block)
            char c;
            if (k < block) {
                c = options[0];
            } else {
                c = options[1];
                k -= block;
            }
            sb.append(c);
            last = c;
        }
        return sb.toString();
    }

    // 返回不等于 last 的两个字符,按字典序升序
    private char[] getOptions(char last) {
        char[] res = new char[2];
        int idx = 0;
        for (char c = 'a'; c <= 'c'; c++) {
            if (c != last) {
                res[idx++] = c;
            }
        }
        return res;
    }

    public String getHappyStringDfs(int n, int k) {
        StringBuilder sb = new StringBuilder();
        int[] cnt = new int[]{k};
        for (char c = 'a'; c <= 'c'; c++) {
            sb.append(c);
            String res = dfs(n, 1, c, cnt);
            if (res != null) {
                return res;
            }
            sb.deleteCharAt(sb.length() - 1);
        }
        return "";
    }

    private String dfs(int n, int len, char last, int[] cnt) {
        if (len == n) {
            cnt[0]--;
            if (cnt[0] == 0) {
                return String.valueOf(last);
            }
            return null;
        }
        for (char c = 'a'; c <= 'c'; c++) {
            if (c == last) {
                continue;
            }
            String res = dfs(n, len + 1, c, cnt);
            if (res != null) {
                return last + res;
            }
        }
        return null;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.getHappyString(1, 3)); // c
        System.out.println(s.getHappyString(1, 4)); // ""
        System.out.println(s.getHappyString(3, 9)); // cab
        System.out.println(s.getHappyString(2, 5)); // bc? 校验:aa.. 开心串按字典序:
        // ab,ac,ba,bc,ca,cb -> 第5个是 ca? 排序: ab,ac,ba,bc,ca,cb => 第5=ca
        System.out.println(s.getHappyStringDfs(1, 3)); // c
        System.out.println(s.getHappyStringDfs(1, 4)); // ""
        System.out.println(s.getHappyStringDfs(3, 9)); // cab
        System.out.println(s.getHappyStringDfs(2, 5)); // ca
    }
}

复杂度分析

复杂度:O(n)。

复杂度:O(3 * 2^(n-1) * n),n ≤ 10 时可行。


1416. Restore The Array(恢复数组)

题目描述

某个程序本来应该输出一个整数数组。但是这个程序忘记输出空格了以致输出了一个数字字符串,

我们所知道的信息只有:数组中所有整数都在 1, `k` 之间,且数组中的数字都没有前导 0。

给你字符串 s 和整数 k。可能会有多种不同的数组恢复结果。

按照上述程序,请你返回所有可能输出字符串 s 的数组方案数。

由于数组方案数可能会很大,请你返回它对 10^9 + 7 取余后的结果。

示例 1

复制代码
s = "1000", k = 10000 -> 1

示例 2

复制代码
s = "1000", k = 10 -> 0

示例 3

复制代码
s = "1317", k = 2000 -> 8

约束:

  • 1 <= s.length <= 10^5;1 <= k <= 10^9。

解题思路

解法一:DP + 数字大小剪枝(主提交版本)

dpi = si... 的恢复方案数。dpn = 1。

对每个 i,枚举 j 从 i 开始累加数字 val = val*10 + digit;

若 val > k 则停止(后续更长只会更大);若 si=='0' 则 break(不能有前导 0)。

dpi = sum(dpj+1)。复杂度:每轮最多枚举 10 位(val 很快超 k)或 O(len * 10)。

解法二:逆序区间和优化(对照)

dpi 依赖一段连续区间 dpi+1...j+1 的和,可用后缀和 O(1) 转移。

复杂度 O(n * 10)。

代码实现

java 复制代码
public class Solution {

    private static final int MOD = 1_000_000_007;

    public int numberOfArrays(String s, int k) {
        int n = s.length();
        long[] dp = new long[n + 1];
        dp[n] = 1;
        for (int i = n - 1; i >= 0; i--) {
            if (s.charAt(i) == '0') {
                dp[i] = 0;
                continue;
            }
            long val = 0;
            for (int j = i; j < n; j++) {
                val = val * 10 + (s.charAt(j) - '0');
                if (val > k) {
                    break;
                }
                dp[i] = (dp[i] + dp[j + 1]) % MOD;
            }
        }
        return (int) dp[0];
    }

    public int numberOfArraysSuffix(String s, int k) {
        int n = s.length();
        long[] dp = new long[n + 1];
        long[] suffix = new long[n + 2];
        dp[n] = 1;
        suffix[n] = 1;
        for (int i = n - 1; i >= 0; i--) {
            if (s.charAt(i) == '0') {
                dp[i] = 0;
            } else {
                long val = 0;
                int j = i;
                while (j < n) {
                    long next = val * 10 + (s.charAt(j) - '0');
                    if (next > k) {
                        break;
                    }
                    val = next;
                    j++;
                }
                // [i, j-1] 为合法分段终点;dp[i] = sum(dp[i+1..j])
                if (j > n) {
                    j = n;
                }
                long sum = suffix[i + 1] - (j + 1 <= n ? suffix[j + 1] : 0);
                dp[i] = (sum % MOD + MOD) % MOD;
            }
            suffix[i] = (suffix[i + 1] + dp[i]) % MOD;
        }
        return (int) dp[0];
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.numberOfArrays("1000", 10000)); // 1
        System.out.println(s.numberOfArrays("1000", 10)); // 0
        System.out.println(s.numberOfArrays("1317", 2000)); // 8
        System.out.println(s.numberOfArrays("123", 100)); // 3([1,23],[12,3],[1,2,3];[123] 超 k 不计)
        System.out.println(s.numberOfArraysSuffix("1000", 10000)); // 1
        System.out.println(s.numberOfArraysSuffix("1000", 10)); // 0
        System.out.println(s.numberOfArraysSuffix("1317", 2000)); // 8
        System.out.println(s.numberOfArraysSuffix("123", 100)); // 3
    }
}

复杂度分析


1417. Reformat The String(重新格式化字符串)

题目描述

给你一个混合了数字和字母的字符串 s,其中的字母均为小写英文字母。

请你将该字符串重新格式化,使得任意两个相邻字符的类型都不同。也就是说,字母后面应该跟着数字,

而数字后面应该跟着字母。

请你返回重新格式化后的字符串;如果无法按要求重新格式化,则返回一个空字符串。

示例 1

复制代码
s = "a0b1c2" -> "0a1b2c"

示例 2

复制代码
s = "leetcode" -> ""

示例 3

复制代码
s = "1229857369" -> ""

示例 4

复制代码
s = "covid2019" -> "c2o0v1i9d"

约束:

  • 1 <= s.length <= 500;s 仅由小写英文字母和/或数字组成。

解题思路

解法一:分桶穿插(主提交版本)

统计字母与数字数量,若 |cntL - cntD| > 1 则不可能,返回 ""。

让数量多的一方作为「首尾较多」的一类,交替拼接。

复杂度:O(n),空间 O(n)。

解法二:双指针交叉填充(对照)

收集两类字符到两个数组,用指针分别取,多的一方先放。

复杂度:O(n)。

代码实现

java 复制代码
public class Solution {

    public String reformat(String s) {
        StringBuilder letters = new StringBuilder();
        StringBuilder digits = new StringBuilder();
        for (char c : s.toCharArray()) {
            if (c >= '0' && c <= '9') {
                digits.append(c);
            } else {
                letters.append(c);
            }
        }
        int l = letters.length();
        int d = digits.length();
        if (Math.abs(l - d) > 1) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        int i = 0, j = 0;
        boolean letterFirst = l >= d;
        while (i < l || j < d) {
            if (letterFirst) {
                if (i < l) {
                    sb.append(letters.charAt(i++));
                }
                if (j < d) {
                    sb.append(digits.charAt(j++));
                }
            } else {
                if (j < d) {
                    sb.append(digits.charAt(j++));
                }
                if (i < l) {
                    sb.append(letters.charAt(i++));
                }
            }
        }
        return sb.toString();
    }

    public String reformatPtr(String s) {
        StringBuilder letters = new StringBuilder();
        StringBuilder digits = new StringBuilder();
        for (char c : s.toCharArray()) {
            if (Character.isDigit(c)) {
                digits.append(c);
            } else {
                letters.append(c);
            }
        }
        int l = letters.length();
        int d = digits.length();
        if (Math.abs(l - d) > 1) {
            return "";
        }
        char[] res = new char[s.length()];
        int li = 0, di = 0, idx = 0;
        if (l >= d) {
            while (li < l || di < d) {
                if (li < l) {
                    res[idx++] = letters.charAt(li++);
                }
                if (di < d) {
                    res[idx++] = digits.charAt(di++);
                }
            }
        } else {
            while (li < l || di < d) {
                if (di < d) {
                    res[idx++] = digits.charAt(di++);
                }
                if (li < l) {
                    res[idx++] = letters.charAt(li++);
                }
            }
        }
        return new String(res);
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.reformat("a0b1c2")); // 0a1b2c 等
        System.out.println(s.reformat("leetcode")); // ""
        System.out.println(s.reformat("1229857369")); // ""
        System.out.println(s.reformat("covid2019")); // c2o0v1i9d 类
        System.out.println(s.reformat("ab123")); // 1a2b3 类
        System.out.println(s.reformatPtr("a0b1c2"));
        System.out.println(s.reformatPtr("leetcode"));
        System.out.println(s.reformatPtr("covid2019"));
    }
}

复杂度分析

复杂度:O(n),空间 O(n)。

复杂度:O(n)。


1418. Display Table of Food Orders in a Restaurant(点菜展示表)

题目描述

给你一个数组 orders,表示客户在餐厅中完成的订单,确切地说,ordersi=[customerNamei,

tableNumberi, foodItemi],其中 customerNamei 是客户的姓名,tableNumberi 是客户所在餐桌的

桌号,而 foodItemi 是客户点的餐品名称。

请你返回该餐厅的点菜展示表。在这张表中,表中第一行为标题,其第一列为餐桌桌号 "Table",

后面每一列都是按字母顺序排列的餐品名称。接下来每一行中的项则表示每张餐桌订购的相应餐品数量,

第一列应当填对应的桌号,后面依次填写下单的餐品数量。

注意:客户姓名不是点菜展示表的一部分。此外,表中的数据行应该按餐桌桌号升序排列。

示例 1

复制代码
见题面 -> [["Table","Beef Burrito","Ceviche","Fried Chicken","Water"],
["3","0","2","1","0"],["5","0","1","0","1"],["10","1","0","0","0"]]

约束:

  • 1 <= orders.length <= 5 * 10^4。

解题思路

解法一:TreeMap 双层计数(主提交版本)

table->(food->count) 用 TreeMap 保证桌号升序;food 集合用 TreeSet 保证字典序。

首行 = "Table" + 所有 food;之后逐桌输出计数。

复杂度:O(n log n)。

解法二:HashMap + 排序(对照)

用 HashMap 收集后,把桌号排序、餐品排序,再生成表。

复杂度:O(n log n)。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;

public class Solution {

    public List<List<String>> displayTable(List<List<String>> orders) {
        TreeMap<Integer, Map<String, Integer>> tableMap = new TreeMap<>();
        TreeSet<String> foods = new TreeSet<>();
        for (List<String> o : orders) {
            int table = Integer.parseInt(o.get(1));
            String food = o.get(2);
            foods.add(food);
            tableMap.computeIfAbsent(table, k -> new HashMap<>())
                    .merge(food, 1, Integer::sum);
        }
        List<List<String>> res = new ArrayList<>();
        List<String> header = new ArrayList<>();
        header.add("Table");
        header.addAll(foods);
        res.add(header);
        for (Map.Entry<Integer, Map<String, Integer>> e : tableMap.entrySet()) {
            List<String> row = new ArrayList<>();
            row.add(String.valueOf(e.getKey()));
            Map<String, Integer> cnt = e.getValue();
            for (String f : foods) {
                row.add(String.valueOf(cnt.getOrDefault(f, 0)));
            }
            res.add(row);
        }
        return res;
    }

    public List<List<String>> displayTableMap(List<List<String>> orders) {
        Map<Integer, Map<String, Integer>> map = new HashMap<>();
        Map<String, Integer> foodSet = new HashMap<>();
        for (List<String> o : orders) {
            int table = Integer.parseInt(o.get(1));
            String food = o.get(2);
            foodSet.put(food, 1);
            map.computeIfAbsent(table, k -> new HashMap<>())
                    .merge(food, 1, Integer::sum);
        }
        List<String> foods = new ArrayList<>(foodSet.keySet());
        java.util.Collections.sort(foods);
        List<Integer> tables = new ArrayList<>(map.keySet());
        java.util.Collections.sort(tables);
        List<List<String>> res = new ArrayList<>();
        List<String> header = new ArrayList<>();
        header.add("Table");
        header.addAll(foods);
        res.add(header);
        for (int t : tables) {
            List<String> row = new ArrayList<>();
            row.add(String.valueOf(t));
            Map<String, Integer> cnt = map.get(t);
            for (String f : foods) {
                row.add(String.valueOf(cnt.getOrDefault(f, 0)));
            }
            res.add(row);
        }
        return res;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        List<List<String>> orders1 = new ArrayList<>();
        orders1.add(java.util.Arrays.asList("David", "3", "Ceviche"));
        orders1.add(java.util.Arrays.asList("Corina", "10", "Beef Burrito"));
        orders1.add(java.util.Arrays.asList("David", "3", "Fried Chicken"));
        orders1.add(java.util.Arrays.asList("Carla", "5", "Water"));
        orders1.add(java.util.Arrays.asList("Carla", "5", "Ceviche"));
        orders1.add(java.util.Arrays.asList("Rous", "3", "Ceviche"));
        System.out.println(s.displayTable(orders1));
        List<List<String>> orders2 = new ArrayList<>();
        orders2.add(java.util.Arrays.asList("James", "12", "Fried Chicken"));
        orders2.add(java.util.Arrays.asList("Ratesh", "12", "Fried Chicken"));
        orders2.add(java.util.Arrays.asList("Amadeus", "12", "Fried Chicken"));
        orders2.add(java.util.Arrays.asList("Adam", "1", "Canadian Waffles"));
        orders2.add(java.util.Arrays.asList("Brianna", "1", "Canadian Waffles"));
        System.out.println(s.displayTable(orders2));
        System.out.println(s.displayTableMap(orders1));
        System.out.println(s.displayTableMap(orders2));
    }
}

复杂度分析

复杂度:O(n log n)。


1419. Minimum Number of Frogs Croaking(数青蛙)

题目描述

给你一个字符串 croakOfFrogs,它表示不同青蛙发出的蛙鸣声(字符串 "croak")的组合。

由于同一时间可以有多只青蛙呱呱作响,所以 croakOfFrogs 中会混合多个 "croak"。

请你返回模拟字符串中所有蛙鸣所需不同青蛙的最少数目。

要想发出蛙鸣 "croak",青蛙必须依序输出 'c','r','o','a','k' 这 5 个字母。

如果没有输出全部五个字母,那么它就不会发出声音。

如果字符串 croakOfFrogs 不是由若干有效的 "croak" 字符混合而成,请返回 -1。

示例 1

复制代码
croakOfFrogs = "croakcroak" -> 1

示例 2

复制代码
croakOfFrogs = "crcoakroak" -> 2

示例 3

复制代码
croakOfFrogs = "croakcrook" -> -1

示例 4

复制代码
croakOfFrogs = "croakcroa" -> -1

约束:

  • 1 <= croakOfFrogs.length <= 10^5。

解题思路

解法一:五状态计数(主提交版本)

维护 c/r/o/a/k 五个前缀状态的数量(即正在进行到该字符的青蛙数)。

遇 'c' 开新蛙同时计数 +1;遇后续字母要求前一个状态数 > 0,转移;

遇 'k' 完成时计数 -1(该蛙空闲可复用)。同时跟踪进行中最大蛙数。

结束时 cntTotal == 0 且中途无非法转移才有效。

复杂度:O(n),空间 O(1)。

解法二:队列模拟(对照)

拆成多只青蛙顺序,用 Map 模拟更直白但开销大;此处给出

「每只蛙用单字符状态机」等价实现:cnt 数组 + 违规检查。

代码实现

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class Solution {

    public int minNumberOfFrogs(String croakOfFrogs) {
        int c = 0, r = 0, o = 0, a = 0, k = 0;
        int active = 0, maxFrogs = 0;
        for (char ch : croakOfFrogs.toCharArray()) {
            if (ch == 'c') {
                c++;
                active++;
                maxFrogs = Math.max(maxFrogs, active);
            } else if (ch == 'r') {
                if (c <= r) {
                    return -1;
                }
                r++;
            } else if (ch == 'o') {
                if (r <= o) {
                    return -1;
                }
                o++;
            } else if (ch == 'a') {
                if (o <= a) {
                    return -1;
                }
                a++;
            } else if (ch == 'k') {
                if (a <= k) {
                    return -1;
                }
                k++; // 完成一只
                active--;
            } else {
                return -1;
            }
        }
        // 结束时所有蛙必须完成:每个状态数量相等且 active == 0
        if (active != 0 || c != r || r != o || o != a || a != k) {
            return -1;
        }
        return maxFrogs;
    }

    public int minNumberOfFrogsArr(String croakOfFrogs) {
        String seq = "croak";
        int[] cnt = new int[5]; // 处于每个阶段(含刚完成)的蛙数
        int active = 0, maxFrogs = 0;
        for (char ch : croakOfFrogs.toCharArray()) {
            int idx = seq.indexOf(ch);
            if (idx < 0) {
                return -1;
            }
            if (idx == 0) {
                cnt[0]++;
                active++;
                maxFrogs = Math.max(maxFrogs, active);
            } else {
                if (cnt[idx - 1] == 0) {
                    return -1;
                }
                cnt[idx - 1]--;
                if (idx == 4) {
                    active--;
                } else {
                    cnt[idx]++;
                }
            }
        }
        for (int i = 0; i < 4; i++) {
            if (cnt[i] != 0) {
                return -1;
            }
        }
        return active == 0 ? maxFrogs : -1;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.minNumberOfFrogs("croakcroak")); // 1
        System.out.println(s.minNumberOfFrogs("crcoakroak")); // 2
        System.out.println(s.minNumberOfFrogs("croakcrook")); // -1
        System.out.println(s.minNumberOfFrogs("croakcroa")); // -1
        System.out.println(s.minNumberOfFrogs("ccrrooaakk")); // 2
        System.out.println(s.minNumberOfFrogsArr("croakcroak")); // 1
        System.out.println(s.minNumberOfFrogsArr("crcoakroak")); // 2
        System.out.println(s.minNumberOfFrogsArr("croakcrook")); // -1
        System.out.println(s.minNumberOfFrogsArr("ccrrooaakk")); // 2
    }
}

复杂度分析

复杂度:O(n),空间 O(1)。


1420. Build Array Where You Can Find The Maximum Exactly K Comparisons(生成数组)

题目描述

给定三个整数 n、m 和 k。考虑使用下图描述的算法找出正整数数组中最大的元素:

算法:ans = -1,cost = 0;从左到右扫描数组,若 x > ans 则 ans = x, cost++。

请你构建一个具有以下属性的数组 arr:

  • arr 中包含确切的 n 个整数;
  • 1 <= arr[i] <= m,其中 (0 <= i < n);
  • 将上面提到的算法应用于 arr 之后,search_cost 的值等于 k。
    返回在满足上述条件的情况下构建数组 arr 的方法数量,由于答案可能会很大,所以必须对 10^9 + 7 取余。

示例 1

复制代码
n = 2, m = 3, k = 1 -> 6

示例 2

复制代码
n = 5, m = 2, k = 3 -> 0

示例 3

复制代码
n = 9, m = 1, k = 1 -> 1

示例 4

复制代码
n = 50, m = 100, k = 25 -> 34549172

约束:

  • 1 <= n <= 50;1 <= m <= 100;0 <= k <= n。

解题思路

解法一:三维 DP + 前缀和优化(主提交版本)

dpijc = 前 i 个数、当前最大值为 j、已发生 c 次更新(cost)的方案数。

转移:

  1. 新元素 <= j:dpijc += dpi-1jc * j(放 1...j 任意值都不更新);
  2. 新元素 > j(新最大):dpixc+1 += dpi-1jc for x in (j, m]。
    复杂度 O(n * m^2 * k),n≤50,m≤100,k≤50 约 1250 万可接受,用前缀和优化到 O(nm k)。
    解法二:三维朴素 DP(对照)
    直接三重枚举转移,无前缀和,代码直观但更慢。复杂度 O(n * m^2 * k)。

代码实现

java 复制代码
public class Solution {

    private static final int MOD = 1_000_000_007;

    public int numOfArrays(int n, int m, int k) {
        if (k == 0) {
            return 0;
        }
        if (k > m) {
            return 0; // 更新次数不可能超过最大值的不同取值
        }
        long[][] dp = new long[m + 2][k + 1]; // dp[j][c]
        // 初始:第一个数取 j 时最大值 j,cost=1
        for (int j = 1; j <= m; j++) {
            dp[j][1] = 1;
        }
        for (int i = 2; i <= n; i++) {
            long[][] ndp = new long[m + 2][k + 1];
            long[][] prefix = new long[m + 2][k + 1];
            // 计算前缀和 prefix[j][c] = sum_{x<=j} dp[x][c]
            for (int c = 1; c <= k; c++) {
                long acc = 0;
                for (int j = 1; j <= m; j++) {
                    acc = (acc + dp[j][c]) % MOD;
                    prefix[j][c] = acc;
                }
            }
            for (int j = 1; j <= m; j++) {
                for (int c = 1; c <= k; c++) {
                    // 1) 放 <= j 的值:不更新,前缀不放大的转移来自 dp[j][c] * j
                    long same = dp[j][c] * j % MOD;
                    // 2) 放 > j 的值:从更小最大值的状态转移(新最大 j, c-1 次更新)
                    long larger = prefix[j - 1][c - 1]; // sum_{x<j} dp[x][c-1] 每个放 j 到 m?
                    // 实际上「新最大 = 恰好 j」来自前一次最大值 x < j:dp[x][c-1] 且放 j。
                    ndp[j][c] = (same + larger) % MOD;
                }
            }
            dp = ndp;
        }
        long ans = 0;
        for (int j = 1; j <= m; j++) {
            ans = (ans + dp[j][k]) % MOD;
        }
        return (int) ans;
    }

    public int numOfArraysNaive(int n, int m, int k) {
        if (k == 0 || k > m) {
            return 0;
        }
        long[][] dp = new long[m + 1][k + 1];
        for (int j = 1; j <= m; j++) {
            dp[j][1] = 1;
        }
        for (int i = 2; i <= n; i++) {
            long[][] ndp = new long[m + 1][k + 1];
            for (int prevMax = 1; prevMax <= m; prevMax++) {
                for (int c = 1; c <= k; c++) {
                    if (dp[prevMax][c] == 0) {
                        continue;
                    }
                    // 放 <= prevMax 的值(共 prevMax 种):最大值不变
                    ndp[prevMax][c] = (ndp[prevMax][c] + dp[prevMax][c] * prevMax) % MOD;
                    // 放 > prevMax 的值:新最大值 = nv,cost + 1
                    for (int nv = prevMax + 1; nv <= m; nv++) {
                        if (c + 1 <= k) {
                            ndp[nv][c + 1] = (ndp[nv][c + 1] + dp[prevMax][c]) % MOD;
                        }
                    }
                }
            }
            dp = ndp;
        }
        long ans = 0;
        for (int j = 1; j <= m; j++) {
            ans = (ans + dp[j][k]) % MOD;
        }
        return (int) ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.numOfArrays(2, 3, 1)); // 6
        System.out.println(s.numOfArrays(5, 2, 3)); // 0
        System.out.println(s.numOfArrays(9, 1, 1)); // 1
        System.out.println(s.numOfArrays(50, 100, 25)); // 34549172
        System.out.println(s.numOfArraysNaive(2, 3, 1)); // 6
        System.out.println(s.numOfArraysNaive(5, 2, 3)); // 0
        System.out.println(s.numOfArraysNaive(9, 1, 1)); // 1
    }
}

复杂度分析

相关推荐
Nuanyt2 小时前
LeetCode Hot 100 链表专题2 3 题 (Java)
java·笔记·算法·leetcode·链表
文丑颜不良啊2 小时前
LeetCode1341~1360题目解析
java·leetcode
QianhangQianping2 小时前
撤销重做不是「存快照」:一个可视化编辑器的Operation 设计
java·前端·数据库·typescript·编辑器·前端架构·撤销重做
海绵宝宝转agent2 小时前
LeetCodehot100题解
java·笔记·学习·算法·leetcode·深度优先
SunnyDays10112 小时前
Java 操作 Word 脚注和尾注:添加、读取与删除
java·word
SunnyDays10112 小时前
Java 为 Word 添加页码:隐藏封面页码、从指定页开始编号、奇偶页页码设置等
java·word·页码
开源推荐官2 小时前
多门店 O2O 商城选型复盘:从开发视角对比主流开源系统
java·架构·开源·php
吴名氏.3 小时前
Java转AI Agent教程(第二周)超时重试与错误分类
java·人工智能·python·分类·ai agent
从琳开始9893 小时前
从Socket到UDP回显服务器:Java网络编程完整指南
java·网络·网络协议·udp·java-ee