LeetCode笔记:Weekly Contest 207 比赛记录

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

0. 赛后总结

leetcode中国和我犯冲觉得没跑了,又一次刷新了我成绩的下限,只做出两道题,国内排名900多,世界都快3000了,从我做题多起来之后已经n久n久没有只做出过两题了,结果最近一段时间居然出现的这么频繁,简直了。。。

这一次真的是,第三题思路事实上是有的,就是实现上的问题,结果卡在那里卡了那么久,到最后都没能搞定,心累。

好好研究一下吧,这时候也只能这么告诫自己了。。。

1. 题目一

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

  • 5519. 重新排列单词间的空格

1. 解题思路

这一题的思路挺简单的,就是统计总的空格字符个数,然后均分到单字之间,最后拼接起所有的单词就行了。

2. 代码实现

给出python代码实现如下:

class Solution:
    def reorderSpaces(self, text: str) -> str:
        tokens = text.split()
        n = len(tokens)
        blank = len(text) - len("".join(tokens))
        if n == 1:
            return tokens[0] + blank * " "
        k = blank // (n-1)
        r = blank % (n-1)
        return (" " * k).join(tokens) + r * " "

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

当前最优的方案耗时16ms,看了一下,算法思路是相同的,所以这里就不再过多展开了。

2. 题目二

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

  • 5520. 拆分字符串使唯一子字符串的数目最大

1. 解题思路

这一题其实也还好,因为总字符数不会超过16,因此只需要做一个迭代算法就行了。

考察如果在idx位置切一刀,看一下获得的字符串是否会与之前的字符串重复,如果不重复,那么遍历下一个可能的idx继续切,知道idx达到字符串尾部,考察总切分的段数是否最大。

2. 代码实现

给出python代码实现如下:

class Solution:
    def maxUniqueSplit(self, s: str) -> int:
        n = len(s)
        if n == 1:
            return 1
        
        ans = 1
        used = set()
        def dfs(idx):
            nonlocal ans, used
            # print(idx, used)
            if idx >= n:
                ans = max(ans, len(used))
            for i in range(idx+1, n+1):
                if s[idx: i] not in used:
                    used.add(s[idx:i])
                    dfs(i)
                    used.remove(s[idx:i])
            return
        
        dfs(0)
        # print("="*10)
        return ans

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

当前的最优算法耗时仅为40ms,有近一个量级的差异,因此,我们需要好好研究一下为什么。

3. 代码优化

考察当前的最优算法,发现算法实现思路上事实上是相同的,但是他做了剪枝操作,剪枝原理为:

  • 当某次切分完成之后,如果后续的总的字符数目加上当前已经切割的字符段数都不会超过当前最大的字符段数时,那么就没有必要在继续切割了。

给出相应的python代码如下:

class Solution:
    def maxUniqueSplit(self, s: str) -> int:
        n = len(s)
        if n == 1:
            return 1
        
        ans = 1
        used = set()
        def dfs(idx):
            nonlocal ans, used
            if idx >= n:
                ans = max(ans, len(used))
            elif len(used) + n-idx+1 <= ans:
                return
            for i in range(idx+1, n+1):
                if s[idx: i] not in used:
                    used.add(s[idx:i])
                    dfs(i)
                    used.remove(s[idx:i])
            return
        
        dfs(0)
        return ans

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

上述代码执行效率和最优方案还有一点点差异,来源于集合的选择以及不停的加入和删除操作,但是整体已经无伤大雅,没有必要再过多展开了。

3. 题目三

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

  • 5521. 矩阵的最大非负积

1. 解题思路

这道题思路其实就是一个深度优先搜索,从开始节点遍历到终止节点,然后返回最大路径的乘积即可。

但是,非常坑爹的是,上午我居然搞这道题搞了一个多小时还没有搞出来,就这么一个简单的思路,顺着去实现居然没实现出来,先是看错题目,然后是超时,再来后面就是各种小bug不断,又不甘心放弃这么简单的一道题目,就一直耗着,结果耗到最后都没有搞定。

然后的然后,最郁闷的是,晚上重新试了一下,还是一样的思路,还是一样的写法,一发入魂,直接搞定。。。

果然上午是中了邪啊!!!

>﹏<

废话不多说了,下面直接上代码吧,也挺好理解的。

2. 代码实现

给出python代码实现如下:

class Solution:
    def maxProductPath(self, grid: List[List[int]]) -> int:
        n = len(grid)
        m = len(grid[0])
        
        @lru_cache(None)
        def dfs(r, c):
            if r >= n or c >= m:
                return None, None, None
            if grid[r][c] == 0:
                return 0, None, None
            elif r == n-1 and c == m-1:
                if grid[r][c] > 0:
                    return None, grid[r][c], None
                else:
                    return None, None, grid[r][c]
            if grid[r][c] > 0:
                x1, y1, z1 = dfs(r+1, c)
                x2, y2, z2 = dfs(r, c+1)
                x = None if x1 is None and x2 is None else 0
                y = None if y1 is None and y2 is None else max(k for k in [y1, y2] if k is not None) * grid[r][c]
                z = None if z1 is None and z2 is None else min(k for k in [z1, z2] if k is not None) * grid[r][c]
            else:
                x1, y1, z1 = dfs(r+1, c)
                x2, y2, z2 = dfs(r, c+1)
                x = None if x1 is None and x2 is None else 0
                z = None if y1 is None and y2 is None else max(k for k in [y1, y2] if k is not None) * grid[r][c]
                y = None if z1 is None and z2 is None else min(k for k in [z1, z2] if k is not None) * grid[r][c]
            return x, y, z
        
        zero, pos, neg = dfs(0,0)
        if pos is not None:
            return pos % 1000000007
        elif zero is not None:
            return zero
        else:
            return -1

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

当前的最优解耗时40ms,看了一下思路,整体大差不差,细节上有点不同,因此这里就不展开了。

4. 题目四

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

  • 5522. 连通两组点的最小成本

1. 解题思路

这一题比赛的时候因为上面第三题的坑爹原因就完全没有看,后来赛后看了一下,感觉多少有一点思路。

看到这题的第一想法是仿照上一次比赛的最小连通图算法进行实现,但是后来试了一下之后发现不对,因为有的时候为了一次性连接两个点,最短边可能不是第一选择。

那么直接的想法就是直接暴力遍历求解了,类似于八皇后问题那样,对于每一行,考虑每一条边是否使用的情况,直至遍历完全部的可能性。

2. 代码实现

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

import math

class Solution:
    def connectTwoGroups(self, cost: List[List[int]]) -> int:
        n = len(cost)
        m = len(cost[0])
        
        @lru_cache(None)
        def dfs(i, j, a, b):
            if i >= n:
                return 0 if all(y == 1 for y in b) else math.inf
            if j >= m:
                return dfs(i+1, 0, a, b) if a[i] == 1 else math.inf
            if a[i] == 1 and b[j] == 1:
                return dfs(i, j+1, a, b)
            else:
                anext, bnext = list(a), list(b)
                anext[i] = 1
                bnext[j] = 1
                anext, bnext = tuple(anext), tuple(bnext)
                return min(cost[i][j] + dfs(i, j+1, anext, bnext), dfs(i, j+1, a, b))
        
        a = tuple([0 for _ in range(n)])
        b = tuple([0 for _ in range(m)])
        return dfs(0, 0, a, b)

提交代码评测得到:耗时5036ms,占用内存199.1MB。勉勉强强没有超时,但是效率和内存使用上绝对算不上优秀,只有代码可读性上自认为还是能够过得去的。

而当前的最优算法耗时仅516ms,占用内存也基本都在14MB这个量级,哪个都比我们的算法高了一整个量级。

因此,这个算法有极大的提升空间。

下面,我们来考察一下他们的算法到底是怎么实现的。

3. 算法优化

看排行榜头部大神们的解答后发现,我们的思路果然存在着根本性的不同。

他们的思路不是考虑每条边是否被使用,而是先考虑点集B中的每个点使用哪一条边,而后给每一个点选择好一条边之后再回头去看点集A中那些点还没有被连接上,将没有被连上的点选择最短边进行添加。通过这种方式,可以确保最终构造的图一定是互联的。

另一方面,在态的保存方面,我们使用的是tuple来进行态的储存,因此有大量的时间耗在态的处理上,而他们使用的是位操作,这样就可以直接通过一个整型数来存储我们的态

综上,我们可以重新写出python实现脚本如下:

import math

class Solution:
    def connectTwoGroups(self, cost: List[List[int]]) -> int:
        n = len(cost)
        m = len(cost[0])
        
        @lru_cache(None)
        def dp(col, state):
            if col < m:
                ans = math.inf
                for i in range(n):
                    ans = min(ans, cost[i][col] + dp(col+1, state | (1 << i)))
                return ans
            if col == m:
                ans = 0
                for i in range(n):
                    if not state & (1 << i):
                        ans += min(cost[i])
                return ans
            
        return dp(0, 0)

上述代码提交之后评测得到:耗时884ms,占用内存30.2MB。比起之前一个版本有了大幅的提升。

看了一下,当前的最优方案耗时已经提升至了384ms,和我们依然有一倍的性能差异,但是看了一下他们的算法,和上述算法事实上是完全一致的,唯一的区别在于他们使用了列表生成式来替换了我们的两个for循环,因此这里就不再过多展开叙述了,有兴趣的读者可以自行替换试一下。

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