LeetCode笔记:Biweekly Contest 32 比赛记录

0. 赛后总结

这一次的比赛结果依然是中规中矩,35分钟搞定了3题,然后很不幸的错了两次,但是总排名还算是OK,千名以内,果然双周比赛的时间点选的太尴尬,大晚上的参赛的人数比较少。。。

看了一下大神们的答题时间,again又一次被深深地打击到,排名第一的老哥仅用时8分钟,前25的耗时差不多都在20分钟以内,这差距,简直是心痛得不行。

果然是路漫漫其修远兮,慢慢锻炼吧。。。

另外,真的是被出租屋里面的网络坑死了,最后半个小时基本完全是在和网速搏斗,根本没法好好调试,这感觉,简直酸爽。。。

1. 题目一

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

1. 解题思路

这一题坦率地说感觉应该是有一些比较巧妙的方法的,但是由于题目中限制了arr的长度为1000以内,且k同样在1000以内,导致哪怕用最暴力的遍历法,时间复杂度事实上也不会超过2000。

因此,针对这一问题,我们就很trivial地采用了暴力检索的方式来找寻第k个确实的正整数。

2. 代码实现

给出代码实现如下:

class Solution:
    def findKthPositive(self, arr: List[int], k: int) -> int:
        counter = 0
        n = len(arr)
        arr = set(arr)
        for i in range(1, n + k + 1):
            if i not in arr:
                counter += 1
            if counter == k:
                return i
        return -1

提交代码运行得到评测结果:耗时48ms,占用内存13.9MB。

这一执行效率略低于第一梯队,但是考察其实现方法,发现他们的方法也是暴力检索的方式,算法本身而言并无差异。

2. 题目二

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

1. 解题思路

这一题的解题思路其实挺简单,就是去看每一位变化时需要进行多少次操作,如果需要的操作次数多于最大上限,那么就返回False,否则返回True。

另外,针对相同转换次数的情况,我们只要将其加上26,即多转一圈就行了。

2. 代码实现

给出代码实现如下:

class Solution:
    def canConvertString(self, s: str, t: str, k: int) -> bool:
        if len(s) != len(t):
            return False
        counter = {
    
    i: 0 for i in range(1,26)}
        for c1, c2 in zip(s,t):
            delta = (ord(c2) - ord(c1)) % 26
            if delta == 0:
                continue
            tmp = delta + 26 * counter[delta]
            if tmp > k:
                return False
            counter[delta] += 1
        return True

提交代码评测得到:耗时416ms,占用内存15.1MB。属于当前最优解答。

3. 题目三

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

1. 解题思路

这一题的解题思路事实上也挺清晰的,就是仿照栈的思路,定义左括号为2,右括号为-1,然后依次消去即可。

但是需要注意的是:

  1. 右括号必须成对出现,因此当其不成对时,我们需要对其进行补足;
  2. 左括号必须出现在右括号左侧,因此当开头一片都是右括号时,我们需要首先将左括号进行补足操作。

2. 代码实现

根据上述思路,我们就可以得到我们的代码实现如下:

class Solution:
    def minInsertions(self, s: str) -> int:
        counter = 0
        n = len(s)
        ans = 0
        consecutive_right = 0
        for i in range(n):
            if s[i] == '(':
                if consecutive_right % 2 == 1:
                    counter -= 1
                    ans += 1
                    consecutive_right = 0
                if counter <= 0:
                    ans += (-counter) // 2
                    counter = 0
                counter += 2
            else:
                counter -= 1
                consecutive_right += 1
        if consecutive_right % 2 == 1:
            counter -= 1
            ans += 1
        if counter >= 0:
            ans += counter
        else:
            ans += (-counter) // 2
        return ans

提交代码得到评测结果:耗时196ms,占用内存14.4MB。均属于当前最优结果。

4. 题目四

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

1. 解题思路

这一题在比赛的时候没能搞出来,一直卡在超时问题上了,当时的想法就是硬怼动态规划,结果一直超时,直到最后都没有搞定。

这题的解法思路其实也比较直接,就是想办法找到一个开始节点st以及一个终止节点ed,令他们满足以下两个条件即可:

  1. s[st:ed]中总数为奇数的字符个数不多于1;
  2. e d − s t ed-st edst最大;

2. 代码实现

因此,我们很快就可以给出一个动态规划的代码样例如下:

class Solution:
    def longestAwesome(self, s: str) -> int:
        n = len(s)
        cumsum = [[0]*10 for i in range(n+1)]
        for i, c in enumerate(s):
            cumsum[i+1][ord(c)-ord('0')] += 1
            for k in range(10):
                cumsum[i+1][k] += cumsum[i][k]

        @lru_cache(None)
        def is_awesome(i, j):
            tmp = [1 for x, y in zip(cumsum[i], cumsum[j]) if (y-x) % 2 == 1]
            return len(tmp) <= 1
        
        @lru_cache(None)
        def dp(i, j):
            if i >= j:
                return 0
            if is_awesome(i,j):
                return j-i
            else:
                return max(dp(i+1, j), dp(i, j-1))
            
        return dp(0, n)

可惜尽管上述方式通过缓存的方式实现了伪动态规划,但是时间复杂度仅仅是在 O ( N 2 ) O(N^2) O(N2)的情况下进行了大量的剪枝而已,在最坏的情况下,他还是会有 O ( N 2 ) O(N^2) O(N2)的时间复杂度。

最终153个测试样例我也只通过了133个测试样例而已。

3. 算法优化

比赛结束之后我研读了大佬们的解答,发现大佬们的算法思路上事实上并没有本质的区别,同样也是去找sted使之满足上述两个条件。

但是,大佬们的实现手法上和我有着本质的区别,我本质上是用一个二层循环进行遍历,而大佬们的方法是通过一个字典储存历史,对每一个位置,寻找历史中是否存在可以使其成为awesome子串的开始节点,然后比较更新答案为较大值;

另外,大佬们对于各个digit的历史个数统计方面也处理得更好:

  1. 由于事实上我们只需要比较奇偶性,因此我们不需要记录每一个digit出现的次数,而是只要记录其总次数的奇偶性即可;
  2. 由于只需要记录奇偶性,因此我们不需要使用list而是使用位操作的方式使用一个10位的二进制数即可进行上述次数统计的储存;

据此,我们可以修改我们的代码得到下述的优化代码:

class Solution:
    def longestAwesome(self, s: str) -> int:
        counter = 0
        cache = {
    
    counter: 0}
        
        ans = 0
        ed = 1
        for c in s:
            counter = counter ^ (1 << (ord(c)-ord('0')))
            if counter in cache:
                ans = max(ans, ed - cache[counter])
            else:
                cache[counter] = ed
                
            bias = 1
            for _ in range(10):
                tmp = counter ^ bias
                if tmp in cache:
                    ans = max(ans, ed - cache[tmp])
                bias = bias << 1
            ed += 1
            
        return ans

如此一来,算法的时间复杂度就可以从 O ( N 2 ) O(N^2) O(N2)降低至 O ( N ) O(N) O(N)

提交代码之后成功通过,评测得到:耗时1408ms,占用内存14.7MB。

算不上当前最优的实现代码,但是总算还是能够排在前20%,总体效率上也还算过得去。

猜你喜欢

转载自blog.csdn.net/codename_cys/article/details/107947318
今日推荐