leetcode biweekly contest 294
感觉第四题确实是个不错的题目,前三题都太普通的题目。少见的高质量的题目。
2278. 字母在字符串中的百分比
题目
给你一个字符串 s 和一个字符 letter ,返回在 s 中等于 letter 字符所占的 百分比 ,向下取整到最接近的百分比。
示例 1:
输入:s = "foobar", letter = "o" |
示例 2:
输入:s = "jjjj", letter = "k" |
提示:
1 <= s.length <= 100s由小写英文字母组成letter是一个小写英文字母
ln -s /usr/local/python3/bin/python3 /usr/bin/python3
pip3 install mkdocs-material
地址
https://leetcode.cn/problems/percentage-of-letter-in-string
题意
直接遍历
思路
- 直接统计字符的个数即可,返回值为 $\lfloor \dfrac{100 * c}{n} \rfloor$。
- 复杂度分析:
- 时间复杂度:$O(n)$, 其中 $n$ 为字符串的长度。
- 空间复杂度:$O(1)$。
代码
class Solution { |
2279. 装满石头的背包的最大数量
题目
现有编号从 0 到 n - 1 的 n 个背包。给你两个下标从 0 开始的整数数组 capacity 和 rocks 。第 i 个背包最大可以装 capacity[i] 块石头,当前已经装了 rocks[i] 块石头。另给你一个整数 additionalRocks ,表示你可以放置的额外石头数量,石头可以往 任意 背包中放置。
请你将额外的石头放入一些背包中,并返回放置后装满石头的背包的 最大 数量。
示例 1:
输入:capacity = [2,3,4,5], rocks = [1,2,4,4], additionalRocks = 2 |
示例 2:
输入:capacity = [10,2,2], rocks = [2,2,0], additionalRocks = 100 |
提示:
n == capacity.length == rocks.length1 <= n <= 5 * 1041 <= capacity[i] <= 1090 <= rocks[i] <= capacity[i]1 <= additionalRocks <= 109
地址
https://leetcode.cn/problems/maximum-bags-with-full-capacity-of-rocks
题意
贪心算法
思路
- 由于额外的石头有限,肯定是有限将其放置在需要数量最少的背包中,将每个背包装满需要的数量按照从小到大进行排序,然后依次填放即可。
- 复杂度分析:
- 时间复杂度:$O(n \log n)$,其中 $n$ 为数组中的长度。
- 空间复杂度:$O(\log n)$, 排序需要的栈空间为 $O(\log n)$.
代码
class Solution { |
2280. 表示一个折线图的最少线段数
题目
给你一个二维整数数组 stockPrices ,其中 stockPrices[i] = [dayi, pricei] 表示股票在 dayi 的价格为 pricei 。折线图 是一个二维平面上的若干个点组成的图,横坐标表示日期,纵坐标表示价格,折线图由相邻的点连接而成。比方说下图是一个例子:
请你返回要表示一个折线图所需要的 最少线段数 。
示例 1:
输入:stockPrices = [[1,7],[2,6],[3,5],[4,4],[5,4],[6,3],[7,2],[8,1]] |
示例 2:
输入:stockPrices = [[3,4],[1,2],[7,8],[2,3]] |
提示:
1 <= stockPrices.length <= 105stockPrices[i].length == 21 <= dayi, pricei <= 109- 所有
dayi互不相同 。
地址
https://leetcode.cn/problems/minimum-lines-to-represent-a-line-chart
题意
数学
思路
- 这个题目毫无意义,感觉就是个简单题目。首先我们将所有的点按照横坐标的大小进行排序,连续的三个点如果不在一条直线上,此时则需要新的折线。设连续的三个点为 $p_1, p_2,p_3$,此时我们只需要判断 $p_3$ 是否在 $p_1$ 与 $p_2$ 构成的直线上即可。
- 复杂度分析:
- 时间复杂度:$O(n \log n)$, 其中 $n$ 为数组的长度。
- 空间复杂度:$O(1)$。
代码
class Solution { |
2281. 巫师的总力量和
题目
作为国王的统治者,你有一支巫师军队听你指挥。
给你一个下标从 0 开始的整数数组 strength ,其中 strength[i] 表示第 i 位巫师的力量值。对于连续的一组巫师(也就是这些巫师的力量值是 strength 的 子数组),总力量 定义为以下两个值的 乘积 :
巫师中 最弱 的能力值。
组中所有巫师的个人力量值 之和 。
请你返回 所有 巫师组的 总 力量之和。由于答案可能很大,请将答案对 109 + 7 取余 后返回。
子数组 是一个数组里 非空 连续子序列。
示例 1:
输入:strength = [1,3,1,2] |
示例 2:
输入:strength = [5,4,6] |
提示:
1 <= strength.length <= 1051 <= strength[i] <= 109
地址
https://leetcode.cn/contest/weekly-contest-294/problems/sum-of-total-strength-of-wizards/
题意
单调栈、动态规划
思路
- 动态规划解法思路,需要参考「907. 子数组的最小值之和」, 知道这个题目这个题目就非常好做了。
- 设数组的长度为 $n$, 数组 $s$ 的前 $i$ 项的前缀和为 $$\textit{sum}[i] = \sum_{j=0}^{i}s[i]$$,前 $i$ 项的前缀和的前缀和为: $$\textit{ssum}[i] = \sum_{j=0}^{i}sum[j] = \sum_{j=0}^{i}\sum_{k=0}^{j}s[k]$$
- 设数组中以 $i$ 为起点,以 $j$ 且满足 $(i \le j)$ 的连续子数组的最小值为 $\textit{min}(i,j)$, 设以 $i$ 为起点的子数组的最小值的和为 $\textit{minSum}[i] = \sum_{j=i}^{n-1}\textit{min}(i,j)$。此处可以利用单调栈来求出 $\textit{minSum}[i]$,具体可以参考「907. 子数组的最小值之和」的解法即可。
- 设 $dp[i]$ 表示以 $i$ 为起点的子数组构成的魔力值之和,则此时我们可以知道如下公式:
$$dp[i] = \sum_{j=i}^{n-1}(sum[j] - sum[i-1]) \times min(i,j)$$ - 我们假设数组中 $i$ 的右侧第一个小于 $s[i]$ 的元素为 $s[j]$,且满足 $i < j \le n - 1$,则此时我们知道当 $i \le k < j $ 时,此时区间 $[i,j-1]$ 之间 $s[i]$ 最小,因此 $min(i,k) = s[i]$;当满足 $j \le k < n$ 时,则此时区间 $[i,k]$ 之间可定不是 $s[i]$ 最小,因为此时已知 $s[i] < s[j]$ 最小的值一定在 $j$ 的右侧,因此 $min(i,k) = min(j,k)$。此时我们将上述式子从 $j$ 处拆分可以得到如下:
$$
dp[i] = \sum_{k=i}^{n-1}(sum[k] - sum[i-1]) \times min(i,k) \
= \sum_{k=i}^{j-1}(sum[k] - sum[i-1]) \times min(i,k) + \sum_{k=j}^{n-1}(sum[k] - sum[i-1]) \times min(i,k) \
= \sum_{k=i}^{j-1}(sum[k] - sum[i-1]) \times s[i] + \sum_{k=j}^{n-1}(sum[k] - sum[i-1]) \times min(j,k) \
= (\sum_{k=i}^{j-1}sum[k] - (j - i)\times sum[i-1]) \times s[i] + \sum_{k=j}^{n-1}(sum[k] - sum[j-1] + sum[j-1] - sum[i-1]) \times min(j,k) \
= (\sum_{k=i}^{j-1}sum[k] - (j - i)\times sum[i-1]) \times s[i] + \sum_{k=j}^{n-1}(sum[k] - sum[j-1] ) \times min(j,k) + \sum_{k=j}^{n-1}(sum[j-1] - sum[i-1]) \times min(j,k)\
= (ssum[j-1] - ssum[i-1] + (j-i) \times sum[i-1]) + (sum[j-1] - sum[i-1]) \times \sum_{k=j}^{n-1}min(j,k) + \sum_{k=j}^{n-1}(sum[k] - sum[j-1] ) \times min(j,k) \
= (ssum[j-1] - ssum[i-1] + (j-i) \times sum[i-1]) + (sum[j-1] - sum[i-1]) \times minSum[j] + dp[j]
$$
以上即为我们推导出的动态规划递推公式。
- 求每个数的贡献值,设以 $s[i]$ 为最小值得区间为 $[l,r]$,此时以 $s[i]$ 为最小值的子数组的魔力值之和为 $s[i] \times \sum_{j=l}^{i}\sum_{k=i}^{r}s[k]$,此时我们只需要求出 $[l,r]$ 即可,可以用用单调栈解决上述问题,可以参考题解,写的很清晰。上述解法难点有两点比较难以处理:
- 左右区间中遇到相同的元素如何处理,当时也很疑惑这个问题,没有想明白,后来看了题解,可以用一边严格的小于,另一边小于等于即可规避相同元素的问题,确实非常巧妙。
- 计算 $s[i] \times \sum_{j=l}^{i}\sum_{k=i}^{r}s[k]$ 需要用到一些数学变换的技巧,不过还是蛮有意思的思考题目。
- 复杂度分析:
- 时间复杂度:$O(n)$, 其中 $n$ 为数组的长度,只需要遍历数组两次即可完成。
- 空间复杂度:$O(n)$, 其中 $n$ 为数组的长度。
代码
- 参考: 单调栈 + 计算贡献值
class Solution {
public:
const int mod = 1e9 + 7;
int totalStrength(vector<int>& strength) {
int n = strength.size();
// 步骤1. 求左侧第一个 <= x 的坐标,和右侧第一个 < x 的坐标
stack<int> st;
vector<int> left(n, -1), right(n, n);
for(int i = 0; i < n; ++i) {
while(st.size() && strength[st.top()] > strength[i]) {
right[st.top()] = i;
st.pop();
}
if(st.size()) {
left[i] = st.top();
}
st.push(i);
}
// 步骤2. 准备前缀和 psum 和 前缀和的前缀和 ppsum
vector<int> psum = strength;
for(int i = 1; i < n; ++i) {
psum[i] = (psum[i] + psum[i-1]) % mod;
}
vector<int> ppsum = psum;
for(int i = 1; i < n; ++i) {
ppsum[i] = (ppsum[i] + ppsum[i-1]) % mod;
}
auto f = [&](int l, int r) {
if(r < 0) return 0;
if(l < 0) return ppsum[r];
return (ppsum[r] - ppsum[l] + mod) % mod;
};
// 步骤3. 针对每个 min 值计算结果
// 对于 a[i], 其管辖区间为 [left[i] + 1, right[i] - 1]
// 问题归结, 将 l = left[i] + 1, r = right[i] - 1 代入上面的式子求解即可
int res = 0;
for(int i = 0; i < n; ++i) {
int l = left[i] + 1, r = right[i] - 1;
int sleft = 1ll * f(l-2, i-1) * (r - i + 1) % mod;
int sright = 1ll * f(i-1, r) * (i - l + 1) % mod;
res += 1ll * strength[i] * (((sright - sleft) + mod) % mod) % mod;
res %= mod;
}
return res;
}
}; - 单调栈 + 动态规划
class Solution {
public:
int totalStrength(vector<int>& strength) {
int n = strength.size();
long long mod = 1e9 + 7;
long long ans = 0;
vector<long long> rminsum(n + 1);
vector<long long> sum(n + 1);
vector<long long> ssum(n + 1);
vector<long long> dp(n + 1);
stack<pair<int,int>> st;
for (int i = 0; i < n; i++) {
sum[i + 1] = (strength[i] + sum[i]) % mod;
ssum[i + 1] = (sum[i + 1] + ssum[i]) % mod;
}
st.emplace(0, n);
for (int i = n - 1; i >= 0; i--) {
while (!st.empty() && strength[i] <= st.top().first) {
st.pop();
}
int j = st.top().second;
rminsum[i] = (rminsum[j] + (long long)(j - i) * (long long)strength[i]) % mod;
dp[i] = (dp[j] + rminsum[j] * (sum[j] - sum[i])) % mod;
dp[i] = (dp[i] + mod + ((ssum[j] - ssum[i] - ((long long)(j - i) * sum[i])) % mod) * strength[i] % mod) % mod;
st.emplace(strength[i], i);
ans = (ans + dp[i]) % mod;
}
return ans;
}
};
扫描二维码,分享此文章