LeetCode笔记:Biweekly Contest 39 比赛记录

  • LeetCode笔记:Biweekly Contest 39
    • 0. 赛后总结
    • 1. 题目一
      • 1. 解题思路
      • 2. 代码实现
    • 2. 题目二
      • 1. 解题思路
      • 2. 代码实现
    • 3. 题目三
      • 1. 解题思路
      • 2. 代码实现
      • 3. 算法优化
    • 4. 题目四
      • 1. 解题思路
      • 2. 代码实现
      • 3. 算法优化

0. 赛后总结

这一次的比赛从成绩上来看是挺满意的,国内84,世界214,挺让人满意的一个结果了,不过可惜的是依然没能把四题全部做出来,卡死在了第三题上,简直了。

看看别人的做法学习一下吧,再接再厉,再接再厉!

1. 题目一

给出题目一的试题链接如下:

  • 5550. 拆炸弹

1. 解题思路

第一题整体还是蛮trival的一道题目,就是按照题目意思进行处理就行了,唯一复杂一点的地方就在于循环的处理。

但这个其实也不难,当k大于零时,我们在序列最后补上头上的k个数,反之当k小于0时,我们在序列头上补上末尾的k个数即可

2. 代码实现

给出最终的python代码实现如下:

class Solution:
    def decrypt(self, code: List[int], k: int) -> List[int]:
        n = len(code)
        if k == 0:
            return [0 for _ in range(n)]
        if k > 0:
            code = code + code[:k]
            return [sum(code[i+1:i+k+1]) for i in range(n)]
        else:
            k = -k
            code = code[-k:] + code
            return [sum(code[i-k:i]) for i in range(k, k+n)]

提交代码评测得到:耗时28ms,占用内存14.1MB。为当前最优的代码实现。

2. 题目二

给出题目二的试题链接如下:

  • 5551. 使字符串平衡的最少删除次数

1. 解题思路

这一题总感觉之前在哪里做过。。。不过本质来说,这题的思路还是非常直白的,它最终要得到的状态就是达到前面都是a,后面都是b的一个字符串,那么,我们要做的就是统计每一个字符位置前方的b的字符的个数以及后方的a的字符的个数,然后求其中的最小值即可。

2. 代码实现

我们直接给出python代码实现如下:

class Solution:
    def minimumDeletions(self, s: str) -> int:
        n = len(s)
        a = [0 for _ in range(n)]
        b = [0 for _ in range(n)]
        
        for i in range(n-1):
            if s[i] == 'b':
                b[i+1] = b[i]+1
            else:
                b[i+1] = b[i]
        for i in range(n-1, 0, -1):
            if s[i] == 'a':
                a[i-1] = a[i] + 1
            else:
                a[i-1] = a[i]
        return min(a[i] + b[i] for i in range(n))

提交代码评测得到:耗时1296ms,占用内存19.8MB。

当前的最优解法耗时仅500ms,看了一下他们的代码,发现思路上应该是一样的,不过实现细节上有所差异,有兴趣的读者可以自行去看一下,这里就不多做展开了。

3. 题目三

给出题目三的试题链接如下:

  • 5552. 到家的最少跳跃次数

1. 解题思路

隔了一天重新看了一下这题,结果昨天的解法和最终答案事实上就差了两行代码,简直了。。。

本质上来说,这个还是一个递归算法,有点像是八皇后问题,解题思路如下:

  1. 如果当前位置小于目标x,且可以向右走,则优先向右走,并将其记录到历史路径当中;
  2. 如果当前位置大于目标x,且可以向左走,则优先向左走,并将其记录到历史路径当中;
  3. 如果某一个位置已经出现在历史路径当中过了,那么这条路径必然不会是最优路径;
  4. 如果一条路径走到了尽头无法再走或者遇到了3中的情况,那么就回退直至上一个可以走下一步的路径遍历下一个可行的路径。

2. 代码实现

给出python代码实现如下:

class Solution:
    def minimumJumps(self, forbidden: List[int], a: int, b: int, x: int) -> int:
        forbidden = set(forbidden)
        
        seen = {
     0}
        
        @lru_cache(None)
        def dfs(loc, can_back):
            if loc == x:
                return 0
            if loc < x:
                if loc + a not in forbidden and loc + a not in seen:
                    seen.add(loc + a)
                    tmp = dfs(loc+a, True)
                    if tmp != -1:
                        return tmp + 1
                    seen.remove(loc+a)
                if can_back and loc - b >= 0 and loc - b not in seen and loc-b not in forbidden:
                    seen.add(loc-b)
                    tmp = dfs(loc-b, False)
                    if tmp != -1:
                        return tmp + 1
                    seen.remove(loc-b)
                return -1
            else:
                if b-a <= 0 and loc > x+b:
                    return -1
                if can_back and loc - b >= 0 and loc - b not in seen and loc-b not in forbidden:
                    seen.add(loc-b)
                    tmp = dfs(loc-b, False)
                    if tmp != -1:
                        return tmp + 1
                    seen.remove(loc-b)
                if loc >= 4000:
                    return -1
                if loc + a not in forbidden and loc + a not in seen:
                    seen.add(loc + a)
                    tmp = dfs(loc+a, True)
                    if tmp != -1:
                        return tmp + 1
                    seen.remove(loc+a)
                return -1
        return dfs(0, True)

提交代码评测得到:耗时96ms,占用内存21.4MB。

当前最优代码实现耗时68ms,看了一下,他的代码实现确实比我们的优雅,唉。。。

3. 算法优化

本质而言,当前最优算法和我们的思路是一样的,但是,他直接使用了一个队列来保存第k步时所有可能的路径,另外在第k步时走过的所有历史线路都直接加入到了forbidden当中,而不像我们哪个另外搞了一个seen集合来另外存储。

另一方面,他对于最大距离的处理也更加好,我们暴力地采用了两倍最大距离,即4000的方式,而他则对这个值进行了优化。

给出他的代码实现如下:

class Solution:
    def minimumJumps(self, forbidden: List[int], a: int, b: int, x: int) -> int:
        from collections import deque
        queue = deque([(0,1)])
        jumps = 0
        forbidden = set(forbidden)
        forbidden.add(0)
        MAX_DIST = max(x, max(forbidden)) + a + b
        while queue:
            tmp = deque()
            for elem in queue:
                (pos, direction) = elem
                if pos == x: 
                    return jumps
                forward = pos + a
                if forward not in forbidden and forward <= MAX_DIST:
                    tmp.append((forward, 1))
                    forbidden.add(forward)
            for elem in queue:
                (pos, direction) = elem
                if pos == x: 
                    return jumps
                backward = pos - b
                if direction != -1 and backward >= 0 and backward not in forbidden:
                    tmp.append((backward, -1))
                    forbidden.add(backward)
            queue = tmp
            jumps += 1
        return -1

4. 题目四

给出题目四的试题链接如下:

  • 5553. 分配重复整数

1. 解题思路

这一题比较直接的一个思路就是仿照八皇后问题那样使用递归的一种算法,将能够分配的先进行分配,直至分配完成或者无法继续匹配为止。

如果匹配完成,则直接返回True即可,否则退回上一次分配,使用下一个可行的分配方案进行分配,直至有一个成功或者所有的候选方案全部匹配失败为止,然后重复这个步骤。

2. 代码实现

给出python代码实现如下:

class Solution:
    def canDistribute(self, nums: List[int], quantity: List[int]) -> bool:
        counter = list(Counter(nums).values())
        quantity = sorted(quantity, reverse=True)
        n = len(quantity)
        
        def dfs(idx):
            if idx >= n:
                return True
            for i, c in enumerate(counter):
                if c < quantity[idx]:
                    continue
                counter[i] -= quantity[idx]
                if dfs(idx+1):
                    return True
                counter[i] += quantity[idx]
            return False
        
        return dfs(0)

提交代码评测得到:耗时4628ms,占用内存25.6MB。

当前最优的解法耗时仅512ms,较之我们的解法有近一个量级的性能提升,因此,我们有必要好好研究一下他们的解法。

3. 算法优化

这个算法的优化我基本放弃了,原则上来说上述dfs代码中最浪费时间的在于每一次都遍历了全部的数据,但是如果我们能够通过某种方式维护住这个有序序列的话,那么就可以减少很多的冗余操作。

但是,这里我们并没有想到什么比较好的方法来消除这个冗余操作,看了一下他们的代码,感觉应该也是在这部分进行了优化,不过实在是不想看他们的代码了,有点累,就直接摘录他们的代码如下了,有兴趣的读者可以自行研究一下。

import heapq
class Solution:
    def canDistribute(self, nums: List[int], quantity: List[int]) -> bool:
        cnt=collections.Counter(nums)
        vs=sorted(cnt.values())
        qt=tuple(sorted(quantity))
        cusum=[vs[0]]*len(vs)
        for i in range(1,len(vs)):
            cusum[i]=cusum[i-1]+vs[i]
        
        def helper(qt,v):
            cands=set()
            seen={
     qt}
            stack=[[qt,v]]
            while stack:
                cqt,cv=stack.pop()
                if cqt[0]>cv:
                    cands.add((sum(cqt),cqt))
                else:
                    for i in range(len(cqt)):
                        if cqt[i]<=cv:
                            nv=cv-cqt[i]
                            nqt=cqt[:i]+cqt[i+1:]
                            if nqt not in seen:
                                seen.add(nqt)
                                stack.append([nqt,nv])
            return cands           
        
        state=[[sum(qt),qt,len(vs)-1]]
        while state:
            csum,cqt,j=heappop(state)
            rem=cusum[j]
            if j==0:
                return rem>=csum
            rem-=cusum[j-1]
            if rem>=csum:
                return True
            if cusum[j]>=csum and cqt[-1]<=vs[j]:
                cands=helper(cqt,vs[j])
                for nsum,nqt in cands:
                    if nsum<=cusum[j-1] and nqt[-1]<=vs[j-1]:
                        heapq.heappush(state,[nsum,nqt,j-1])
        return False

你可能感兴趣的:(leetcode笔记,leetcode,算法,python)