leetcode中国和我犯冲觉得没跑了,又一次刷新了我成绩的下限,只做出两道题,国内排名900多,世界都快3000了,从我做题多起来之后已经n久n久没有只做出过两题了,结果最近一段时间居然出现的这么频繁,简直了。。。
这一次真的是,第三题思路事实上是有的,就是实现上的问题,结果卡在那里卡了那么久,到最后都没能搞定,心累。
好好研究一下吧,这时候也只能这么告诫自己了。。。
给出题目一的试题链接如下:
这一题的思路挺简单的,就是统计总的空格字符个数,然后均分到单字之间,最后拼接起所有的单词就行了。
给出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,看了一下,算法思路是相同的,所以这里就不再过多展开了。
给出题目二的试题链接如下:
这一题其实也还好,因为总字符数不会超过16,因此只需要做一个迭代算法就行了。
考察如果在idx位置切一刀,看一下获得的字符串是否会与之前的字符串重复,如果不重复,那么遍历下一个可能的idx继续切,知道idx达到字符串尾部,考察总切分的段数是否最大。
给出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,有近一个量级的差异,因此,我们需要好好研究一下为什么。
考察当前的最优算法,发现算法实现思路上事实上是相同的,但是他做了剪枝操作,剪枝原理为:
给出相应的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。
上述代码执行效率和最优方案还有一点点差异,来源于集合的选择以及不停的加入和删除操作,但是整体已经无伤大雅,没有必要再过多展开了。
给出题目三的试题链接如下:
这道题思路其实就是一个深度优先搜索,从开始节点遍历到终止节点,然后返回最大路径的乘积即可。
但是,非常坑爹的是,上午我居然搞这道题搞了一个多小时还没有搞出来,就这么一个简单的思路,顺着去实现居然没实现出来,先是看错题目,然后是超时,再来后面就是各种小bug不断,又不甘心放弃这么简单的一道题目,就一直耗着,结果耗到最后都没有搞定。
然后的然后,最郁闷的是,晚上重新试了一下,还是一样的思路,还是一样的写法,一发入魂,直接搞定。。。
果然上午是中了邪啊!!!
>﹏<
废话不多说了,下面直接上代码吧,也挺好理解的。
给出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,看了一下思路,整体大差不差,细节上有点不同,因此这里就不展开了。
给出题目四的试题链接如下:
这一题比赛的时候因为上面第三题的坑爹原因就完全没有看,后来赛后看了一下,感觉多少有一点思路。
看到这题的第一想法是仿照上一次比赛的最小连通图算法进行实现,但是后来试了一下之后发现不对,因为有的时候为了一次性连接两个点,最短边可能不是第一选择。
那么直接的想法就是直接暴力遍历求解了,类似于八皇后问题那样,对于每一行,考虑每一条边是否使用的情况,直至遍历完全部的可能性。
我们给出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这个量级,哪个都比我们的算法高了一整个量级。
因此,这个算法有极大的提升空间。
下面,我们来考察一下他们的算法到底是怎么实现的。
看排行榜头部大神们的解答后发现,我们的思路果然存在着根本性的不同。
他们的思路不是考虑每条边是否被使用,而是先考虑点集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循环,因此这里就不再过多展开叙述了,有兴趣的读者可以自行替换试一下。