[LeetCode周赛复盘] 第 316 场周赛20221023

一、本周周赛总结

  • 这次挺难的。
  • T1水题。
  • T2 gcd st模板暴力。
  • T3 可以 数学:找中位数;或者前缀和遍历。
  • T4 找规律猜答案
  • T3 12:00:01提交过的,我可太难受了。
    在这里插入图片描述

二、 [Easy] 6214. 判断两个事件是否存在冲突

链接: 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 的子数组数目

链接: 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. 思路分析

这题我来两种做法:

  1. 排序前缀和然后模拟前后缀。
    • 显然可以发现,执行完操作后,所有的数字都将变成一个介于[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是可以从前边状态转移的。
  2. 数学,求带权中位数直接计算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. 使数组相似的最少操作次数

链接: 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

六、参考链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值