Leetcode 第 364 场周赛题解
- Leetcode 第 364 场周赛题解
- 题目1:2864. 最大二进制奇数
- 思路
- 代码
- 复杂度分析
- 题目2:美丽塔 I
- 思路
- 代码
- 复杂度分析
- 题目3:美丽塔 II
- 思路
- 代码
- 复杂度分析
- 题目4:统计树中的合法路径数目
- 思路
- 代码
- 复杂度分析
Leetcode 第 364 场周赛题解
题目1:2864. 最大二进制奇数
思路
贪心。
要得到最大二进制奇数,则高位尽可能放 1,为了是奇数,最后一位必须是 1。
设原字符串的长度为 n,统计原字符串中 ‘1’ 的个数,记为 count_one。
新字符串 = count_one - 1 个 ‘1’ + n - count_one 个 ‘0’ + ‘1’。
代码
/** @lc app=leetcode.cn id=2864 lang=cpp** [2864] 最大二进制奇数*/// @lc code=start
class Solution
{
public:string maximumOddBinaryNumber(string s){int n = s.size(), count_one = 0;for (char &c : s)if (c == '1')count_one++;string ans;for (int i = 0; i < count_one - 1; i++)ans += '1';for (int i = 0; i < n - count_one; i++)ans += '0';ans += '1';return ans;}
};
// @lc code=end
复杂度分析
时间复杂度:O(n),其中 n 是字符串的长度。
空间复杂度:O(1)。
题目2:美丽塔 I
思路
枚举峰值的位置,向左、向右求高度和,更新高度和的最大值。
代码
/** @lc app=leetcode.cn id=2865 lang=cpp** [2865] 美丽塔 I*/// @lc code=start
class Solution
{
public:long long maximumSumOfHeights(vector<int> &maxHeights){int n = maxHeights.size();long long max_sum = 0;// 枚举峰值的位置for (int i = 0; i < n; i++){long long cur_sum = maxHeights[i];// 向左求和int left_height = maxHeights[i];for (int j = i - 1; j >= 0; j--){left_height = min(left_height, maxHeights[j]);cur_sum += left_height;}// 向右求和int right_height = maxHeights[i];for (int j = i + 1; j < n; j++){right_height = min(right_height, maxHeights[j]);cur_sum += right_height;}// 更新答案max_sum = max(max_sum, cur_sum);}return max_sum;}
};
// @lc code=end
复杂度分析
时间复杂度:O(n2),其中 n 是数组 maxHeights 的长度。
空间复杂度:O(1)。
题目3:美丽塔 II
思路
思考优化:
以示例 [6,5,3,9,2,7] 为例,我们观察以 3 和 9 作为山顶的两个方案:
- 以 3 作为山顶:3 3 |3 3| 2 2
- 以 9 作为山顶:3 3 |3 9| 2 2
可以发现:以 3 作为山顶的左侧与以 9 为山顶的右侧在两个方案之间是可以复用的,至此发现解决方法:我们可以分别预处理出以每个节点作为山顶的前缀和后缀的和:
- prev[i] 表示以 maxHeights[i] 作为山顶时左侧段的前缀和;
- suffix[i] 表示以 maxHeights[i] 作为山顶时右侧段的后缀和。
那么,最佳方案就是 prev[i]+suffix[i]−maxHeight[i] 的最大值。 现在,最后的问题是如何以均摊 O(1) 的时间复杂度计算出每个元素前后缀的和?
继续以示例 [6,5,3,9,2,7] 为例:
- 以 6 为山顶,前缀为 [6]
- 以 5 为山顶,需要保证左侧元素不大于 5,因此找到 666 并修改为 555,前缀为 [5,5]
- 以 3 为山顶,需要保证左侧元素不大于 3,因此找到两个 555 并修改为 333,前缀为 [3,3,3]
- 以 9 为山顶,需要保证左侧元素不大于 9,不需要修改,前缀为 [3,3,3,9]
- 以 2 为山顶,需要保证左侧元素不大于 2,修改后为 [2,2,2,2,2]
- 以 7 为山顶,需要保证左侧元素不大于 7,不需要修改,前缀为 [2,2,2,2,2,7]
观察以上步骤,问题的关键在于修改操作:由于数组是递增的,因此修改的步骤就是在「寻找小于等于当前元素 x 的上一个元素」,再将中间的元素削减为 x。「寻找上一个更小元素」,这是单调栈的典型场景。
代码
/** @lc app=leetcode.cn id=2866 lang=cpp** [2866] 美丽塔 II*/// @lc code=start
class Solution
{
public:long long maximumSumOfHeights(vector<int> &maxHeights){int n = maxHeights.size();vector<long long> suffix(n + 1, 0);vector<long long> prev(n + 1, 0);stack<int> s;// 单调栈求前缀for (int i = 0; i < n; i++){// 弹出栈顶while (!s.empty() && maxHeights[s.top()] > maxHeights[i])s.pop();int j = s.empty() ? -1 : s.top();prev[i + 1] = prev[j + 1] + (long long)(i - j) * maxHeights[i];s.push(i);}// 清空栈while (!s.empty())s.pop();// 单调栈求后缀for (int i = n - 1; i >= 0; i--){// 弹出栈顶while (!s.empty() && maxHeights[s.top()] > maxHeights[i])s.pop();int j = s.empty() ? n : s.top();suffix[i] = suffix[j] + (long long)(j - i) * maxHeights[i];s.push(i);}// 合并long long max_sum = 0;for (int i = 0; i < n; i++)max_sum = max(max_sum, prev[i + 1] + suffix[i] - maxHeights[i]);return max_sum;}
};
// @lc code=end
复杂度分析
时间复杂度:O(n),其中 n 是数组 maxHeights 的长度。在一侧的计算中,每个元素最多如何和出栈 1 次。
空间复杂度:O(n),其中 n 是数组 maxHeights 的长度。
题目4:统计树中的合法路径数目
超出能力范围。
思路
经典的树型 DP 模型。维护以下值:
- fu 表示以节点 u 为起点,以 u 的子树中某个节点为终点的路径中,所有点编号都是合数的路径有几条。
- gu 表示以节点 u 为起点,以 u 的子树中某个节点为终点的路径中,有一个点的编号为质数的路径有几条。
answer += f[u] * g[v] + f[v] * g[u]。
题解:
https://leetcode.cn/problems/count-valid-paths-in-a-tree/solutions/2456568/shu-dp-by-tsreaper-of6v/
【图解】O(n) 线性做法(Python/Java/C++/Go)
代码
/** @lc app=leetcode.cn id=2867 lang=cpp** [2867] 统计树中的合法路径数目*/// @lc code=start// 树型 DPclass Solution
{
private:#define MAXN (int)1e5bool inited = false;bool flag[MAXN + 10];// 筛法预处理 1e5 以内的质数void init(){if (inited)return;inited = true;memset(flag, 0, sizeof(flag));flag[0] = flag[1] = true;for (int i = 2; i * i <= MAXN; i++)if (!flag[i])for (int j = i << 1; j <= MAXN; j += i)flag[j] = true;}public:long long countPaths(int n, vector<vector<int>> &edges){init();vector<int> e[n + 1];for (auto &edge : edges){e[edge[0]].push_back(edge[1]);e[edge[1]].push_back(edge[0]);}long long ans = 0;int f[n + 1], g[n + 1];// 树 dpfunction<void(int, int)> dfs = [&](int sn, int fa){if (flag[sn])f[sn] = 1, g[sn] = 0;elsef[sn] = 0, g[sn] = 1;for (int fn : e[sn])if (fn != fa){dfs(fn, sn);// 路径上深度最低的节点为 sn,这样的合法路径有几条ans += f[sn] * g[fn] + g[sn] * f[fn];// 更新以 sn 为起点,且走到子树里的路径数量if (flag[sn]){f[sn] += f[fn];g[sn] += g[fn];}else{g[sn] += f[fn];}}};dfs(1, 0);return ans;}
};
// @lc code=end
复杂度分析
时间复杂度:O(n),其中 n 是数组 edges 的长度。
空间复杂度:O(n),其中 n 是数组 edges 的长度。