[LeetCode周赛复盘] 第 316 场周赛20221023
一、本周周赛总结
- 这次挺难的。
- T1水题。
- T2 gcd st模板暴力。
- T3 可以 数学:找中位数;或者前缀和遍历。
- T4 找规律猜答案
- T3 12:00:01提交过的,我可太难受了。
二、 [Easy] 6214. 判断两个事件是否存在冲突
1. 题目描述
2. 思路分析
按题意模拟即可。
- 按左端点排序,如果没有重叠,前边的右端点应该小于后边的左端点
3. 代码实现
class Solution:
def haveConflict(self, event1: List[str], event2: List[str]) -> bool:
a = [event1,event2]
a.sort()
if a[0][1] >= a[1][0]:
return True
return False
三、[Medium] 6224. 最大公因数等于 K 的子数组数目
1. 题目描述
2. 思路分析
- 这题一眼看到是区间gcd,就知道可以套st模板;
- 又看了眼数据量可以N方,知道可以套板后暴力。
- 不过显然对于某个固定右端点来说,左端点的大小和区间gcd是有单调递增关系的,所以其实遍历r可以二分两次,找到对r来说l的上下界;时间可以优化成nlgn。(这里忽略gcd的开销(lg))(116 ms)
- 比赛时还是想了一会有没有更好地方案没想出来于是决定套板。
- 比赛时纠结了一会,wa了,因为l写成了range(0,n-1),其实本题区间大小可以是1
- 补充,看了别人的题解,发现其实不用st,直接n方遍历即可,因为gcd的继承性,我们在固定l时,r可以一直向右继承计算的。又由于这个单调性,还可以提前退出。(120 ms)
3. 代码实现
class SparseTable:
def __init__(self, data: list, func=gcd):
# 稀疏表,O(nlgn)预处理,O(1)查询区间最值/或和/gcd
# 下标从0开始
self.func = func
self.st = st = [list(data)]
i, N = 1, len(st[0])
while 2 * i <= N+1:
pre = st[-1]
st.append([func(pre[j], pre[j + i]) for j in range(N - 2 * i + 1)])
i <<= 1
def query(self, begin: int, end: int): # 查询闭区间[begin, end]的最大值
lg = (end - begin+1).bit_length() - 1
return self.func(self.st[lg][begin], self.st[lg][end - (1 << lg) + 1])
class Solution:
def subarrayGCD(self, nums: List[int], k: int) -> int:
# st暴力
# n = len(nums)
# st = SparseTable(nums)
# ans = 0
# for l in range(n):
# for r in range(l,n):
# if st.query(l,r) == k :
# ans += 1
# return ans
# 直接暴力,利用gcd继承性 120ms
# n = len(nums)
# ans = 0
# for i in range(n):
# p = nums[i]
# if p < k:
# continue
# for j in range(i,n):
# p = gcd(p,nums[j])
# if p == k:
# ans += 1
# elif p < k:
# break
# return ans
# st二分 116ms
n = len(nums)
st = SparseTable(nums)
ans = 0
for i in range(n):
l = bisect_left(range(i+1),k,key = lambda x:st.query(x,i))
r = bisect_right(range(i+1),k,key = lambda x:st.query(x,i))
ans += r-l
return ans
四、[Medium] 6216. 使数组相等的最小开销
链接: 6216. 使数组相等的最小开销
1. 题目描述
2. 思路分析
这题我来两种做法:
- 排序前缀和然后模拟前后缀。
- 显然可以发现,执行完操作后,所有的数字都将变成一个介于[min,max]之间的数。
- 假设我们知道这个数是t,那么答案显然可以计算,只需要求一下每个数字的差的绝对值即可。(记得加权)
- 那么模拟每个num作为这个数t,向右移动时,变化当前ans,显然前边的所有数字要增长t-prev的长度ans+=(t-prev) * sum(pre) ;后边的所有数字省去减少suf-t的长度ans -= (t-prev) * sum(suff)。
- 这里有个问题,最终的数字t,不会出现在nums之外么,可以看下一种数学做法。
- 当然,看了执梗老师的代码后发现,可以枚举1~1e5之间所有数字作为答案也行,反正中间每个数作为答案的ans是可以从前边状态转移的。
- 数学,求带权中位数直接计算t。
- 先说结论:t一定可以选带权中位数那个值。
- 先看不带权的情况,假设我们的数组只有两个数[1,9],我们要把这个数组变成几答案最优呢,假设是t,显然1<=t<=9。
- 这时答案是t-1+9-t = 8,t可以任取[1,9]中的数字没有影响。
- 如果数组是[1,3,9]这时,t一定是取3,因为对[1,9]没有影响,取3可以使3对答案的贡献是0.
- 也就是说,数组最左最右的两个数可以抵消(他们对答案的贡献是固定的,不决定t)。
- 因此一直抵消,最终剩下那个数作为t就是最优的。
- 本题带权c,其实相当于每个数展开一下,增加成c个对应的num即可。
- 当然实现时不要真的展开模拟,这样内存就炸了。我们累计c的前缀和,发现到达total的一半就是中位数所在的数。
- 这也解答了第一种做法遗留的一个问题,就是t一定是可以选nums中的数字的。(就是中位数,如果没有中位数是偶数长度,则选中间俩任意即可。)
3. 代码实现
数学
class Solution:
def minCost(self, nums: List[int], cost: List[int]) -> int:
n = len(nums)
zp = sorted(zip(nums,cost))
# pre = list(accumulate([x[1] for x in zp])) # 这段是可以的代码短但是不便于理解。
# mid = bisect_left(pre,pre[-1]//2)
total = sum(cost)
s=t = 0
for x,y in zp:
s += y
if s >= total//2:
t = x
break
ans = 0
for x,y in zip(nums,cost):
ans += abs(x-t)*y
return ans
前缀和遍历枚举
class Solution:
def minCost(self, nums: List[int], cost: List[int]) -> int:
n = len(nums)
zp = sorted(zip(nums,cost))
pre = [0] + list(accumulate([x[1] for x in zp]))
def calc(pr,l,r):
return pr[r+1] - pr[l]
ans = 0
t = zp[0][0]
for i in range(1,n):
x,c = zp[i]
ans += (x-t)*c
ret = ans
for i in range(1,n):
cur,c = zp[i]
ret -= calc(pre,i,n-1)*(cur-t)
ret += calc(pre,0,i-1)*(cur-t)
t = cur
ans = min(ans,ret)
return ans
五、[Hard] 6217. 使数组相似的最少操作次数
1. 题目描述
2. 思路分析
- 这题比赛时猜了个结论。
- 首先我们发现每次操作都使数字的奇偶性保持不变。猜要奇偶分组。
- 那么如果我们要把一个大的数变成小的数,其实就是变t=(a-b)//2 步,同时我们多了t步可以用来把小的数变成大的数。
- 由于题目保证可以完成操作,那么我们不关小变大,只管大变小即可。
- 补充,灵神说通过临项交换法证明:我们必须小的和小的匹配转换,大的和大的匹配。先把题目转化为+1-1这样完全符合临项交换。
- 然后通过+2-2就分奇偶、如果是+3-3则是对3取模。
3. 代码实现
python一行流
class Solution:
def makeSimilar(self, nums: List[int], target: List[int]) -> int:
nums.sort(key = lambda x:(x%2,x))
target.sort(key = lambda x:(x%2,x))
return sum(x-y for x,y in zip(nums,target) if x>y) // 2
class Solution:
def makeSimilar(self, nums: List[int], target: List[int]) -> int:
n = len(nums)
nums.sort()
target.sort()
ans = 0
nums1 = [x for x in nums if x&1]
nums2 = [x for x in nums if x&1==0]
t1 = [x for x in target if x&1]
t2 = [x for x in target if x&1==0]
for x,y in zip(nums1,t1):
if x < y:
ans += (y-x)//2
for x,y in zip(nums2,t2):
if x < y:
ans += (y-x)//2
return ans