记录 LeetCode 第 294 场周赛
本次周赛依旧只写出前三题,第四题只想到时间复杂度为 的方法。最可惜的是第三题,很简单的题目却花了很长时间。
战局详情
排名 | 用户名 | 得分 | 完成时间 | 题目1(3) | 题目2(4) | 题目3(5) | 题目4(6) |
---|---|---|---|---|---|---|---|
1895 / 6640 | Juruoer | 12 | 1:04:41 | 0:02:34 | 0:10:05 | 0:54:41 2 |
题目及解答
字母在字符串中的百分比
给你一个字符串 s
和一个字符 letter
,返回在 s
中等于 letter
字符所占的 百分比 ,向下取整到最接近的百分比。
提示:
1 <= s.length <= 100
s
由小写英文字母组成letter
是一个小写英文字母
示例一:
输入:s = “foobar”, letter = “o”
输出:33
解释:
等于字母 ‘o’ 的字符在 s 中占到的百分比是 2 / 6 * 100% = 33% ,向下取整,所以返回 33 。
示例二:
输入:s = “jjjj”, letter = “k”
输出:0
解释:
等于字母 ‘k’ 的字符在 s 中占到的百分比是 0% ,所以返回 0 。
解决方案:
统计出字符 letter
的个数即可。
代码:
1 | class Solution |
时间复杂度:,空间复杂度:,其中 n
表示 s
的字符数。
装满石头的背包的最大数量
现有编号从 0
到 n - 1
的 n
个背包。给你两个下标从 0 开始的整数数组 capacity
和 rocks
。第 i
个背包最大可以装 capacity[i]
块石头,当前已经装了 rocks[i]
块石头。另给你一个整数 additionalRocks
,表示你可以放置的额外石头数量,石头可以往 任意 背包中放置。
请你将额外的石头放入一些背包中,并返回放置后装满石头的背包的 最大 数量。
提示:
n == capacity.length == rocks.length
1 <= n <= 5 * 1e4
1 <= capacity[i] <= 1e9
0 <= rocks[i] <= capacity[i]
1 <= additionalRocks <= 1e9
示例一:
输入:capacity = [2,3,4,5], rocks = [1,2,4,4], additionalRocks = 2
输出:3
解释:
1 块石头放入背包 0 ,1 块石头放入背包 1 。
每个背包中的石头总数是 [2,3,4,4] 。
背包 0 、背包 1 和 背包 2 都装满石头。
总计 3 个背包装满石头,所以返回 3 。
可以证明不存在超过 3 个背包装满石头的情况。
注意,可能存在其他放置石头的方案同样能够得到 3 这个结果。
示例二:
输入:capacity = [10,2,2], rocks = [2,2,0], additionalRocks = 100
输出:3
解释:
8 块石头放入背包 0 ,2 块石头放入背包 2 。
每个背包中的石头总数是 [10,2,2] 。
背包 0 、背包 1 和背包 2 都装满石头。
总计 3 个背包装满石头,所以返回 3 。
可以证明不存在超过 3 个背包装满石头的情况。
注意,不必用完所有的额外石头。
解决方案:
先统计出每个背包还需要多少块石头才能将背包装满,当额外的石头还足够时,优先装满需要石头数少的背包。
代码:
1 | class Solution |
时间复杂度:,空间复杂度:,其中 n
表示数组 capacity
的长度。也可以不需要 need
数组,重复利用 capacity
数组或 rocks
数组来代替 need
,此时空间复杂度降为:。
表示一个折线图的最少线段数
给你一个二维整数数组 stockPrices
,其中 stockPrices[i] = [dayi, pricei]
表示股票在 dayi
的价格为 pricei
。折线图 是一个二维平面上的若干个点组成的图,横坐标表示日期,纵坐标表示价格,折线图由相邻的点连接而成。比方说下图是一个例子:
请你返回要表示一个折线图所需要的 最少线段数 。
提示:
1 <= stockPrices.length <= 1e5
stockPrices[i].length == 2
1 <= dayi, pricei <= 1e9
- 所有
dayi
互不相同 。
示例一:
输入:stockPrices = [[1,7],[2,6],[3,5],[4,4],[5,4],[6,3],[7,2],[8,1]]
输出:3
解释:
上图为输入对应的图,横坐标表示日期,纵坐标表示价格。
以下 3 个线段可以表示折线图:
- 线段 1 (红色)从 (1,7) 到 (4,4) ,经过 (1,7) ,(2,6) ,(3,5) 和 (4,4) 。
- 线段 2 (蓝色)从 (4,4) 到 (5,4) 。
- 线段 3 (绿色)从 (5,4) 到 (8,1) ,经过 (5,4) ,(6,3) ,(7,2) 和 (8,1) 。
可以证明,无法用少于 3 条线段表示这个折线图。
示例二:
输入:stockPrices = [[3,4],[1,2],[7,8],[2,3]]
输出:1
解释:
如上图所示,折线图可以用一条线段表示。
解决方案:
先将所有点按 x 轴从左到右排序(即按照日期从小到大排序),然后从第三个点开始计算该点与该点左边的两个点是否共线,若共线则该点与前两点在同一线段上,否则该点与左边的一个点形成一条新的线段。
代码:
1 | class Solution |
时间复杂度:,空间复杂度为,其中 n
表示数组 stockPrices
的长度。
巫师的总力量和
作为国王的统治者,你有一支巫师军队听你指挥。
给你一个下标从 0 开始的整数数组 strength
,其中 strength[i]
表示第 i
位巫师的力量值。对于连续的一组巫师(也就是这些巫师的力量值是 strength
的 子数组),总力量 定义为以下两个值的 乘积 :
- 巫师中 最弱 的能力值。
- 组中所有巫师的个人力量值 之和 。
请你返回 所有 巫师组的 总 力量之和。由于答案可能很大,请将答案对 1e9 + 7
取余 后返回。
子数组 是一个数组里 非空 连续子序列。
提示:
1 <= strength.length <= 1e5
1 <= strength[i] <= 1e9
示例一:
输入:strength = [1,3,1,2]
输出:44
解释:以下是所有连续巫师组:
- [1,3,1,2] 中 [1] ,总力量值为 min([1]) * sum([1]) = 1 * 1 = 1
- [1,3,1,2] 中 [3] ,总力量值为 min([3]) * sum([3]) = 3 * 3 = 9
- [1,3,1,2] 中 [1] ,总力量值为 min([1]) * sum([1]) = 1 * 1 = 1
- [1,3,1,2] 中 [2] ,总力量值为 min([2]) * sum([2]) = 2 * 2 = 4
- [1,3,1,2] 中 [1,3] ,总力量值为 min([1,3]) * sum([1,3]) = 1 * 4 = 4
- [1,3,1,2] 中 [3,1] ,总力量值为 min([3,1]) * sum([3,1]) = 1 * 4 = 4
- [1,3,1,2] 中 [1,2] ,总力量值为 min([1,2]) * sum([1,2]) = 1 * 3 = 3
- [1,3,1,2] 中 [1,3,1] ,总力量值为 min([1,3,1]) * sum([1,3,1]) = 1 * 5 = 5
- [1,3,1,2] 中 [3,1,2] ,总力量值为 min([3,1,2]) * sum([3,1,2]) = 1 * 6 = 6
- [1,3,1,2] 中 [1,3,1,2] ,总力量值为 min([1,3,1,2]) * sum([1,3,1,2]) = 1 * 7 = 7
所有力量值之和为 1 + 9 + 1 + 4 + 4 + 4 + 3 + 5 + 6 + 7 = 44 。
示例二:
输入:strength = [5,4,6]
输出:213
解释:以下是所有连续巫师组:
- [5,4,6] 中 [5] ,总力量值为 min([5]) * sum([5]) = 5 * 5 = 25
- [5,4,6] 中 [4] ,总力量值为 min([4]) * sum([4]) = 4 * 4 = 16
- [5,4,6] 中 [6] ,总力量值为 min([6]) * sum([6]) = 6 * 6 = 36
- [5,4,6] 中 [5,4] ,总力量值为 min([5,4]) * sum([5,4]) = 4 * 9 = 36
- [5,4,6] 中 [4,6] ,总力量值为 min([4,6]) * sum([4,6]) = 4 * 10 = 40
- [5,4,6] 中 [5,4,6] ,总力量值为 min([5,4,6]) * sum([5,4,6]) = 4 * 15 = 60
所有力量值之和为 25 + 16 + 36 + 36 + 40 + 60 = 213 。
解决方案:
一、暴力穷举
设数组 strength
的长度为 n
,则该数组共有 个子序列(长度为 i
的子序列有 n - i + 1
个),对每个子序列求和及最小值需要遍历该子序列,则时间复杂度为:
显然时间复杂度过高,必然 TLE。
优化方法:假设我们已经计算出了长度为 s
的所有子序列的和及最小值,则计算长度为 s + 1
的子数组的和及最小值时不需要重新遍历这些子序列,可以由之前的结果来推导。
代码:
1 | class Solution |
时间复杂度降低为:,但仍然 TLE。
二、单调栈 + 前缀和
本题与 LeetCode 上的 907. 子数组的最小值之和 类似,可以先求出以数组中某个元素 strength[i]
为 最小值 的所有子序列的下标范围 [left, right]
,即在 strength[left ~ right]
范围内所有含有 strength[i]
(值相同不行,必须是下标为 i)的子序列的最小值均为 strength[i]
。为了避免 strength[i] = strength[j]
而导致的范围的重叠,定义当 i > left
时,strength[left ~ i - 1]
的所有值 严格小于 strength[i]
;当 i < right
时,strength[i + 1 ~ right]
的所有值 小于等于 strength[i]
。
该范围的简单的计算方法是:对于 strength[i]
,找到其左边最后一个大于等于它的值的下标 l
,则 left = l + 1
;再找到其右边第一个大于它的值的下标 r
,则 right = r - 1
,但此时间复杂度为
可以使用一个单调递增栈 st 优化计算:
1 | // 计算以 strength[i] 为最小值的子序列的区间 [left, right] |
因为每一个元素只入栈和出栈一次,时间复杂度为。
对于 strength[i]
,其对最终结果产生的贡献为:strength[i]
乘以 strength[left[i] ~ right[i]]
范围内所有含有 strength[i]
(必须是该下标值) 的子序列的和的和。
设 strength
的前缀和为 sum
,其中,故子数组 [l, r]
的元素和为 sum[r + 1] - sum[l]
。
设 sum
的前缀和为 ssum
,其中,故 sum[L ~ R]
的和为 ssum[R + 1] - ssum[L]
。
则 strength[left[i] ~ right[i]]
(方便期间,令 L = left[i],R = right[i])范围内所有含有 strength[i]
(必须是该下标值) 的子序列的和的和可以表示为:
计算出所有元素的贡献并累加即为答案。
最终代码:
1 | class Solution |
时间复杂度为:,空间复杂度为。