LeetCode 1461-1480. 题解合集
文章目录
- [LeetCode 1461-1480. 题解合集](#LeetCode 1461-1480. 题解合集)
-
- [1461. Check If a String Contains All Binary Codes of Size K(检查一个字符串是否包含所有长度为 K 的二进制子串)](#1461. Check If a String Contains All Binary Codes of Size K(检查一个字符串是否包含所有长度为 K 的二进制子串))
- [1462. Course Schedule IV(课程表 IV)](#1462. Course Schedule IV(课程表 IV))
- [1463. Cherry Pickup II(摘樱桃 II)](#1463. Cherry Pickup II(摘樱桃 II))
- [1464. Maximum Product of Two Elements in an Array(数组中两元素的最大乘积)](#1464. Maximum Product of Two Elements in an Array(数组中两元素的最大乘积))
- [1465. Maximum Area of a Piece of Cake After Horizontal and Vertical Cuts(切割后面积最大的蛋糕)](#1465. Maximum Area of a Piece of Cake After Horizontal and Vertical Cuts(切割后面积最大的蛋糕))
- [1466. Reorder Routes to Make All Paths Lead to the City Zero(重新规划路线)](#1466. Reorder Routes to Make All Paths Lead to the City Zero(重新规划路线))
- [1467. Probability of a Two Boxes Having The Same Number of Distinct Balls(两个盒子中球的颜色数相同的概率)](#1467. Probability of a Two Boxes Having The Same Number of Distinct Balls(两个盒子中球的颜色数相同的概率))
- [1468. Calculate Salaries(计算税后工资 🔒)](#1468. Calculate Salaries(计算税后工资 🔒))
- [1469. Find All The Lonely Nodes(寻找所有的独生节点)](#1469. Find All The Lonely Nodes(寻找所有的独生节点))
- [1470. Shuffle the Array(重新排列数组)](#1470. Shuffle the Array(重新排列数组))
- [1471. The k Strongest Values in an Array(数组中的 k 个最强值)](#1471. The k Strongest Values in an Array(数组中的 k 个最强值))
- [1472. Design Browser History(设计浏览器历史记录)](#1472. Design Browser History(设计浏览器历史记录))
- [1473. Paint House III(粉刷房子 III)](#1473. Paint House III(粉刷房子 III))
- [1474. Delete N Nodes After M Nodes of a Linked List(删除链表 M 个节点之后的 N 个节点)](#1474. Delete N Nodes After M Nodes of a Linked List(删除链表 M 个节点之后的 N 个节点))
- [1475. Final Prices With a Special Discount in a Shop(商品折扣后的最终价格)](#1475. Final Prices With a Special Discount in a Shop(商品折扣后的最终价格))
- [1476. Subrectangle Queries(子矩形查询)](#1476. Subrectangle Queries(子矩形查询))
- [1477. Find Two Non-overlapping Sub-arrays Each With Target Sum(找两个和为目标值且不重叠的子数组)](#1477. Find Two Non-overlapping Sub-arrays Each With Target Sum(找两个和为目标值且不重叠的子数组))
- [1478. Allocate Mailboxes(安排邮筒)](#1478. Allocate Mailboxes(安排邮筒))
- [1479. Sales by Day of the Week(周内每天的销售情况 🔒)](#1479. Sales by Day of the Week(周内每天的销售情况 🔒))
- [1480. Running Sum of 1d Array(一维数组的动态和)](#1480. Running Sum of 1d Array(一维数组的动态和))
1461. Check If a String Contains All Binary Codes of Size K(检查一个字符串是否包含所有长度为 K 的二进制子串)
题目描述
给你一个二进制字符串 s 和一个整数 k。如果所有长度为 k 的二进制字符串都是 s 的子串,
请返回 true,否则请返回 false。
示例 1
s = "00110110", k = 2 -> true
示例 2
s = "0110", k = 1 -> true
示例 3
s = "0110", k = 2 -> false
约束:
- 1 <=
s.length<= 5 * 10^5;s[i]为 '0' 或 '1';1 <=k<= 20。
解题思路
解法一:滑动窗口哈希收集(主提交版本)
全部 k 位二进制串共有 2^k 个。用长度为 k 的滑动窗口,把每个窗口的十进制值
放入 boolean 数组 seen(大小 2^k),最终统计不同值个数是否达到 2^k。
维护滚动值 val = ((val << 1) & mask) | bit,mask = (1<<k) - 1。
复杂度:O(n),空间 O(2^k)。
解法二:HashSet 收集(对照)
同样滑动窗口,但用 HashSet 存储窗口值,最后 size == 1<<k。
复杂度:O(n),空间 O(2^k)。
代码实现
java
import java.util.HashSet;
public class Solution {
public boolean hasAllCodes(String s, int k) {
int n = s.length();
if (n < k) {
return false;
}
int total = 1 << k;
boolean[] seen = new boolean[total];
int mask = total - 1;
int val = 0;
for (int i = 0; i < k; i++) {
val = ((val << 1) | (s.charAt(i) - '0')) & mask;
}
int count = 0;
if (!seen[val]) {
seen[val] = true;
count++;
}
for (int i = k; i < n; i++) {
val = ((val << 1) | (s.charAt(i) - '0')) & mask;
if (!seen[val]) {
seen[val] = true;
count++;
}
}
return count == total;
}
public boolean hasAllCodesSet(String s, int k) {
int n = s.length();
if (n < k) {
return false;
}
int total = 1 << k;
int mask = total - 1;
int val = 0;
for (int i = 0; i < k; i++) {
val = ((val << 1) | (s.charAt(i) - '0')) & mask;
}
HashSet<Integer> set = new HashSet<Integer>();
set.add(val);
for (int i = k; i < n; i++) {
val = ((val << 1) | (s.charAt(i) - '0')) & mask;
set.add(val);
if (set.size() == total) {
return true;
}
}
return set.size() == total;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.hasAllCodes("00110110", 2)); // true
System.out.println(s.hasAllCodes("0110", 1)); // true
System.out.println(s.hasAllCodes("0110", 2)); // false
System.out.println(s.hasAllCodes("0000000001011100", 4)); // false
System.out.println(s.hasAllCodesSet("00110110", 2)); // true
System.out.println(s.hasAllCodesSet("0110", 1)); // true
System.out.println(s.hasAllCodesSet("0110", 2)); // false
}
}
复杂度分析
复杂度:O(n),空间 O(2^k)。
1462. Course Schedule IV(课程表 IV)
题目描述
你总共需要上 numCourses 门课,课程编号依次为 0 到 numCourses-1。
你会得到一个数组 prerequisites,其中 prerequisitesi = ai, bi 表示如果你想选 bi 课程,
你必须先选 ai 课程(先决条件可以为间接的:a 是 b 的先决条件、b 是 c 的先决条件,
那么 a 也是 c 的先决条件)。
你也得到一个数组 queries,其中 queriesj = uj, vj。
对于第 j 个查询,你应该回答课程 uj 是否是课程 vj 的先决条件。
返回一个布尔数组 answer,其中 answerj 是第 j 个查询的答案。
示例 1
numCourses = 2, prerequisites = [[1,0]], queries = [[0,1],[1,0]] -> [false,true]
示例 3
numCourses = 3, prerequisites = [[1,2],[1,0],[2,0]], queries = [[1,0],[1,2]] -> [true,true]
约束:
- 2 <=
numCourses<= 100;0 <= prerequisites.length <=numCourses*(numCourses-1)/2; - 先修课程图中没有环;1 <= queries.length <= 10^4。
解题思路
解法一:Floyd 传递闭包(主提交版本)
用邻接矩阵 reachuv 表示 u 是否是 v 的直接或间接先修。
先用入度为零的点做拓扑排序(或直接按 i<k<j 的 Floyd):
对中间节点 k,若 u 可达 k 且 k 可达 v,则 u 可达 v。
因 n<=100,O(n^3) 完全可接受,之后每条查询 O(1)。
复杂度:O(n^3 + q)。
解法二:反向 DFS 记忆化(对照)
把先修关系反向建图(v -> 所有必须以 v 为前提的课?)。标准反向:
给每个课程 u 计算所有"以 u 为先修"的课程集合(即 u 的直接/间接后继),
查询 (u, v) 即判断 v 是否在 u 的后继集合中。
这里实现为:对每个查询做一次 DFS(带全局记忆化 visited),
复杂度 O(q * (n + m)),因 n 小也可接受。
代码实现
java
import java.util.ArrayList;
import java.util.List;
public class Solution {
public List<Boolean> checkIfPrerequisite(int numCourses, int[][] prerequisites, int[][] queries) {
boolean[][] reach = new boolean[numCourses][numCourses];
for (int[] p : prerequisites) {
reach[p[0]][p[1]] = true;
}
for (int k = 0; k < numCourses; k++) {
for (int i = 0; i < numCourses; i++) {
if (!reach[i][k]) {
continue;
}
for (int j = 0; j < numCourses; j++) {
if (reach[k][j]) {
reach[i][j] = true;
}
}
}
}
List<Boolean> ans = new ArrayList<Boolean>();
for (int[] q : queries) {
ans.add(reach[q[0]][q[1]]);
}
return ans;
}
public List<Boolean> checkIfPrerequisiteDfs(int numCourses, int[][] prerequisites, int[][] queries) {
// 反向图:adj[v] 中存所有直接先修 u(u -> v 正向,反向建 v -> u)
List<List<Integer>> adj = new ArrayList<List<Integer>>();
for (int i = 0; i < numCourses; i++) {
adj.add(new ArrayList<Integer>());
}
for (int[] p : prerequisites) {
adj.get(p[1]).add(p[0]); // v 的先修是 u
}
List<Boolean> ans = new ArrayList<Boolean>();
for (int[] q : queries) {
ans.add(canReach(q[0], q[1], adj, numCourses));
}
return ans;
}
private boolean canReach(int start, int end, List<List<Integer>> adj, int n) {
boolean[] visited = new boolean[n];
java.util.ArrayDeque<Integer> stack = new java.util.ArrayDeque<Integer>();
stack.push(end);
while (!stack.isEmpty()) {
int cur = stack.pop();
if (cur == start) {
return true;
}
if (visited[cur]) {
continue;
}
visited[cur] = true;
for (int pre : adj.get(cur)) {
if (!visited[pre]) {
stack.push(pre);
}
}
}
return false;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.checkIfPrerequisite(2, new int[][]{{1, 0}}, new int[][]{{0, 1}, {1, 0}}));
// [false, true]
System.out.println(s.checkIfPrerequisite(2, new int[][]{}, new int[][]{{1, 0}, {0, 1}}));
// [false, false]
System.out.println(s.checkIfPrerequisite(3, new int[][]{{1, 2}, {1, 0}, {2, 0}}, new int[][]{{1, 0}, {1, 2}}));
// [true, true]
System.out.println(s.checkIfPrerequisiteDfs(2, new int[][]{{1, 0}}, new int[][]{{0, 1}, {1, 0}}));
// [false, true]
System.out.println(s.checkIfPrerequisiteDfs(3, new int[][]{{1, 2}, {1, 0}, {2, 0}}, new int[][]{{1, 0}, {1, 2}}));
// [true, true]
}
}
复杂度分析
复杂度:O(n^3 + q)。
1463. Cherry Pickup II(摘樱桃 II)
题目描述
给你一个 rows x cols 的矩阵 grid 来表示一块樱桃地。grid 中每个格子的数字表示你能获得的樱桃数目。
你有两个机器人帮你收集樱桃:机器人 1 从左上角格子 (0,0) 出发,机器人 2 从右上角格子 (0, cols-1) 出发。
规则:
- 从格子 (
i,j) 出发,机器人可以移动到格子 (i+1,j-1)、(i+1,j) 或 (i+1,j+1); - 当一个机器人经过某个格子时,它会把该格子内所有的樱桃都摘走,然后这个位置会变成空格子;
- 当两个机器人同时到达同一个格子时,它们中只有一个可以摘到樱桃;
- 两个机器人在任意时刻都不能移动到
grid外面; - 两个机器人最后都要到达
grid最底下一行。
返回两个机器人能收集的最多樱桃数目。
示例 1
grid = [[3,1,1],[2,5,1],[1,5,5],[2,1,1]] -> 24
示例 2
grid = [[1,0,0,0,0,0,1],[2,0,0,0,0,3,0],[2,0,9,0,0,0,0],[0,3,0,5,4,0,0],[1,0,2,3,0,0,6]] -> 28
约束:
- 2 <= rows,
cols<= 70;0 <=grid[i][j]<= 100。
解题思路
解法一:同步 DP 状态压缩(主提交版本)
两个机器人第 i 行时列位置分别为 c1、c2,且两机器人同时逐行向下走,
则"已用步数"相同 → 同步 DP:dpic1c2 = 两机器人都到达第 i 行、
分别位于列 c1、c2 时收集的最大樱桃数。
转移:上一行位置 (pc1 in {c1-1,c1,c1+1}, pc2 in {c2-1,c2,c2+1}) 的最大值 +
当前行两位置的樱桃(同列只计一次)。
复杂度:O(rows * cols^2 * 9),空间 O(cols^2) 滚动。
解法二:记忆化递归(对照)
直接写 dfs(i, c1, c2) 递归向下,返回收集樱桃数;等价转移。
复杂度:O(rows * cols^2)。
代码实现
java
import java.util.Arrays;
public class Solution {
public int cherryPickup(int[][] grid) {
int rows = grid.length;
int cols = grid[0].length;
int neg = Integer.MIN_VALUE / 2;
int[][] dp = new int[cols][cols];
for (int[] row : dp) {
Arrays.fill(row, neg);
}
dp[0][cols - 1] = grid[0][0] + grid[0][cols - 1];
for (int i = 1; i < rows; i++) {
int[][] ndp = new int[cols][cols];
for (int[] row : ndp) {
Arrays.fill(row, neg);
}
for (int c1 = 0; c1 < cols; c1++) {
for (int c2 = 0; c2 < cols; c2++) {
int best = neg;
for (int d1 = -1; d1 <= 1; d1++) {
for (int d2 = -1; d2 <= 1; d2++) {
int pc1 = c1 + d1;
int pc2 = c2 + d2;
if (pc1 >= 0 && pc1 < cols && pc2 >= 0 && pc2 < cols) {
best = Math.max(best, dp[pc1][pc2]);
}
}
}
if (best == neg) {
continue;
}
int gain = grid[i][c1] + (c1 == c2 ? 0 : grid[i][c2]);
ndp[c1][c2] = best + gain;
}
}
dp = ndp;
}
int ans = 0;
for (int c1 = 0; c1 < cols; c1++) {
for (int c2 = 0; c2 < cols; c2++) {
ans = Math.max(ans, dp[c1][c2]);
}
}
return ans;
}
public int cherryPickupDfs(int[][] grid) {
int rows = grid.length;
int cols = grid[0].length;
int[][][] memo = new int[rows][cols][cols];
for (int[][] a : memo) {
for (int[] b : a) {
Arrays.fill(b, -1);
}
}
return dfs(0, 0, cols - 1, grid, memo);
}
private int dfs(int r, int c1, int c2, int[][] grid, int[][][] memo) {
if (memo[r][c1][c2] != -1) {
return memo[r][c1][c2];
}
int rows = grid.length;
int cols = grid[0].length;
int gain = grid[r][c1] + (c1 == c2 ? 0 : grid[r][c2]);
if (r == rows - 1) {
memo[r][c1][c2] = gain;
return gain;
}
int best = 0;
for (int d1 = -1; d1 <= 1; d1++) {
for (int d2 = -1; d2 <= 1; d2++) {
int nc1 = c1 + d1;
int nc2 = c2 + d2;
if (nc1 >= 0 && nc1 < cols && nc2 >= 0 && nc2 < cols) {
best = Math.max(best, dfs(r + 1, nc1, nc2, grid, memo));
}
}
}
memo[r][c1][c2] = gain + best;
return gain + best;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.cherryPickup(new int[][]{{3, 1, 1}, {2, 5, 1}, {1, 5, 5}, {2, 1, 1}})); // 24
System.out.println(s.cherryPickup(new int[][]{{1, 0, 0, 0, 0, 0, 1}, {2, 0, 0, 0, 0, 3, 0},
{2, 0, 9, 0, 0, 0, 0}, {0, 3, 0, 5, 4, 0, 0}, {1, 0, 2, 3, 0, 0, 6}})); // 28
System.out.println(s.cherryPickup(new int[][]{{1, 0, 0, 3}, {0, 0, 0, 3}, {0, 0, 3, 3}, {9, 0, 3, 3}})); // 22
System.out.println(s.cherryPickup(new int[][]{{1, 1}, {1, 1}})); // 4
System.out.println(s.cherryPickupDfs(new int[][]{{3, 1, 1}, {2, 5, 1}, {1, 5, 5}, {2, 1, 1}})); // 24
System.out.println(s.cherryPickupDfs(new int[][]{{1, 0, 0, 0, 0, 0, 1}, {2, 0, 0, 0, 0, 3, 0},
{2, 0, 9, 0, 0, 0, 0}, {0, 3, 0, 5, 4, 0, 0}, {1, 0, 2, 3, 0, 0, 6}})); // 28
System.out.println(s.cherryPickupDfs(new int[][]{{1, 0, 0, 3}, {0, 0, 0, 3}, {0, 0, 3, 3}, {9, 0, 3, 3}})); // 22
}
}
复杂度分析
复杂度:O(rows * cols^2 * 9),空间 O(cols^2) 滚动。
复杂度:O(rows * cols^2)。
1464. Maximum Product of Two Elements in an Array(数组中两元素的最大乘积)
题目描述
给你一个整数数组 nums。请你选择数组的两个不同下标 i 和 j,
返回 (nums[i] - 1) * (nums[j] - 1) 的最大值。
示例 1
nums = [3,4,5,2] -> 12((4-1)*(5-1))
示例 2
nums = [1,5,4,5] -> 16
示例 3
nums = [3,7] -> 12
约束:
- 2 <=
nums.length<= 500;1 <=nums[i]<= 10^3。
解题思路
解法一:线性扫描维护两个最大值(主提交版本)
一遍扫描维护第一大 max1 与第二大 max2,答案为 (max1-1)*(max2-1)。
复杂度:O(n),空间 O(1)。
解法二:排序取前二(对照)
排序后取最大两个元素。
复杂度:O(n log n),空间 O(1)。
代码实现
java
import java.util.Arrays;
public class Solution {
public int maxProduct(int[] nums) {
int max1 = 0, max2 = 0;
for (int v : nums) {
if (v > max1) {
max2 = max1;
max1 = v;
} else if (v > max2) {
max2 = v;
}
}
return (max1 - 1) * (max2 - 1);
}
public int maxProductSort(int[] nums) {
Arrays.sort(nums);
int n = nums.length;
return (nums[n - 1] - 1) * (nums[n - 2] - 1);
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.maxProduct(new int[]{3, 4, 5, 2})); // 12
System.out.println(s.maxProduct(new int[]{1, 5, 4, 5})); // 16
System.out.println(s.maxProduct(new int[]{3, 7})); // 12
System.out.println(s.maxProductSort(new int[]{3, 4, 5, 2})); // 12
System.out.println(s.maxProductSort(new int[]{1, 5, 4, 5})); // 16
System.out.println(s.maxProductSort(new int[]{3, 7})); // 12
}
}
复杂度分析
复杂度:O(n),空间 O(1)。
复杂度:O(n log n),空间 O(1)。
1465. Maximum Area of a Piece of Cake After Horizontal and Vertical Cuts(切割后面积最大的蛋糕)
题目描述
矩形蛋糕的高度为 h 且宽度为 w,给你两个整数数组 horizontalCuts 和 verticalCuts,其中:
horizontalCuts[i]是从矩形蛋糕顶部到第i个水平切口的距离;verticalCuts[j]是从矩形蛋糕的左侧到第j个竖直切口的距离。
请你按数组中提供的水平和竖直位置切割后,找出面积最大的那份蛋糕,并返回其面积。
由于答案可能是一个很大的数字,因此需要将结果对 10^9+7 取余后返回。
示例 1
h = 5, w = 4, horizontalCuts = [1,2,4], verticalCuts = [1,3] -> 4
示例 2
h = 5, w = 4, horizontalCuts = [3,1], verticalCuts = [1] -> 6
示例 3
h = 5, w = 4, horizontalCuts = [3], verticalCuts = [3] -> 9
约束:
- 2 <=
h,w<= 10^9;1 <=horizontalCuts.length<=min(h-1, 10^5)。
解题思路
解法一:排序求相邻最大间隔(主提交版本)
切割后水平方向各块的高度 = 相邻水平切口之间的高度差(含边界 0 与 h),
最大高度 = 这些间隔的最大值;同理最大宽度 = 相邻竖直切口间隔最大值。
面积 = 最大高度 * 最大宽度(取模)。
复杂度:O(a log a + b log b),空间 O(1)。
解法二:布尔数组标记切割位置(对照)
当 h、w 较小时可用 boolean 数组标记切口并两遍扫描找最长连续段;
此处展示该思路(h/w 大时退化为排序法统计,这里以数组模拟演示)。
复杂度:O(h + w + a + b)。
代码实现
java
import java.util.Arrays;
public class Solution {
public int maxArea(int h, int w, int[] horizontalCuts, int[] verticalCuts) {
int MOD = 1000000007;
long maxH = maxGap(h, horizontalCuts);
long maxW = maxGap(w, verticalCuts);
return (int) ((maxH % MOD) * (maxW % MOD) % MOD);
}
private long maxGap(int total, int[] cuts) {
Arrays.sort(cuts);
long best = cuts[0] - 0; // 第一段
for (int i = 1; i < cuts.length; i++) {
best = Math.max(best, (long) cuts[i] - cuts[i - 1]);
}
best = Math.max(best, (long) total - cuts[cuts.length - 1]); // 最后一段
return best;
}
public int maxAreaTree(int h, int w, int[] horizontalCuts, int[] verticalCuts) {
int MOD = 1000000007;
long maxH = maxGapTree(h, horizontalCuts);
long maxW = maxGapTree(w, verticalCuts);
return (int) ((maxH % MOD) * (maxW % MOD) % MOD);
}
private long maxGapTree(int total, int[] cuts) {
java.util.TreeSet<Integer> set = new java.util.TreeSet<Integer>();
set.add(0);
set.add(total);
for (int x : cuts) {
set.add(x);
}
long best = 0;
Integer prev = null;
for (int v : set) {
if (prev != null) {
best = Math.max(best, (long) v - prev);
}
prev = v;
}
return best;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.maxArea(5, 4, new int[]{1, 2, 4}, new int[]{1, 3})); // 4
System.out.println(s.maxArea(5, 4, new int[]{3, 1}, new int[]{1})); // 6
System.out.println(s.maxArea(5, 4, new int[]{3}, new int[]{3})); // 9
System.out.println(s.maxArea(1000000000, 1000000000, new int[]{2}, new int[]{2})); // 4
System.out.println(s.maxAreaTree(5, 4, new int[]{1, 2, 4}, new int[]{1, 3})); // 4
System.out.println(s.maxAreaTree(5, 4, new int[]{3, 1}, new int[]{1})); // 6
System.out.println(s.maxAreaTree(5, 4, new int[]{3}, new int[]{3})); // 9
}
}
复杂度分析
复杂度:O(a log a + b log b),空间 O(1)。
复杂度:O(h + w + a + b)。
1466. Reorder Routes to Make All Paths Lead to the City Zero(重新规划路线)
题目描述
n 座城市,从 0 到 n-1 编号,其间共有 n-1 条路线(路线网形成一棵树)。
路线用 connections 表示,其中 connectionsi = `a`, `b` 表示从城市 a 到 b 的一条有向路线。
今年,城市 0 将会举办一场大型比赛,很多游客都想前往城市 0。
请你帮助重新规划路线方向,使每个城市都可以访问城市 0。返回需要变更方向的最小路线数。
题目数据保证每个城市在重新规划路线方向后都能到达城市 0。
示例 1
n = 6, connections = [[0,1],[1,3],[2,3],[4,0],[4,5]] -> 3
示例 2
n = 5, connections = [[1,0],[1,2],[3,2],[3,4]] -> 2
示例 3
n = 3, connections = [[1,0],[2,0]] -> 0
约束:
- 2 <=
n<= 5 * 10^4;connections.length ==n- 1。
解题思路
解法一:无向建图 + DFS 计数反向边(主提交版本)
对每条边 a, b 建立双向邻接:正向 a->b 标记 dir=1(按原始方向),
b->a 标记 dir=0(表示逆边)。从城市 0 出发 DFS 整棵树:
走到邻接节点时,若原边方向是"离开城市 0"(dir=1),则需要翻转该边,答案+1;
逆边(dir=0)表示已经朝向城市 0,不需要翻转。
因为图是树,从 0 出发 DFS 每条边只访问一次。
复杂度:O(n),空间 O(n)。
解法二:BFS 层序计数(对照)
同样建双向图,用队列 BFS 从 0 出发,逐层向外扩展,统计需要翻转的边数。
复杂度:O(n),空间 O(n)。
代码实现
java
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
public class Solution {
public int minReorder(int n, int[][] connections) {
List<List<int[]>> g = new ArrayList<List<int[]>>();
for (int i = 0; i < n; i++) {
g.add(new ArrayList<int[]>());
}
for (int[] e : connections) {
g.get(e[0]).add(new int[]{e[1], 1}); // 原始方向 0->1
g.get(e[1]).add(new int[]{e[0], 0}); // 反向 1->0
}
boolean[] visited = new boolean[n];
visited[0] = true;
int[] cnt = new int[1];
dfs(0, g, visited, cnt);
return cnt[0];
}
private void dfs(int u, List<List<int[]>> g, boolean[] visited, int[] cnt) {
for (int[] next : g.get(u)) {
int v = next[0];
int dir = next[1];
if (visited[v]) {
continue;
}
visited[v] = true;
if (dir == 1) { // 原始方向是从 u 指向 v(离开 0),需要翻转
cnt[0]++;
}
dfs(v, g, visited, cnt);
}
}
public int minReorderBfs(int n, int[][] connections) {
List<List<int[]>> g = new ArrayList<List<int[]>>();
for (int i = 0; i < n; i++) {
g.add(new ArrayList<int[]>());
}
for (int[] e : connections) {
g.get(e[0]).add(new int[]{e[1], 1});
g.get(e[1]).add(new int[]{e[0], 0});
}
boolean[] visited = new boolean[n];
visited[0] = true;
ArrayDeque<Integer> q = new ArrayDeque<Integer>();
q.add(0);
int cnt = 0;
while (!q.isEmpty()) {
int u = q.poll();
for (int[] next : g.get(u)) {
int v = next[0];
int dir = next[1];
if (visited[v]) {
continue;
}
visited[v] = true;
if (dir == 1) {
cnt++;
}
q.add(v);
}
}
return cnt;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.minReorder(6, new int[][]{{0, 1}, {1, 3}, {2, 3}, {4, 0}, {4, 5}})); // 3
System.out.println(s.minReorder(5, new int[][]{{1, 0}, {1, 2}, {3, 2}, {3, 4}})); // 2
System.out.println(s.minReorder(3, new int[][]{{1, 0}, {2, 0}})); // 0
System.out.println(s.minReorderBfs(6, new int[][]{{0, 1}, {1, 3}, {2, 3}, {4, 0}, {4, 5}})); // 3
System.out.println(s.minReorderBfs(5, new int[][]{{1, 0}, {1, 2}, {3, 2}, {3, 4}})); // 2
System.out.println(s.minReorderBfs(3, new int[][]{{1, 0}, {2, 0}})); // 0
}
}
复杂度分析
复杂度:O(n),空间 O(n)。
1467. Probability of a Two Boxes Having The Same Number of Distinct Balls(两个盒子中球的颜色数相同的概率)
题目描述
桌面上有 2n 个颜色不完全相同的球,球的颜色共有 k 种。
给你一个大小为 k 的整数数组 balls,其中 ballsi 是颜色为 i 的球的数量。
所有的球都已经随机打乱顺序,前 n 个球放入第一个盒子,后 n 个球放入另一个盒子。
注意:这两个盒子是不同的。
请返回「两个盒子中球的颜色数相同」的情况的概率。答案与真实值误差在 10^-5 以内即可。
示例 1
balls = [1,1] -> 1.00000
示例 2
balls = [2,1,1] -> 0.66667
示例 3
balls = [1,2,1,2] -> 0.60000
约束:
- 1 <= balls.length <= 8;1 <= ballsi <= 6;
sum(balls) 是偶数。
解题思路
解法一:组合计数(主提交版本)
总方案数 = 把 2n 个球分成两组(各 n 个)的方案数 = Π C(ballsi, take_i) 的所有
take_i 之和 = C(2n, n)(本质上每种"取出前半组"的方式对应一种分组)。
合法方案数 = 满足"第一盒颜色数 == 第二盒颜色数"的取出方案数。
DFS 枚举每种颜色 i 取 take_i 个放入第一盒(0 <= take_i <= ballsi,总数= n),
累加组合数乘积:ways_first = Π C(ballsi, take_i)。
条件:第一盒颜色数 c1 = #{i | take_i > 0} 与第二盒颜色数 c2 = #{i | ballsi-take_i > 0} 相等。
概率 = 合法方案数 / C(2n, n)。
组合数用 double 或 long 预处理(数值不超过 C(12,6)=924 的组合乘法,double 足够精确)。
复杂度:O(Π(ballsi+1)) 以 k<=8、balls<=6 为限,状态数很少。
解法二:记忆化 + 递归剩余(对照)
与解法一同构,只是把 DFS 换成显式的递归函数并用组合表直接相乘;
这里同样实现,但把累计合法数/总数表达为分数相加,逻辑一致。
复杂度与解法一同。
代码实现
java
public class Solution {
private long[][] comb;
private long totalWays;
private long goodWays;
public double getProbability(int[] balls) {
int k = balls.length;
int total = 0;
for (int b : balls) {
total += b;
}
int half = total / 2;
// 预处理组合数 C[n][r],n <= 12
comb = new long[13][13];
for (int i = 0; i <= 12; i++) {
comb[i][0] = 1;
for (int j = 1; j <= i; j++) {
comb[i][j] = comb[i - 1][j - 1] + comb[i - 1][j];
}
}
totalWays = comb[total][half]; // 总取法数 C(2n, n)
goodWays = 0;
dfs(0, half, 0, 0, 1L, balls);
return (double) goodWays / totalWays;
}
private void dfs(int idx, int remaining, int c1, int c2, long ways, int[] balls) {
if (idx == balls.length) {
if (remaining == 0 && c1 == c2) {
goodWays += ways;
}
return;
}
int maxTake = Math.min(balls[idx], remaining);
// 枚举第一盒取 take 个该颜色球
for (int take = 0; take <= maxTake; take++) {
int left = balls[idx] - take; // 第二盒该颜色球数
int n1 = take > 0 ? 1 : 0;
int n2 = left > 0 ? 1 : 0;
long next = ways * comb[balls[idx]][take];
dfs(idx + 1, remaining - take, c1 + n1, c2 + n2, next, balls);
}
}
public double getProbability2(int[] balls) {
int k = balls.length;
int total = 0;
for (int b : balls) {
total += b;
}
int half = total / 2;
long[][] c = new long[13][13];
for (int i = 0; i <= 12; i++) {
c[i][0] = 1;
for (int j = 1; j <= i; j++) {
c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
}
}
// 枚举所有第一盒取出方案
long[] good = new long[1];
long[] all = new long[1];
all[0] = c[total][half];
dfs2(0, half, 0, 0, 1L, balls, c, good);
return (double) good[0] / all[0];
}
private void dfs2(int idx, int remaining, int c1, int c2, long ways,
int[] balls, long[][] c, long[] good) {
if (idx == balls.length) {
if (remaining == 0 && c1 == c2) {
good[0] += ways;
}
return;
}
int maxTake = Math.min(balls[idx], remaining);
for (int take = 0; take <= maxTake; take++) {
int left = balls[idx] - take;
dfs2(idx + 1, remaining - take, c1 + (take > 0 ? 1 : 0),
c2 + (left > 0 ? 1 : 0), ways * c[balls[idx]][take], balls, c, good);
}
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.printf("%.5f%n", s.getProbability(new int[]{1, 1})); // 1.00000
System.out.printf("%.5f%n", s.getProbability(new int[]{2, 1, 1})); // 0.66667
System.out.printf("%.5f%n", s.getProbability(new int[]{1, 2, 1, 2})); // 0.60000
System.out.printf("%.5f%n", s.getProbability2(new int[]{1, 1})); // 1.00000
System.out.printf("%.5f%n", s.getProbability2(new int[]{2, 1, 1})); // 0.66667
System.out.printf("%.5f%n", s.getProbability2(new int[]{1, 2, 1, 2})); // 0.60000
}
}
复杂度分析
复杂度:O(Π(ballsi+1)) 以 k<=8、balls<=6 为限,状态数很少。
1468. Calculate Salaries(计算税后工资 🔒)
题目类型:数据库(SQL)
题目描述
Salaries 表:
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| company_id | int |
| employee_id | int |
| employee_name | varchar |
| salary | int |
+---------------+---------+
在 SQL 中,(company_id, employee_id) 是这个表的主键
这个表包括员工的company id, id, name 和 salary
查找出每个员工的税后工资
每个公司的税率计算依照以下规则
- 如果这个公司员工最高工资不到 `$1000` ,税率为 `0%`
- 如果这个公司员工最高工资在 `[1000, 10000]` 之间,税率为 `24%`
- 如果这个公司员工最高工资大于 `$10000` ,税率为 `49%`
按 任意顺序 返回结果。
返回结果的格式如下例所示。
示例 1:
**输入:**
Salaries 表:
+------------+-------------+---------------+--------+
| company_id | employee_id | employee_name | salary |
+------------+-------------+---------------+--------+
| 1 | 1 | Tony | 2000 |
| 1 | 2 | Pronub | 21300 |
| 1 | 3 | Tyrrox | 10800 |
| 2 | 1 | Pam | 300 |
| 2 | 7 | Bassem | 450 |
| 2 | 9 | Hermione | 700 |
| 3 | 7 | Bocaben | 100 |
| 3 | 2 | Ognjen | 2200 |
| 3 | 13 | Nyancat | 3300 |
| 3 | 15 | Morninngcat | 7777 |
+------------+-------------+---------------+--------+
**输出:**
+------------+-------------+---------------+--------+
| company_id | employee_id | employee_name | salary |
+------------+-------------+---------------+--------+
| 1 | 1 | Tony | 1020 |
| 1 | 2 | Pronub | 10863 |
| 1 | 3 | Tyrrox | 5508 |
| 2 | 1 | Pam | 300 |
| 2 | 7 | Bassem | 450 |
| 2 | 9 | Hermione | 700 |
| 3 | 7 | Bocaben | 76 |
| 3 | 2 | Ognjen | 1672 |
| 3 | 13 | Nyancat | 2508 |
| 3 | 15 | Morninngcat | 5911 |
+------------+-------------+---------------+--------+
**解释:**
对于公司 1,最高薪资为 21300。公司 1 的员工税率为 49%。
对于公司 2,最高薪资为 700。公司 2 的员工税率为 0%。
对于公司 3,最高薪资为 7777。公司 3 的员工税率为 24%。
薪资扣除税后的金额计算公式为:薪资 - (税率百分比 / 100) * 薪资
例如,Morninngcat(员工号 3,薪资为 7777)扣除税后的薪资为:7777 - 7777 * (24 / 100) = 7777 - 1866.48 = 5910.52,四舍五入为 5911。
解题思路
思考
税率由公司最高工资决定,需先按公司求 MAX ( s a l a r y ) \textit{MAX}(salary) MAX(salary),再与每位员工连接。按阈值 1000 1000 1000、 10000 10000 10000 用
CASE打折并ROUND。
考察CASE + 子查询/窗口函数:按公司分组求出最高/最低工资,再按比例计算税前/税后。
代码实现
sql
# Write your MySQL query statement below
SELECT
s.company_id,
employee_id,
employee_name,
ROUND(
CASE
WHEN top < 1000 THEN salary
WHEN top >= 1000
AND top <= 10000 THEN salary * 0.76
ELSE salary * 0.51
END
) AS salary
FROM
Salaries AS s
JOIN (
SELECT company_id, MAX(salary) AS top
FROM Salaries
GROUP BY company_id
) AS t
ON s.company_id = t.company_id;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1469. Find All The Lonely Nodes(寻找所有的独生节点)
题目描述
二叉树中,如果一个节点是其父节点的唯一子节点,则称这样的节点为"独生节点"。
二叉树的根节点不会是独生节点,因为它没有父节点。
给定一棵二叉树的根节点 root,返回树中所有的独生节点的值所构成的数组。数组的顺序不限。
示例 1
root = [1,2,3,null,4] -> [4]
示例 2
root = [7,1,4,6,null,5,3,null,null,null,null,null,2] -> [6,2]
示例 3
root = [11,99,88,77,null,null,66,55,null,null,44,33,null,null,22]
-> [77,55,33,66,44,22]
约束:
- 节点个数 1, 1000;1 <=
Node.val<= 10^6。
解题思路
解法一:DFS 判定(主提交版本)
递归时传入当前节点是否为"独生"(即它是其父节点的唯一孩子)。
对每个节点:若它是独生节点则把值加入列表;然后分别以
左孩子(唯一孩子?= 右孩子为空)、右孩子(唯一孩子?= 左孩子为空)递归。
复杂度:O(n),空间 O(h)。
解法二:栈迭代(对照)
用显式栈存 (node, isLonely) 状态,逻辑同上。
复杂度:O(n),空间 O(n)。
代码实现
java
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
public class Solution {
public static class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
public List<Integer> getLonelyNodes(TreeNode root) {
List<Integer> ans = new ArrayList<Integer>();
if (root == null) {
return ans;
}
dfs(root, false, ans);
return ans;
}
private void dfs(TreeNode node, boolean lonely, List<Integer> ans) {
if (lonely) {
ans.add(node.val);
}
if (node.left != null) {
dfs(node.left, node.right == null, ans);
}
if (node.right != null) {
dfs(node.right, node.left == null, ans);
}
}
public List<Integer> getLonelyNodesStack(TreeNode root) {
List<Integer> ans = new ArrayList<Integer>();
if (root == null) {
return ans;
}
ArrayDeque<Object[]> stack = new ArrayDeque<Object[]>();
stack.push(new Object[]{root, Boolean.FALSE});
while (!stack.isEmpty()) {
Object[] top = stack.pop();
TreeNode node = (TreeNode) top[0];
boolean lonely = (Boolean) top[1];
if (lonely) {
ans.add(node.val);
}
// 后入先出:先压右再压左,保证左先处理(顺序不限,无要求)
if (node.right != null) {
stack.push(new Object[]{node.right, node.left == null});
}
if (node.left != null) {
stack.push(new Object[]{node.left, node.right == null});
}
}
return ans;
}
private static TreeNode build(Integer[] a) {
if (a == null || a.length == 0) {
return null;
}
TreeNode root = new TreeNode(a[0]);
java.util.LinkedList<TreeNode> q = new java.util.LinkedList<TreeNode>();
q.add(root);
int i = 1;
while (!q.isEmpty() && i < a.length) {
TreeNode cur = q.poll();
if (a[i] != null) {
cur.left = new TreeNode(a[i]);
q.add(cur.left);
}
i++;
if (i < a.length && a[i] != null) {
cur.right = new TreeNode(a[i]);
q.add(cur.right);
}
i++;
}
return root;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.getLonelyNodes(build(new Integer[]{1, 2, 3, null, 4}))); // [4]
System.out.println(s.getLonelyNodes(build(new Integer[]{7, 1, 4, 6, null, 5, 3, null, null, null, null, null, 2})));
// [6, 2]
System.out.println(s.getLonelyNodesStack(build(new Integer[]{1, 2, 3, null, 4}))); // [4]
System.out.println(s.getLonelyNodesStack(build(new Integer[]{7, 1, 4, 6, null, 5, 3, null, null, null, null, null, 2})));
// [6, 2]
}
}
复杂度分析
复杂度:O(n),空间 O(h)。
复杂度:O(n),空间 O(n)。
1470. Shuffle the Array(重新排列数组)
题目描述
给你一个数组 nums,数组中有 2n 个元素,按 x1,x2,...,xn,y1,y2,...,yn 的格式排列。
请你将数组按 x1,y1,x2,y2,...,xn,yn 格式重新排列,返回重排后的数组。
示例 1
nums = [2,5,1,3,4,7], n = 3 -> [2,3,5,4,1,7]
示例 2
nums = [1,2,3,4,4,3,2,1], n = 4 -> [1,4,2,3,3,2,4,1]
示例 3
nums = [1,1,2,2], n = 2 -> [1,2,1,2]
约束:
- 1 <=
n<= 500;nums.length== 2n;1 <=nums[i]<= 10^3。
解题思路
解法一:双指针交错填充(主提交版本)
结果数组按 x1,y1,x2,y2,... 交错:下标 2i 放 x_{i+1}=numsi,
下标 2i+1 放 y_{i+1}=numsn+i。
复杂度:O(n),空间 O(n)(结果)。
解法二:单指针 + 数学映射(对照)
对结果位置 k,其来源为:k 偶数 -> numsk/2;k 奇数 -> numsn + k/2。
复杂度:O(n),空间 O(n)。
代码实现
java
public class Solution {
public int[] shuffle(int[] nums, int n) {
int[] res = new int[2 * n];
for (int i = 0; i < n; i++) {
res[2 * i] = nums[i];
res[2 * i + 1] = nums[n + i];
}
return res;
}
public int[] shuffleMap(int[] nums, int n) {
int[] res = new int[2 * n];
for (int k = 0; k < 2 * n; k++) {
if ((k & 1) == 0) {
res[k] = nums[k / 2];
} else {
res[k] = nums[n + k / 2];
}
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(java.util.Arrays.toString(s.shuffle(new int[]{2, 5, 1, 3, 4, 7}, 3)));
// [2, 3, 5, 4, 1, 7]
System.out.println(java.util.Arrays.toString(s.shuffle(new int[]{1, 2, 3, 4, 4, 3, 2, 1}, 4)));
// [1, 4, 2, 3, 3, 2, 4, 1]
System.out.println(java.util.Arrays.toString(s.shuffle(new int[]{1, 1, 2, 2}, 2)));
// [1, 2, 1, 2]
System.out.println(java.util.Arrays.toString(s.shuffleMap(new int[]{2, 5, 1, 3, 4, 7}, 3)));
// [2, 3, 5, 4, 1, 7]
System.out.println(java.util.Arrays.toString(s.shuffleMap(new int[]{1, 2, 3, 4, 4, 3, 2, 1}, 4)));
// [1, 4, 2, 3, 3, 2, 4, 1]
}
}
复杂度分析
复杂度:O(n),空间 O(n)(结果)。
复杂度:O(n),空间 O(n)。
1471. The k Strongest Values in an Array(数组中的 k 个最强值)
题目描述
给你一个整数数组 arr 和一个整数 k。
设 m 为数组的中位数(有序列表中位于 ((n-1)/2) 的元素),只要满足下述两个前提之一,
就可以判定 arr[i] 的值比 arr[j] 的值更强:
- |
arr[i]-m| > |arr[j]-m|; - |
arr[i]-m| == |arr[j]-m|,且arr[i]>arr[j]。
请返回由数组中最强的k个值组成的列表。答案可以以任意顺序返回。
示例 1
arr = [1,2,3,4,5], k = 2 -> [5,1]
示例 2
arr = [1,1,3,5,5], k = 2 -> [5,5]
示例 3
arr = [6,7,11,7,6,8], k = 5 -> [11,8,6,6,7]
约束:
- 1 <=
arr.length<= 105;-105 <=arr[i]<= 10^5;1 <=k<=arr.length。
解题思路
解法一:排序 + 双指针取最强(主提交版本)
排序后取中位数 m = sorted(n-1)/2。
用左右两个指针(l=0, r=n-1):比较 sortedl 与 sortedr 谁更强
(距离 m 更远者强;等距时值更大者强------由于已排序,右端值更大),
较强的一个放入结果并移动对应指针,直至取满 k 个。
复杂度:O(n log n),空间 O(n)。
解法二:自定义排序(对照)
按"强度"定义写比较器对整个数组排序,取前 k 个。
复杂度:O(n log n),空间 O(n)。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
public int[] getStrongest(int[] arr, int k) {
int n = arr.length;
int[] sorted = arr.clone();
Arrays.sort(sorted);
int m = sorted[(n - 1) / 2];
int l = 0, r = n - 1;
int[] res = new int[k];
int idx = 0;
while (idx < k) {
int a = sorted[l];
int b = sorted[r];
int da = Math.abs(a - m);
int db = Math.abs(b - m);
if (da > db || (da == db && a > b)) {
res[idx++] = a;
l++;
} else {
res[idx++] = b;
r--;
}
}
return res;
}
public List<Integer> getStrongestSort(Integer[] arr, int k) {
int n = arr.length;
Integer[] copy = arr.clone();
Arrays.sort(copy);
final int m = copy[(n - 1) / 2];
Arrays.sort(copy, (x, y) -> {
int dx = Math.abs(x - m);
int dy = Math.abs(y - m);
if (dx != dy) {
return dy - dx; // 距离大的排前面
}
return y - x; // 值大的排前面
});
List<Integer> res = new ArrayList<Integer>();
for (int i = 0; i < k; i++) {
res.add(copy[i]);
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(java.util.Arrays.toString(s.getStrongest(new int[]{1, 2, 3, 4, 5}, 2)));
// [5, 1]
System.out.println(java.util.Arrays.toString(s.getStrongest(new int[]{1, 1, 3, 5, 5}, 2)));
// [5, 5]
System.out.println(java.util.Arrays.toString(s.getStrongest(new int[]{6, 7, 11, 7, 6, 8}, 5)));
// [11, 8, 6, 6, 7] 任一排列
System.out.println(s.getStrongestSort(new Integer[]{1, 2, 3, 4, 5}, 2)); // [5, 1]
System.out.println(s.getStrongestSort(new Integer[]{1, 1, 3, 5, 5}, 2)); // [5, 5]
}
}
复杂度分析
复杂度:O(n log n),空间 O(n)。
1472. Design Browser History(设计浏览器历史记录)
题目描述
你有一个只支持单个标签页的浏览器,最开始你浏览的网页是 homepage,你可以访问其他的网站 url,
也可以在浏览历史中后退 steps 步或前进 steps 步。请你实现 BrowserHistory 类:
- BrowserHistory(string homepage):用 homepage 初始化浏览器类;
voidvisit(string url):从当前页跳转访问 url 对应的页面,执行此操作会把浏览历史前进的记录全部删除;- string
back(intsteps):在浏览历史中后退steps步,如果steps超出边界则只后退边界,返回后退后的 url; - string forward(
intsteps):在浏览历史中前进steps步,同样受限。
示例
["BrowserHistory","visit","visit","visit","back","back","forward","visit","forward","back","back"]
[["leetcode.com"],["google.com"],["facebook.com"],["youtube.com"],[1],[1],[1],["linkedin.com"],[2],[2],[7]]
-> [null,null,null,null,"facebook.com","google.com","facebook.com",null,"linkedin.com","google.com","leetcode.com"]
约束:
- 最多调用 5000 次 visit、
back和 forward。
解题思路
解法一:双栈/数组 + 游标(主提交版本)
用 ArrayList history 保存访问历史,pos 指向当前页下标。
visit(url):截断 pos 之后的记录(前进记录作废),加入 url,pos 指向它。
back(steps):pos = max(0, pos - steps),返回 history.get(pos)。
forward(steps):pos = min(len-1, pos + steps),返回 history.get(pos)。
复杂度:visit O(1) 均摊(截断用 subList 清空),back/forward O(1)。
解法二:双向链表节点(对照)
每个节点保存 url 与 prev/next 指针;visit 时新建节点接到当前之后;
back/forward 沿链表移动至多 steps 步。
复杂度:visit O(1),back/forward O(steps)。
代码实现
java
import java.util.ArrayList;
import java.util.List;
public class Solution {
public static class BrowserHistory {
private List<String> history;
private int pos;
public BrowserHistory(String homepage) {
history = new ArrayList<String>();
history.add(homepage);
pos = 0;
}
public void visit(String url) {
// 清空前进记录
while (history.size() - 1 > pos) {
history.remove(history.size() - 1);
}
history.add(url);
pos++;
}
public String back(int steps) {
pos = Math.max(0, pos - steps);
return history.get(pos);
}
public String forward(int steps) {
pos = Math.min(history.size() - 1, pos + steps);
return history.get(pos);
}
}
public static class BrowserHistoryList {
private static class Node {
String url;
Node prev;
Node next;
Node(String u) {
url = u;
}
}
private Node cur;
public BrowserHistoryList(String homepage) {
cur = new Node(homepage);
}
public void visit(String url) {
Node node = new Node(url);
node.prev = cur;
cur.next = node; // 断开前进记录
cur = node;
}
public String back(int steps) {
int s = 0;
while (s < steps && cur.prev != null) {
cur = cur.prev;
s++;
}
return cur.url;
}
public String forward(int steps) {
int s = 0;
while (s < steps && cur.next != null) {
cur = cur.next;
s++;
}
return cur.url;
}
}
public static void main(String[] args) {
BrowserHistory bh = new BrowserHistory("leetcode.com");
bh.visit("google.com");
bh.visit("facebook.com");
bh.visit("youtube.com");
System.out.println(bh.back(1)); // facebook.com
System.out.println(bh.back(1)); // google.com
System.out.println(bh.forward(1)); // facebook.com
bh.visit("linkedin.com");
System.out.println(bh.forward(2)); // linkedin.com
System.out.println(bh.back(2)); // google.com
System.out.println(bh.back(7)); // leetcode.com
BrowserHistoryList bh2 = new BrowserHistoryList("leetcode.com");
bh2.visit("google.com");
bh2.visit("facebook.com");
bh2.visit("youtube.com");
System.out.println(bh2.back(1)); // facebook.com
System.out.println(bh2.back(1)); // google.com
System.out.println(bh2.forward(1)); // facebook.com
bh2.visit("linkedin.com");
System.out.println(bh2.forward(2)); // linkedin.com
System.out.println(bh2.back(2)); // google.com
System.out.println(bh2.back(7)); // leetcode.com
}
}
复杂度分析
复杂度:visit O(1) 均摊(截断用 subList 清空),back/forward O(1)。
复杂度:visit O(1),back/forward O(steps)。
1473. Paint House III(粉刷房子 III)
题目描述
在一个小城市里,有 m 个房子排成一排,你需要给每个房子涂上 n 种颜色之一(颜色编号为 1 到 n)。
有的房子去年夏天已经涂过颜色了,所以这些房子不可以被重新涂色。
我们将连续相同颜色尽可能多的房子称为一个街区。
给你一个数组 houses(0 表示未涂色)、一个 m * n 的矩阵 cost 和一个整数 target,其中:
- housesi 是第
i个房子的颜色,0 表示还没有涂色; cost[i][j]是将第i个房子涂成颜色j+1 的花费。
请你返回房子涂色方案的最小总花费,使得每个房子都被涂色后,恰好组成target个街区。
如果没有可用的涂色方案,请返回 -1。
示例 1
houses = [0,0,0,0,0], cost = [[1,10],[10,1],[10,1],[1,10],[5,1]], m = 5, n = 2, target = 3 -> 9
示例 2
houses = [0,2,1,2,0], ... -> 11
示例 4
houses = [3,1,2,3], cost = [[1,1,1],...], m = 4, n = 3, target = 3 -> -1
约束:
- 1 <=
m<= 100;1 <=n<= 20;1 <=target<=m;0 <= housesi <=n。
解题思路
解法一:三维 DP(主提交版本)
dpict = 处理完前 i 个房子,第 i 个房子颜色为 c(1...n),总街区数为 t 的最小花费。
初始化:-inf。
转移:若 housei 已固定(housesi != 0),只能取 c == housesi,花费不变;
否则 c 可取 1...n,花费 costic-1。
对上一颜色 pc:若 pc == c 街区数不变(t),否则街区数 +1(t-1 -> t,即 dpict = min(dpi-1pct-1, ...))。
答案 = min over c of dpmctarget(>= 0 才有效)。
复杂度:O(m * n^2 * target)。
解法二:滚动数组优化(对照)
只用到上一行,将 i 维压缩为 2 层滚动。
复杂度:O(m * n^2 * target),空间 O(n * target)。
代码实现
java
import java.util.Arrays;
public class Solution {
public int minCost(int[] houses, int[][] cost, int m, int n, int target) {
int INF = Integer.MAX_VALUE / 2;
// dp[house][color][blocks]
int[][][] dp = new int[m + 1][n + 1][target + 1];
for (int[][] a : dp) {
for (int[] b : a) {
Arrays.fill(b, INF);
}
}
// 第 0 个房子之前无所谓颜色,看作"无颜色"(0)且 0 个街区
// 用虚拟第 0 层:初始 dp[0][0][0] = 0
for (int c = 0; c <= n; c++) {
dp[0][c][0] = 0;
}
for (int i = 1; i <= m; i++) {
int fixed = houses[i - 1]; // 0 表示未涂
for (int c = 1; c <= n; c++) {
if (fixed != 0 && fixed != c) {
continue; // 已固定颜色且不是 c,跳过
}
int add = (fixed == 0) ? cost[i - 1][c - 1] : 0;
for (int t = 1; t <= target; t++) {
// 上一房子颜色 pc
for (int pc = 0; pc <= n; pc++) {
if (pc == c) {
// 颜色相同,街区数不变
if (dp[i - 1][pc][t] != INF) {
dp[i][c][t] = Math.min(dp[i][c][t], dp[i - 1][pc][t] + add);
}
} else {
// 颜色不同,街区数 +1(从 t-1 到 t)
if (t - 1 >= 0 && dp[i - 1][pc][t - 1] != INF) {
dp[i][c][t] = Math.min(dp[i][c][t], dp[i - 1][pc][t - 1] + add);
}
}
}
}
}
}
int ans = INF;
for (int c = 1; c <= n; c++) {
ans = Math.min(ans, dp[m][c][target]);
}
return ans >= INF ? -1 : ans;
}
public int minCostRoll(int[] houses, int[][] cost, int m, int n, int target) {
int INF = Integer.MAX_VALUE / 2;
int[][] dp = new int[n + 1][target + 1];
for (int[] row : dp) {
Arrays.fill(row, INF);
}
for (int c = 0; c <= n; c++) {
dp[c][0] = 0; // 虚拟第 0 层
}
for (int i = 1; i <= m; i++) {
int[][] ndp = new int[n + 1][target + 1];
for (int[] row : ndp) {
Arrays.fill(row, INF);
}
int fixed = houses[i - 1];
for (int c = 1; c <= n; c++) {
if (fixed != 0 && fixed != c) {
continue;
}
int add = (fixed == 0) ? cost[i - 1][c - 1] : 0;
for (int t = 1; t <= target; t++) {
// 来自上一颜色 pc == c:街区数不变
if (dp[c][t] != INF) {
ndp[c][t] = Math.min(ndp[c][t], dp[c][t] + add);
}
// 来自上一颜色 pc != c:街区数从 t-1 到 t
for (int pc = 1; pc <= n; pc++) {
if (pc != c && dp[pc][t - 1] != INF) {
ndp[c][t] = Math.min(ndp[c][t], dp[pc][t - 1] + add);
}
}
}
}
dp = ndp;
}
int ans = INF;
for (int c = 1; c <= n; c++) {
ans = Math.min(ans, dp[c][target]);
}
return ans >= INF ? -1 : ans;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.minCost(new int[]{0, 0, 0, 0, 0},
new int[][]{{1, 10}, {10, 1}, {10, 1}, {1, 10}, {5, 1}}, 5, 2, 3)); // 9
System.out.println(s.minCost(new int[]{0, 2, 1, 2, 0},
new int[][]{{1, 10}, {10, 1}, {10, 1}, {1, 10}, {5, 1}}, 5, 2, 3)); // 11
System.out.println(s.minCost(new int[]{3, 1, 2, 3},
new int[][]{{1, 1, 1}, {1, 1, 1}, {1, 1, 1}, {1, 1, 1}}, 4, 3, 3)); // -1
System.out.println(s.minCostRoll(new int[]{0, 0, 0, 0, 0},
new int[][]{{1, 10}, {10, 1}, {10, 1}, {1, 10}, {5, 1}}, 5, 2, 3)); // 9
System.out.println(s.minCostRoll(new int[]{0, 2, 1, 2, 0},
new int[][]{{1, 10}, {10, 1}, {10, 1}, {1, 10}, {5, 1}}, 5, 2, 3)); // 11
System.out.println(s.minCostRoll(new int[]{3, 1, 2, 3},
new int[][]{{1, 1, 1}, {1, 1, 1}, {1, 1, 1}, {1, 1, 1}}, 4, 3, 3)); // -1
}
}
复杂度分析
复杂度:O(m * n^2 * target)。
复杂度:O(m * n^2 * target),空间 O(n * target)。
1474. Delete N Nodes After M Nodes of a Linked List(删除链表 M 个节点之后的 N 个节点)
题目描述
给定链表 head 和两个整数 m 和 n。遍历该链表并按照如下方式删除节点:
- 开始时以头节点作为当前节点;
- 保留以当前节点开始的前
m个节点; - 删除接下来的
n个节点; - 重复步骤 2 和 3,直到到达链表结尾。
在删除了指定结点之后,返回修改过后的链表的头节点。
示例 1
head = [1,2,3,4,5,6,7,8,9,10,11,12,13], m = 2, n = 3 -> [1,2,6,7,11,12]
示例 2
head = [1,2,3,4,5,6,7,8,9,10,11], m = 1, n = 3 -> [1,5,9]
示例 4
head = [9,3,7,7,9,10,8,2], m = 1, n = 2 -> [9,7,8]
约束:
- 节点数 1, 10\^4;1 <=
Node.val<= 10^6;1 <=m,n<= 1000。
解题思路
解法一:双指针模拟保留/删除(主提交版本)
用一个指针 cur 每次先走 m 步(保留段末尾),若中途到链表尾则结束;
再从保留段末尾继续走 n 步(跳过要删除的节点),把 cur.next 接到删除段之后的节点。
复杂度:O(nodes),空间 O(1)。
解法二:计数循环一(对照)
用一个变量 state 与计数 cnt 交替执行步数,实现与解法一等价但用单一循环。
复杂度:O(nodes),空间 O(1)。
代码实现
java
public class Solution {
public static class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
public ListNode deleteNodes(ListNode head, int m, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode cur = dummy;
while (cur != null) {
// 保留 m 步
ListNode keep = cur;
int keepCount = 0;
while (keepCount < m && cur.next != null) {
cur = cur.next;
keepCount++;
}
if (cur.next == null) {
break; // 已到链表尾,无需再删除
}
// 跳过 n 个要删除的节点
int deleteCount = 0;
while (deleteCount < n && cur.next != null) {
cur = cur.next;
deleteCount++;
}
keep.next = cur.next; // 接上删除段之后的节点
}
return dummy.next;
}
public ListNode deleteNodesLoop(ListNode head, int m, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode prev = dummy;
ListNode cur = head;
while (cur != null) {
// 保留 m 个
int k = 0;
while (cur != null && k < m) {
prev = cur;
cur = cur.next;
k++;
}
// 删除 n 个
k = 0;
while (cur != null && k < n) {
cur = cur.next;
k++;
}
prev.next = cur; // 保留段末尾直接接上删除段之后
}
return dummy.next;
}
private static ListNode build(int[] a) {
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
for (int v : a) {
cur.next = new ListNode(v);
cur = cur.next;
}
return dummy.next;
}
private static String toStr(ListNode head) {
StringBuilder sb = new StringBuilder();
while (head != null) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(head.val);
head = head.next;
}
return sb.toString();
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(toStr(s.deleteNodes(build(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}), 2, 3)));
// 1,2,6,7,11,12
System.out.println(toStr(s.deleteNodes(build(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}), 1, 3)));
// 1,5,9
System.out.println(toStr(s.deleteNodes(build(new int[]{9, 3, 7, 7, 9, 10, 8, 2}), 1, 2)));
// 9,7,8
System.out.println(toStr(s.deleteNodesLoop(build(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}), 2, 3)));
// 1,2,6,7,11,12
System.out.println(toStr(s.deleteNodesLoop(build(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}), 1, 3)));
// 1,5,9
System.out.println(toStr(s.deleteNodesLoop(build(new int[]{9, 3, 7, 7, 9, 10, 8, 2}), 1, 2)));
// 9,7,8
}
}
复杂度分析
复杂度:O(nodes),空间 O(1)。
1475. Final Prices With a Special Discount in a Shop(商品折扣后的最终价格)
题目描述
给你一个数组 prices,其中 prices[i] 是商店里第 i 件商品的价格。
商店里正在进行促销活动,如果你要买第 i 件商品,那么你可以得到与 prices[j] 相等的折扣,
其中 j 是满足 j > i 且 prices[j] <= prices[i] 的最小下标,如果没有满足条件的 j,你将没有任何折扣。
请你返回一个数组,数组中第 i 个元素是折扣后你购买商品 i 最终需要支付的价格。
示例 1
prices = [8,4,6,2,3] -> [4,2,4,2,3]
示例 2
prices = [1,2,3,4,5] -> [1,2,3,4,5]
示例 3
prices = [10,1,1,6] -> [9,0,1,6]
约束:
- 1 <=
prices.length<= 500;1 <=prices[i]<= 10^3。
解题思路
解法一:单调栈(主提交版本)
从右向左遍历,维护一个单调递增栈(栈内价格递增,栈顶是「右侧第一个 <= 当前价格」的候选)。
对当前 pricesi,栈顶即折扣价;出栈所有 > pricesi 的元素(它们不可能成为更左边元素
的第一个 <= 的候选,因为当前 pricesi 更小且更靠左)。
复杂度:O(n),空间 O(n)。
解法二:两重循环暴力(对照)
对每个 i 向右找第一个 <= pricesi 的 j。
复杂度:O(n^2)(n<=500 可行)。
代码实现
java
import java.util.ArrayDeque;
public class Solution {
public int[] finalPrices(int[] prices) {
int n = prices.length;
int[] ans = new int[n];
ArrayDeque<Integer> stack = new ArrayDeque<Integer>(); // 存下标,价格单调递增
for (int i = n - 1; i >= 0; i--) {
// 弹出所有价格 > prices[i] 的(它们对更左的元素也会被 prices[i] 屏蔽)
while (!stack.isEmpty() && prices[stack.peek()] > prices[i]) {
stack.pop();
}
int discount = stack.isEmpty() ? 0 : prices[stack.peek()];
ans[i] = prices[i] - discount;
stack.push(i);
}
return ans;
}
public int[] finalPricesBrute(int[] prices) {
int n = prices.length;
int[] ans = new int[n];
for (int i = 0; i < n; i++) {
int discount = 0;
for (int j = i + 1; j < n; j++) {
if (prices[j] <= prices[i]) {
discount = prices[j];
break;
}
}
ans[i] = prices[i] - discount;
}
return ans;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(java.util.Arrays.toString(s.finalPrices(new int[]{8, 4, 6, 2, 3})));
// [4, 2, 4, 2, 3]
System.out.println(java.util.Arrays.toString(s.finalPrices(new int[]{1, 2, 3, 4, 5})));
// [1, 2, 3, 4, 5]
System.out.println(java.util.Arrays.toString(s.finalPrices(new int[]{10, 1, 1, 6})));
// [9, 0, 1, 6]
System.out.println(java.util.Arrays.toString(s.finalPricesBrute(new int[]{8, 4, 6, 2, 3})));
// [4, 2, 4, 2, 3]
System.out.println(java.util.Arrays.toString(s.finalPricesBrute(new int[]{10, 1, 1, 6})));
// [9, 0, 1, 6]
}
}
复杂度分析
复杂度:O(n),空间 O(n)。
复杂度:O(n^2)(n<=500 可行)。
1476. Subrectangle Queries(子矩形查询)
题目描述
请你实现一个类 SubrectangleQueries,它的构造函数的参数是一个 rows x cols 的矩形
(用整数矩阵表示),并支持以下两种操作:
voidupdateSubrectangle(introw1,intcol1,introw2,intcol2,intnewValue)
------用newValue更新以 (row1,col1) 为左上角、(row2,col2) 为右下角的子矩形;intgetValue(introw,intcol) ------返回矩形中坐标 (row,col) 的当前值。
示例 1
["SubrectangleQueries","getValue","updateSubrectangle","getValue","getValue","updateSubrectangle","getValue","getValue"]
[[[[1,2,1],[4,3,4],[3,2,1],[1,1,1]]],[0,2],[0,0,3,2,5],[0,2],[3,1],[3,0,3,2,10],[3,1],[0,2]]
-> [null,1,null,5,5,null,10,5]
约束:
- 最多有 500 次
updateSubrectangle和getValue操作;1 <= rows,cols<= 100。
解题思路
解法一:直接改矩阵(主提交版本)
用 int\[\]\[\] rect 保存矩阵;updateSubrectangle 直接双重循环覆盖子矩形;
getValue 直接返回 rectrowcol。
分析:更新 O(rows*cols)(最坏 10^4),500 次操作内完全可行。
解法二:记录操作日志(对照)
不直接改矩阵,每次 update 只记录 (row1, col1, row2, col2, newValue);
getValue(row, col) 倒序遍历操作日志,第一个覆盖 (row,col) 的操作值即为答案,
若均未覆盖则返回初始矩阵值。
更新 O(1),查询 O(#updates)。
代码实现
java
import java.util.ArrayList;
import java.util.List;
public class Solution {
public static class SubrectangleQueries {
private int[][] rect;
public SubrectangleQueries(int[][] rectangle) {
rect = rectangle;
}
public void updateSubrectangle(int row1, int col1, int row2, int col2, int newValue) {
for (int r = row1; r <= row2; r++) {
for (int c = col1; c <= col2; c++) {
rect[r][c] = newValue;
}
}
}
public int getValue(int row, int col) {
return rect[row][col];
}
}
public static class SubrectangleQueriesLog {
private int[][] base;
private List<int[]> ops; // {row1, col1, row2, col2, newValue}
public SubrectangleQueriesLog(int[][] rectangle) {
base = rectangle;
ops = new ArrayList<int[]>();
}
public void updateSubrectangle(int row1, int col1, int row2, int col2, int newValue) {
ops.add(new int[]{row1, col1, row2, col2, newValue});
}
public int getValue(int row, int col) {
for (int i = ops.size() - 1; i >= 0; i--) {
int[] op = ops.get(i);
if (row >= op[0] && row <= op[2] && col >= op[1] && col <= op[3]) {
return op[4];
}
}
return base[row][col];
}
}
public static void main(String[] args) {
int[][] rect = {{1, 2, 1}, {4, 3, 4}, {3, 2, 1}, {1, 1, 1}};
SubrectangleQueries srq = new SubrectangleQueries(rect);
System.out.println(srq.getValue(0, 2)); // 1
srq.updateSubrectangle(0, 0, 3, 2, 5);
System.out.println(srq.getValue(0, 2)); // 5
System.out.println(srq.getValue(3, 1)); // 5
srq.updateSubrectangle(3, 0, 3, 2, 10);
System.out.println(srq.getValue(3, 1)); // 10
System.out.println(srq.getValue(0, 2)); // 5
SubrectangleQueriesLog srq2 = new SubrectangleQueriesLog(rect);
System.out.println(srq2.getValue(0, 2)); // 1
srq2.updateSubrectangle(0, 0, 3, 2, 5);
System.out.println(srq2.getValue(0, 2)); // 5
System.out.println(srq2.getValue(3, 1)); // 5
srq2.updateSubrectangle(3, 0, 3, 2, 10);
System.out.println(srq2.getValue(3, 1)); // 10
System.out.println(srq2.getValue(0, 2)); // 5
}
}
复杂度分析
1477. Find Two Non-overlapping Sub-arrays Each With Target Sum(找两个和为目标值且不重叠的子数组)
题目描述
给你一个整数数组 arr 和一个整数值 target。
请你在 arr 中找两个互不重叠的子数组且它们的和都等于 target。
可能会有多种方案,请你返回满足要求的两个子数组长度和的最小值。
请返回满足要求的最小长度和,如果无法找到这样的两个子数组,请返回 -1。
示例 1
arr = [3,2,2,4,3], target = 3 -> 2
示例 2
arr = [7,3,4,7], target = 7 -> 2
示例 3
arr = [4,3,2,6,2,3,4], target = 6 -> -1
示例 5
arr = [3,1,1,1,5,1,2,1], target = 3 -> 3
约束:
- 1 <=
arr.length<= 10^5;1 <=arr[i]<= 1000;1 <=target<= 10^8。
解题思路
解法一:前缀和 + 左右最小长度(主提交版本)
由于元素为正数,可以用滑动窗口找到所有和为 target 的区间 l, r。
定义 lefti = arr0...i-1 中"和为 target 的最短子数组长度"(没有则 INF);
处理时按右端点递增扫描:对每个右端点 r,若窗口和为 target 则长度 len = r-l+1,
使 leftr+1 = min(leftr, len)。
同理从小到大计算右半部分 righti = arri...n-1 中"和为 target 的最短子数组长度"。
枚举分割点 i(0...n),答案 = min(lefti + righti)。
复杂度:O(n),空间 O(n)。
解法二:仅从左到右扫描并配对(对照)
用滑动窗口找区间并把每个区间存入 (l, r, len),再对区间按某种序检查
是否与先前区间不重叠且取最小和。这里采用"对每个以 i 结尾的区间,与
之前所有以 < i 结尾的区间配对",用前缀最小长度数组加速。
代码实现
java
import java.util.Arrays;
public class Solution {
public int minSumOfLengths(int[] arr, int target) {
int n = arr.length;
int INF = Integer.MAX_VALUE / 2;
int[] left = new int[n + 1];
Arrays.fill(left, INF);
int sum = 0, l = 0;
// 计算 left[i]:arr[0..i-1] 内和为 target 的最短子数组长度
for (int r = 0; r < n; r++) {
sum += arr[r];
while (sum > target) {
sum -= arr[l];
l++;
}
left[r + 1] = left[r];
if (sum == target) {
left[r + 1] = Math.min(left[r + 1], r - l + 1);
}
}
int[] right = new int[n + 1];
Arrays.fill(right, INF);
sum = 0;
int rr = n;
// 计算 right[i]:arr[i..n-1] 内和为 target 的最短子数组长度
for (int l2 = n - 1; l2 >= 0; l2--) {
sum += arr[l2];
while (sum > target) {
sum -= arr[rr - 1];
rr--;
}
right[l2] = right[l2 + 1];
if (sum == target) {
right[l2] = Math.min(right[l2], rr - l2);
}
}
int ans = INF;
for (int i = 0; i <= n; i++) {
if (left[i] < INF && right[i] < INF) {
ans = Math.min(ans, left[i] + right[i]);
}
}
return ans >= INF ? -1 : ans;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.minSumOfLengths(new int[]{3, 2, 2, 4, 3}, 3)); // 2
System.out.println(s.minSumOfLengths(new int[]{7, 3, 4, 7}, 7)); // 2
System.out.println(s.minSumOfLengths(new int[]{4, 3, 2, 6, 2, 3, 4}, 6)); // -1
System.out.println(s.minSumOfLengths(new int[]{5, 5, 4, 4, 5}, 3)); // -1
System.out.println(s.minSumOfLengths(new int[]{3, 1, 1, 1, 5, 1, 2, 1}, 3)); // 3
}
}
复杂度分析
复杂度:O(n),空间 O(n)。
1478. Allocate Mailboxes(安排邮筒)
题目描述
给你一个房屋数组 houses 和一个整数 k,其中 housesi 是第 i 栋房子在一条街上的位置,
现需要在这条街上安排 k 个邮筒。
请你返回每栋房子与离它最近的邮筒之间的距离的最小总和。
答案保证在 32 位有符号整数范围以内。
示例 1
houses = [1,4,8,10,20], k = 3 -> 5
示例 2
houses = [2,3,5,12,18], k = 2 -> 9
示例 3
houses = [7,4,6,1], k = 1 -> 8
示例 4
houses = [3,6,14,10], k = 4 -> 0
约束:
- 1 <=
n<= 100;1 <= housesi <= 10^4;1 <=k<=n。
解题思路
解法一:排序 + 区间成本 + 划分 DP(主提交版本)
邮筒最优放在区间房子的中位位置。先对 houses 排序。
costij = 用一个邮筒服务 housesi...j 的最小总距离(放中位数的位置):
递推 costij = costij-1 + housesj - houses(i+j)/2。
DP:dpti = 用 t 个邮筒服务前 i 个房子(下标 0...i-1)的最小总距离,
dpti = min over j < i of dpt-1j + costji-1。
答案 dpkn。
复杂度:O(k * n^2)。
解法二:记忆化递归(对照)
f(t, i) 表示用 t 个邮筒服务 houses0...i-1;与解法一同构,用 memo 记录。
复杂度:O(k * n^2)。
代码实现
java
import java.util.Arrays;
public class Solution {
public int minDistance(int[] houses, int k) {
int n = houses.length;
Arrays.sort(houses);
int[][] cost = new int[n][n];
for (int i = 0; i < n; i++) {
cost[i][i] = 0;
for (int j = i + 1; j < n; j++) {
cost[i][j] = cost[i][j - 1] + houses[j] - houses[(i + j) / 2];
}
}
int INF = Integer.MAX_VALUE / 2;
int[] dp = new int[n + 1]; // dp[i] = 前 i 个房子
Arrays.fill(dp, INF);
dp[0] = 0;
// 使用一个邮筒时
for (int t = 1; t <= k; t++) {
int[] ndp = new int[n + 1];
Arrays.fill(ndp, INF);
ndp[0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
if (dp[j] != INF) {
ndp[i] = Math.min(ndp[i], dp[j] + cost[j][i - 1]);
}
}
}
dp = ndp;
}
return dp[n];
}
public int minDistanceMemo(int[] houses, int k) {
int n = houses.length;
Arrays.sort(houses);
int[][] cost = new int[n][n];
for (int i = 0; i < n; i++) {
cost[i][i] = 0;
for (int j = i + 1; j < n; j++) {
cost[i][j] = cost[i][j - 1] + houses[j] - houses[(i + j) / 2];
}
}
int[][] memo = new int[k + 1][n + 1];
for (int[] row : memo) {
Arrays.fill(row, -1);
}
return rec(k, n, cost, memo);
}
private int rec(int t, int i, int[][] cost, int[][] memo) {
if (i == 0) {
return 0;
}
if (t == 0) {
return Integer.MAX_VALUE / 2; // 没有邮筒但还有房子
}
if (memo[t][i] != -1) {
return memo[t][i];
}
int best = Integer.MAX_VALUE / 2;
for (int j = 0; j < i; j++) {
int sub = rec(t - 1, j, cost, memo);
if (sub != Integer.MAX_VALUE / 2) {
best = Math.min(best, sub + cost[j][i - 1]);
}
}
memo[t][i] = best;
return best;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.minDistance(new int[]{1, 4, 8, 10, 20}, 3)); // 5
System.out.println(s.minDistance(new int[]{2, 3, 5, 12, 18}, 2)); // 9
System.out.println(s.minDistance(new int[]{7, 4, 6, 1}, 1)); // 8
System.out.println(s.minDistance(new int[]{3, 6, 14, 10}, 4)); // 0
System.out.println(s.minDistanceMemo(new int[]{1, 4, 8, 10, 20}, 3)); // 5
System.out.println(s.minDistanceMemo(new int[]{2, 3, 5, 12, 18}, 2)); // 9
System.out.println(s.minDistanceMemo(new int[]{7, 4, 6, 1}, 1)); // 8
System.out.println(s.minDistanceMemo(new int[]{3, 6, 14, 10}, 4)); // 0
}
}
复杂度分析
复杂度:O(k * n^2)。
1479. Sales by Day of the Week(周内每天的销售情况 🔒)
题目类型:数据库(SQL)
题目描述
表:Orders
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| order_id | int |
| customer_id | int |
| order_date | date |
| item_id | varchar |
| quantity | int |
+---------------+---------+
(order_id, item_id) 是该表主键(具有唯一值的列的组合)
该表包含了订单信息
order_date 是id为 item_id 的商品被id为 customer_id 的消费者订购的日期.
表:Items
+---------------------+---------+
| Column Name | Type |
+---------------------+---------+
| item_id | varchar |
| item_name | varchar |
| item_category | varchar |
+---------------------+---------+
item_id 是该表主键(具有唯一值的列)
item_name 是商品的名字
item_category 是商品的类别
你是企业主,想要获得分类商品和周内每天的销售报告。
编写解决方案,报告 **周内每天 **每个商品类别下订购了多少单位。
返回结果表单** 按商品类别排序 **。
结果格式如下例所示。
示例 1:
`**输入:**
Orders` 表:
+------------+--------------+-------------+--------------+-------------+
| order_id | customer_id | order_date | item_id | quantity |
+------------+--------------+-------------+--------------+-------------+
| 1 | 1 | 2020-06-01 | 1 | 10 |
| 2 | 1 | 2020-06-08 | 2 | 10 |
| 3 | 2 | 2020-06-02 | 1 | 5 |
| 4 | 3 | 2020-06-03 | 3 | 5 |
| 5 | 4 | 2020-06-04 | 4 | 1 |
| 6 | 4 | 2020-06-05 | 5 | 5 |
| 7 | 5 | 2020-06-05 | 1 | 10 |
| 8 | 5 | 2020-06-14 | 4 | 5 |
| 9 | 5 | 2020-06-21 | 3 | 5 |
+------------+--------------+-------------+--------------+-------------+
`Items` 表:
+------------+----------------+---------------+
| item_id | item_name | item_category |
+------------+----------------+---------------+
| 1 | LC Alg. Book | Book |
| 2 | LC DB. Book | Book |
| 3 | LC SmarthPhone | Phone |
| 4 | LC Phone 2020 | Phone |
| 5 | LC SmartGlass | Glasses |
| 6 | LC T-Shirt XL | T-Shirt |
+------------+----------------+---------------+
**输出:**
+------------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| Category | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday |
+------------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| Book | 20 | 5 | 0 | 0 | 10 | 0 | 0 |
| Glasses | 0 | 0 | 0 | 0 | 5 | 0 | 0 |
| Phone | 0 | 0 | 5 | 1 | 0 | 0 | 10 |
| T-Shirt | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+------------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
**解释:**
在周一(2020-06-01, 2020-06-08),Book分类(ids: 1, 2)下,总共销售了20个单位(10 + 10)
在周二(2020-06-02),Book分类(ids: 1, 2)下,总共销售了5个单位
在周三(2020-06-03),Phone分类(ids: 3, 4)下,总共销售了5个单位
在周四(2020-06-04),Phone分类(ids: 3, 4)下,总共销售了1个单位
在周五(2020-06-05),Book分类(ids: 1, 2)下,总共销售了10个单位,Glasses分类(ids: 5)下,总共销售了5个单位
在周六, 没有商品销售
在周天(2020-06-14, 2020-06-21),Phone分类(ids: 3, 4)下,总共销售了10个单位(5 + 5)
没有销售 T-Shirt 类别的商品
解题思路
思考
要按品类输出周一至周日的销量,没有订单的品类也应出现。以
Items为右表连接Orders,用DAYOFWEEK把数量加到对应列,再按品类分组排序。
考察GROUP BY + CASE WHEN 列转行:把每天销量按周一到周日展开为列。
代码实现
sql
# Write your MySQL query statement below
SELECT
item_category AS category,
SUM(IF(DAYOFWEEK(order_date) = '2', quantity, 0)) AS Monday,
SUM(IF(DAYOFWEEK(order_date) = '3', quantity, 0)) AS Tuesday,
SUM(IF(DAYOFWEEK(order_date) = '4', quantity, 0)) AS Wednesday,
SUM(IF(DAYOFWEEK(order_date) = '5', quantity, 0)) AS Thursday,
SUM(IF(DAYOFWEEK(order_date) = '6', quantity, 0)) AS Friday,
SUM(IF(DAYOFWEEK(order_date) = '7', quantity, 0)) AS Saturday,
SUM(IF(DAYOFWEEK(order_date) = '1', quantity, 0)) AS Sunday
FROM
Orders AS o
RIGHT JOIN Items AS i ON o.item_id = i.item_id
GROUP BY category
ORDER BY category;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1480. Running Sum of 1d Array(一维数组的动态和)
题目描述
给你一个数组 nums。数组「动态和」的计算公式为:runningSum[i] = sum(nums[0]...nums[i])。
请返回 nums 的动态和。
示例 1
nums = [1,2,3,4] -> [1,3,6,10]
示例 2
nums = [1,1,1,1,1] -> [1,2,3,4,5]
示例 3
nums = [3,1,2,10,1] -> [3,4,6,16,17]
约束:
- 1 <=
nums.length<= 1000;-10^6 <=nums[i]<= 10^6。
解题思路
解法一:原地累加(主提交版本)
从下标 1 开始,令 numsi += numsi-1,直接在原数组上累加得出前缀和。
复杂度:O(n) 时间,O(1) 额外空间。
解法二:新数组(对照)
新建 ans 数组逐一记录前缀和,不修改原数组。
复杂度:O(n) 时间,O(n) 空间。
代码实现
java
import java.util.Arrays;
public class Solution {
public int[] runningSum(int[] nums) {
for (int i = 1; i < nums.length; i++) {
nums[i] += nums[i - 1];
}
return nums;
}
public int[] runningSumNew(int[] nums) {
int n = nums.length;
int[] ans = new int[n];
int sum = 0;
for (int i = 0; i < n; i++) {
sum += nums[i];
ans[i] = sum;
}
return ans;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(Arrays.toString(s.runningSum(new int[]{1, 2, 3, 4}))); // [1,3,6,10]
System.out.println(Arrays.toString(s.runningSum(new int[]{1, 1, 1, 1, 1}))); // [1,2,3,4,5]
System.out.println(Arrays.toString(s.runningSum(new int[]{3, 1, 2, 10, 1}))); // [3,4,6,16,17]
System.out.println(Arrays.toString(s.runningSumNew(new int[]{1, 2, 3, 4}))); // [1,3,6,10]
System.out.println(Arrays.toString(s.runningSumNew(new int[]{1, 1, 1, 1, 1}))); // [1,2,3,4,5]
System.out.println(Arrays.toString(s.runningSumNew(new int[]{3, 1, 2, 10, 1}))); // [3,4,6,16,17]
}
}
复杂度分析
复杂度:O(n) 时间,O(1) 额外空间。
复杂度:O(n) 时间,O(n) 空间。