第 96 场周赛:三维形体投影面积、救生艇、索引处的解码字符串、细分图中的可到达节点
Q1、三维形体投影面积
1、题目描述
在 n x n
的网格 grid
中,我们放置了一些与 x,y,z 三轴对齐的 1 x 1 x 1
立方体。
每个值 v = grid[i][j]
表示有一列 v
个正方体叠放在格子 (i, j)
上。
现在,我们查看这些立方体在 xy
、yz
和 zx
平面上的投影。
投影 就像影子,将 三维 形体映射到一个 二维 平面上。从顶部、前面和侧面看立方体时,我们会看到 “影子”。
返回 所有三个投影的总面积 。
示例 1:
![]()
输入:[[1,2],[3,4]] 输出:17 解释:这里有该形体在三个轴对齐平面上的三个投影(“阴影部分”)。
示例 2:
输入:grid = [[2]] 输出:5
示例 3:
输入:[[1,0],[0,2]] 输出:8
提示:
n == grid.length == grid[i].length
1 <= n <= 50
0 <= grid[i][j] <= 50
2、解题思路
-
xy 平面投影:
- 每个格子只要有立方体(
grid[i][j] > 0
),就贡献 1 的面积。 - 所以
xyArea
就是所有grid[i][j] > 0
的格子数。
- 每个格子只要有立方体(
-
yz 平面投影:
- 对于每一列
j
,取该列所有行的最大值max(grid[0][j], grid[1][j], ..., grid[n-1][j])
。 yzArea
是所有列的最大值之和。
- 对于每一列
-
zx 平面投影:
- 对于每一行
i
,取该行所有列的最大值max(grid[i][0], grid[i][1], ..., grid[i][n-1])
。 zxArea
是所有行的最大值之和。
- 对于每一行
3、代码实现
C++
class Solution {
public:int projectionArea(vector<vector<int>>& grid) {int n = grid.size();int xyArea = 0, yzArea = 0, zxArea = 0;for (int i = 0; i < n; ++i) {int yzHeight = 0, zxHeight = 0;for (int j = 0; j < n; ++j) {xyArea += grid[i][j] > 0 ? 1 : 0; // xy 投影: 每个格子贡献 1yzHeight = max(yzHeight, grid[j][i]); // yz 投影: 每列最大值zxHeight = max(zxHeight, grid[i][j]); // zx 投影: 每行最大值}yzArea += yzHeight;zxArea += zxHeight;}return xyArea + yzArea + zxArea;}
};
Java
class Solution {public int projectionArea(int[][] grid) {int n = grid.length;int xyArea = 0, yzArea = 0, zxArea = 0;for (int i = 0; i < n; i++) {int yzHeight = 0, zxHeight = 0;for (int j = 0; j < n; j++) {xyArea += grid[i][j] > 0 ? 1 : 0; // xy投影yzHeight = Math.max(yzHeight, grid[j][i]); // yz投影zxHeight = Math.max(zxHeight, grid[i][j]); // zx投影}yzArea += yzHeight;zxArea += zxHeight;}return xyArea + yzArea + zxArea;}
}
Python
class Solution:def projectionArea(self, grid: List[List[int]]) -> int:n = len(grid)xyArea = sum(1 for i in range(n) for j in range(n) if grid[i][j] > 0)yzArea = sum(max(grid[i][j] for i in range(n)) for j in range(n))zxArea = sum(max(row) for row in grid)return xyArea + yzArea + zxArea
4、复杂度分析
- 时间复杂度:
O(n^2)
,需要遍历整个网格两次(行和列)。 - 空间复杂度:
O(1)
,仅需常数空间存储中间变量。
Q2、救生艇
1、题目描述
给定数组 people
。people[i]
表示第 i
个人的体重 ,船的数量不限,每艘船可以承载的最大重量为 limit
。
每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit
。
返回 承载所有人所需的最小船数 。
示例 1:
输入:people = [1,2], limit = 3 输出:1 解释:1 艘船载 (1, 2)
示例 2:
输入:people = [3,2,2,1], limit = 3 输出:3 解释:3 艘船分别载 (1, 2), (2) 和 (3)
示例 3:
输入:people = [3,5,3,4], limit = 5 输出:4 解释:4 艘船分别载 (3), (3), (4), (5)
提示:
1 <= people.length <= 5 * 104
1 <= people[i] <= limit <= 3 * 104
2、解题思路
-
贪心算法 :
- 将数组排序,这样我们可以尝试将最轻和最重的人配对。
- 如果最轻和最重的人可以一起乘船(
people[light] + people[heavy] <= limit
),则配对成功,船数加一,双指针移动。 - 如果无法配对,则最重的人单独乘船,船数加一,右指针移动。
3、代码实现
C++
class Solution {
public:int numRescueBoats(vector<int>& people, int limit) {int ans = 0;sort(people.begin(), people.end()); // 排序int light = 0, heavy = people.size() - 1;while (light <= heavy) {if (people[light] + people[heavy] <= limit) {// 可以配对++light;--heavy;} else // 无法配对, 最重的人单独乘船{--heavy;}++ans; // 船数 +1}return ans;}
};
Java
class Solution {public int numRescueBoats(int[] people, int limit) {int ans = 0;Arrays.sort(people); // 排序int light = 0, heavy = people.length - 1;while (light <= heavy) {if (people[light] + people[heavy] <= limit) { // 可以配对light++;heavy--;} else { // 无法配对,最重的人单独乘船heavy--;}ans++; // 船数加一}return ans;}
}
Python
class Solution:def numRescueBoats(self, people: List[int], limit: int) -> int:people.sort() # 排序light, heavy = 0, len(people) - 1ans = 0while light <= heavy:if people[light] + people[heavy] <= limit: # 可以配对light += 1heavy -= 1else: # 无法配对,最重的人单独乘船heavy -= 1ans += 1 # 船数加一return ans
4、复杂度分析
- 时间复杂度:
O(n log n)
,排序的时间复杂度。 - 空间复杂度:
O(1)
,仅需常数空间。
Q3、索引处的解码字符串
1、题目描述
给定一个编码字符串 s
。请你找出 解码字符串 并将其写入磁带。解码时,从编码字符串中 每次读取一个字符 ,并采取以下步骤:
- 如果所读的字符是字母,则将该字母写在磁带上。
- 如果所读的字符是数字(例如
d
),则整个当前磁带总共会被重复写d-1
次。
现在,对于给定的编码字符串 s
和索引 k
,查找并返回解码字符串中的第 k
个字母。
示例 1:
输入:s = "leet2code3", k = 10 输出:"o" 解释: 解码后的字符串为 "leetleetcodeleetleetcodeleetleetcode"。 字符串中的第 10 个字母是 "o"。
示例 2:
输入:s = "ha22", k = 5 输出:"h" 解释: 解码后的字符串为 "hahahaha"。第 5 个字母是 "h"。
示例 3:
输入:s = "a2345678999999999999999", k = 1 输出:"a" 解释: 解码后的字符串为 "a" 重复 8301530446056247680 次。第 1 个字母是 "a"。
提示:
2 <= s.length <= 100
s
只包含小写字母与数字2
到9
。s
以字母开头。1 <= k <= 109
- 题目保证
k
小于或等于解码字符串的长度。- 解码后的字符串保证少于
263
个字母。
2、解题思路
-
正向计算总长度:
- 遍历字符串
s
,计算解码后的字符串总长度size
。 - 遇到字母时,
size
加 1。 - 遇到数字时,
size
乘以该数字。
- 遍历字符串
-
逆向定位第
k
个字母:- 从后向前遍历字符串
s
,逐步缩小size
和k
的范围。 k %= size
,如果k == 0
且当前字符是字母,则返回该字母。- 如果当前字符是数字,则
size /= d
(逆向操作)。 - 否则
size--
(逆向操作)。
- 从后向前遍历字符串
3、代码实现
C++
class Solution {
public:string decodeAtIndex(string s, int k) {long size = 0;int n = s.size();// 计算解码后的字符串总长度for (int i = 0; i < n; ++i) {if (isdigit(s[i])) {size *= s[i] - '0';} else {++size;}}// 逆向定位第 k 个字母for (int i = n - 1; i >= 0; --i) {k %= size;if (k == 0 && isalpha(s[i])) {return string(1, s[i]);}if (isdigit(s[i])) {size /= s[i] - '0';} else {--size;}}return ""; // 不会执行, 题目保证 k 有效}
};
Java
class Solution {public String decodeAtIndex(String s, int k) {long size = 0;int n = s.length();// 计算解码后的字符串总长度for (int i = 0; i < n; ++i) {char c = s.charAt(i);if (Character.isDigit(c))size *= c - '0';elsesize++;}// 逆向定位第 k 个字母for (int i = n - 1; i >= 0; --i) {char c = s.charAt(i);k %= size;if (k == 0 && Character.isLetter(c))return String.valueOf(c);if (Character.isDigit(c))size /= c - '0';elsesize--;}return ""; // 不会执行,题目保证 k 有效}
}
Python
class Solution:def decodeAtIndex(self, s: str, k: int) -> str:size = 0n = len(s)# 计算解码后的字符串总长度for c in s:if c.isdigit():size *= int(c)else:size += 1# 逆向定位第 k 个字母for c in reversed(s):k %= sizeif k == 0 and c.isalpha():return cif c.isdigit():size //= int(c)else:size -= 1return "" # 不会执行,题目保证 k 有效
4、复杂度分析
- 时间复杂度:
O(n)
,其中n
是字符串s
的长度。 - 空间复杂度:
O(1)
,仅使用常数空间。
Q4、细分图中的可到达节点
1、题目描述
给你一个无向图(原始图),图中有 n
个节点,编号从 0
到 n - 1
。你决定将图中的每条边 细分 为一条节点链,每条边之间的新节点数各不相同。
图用由边组成的二维数组 edges
表示,其中 edges[i] = [ui, vi, cnti]
表示原始图中节点 ui
和 vi
之间存在一条边,cnti
是将边 细分 后的新节点总数。注意,cnti == 0
表示边不可细分。
要 细分 边 [ui, vi]
,需要将其替换为 (cnti + 1)
条新边,和 cnti
个新节点。新节点为 x1
, x2
, …, xcnti
,新边为 [ui, x1]
, [x1, x2]
, [x2, x3]
, …, [xcnti-1, xcnti]
, [xcnti, vi]
。
现在得到一个 新的细分图 ,请你计算从节点 0
出发,可以到达多少个节点?如果节点间距离是 maxMoves
或更少,则视为 可以到达 。
给你原始图和 maxMoves
,返回 新的细分图中从节点 0
出发 *可到达的节点数* 。
示例 1:
![]()
输入:edges = [[0,1,10],[0,2,1],[1,2,2]], maxMoves = 6, n = 3 输出:13 解释:边的细分情况如上图所示。 可以到达的节点已经用黄色标注出来。
示例 2:
输入:edges = [[0,1,4],[1,2,6],[0,2,8],[1,3,1]], maxMoves = 10, n = 4 输出:23
示例 3:
输入:edges = [[1,2,4],[1,4,5],[1,3,1],[2,3,4],[3,4,5]], maxMoves = 17, n = 5 输出:1 解释:节点 0 与图的其余部分没有连通,所以只有节点 0 可以到达。
提示:
0 <= edges.length <= min(n * (n - 1) / 2, 104)
edges[i].length == 3
0 <= ui < vi < n
- 图中 不存在平行边
0 <= cnti <= 104
0 <= maxMoves <= 109
1 <= n <= 3000
2、解题思路
- 构建邻接表:
- 将原始图的每条边及其细分节点数存入邻接表。
- Dijkstra算法:
- 使用优先队列(最小堆)来遍历图,计算从节点0到其他节点的最短路径。
- 在遍历过程中,记录每个节点是否被访问过,以及从节点0到该节点的最短距离。
- 统计可到达节点:
- 原始节点:如果从节点0到该节点的最短距离不超过
maxMoves
,则该节点可到达。 - 细分节点:对于每条边
[u, v]
,计算从u
和v
出发可以覆盖的细分节点数,取最小值。
- 原始节点:如果从节点0到该节点的最短距离不超过
3、代码实现
C++
class Solution {
private:// 自定义函数:将两个节点的编号编码为一个唯一的整数,// 用于在哈希表中存储边的使用情况int encode(int u, int v, int n) { return u * n + v; }public:int reachableNodes(vector<vector<int>>& edges, int maxMoves, int n) {// 构建邻接表,存储图的边和细分节点数vector<vector<pair<int, int>>> adList(n);for (auto& edge : edges) {int u = edge[0], v = edge[1], nodes = edge[2];// 无向图,双向存储adList[u].emplace_back(v, nodes);adList[v].emplace_back(u, nodes);}// used: 记录每条边已经覆盖的细分节点数unordered_map<int, int> used;// visited: 记录已经访问过的原始节点unordered_set<int> visited;// reachableNodes: 统计可到达的节点数int reachableNodes = 0;// 优先队列 (最小堆), 用于 Dijkstra 算法, 存储 (从节点0到当前节点的步数,// 当前节点编号)priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;pq.emplace(0, 0); // 初始状态: 步数 0, 节点 0// Dijkstra 算法遍历图while (!pq.empty() && pq.top().first <= maxMoves) {auto [step, u] = pq.top();pq.pop();// 如果节点已经访问过,跳过if (visited.count(u)) {continue;}// 标记节点为已访问visited.emplace(u);// 可到达的原始节点数 +1reachableNodes++;// 遍历当前节点的所有邻居for (auto [v, nodes] : adList[u]) {// 如果从 u 到 v 的路径 (包括细分节点) 总步数不超过 maxMoves, 且 v 未被访问if (nodes + step + 1 <= maxMoves && !visited.count(v)) {// 将 v 加入优先队列, 步数为 u 的步数 + 细分节点数 + 1 (原始边算一步)pq.emplace(nodes + step + 1, v);}// 记录从 u 到 v 这条边上覆盖的细分节点数, 最多覆盖 maxMoves - step 个 (剩余步数限制)used[encode(u, v, n)] = min(nodes, maxMoves - step);}}// 统计所有边上的细分节点覆盖情况for (auto& edge : edges) {int u = edge[0], v = edge[1], nodes = edge[2];// 计算这条边两端节点覆盖的细分节点数之和, 但不能超过总细分节点数reachableNodes += min(nodes, used[encode(u, v, n)] + used[encode(v, u, n)]);}return reachableNodes;}
};
Java
class Solution {public int reachableNodes(int[][] edges, int maxMoves, int n) {// 邻接表Map<Integer, Integer>[] adList = new Map[n];for (int i = 0; i < n; i++) {adList[i] = new HashMap<>();}for (int[] edge : edges) {int u = edge[0], v = edge[1], nodes = edge[2];adList[u].put(v, nodes);adList[v].put(u, nodes);}// 记录每条边已覆盖的细分节点数Map<Integer, Integer> used = new HashMap<>();// 记录已访问的原始节点Set<Integer> visited = new HashSet<>();int reachableNodes = 0;// 优先队列(最小堆)PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);pq.offer(new int[] { 0, 0 });// Dijkstra 算法while (!pq.isEmpty() && pq.peek()[0] <= maxMoves) {int[] curr = pq.poll();int step = curr[0], u = curr[1];if (visited.contains(u)) {continue;}visited.add(u);reachableNodes++;for (Map.Entry<Integer, Integer> entry : adList[u].entrySet()) {int v = entry.getKey(), nodes = entry.getValue();if (nodes + step + 1 <= maxMoves && !visited.contains(v)) {pq.offer(new int[] { nodes + step + 1, v });}used.put(encode(u, v, n), Math.min(nodes, maxMoves - step));}}// 统计细分节点for (int[] edge : edges) {int u = edge[0], v = edge[1], nodes = edge[2];reachableNodes += Math.min(nodes, used.getOrDefault(encode(u, v, n), 0) + used.getOrDefault(encode(v, u, n), 0));}return reachableNodes;}// 编码函数private int encode(int u, int v, int n) {return u * n + v;}
}
Python
class Solution:def reachableNodes(self, edges: List[List[int]], maxMoves: int, n: int) -> int:# 邻接表adList = [{} for _ in range(n)]for u, v, nodes in edges:adList[u][v] = nodesadList[v][u] = nodesused = {}visited = set()reachableNodes = 0heap = [(0, 0)]while heap and heap[0][0] <= maxMoves:step, u = heapq.heappop(heap)if u in visited:continuevisited.add(u)reachableNodes += 1for v, nodes in adList[u].items():if nodes + step + 1 <= maxMoves and v not in visited:heapq.heappush(heap, (nodes + step + 1, v))used[(u, v)] = min(nodes, maxMoves - step)# 统计细分节点for u, v, nodes in edges:reachableNodes += min(nodes, used.get((u, v), 0) + used.get((v, u), 0))return reachableNodes
4、复杂度分析
- 时间复杂度:
O(E log V)
,其中E
是边的数量,V
是节点数量。Dijkstra算法的时间复杂度为O(E log V)
。 - 空间复杂度:
O(V + E)
,用于存储邻接表和优先队列。