LeetCode1541~1560题目解析

LeetCode 1541-1560. 题解合集

文章目录


1541. Minimum Insertions to Balance a Parentheses String(平衡括号字符串的最少插入次数)

题目描述

给你一个括号字符串 s,只包含 '(' 和 ')'。一个括号字符串是平衡的当:

  1. 任何左括号 '(' 必须对应两个连续的右括号 '))';
  2. 左括号必须在对应的连续两个右括号之前。
    你可以在任意位置插入 '(' 和 ')' 使字符串平衡。返回最少插入次数。

示例 1

复制代码
s="(()))",输出 1(结尾补一个 ')')。

示例 2

复制代码
s="())",输出 0(已平衡)。

示例 3

复制代码
s="))())(",输出 3。

示例 4

复制代码
s="((((((",输出 12。

约束:

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

解题思路

解法一:栈模拟(主提交版本)

  • 用 needRight 表示"当前已挂起的 '(' 还差几个右括号";
  • 扫描:
  • 遇到 '(':needRight += 2;
  • 遇到 ')':needRight -= 1;若 needRight 变成负数,说明多了一个孤立的 ')',
    需要在其前补一个 '('(ans++),并把 needRight 修正为 1;
  • 扫描完 needRight 还 > 0 则补 ans += needRight。
    复杂度:时间复杂度 O(n),空间复杂度 O(1)。
    解法二:显式双栈计数(对照)
  • 维护 pending 左括号数 left 与当前等待的右括号数 wait,
    分情况显式计算每次补插的数量,逻辑同样 O(n)。
    复杂度:时间复杂度 O(n),空间复杂度 O(1)。

代码实现

java 复制代码
public class Solution {

    public int minInsertionsNeedRight(String s) {
        int needRight = 0;
        int ans = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '(') {
                needRight += 2;
            } else {
                needRight--;
                if (needRight < 0) {
                    // 多出的孤立 ')':在前面补一个 '('(补 1 个字符),并把 needRight 重置为 1
                    ans++;
                    needRight = 1;
                }
            }
        }
        ans += needRight;
        return ans;
    }

    public int minInsertionsCount(String s) {
        int left = 0;  // 已出现但未配对的 '(' 数
        int wait = 0;  // 当前还需补的右括号数(来自未配对的 '(')
        int ans = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '(') {
                left++;
            } else {
                if (wait == 0) {
                    left--; // 用掉一个 '('
                    wait = 1; // 它需要两个右括号,已给一个,还差一个
                    if (left < 0) {
                        // 没有 '(' 可配:先补一个 '('
                        ans++;
                        left = 0;
                        wait = 1;
                    }
                } else {
                    wait--;
                }
            }
        }
        ans += wait;      // 剩余的待补右括号
        ans += left * 2;  // 剩余的 '(' 各需两个右括号
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.minInsertionsNeedRight("(()))")); // 1
        System.out.println(sol.minInsertionsNeedRight("()")); // 0
        System.out.println(sol.minInsertionsNeedRight("))())(")); // 3
        System.out.println(sol.minInsertionsNeedRight("((((((")); // 12
        System.out.println(sol.minInsertionsNeedRight(")))))))")); // 5
        System.out.println(sol.minInsertionsCount("(()))")); // 1
        System.out.println(sol.minInsertionsCount("))())(")); // 3
        System.out.println(sol.minInsertionsCount("((((((")); // 12
    }
}

复杂度分析

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


1542. Find Longest Awesome Substring(找出最长的超赞子字符串)

题目描述

给你一个字符串 s,返回 s 中最长的超赞子字符串的长度。

超赞子字符串:是 s 的一个非空子字符串,且进行任意次数的字符交换后可以变成一个回文串

(即:子串中最多只有一个字符出现奇数次)。

示例 1

复制代码
s="3242415",输出 5("24241" 可交换成回文 "24142")。

示例 2

复制代码
s="12345678",输出 1。

约束:

  • 1 <= s.length <= 10^5;s 仅由数字组成。

解题思路

解法一:前缀奇偶状态 + 首现位置(主提交版本)

  • 用 10 位二进制 mask 表示前缀中每个数字出现次数的奇偶性;
  • 子串 i, j 可重排成回文当且仅当 maskj ^ maski-1 至多含 1 个 1(即 power of two 或 0);
  • 遍历时对每个 mask 记录最早出现位置,再枚举 mask ^ (1<<d)(d=0...9)与 mask 本身查最早位置,
    计算最大长度。
    复杂度:时间复杂度 O(10 * n),空间复杂度 O(2^10)。
    解法二:暴力枚举起点(对照,小数据)
  • 枚举子串起点,向右扩展维护奇偶状态,遇合法状态更新长度。
    复杂度:时间复杂度 O(n^2 * 10),空间复杂度 O(1)。

代码实现

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

public class Solution {

    public int longestAwesomePrefix(String s) {
        int n = s.length();
        int[] first = new int[1 << 10];
        Arrays.fill(first, -1);
        first[0] = -1; // 空前缀位置 -1(下标前)
        int mask = 0;
        int ans = 0;
        for (int i = 0; i < n; i++) {
            int d = s.charAt(i) - '0';
            mask ^= (1 << d);
            // 与之前相同状态(全偶)------子串本身合法
            if (first[mask] != -1) {
                ans = Math.max(ans, i - first[mask]);
            } else {
                first[mask] = i;
            }
            // 恰有一个数字出现奇数次
            for (int bit = 0; bit < 10; bit++) {
                int target = mask ^ (1 << bit);
                if (first[target] != -1) {
                    ans = Math.max(ans, i - first[target]);
                }
            }
        }
        return ans;
    }

    public int longestAwesomeBrute(String s) {
        int n = s.length();
        int ans = 0;
        for (int i = 0; i < n; i++) {
            int mask = 0;
            for (int j = i; j < n; j++) {
                mask ^= (1 << (s.charAt(j) - '0'));
                if ((mask & (mask - 1)) == 0) { // mask 是 0 或 2 的幂
                    ans = Math.max(ans, j - i + 1);
                }
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.longestAwesomePrefix("3242415")); // 5
        System.out.println(sol.longestAwesomePrefix("12345678")); // 1
        System.out.println(sol.longestAwesomePrefix("213123")); // 6
        System.out.println(sol.longestAwesomePrefix("00")); // 2
        System.out.println(sol.longestAwesomeBrute("3242415")); // 5
        System.out.println(sol.longestAwesomeBrute("213123")); // 6
        System.out.println(sol.longestAwesomeBrute("00")); // 2
    }
}

复杂度分析

复杂度:时间复杂度 O(10 * n),空间复杂度 O(2^10)。

复杂度:时间复杂度 O(n^2 * 10),空间复杂度 O(1)。


1543. Fix Product Name Format(产品名称格式修复)

题目类型:数据库(SQL)

题目描述

表:Sales

复制代码
+--------------+---------+
| Column Name  | Type    |
+--------------+---------+
| sale_id      | int     |
| product_name | varchar |
| sale_date    | date    |
+--------------+---------+
sale_id 是该表具有唯一值的列
该表的每一行包含了产品的名称及其销售日期

因为在 2000 年该表是手工填写的,product_name 可能包含前后空格,而且包含大小写。

编写一个解决方案报告每个月的销售情况:

复制代码
- `product_name` 是小写字母且不包含前后空格

- `sale_date` 格式为 `('YYYY-MM')`

- `total` 是产品在本月销售的次数

返回结果以 product_name 升序 排列,如果有排名相同,再以 sale_date **升序 **排列。

查询结果格式如下所示。

示例 1:

复制代码
`**输入:**
Sales` 表:
+---------+--------------+------------+
| sale_id | product_name | sale_date  |
+---------+--------------+------------+
| 1       | LCPHONE      | 2000-01-16 |
| 2       | LCPhone      | 2000-01-17 |
| 3       | LcPhOnE      | 2000-02-18 |
| 4       | LCKeyCHAiN   | 2000-02-19 |
| 5       | LCKeyChain   | 2000-02-28 |
| 6       | Matryoshka   | 2000-03-31 |
+---------+--------------+------------+
**输出:**
+--------------+-----------+-------+
| product_name | sale_date | total |
+--------------+-----------+-------+
| lckeychain   | 2000-02   | 2     |
| lcphone      | 2000-01   | 2     |
| lcphone      | 2000-02   | 1     |
| matryoshka   | 2000-03   | 1     |
+--------------+-----------+-------+
**解释:**
一月份售出 2 部 LcPhones。请注意,产品名称不区分大小写,且可能包含空格。
二月份售出 2 个 LCKeychains 和 1 部 LCPhone。
三月份售出 1 个 Matryoshka。

解题思路

思考

销售表里商品名大小写与空格不统一,日期要按月汇总销量。直接分组会把同一商品的不同写法拆成多组。

先把名称去空白并转成小写,日期格式化到年月,再按这两列分组计数。规范化放在公共表表达式中,外层只负责聚合与排序。

考察字符串处理 + 聚合:先对产品名去空格、统一大小写,再按月汇总:按 product 分组(产品名归一化后),统计销售总价与总数量。

代码实现

sql 复制代码
# Write your MySQL query statement below
WITH
    t AS (
        SELECT
            LOWER(TRIM(product_name)) AS product_name,
            DATE_FORMAT(sale_date, '%Y-%m') AS sale_date
        FROM Sales
    )
SELECT product_name, sale_date, COUNT(1) AS total
FROM t
GROUP BY 1, 2
ORDER BY 1, 2;

复杂度分析

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


1544. Make The String Great(整理字符串)

题目描述

给你一个由大小写英文字母组成的字符串 s。整理好的字符串中,两个相邻字符 s[i] 和 s[i+1]

(0 <= i <= s.length-2)要满足:s[i] 是小写字母但 s[i+1] 是相同字母的大写,或者反之

(即两个相邻字符互为大小写的一对)。

每次操作你可以选择两个满足上述条件的相邻字符并删除它们(每次删除后字符串会拼接)。

反复操作,直到无法继续为止,返回整理好的字符串。答案保证是唯一的。

示例 1

复制代码
s="leEeetcode",输出 "leetcode"。

示例 2

复制代码
s="abBAcC",输出 ""。

约束:

  • 1 <= s.length <= 100;s 只包含大小写英文字母。

解题思路

解法一:栈模拟(主提交版本)

  • 用 StringBuilder 当栈:遍历字符,若栈顶与当前字符互为大小写(ASCII 差 32)则弹栈,
    否则入栈。
    复杂度:时间复杂度 O(n),空间复杂度 O(n)。
    解法二:原地双指针(对照)
  • 用下标 i 表示当前输出位置(类似栈),遍历原串时互为大小写则回退。
    复杂度:时间复杂度 O(n),空间复杂度 O(n)(结果占位)。

代码实现

java 复制代码
public class Solution {

    public String makeGoodStack(String s) {
        StringBuilder sb = new StringBuilder();
        for (char c : s.toCharArray()) {
            int n = sb.length();
            if (n > 0 && Math.abs(sb.charAt(n - 1) - c) == 32) {
                sb.deleteCharAt(n - 1);
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    public String makeGoodTwoPointer(String s) {
        char[] arr = s.toCharArray();
        int write = 0;
        for (int read = 0; read < arr.length; read++) {
            if (write > 0 && Math.abs(arr[write - 1] - arr[read]) == 32) {
                write--;
            } else {
                arr[write++] = arr[read];
            }
        }
        return new String(arr, 0, write);
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.makeGoodStack("leEeetcode")); // leetcode
        System.out.println(sol.makeGoodStack("abBAcC")); // ""
        System.out.println(sol.makeGoodStack("s")); // s
        System.out.println(sol.makeGoodTwoPointer("leEeetcode")); // leetcode
        System.out.println(sol.makeGoodTwoPointer("abBAcC")); // ""
        System.out.println(sol.makeGoodTwoPointer("s")); // s
    }
}

复杂度分析

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

复杂度:时间复杂度 O(n),空间复杂度 O(n)(结果占位)。


1545. Find Kth Bit in Nth Binary String(找出第 N 个二进制字符串中的第 K 位)

题目描述

给你两个正整数 n 和 k,二进制字符串 Sn 的形成规则如下:

  • S1 = "0";
  • 当 i > 1 时,Si = Si-1 + "1" + reverse(invert(Si-1))。
    返回 Sn 的第 k 位字符(1 索引)。数据保证 k 在 Sn 长度以内。

示例 1

复制代码
n=3, k=1,输出 '0'(S3="0111001" 第 1 位)。

示例 2

复制代码
n=4, k=11,输出 '1'(S4="011100110110001" 第 11 位)。

约束:

  • 1 <= n <= 20;1 <= k <= 2^n - 1。

解题思路

解法一:递归按位定位(主提交版本)

  • S(n) 长度 L = 2^n - 1,中位是 '1',左半是 S(n-1),右半是 S(n-1) 的取反+反转;
  • find(n, k):
  • k < mid:递归 find(n-1, k);
  • k == mid:返回 '1';
  • k > mid:对应右半位置,pos = k - mid,在原 S(n-1) 中的位置为
    L(n-1) - pos + 1,返回 find 结果的翻转(0<->1)。
    复杂度:时间复杂度 O(n),空间复杂度 O(n)(递归深度)。
    解法二:迭代构造(对照,n 很小可直接构造)
  • 从 S1 开始迭代构造直到 Sn,取第 k-1 位。
    复杂度:时间复杂度 O(2^n),空间复杂度 O(2^n)。

代码实现

java 复制代码
public class Solution {

    public char findKthBitRec(int n, int k) {
        if (n == 1) {
            return '0';
        }
        int len = (1 << n) - 1;
        int mid = len / 2 + 1;
        if (k == mid) {
            return '1';
        }
        if (k < mid) {
            return findKthBitRec(n - 1, k);
        }
        // 右半:对应 S(n-1) 的取反反转后的位置
        int pos = k - mid;
        int lenPrev = (1 << (n - 1)) - 1;
        int original = lenPrev - pos + 1;
        char c = findKthBitRec(n - 1, original);
        return c == '0' ? '1' : '0';
    }

    public char findKthBitBuild(int n, int k) {
        String s = "0";
        for (int i = 2; i <= n; i++) {
            StringBuilder inv = new StringBuilder();
            for (int j = s.length() - 1; j >= 0; j--) {
                inv.append(s.charAt(j) == '0' ? '1' : '0');
            }
            s = s + "1" + inv;
        }
        return s.charAt(k - 1);
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.findKthBitRec(3, 1)); // 0
        System.out.println(sol.findKthBitRec(4, 11)); // 1
        System.out.println(sol.findKthBitRec(2, 3)); // 1 (S2="011")
        System.out.println(sol.findKthBitBuild(3, 1)); // 0
        System.out.println(sol.findKthBitBuild(4, 11)); // 1
        System.out.println(sol.findKthBitBuild(2, 3)); // 1
    }
}

复杂度分析

复杂度:时间复杂度 O(n),空间复杂度 O(n)(递归深度)。

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


1546. Maximum Number of Non-Overlapping Subarrays With Sum Equals Target

题目描述

(和为目标值且不重叠的非空子数组的最大数目)

给你一个数组 nums 和一个整数 target。请你返回非空不重叠子数组的最大数目,

且每个子数组的元素和为 target。

示例 1

复制代码
nums=[1,1,1,1,1], target=2,输出 2。

示例 2

复制代码
nums=[-1,3,5,1,4,2,-9], target=6,输出 2。

示例 3

复制代码
nums=[-2,6,6,3,5,4,1,2,8], target=10,输出 3。

约束:

  • 1 <= nums.length <= 105;-104 <= nums[i] <= 104;-106 <= target <= 10^6。

解题思路

解法一:前缀和 + 贪心选最短区间(主提交版本)

  • 贪心:从左到右扫描,维护前缀和 pre 与 HashSet 记录出现过的前缀和;
  • 若 pre - target 在集合中,说明存在以当前下标为右端点的目标和区间,选中该区间
    (不重叠),ans++ 并清空集合(重置扫描起点);
  • 每步把当前 pre 加入集合。
    复杂度:时间复杂度 O(n),空间复杂度 O(n)。
    解法二:Map 记录前缀和最早位置 + 区间端点排序(对照)
  • 找出所有 l, r 使区间和为 target,再按 r 升序贪心选不重叠。
    复杂度:时间复杂度 O(n),空间复杂度 O(n)。

代码实现

java 复制代码
import java.util.HashSet;
import java.util.Set;

public class Solution {

    public int maxNonOverlappingGreedy(int[] nums, int target) {
        Set<Integer> seen = new HashSet<>();
        seen.add(0);
        int pre = 0;
        int ans = 0;
        for (int v : nums) {
            pre += v;
            if (seen.contains(pre - target)) {
                ans++;
                pre = 0;
                seen.clear();
                seen.add(0);
            } else {
                seen.add(pre);
            }
        }
        return ans;
    }

    public int maxNonOverlappingMap(int[] nums, int target) {
        java.util.Map<Integer, Integer> map = new java.util.HashMap<>();
        map.put(0, -1);
        int pre = 0;
        int ans = 0;
        int lastEnd = -1; // 已选区间的最右端点
        for (int i = 0; i < nums.length; i++) {
            pre += nums[i];
            if (map.containsKey(pre - target)) {
                int start = map.get(pre - target) + 1;
                if (start > lastEnd) {
                    ans++;
                    lastEnd = i;
                }
            }
            map.put(pre, i);
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.maxNonOverlappingGreedy(new int[]{1, 1, 1, 1, 1}, 2)); // 2
        System.out.println(sol.maxNonOverlappingGreedy(new int[]{-1, 3, 5, 1, 4, 2, -9}, 6)); // 2
        System.out.println(sol.maxNonOverlappingGreedy(new int[]{-2, 6, 6, 3, 5, 4, 1, 2, 8}, 10)); // 3
        System.out.println(sol.maxNonOverlappingMap(new int[]{1, 1, 1, 1, 1}, 2)); // 2
        System.out.println(sol.maxNonOverlappingMap(new int[]{-1, 3, 5, 1, 4, 2, -9}, 6)); // 2
        System.out.println(sol.maxNonOverlappingMap(new int[]{-2, 6, 6, 3, 5, 4, 1, 2, 8}, 10)); // 3
    }
}

复杂度分析

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


1547. Minimum Cost to Cut a Stick(切棍子的最小成本)

题目描述

有一根长度为 n 个单位的木棍,棍上从 0 到 n 标记了若干位置。

给你一个整数数组 cuts,其中 cuts[i] 表示你需要将棍子切开的位置。

你可以按任意顺序切这些位置,每次切割的成本 = 当前棍子的长度。

求切完所有位置的最小总成本。

示例 1

复制代码
n=7, cuts=[1,3,4,5],输出 16。

示例 2

复制代码
n=9, cuts=[5,6,1,4,2],输出 22。

约束:

  • 2 <= n <= 10^6;1 <= cuts.length <= min(n-1, 100)。

解题思路

解法一:区间 DP(主提交版本)

  • 把 0 和 n 也看作切点,与 cuts 合并排序后得到 m 个端点;
  • dpij = 把区间 (pointsi, pointsj) 内所有切点都切完的最小成本;
  • 转移:枚举第一刀切点 k(i<k<j),成本 = pointsj-pointsi 加上两侧子区间;
    dpij = min(dpik + dpkj) + (pointsj-pointsi)。
    复杂度:时间复杂度 O(m^3),空间复杂度 O(m^2),m <= 102。
    解法二:记忆化递归(对照)
  • 同一区间 DP 用递归 + 缓存实现,便于理解。
    复杂度:时间复杂度 O(m^3),空间复杂度 O(m^2)。

代码实现

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

public class Solution {

    public int minCostDp(int n, int[] cuts) {
        int[] points = new int[cuts.length + 2];
        points[0] = 0;
        for (int i = 0; i < cuts.length; i++) {
            points[i + 1] = cuts[i];
        }
        points[points.length - 1] = n;
        Arrays.sort(points);
        int m = points.length;
        int[][] dp = new int[m][m];
        for (int len = 2; len < m; len++) {
            for (int i = 0; i + len < m; i++) {
                int j = i + len;
                dp[i][j] = Integer.MAX_VALUE;
                for (int k = i + 1; k < j; k++) {
                    dp[i][j] = Math.min(dp[i][j], dp[i][k] + dp[k][j]);
                }
                dp[i][j] += points[j] - points[i];
            }
        }
        return dp[0][m - 1];
    }

    private int[] pts;
    private int[][] memo;

    public int minCostMemo(int n, int[] cuts) {
        pts = new int[cuts.length + 2];
        pts[0] = 0;
        for (int i = 0; i < cuts.length; i++) {
            pts[i + 1] = cuts[i];
        }
        pts[pts.length - 1] = n;
        Arrays.sort(pts);
        memo = new int[pts.length][pts.length];
        return dfs(0, pts.length - 1);
    }

    private int dfs(int i, int j) {
        if (j - i <= 1) {
            return 0;
        }
        if (memo[i][j] != 0) {
            return memo[i][j];
        }
        int best = Integer.MAX_VALUE;
        for (int k = i + 1; k < j; k++) {
            best = Math.min(best, dfs(i, k) + dfs(k, j));
        }
        memo[i][j] = best + (pts[j] - pts[i]);
        return memo[i][j];
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.minCostDp(7, new int[]{1, 3, 4, 5})); // 16
        System.out.println(sol.minCostDp(9, new int[]{5, 6, 1, 4, 2})); // 22
        System.out.println(sol.minCostMemo(7, new int[]{1, 3, 4, 5})); // 16
        System.out.println(sol.minCostMemo(9, new int[]{5, 6, 1, 4, 2})); // 22
    }
}

复杂度分析

复杂度:时间复杂度 O(m^3),空间复杂度 O(m^2),m <= 102。

复杂度:时间复杂度 O(m^3),空间复杂度 O(m^2)。


1548. The Most Similar Path in a Graph(图中最相似的路径)

题目描述

给定一个无向图,节点数为 n,节点标签数组 names(字符串数组),以及目标路径数组 targetPath。

一条路径 path(节点序列)的相似度定义为:path 中与 targetPath 对应位置标签相同的节点数。

从任一节点出发、长度恰好等于目标路径长度的路径中,找相似度最大的一条

(若有多条输出任意一条)。返回该路径的节点序列。

约束:

  • 2 <= n <= 100;0 <= edges.length;targetPath.length <= 100。

解题思路

解法一:DP 求最长公共路径(主提交版本)

  • dpiv = 构造 targetPath 前 i 个位置、且第 i 个位置落在节点 v 时的最大相同数;
  • 转移:previv 记录到达 v 的上一个节点,用于回溯路径;
    dpiv = (namesv == targetPathi) + max(dpi-1u),u 为 v 的邻居;
  • 从 i = targetPath.length 倒推路径。
    复杂度:时间复杂度 O(L * (V + E)),空间复杂度 O(L * V)。
    解法二:暴力枚举起点(对照,仅小数据)
  • 枚举每个起点做 DFS 记录最优。
    复杂度:指数级,仅说明思路。

代码实现

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

public class Solution {

    public List<Integer> mostSimilarDp(int n, int[][] roads, String[] names, String[] targetPath) {
        List<Integer>[] graph = new List[n];
        for (int i = 0; i < n; i++) {
            graph[i] = new ArrayList<>();
        }
        for (int[] e : roads) {
            graph[e[0]].add(e[1]);
            graph[e[1]].add(e[0]);
        }
        int L = targetPath.length;
        int[][] dp = new int[L][n];
        int[][] prev = new int[L][n];
        for (int[] row : dp) {
            Arrays.fill(row, -1000000);
        }
        for (int v = 0; v < n; v++) {
            dp[0][v] = names[v].equals(targetPath[0]) ? 1 : 0;
        }
        for (int i = 1; i < L; i++) {
            for (int v = 0; v < n; v++) {
                for (int u : graph[v]) {
                    int val = dp[i - 1][u] + (names[v].equals(targetPath[i]) ? 1 : 0);
                    if (val > dp[i][v]) {
                        dp[i][v] = val;
                        prev[i][v] = u;
                    }
                }
            }
        }
        int best = -1, last = -1;
        for (int v = 0; v < n; v++) {
            if (dp[L - 1][v] > best) {
                best = dp[L - 1][v];
                last = v;
            }
        }
        List<Integer> res = new ArrayList<>();
        for (int i = L - 1; i >= 0; i--) {
            res.add(0, last);
            if (i > 0) {
                last = prev[i][last];
            }
        }
        return res;
    }

    public List<Integer> mostSimilarDfs(int n, int[][] roads, String[] names, String[] targetPath) {
        List<Integer>[] graph = new List[n];
        for (int i = 0; i < n; i++) {
            graph[i] = new ArrayList<>();
        }
        for (int[] e : roads) {
            graph[e[0]].add(e[1]);
            graph[e[1]].add(e[0]);
        }
        int L = targetPath.length;
        int[] bestPath = new int[L];
        int[] bestScore = {-1};
        int[] cur = new int[L];
        for (int start = 0; start < n; start++) {
            cur[0] = start;
            dfs(graph, names, targetPath, 1, cur, bestPath, bestScore);
        }
        List<Integer> res = new ArrayList<>();
        for (int v : bestPath) {
            res.add(v);
        }
        return res;
    }

    private void dfs(List<Integer>[] g, String[] names, String[] tp, int pos, int[] cur, int[] bestPath, int[] bestScore) {
        if (pos == tp.length) {
            int score = 0;
            for (int i = 0; i < tp.length; i++) {
                if (names[cur[i]].equals(tp[i])) {
                    score++;
                }
            }
            if (score > bestScore[0]) {
                bestScore[0] = score;
                System.arraycopy(cur, 0, bestPath, 0, cur.length);
            }
            return;
        }
        for (int v : g[cur[pos - 1]]) {
            cur[pos] = v;
            dfs(g, names, tp, pos + 1, cur, bestPath, bestScore);
        }
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int[][] roads = {{0, 1}, {1, 2}, {2, 3}, {3, 0}};
        String[] names = {"a", "b", "c", "d"};
        String[] target = {"b", "c", "d"};
        System.out.println(sol.mostSimilarDp(4, roads, names, target)); // 期望 [1,2,3]
        System.out.println(sol.mostSimilarDfs(4, roads, names, target)); // [1,2,3]
    }
}

复杂度分析

复杂度:时间复杂度 O(L * (V + E)),空间复杂度 O(L * V)。


1549. The Most Recent Orders for Each Product(每件商品的最新订单)

题目类型:数据库(SQL)

题目描述

表: Customers

复制代码
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| customer_id   | int     |
| name          | varchar |
+---------------+---------+
customer_id 是该表主键.
该表包含消费者的信息.

表: Orders

复制代码
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| order_id      | int     |
| order_date    | date    |
| customer_id   | int     |
| product_id    | int     |
+---------------+---------+
order_id 是该表主键.
该表包含消费者customer_id产生的订单.
不会有商品被相同的用户在一天内下单**超过一次**.

表: Products

复制代码
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| product_id    | int     |
| product_name  | varchar |
| price         | int     |
+---------------+---------+
product_id 是该表主键.
该表包含所有商品的信息.

写一个解决方案, 找到每件商品的最新订单(可能有多个).

返回的结果以 product_name 升序排列 , 如果有排序相同, 再以 product_id 升序 排列. 如果还有排序相同, 再以 order_id 升序排列.

查询结果格式如下例所示。

示例 1:

复制代码
**输入:**
`Customers表:`
+-------------+-----------+
| customer_id | name      |
+-------------+-----------+
| 1           | Winston   |
| 2           | Jonathan  |
| 3           | Annabelle |
| 4           | Marwan    |
| 5           | Khaled    |
+-------------+-----------+
`Orders表:`
+----------+------------+-------------+------------+
| order_id | order_date | customer_id | product_id |
+----------+------------+-------------+------------+
| 1        | 2020-07-31 | 1           | 1          |
| 2        | 2020-07-30 | 2           | 2          |
| 3        | 2020-08-29 | 3           | 3          |
| 4        | 2020-07-29 | 4           | 1          |
| 5        | 2020-06-10 | 1           | 2          |
| 6        | 2020-08-01 | 2           | 1          |
| 7        | 2020-08-01 | 3           | 1          |
| 8        | 2020-08-03 | 1           | 2          |
| 9        | 2020-08-07 | 2           | 3          |
| 10       | 2020-07-15 | 1           | 2          |
+----------+------------+-------------+------------+
`Products表:`
+------------+--------------+-------+
| product_id | product_name | price |
+------------+--------------+-------+
| 1          | keyboard     | 120   |
| 2          | mouse        | 80    |
| 3          | screen       | 600   |
| 4          | hard disk    | 450   |
+------------+--------------+-------+
**输出:**
+--------------+------------+----------+------------+
| product_name | product_id | order_id | order_date |
+--------------+------------+----------+------------+
| keyboard     | 1          | 6        | 2020-08-01 |
| keyboard     | 1          | 7        | 2020-08-01 |
| mouse        | 2          | 8        | 2020-08-03 |
| screen       | 3          | 3        | 2020-08-29 |
+--------------+------------+----------+------------+
**解释:**
keyboard 的最新订单在2020-08-01, 在这天有两次下单.
mouse 的最新订单在2020-08-03, 在这天只有一次下单.
screen 的最新订单在2020-08-29, 在这天只有一次下单.
hard disk 没有被下单, 我们不把它包含在结果表中.

解题思路

思考

对每种商品找出日期最新的全部订单。同一天可能有多笔,不能用 R O W _ N U M B E R ROW\_NUMBER ROW_NUMBER 只留一行,需要并列第一都保留。

连接 O r d e r s Orders Orders 与 P r o d u c t s Products Products 后,按商品分区、按日期降序做 R A N K RANK RANK。保留 r k = 1 rk=1 rk=1 的行即可同时拿到所有并列的最近订单,再按名称与编号排序。

考察窗口函数 RANK/DENSE_RANK:按产品分组按下单日期取最近一笔,再连接产品表与订单表输出明细。

代码实现

sql 复制代码
# Write your MySQL query statement below
WITH
    T AS (
        SELECT
            *,
            RANK() OVER (
                PARTITION BY product_id
                ORDER BY order_date DESC
            ) AS rk
        FROM
            Orders
            JOIN Products USING (product_id)
    )
SELECT product_name, product_id, order_id, order_date
FROM T
WHERE rk = 1
ORDER BY 1, 2, 3;

复杂度分析

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


1550. Three Consecutive Odds(存在连续三个奇数的数组)

题目描述

给你一个整数数组 arr,请你判断数组中是否存在连续三个元素都是奇数:如果存在返回 true,否则返回 false。

示例 1

复制代码
arr=[2,6,4,1],输出 false。

示例 2

复制代码
arr=[1,2,34,3,4,5,7,23,12],输出 true([5,7,23] 连续三个奇数)。

约束:

  • 1 <= arr.length <= 1000;1 <= arr[i] <= 1000。

解题思路

解法一:计数连续奇数(主提交版本)

  • 遇到奇数 count++,count 达到 3 返回 true;遇到偶数 count 清零。
    复杂度:时间复杂度 O(n),空间复杂度 O(1)。
    解法二:窗口滑窗(对照)
  • 检查每个长度为 3 的窗口内是否全为奇数。
    复杂度:时间复杂度 O(n),空间复杂度 O(1)。

代码实现

java 复制代码
public class Solution {

    public boolean threeConsecutiveOddsCount(int[] arr) {
        int cnt = 0;
        for (int v : arr) {
            if ((v & 1) == 1) {
                cnt++;
                if (cnt == 3) {
                    return true;
                }
            } else {
                cnt = 0;
            }
        }
        return false;
    }

    public boolean threeConsecutiveOddsWindow(int[] arr) {
        for (int i = 0; i + 2 < arr.length; i++) {
            if ((arr[i] & 1) == 1 && (arr[i + 1] & 1) == 1 && (arr[i + 2] & 1) == 1) {
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.threeConsecutiveOddsCount(new int[]{2, 6, 4, 1})); // false
        System.out.println(sol.threeConsecutiveOddsCount(new int[]{1, 2, 34, 3, 4, 5, 7, 23, 12})); // true
        System.out.println(sol.threeConsecutiveOddsWindow(new int[]{2, 6, 4, 1})); // false
        System.out.println(sol.threeConsecutiveOddsWindow(new int[]{1, 2, 34, 3, 4, 5, 7, 23, 12})); // true
    }
}

复杂度分析

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


1551. Minimum Operations to Make Array Equal(使数组中所有元素相等的最小操作数)

题目描述

存在一个长度为 n 的数组 arr,其中 arr[i] = 2*i + 1(0 <= i < n)。

一次操作中,你可以选出两个下标 x 和 y,使 arr[x] 减 1、arr[y] 加 1。

目标使所有元素相等(保证可达)。返回最小操作数。

示例 1

复制代码
n=3,输出 2([1,3,5] -> [2,3,4] -> [3,3,3])。

示例 2

复制代码
n=6,输出 9。

约束:

  • 1 <= n <= 10^4。

解题思路

解法一:数学公式(主提交版本)

  • 所有元素最终相等,中位数即为目标。奇数个取中间元素;偶数个取两中间元素的均值;
  • 最小操作数 = 各元素与目标差绝对值之和 / 2(一次操作同时调整两个元素);
  • 对奇数 n:答案为 2 * sum(1...(n-1)/2) = (n^2 - 1) / 4;
  • 对偶数 n:答案为 n^2 / 4。
    复杂度:时间复杂度 O(1),空间复杂度 O(1)。
    解法二:模拟差分累加(对照)
  • 显式构造数组,按"大的减、小的加"逐对计算操作数 = 每对差值 / 2 的累加。
    复杂度:时间复杂度 O(n),空间复杂度 O(1)。

代码实现

java 复制代码
public class Solution {

    public int minOperationsFormula(int n) {
        if (n % 2 == 1) {
            return (n * n - 1) / 4;
        }
        return n * n / 4;
    }

    public int minOperationsSimulate(int n) {
        long ans = 0;
        int lo = 0, hi = n - 1;
        while (lo < hi) {
            ans += arrV(hi) - arrV(lo) >> 1 == 0 ? (arrV(hi) - arrV(lo)) : (arrV(hi) - arrV(lo)) / 2;
            lo++;
            hi--;
        }
        return (int) ans;
    }

    private int arrV(int i) {
        return 2 * i + 1;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.minOperationsFormula(3)); // 2
        System.out.println(sol.minOperationsFormula(6)); // 9
        for (int n = 1; n <= 10; n++) {
            System.out.print(sol.minOperationsFormula(n) + " ");
        }
        System.out.println(); // 0 0 2 4 6 9 12 16 20 25
        System.out.println(sol.minOperationsSimulate(3)); // 2
        System.out.println(sol.minOperationsSimulate(6)); // 9
    }
}

复杂度分析

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

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


1552. Magnetic Force Between Two Balls(两球之间的磁力)

题目描述

在 C-137 地球上,Rick 有 n 个空的篮子,第 i 个篮子的位置在 position[i]。

Morty 想把 m 个球放到这些篮子里,使得任意两球间最小磁力最大。两球位于 x 和 y 的磁力为 |x-y|。

返回最大化的最小磁力。

示例 1

复制代码
position=[1,2,3,4,7], m=3,输出 3(放 1,4,7)。

示例 2

复制代码
position=[5,4,3,2,1,1000000000], m=2,输出 999999999。

约束:

  • 2 <= n <= 10^5;m <= n;1 <= positioni <= 10^9。

解题思路

解法一:排序 + 二分答案(主提交版本)

  • 最小磁力越大,能放下的球越少,单调;
  • 二分磁力 d,检查"以 >= d 的间隔最多能放几个球",>= m 则 d 可行;
  • 贪心放置:排序后从第一个篮子开始,间隔达到 d 才放下一个球。
    复杂度:时间复杂度 O(n log(maxPos)),空间复杂度 O(log n)(排序)。
    解法二:线性扫描暴力(对照,仅小数据)
  • 枚举所有组合计算最小间隔,取最大。
    复杂度:时间复杂度 O(n^m),指数级,仅说明思路。

代码实现

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

public class Solution {

    public int maxDistanceBinary(int[] position, int m) {
        Arrays.sort(position);
        int lo = 1, hi = position[position.length - 1] - position[0];
        int ans = 0;
        while (lo <= hi) {
            int mid = (lo + hi) >>> 1;
            if (canPlace(position, m, mid)) {
                ans = mid;
                lo = mid + 1;
            } else {
                hi = mid - 1;
            }
        }
        return ans;
    }

    private boolean canPlace(int[] pos, int m, int gap) {
        int cnt = 1;
        int last = pos[0];
        for (int i = 1; i < pos.length; i++) {
            if (pos[i] - last >= gap) {
                cnt++;
                last = pos[i];
                if (cnt >= m) {
                    return true;
                }
            }
        }
        return false;
    }

    public int maxDistanceBrute(int[] position, int m) {
        int ans = 0;
        int[] idx = new int[m];
        for (int i = 0; i < m; i++) {
            idx[i] = i;
        }
        int n = position.length;
        while (true) {
            int minGap = Integer.MAX_VALUE;
            for (int i = 1; i < m; i++) {
                minGap = Math.min(minGap, Math.abs(position[idx[i]] - position[idx[i - 1]]));
            }
            ans = Math.max(ans, minGap);
            // 下一个组合
            int p = m - 1;
            while (p >= 0 && idx[p] == n - (m - p)) {
                p--;
            }
            if (p < 0) {
                break;
            }
            idx[p]++;
            for (int q = p + 1; q < m; q++) {
                idx[q] = idx[q - 1] + 1;
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.maxDistanceBinary(new int[]{1, 2, 3, 4, 7}, 3)); // 3
        System.out.println(sol.maxDistanceBinary(new int[]{5, 4, 3, 2, 1, 1000000000}, 2)); // 999999999
        System.out.println(sol.maxDistanceBrute(new int[]{1, 2, 3, 4, 7}, 3)); // 3
    }
}

复杂度分析

复杂度:时间复杂度 O(n log(maxPos)),空间复杂度 O(log n)(排序)。

复杂度:时间复杂度 O(n^m),指数级,仅说明思路。


1553. Minimum Number of Days to Eat N Oranges(吃掉 N 个橘子的最少天数)

题目描述

厨房里总共有 n 个橘子,你决定每一天选择如下方式之一吃:

  1. 吃掉一个橘子;
  2. 如果剩余橘子数 n 能被 2 整除,可以吃掉 n/2 个橘子(剩 n/2);
  3. 如果剩余橘子数 n 能被 3 整除,可以吃掉 2*(n/3) 个橘子(剩 n/3)。
    返回吃掉所有 n 个橘子的最少天数。

示例 1

复制代码
n=10,输出 4。

示例 2

复制代码
n=6,输出 3。

约束:

  • 1 <= n <= 2*10^9。

解题思路

解法一:记忆化 + 整除跳变(主提交版本)

  • 朴素 dpn = 1 + min(dpn-1, n%20?dpn/2:inf, n%30?dpn/3:inf);
  • 优化:每次"吃 1 个"只为凑到能整除 2/3 的数,因此
    dpn = min(n % 2 + dpn/2, n % 3 + dpn/3) + 1(先吃掉 n%2 个凑偶数、或 n%3 个凑 3 倍数,
    再执行一次 2 分/3 分 操作);
  • 用 HashMap 记忆化。
    复杂度:时间复杂度 O(log n)(每层减半/减 2/3),空间复杂度 O(log n)。
    解法二:BFS 最短路(对照)
  • 从 n 到 0 的状态图(-1、/2、/3),BFS 求最少天数。
    复杂度:时间复杂度 O(log n) 状态数,空间复杂度 O(log n)。

代码实现

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

public class Solution {

    private Map<Integer, Integer> memo = new HashMap<>();

    public int minDaysMemo(int n) {
        memo.clear();
        return solve(n);
    }

    private int solve(int n) {
        if (n <= 1) {
            return n;
        }
        Integer cached = memo.get(n);
        if (cached != null) {
            return cached;
        }
        // 先吃 n%2 个凑成偶数再除以2;或先吃 n%3 个凑成 3 倍数再除以3;最后 +1 次整除操作
        int res = 1 + Math.min(n % 2 + solve(n / 2), n % 3 + solve(n / 3));
        memo.put(n, res);
        return res;
    }

    public int minDaysBfs(int n) {
        if (n <= 1) {
            return n;
        }
        java.util.Set<Integer> visited = new java.util.HashSet<>();
        java.util.Map<Integer, Integer> dist = new java.util.HashMap<>();
        java.util.ArrayDeque<Integer> q = new java.util.ArrayDeque<>();
        q.offer(n);
        visited.add(n);
        dist.put(n, 0);
        while (!q.isEmpty()) {
            int cur = q.poll();
            int d = dist.get(cur);
            if (cur == 0) {
                return d;
            }
            // -1
            if (!visited.contains(cur - 1)) {
                visited.add(cur - 1);
                dist.put(cur - 1, d + 1);
                q.offer(cur - 1);
            }
            if (cur % 2 == 0 && !visited.contains(cur / 2)) {
                visited.add(cur / 2);
                dist.put(cur / 2, d + 1);
                q.offer(cur / 2);
            }
            if (cur % 3 == 0 && !visited.contains(cur / 3)) {
                visited.add(cur / 3);
                dist.put(cur / 3, d + 1);
                q.offer(cur / 3);
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.minDaysMemo(10)); // 4
        System.out.println(sol.minDaysMemo(6)); // 3
        System.out.println(sol.minDaysMemo(1)); // 1
        System.out.println(sol.minDaysBfs(10)); // 4
        System.out.println(sol.minDaysBfs(6)); // 3
        System.out.println(sol.minDaysMemo(56)); // 6
        System.out.println(sol.minDaysMemo(2000000000)); // 30 附近
    }
}

复杂度分析

复杂度:时间复杂度 O(log n)(每层减半/减 2/3),空间复杂度 O(log n)。

复杂度:时间复杂度 O(log n) 状态数,空间复杂度 O(log n)。


1554. Strings Differ by One Character(只有一个不同字符的字符串)

题目描述

给定一个字符串列表 dict,其中所有字符串的长度都相同。

当存在两个字符串在相同索引处只有一个字符不同时返回 true,否则返回 false。

示例 1

复制代码
dict=["abcd","acbd","aacd"],输出 true("abcd" 与 "aacd" 在索引 1 不同)。

示例 2

复制代码
dict=["ab","cd","yz"],输出 false。

约束:

  • 1 <= dict.length <= 200;字符串长度相同。

解题思路

解法一:掩码哈希(主提交版本)

  • 对每个字符串,枚举"挖掉"一个位置后剩余的字符串模式(如 "abcd" 挖第 1 位 -> "a?cd");
  • 若任意两个字符串在同一个位置挖掉后得到的模式相同,则它们在该位置恰好一个字符不同;
  • 用 HashSet 记录模式,冲突即返回 true。
    复杂度:时间复杂度 O(L * n * 哈希代价),空间复杂度 O(L * n)。
    解法二:两两暴力比较(对照)
  • 枚举字符串对,统计不同字符个数,恰好为 1 返回 true。
    复杂度:时间复杂度 O(n^2 * L),空间复杂度 O(1)。

代码实现

java 复制代码
import java.util.HashSet;
import java.util.Set;

public class Solution {

    public boolean differByOneHash(String[] dict) {
        Set<String> seen = new HashSet<>();
        for (String s : dict) {
            char[] arr = s.toCharArray();
            for (int i = 0; i < arr.length; i++) {
                char tmp = arr[i];
                arr[i] = '?'; // 挖空位置
                String pat = new String(arr);
                if (!seen.add(pat)) {
                    return true;
                }
                arr[i] = tmp;
            }
        }
        return false;
    }

    public boolean differByOneBrute(String[] dict) {
        int n = dict.length;
        int len = dict[0].length();
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                int diff = 0;
                for (int k = 0; k < len; k++) {
                    if (dict[i].charAt(k) != dict[j].charAt(k)) {
                        diff++;
                        if (diff > 1) {
                            break;
                        }
                    }
                }
                if (diff == 1) {
                    return true;
                }
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.differByOneHash(new String[]{"abcd", "acbd", "aacd"})); // true
        System.out.println(sol.differByOneHash(new String[]{"ab", "cd", "yz"})); // false
        System.out.println(sol.differByOneHash(new String[]{"abcd", "cccc", "abyd", "abab"})); // true
        System.out.println(sol.differByOneBrute(new String[]{"abcd", "acbd", "aacd"})); // true
        System.out.println(sol.differByOneBrute(new String[]{"ab", "cd", "yz"})); // false
    }
}

复杂度分析

复杂度:时间复杂度 O(L * n * 哈希代价),空间复杂度 O(L * n)。

复杂度:时间复杂度 O(n^2 * L),空间复杂度 O(1)。


1555. Bank Account Summary(银行账户概要)

题目类型:数据库(SQL)

题目描述

用户表: Users

复制代码
+--------------+---------+
| Column Name  | Type    |
+--------------+---------+
| user_id      | int     |
| user_name    | varchar |
| credit       | int     |
+--------------+---------+
user_id 是这个表的主键(具有唯一值的列)。
表中的每一列包含每一个用户当前的额度信息。

交易表:Transactions

复制代码
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| trans_id      | int     |
| paid_by       | int     |
| paid_to       | int     |
| amount        | int     |
| transacted_on | date    |
+---------------+---------+
trans_id 是这个表的主键(具有唯一值的列)。
表中的每一列包含银行的交易信息。
ID 为 paid_by 的用户给 ID 为 paid_to 的用户转账。

力扣银行 (LCB) 帮助程序员们完成虚拟支付。我们的银行在表 Transaction 中记录每条交易信息,我们要查询每个用户的当前余额,并检查他们是否已透支(当前额度小于 0)。

编写解决方案报告:

复制代码
- `user_id` 用户 `ID`

- `user_name` 用户名

- `credit` 完成交易后的余额

- `credit_limit_breached` 检查是否透支 ("`Yes`" 或 "`No`")

以任意顺序返回结果表。

结果格式见如下所示。

示例 1:

复制代码
`**输入:**
Users` 表:
+------------+--------------+-------------+
| user_id    | user_name    | credit      |
+------------+--------------+-------------+
| 1          | Moustafa     | 100         |
| 2          | Jonathan     | 200         |
| 3          | Winston      | 10000       |
| 4          | Luis         | 800         |
+------------+--------------+-------------+

`Transactions` 表:
+------------+------------+------------+----------+---------------+
| trans_id   | paid_by    | paid_to    | amount   | transacted_on |
+------------+------------+------------+----------+---------------+
| 1          | 1          | 3          | 400      | 2020-08-01    |
| 2          | 3          | 2          | 500      | 2020-08-02    |
| 3          | 2          | 1          | 200      | 2020-08-03    |
+------------+------------+------------+----------+---------------+

**输出:**
+------------+------------+------------+-----------------------+
| `user_id `   | `user_name`  | `credit `    | `credit_limit_breached` |
+------------+------------+------------+-----------------------+
| 1          | Moustafa   | -100       | Yes                   |
| 2          | Jonathan   | 500        | No                    |
| 3          | Winston    | 9900       | No                    |
| 4          | Luis       | 800        | No                    |
+------------+------------+------------+-----------------------+
Moustafa 在 "2020-08-01" 支付了 $400 并在 "2020-08-03" 收到了 $200 ,当前额度 (100 -400 +200) = -$100
Jonathan 在 "2020-08-02" 收到了 $500 并在 "2020-08-08" 支付了 $200 ,当前额度 (200 +500 -200) = $500
Winston 在 "2020-08-01" 收到了 $400 并在 "2020-08-03" 支付了 $500 ,当前额度 (10000 +400 -500) = $9900
Luis 未收到任何转账信息,额度 = $800

解题思路

思考

要输出每位用户的期末余额,以及是否透支。余额变化来自转出、转入与初始额度,分三次更新再关联容易漏掉没有交易的用户。

把转出记为负金额、转入记为正金额,再与 U s e r s Users Users 的初始额度做 U N I O N A L L UNION\ ALL UNION ALL,按用户求和即得期末余额。没有交易的人仍保留初始行。外层用符号判断是否突破限额。

考察UNION + 分组聚合:把发款、收款两方向统一视角(转成 user2_id 视角),LEFT JOIN 用户表,用 COALESCE 补零,按用户分组求差值。

代码实现

sql 复制代码
# Write your MySQL query statement below
SELECT
    t.user_id,
    user_name,
    SUM(t.credit) AS credit,
    IF(SUM(t.credit) < 0, 'Yes', 'No') AS credit_limit_breached
FROM
    (
        SELECT paid_by AS user_id, -amount AS credit FROM Transactions
        UNION ALL
        SELECT paid_to AS user_id, amount AS credit FROM Transactions
        UNION ALL
        SELECT user_id, credit FROM Users
    ) AS t
    JOIN Users AS u ON t.user_id = u.user_id
GROUP BY t.user_id;

复杂度分析

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


1556. Thousand Separator(千位分隔数)

题目描述

给你一个整数 n,请你每隔三位添加点("." 符号)作为千位分隔符,并将结果以字符串格式返回。

示例 1

复制代码
n=987,输出 "987"。

示例 2

复制代码
n=1234,输出 "1.234"。

示例 3

复制代码
n=123456789,输出 "123.456.789"。

示例 4

复制代码
n=0,输出 "0"。

约束:

  • 0 <= n < 2^31。

解题思路

解法一:从右往左每隔三位加点(主提交版本)

  • 转字符串后从右往左每 3 位插入 '.'(或倒序遍历拼接)。
    复杂度:时间复杂度 O(len(n)),空间复杂度 O(len(n))。
    解法二:数值取模拼接(对照)
  • 每次取 n % 1000 追加到前面,用 %.3d 补零。
    复杂度:时间复杂度 O(len(n)),空间复杂度 O(len(n))。

代码实现

java 复制代码
public class Solution {

    public String thousandSeparatorStr(int n) {
        String s = Integer.toString(n);
        StringBuilder sb = new StringBuilder();
        int cnt = 0;
        for (int i = s.length() - 1; i >= 0; i--) {
            sb.append(s.charAt(i));
            cnt++;
            if (cnt % 3 == 0 && i > 0) {
                sb.append('.');
            }
        }
        return sb.reverse().toString();
    }

    public String thousandSeparatorMath(int n) {
        if (n == 0) {
            return "0";
        }
        StringBuilder sb = new StringBuilder();
        while (n > 0) {
            int part = n % 1000;
            n /= 1000;
            if (n > 0) {
                sb.insert(0, String.format("%03d.", part));
            } else {
                sb.insert(0, part);
            }
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.thousandSeparatorStr(987)); // 987
        System.out.println(sol.thousandSeparatorStr(1234)); // 1.234
        System.out.println(sol.thousandSeparatorStr(123456789)); // 123.456.789
        System.out.println(sol.thousandSeparatorStr(0)); // 0
        System.out.println(sol.thousandSeparatorMath(1234)); // 1.234
        System.out.println(sol.thousandSeparatorMath(123456789)); // 123.456.789
        System.out.println(sol.thousandSeparatorMath(0)); // 0
    }
}

复杂度分析

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


1557. Minimum Number of Vertices to Reach All Nodes(可以到达所有点的最少点数目)

题目描述

给你一个有向无环图 ,n 个节点编号为 0 到 n-1,以及一个边数组 edges(edges[i] = `fromi`, `toi`)。

找到最小的点集使得从这些点出发能到达图中所有点(解存在且唯一)。可以任意顺序返回节点编号。

示例 1

复制代码
n=6, edges=[[0,1],[0,2],[2,5],[3,4],[4,2]],输出 [0,3]。

示例 2

复制代码
n=5, edges=[[0,1],[2,1],[3,1],[1,4],[2,4]],输出 [0,2,3]。

约束:

  • 2 <= n <= 10^5;0 <= edges.length <= min(n*(n-1)/2, 10^5)。

解题思路

解法一:入度为零的点(主提交版本)

  • 关键定理(DAG):最小点集 = 所有入度为 0 的节点;
    因为入度为 0 的节点不可能被其他节点到达,必须选;而选齐后能到达全部节点(DAG 性质)。
    复杂度:时间复杂度 O(n + E),空间复杂度 O(n)。
    解法二:可达性并查集不可行,改用反向边 DFS 排除(对照)
  • 统计每个点是否有入边,无入边的点全部加入答案。
    复杂度:时间复杂度 O(n + E),空间复杂度 O(n)。

代码实现

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

public class Solution {

    public List<Integer> findSmallestSetOfVerticesIndegree(int n, List<List<Integer>> edges) {
        boolean[] hasIn = new boolean[n];
        for (List<Integer> e : edges) {
            hasIn[e.get(1)] = true;
        }
        List<Integer> ans = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            if (!hasIn[i]) {
                ans.add(i);
            }
        }
        return ans;
    }

    public List<Integer> findSmallestSetOfVerticesArr(int n, int[][] edges) {
        boolean[] in = new boolean[n];
        for (int[] e : edges) {
            in[e[1]] = true;
        }
        List<Integer> ans = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            if (!in[i]) {
                ans.add(i);
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        List<List<Integer>> e1 = new ArrayList<>();
        int[][] raw1 = {{0, 1}, {0, 2}, {2, 5}, {3, 4}, {4, 2}};
        for (int[] r : raw1) {
            List<Integer> p = new ArrayList<>();
            p.add(r[0]);
            p.add(r[1]);
            e1.add(p);
        }
        System.out.println(sol.findSmallestSetOfVerticesIndegree(6, e1)); // [0, 3]
        System.out.println(sol.findSmallestSetOfVerticesArr(5, new int[][]{{0, 1}, {2, 1}, {3, 1}, {1, 4}, {2, 4}})); // [0,2,3]
    }
}

复杂度分析

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


1558. Minimum Numbers of Function Calls to Make Target Array(得到目标数组的最少函数调用次数)

题目描述

给你一个与 nums 大小相同且初始值全为 0 的数组 arr,你可以调用以下函数得到整数数组 nums:

  • op 1:给某个元素加 1(每个元素单独);
  • op 2:给所有元素乘以 2(全局)。
    返回将 arr 变成 nums 的最少函数调用次数。答案保证在 32 位有符号整数以内。

示例 1

复制代码
nums=[1,5],输出 5。

示例 2

复制代码
nums=[2,2],输出 3。

约束:

  • 1 <= nums.length <= 10^5;0 <= nums[i] <= 10^9。

解题思路

解法一:倒推(主提交版本)

  • 逆向:把 nums 变回全 0,操作变为"某个元素减 1"与"所有元素除以 2(需全为偶数)";
  • 每次"除以 2"会让所有偶数元素减半,是最高效的,应尽量多做;
  • 统计每个元素二进制中 1 的个数 = 该元素需要的 +1 次数;所有元素能整除 2 的轮数取最大值,
    且每次全局 /2 只算一次 => 总次数 = sum(每个数二进制中 1 的个数) + max(每个数的二进制位数 - 1, 0)
    即 sum(popcount) + maxBitLen(最高位位置-1)。
    复杂度:时间复杂度 O(n * 31),空间复杂度 O(1)。
    解法二:模拟逐轮减半(对照)
  • 反复执行:若存在奇数,将其 -1 并计数;否则全部 /2 并计数,直到全 0。
    复杂度:时间复杂度 O(n * log(max)),空间复杂度 O(1)。

代码实现

java 复制代码
public class Solution {

    public int minOperationsBits(int[] nums) {
        int addCnt = 0;
        int maxBit = 0;
        for (int v : nums) {
            addCnt += Integer.bitCount(v);
            int bit = 0;
            while (v > 0) {
                bit++;
                v >>= 1;
            }
            maxBit = Math.max(maxBit, bit);
        }
        return addCnt + (maxBit > 0 ? maxBit - 1 : 0);
    }

    public int minOperationsSimulate(int[] nums) {
        int ans = 0;
        boolean allZero = false;
        while (!allZero) {
            allZero = true;
            boolean hasOdd = false;
            for (int i = 0; i < nums.length; i++) {
                if (nums[i] > 0) {
                    allZero = false;
                }
                if ((nums[i] & 1) == 1) {
                    nums[i]--;
                    ans++;
                }
            }
            if (!allZero) {
                // 全部偶数,除 2
                for (int i = 0; i < nums.length; i++) {
                    nums[i] >>= 1;
                }
                ans++;
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.minOperationsBits(new int[]{1, 5})); // 5
        System.out.println(sol.minOperationsBits(new int[]{2, 2})); // 3
        System.out.println(sol.minOperationsBits(new int[]{4, 2, 5})); // 6
        System.out.println(sol.minOperationsSimulate(new int[]{1, 5})); // 5
        System.out.println(sol.minOperationsSimulate(new int[]{2, 2})); // 3
        System.out.println(sol.minOperationsSimulate(new int[]{4, 2, 5})); // 6
    }
}

复杂度分析

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

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


1559. Detect Cycles in 2D Grid(二维网格图中判断是否有环)

题目描述

给你一个二维字符网格 grid(m x n),检查 grid 中是否存在相同值 形成的环。

一个环是一条开始和结束于同一个格子的长度大于等于 4 的路径。可以移动到上下左右四个方向

相邻且值相同的格子。不能回到上一次移动所在的格子(即不能走回头路来构成 2 格环)。

若存在环返回 true,否则返回 false。

示例 1

复制代码
grid=[[a,a,a,a],[a,b,b,a],[a,b,b,a],[a,a,a,a]],输出 true。

示例 2

复制代码
grid=[[c,c,c,a],[c,d,c,c],[c,c,e,c],[f,c,c,c]],输出 true。

解题思路

解法一:DFS 判环(主提交版本)

  • 对每个未访问格子做 DFS,携带"上一个格子";
  • 若 DFS 过程中遇到已访问且未完成的格子(在递归栈中)、且不是父节点,说明存在环;
    等价地,若访问到已访问过的格子且该格子 != parent,则环成立(因为路径长度 >= 4)。
    复杂度:时间复杂度 O(mn),空间复杂度 O(mn)。
    解法二:并查集判环(对照)
  • 把每个格子编号,向右、向下合并相同值的相邻格子;
  • 若合并前两者已在同一集合,说明环出现。
    复杂度:时间复杂度 O(mn * α),空间复杂度 O(mn)。

代码实现

java 复制代码
public class Solution {

    private char[][] grid;
    private int m, n;
    private boolean[][] visited;

    private int[] dx = {1, -1, 0, 0};
    private int[] dy = {0, 0, 1, -1};

    public boolean containsCycleDfs(char[][] grid) {
        this.grid = grid;
        this.m = grid.length;
        this.n = grid[0].length;
        visited = new boolean[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (!visited[i][j] && dfs(i, j, -1, -1, grid[i][j])) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean dfs(int x, int y, int px, int py, char c) {
        visited[x][y] = true;
        for (int d = 0; d < 4; d++) {
            int nx = x + dx[d], ny = y + dy[d];
            if (nx < 0 || nx >= m || ny < 0 || ny >= n) {
                continue;
            }
            if (grid[nx][ny] != c) {
                continue;
            }
            if (nx == px && ny == py) {
                continue; // 不走回头路
            }
            if (visited[nx][ny]) {
                return true; // 遇到已访问非父节点 => 环
            }
            if (dfs(nx, ny, x, y, c)) {
                return true;
            }
        }
        return false;
    }

    public boolean containsCycleUnionFind(char[][] grid) {
        this.m = grid.length;
        this.n = grid[0].length;
        DSU dsu = new DSU(m * n);
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                // 向右
                if (j + 1 < n && grid[i][j] == grid[i][j + 1]) {
                    if (!dsu.union(id(i, j), id(i, j + 1))) {
                        return true;
                    }
                }
                // 向下
                if (i + 1 < m && grid[i][j] == grid[i + 1][j]) {
                    if (!dsu.union(id(i, j), id(i + 1, j))) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private int id(int i, int j) {
        return i * n + j;
    }

    static class DSU {
        int[] parent;

        DSU(int n) {
            parent = new int[n];
            for (int i = 0; i < n; i++) {
                parent[i] = i;
            }
        }

        int find(int x) {
            while (parent[x] != x) {
                parent[x] = parent[parent[x]];
                x = parent[x];
            }
            return x;
        }

        // 返回 false 表示两个已在同一集合(成环)
        boolean union(int a, int b) {
            int ra = find(a), rb = find(b);
            if (ra == rb) {
                return false;
            }
            parent[ra] = rb;
            return true;
        }
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        char[][] g1 = {{'a', 'a', 'a', 'a'}, {'a', 'b', 'b', 'a'}, {'a', 'b', 'b', 'a'}, {'a', 'a', 'a', 'a'}};
        char[][] g2 = {{'c', 'c', 'c', 'a'}, {'c', 'd', 'c', 'c'}, {'c', 'c', 'e', 'c'}, {'f', 'c', 'c', 'c'}};
        char[][] g3 = {{'a', 'b', 'b'}, {'a', 'c', 'b'}, {'a', 'b', 'b'}}; // 无环
        System.out.println(sol.containsCycleDfs(g1)); // true
        System.out.println(sol.containsCycleDfs(g2)); // true
        System.out.println(sol.containsCycleDfs(g3)); // false
        System.out.println(sol.containsCycleUnionFind(g1)); // true
        System.out.println(sol.containsCycleUnionFind(g2)); // true
        System.out.println(sol.containsCycleUnionFind(g3)); // false
    }
}

复杂度分析

复杂度:时间复杂度 O(mn),空间复杂度 O(mn)。

复杂度:时间复杂度 O(mn * α),空间复杂度 O(mn)。


1560. Most Visited Sector in a Circular Track(圆形赛道上经过次数最多的扇区)

题目描述

给你一个整数 n 和一个整数数组 rounds。有一条圆形赛道由 n 个扇区组成,扇区编号从 1 到 n。

第 i 个阶段从扇区 rounds[i-1] 开始,到扇区 rounds[i] 结束。

以数组形式返回经过次数最多的那几个扇区,按扇区编号升序排列。

示例 1

复制代码
n=4, rounds=[1,3,1,2],输出 [1,2]。

示例 2

复制代码
n=2, rounds=[2,1,2,1,2,1,2,1,2],输出 [2]。

示例 3

复制代码
n=7, rounds=[1,3,5,7],输出 [1,2,3,4,5,6,7]。

约束:

  • 2 <= n <= 100;1 <= rounds.length <= 10000。

解题思路

解法一:起点终点简化(主提交版本)

  • 完整跑过的整圈对每个扇区贡献相同,只有起点到终点这段是"增量";
  • 关键结论:经过次数最多的扇区 = start, end 顺向区间(含端点的闭区间,环绕取模)。
    复杂度:时间复杂度 O(n + m),空间复杂度 O(n)。
    解法二:直接模拟计数(对照)
  • 模拟每阶段路径,对经过的每个扇区计数(整圈计数等价于起点到终点段)。
    复杂度:时间复杂度 O(m * n),空间复杂度 O(n)。

代码实现

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

public class Solution {

    public List<Integer> mostVisitedRange(int n, int[] rounds) {
        int start = rounds[0];
        int end = rounds[rounds.length - 1];
        List<Integer> ans = new ArrayList<>();
        if (start <= end) {
            for (int i = start; i <= end; i++) {
                ans.add(i);
            }
        } else {
            for (int i = 1; i <= end; i++) {
                ans.add(i);
            }
            for (int i = start; i <= n; i++) {
                ans.add(i);
            }
        }
        return ans;
    }

    public List<Integer> mostVisitedSim(int n, int[] rounds) {
        int[] cnt = new int[n + 1];
        int cur = rounds[0];
        cnt[cur]++;
        for (int i = 1; i < rounds.length; i++) {
            int nxt = rounds[i];
            while (cur != nxt) {
                cur++;
                if (cur > n) {
                    cur = 1;
                }
                cnt[cur]++;
            }
        }
        int max = 0;
        for (int c : cnt) {
            max = Math.max(max, c);
        }
        List<Integer> ans = new ArrayList<>();
        for (int i = 1; i <= n; i++) {
            if (cnt[i] == max) {
                ans.add(i);
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.mostVisitedRange(4, new int[]{1, 3, 1, 2})); // [1, 2]
        System.out.println(sol.mostVisitedRange(2, new int[]{2, 1, 2, 1, 2, 1, 2, 1, 2})); // [2]
        System.out.println(sol.mostVisitedRange(7, new int[]{1, 3, 5, 7})); // [1..7]
        System.out.println(sol.mostVisitedSim(4, new int[]{1, 3, 1, 2})); // [1, 2]
        System.out.println(sol.mostVisitedSim(2, new int[]{2, 1, 2, 1, 2, 1, 2, 1, 2})); // [2]
    }
}

复杂度分析

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

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

相关推荐
她说彩礼65万1 小时前
C# TaskCompletionSource
java·开发语言·c#
当战神遇到编程1 小时前
Java 基础入门(七):递归详解——执行原理、方法调用栈与经典练习
java
若亦_1 小时前
排序>>交换排序
java·数据结构·算法
Wang's Blog1 小时前
MongoDB 地理位置上报实战:从接口设计到覆盖写入的完整实现
java·交友
鬼手点金2 小时前
Skills:领域能力的可组合单元
java·人工智能·笔记·自动化·1024程序员节
captain37610 小时前
MyBatis(入门5)其他查询操作
java·数据库·spring boot·mybatis
Novproxy_Z11 小时前
商用代理避坑:如何识别商家拿机房 IP 冒充住宅 IP
java·网络·tcp/ip
文丑颜不良啊11 小时前
LeetCode1481~1500题目解析
java·leetcode
2601_9628857211 小时前
quotes 字段全解:涨跌幅、振幅、换手率的口径与单位
java·前端·spring