文章目录
- 周赛364
- [8048. 最大二进制奇数](https://leetcode.cn/problems/maximum-odd-binary-number/)
- 贪心 + 模拟
- [100049. 美丽塔 I](https://leetcode.cn/problems/beautiful-towers-i/)
- 枚举
- [100048. 美丽塔 II](https://leetcode.cn/problems/beautiful-towers-ii/)
- 单调栈 + 前后缀分解
- [496. 下一个更大元素 I](https://leetcode.cn/problems/next-greater-element-i/)
- [100047. 统计树中的合法路径数目](https://leetcode.cn/problems/count-valid-paths-in-a-tree/)
- 枚举 + DFS
周赛364
8048. 最大二进制奇数
简单
给你一个 二进制 字符串 s
,其中至少包含一个 '1'
。
你必须按某种方式 重新排列 字符串中的位,使得到的二进制数字是可以由该组合生成的 最大二进制奇数 。
以字符串形式,表示并返回可以由给定组合生成的最大二进制奇数。
注意 返回的结果字符串 可以 含前导零。
示例 1:
输入:s = "010"
输出:"001"
解释:因为字符串 s 中仅有一个 '1' ,其必须出现在最后一位上。所以答案是 "001" 。
示例 2:
输入:s = "0101"
输出:"1001"
解释:其中一个 '1' 必须出现在最后一位上。而由剩下的数字可以生产的最大数字是 "100" 。所以答案是 "1001" 。
提示:
1 <= s.length <= 100
s
仅由'0'
和'1'
组成s
中至少包含一个'1'
贪心 + 模拟
class Solution {
public String maximumOddBinaryNumber(String s) {
int cnt1 = 0, n = s.length();
for(char c : s.toCharArray()){
if(c == '1') cnt1 += 1;
}
StringBuilder sb = new StringBuilder();
for(int i = 0; i < cnt1 - 1; i++){
sb.append("1");
}
for(int i = 0; i < n - cnt1; i++){
sb.append("0");
}
sb.append("1");
return sb.toString();
}
}
100049. 美丽塔 I
中等
给你一个长度为 n
下标从 0 开始的整数数组 maxHeights
。
你的任务是在坐标轴上建 n
座塔。第 i
座塔的下标为 i
,高度为 heights[i]
。
如果以下条件满足,我们称这些塔是 美丽 的:
1 <= heights[i] <= maxHeights[i]
heights
是一个 山状 数组。
如果存在下标 i
满足以下条件,那么我们称数组 heights
是一个 山状 数组:
- 对于所有
0 < j <= i
,都有heights[j - 1] <= heights[j]
- 对于所有
i <= k < n - 1
,都有heights[k + 1] <= heights[k]
请你返回满足 美丽塔 要求的方案中,高度和的最大值 。
示例 1:
输入:maxHeights = [5,3,4,1,1]
输出:13
解释:和最大的美丽塔方案为 heights = [5,3,3,1,1] ,这是一个美丽塔方案,因为:
- 1 <= heights[i] <= maxHeights[i]
- heights 是个山状数组,峰值在 i = 0 处。
13 是所有美丽塔方案中的最大高度和。
示例 2:
输入:maxHeights = [6,5,3,9,2,7]
输出:22
解释: 和最大的美丽塔方案为 heights = [3,3,3,9,2,2] ,这是一个美丽塔方案,因为:
- 1 <= heights[i] <= maxHeights[i]
- heights 是个山状数组,峰值在 i = 3 处。
22 是所有美丽塔方案中的最大高度和。
示例 3:
输入:maxHeights = [3,2,5,5,2,3]
输出:18
解释:和最大的美丽塔方案为 heights = [2,2,5,5,2,2] ,这是一个美丽塔方案,因为:
- 1 <= heights[i] <= maxHeights[i]
- heights 是个山状数组,最大值在 i = 2 处。
注意,在这个方案中,i = 3 也是一个峰值。
18 是所有美丽塔方案中的最大高度和。
提示:
1 <= n == maxHeights <= 103
1 <= maxHeights[i] <= 109
枚举
class Solution {
// 观察到数据范围10^3,可以枚举每个点作为山峰
public long maximumSumOfHeights(List<Integer> maxHeights) {
long ans = 0;
int n = maxHeights.size();
for(int i = 0 ; i < n; i++){
long heightsum = maxHeights.get(i);
for(int j = i-1, curtop = maxHeights.get(i); j >= 0; j--){
curtop = Math.min(curtop, maxHeights.get(j));
heightsum += curtop;
}
for(int j = i+1, curtop = maxHeights.get(i); j < n; j++){
curtop = Math.min(curtop, maxHeights.get(j));
heightsum += curtop;
}
ans = Math.max(ans, heightsum);
}
return ans;
}
}
100048. 美丽塔 II
中等
给你一个长度为 n
下标从 0 开始的整数数组 maxHeights
。
你的任务是在坐标轴上建 n
座塔。第 i
座塔的下标为 i
,高度为 heights[i]
。
如果以下条件满足,我们称这些塔是 美丽 的:
1 <= heights[i] <= maxHeights[i]
heights
是一个 山状 数组。
如果存在下标 i
满足以下条件,那么我们称数组 heights
是一个 山状 数组:
- 对于所有
0 < j <= i
,都有heights[j - 1] <= heights[j]
- 对于所有
i <= k < n - 1
,都有heights[k + 1] <= heights[k]
请你返回满足 美丽塔 要求的方案中,高度和的最大值 。
示例 1:
输入:maxHeights = [5,3,4,1,1]
输出:13
解释:和最大的美丽塔方案为 heights = [5,3,3,1,1] ,这是一个美丽塔方案,因为:
- 1 <= heights[i] <= maxHeights[i]
- heights 是个山状数组,峰值在 i = 0 处。
13 是所有美丽塔方案中的最大高度和。
示例 2:
输入:maxHeights = [6,5,3,9,2,7]
输出:22
解释: 和最大的美丽塔方案为 heights = [3,3,3,9,2,2] ,这是一个美丽塔方案,因为:
- 1 <= heights[i] <= maxHeights[i]
- heights 是个山状数组,峰值在 i = 3 处。
22 是所有美丽塔方案中的最大高度和。
示例 3:
输入:maxHeights = [3,2,5,5,2,3]
输出:18
解释:和最大的美丽塔方案为 heights = [2,2,5,5,2,2] ,这是一个美丽塔方案,因为:
- 1 <= heights[i] <= maxHeights[i]
- heights 是个山状数组,最大值在 i = 2 处。
注意,在这个方案中,i = 3 也是一个峰值。
18 是所有美丽塔方案中的最大高度和。
提示:
1 <= n == maxHeights <= 105
1 <= maxHeights[i] <= 109
单调栈 + 前后缀分解
https://leetcode.cn/problems/beautiful-towers-ii/solutions/2456562/qian-hou-zhui-fen-jie-dan-diao-zhan-pyth-1exe/
class Solution {
/**
10^5 次方数据,枚举每个点作为最大值会超时,怎么优化呢?
计算从 a[0] 到 a[i] 形成山状数组的左侧递增段,元素和最大是多少,记到数组 pre[i] 中。
计算从 a|i] 到 a[n-1] 形成山状数组的右侧递减段,元素和最大是多少,记到数组 suf[i] 中。
答案就是 pre[i] + suf[i+1] 的最大值
如何计算 pre 和 suf? 单调栈
用单调栈,元素值从栈底到栈顶严格递增
以 suf 为例,我们从右往左遍历 a,设当前得到的元素和为 sum
1. 如果 a[i] 大于栈顶的元素值,那么直接把 a[i] 加到 sum 中,同时把i入栈 (栈中只需要保存下标)
2. 否则,只要 a[i] 小于等于栈顶元素值,就不断循环,把之前加到sum 的撤销掉。
循环结束后,从 a[i] 到 a[j-1] (假设现在栈顶下标是 j) 都必须是 a,
把 a[i]*(j-1) 加到 sum 中
*/
public long maximumSumOfHeights(List<Integer> maxHeights) {
int[] a = maxHeights.stream().mapToInt(i -> i).toArray();
int n = a.length;
long[] suf = new long[n+1];
Deque<Integer> st = new ArrayDeque<>();
st.push(n); // 哨兵
long sum = 0;
// 计算suf
for(int i = n-1; i >= 0; i--){
int x = a[i];
while(st.size() > 1 && x <= a[st.peek()]){
int j = st.pop();
// 撤销之前加到 sum 中的
sum -= (long)a[j] * (st.peek() - j);
}
sum += (long) x * (st.peek() - i); // 从 i 到 st.peek()-1 都是 x
suf[i] = sum;
st.push(i);
}
long ans = sum;
st.clear();
st.push(-1); // 哨兵
long pre = 0;
for(int i = 0; i < n; i++){
int x = a[i];
while(st.size() > 1 && x <= a[st.peek()]){
int j = st.pop();
// 撤销之前加到 sum 中的
pre -= (long) a[j] * (j - st.peek());
}
pre += (long) x * (i - st.peek());// 从 st.peek()+1 到 i 都是 x
ans = Math.max(pre + suf[i+1], ans);
st.push(i);
}
return ans;
}
}
小羊肖恩
10^5 次方数据,枚举每个点作为最大值会超时,怎么优化呢?
我们考虑从中间点往前的结果的数组,可以发现其是这么更新的:
假设以 i-1
位置为中间点的结果数组前缀为 nums[i-1]
,那么 i
位置会**把其中大于上界的数改为上界,同时增加一个新的数。**而这个过程很类似于单调栈。
因为这个过程等价于,我们一旦结果的前缀中有更大的数,我们将其(整段)删掉,后面一整段区间换成新的数。
同时我们也可以对后缀进行相似的处理。得到前缀和后缀的结果后,我们将其加总即可得到每个点为中间点的答案,也就得到了这个问题的解
-
“左边最近的更小值”是一个经典问题,可以用单调栈解决,详见 leetcode 496. 下一个更大元素 I。
-
改成去掉一些数变成先递增再递减,至少要去掉多少个数?
- 这题是 1671. 得到山形数组的最少删除次数
496. 下一个更大元素 I
简单
nums1
中数字 x
的 下一个更大元素 是指 x
在 nums2
中对应位置 右侧 的 第一个 比 x
大的元素。
给你两个 没有重复元素 的数组 nums1
和 nums2
,下标从 0 开始计数,其中nums1
是 nums2
的子集。
对于每个 0 <= i < nums1.length
,找出满足 nums1[i] == nums2[j]
的下标 j
,并且在 nums2
确定 nums2[j]
的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1
。
返回一个长度为 nums1.length
的数组 ans
作为答案,满足 ans[i]
是如上所述的 下一个更大元素 。
示例 1:
输入:nums1 = [4,1,2], nums2 = [1,3,4,2].
输出:[-1,3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
- 4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
- 1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3 。
- 2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
示例 2:
输入:nums1 = [2,4], nums2 = [1,2,3,4].
输出:[3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
- 2 ,用加粗斜体标识,nums2 = [1,2,3,4]。下一个更大元素是 3 。
- 4 ,用加粗斜体标识,nums2 = [1,2,3,4]。不存在下一个更大元素,所以答案是 -1 。
提示:
1 <= nums1.length <= nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 104
nums1
和nums2
中所有整数 互不相同nums1
中的所有整数同样出现在nums2
中
**进阶:**你可以设计一个时间复杂度为 O(nums1.length + nums2.length)
的解决方案吗?
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
Map<Integer, Integer> map = new HashMap<>();
// 维护一个单调递减栈
Deque<Integer> dq = new ArrayDeque<>();
for(int num : nums2){
// 弹出比当前值小的元素,记录该弹出值的下一个更大元素
while(!dq.isEmpty() && num > dq.peekLast()){
map.put(dq.pollLast(), num);
}
dq.addLast(num);
}
int n = nums1.length;
int[] res = new int[n];
for(int i = 0; i < n; i++){
res[i] = map.getOrDefault(nums1[i], -1);
}
return res;
}
}
100047. 统计树中的合法路径数目
困难
给你一棵 n
个节点的无向树,节点编号为 1
到 n
。给你一个整数 n
和一个长度为 n - 1
的二维整数数组 edges
,其中 edges[i] = [ui, vi]
表示节点 ui
和 vi
在树中有一条边。
请你返回树中的 合法路径数目 。
如果在节点 a
到节点 b
之间 恰好有一个 节点的编号是质数,那么我们称路径 (a, b)
是 合法的 。
注意:
- 路径
(a, b)
指的是一条从节点a
开始到节点b
结束的一个节点序列,序列中的节点 互不相同 ,且相邻节点之间在树上有一条边。 - 路径
(a, b)
和路径(b, a)
视为 同一条 路径,且只计入答案 一次 。
示例 1:
输入:n = 5, edges = [[1,2],[1,3],[2,4],[2,5]]
输出:4
解释:恰好有一个质数编号的节点路径有:
- (1, 2) 因为路径 1 到 2 只包含一个质数 2 。
- (1, 3) 因为路径 1 到 3 只包含一个质数 3 。
- (1, 4) 因为路径 1 到 4 只包含一个质数 2 。
- (2, 4) 因为路径 2 到 4 只包含一个质数 2 。
只有 4 条合法路径。
示例 2:
输入:n = 6, edges = [[1,2],[1,3],[2,4],[3,5],[3,6]]
输出:6
解释:恰好有一个质数编号的节点路径有:
- (1, 2) 因为路径 1 到 2 只包含一个质数 2 。
- (1, 3) 因为路径 1 到 3 只包含一个质数 3 。
- (1, 4) 因为路径 1 到 4 只包含一个质数 2 。
- (1, 6) 因为路径 1 到 6 只包含一个质数 3 。
- (2, 4) 因为路径 2 到 4 只包含一个质数 2 。
- (3, 6) 因为路径 3 到 6 只包含一个质数 3 。
只有 6 条合法路径。
提示:
1 <= n <= 105
edges.length == n - 1
edges[i].length == 2
1 <= ui, vi <= n
- 输入保证
edges
形成一棵合法的树。
枚举 + DFS
0X3F:https://leetcode.cn/problems/count-valid-paths-in-a-tree/solutions/2456716/tu-jie-on-xian-xing-zuo-fa-pythonjavacgo-tjz2/
class Solution {
private final static int MX = (int) 1e5;
private final static boolean[] np = new boolean[MX + 1]; // 质数=false 非质数=true
static {
np[1] = true;
for (int i = 2; i * i <= MX; i++) {
if (!np[i]) {
for (int j = i * i; j <= MX; j += i) {
np[j] = true;
}
}
}
}
public long countPaths(int n, int[][] edges) {
List<Integer>[] g = new ArrayList[n + 1];
Arrays.setAll(g, e -> new ArrayList<>());
for (var e : edges) {
int x = e[0], y = e[1];
g[x].add(y);
g[y].add(x);
}
long ans = 0;
int[] size = new int[n + 1];
var nodes = new ArrayList<Integer>();
for (int x = 1; x <= n; x++) {
if (np[x]) { // 跳过非质数
continue;
}
int sum = 0;
for (int y : g[x]) { // 质数 x 把这棵树分成了若干个连通块
if (!np[y]) {
continue;
}
if (size[y] == 0) { // 尚未计算过
nodes.clear();
dfs(y, -1, g, nodes); // 遍历 y 所在连通块,在不经过质数的前提下,统计有多少个非质数
for (int z : nodes) {
size[z] = nodes.size();
}
}
// 这 size[y] 个非质数与之前遍历到的 sum 个非质数,两两之间的路径只包含质数 x
ans += (long) size[y] * sum;
sum += size[y];
}
ans += sum; // 从 x 出发的路径
}
return ans;
}
private void dfs(int x, int fa, List<Integer>[] g, List<Integer> nodes) {
nodes.add(x);
for (int y : g[x]) {
if (y != fa && np[y]) {
dfs(y, x, g, nodes);
}
}
}
}