LeetCode笔记:Weekly Contest 203 比赛记录

  • LeetCode笔记:Weekly Contest 203
    • 0. 赛后总结
    • 1. 题目一
      • 1. 解题思路
      • 2. 代码实现
    • 2. 题目二
      • 1. 解题思路
      • 2. 代码实现
    • 3. 题目三
      • 1. 解题思路
      • 2. 代码实现
    • 4. 题目四
      • 1. 解题思路
      • 2. 代码实现

0. 赛后总结

昨天刚刚历史性地第一次打进了前500,今天转头就历史性地打进了前200,实在是有点开心。

不过说到底还是因为题目比较简单的关系,看了一下,前头的大佬们都是10分钟左右就做完的,实在不认为这次的题目比较难。

而且这次虽然说把4道题都搞定了,不过最后看了一下最后一题的实现细节,发现还是很险的,耗时花了4100ms,而最优解答仅耗时96ms,说明明显就是暴力解答涉险过关了,对整体的问题还是需要好好理解优化一下才行。

不过终归来说,还是非常开心的,就让我们先点一份炸鸡好好犒劳一下自己吧,哈哈。

1. 题目一

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

  • 1560. Most Visited Sector in a Circular Track

1. 解题思路

这一题题目很长,但是其实挺简单的,因为一直是在绕圈,所以只要看最后一圈中哪些点被多访问了一次即可。即是说,答案就是 r o u n d s [ 0 ] → r o u n d s [ − 1 ] rounds[0] \rightarrow rounds[-1] rounds[0]rounds[1]

但是,需要注意的是,起点坐标并不为0,因此,可能会有终点的坐标值事实上小于起点坐标的情况,需要特殊考虑一下。

2. 代码实现

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

class Solution:
    def mostVisited(self, n: int, rounds: List[int]) -> List[int]:
        if rounds[0] <= rounds[-1]:
            return list(range(rounds[0], rounds[-1]+1))
        else:
            return list(range(1, rounds[-1]+1)) + list(range(rounds[0], n+1))

提交代码评测得到:耗时52ms,占用内存14.1MB。略逊于当前最优解答,但是算法本质上毫无差别。

2. 题目二

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

  • 1561. Maximum Number of Coins You Can Get

1. 解题思路

这一题的关键在于考虑清楚选择策略。由于选择次序在第二位,因此,我们无法控制上位(即Alice)的选择,她的选择永远会比我们大,但是,为了让我们最终的结果更优,我们可以强行安排Bob,让他每次取的都是全局最小的数字。

如此一来,我们的最优策略就是取第 3 × i + 2 ( i = 0 , . . . , n − 1 ) 3\times i + 2 (i = 0, ..., n-1) 3×i+2(i=0,...,n1)大的堆中的硬币(假设总共3n个堆的硬币)

2. 代码实现

给出代码实现如下:

class Solution:
    def maxCoins(self, piles: List[int]) -> int:
        piles = sorted(piles, reverse = True)
        n = len(piles) // 3
        return sum([piles[2*i+1] for i in range(n)])

提交代码评测得到:耗时784ms,占用内存26.2MB。并非当前最优方案,但是本质上来说两者并没有太大的差异。

3. 题目三

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

  • 1562. Find Latest Group of Size M

1. 解题思路

这一题的关键点在于连续数字1的存储方式。如果我们用一个长度为n的数组来真实记录当前的状态,然后每一次都查找是否存在有长度恰好为m的子串,那效率显然是极低的,必然会引发超时问题。

更为合理的方式是只保存每一个子串的开始和结束坐标。另一方面,由于每一次操作事实上只会改变与之相关的子串的长度,因此,我们不需要统计每一次的全部子串的长度,只要看每一次操作之后长度恰好为m的子串的数目是否有所变化即可

2. 代码实现

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

class Solution:
    def findLatestStep(self, arr: List[int], m: int) -> int:
        ans = -1
        start_set = {
     }
        end_set = {
     }
        counter = 0
        for idx, n in enumerate(arr):
            if n - 1 in end_set and n + 1 in start_set:
                st = end_set.pop(n-1)
                ed = start_set.pop(n+1)
                end_set[ed] = st
                start_set[st] = ed
                if n - st == m:
                    counter -= 1
                if ed - n == m:
                    counter -= 1
                if ed - st + 1 == m:
                    counter += 1
            elif n - 1 in end_set:
                st = end_set.pop(n-1)
                end_set[n] = st
                start_set[st] = n
                if n - st == m:
                    counter -= 1
                elif n-st+1 == m:
                    counter += 1
            elif n + 1 in start_set:
                ed = start_set.pop(n+1)
                start_set[n] = ed
                end_set[ed] = n
                if ed - n == m:
                    counter -= 1
                elif ed-n+1 == m:
                    counter += 1
            else:
                start_set[n] = n
                end_set[n] = n
                if m == 1:
                    counter += 1
            if counter > 0:
                ans = idx + 1
        return ans

上述代码多少有些繁琐,但是思路就是那么一个思路,无非就是在每一次操作之后考察是否会波及其他现有的子串,然后更新子串的长度,并修改当前所有的长度为m的子串的个数

提交上述代码之后,评测得到:耗时1696ms,占用内存27.8MB。

而当前的最优算法耗时仅为680ms。看了一下他们的解法,发现本质思路上是相同的,但是他们的存储方式上更为优雅。他们采用列表的方式储存切割点,每次都采用二分插入的方式进行插入。如果有兴趣的读者可以自行尝试实现一下。

4. 题目四

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

  • 1563. Stone Game V

1. 解题思路

针对这一题,我的解题思路是非常暴力的,直接上手就是一个动态规划,只要不超时,那就一定不会错。

2. 代码实现

给出代码实现如下:

class Solution:
    def stoneGameV(self, stoneValue: List[int]) -> int:
        n = len(stoneValue)
        cumsum = [0 for i in range(n + 1)]
        for i, v in enumerate(stoneValue):
            cumsum[i+1] = cumsum[i] + stoneValue[i]
        
        @lru_cache(None)
        def dp(st, ed):
            nonlocal cumsum
            if ed - st == 1:
                return 0
            ans = 0
            for i in range(st+1, ed):
                if cumsum[i] - cumsum[st] < cumsum[ed] - cumsum[i]:
                    ans = max(ans, cumsum[i] - cumsum[st] + dp(st, i))
                elif cumsum[i] - cumsum[st] > cumsum[ed] - cumsum[i]:
                    ans = max(ans, cumsum[ed] - cumsum[i] + dp(i, ed))
                else:
                    ans = max(ans, cumsum[i] - cumsum[st] + dp(st, i), cumsum[ed] - cumsum[i] + dp(i, ed))
            return ans
        
        return dp(0, n)

这一次的运气非常不错,上述代码并没有超时,提交能够通过,但是耗时4100ms,占用内存39.1MB。远高于当前最优的解法,后者仅耗时96ms。

但是,让人无语的是,看了半天没看懂当前的最优算法是什么意思,然后直接复制了代码跑了一下,结果居然运行失败了,失败了,败了,了。。。

what the f**k ?!

然后我又测试了一下几个次优的算法方案,发现和我们的算法本质上就是一样的,执行效率也大差不差,目前我测得的最优的一个算法执行耗时3640ms,远远没有宣称的10倍左右的性能提升。

然后看了一下比赛中头几名的大佬们基于C++的解答,也都是用的动态规划方法,唯一的区别我使用了缓存,他们测试使用二维数组来进行数据的存储,本质上来说是一样的。

综上,目前来看,现阶段上述解法应该就是当前的最优算法思路了。暂时大约是没有什么特别优雅的替代算法能够在量级上大幅提升效率了。

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