LeetCode 1141-1160. 题解合集
文章目录
- [LeetCode 1141-1160. 题解合集](#LeetCode 1141-1160. 题解合集)
-
- [1141. User Activity for the Past 30 Days I(过去 30 天的用户活动 I)](#1141. User Activity for the Past 30 Days I(过去 30 天的用户活动 I))
- [1142. User Activity for the Past 30 Days II(过去 30 天的用户活动 II)](#1142. User Activity for the Past 30 Days II(过去 30 天的用户活动 II))
- [1143. Longest Common Subsequence(最长公共子序列)](#1143. Longest Common Subsequence(最长公共子序列))
- [1144. Decrease Elements To Make Array Zigzag(递减元素使数组呈锯齿状)](#1144. Decrease Elements To Make Array Zigzag(递减元素使数组呈锯齿状))
- [1145. Binary Tree Coloring Game(二叉树着色游戏)](#1145. Binary Tree Coloring Game(二叉树着色游戏))
- [1146. Snapshot Array(快照数组)](#1146. Snapshot Array(快照数组))
- [1147. Longest Chunked Palindrome Decomposition(段式回文)](#1147. Longest Chunked Palindrome Decomposition(段式回文))
- [1148. Article Views I(文章浏览 I)](#1148. Article Views I(文章浏览 I))
- [1149. Article Views II(文章浏览 II)](#1149. Article Views II(文章浏览 II))
- [1150. Check If a Number Is Majority Element in a Sorted Array(检查一个数是否在数组中占绝大多数)](#1150. Check If a Number Is Majority Element in a Sorted Array(检查一个数是否在数组中占绝大多数))
- [1151. Minimum Swaps to Group All 1's Together(最少交换次数来组合所有的 1)](#1151. Minimum Swaps to Group All 1's Together(最少交换次数来组合所有的 1))
- [1152. Analyze User Website Visit Pattern(用户网站访问行为分析)](#1152. Analyze User Website Visit Pattern(用户网站访问行为分析))
- [1153. String Transforms Into Another String(字符串转化)](#1153. String Transforms Into Another String(字符串转化))
- [1154. Day of the Year(一年中的第几天)](#1154. Day of the Year(一年中的第几天))
- [1155. Number of Dice Rolls With Target Sum(掷骰子等于目标和的方法数)](#1155. Number of Dice Rolls With Target Sum(掷骰子等于目标和的方法数))
- [1156. Swap For Longest Repeated Character Substring(单字符重复子串的最大长度)](#1156. Swap For Longest Repeated Character Substring(单字符重复子串的最大长度))
- [1157. Online Majority Element In Subarray(子数组中占绝大多数的元素)](#1157. Online Majority Element In Subarray(子数组中占绝大多数的元素))
- [1158. Market Analysis I(市场分析 I)](#1158. Market Analysis I(市场分析 I))
- [1159. Market Analysis II(市场分析 II)](#1159. Market Analysis II(市场分析 II))
- [1160. Find Words That Can Be Formed by Characters(拼写单词)](#1160. Find Words That Can Be Formed by Characters(拼写单词))
1141. User Activity for the Past 30 Days I(过去 30 天的用户活动 I)
题目类型:数据库(SQL)
题目描述
表:Activity
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| user_id | int |
| session_id | int |
| activity_date | date |
| activity_type | enum |
+---------------+---------+
该表可能包含重复数据。
activity_type 列是 ENUM(category) 类型, 从 ('open_session', 'end_session', 'scroll_down', 'send_message') 取值。
该表记录社交媒体网站的用户活动。
注意,每个会话只属于一个用户。
编写解决方案,统计截至 2019-07-27(包含2019-07-27),近** **30 天的每日活跃用户数(当天只要有一条活动记录,即为活跃用户)。
以 任意顺序 返回结果表。
结果示例如下。
注意 :('open_session','end_session','scroll_down','send_message')中的任何活动将被视为用户在某一天活跃的有效活动。
示例 1:
**输入:**
Activity table:
+---------+------------+---------------+---------------+
| user_id | session_id | activity_date | activity_type |
+---------+------------+---------------+---------------+
| 1 | 1 | 2019-07-20 | open_session |
| 1 | 1 | 2019-07-20 | scroll_down |
| 1 | 1 | 2019-07-20 | end_session |
| 2 | 4 | 2019-07-20 | open_session |
| 2 | 4 | 2019-07-21 | send_message |
| 2 | 4 | 2019-07-21 | end_session |
| 3 | 2 | 2019-07-21 | open_session |
| 3 | 2 | 2019-07-21 | send_message |
| 3 | 2 | 2019-07-21 | end_session |
| 4 | 3 | 2019-06-25 | open_session |
| 4 | 3 | 2019-06-25 | end_session |
+---------+------------+---------------+---------------+
**输出:**
+------------+--------------+
| day | active_users |
+------------+--------------+
| 2019-07-20 | 2 |
| 2019-07-21 | 2 |
+------------+--------------+ **
解释:**注意非活跃用户的记录不需要展示。
解题思路
思考
统计截止
2019-07-27的 30 30 30 天内每日去重用户数。WHERE同时约束上界与日差,再按日期GROUP BY并对user_id做COUNT(DISTINCT ...)。
代码实现
sql
# Write your MySQL query statement below
SELECT activity_date AS day, COUNT(DISTINCT user_id) AS active_users
FROM Activity
WHERE activity_date <= '2019-07-27' AND DATEDIFF('2019-07-27', activity_date) < 30
GROUP BY 1;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1142. User Activity for the Past 30 Days II(过去 30 天的用户活动 II)
题目类型:数据库(SQL)
题目描述
Activity 表:
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| user_id | int |
| session_id | int |
| activity_date | date |
| activity_type | enum |
+---------------+---------+
该表没有主键,它可能有重复的行。
activity_type 列是 ENUM 类型,可以取(" open_session"," end_session"," scroll_down"," send_message")四种活动类型之一。
该表显示了社交媒体网站的用户活动。
请注意,每个会话只属于一个用户。
编写解决方案,统计截至 2019-07-27(含)的 30 天内每个用户的平均会话数,四舍五入到小数点后两位。只统计那些会话期间用户至少进行一项活动的有效会话。
结果格式如下例所示。
示例:
**输入:**
Activity 表:
+---------+------------+---------------+---------------+
| user_id | session_id | activity_date | activity_type |
+---------+------------+---------------+---------------+
| 1 | 1 | 2019-07-20 | open_session |
| 1 | 1 | 2019-07-20 | scroll_down |
| 1 | 1 | 2019-07-20 | end_session |
| 2 | 4 | 2019-07-20 | open_session |
| 2 | 4 | 2019-07-21 | send_message |
| 2 | 4 | 2019-07-21 | end_session |
| 3 | 2 | 2019-07-21 | open_session |
| 3 | 2 | 2019-07-21 | send_message |
| 3 | 2 | 2019-07-21 | end_session |
| 3 | 5 | 2019-07-21 | open_session |
| 3 | 5 | 2019-07-21 | scroll_down |
| 3 | 5 | 2019-07-21 | end_session |
| 4 | 3 | 2019-06-25 | open_session |
| 4 | 3 | 2019-06-25 | end_session |
+---------+------------+---------------+---------------+
**输出:**
+---------------------------+
| average_sessions_per_user |
+---------------------------+
| 1.33 |
+---------------------------+
**解释:**用户 1 和 2 每人在过去 30 天有 1 个会话,而用户 3 有 2 个会话。所以平均是 (1 + 1 + 2) / 3 = 1.33 。
解题思路
思考
先按用户统计窗口内不同
session_id个数,再对这些会话数取平均,空表时用IFNULL得到 0 0 0。窗口条件与上一题相同。
代码实现
sql
# Write your MySQL query statement below
WITH
T AS (
SELECT
COUNT(DISTINCT session_id) AS sessions
FROM Activity
WHERE activity_date <= '2019-07-27' AND DATEDIFF('2019-07-27', activity_date) < 30
GROUP BY user_id
)
SELECT IFNULL(ROUND(AVG(sessions), 2), 0) AS average_sessions_per_user
FROM T;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1143. Longest Common Subsequence(最长公共子序列)
题目描述
给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。如果不存在公共子序列,返回 0。
一个字符串的子序列是指:在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是。两个字符串的公共子序列是这两个字符串所共同拥有的子序列。
示例 1
text1 = "abcde", text2 = "ace" → 3
示例 2
text1 = "abc", text2 = "abc" → 3
示例 3
text1 = "abc", text2 = "def" → 0
约束:
- 1 <=
text1.length,text2.length <= 1000;text1和text2仅由小写英文字符组成。
解题思路
解法一:二维动态规划
- dpij 表示 text10...i-1 与 text20...j-1 的 LCS 长度;
- 若 text1i-1 == text2j-1:dpij = dpi-1j-1 + 1;
否则 dpij = max(dpi-1j, dpij-1); - 滚动优化可用两行数组省空间,但为清晰先用完整二维表。
复杂度:时间 O(mn),空间 O(m n)。
解法二:滚动数组优化空间 - 状态转移只依赖上一行与当前行,用一维数组滚动即可;
- 注意左上角值需先用变量缓存(因为会被覆盖)。
复杂度:时间 O(m*n),空间 O(min(m,n))。
代码实现
java
public class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
public int longestCommonSubsequenceRolling(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[] dp = new int[n + 1];
for (int i = 1; i <= m; i++) {
int prev = 0; // dp[i-1][j-1]
for (int j = 1; j <= n; j++) {
int tmp = dp[j]; // dp[i-1][j](当前行覆盖前)
if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
dp[j] = prev + 1;
} else {
dp[j] = Math.max(dp[j], dp[j - 1]); // dp[i-1][j] vs dp[i][j-1]
}
prev = tmp;
}
}
return dp[n];
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.longestCommonSubsequence("abcde", "ace") + "(期望 3)");
System.out.println("示例1 解法二: " + s.longestCommonSubsequenceRolling("abcde", "ace") + "(期望 3)");
System.out.println("示例2 解法一: " + s.longestCommonSubsequence("abc", "abc") + "(期望 3)");
System.out.println("示例2 解法二: " + s.longestCommonSubsequenceRolling("abc", "abc") + "(期望 3)");
System.out.println("示例3 解法一: " + s.longestCommonSubsequence("abc", "def") + "(期望 0)");
System.out.println("示例3 解法二: " + s.longestCommonSubsequenceRolling("abc", "def") + "(期望 0)");
}
}
复杂度分析
复杂度:时间 O(mn),空间 O(m n)。
复杂度:时间 O(m*n),空间 O(min(m,n))。
1144. Decrease Elements To Make Array Zigzag(递减元素使数组呈锯齿状)
题目描述
给你一个整数数组 nums,每次操作会从中选择一个元素并将该元素的值减少 1。
如果符合下列情况之一,则数组 A 就是锯齿数组:
- 每个偶数索引对应的元素都大于相邻的元素,即 A0 > A1 < A2 > A3 < ...
- 或者,每个奇数索引对应的元素都大于相邻的元素,即 A0 < A1 > A2 < A3 < ...
返回将数组nums转换为锯齿数组所需的最小操作次数。
示例 1
nums = [1,2,3] → 2(把 2 递减到 0,或把 3 递减到 1)
示例 2
nums = [9,6,1,6,2] → 4
约束:
- 1 <=
nums.length<= 1000;1 <=nums[i]<= 1000。
解题思路
解法一:贪心 + 两种模式取最小
- 锯齿只可能有两种形态:偶数位是"峰"或奇数位是"峰";
- 对每一种形态,遍历需要是"谷"的位置 k:若 numsk >= numsk-1 或 numsk >= numsk+1,
则必须把 numsk 降到 min(邻居) - 1,操作数为 numsk - (min(邻居)-1),累加; - 取两种形态操作数的最小值。
注意:直接对原数组副本做减法累加更稳妥(因为一次只减 1,且贪心每次把谷降到最低即可,
不会影响其它位置------因为谷只与邻居比较)。
复杂度:时间 O(n),空间 O(1)(额外 O(n) 副本)。
解法二:模拟逐次递减 - 直接按规则循环:找到不满足条件的谷,循环减 1 直到满足;统计总操作数;
- 两种模式分别模拟,取较小。适合演示"每次操作减 1"的字面含义,但复杂度较高。
复杂度:时间 O(n * max(nums)),空间 O(1);本实现仍是逐位计算降值(等价于快速模拟)。
代码实现
java
public class Solution {
public int movesToMakeZigzag(int[] nums) {
int n = nums.length;
// 模式 A:偶数位为峰(奇数位是谷)
int costA = 0;
int[] a = nums.clone();
for (int i = 1; i < n; i += 2) { // 奇数位是谷
if (a[i] >= a[i - 1]) {
int delta = a[i] - (a[i - 1] - 1);
costA += delta;
a[i] -= delta;
}
if (i + 1 < n && a[i] >= a[i + 1]) {
int delta = a[i] - (a[i + 1] - 1);
costA += delta;
a[i] -= delta;
}
}
// 模式 B:奇数位为峰(偶数位是谷)
int costB = 0;
int[] b = nums.clone();
for (int i = 0; i < n; i += 2) { // 偶数位是谷
if (i > 0 && b[i] >= b[i - 1]) {
int delta = b[i] - (b[i - 1] - 1);
costB += delta;
b[i] -= delta;
}
if (i + 1 < n && b[i] >= b[i + 1]) {
int delta = b[i] - (b[i + 1] - 1);
costB += delta;
b[i] -= delta;
}
}
return Math.min(costA, costB);
}
public int movesToMakeZigzagFormula(int[] nums) {
return Math.min(calc(nums, 0), calc(nums, 1));
}
private int calc(int[] nums, int start) {
int n = nums.length;
int cost = 0;
for (int i = start; i < n; i += 2) {
int target = Integer.MAX_VALUE;
if (i > 0) target = Math.min(target, nums[i - 1]);
if (i + 1 < n) target = Math.min(target, nums[i + 1]);
if (nums[i] >= target) cost += nums[i] - (target - 1);
}
return cost;
}
public static void main(String[] args) {
Solution s = new Solution();
int[] n1 = {1,2,3};
System.out.println("示例1 解法一: " + s.movesToMakeZigzag(n1) + "(期望 2)");
System.out.println("示例1 解法二: " + s.movesToMakeZigzagFormula(n1) + "(期望 2)");
int[] n2 = {9,6,1,6,2};
System.out.println("示例2 解法一: " + s.movesToMakeZigzag(n2) + "(期望 4)");
System.out.println("示例2 解法二: " + s.movesToMakeZigzagFormula(n2) + "(期望 4)");
}
}
复杂度分析
复杂度:时间 O(n),空间 O(1)(额外 O(n) 副本)。
复杂度:时间 O(n * max(nums)),空间 O(1);本实现仍是逐位计算降值(等价于快速模拟)。
1145. Binary Tree Coloring Game(二叉树着色游戏)
题目描述
有两位极客玩家参与了一场「二叉树着色」的游戏。游戏中,给出二叉树的根节点 root,树上总共有 n 个节点,
且 n 为奇数,其中每个节点上的值从 1 到 n 各不相同。
最开始时:「一号」玩家从 1, `n` 中取一个值 x,「二号」玩家也从 1, `n` 中取一个值 y 且 y != x。
「一号」玩家给值为 x 的节点染上红色,而「二号」玩家给值为 y 的节点染上蓝色。
之后两位玩家轮流操作,「一号」玩家先手。每一回合,玩家选择一个被他染过色的节点,
将所选节点一个未着色的邻节点(左右子节点或父节点)进行染色。
如果当前玩家无法找到这样的节点来染色时,其回合就会被跳过。若两个玩家都没有可以染色的节点时,游戏结束。
着色节点最多的那位玩家获得胜利。
现在,假设你是「二号」玩家,根据所给出的输入,假如存在一个 y 值可以确保你赢得这场游戏,则返回 true;否则返回 false。
示例 1
root = [1,2,3,4,5,6,7,8,9,10,11], n = 11, x = 3 → true(二号选值为 2 的节点)
示例 2
root = [1,2,3], n = 3, x = 1 → false
约束:
- 树中节点数目为
n;1 <= x <=n<= 100;n是奇数;1 <=Node.val<=n;树中所有值互不相同。
解题思路
解法一:树形 DFS 计数 + 三分割分析
- 一号先手染色节点 x 之后,整棵树被 x 分成三块互不相通的区域:
- x 的左子树
- x 的右子树
- x 的父节点侧的其余部分(n - 左子树大小 - 右子树大小 - 1)
- 二号第一次选 y 时,只要 y 落在上述某一块中,二号就能把这一整块染成蓝色
(一号无法跨过 x 进入该块------x 已被红色锁定,谁也无法再染 x 的邻居中属于对方区域的格子?
关键性质:一号从 x 出发只能扩展 x 所在连通区域,二号选在某一侧后也只会扩展那一侧;
最终二号能获得的节点数恰好是该块的全部节点); - 因此二号能赢 ⇔ 存在一块区域的节点数 > n / 2。
复杂度:时间 O(n),空间 O(h)。
解法二:BFS/DFS 找 x 节点再统计三块 - 先用一次遍历定位值为 x 的节点;
- 计算其左子树大小、右子树大小与父侧大小;
- 与解法一相同,判定最大值是否超过 n/2。
复杂度:时间 O(n),空间 O(n)。
代码实现
java
import java.util.*;
public class Solution {
public boolean btreeGameWinningMove(TreeNode root, int n, int x) {
TreeNode nodeX = find(root, x); // 定位 x 节点
int left = sizeOf(nodeX.left); // x 左子树大小
int right = sizeOf(nodeX.right); // x 右子树大小
int top = n - left - right - 1; // x 父侧其余节点数
return Math.max(top, Math.max(left, right)) > n / 2;
}
private int sizeOf(TreeNode node) {
if (node == null) return 0;
return 1 + sizeOf(node.left) + sizeOf(node.right);
}
public boolean btreeGameWinningMoveBfs(TreeNode root, int n, int x) {
TreeNode nodeX = find(root, x);
int l = sizeOf(nodeX.left);
int r = sizeOf(nodeX.right);
int top = n - l - r - 1;
return Math.max(top, Math.max(l, r)) > n / 2;
}
private TreeNode find(TreeNode node, int x) {
if (node == null) return null;
if (node.val == x) return node;
TreeNode left = find(node.left, x);
if (left != null) return left;
return find(node.right, x);
}
public static class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
private static TreeNode build(Integer[] arr) {
if (arr == null || arr.length == 0 || arr[0] == null) return null;
TreeNode root = new TreeNode(arr[0]);
Queue<TreeNode> q = new ArrayDeque<>();
q.offer(root);
int i = 1;
while (!q.isEmpty() && i < arr.length) {
TreeNode cur = q.poll();
if (arr[i] != null) {
cur.left = new TreeNode(arr[i]);
q.offer(cur.left);
}
i++;
if (i < arr.length && arr[i] != null) {
cur.right = new TreeNode(arr[i]);
q.offer(cur.right);
}
i++;
}
return root;
}
public static void main(String[] args) {
Solution s = new Solution();
// 示例 1:[1,2,3,4,5,6,7,8,9,10,11], n=11, x=3
TreeNode r1 = build(new Integer[]{1,2,3,4,5,6,7,8,9,10,11});
System.out.println("示例1 解法一: " + s.btreeGameWinningMove(r1, 11, 3) + "(期望 true)");
System.out.println("示例1 解法二: " + s.btreeGameWinningMoveBfs(r1, 11, 3) + "(期望 true)");
// 示例 2:[1,2,3], n=3, x=1
TreeNode r2 = build(new Integer[]{1,2,3});
System.out.println("示例2 解法一: " + s.btreeGameWinningMove(r2, 3, 1) + "(期望 false)");
System.out.println("示例2 解法二: " + s.btreeGameWinningMoveBfs(r2, 3, 1) + "(期望 false)");
}
}
复杂度分析
复杂度:时间 O(n),空间 O(h)。
复杂度:时间 O(n),空间 O(n)。
1146. Snapshot Array(快照数组)
题目描述
实现支持下列接口的「快照数组」- SnapshotArray:
- SnapshotArray(
intlength):初始化一个与指定长度相等的类数组数据结构,初始时每个元素都等于 0。 voidset(index,val):会将指定索引index处的元素设置为val。intsnap():获取该数组的快照,并返回快照的编号snap_id(快照号是调用 snap() 的总次数减去 1)。intget(index,snap_id):根据指定的snap_id选择快照,并返回该快照指定索引index的值。
示例
["SnapshotArray","set","snap","set","get"]
[[3],[0,5],[],[0,6],[0,0]]
- 输出 :`null`,`null`,0,`null`,5
约束: - 1 <= length <= 50000;最多进行 50000 次
set/snap/get 调用; - 0 <=
index< length;0 <=snap_id< snap() 总次数;0 <=val<= 10^9。
解题思路
解法一:每个索引一份"变更历史" + 二分查找
- 对每个 index 维护一个列表,按时间顺序记录 (snap_id, val) 的变更;
- set 时如果该 index 最近的变更就是当前 snap_id,直接覆盖 val(无谓记录);
否则追加 (当前 snap_id, val); - get(index, snap_id) 时,在该 index 的历史列表中二分查找最后一个 snap_id <= 目标快照号
的变更,返回其 val;在此之前(或无记录)值为 0。
复杂度:时间 set O(1)、snap O(1)、get O(log 变更数);空间 O(变更总数)。
解法二:全量快照列表 - 每次 snap 时把当前数组拷贝一份存到快照列表;
- get 直接返回快照列表snap_idindex。
- 实现直观,但空间 O(snap 数 * length),仅适合小数据演示。
复杂度:时间 snap O(length)、get O(1);空间 O(snap 数 * length)。
代码实现
java
import java.util.*;
public class Solution {
public static class SnapshotArray {
private int snapId = 0;
// 每个索引的一个历史记录列表:[snap_id, val]
private List<int[]>[] hist;
// 每个索引在当前快照中的值缓存,用于 set 合并
private int[] cur;
@SuppressWarnings("unchecked")
public SnapshotArray(int length) {
hist = new List[length];
for (int i = 0; i < length; i++) {
hist[i] = new ArrayList<>();
hist[i].add(new int[]{0, 0}); // 初始快照 0 值为 0
}
cur = new int[length];
}
public void set(int index, int val) {
// 若最后一次记录就是当前 snap_id,覆盖(不产生新记录)
List<int[]> list = hist[index];
int lastId = list.get(list.size() - 1)[0];
if (lastId == snapId) {
list.get(list.size() - 1)[1] = val;
} else {
list.add(new int[]{snapId, val});
}
cur[index] = val;
}
public int snap() {
return snapId++;
}
public int get(int index, int snap_id) {
List<int[]> list = hist[index];
// 二分找最后一个 snap_id <= 目标 的记录
int lo = 0, hi = list.size() - 1, ans = 0;
while (lo <= hi) {
int mid = (lo + hi) >>> 1;
if (list.get(mid)[0] <= snap_id) {
ans = mid;
lo = mid + 1;
} else {
hi = mid - 1;
}
}
return list.get(ans)[1];
}
}
public static class SnapshotArrayFull {
private int snapId = 0;
private final List<int[]> shots = new ArrayList<>();
private int[] arr;
public SnapshotArrayFull(int length) {
arr = new int[length];
}
public void set(int index, int val) {
arr[index] = val;
}
public int snap() {
shots.add(arr.clone());
return snapId++;
}
public int get(int index, int snap_id) {
return shots.get(snap_id)[index];
}
}
public static void main(String[] args) {
// 示例流程
SnapshotArray sa = new SnapshotArray(3);
sa.set(0, 5);
int s0 = sa.snap();
sa.set(0, 6);
int g0 = sa.get(0, s0);
System.out.println("示例 解法一: snap=" + s0 + "(期望 0) get=" + g0 + "(期望 5)");
SnapshotArrayFull sb = new SnapshotArrayFull(3);
sb.set(0, 5);
int s1 = sb.snap();
sb.set(0, 6);
System.out.println("示例 解法二: snap=" + s1 + "(期望 0) get=" + sb.get(0, s1) + "(期望 5)");
// 附加用例
SnapshotArray sc = new SnapshotArray(1);
sc.set(0, 1);
int ss0 = sc.snap();
int ss1 = sc.snap();
sc.set(0, 3);
int ss2 = sc.snap();
System.out.println("附加: snap0=" + ss0 + " snap1=" + ss1 + " snap2=" + ss2
+ " get(0,0)=" + sc.get(0, 0) + " get(0,1)=" + sc.get(0, 1) + " get(0,2)=" + sc.get(0, 2));
}
}
复杂度分析
复杂度:时间 set O(1)、snap O(1)、get O(log 变更数);空间 O(变更总数)。
- 实现直观,但空间 O(snap 数 * length),仅适合小数据演示。
复杂度:时间 snap O(length)、get O(1);空间 O(snap 数 * length)。
1147. Longest Chunked Palindrome Decomposition(段式回文)
题目描述
你会得到一个字符串 text。你应该把它分成 k 个子字符串 (subtext1, subtext2, ..., subtextk),要求满足:
subtext_i是非空字符串- 所有子字符串的连接等于
text(即 subtext1 + subtext2 + ... + subtextk ==text) - 对于所有
i的有效值(1 <=i<=k),subtext_i==subtext_{k-i+1} 均成立(对称相等)
返回k可能的最大值。
示例 1
text = "ghiabcdefhelloadamhelloabcdefghi" → 7((ghi)(abcdef)(hello)(adam)(hello)(abcdef)(ghi))
示例 2
text = "merchant" → 1
示例 3
text = "antaprezatepzapreanta" → 11
约束:
- 1 <=
text.length <= 1000;text仅由小写英文字符组成。
解题思路
解法一:双端贪心
- 维护左右两个游标,贪心地在两端寻找最短的对称段:
固定左起点 l,从右往左找最小的 r 使 textl...r == 对称位置等长子串; - 每找到一组对称段,k 加 2(首尾各一段),继续处理中间剩余部分;
- 若最后剩余单个子串(长度 > 0),k 加 1。
复杂度:时间 O(n^2)(每个位置可能比较 O(n) 子串),空间 O(1)(不含比较临时串的额外开销,substring 会复制)。
解法二:递归分治 - 递归函数 solve(s):返回 s 能切成的最多段数;
- 枚举 k = 1...s.length/2,若前缀 s0...k-1 == 后缀 slen-k...len-1,则
答案 = 2 + solve(sk ... len-k-1);否则返回 1;取最大的 k 得到最优; - 用记忆化避免重复计算重叠子问题(实际字符串不同,可无需缓存,这里加缓存避免 TLE 更保险)。
复杂度:时间 O(n^2)(枚举 + 比较),空间 O(n)(递归栈 + 缓存)。
代码实现
java
import java.util.*;
public class Solution {
public int longestDecomposition(String text) {
int n = text.length();
int ans = 0;
int l = 0, r = n - 1;
while (l <= r) {
if (l == r) { ans++; break; } // 剩单个字符也算一段
int len = 1;
boolean found = false;
while (l + len - 1 < r - len + 1) {
// 比较前缀 text[l..l+len-1] 与后缀 text[r-len+1..r]
if (eq(text, l, r - len + 1, len)) {
ans += 2;
l += len;
r -= len;
found = true;
break;
}
len++;
}
if (!found) { // 整个剩余部分作为一段
ans++;
break;
}
}
return ans;
}
private boolean eq(String s, int i, int j, int len) {
for (int k = 0; k < len; k++) {
if (s.charAt(i + k) != s.charAt(j + k)) return false;
}
return true;
}
public int longestDecompositionRec(String text) {
return rec(text);
}
private int rec(String s) {
int n = s.length();
if (n == 0) return 0;
for (int k = 1; k <= n / 2; k++) {
String pre = s.substring(0, k);
String suf = s.substring(n - k);
if (pre.equals(suf)) {
return 2 + rec(s.substring(k, n - k));
}
}
return 1;
}
public static void main(String[] args) {
Solution s = new Solution();
String t1 = "ghiabcdefhelloadamhelloabcdefghi";
System.out.println("示例1 解法一: " + s.longestDecomposition(t1) + "(期望 7)");
System.out.println("示例1 解法二: " + s.longestDecompositionRec(t1) + "(期望 7)");
String t2 = "merchant";
System.out.println("示例2 解法一: " + s.longestDecomposition(t2) + "(期望 1)");
System.out.println("示例2 解法二: " + s.longestDecompositionRec(t2) + "(期望 1)");
String t3 = "antaprezatepzapreanta";
System.out.println("示例3 解法一: " + s.longestDecomposition(t3) + "(期望 11)");
System.out.println("示例3 解法二: " + s.longestDecompositionRec(t3) + "(期望 11)");
String t4 = "aaa";
System.out.println("aaa 解法一: " + s.longestDecomposition(t4) + "(期望 3)");
System.out.println("aaa 解法二: " + s.longestDecompositionRec(t4) + "(期望 3)");
}
}
复杂度分析
复杂度:时间 O(n^2)(每个位置可能比较 O(n) 子串),空间 O(1)(不含比较临时串的额外开销,substring 会复制)。
复杂度:时间 O(n^2)(枚举 + 比较),空间 O(n)(递归栈 + 缓存)。
1148. Article Views I(文章浏览 I)
题目类型:数据库(SQL)
题目描述
Views 表:
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| article_id | int |
| author_id | int |
| viewer_id | int |
| view_date | date |
+---------------+---------+
此表可能会存在重复行。(换句话说,在 SQL 中这个表没有主键)
此表的每一行都表示某人在某天浏览了某位作者的某篇文章。
请注意,同一人的 author_id 和 viewer_id 是相同的。
请查询出所有浏览过自己文章的作者。
结果按照作者的 id 升序排列。
查询结果的格式如下所示:
示例 1:
**输入:**
Views 表:
+------------+-----------+-----------+------------+
| article_id | author_id | viewer_id | view_date |
+------------+-----------+-----------+------------+
| 1 | 3 | 5 | 2019-08-01 |
| 1 | 3 | 6 | 2019-08-02 |
| 2 | 7 | 7 | 2019-08-01 |
| 2 | 7 | 6 | 2019-08-02 |
| 4 | 7 | 1 | 2019-07-22 |
| 3 | 4 | 4 | 2019-07-21 |
| 3 | 4 | 4 | 2019-07-21 |
+------------+-----------+-----------+------------+
**输出:**
+------+
| id |
+------+
| 4 |
| 7 |
+------+
解题思路
思考
作者阅读自己文章即
author_id = viewer_id。WHERE筛出这些行,DISTINCT去掉同一作者的重复浏览,再按 id 排序。
代码实现
sql
# Write your MySQL query statement below
SELECT DISTINCT author_id AS id
FROM Views
WHERE author_id = viewer_id
ORDER BY 1;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1149. Article Views II(文章浏览 II)
题目类型:数据库(SQL)
题目描述
表: Views
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| article_id | int |
| author_id | int |
| viewer_id | int |
| view_date | date |
+---------------+---------+
此表可能会存在重复行。
此表的每一行都表示某人在某天浏览了某位作者的某篇文章。
请注意,同一人的 author_id 和 viewer_id 是相同的。
编写解决方案来找出在同一天阅读至少两篇文章的人。
结果按照 id 升序排序。
结果的格式如下。
示例 1:
**输入:**
Views 表:
+------------+-----------+-----------+------------+
| article_id | author_id | viewer_id | view_date |
+------------+-----------+-----------+------------+
| 1 | 3 | 5 | 2019-08-01 |
| 3 | 4 | 5 | 2019-08-01 |
| 1 | 3 | 6 | 2019-08-02 |
| 2 | 7 | 7 | 2019-08-01 |
| 2 | 7 | 6 | 2019-08-02 |
| 4 | 7 | 1 | 2019-07-22 |
| 3 | 4 | 4 | 2019-07-21 |
| 3 | 4 | 4 | 2019-07-21 |
+------------+-----------+-----------+------------+
**输出:**
+------+
| id |
+------+
| 5 |
| 6 |
+------+
解题思路
思考
同一天浏览不少于两篇不同文章的人。按
viewer_id, view_date分组,HAVING COUNT(DISTINCT article_id) > 1即满足,外层再DISTINCT观看者以免跨日重复。
代码实现
sql
# Write your MySQL query statement below
SELECT DISTINCT viewer_id AS id
FROM Views
GROUP BY viewer_id, view_date
HAVING COUNT(DISTINCT article_id) > 1
ORDER BY 1;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1150. Check If a Number Is Majority Element in a Sorted Array(检查一个数是否在数组中占绝大多数)
题目描述
给出一个按非递减顺序排列的数组 nums,和一个目标数值 target。假如数组 nums 中绝大多数元素的数值都等于 target,
则返回 true,否则请返回 false。所谓占绝大多数,是指在长度为 N 的数组中出现必须超过 N/2 次。
示例 1
nums = [2,4,5,5,5,5,5,6,6], target = 5 → true(5 出现 5 次 > 9/2)
示例 2
nums = [10,100,101,101], target = 101 → false(2 次 = 4/2 不满足)
约束:
- 1 <=
nums.length<= 1000;1 <=nums[i]<= 10^9;1 <=target<= 10^9。
解题思路
解法一:二分查找下界与上界
- 数组有序,用二分找到第一个 >= target 的位置 first 和第一个 > target 的位置 last;
- target 出现次数 = last - first,判断是否 > n / 2。
复杂度:时间 O(log n),空间 O(1)。
解法二:线性扫描计数 - 直接遍历统计 target 出现次数;
- 判断 > n / 2。小数据下简单直观。
复杂度:时间 O(n),空间 O(1)。
代码实现
java
import java.util.*;
public class Solution {
public boolean isMajorityElement(int[] nums, int target) {
int n = nums.length;
int first = lowerBound(nums, target);
if (first == n || nums[first] != target) return false;
int last = upperBound(nums, target);
return (last - first) > n / 2;
}
private int lowerBound(int[] nums, int target) {
int lo = 0, hi = nums.length;
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (nums[mid] >= target) hi = mid;
else lo = mid + 1;
}
return lo;
}
private int upperBound(int[] nums, int target) {
int lo = 0, hi = nums.length;
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (nums[mid] > target) hi = mid;
else lo = mid + 1;
}
return lo;
}
public boolean isMajorityElementScan(int[] nums, int target) {
int cnt = 0;
for (int v : nums) if (v == target) cnt++;
return cnt > nums.length / 2;
}
public static void main(String[] args) {
Solution s = new Solution();
int[] a1 = {2,4,5,5,5,5,5,6,6};
System.out.println("示例1 解法一: " + s.isMajorityElement(a1, 5) + "(期望 true)");
System.out.println("示例1 解法二: " + s.isMajorityElementScan(a1, 5) + "(期望 true)");
int[] a2 = {10,100,101,101};
System.out.println("示例2 解法一: " + s.isMajorityElement(a2, 101) + "(期望 false)");
System.out.println("示例2 解法二: " + s.isMajorityElementScan(a2, 101) + "(期望 false)");
int[] a3 = {5,5,5};
System.out.println("全等 解法一: " + s.isMajorityElement(a3, 5) + "(期望 true)");
System.out.println("全等 解法二: " + s.isMajorityElementScan(a3, 5) + "(期望 true)");
}
}
复杂度分析
复杂度:时间 O(log n),空间 O(1)。
复杂度:时间 O(n),空间 O(1)。
1151. Minimum Swaps to Group All 1's Together(最少交换次数来组合所有的 1)
题目描述
给出一个二进制数组 data,你需要通过交换位置,将数组中任何位置上的 1 组合到一起,
并返回所有可能中所需最少的交换次数。
示例 1
data = [1,0,1,0,1] → 1
示例 2
data = [0,0,0,1,0] → 0
示例 3
data = [1,0,1,0,1,0,0,1,1,0,1] → 3
示例 4
data = [1,0,1,0,1,0,1,1,1,0,1,0,0,1,1,1,0,0,1,1,1,0,1,0,1,1,0,0,0,1,1,1,1,0,0,1] → 8
约束:
- 1 <=
data.length <= 10^5;data`i` == 0 或 1。
解题思路
解法一:滑动窗口
- 设一共有 ones 个 1,最终所有 1 必然聚集在某个长度为 ones 的连续窗口中;
- 对每个长度 ones 的窗口,需要交换的次数 = 窗口内 0 的个数(把 0 换成窗口外的 1);
- 滑动窗口维护窗口内 1 的个数,取 max(窗口内 1 个数),答案 = ones - max;
(窗口内 1 越多,需要换进来的 0 越少)
复杂度:时间 O(n),空间 O(1)。
解法二:前缀和 - 前缀和数组统计 1 的个数;
- 枚举窗口起点 i,窗口 i, i+ones-1 内 0 的个数 = ones - (prei+ones - prei),取最小值。
复杂度:时间 O(n),空间 O(n)。
代码实现
java
import java.util.*;
public class Solution {
public int minSwaps(int[] data) {
int ones = 0;
for (int v : data) ones += v;
if (ones <= 1) return 0;
int n = data.length;
int curOnes = 0;
for (int i = 0; i < ones; i++) curOnes += data[i];
int maxOnes = curOnes;
for (int i = ones; i < n; i++) {
curOnes += data[i] - data[i - ones];
maxOnes = Math.max(maxOnes, curOnes);
}
return ones - maxOnes;
}
public int minSwapsPrefix(int[] data) {
int n = data.length;
int ones = 0;
for (int v : data) ones += v;
if (ones <= 1) return 0;
int[] pre = new int[n + 1];
for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + data[i - 1];
int best = ones;
for (int i = 0; i + ones <= n; i++) {
int inOnes = pre[i + ones] - pre[i];
best = Math.min(best, ones - inOnes);
}
return best;
}
public static void main(String[] args) {
Solution s = new Solution();
int[] d1 = {1,0,1,0,1};
System.out.println("示例1 解法一: " + s.minSwaps(d1) + "(期望 1)");
System.out.println("示例1 解法二: " + s.minSwapsPrefix(d1) + "(期望 1)");
int[] d2 = {0,0,0,1,0};
System.out.println("示例2 解法一: " + s.minSwaps(d2) + "(期望 0)");
System.out.println("示例2 解法二: " + s.minSwapsPrefix(d2) + "(期望 0)");
int[] d3 = {1,0,1,0,1,0,0,1,1,0,1};
System.out.println("示例3 解法一: " + s.minSwaps(d3) + "(期望 3)");
System.out.println("示例3 解法二: " + s.minSwapsPrefix(d3) + "(期望 3)");
}
}
复杂度分析
复杂度:时间 O(n),空间 O(1)。
复杂度:时间 O(n),空间 O(n)。
1152. Analyze User Website Visit Pattern(用户网站访问行为分析)
题目描述
给定两个字符串数组 username 和 website 和一个整数数组 timestamp。给定的数组长度相同,
其中元组 username\[`i`, website`i`, timestamp[i]] 表示用户 username`i` 在时间 timestamp[i] 访问了网站 website`i`。
访问模式是包含三个网站的列表(不一定是完全不同的)。
一种访问模式的得分是访问该模式中所有网站的用户数量,这些网站在该模式中出现的顺序相同。
返回得分最大的访问模式。如果有多个访问模式具有相同的最大分数,则返回字典序最小的。
注意,模式中的网站不需要连续访问,只需按照模式中出现的顺序访问即可。
示例 1
username = ["joe","joe","joe","james","james","james","james","mary","mary","mary"],
timestamp = [1,2,3,4,5,6,7,8,9,10],
website = ["home","about","career","home","cart","maps","home","home","about","career"]
→ ["home","about","career"]
示例 2
username = ["ua","ua","ua","ub","ub","ub"], timestamp = [1,2,3,4,5,6],
website = ["a","b","a","a","b","c"] → ["a","b","a"]
约束:
- 3 <= username.length <= 50;
timestamp/website 数组等长;所有元组不重复; - 保证至少有一个用户访问了至少三个网站。
解题思路
解法一:用户分组 + 组合去重计数
- 按用户名分组,组内按 timestamp 升序排序网站访问序列;
- 对每个用户,从访问序列中枚举所有三元组 (i<j<k) 作为该用户访问过的模式(用 Set 去重------
同一用户对同一模式只计一次分); - 用 Map<模式, 分数> 累加各用户贡献,最后取分数最大、字典序最小的模式。
复杂度:时间 O(U * V^3)(实际数据量很小,U 为人数 V 为人均访问数)。
解法二:全局排序后按用户收集 - 先把 (username, website, timestamp) 三元组按时间全局排序;
- 再按用户分组收集按时间有序的网站列表;
- 与解法一相同地枚举三元组并计数,取最优模式。
复杂度:时间 O(N log N + U*V^3),空间 O(N)。
代码实现
java
import java.util.*;
public class Solution {
public List<String> mostVisitedPattern(String[] username, int[] timestamp, String[] website) {
int n = username.length;
// 按用户分组收集 (时间, 网站)
Map<String, List<int[]>> byUser = new HashMap<>();
for (int i = 0; i < n; i++) {
byUser.computeIfAbsent(username[i], k -> new ArrayList<>())
.add(new int[]{timestamp[i], i});
}
Map<String, Integer> score = new HashMap<>();
for (List<int[]> visits : byUser.values()) {
visits.sort((a, b) -> a[0] - b[0]); // 按时间升序
int m = visits.size();
if (m < 3) continue;
Set<String> seen = new HashSet<>(); // 同一用户对同一模式去重
for (int i = 0; i < m; i++) {
for (int j = i + 1; j < m; j++) {
for (int k = j + 1; k < m; k++) {
String pat = website[visits.get(i)[1]] + "#"
+ website[visits.get(j)[1]] + "#"
+ website[visits.get(k)[1]];
if (seen.add(pat)) {
score.put(pat, score.getOrDefault(pat, 0) + 1);
}
}
}
}
}
String best = null;
int bestScore = -1;
for (Map.Entry<String, Integer> e : score.entrySet()) {
if (e.getValue() > bestScore
|| (e.getValue() == bestScore && e.getKey().compareTo(best) < 0)) {
best = e.getKey();
bestScore = e.getValue();
}
}
String[] parts = best.split("#");
return Arrays.asList(parts);
}
public List<String> mostVisitedPatternSorted(String[] username, int[] timestamp, String[] website) {
int n = username.length;
Integer[] idx = new Integer[n];
for (int i = 0; i < n; i++) idx[i] = i;
Arrays.sort(idx, (a, b) -> {
if (username[a].equals(username[b])) return timestamp[a] - timestamp[b];
return username[a].compareTo(username[b]);
});
Map<String, List<String>> seq = new LinkedHashMap<>();
for (int i : idx) {
seq.computeIfAbsent(username[i], k -> new ArrayList<>()).add(website[i]);
}
Map<String, Integer> score = new HashMap<>();
for (List<String> ws : seq.values()) {
int m = ws.size();
if (m < 3) continue;
Set<String> seen = new HashSet<>();
for (int i = 0; i < m; i++) {
for (int j = i + 1; j < m; j++) {
for (int k = j + 1; k < m; k++) {
String pat = ws.get(i) + "#" + ws.get(j) + "#" + ws.get(k);
if (seen.add(pat)) {
score.put(pat, score.getOrDefault(pat, 0) + 1);
}
}
}
}
}
String best = null;
int bestScore = -1;
for (Map.Entry<String, Integer> e : score.entrySet()) {
if (e.getValue() > bestScore
|| (e.getValue() == bestScore && e.getKey().compareTo(best) < 0)) {
best = e.getKey();
bestScore = e.getValue();
}
}
return Arrays.asList(best.split("#"));
}
public static void main(String[] args) {
Solution s = new Solution();
String[] u1 = {"joe","joe","joe","james","james","james","james","mary","mary","mary"};
int[] t1 = {1,2,3,4,5,6,7,8,9,10};
String[] w1 = {"home","about","career","home","cart","maps","home","home","about","career"};
System.out.println("示例1 解法一: " + s.mostVisitedPattern(u1, t1, w1) + "(期望 [home, about, career])");
System.out.println("示例1 解法二: " + s.mostVisitedPatternSorted(u1, t1, w1) + "(期望 [home, about, career])");
String[] u2 = {"ua","ua","ua","ub","ub","ub"};
int[] t2 = {1,2,3,4,5,6};
String[] w2 = {"a","b","a","a","b","c"};
System.out.println("示例2 解法一: " + s.mostVisitedPattern(u2, t2, w2) + "(期望 [a, b, a])");
System.out.println("示例2 解法二: " + s.mostVisitedPatternSorted(u2, t2, w2) + "(期望 [a, b, a])");
}
}
复杂度分析
复杂度:时间 O(U * V^3)(实际数据量很小,U 为人数 V 为人均访问数)。
复杂度:时间 O(N log N + U*V^3),空间 O(N)。
1153. String Transforms Into Another String(字符串转化)
题目描述
给出两个长度相同的字符串 str1 和 str2。请你帮忙判断字符串 str1 能不能在零次或多次转化后变成字符串 str2。
每一次转化时,你可以将 str1 中出现的所有相同字母变成其他任何小写英文字母。
只有在字符串 str1 能够通过上述方式顺利转化为字符串 str2 时才能返回 true。
示例 1
str1 = "aabcc", str2 = "ccdee" → true('c'→'e',然后 'b'→'d',最后 'a'→'c')
示例 2
str1 = "leetcode", str2 = "codeleet" → false
约束:
- 1 <= str1.length == str2.length <= 10^4;str1 和 str2 中都只会出现小写英文字母。
解题思路
解法一:映射检查 + 26 字母换空间
- 建立映射 map26,str1 中每个字符必须映射到唯一的字符:若已有映射且与当前要求不符则 false;
- 若 str2 使用了全部 26 个小写字母(即 str1 的 26 个字符映射成 26 个不同字符),
则不存在可转换的"中转"字母,会死锁------此时若 str1 != str2 则 false;
(因为任何单步替换都会连环破坏其他映射,但若 str1 == str2 则零次转化即可) - 特殊判断:只有当 str2 包含全部 26 种字母且存在映射冲突(str1 != str2)时才无解。
更准确的充要条件:str1 中每个字符映射唯一,且若 str2 中 26 个字母全部出现并且
str1 的 26 个字符也全部不同(即 str1 无重复字符映射到多个)......
标准结论:检查映射一致性 + 特殊情况「str2 包含 26 个不同字母且 str1 != str2 时为 false」。
复杂度:时间 O(n),空间 O(26)。
解法二:并查集建模 - 每个小写字母是一个节点,str1i → str2i 建立"转化边";
- 若 str1i 已被映射到别的字符而出现冲突 → false;
- 检查图中是否存在自环长度>1 的环导致无中转字符:若某个连通分量是单纯环且覆盖全部 26 节点,
则无法转化(除非 str1 == str2);实现上用「str2 用满 26 字母且 str1 != str2」特判即可。
复杂度:时间 O(n),空间 O(26)。
代码实现
java
import java.util.*;
public class Solution {
public boolean canConvert(String str1, String str2) {
if (str1.equals(str2)) return true;
int n = str1.length();
int[] map = new int[26];
Arrays.fill(map, -1);
boolean[] usedInStr2 = new boolean[26];
int distinct2 = 0;
for (int i = 0; i < n; i++) {
int a = str1.charAt(i) - 'a';
int b = str2.charAt(i) - 'a';
if (map[a] == -1) {
map[a] = b;
} else if (map[a] != b) {
return false; // 同一字符被要求映射到不同字符
}
if (!usedInStr2[b]) {
usedInStr2[b] = true;
distinct2++;
}
}
// str2 若包含了全部 26 个字母,则没有空闲字母做"中转",且 str1 != str2 时必然死锁
return distinct2 < 26;
}
public boolean canConvertUnion(int[] map, String str1, String str2) {
// 此处演示不带并查集的等价判据,避免过度设计;实际用与方法一相同的映射判据
return canConvert(str1, str2);
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.canConvert("aabcc", "ccdee") + "(期望 true)");
System.out.println("示例2 解法一: " + s.canConvert("leetcode", "codeleet") + "(期望 false)");
// 附加用例
System.out.println("相同串 解法一: " + s.canConvert("abc", "abc") + "(期望 true)");
System.out.println("全26死锁 解法一: " + s.canConvert("abcdefghijklmnopqrstuvwxyz",
"bcdefghijklmnopqrstuvwxyza") + "(期望 false)");
System.out.println("26->26但str2不全: " + s.canConvert(
"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",
"bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyza") + "(期望 false)");
}
}
复杂度分析
复杂度:时间 O(n),空间 O(26)。
1154. Day of the Year(一年中的第几天)
题目描述
给你一个字符串 date,按 YYYY-MM-DD 格式表示一个现行公元纪年法日期。返回该日期是当年的第几天。
示例 1
date = "2019-01-09" → 9
示例 2
date = "2019-02-10" → 41
约束:
date.length == 10;date4 ==date7 == '-';其他字符都是数字;date表示的范围从 1900 年 1 月 1 日至 2019 年 12 月 31 日。
解题思路
解法一:查表 + 闰年判断
- 解析出年、月、日;
- 累加前 month-1 个月的天数,加上日;二月按闰年调整(能被 400 整除或能被 4 整除但不能被 100 整除)。
复杂度:时间 O(1),空间 O(1)。
解法二:LocalDate 日期库 - 用 LocalDate.parse 解析,getDayOfYear() 直接得到当年第几天;
- 代码最简,适合展示标准库用法。
复杂度:时间 O(1),空间 O(1)。
代码实现
java
public class Solution {
private static final int[] DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
public int dayOfYear(String date) {
int y = Integer.parseInt(date.substring(0, 4));
int m = Integer.parseInt(date.substring(5, 7));
int d = Integer.parseInt(date.substring(8, 10));
int ans = 0;
for (int i = 0; i < m - 1; i++) ans += DAYS[i];
if (m > 2 && isLeap(y)) ans++;
return ans + d;
}
private boolean isLeap(int y) {
return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0);
}
public int dayOfYearLibrary(String date) {
return java.time.LocalDate.parse(date).getDayOfYear();
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.dayOfYear("2019-01-09") + "(期望 9)");
System.out.println("示例1 解法二: " + s.dayOfYearLibrary("2019-01-09") + "(期望 9)");
System.out.println("示例2 解法一: " + s.dayOfYear("2019-02-10") + "(期望 41)");
System.out.println("示例2 解法二: " + s.dayOfYearLibrary("2019-02-10") + "(期望 41)");
System.out.println("闰年 解法一: " + s.dayOfYear("2008-03-01") + "(期望 61)");
System.out.println("闰年 解法二: " + s.dayOfYearLibrary("2008-03-01") + "(期望 61)");
System.out.println("平年 解法一: " + s.dayOfYear("2007-03-01") + "(期望 60)");
System.out.println("平年 解法二: " + s.dayOfYearLibrary("2007-03-01") + "(期望 60)");
}
}
复杂度分析
复杂度:时间 O(1),空间 O(1)。
1155. Number of Dice Rolls With Target Sum(掷骰子等于目标和的方法数)
题目描述
这里有 n 个一样的骰子,每个骰子上都有 k 个面,分别标号为 1 到 k。
给定三个整数 n、k 和 target,请返回投掷骰子的所有可能得到的结果(共有 k^n 种方式),
使得骰子面朝上的数字总和等于 target。由于答案可能很大,需要对 10^9 + 7 取模。
示例 1
n = 1, k = 6, target = 3 → 1
示例 2
n = 2, k = 6, target = 7 → 6
示例 3
n = 30, k = 30, target = 500 → 222616187
约束:
- 1 <=
n,k<= 30;1 <=target<= 1000。
解题思路
解法一:二维动态规划
- dpis 表示投掷前 i 个骰子,总和为 s 的方法数;
- 转移:dpis = sum(dpi-1s-f) for f in 1...k;
- 边界 dp00 = 1;最终 dpntarget。
复杂度:时间 O(n * target * k),空间 O(n * target)(可滚动优化到 O(target))。
解法二:滚动数组优化 + 前缀和加速 - 只用一维数组滚动;对每个 i 重新计算新 dp;
- 用滑动窗口/前缀和把内层 k 求和降为 O(1):
对当前层,维护长度 k 的窗口和即可。
复杂度:时间 O(n * target),空间 O(target)。
代码实现
java
public class Solution {
private static final int MOD = 1_000_000_007;
public int numRollsToTarget(int n, int k, int target) {
int[][] dp = new int[n + 1][target + 1];
dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int s = 0; s <= target; s++) {
for (int f = 1; f <= k; f++) {
if (s >= f) {
dp[i][s] = (int) ((dp[i][s] + (long) dp[i - 1][s - f]) % MOD);
}
}
}
}
return dp[n][target];
}
public int numRollsToTargetRolling(int n, int k, int target) {
int[] dp = new int[target + 1];
dp[0] = 1;
for (int i = 1; i <= n; i++) {
int[] ndp = new int[target + 1];
long window = 0;
// ndp[s] = sum_{f=1..k} dp_prev[s-f],即窗口 [s-k, s-1] 内 dp 之和
for (int s = 1; s <= target; s++) {
window += dp[s - 1]; // 新元素 dp[s-1] 进入窗口
if (s - k - 1 >= 0) window -= dp[s - k - 1]; // 滑出窗口外元素
window = (window % MOD + MOD) % MOD;
ndp[s] = (int) window;
}
dp = ndp;
}
return dp[target];
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.numRollsToTarget(1, 6, 3) + "(期望 1)");
System.out.println("示例1 解法二: " + s.numRollsToTargetRolling(1, 6, 3) + "(期望 1)");
System.out.println("示例2 解法一: " + s.numRollsToTarget(2, 6, 7) + "(期望 6)");
System.out.println("示例2 解法二: " + s.numRollsToTargetRolling(2, 6, 7) + "(期望 6)");
System.out.println("示例3 解法一: " + s.numRollsToTarget(30, 30, 500) + "(期望 222616187)");
System.out.println("示例3 解法二: " + s.numRollsToTargetRolling(30, 30, 500) + "(期望 222616187)");
}
}
复杂度分析
复杂度:时间 O(n * target * k),空间 O(n * target)(可滚动优化到 O(target))。
复杂度:时间 O(n * target),空间 O(target)。
1156. Swap For Longest Repeated Character Substring(单字符重复子串的最大长度)
题目描述
如果字符串中的所有字符都相同,那么这个字符串是单字符重复的字符串。
给你一个字符串 text,你只能交换其中两个字符一次或者什么都不做,然后得到一些单字符重复的子串。
返回其中最长的子串的长度。
示例 1
text = "ababa" → 3
示例 2
text = "aaabaaa" → 6
示例 3
text = "aaabbaaa" → 4
示例 4
text = "aaaaa" → 5
示例 5
text = "abcdef" → 1
约束:
- 1 <=
text.length <= 20000;text仅由小写英文字母组成。
解题思路
解法一:分组连续段 + 合并相邻端
- 把 text 压缩为连续同字符段:seg0 为段长度、seg1 为字符;
- 对每个字符 c:
- 若总出现次数 totalc,单个最长连续段 len 即为候选(无需交换);
- 若存在两个相邻段且中间隔一个其他字符(如 aabaa 的 a-a 段),且 totalc > 两段和,
则可从别处换一个 c 来连通:候选 = 两段和 + 1;否则候选 = 两段和;
- 取全局最大。
复杂度:时间 O(n),空间 O(n)。
解法二:滑动窗口 + 频率统计(最多一个非 c 字符) - 对每个字符 c,滑动窗口保持窗口内最多 1 个非 c 字符,且窗口外至少还有一个 c 可换入;
- 记录窗口内 c 的个数,那些 c 紧邻段长度即候选;维护最大。
- 实现简洁但需特判交换来源;这里用解法一的贪心更清晰,解法二给出另一种等价扫描。
复杂度:时间 O(26 * n),空间 O(1)。
代码实现
java
import java.util.*;
public class Solution {
public int maxRepOpt1(String text) {
int n = text.length();
int[] total = new int[26];
for (int i = 0; i < n; i++) total[text.charAt(i) - 'a']++;
// 压缩连续段
List<int[]> segs = new ArrayList<>();
int i = 0;
while (i < n) {
int j = i;
while (j < n && text.charAt(j) == text.charAt(i)) j++;
segs.add(new int[]{j - i, text.charAt(i) - 'a'});
i = j;
}
int ans = 0;
for (int s = 0; s < segs.size(); s++) {
int[] cur = segs.get(s);
int len = cur[0], ch = cur[1];
// 不交换:最长连续段,但不能超过总数
ans = Math.max(ans, Math.min(len + (s > 0 || s < segs.size() - 1 ? 1 : 0) - 0, total[ch]));
// 更准确:len 本身,或与相邻相同字符段通过一次交换连通
int cand = len;
if (total[ch] > len) cand = len + 1; // 窗口外还有同字符可换入
ans = Math.max(ans, cand);
// 中间隔一个字符的两个同类段(如 a b a 中间一个 b)
if (s + 2 < segs.size()
&& segs.get(s + 2)[1] == ch
&& segs.get(s + 1)[0] == 1) {
int merged = len + segs.get(s + 2)[0]; // 两段和
if (total[ch] > merged) merged++; // 别处还有一个 c 可换到中间
ans = Math.max(ans, merged);
}
}
return ans == 0 ? 1 : ans; // n >= 1,至少 1
}
public int maxRepOpt1Sliding(String text) {
int n = text.length();
int[] total = new int[26];
for (int i = 0; i < n; i++) total[text.charAt(i) - 'a']++;
int ans = 1;
for (int c = 0; c < 26; c++) {
int ch = (char) ('a' + c);
int l = 0, other = 0, cnt = 0;
for (int r = 0; r < n; r++) {
if (text.charAt(r) == ch) cnt++;
else other++;
while (other > 1) { // 窗口内至多 1 个非 c
if (text.charAt(l) == ch) cnt--;
else other--;
l++;
}
// 若窗口外还有 c,则可以多换 1 个进来
if (total[c] > cnt) ans = Math.max(ans, cnt + 1);
else ans = Math.max(ans, cnt);
}
}
return ans;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.maxRepOpt1("ababa") + "(期望 3)");
System.out.println("示例1 解法二: " + s.maxRepOpt1Sliding("ababa") + "(期望 3)");
System.out.println("示例2 解法一: " + s.maxRepOpt1("aaabaaa") + "(期望 6)");
System.out.println("示例2 解法二: " + s.maxRepOpt1Sliding("aaabaaa") + "(期望 6)");
System.out.println("示例3 解法一: " + s.maxRepOpt1("aaabbaaa") + "(期望 4)");
System.out.println("示例3 解法二: " + s.maxRepOpt1Sliding("aaabbaaa") + "(期望 4)");
System.out.println("示例4 解法一: " + s.maxRepOpt1("aaaaa") + "(期望 5)");
System.out.println("示例4 解法二: " + s.maxRepOpt1Sliding("aaaaa") + "(期望 5)");
System.out.println("示例5 解法一: " + s.maxRepOpt1("abcdef") + "(期望 1)");
System.out.println("示例5 解法二: " + s.maxRepOpt1Sliding("abcdef") + "(期望 1)");
}
}
复杂度分析
复杂度:时间 O(n),空间 O(n)。
复杂度:时间 O(26 * n),空间 O(1)。
1157. Online Majority Element In Subarray(子数组中占绝大多数的元素)
题目描述
设计一个数据结构,有效地找到给定子数组的多数元素。
子数组的多数元素是在子数组中出现 threshold 次数或次数以上的元素。
实现 MajorityChecker 类:
- MajorityChecker(
int[]arr) 会用给定的数组arr对 MajorityChecker 初始化。 intquery(intleft,intright,intthreshold) 返回子数组arr`left`...`right` 中至少出现 threshold 次的元素,
如果不存在这样的元素则返回 -1。
示例
["MajorityChecker", "query", "query", "query"]
[[[1, 1, 2, 2, 1, 1]], [0, 5, 4], [0, 3, 3], [2, 3, 2]]
- 输出 :`null`, 1, -1, 2
约束: - 1 <=
arr.length<= 210^4;1 <=arr[i]<= 2 10^4;0 <=left<=right<arr.length; - threshold <=
right-left+1;2*threshold >right-left+1;query 调用最多 10^4。
解题思路
解法一:线段树摩尔投票 + 位置表二分验证
- 关键性质:由于 2*threshold > 区间长度,区间内最多只有一个元素的出现次数 >= threshold;
且若存在,该元素必然是该区间的「多数元素」(摩尔投票候选); - 线段树每个节点维护 (candidate, count):该区间摩尔投票后剩余的候选及其净票数,
合并规则:若两段候选相同则票数相加,否则取票数大的候选并相减; - query 时线段树区间查询得到候选值,再用该值的位置列表二分得到区间内真实出现次数,
若 >= threshold 则返回,否则 -1。
复杂度:建树 O(n),query 时间 O(log n)(线段树)+ O(log n)(二分)= O(log n);空间 O(n)。
解法二:随机采样候选 + 位置表验证 - 区间长度 len,若存在多数元素则随机取一个下标,命中该元素的概率 > 1/2;
- 采样 20 次均未命中的概率 < 2^-20,可忽略;
- 每个候选用位置列表二分验证真实出现次数是否 >= threshold。
复杂度:query 时间 O(k * log n)(k 为采样次数,k=20),空间 O(n)。
代码实现
java
import java.util.*;
public class Solution {
public static class MajorityChecker {
private int[] arr;
private int n;
private Map<Integer, List<Integer>> pos;
private int[] treeCand;
private int[] treeCnt;
public MajorityChecker(int[] arr) {
this.arr = arr;
this.n = arr.length;
this.pos = new HashMap<>();
for (int i = 0; i < n; i++) {
pos.computeIfAbsent(arr[i], k -> new ArrayList<>()).add(i);
}
treeCand = new int[4 * n];
treeCnt = new int[4 * n];
build(1, 0, n - 1);
}
private void build(int node, int l, int r) {
if (l == r) {
treeCand[node] = arr[l];
treeCnt[node] = 1;
return;
}
int mid = (l + r) >>> 1;
build(node * 2, l, mid);
build(node * 2 + 1, mid + 1, r);
// 摩尔投票合并
if (treeCand[node * 2] == treeCand[node * 2 + 1]) {
treeCand[node] = treeCand[node * 2];
treeCnt[node] = treeCnt[node * 2] + treeCnt[node * 2 + 1];
} else if (treeCnt[node * 2] >= treeCnt[node * 2 + 1]) {
treeCand[node] = treeCand[node * 2];
treeCnt[node] = treeCnt[node * 2] - treeCnt[node * 2 + 1];
} else {
treeCand[node] = treeCand[node * 2 + 1];
treeCnt[node] = treeCnt[node * 2 + 1] - treeCnt[node * 2];
}
}
public int query(int left, int right, int threshold) {
int[] res = queryTree(1, 0, n - 1, left, right);
int cand = res[0];
if (cand == -1) return -1;
List<Integer> list = pos.get(cand);
int lo = lowerBound(list, left), hi = upperBound(list, right);
return (hi - lo) >= threshold ? cand : -1;
}
private int[] queryTree(int node, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) {
return new int[]{treeCand[node], treeCnt[node]};
}
int mid = (l + r) >>> 1;
if (qr <= mid) return queryTree(node * 2, l, mid, ql, qr);
if (ql > mid) return queryTree(node * 2 + 1, mid + 1, r, ql, qr);
int[] a = queryTree(node * 2, l, mid, ql, qr);
int[] b = queryTree(node * 2 + 1, mid + 1, r, ql, qr);
// 合并
if (a[0] == -1) return b;
if (b[0] == -1) return a;
if (a[0] == b[0]) return new int[]{a[0], a[1] + b[1]};
if (a[1] >= b[1]) return new int[]{a[0], a[1] - b[1]};
return new int[]{b[0], b[1] - a[1]};
}
private int lowerBound(List<Integer> list, int target) {
int lo = 0, hi = list.size();
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (list.get(mid) >= target) hi = mid;
else lo = mid + 1;
}
return lo;
}
private int upperBound(List<Integer> list, int target) {
int lo = 0, hi = list.size();
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (list.get(mid) > target) hi = mid;
else lo = mid + 1;
}
return lo;
}
}
public static class MajorityCheckerRandom {
private int[] arr;
private Map<Integer, List<Integer>> pos;
private java.util.Random rand = new java.util.Random();
public MajorityCheckerRandom(int[] arr) {
this.arr = arr;
this.pos = new HashMap<>();
for (int i = 0; i < arr.length; i++) {
pos.computeIfAbsent(arr[i], k -> new ArrayList<>()).add(i);
}
}
public int query(int left, int right, int threshold) {
int len = right - left + 1;
for (int t = 0; t < 20; t++) {
int idx = left + rand.nextInt(len);
int cand = arr[idx];
List<Integer> list = pos.get(cand);
int lo = lowerBound(list, left), hi = upperBound(list, right);
if (hi - lo >= threshold) return cand;
}
return -1;
}
private int lowerBound(List<Integer> list, int target) {
int lo = 0, hi = list.size();
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (list.get(mid) >= target) hi = mid;
else lo = mid + 1;
}
return lo;
}
private int upperBound(List<Integer> list, int target) {
int lo = 0, hi = list.size();
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (list.get(mid) > target) hi = mid;
else lo = mid + 1;
}
return lo;
}
}
public static void main(String[] args) {
// 示例
MajorityChecker mc = new MajorityChecker(new int[]{1,1,2,2,1,1});
System.out.println("示例 解法一 query(0,5,4): " + mc.query(0, 5, 4) + "(期望 1)");
System.out.println("示例 解法一 query(0,3,3): " + mc.query(0, 3, 3) + "(期望 -1)");
System.out.println("示例 解法一 query(2,3,2): " + mc.query(2, 3, 2) + "(期望 2)");
MajorityCheckerRandom mr = new MajorityCheckerRandom(new int[]{1,1,2,2,1,1});
System.out.println("示例 解法二 query(0,5,4): " + mr.query(0, 5, 4) + "(期望 1)");
System.out.println("示例 解法二 query(0,3,3): " + mr.query(0, 3, 3) + "(期望 -1)");
System.out.println("示例 解法二 query(2,3,2): " + mr.query(2, 3, 2) + "(期望 2)");
}
}
复杂度分析
复杂度:建树 O(n),query 时间 O(log n)(线段树)+ O(log n)(二分)= O(log n);空间 O(n)。
复杂度:query 时间 O(k * log n)(k 为采样次数,k=20),空间 O(n)。
1158. Market Analysis I(市场分析 I)
题目类型:数据库(SQL)
题目描述
表: Users
+----------------+---------+
| Column Name | Type |
+----------------+---------+
| user_id | int |
| join_date | date |
| favorite_brand | varchar |
+----------------+---------+
user_id 是此表主键(具有唯一值的列)。
表中描述了购物网站的用户信息,用户可以在此网站上进行商品买卖。
表: Orders
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| order_id | int |
| order_date | date |
| item_id | int |
| buyer_id | int |
| seller_id | int |
+---------------+---------+
order_id 是此表主键(具有唯一值的列)。
item_id 是 Items 表的外键(reference 列)。
(buyer_id,seller_id)是 User 表的外键。
表:Items
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| item_id | int |
| item_brand | varchar |
+---------------+---------+
item_id 是此表的主键(具有唯一值的列)。
编写解决方案找出每个用户的注册日期和在 **2019 **年作为买家的订单总数。
以 任意顺序 返回结果表。
查询结果格式如下。
示例 1:
**输入:**
Users 表:
+---------+------------+----------------+
| user_id | join_date | favorite_brand |
+---------+------------+----------------+
| 1 | 2018-01-01 | Lenovo |
| 2 | 2018-02-09 | Samsung |
| 3 | 2018-01-19 | LG |
| 4 | 2018-05-21 | HP |
+---------+------------+----------------+
Orders 表:
+----------+------------+---------+----------+-----------+
| order_id | order_date | item_id | buyer_id | seller_id |
+----------+------------+---------+----------+-----------+
| 1 | 2019-08-01 | 4 | 1 | 2 |
| 2 | 2018-08-02 | 2 | 1 | 3 |
| 3 | 2019-08-03 | 3 | 2 | 3 |
| 4 | 2018-08-04 | 1 | 4 | 2 |
| 5 | 2018-08-04 | 1 | 3 | 4 |
| 6 | 2019-08-05 | 2 | 2 | 4 |
+----------+------------+---------+----------+-----------+
Items 表:
+---------+------------+
| item_id | item_brand |
+---------+------------+
| 1 | Samsung |
| 2 | Lenovo |
| 3 | LG |
| 4 | HP |
+---------+------------+
**输出:**
+-----------+------------+----------------+
| buyer_id | join_date | orders_in_2019 |
+-----------+------------+----------------+
| 1 | 2018-01-01 | 1 |
| 2 | 2018-02-09 | 2 |
| 3 | 2018-01-19 | 0 |
| 4 | 2018-05-21 | 0 |
+-----------+------------+----------------+
解题思路
思考
每位用户都要输出,即使 2019 2019 2019 年没有订单。
Users左连「买家且年份为 2019 2019 2019」的Orders,再按用户分组计数;无匹配订单的用户计数为 0 0 0。
代码实现
sql
# Write your MySQL query statement below
SELECT
u.user_id AS buyer_id,
u.join_date,
COUNT(order_id) AS orders_in_2019
FROM
Users AS u
LEFT JOIN Orders AS o ON u.user_id = o.buyer_id AND YEAR(order_date) = 2019
GROUP BY user_id;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1159. Market Analysis II(市场分析 II)
题目类型:数据库(SQL)
题目描述
表: Users
+----------------+---------+
| Column Name | Type |
+----------------+---------+
| user_id | int |
| join_date | date |
| favorite_brand | varchar |
+----------------+---------+
user_id 是该表的主键(具有唯一值的列)。
表中包含一位在线购物网站用户的个人信息,用户可以在该网站出售和购买商品。
表: Orders
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| order_id | int |
| order_date | date |
| item_id | int |
| buyer_id | int |
| seller_id | int |
+---------------+---------+
order_id 是该表的主键(具有唯一值的列)。
item_id 是 Items 表的外键(reference 列)。
buyer_id 和 seller_id 是 Users 表的外键。
表: Items
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| item_id | int |
| item_brand | varchar |
+---------------+---------+
item_id 是该表的主键(具有唯一值的列)。
编写一个解决方案,为每个用户找出他们出售的第二件商品(按日期)的品牌是否是他们最喜欢的品牌。如果用户售出的商品少于两件,则该用户的结果为否。保证卖家不会在一天内卖出一件以上的商品。
以 任意顺序 返回结果表。
返回结果格式如下例所示:
示例 1:
**输入:**
Users table:
+---------+------------+----------------+
| user_id | join_date | favorite_brand |
+---------+------------+----------------+
| 1 | 2019-01-01 | Lenovo |
| 2 | 2019-02-09 | Samsung |
| 3 | 2019-01-19 | LG |
| 4 | 2019-05-21 | HP |
+---------+------------+----------------+
Orders table:
+----------+------------+---------+----------+-----------+
| order_id | order_date | item_id | buyer_id | seller_id |
+----------+------------+---------+----------+-----------+
| 1 | 2019-08-01 | 4 | 1 | 2 |
| 2 | 2019-08-02 | 2 | 1 | 3 |
| 3 | 2019-08-03 | 3 | 2 | 3 |
| 4 | 2019-08-04 | 1 | 4 | 2 |
| 5 | 2019-08-04 | 1 | 3 | 4 |
| 6 | 2019-08-05 | 2 | 2 | 4 |
+----------+------------+---------+----------+-----------+
Items table:
+---------+------------+
| item_id | item_brand |
+---------+------------+
| 1 | Samsung |
| 2 | Lenovo |
| 3 | LG |
| 4 | HP |
+---------+------------+
**输出:**
+-----------+--------------------+
| seller_id | 2nd_item_fav_brand |
+-----------+--------------------+
| 1 | no |
| 2 | yes |
| 3 | yes |
| 4 | no |
+-----------+--------------------+
**解释:**
id 为 1 的用户的查询结果是 no,因为他什么也没有卖出
id为 2 和 3 的用户的查询结果是 yes,因为他们卖出的第二件商品的品牌是他们最喜爱的品牌
id为 4 的用户的查询结果是 no,因为他卖出的第二件商品的品牌不是他最喜爱的品牌
解题思路
思考
比较每位卖家第二笔订单的商品品牌是否为其偏爱品牌。对
orders按卖家、order_date排名,取 r k = 2 rk=2 rk=2 的行左连用户与商品;无第二笔订单则为no。
代码实现
sql
# Write your MySQL query statement below
SELECT
u.user_id AS seller_id,
CASE
WHEN u.favorite_brand = i.item_brand THEN 'yes'
ELSE 'no'
END AS 2nd_item_fav_brand
FROM
users AS u
LEFT JOIN (
SELECT
order_date,
item_id,
seller_id,
RANK() OVER (
PARTITION BY seller_id
ORDER BY order_date
) AS rk
FROM orders
) AS o
ON u.user_id = o.seller_id AND o.rk = 2
LEFT JOIN items AS i ON o.item_id = i.item_id;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1160. Find Words That Can Be Formed by Characters(拼写单词)
题目描述
给定一个字符串数组 words 和一个字符串 chars。
如果字符串可以由 chars 中的字符组成(每个字符在每个 words 中只能使用一次),则认为它是好的。
返回 words 中所有好的字符串的长度之和。
示例 1
words = ["cat","bt","hat","tree"], chars = "atach" → 6("cat" 与 "hat" 可拼出)
示例 2
words = ["hello","world","leetcode"], chars = "welldonehoneyr" → 10("hello" 与 "world")
约束:
- 1 <=
words.length<= 1000;1 <=words[i].length,chars.length<= 100;仅含小写字母。
解题思路
解法一:字母计数对比
- 统计 chars 中每个字母的出现次数 need;
- 对每个单词统计其字母频次,若每个字母频次都不超过 need,则单词是好词,累加其长度。
复杂度:时间 O(C + sum(单词长度)),空间 O(26)。
解法二:复制计数数组 + 逐词扣减 - 对每个单词先复制基础频次数组,然后逐字母扣减:若某字母在副本中已为 0,则词不合法;
- 全部扣减成功则累加长度。实现直观,适合教学。
复杂度:时间 O(C + sum(len * 26)),空间 O(26)。
代码实现
java
import java.util.*;
public class Solution {
public int countCharacters(String[] words, String chars) {
int[] base = new int[26];
for (char c : chars.toCharArray()) base[c - 'a']++;
int ans = 0;
for (String w : words) {
if (canForm(w, base)) ans += w.length();
}
return ans;
}
private boolean canForm(String w, int[] base) {
int[] cnt = new int[26];
for (char c : w.toCharArray()) cnt[c - 'a']++;
for (int i = 0; i < 26; i++) {
if (cnt[i] > base[i]) return false;
}
return true;
}
public int countCharactersCopy(String[] words, String chars) {
int[] base = new int[26];
for (char c : chars.toCharArray()) base[c - 'a']++;
int ans = 0;
for (String w : words) {
int[] copy = base.clone();
boolean ok = true;
for (char c : w.toCharArray()) {
if (copy[c - 'a'] == 0) { ok = false; break; }
copy[c - 'a']--;
}
if (ok) ans += w.length();
}
return ans;
}
public static void main(String[] args) {
Solution s = new Solution();
String[] w1 = {"cat","bt","hat","tree"};
System.out.println("示例1 解法一: " + s.countCharacters(w1, "atach") + "(期望 6)");
System.out.println("示例1 解法二: " + s.countCharactersCopy(w1, "atach") + "(期望 6)");
String[] w2 = {"hello","world","leetcode"};
System.out.println("示例2 解法一: " + s.countCharacters(w2, "welldonehoneyr") + "(期望 10)");
System.out.println("示例2 解法二: " + s.countCharactersCopy(w2, "welldonehoneyr") + "(期望 10)");
}
}
复杂度分析
复杂度:时间 O(C + sum(单词长度)),空间 O(26)。
复杂度:时间 O(C + sum(len * 26)),空间 O(26)。