leetcode 第 217 场算法比赛
作者:
| 更新日期:线段树模板很重要
本文首发于公众号:天空的代码世界,微信号:tiankonguse
零、背景
这次比赛有点难度,但是想想还是可以做出来的。
一、最富有客户的资产总量
题意:给出每个客户每个账号里的资产,求资产最大的客户的资产。
思路:每个客户得资产求和,取最大值即可。
源代码:
https://github.com/tiankonguse/leetcode-solutions/blob/master/contest/217/A.cc
二、找出最具竞争力的子序列
题意:给一个长度为 n 的数组,求长度为 k 的子序列,使得子序列字典序最小。
思路:一个一个数字来看。
首先第一个数字可选的位置范围是 [1, n-k+1]
。
目标是选出这个范围的第一个最小值。
假设选择的是位置k1
,则第二个数字的可选范围是 [k1+1, n-k+2]
。
目标依旧是选出这个范围的第一个最小值。
由此可以想到一个数据结构可以快速求区间最小值,即线段树。
我当是写了一个可以返回下标的最小值线段树通过了。
源代码:
https://github.com/tiankonguse/leetcode-solutions/blob/master/contest/217/B.cc
赛后想,能不能使用更简单的方法来做呢?
毕竟这是 leetcode 的第二题,不能使用线段树这种 hard 级别的数据结构。
通过分析答案之间的关系,发现子序列的前半部分是递增的。
由此可以想到使用单调性这个性质来做这道题。
具体来说,对于前面可选的数据范围,数字需要保持单点非递减。
依据时 ,在可以任意选择的数字里面,后面遇到一个更小的数字,前面那些较大的数字不可能被选择,所以需要删除。
源代码:
https://github.com/tiankonguse/leetcode-solutions/blob/master/contest/217/B02.cc
三、使数组互补的最少操作次数
题意:给一个长度为偶数的数组,每一次操作可以选择一个数字设置为[1,limit]
之间的数字。
问最少需要多少次操作,才能使得所有的s[i] + s[n-i+1]
的和相等。
思路:初步看这道题,只能想到枚举和,然后判断这个和的操作次数。
复杂度O(n^2)
枚举每个和,计算每对数字的代价,画出这个n*n
的矩阵。
可以发现,对于每对数字,有一个和代价是0,两个区间的代价是1,两头的代价是 2。
另两个数字为 a 和 b 且 a<=b
。
具体公式如下
2: [b+limit+1, 2*limit]
1: [a+b+1, b+limit]
0: [a+b, a+b]
1: [a+1, a+b-1]
2: [2, a]
对于每个枚举和,我们其实是求矩阵一行的操作数和。
最终的目标是求最小的和。
这样,我们可以发现这是一个区间操作问题。
可以[2, 2*limit]
区间,每个数字会对这个区间进行 5 个区间加相同值得操作。
最终求区间的最小值。
这个可以直接套用线段树或者树状数组解决。
源代码:
https://github.com/tiankonguse/leetcode-solutions/blob/master/contest/217/C.cc
其实,对于若干区间加减操作,一次前缀和查询的场景,可以使用差分数组来解决。
差分的具体介绍大家可以看下面这个文档
https://oi-wiki.org/basic/prefix-sum/#_6
其实,树状数组的区间操作就是使用差分实现的。
后面我也会升级我的树状数组模板,支持区间操作。
四、数组的最小偏移量
题意:给一个数组,可以按下面的规则对数组的数字做任意次操作。
1)如果数字是偶数,可以除 2。
2)如果数字是奇数,可以乘 2。
问通过若干次操作后,使得数组里任意两个元素之间的最大差值最小。
PS:这里两个元素之间的最大差值误导了我,导致我理解错题意了。
我理解为求相邻的两个元素之间的差值最小。
思路:根据两个操作可以发现两个性质。
1)对于偶数,可以一直除,直到遇到奇数,顶多log(n)
个。
2)对于奇数,只能乘一次,得到偶数,顶多 2 个。
由此,可以根据这两个性质,得到一个二维矩阵,每一行是数组中数字可以操作的数字。
矩阵的大小顶多为n * log(n)
我们的目标是从上到下选择一条路径,使得路径上的最大值与最小值差值最小。
对于奇数,可以证明,我们先预处理转化为偶数最优答案不变(可以操作无数次)。
所以这里可以先预处理输入,是的所有输入都是偶数,只能越操作越小。
预处理之后,所有数字只能越操作越小。
由于需要使最大值与最小值差值最小,那只能优先操作最大的值。
毕竟这样才有可能使最大的差值变小,
于是这道题的方法就出来了。
维护一个可以得到最大值和最小值的数据结构,不断的找到一个找到最大值做除 2 操作,并更新答案。
当最大值是奇数的时候,就不能再操作了。
我选择的数据结构是 set。
源代码:
https://github.com/tiankonguse/leetcode-solutions/blob/master/contest/217/D.cc
当然,这道题也可以使用线段树来操作,即单点更新,区间查询。
具体方法是讲矩阵展开,即线段树有n log(n)
个节点。
删除最大值的时候,就将对应下标的值设置为 无效,然后将另一个下标的值设置为除 2。
五、最后
这次比赛的题比较有质量,竟然遇到三个线段树,你可以用来练习线段树了。
PS:周六尝试参加了 atcode 比赛,从第一题开始,题目就看不懂。
以为是例外,看了第二题,题目依旧看不懂。
又看了第三题,还是看不懂。
最终花了好久才看懂这些题的含义,不过最后还是放弃比赛了。
加油,算法人。
《完》
-EOF-
本文公众号:天空的代码世界
个人微信号:tiankonguse
公众号ID:tiankonguse-code
本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。