LeetCode 1541-1560. 题解合集
文章目录
- [LeetCode 1541-1560. 题解合集](#LeetCode 1541-1560. 题解合集)
-
- [1541. Minimum Insertions to Balance a Parentheses String(平衡括号字符串的最少插入次数)](#1541. Minimum Insertions to Balance a Parentheses String(平衡括号字符串的最少插入次数))
- [1542. Find Longest Awesome Substring(找出最长的超赞子字符串)](#1542. Find Longest Awesome Substring(找出最长的超赞子字符串))
- [1543. Fix Product Name Format(产品名称格式修复)](#1543. Fix Product Name Format(产品名称格式修复))
- [1544. Make The String Great(整理字符串)](#1544. Make The String Great(整理字符串))
- [1545. Find Kth Bit in Nth Binary String(找出第 N 个二进制字符串中的第 K 位)](#1545. Find Kth Bit in Nth Binary String(找出第 N 个二进制字符串中的第 K 位))
- [1546. Maximum Number of Non-Overlapping Subarrays With Sum Equals Target](#1546. Maximum Number of Non-Overlapping Subarrays With Sum Equals Target)
- [1547. Minimum Cost to Cut a Stick(切棍子的最小成本)](#1547. Minimum Cost to Cut a Stick(切棍子的最小成本))
- [1548. The Most Similar Path in a Graph(图中最相似的路径)](#1548. The Most Similar Path in a Graph(图中最相似的路径))
- [1549. The Most Recent Orders for Each Product(每件商品的最新订单)](#1549. The Most Recent Orders for Each Product(每件商品的最新订单))
- [1550. Three Consecutive Odds(存在连续三个奇数的数组)](#1550. Three Consecutive Odds(存在连续三个奇数的数组))
- [1551. Minimum Operations to Make Array Equal(使数组中所有元素相等的最小操作数)](#1551. Minimum Operations to Make Array Equal(使数组中所有元素相等的最小操作数))
- [1552. Magnetic Force Between Two Balls(两球之间的磁力)](#1552. Magnetic Force Between Two Balls(两球之间的磁力))
- [1553. Minimum Number of Days to Eat N Oranges(吃掉 N 个橘子的最少天数)](#1553. Minimum Number of Days to Eat N Oranges(吃掉 N 个橘子的最少天数))
- [1554. Strings Differ by One Character(只有一个不同字符的字符串)](#1554. Strings Differ by One Character(只有一个不同字符的字符串))
- [1555. Bank Account Summary(银行账户概要)](#1555. Bank Account Summary(银行账户概要))
- [1556. Thousand Separator(千位分隔数)](#1556. Thousand Separator(千位分隔数))
- [1557. Minimum Number of Vertices to Reach All Nodes(可以到达所有点的最少点数目)](#1557. Minimum Number of Vertices to Reach All Nodes(可以到达所有点的最少点数目))
- [1558. Minimum Numbers of Function Calls to Make Target Array(得到目标数组的最少函数调用次数)](#1558. Minimum Numbers of Function Calls to Make Target Array(得到目标数组的最少函数调用次数))
- [1559. Detect Cycles in 2D Grid(二维网格图中判断是否有环)](#1559. Detect Cycles in 2D Grid(二维网格图中判断是否有环))
- [1560. Most Visited Sector in a Circular Track(圆形赛道上经过次数最多的扇区)](#1560. Most Visited Sector in a Circular Track(圆形赛道上经过次数最多的扇区))
1541. Minimum Insertions to Balance a Parentheses String(平衡括号字符串的最少插入次数)
题目描述
给你一个括号字符串 s,只包含 '(' 和 ')'。一个括号字符串是平衡的当:
- 任何左括号 '(' 必须对应两个连续的右括号 '))';
- 左括号必须在对应的连续两个右括号之前。
你可以在任意位置插入 '(' 和 ')' 使字符串平衡。返回最少插入次数。
示例 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 个橘子,你决定每一天选择如下方式之一吃:
- 吃掉一个橘子;
- 如果剩余橘子数
n能被 2 整除,可以吃掉n/2 个橘子(剩n/2); - 如果剩余橘子数
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:
op1:给某个元素加 1(每个元素单独);op2:给所有元素乘以 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)。