贪心。
要得到最大二进制奇数,则高位尽可能放 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)。
枚举峰值的位置,向左、向右求高度和,更新高度和的最大值。
/*
* @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)。
思考优化:
以示例 [6,5,3,9,2,7] 为例,我们观察以 3 和 9 作为山顶的两个方案:
可以发现:以 3 作为山顶的左侧与以 9 为山顶的右侧在两个方案之间是可以复用的,至此发现解决方法:我们可以分别预处理出以每个节点作为山顶的前缀和后缀的和:
那么,最佳方案就是 prev[i]+suffix[i]−maxHeight[i] 的最大值。 现在,最后的问题是如何以均摊 O(1) 的时间复杂度计算出每个元素前后缀的和?
继续以示例 [6,5,3,9,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 的长度。
超出能力范围。
经典的树型 DP 模型。维护以下值:
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
// 树型 DP
class Solution
{
private:
#define MAXN (int)1e5
bool 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];
// 树 dp
function<void(int, int)> dfs = [&](int sn, int fa)
{
if (flag[sn])
f[sn] = 1, g[sn] = 0;
else
f[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 的长度。