这一次的比赛结果依然是中规中矩,35分钟搞定了3题,然后很不幸的错了两次,但是总排名还算是OK,千名以内,果然双周比赛的时间点选的太尴尬,大晚上的参赛的人数比较少。。。
看了一下大神们的答题时间,again又一次被深深地打击到,排名第一的老哥仅用时8分钟,前25的耗时差不多都在20分钟以内,这差距,简直是心痛得不行。
果然是路漫漫其修远兮,慢慢锻炼吧。。。
另外,真的是被出租屋里面的网络坑死了,最后半个小时基本完全是在和网速搏斗,根本没法好好调试,这感觉,简直酸爽。。。
给出题目一的试题链接如下:
这一题坦率地说感觉应该是有一些比较巧妙的方法的,但是由于题目中限制了arr的长度为1000以内,且k同样在1000以内,导致哪怕用最暴力的遍历法,时间复杂度事实上也不会超过2000。
因此,针对这一问题,我们就很trivial地采用了暴力检索的方式来找寻第k个确实的正整数。
给出代码实现如下:
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。
这一执行效率略低于第一梯队,但是考察其实现方法,发现他们的方法也是暴力检索的方式,算法本身而言并无差异。
给出题目二的试题链接如下:
这一题的解题思路其实挺简单,就是去看每一位变化时需要进行多少次操作,如果需要的操作次数多于最大上限,那么就返回False,否则返回True。
另外,针对相同转换次数的情况,我们只要将其加上26,即多转一圈就行了。
给出代码实现如下:
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。属于当前最优解答。
给出题目三的试题链接如下:
这一题的解题思路事实上也挺清晰的,就是仿照栈的思路,定义左括号为2,右括号为-1,然后依次消去即可。
但是需要注意的是:
根据上述思路,我们就可以得到我们的代码实现如下:
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。均属于当前最优结果。
给出题目四的试题链接如下:
这一题在比赛的时候没能搞出来,一直卡在超时问题上了,当时的想法就是硬怼动态规划,结果一直超时,直到最后都没有搞定。
这题的解法思路其实也比较直接,就是想办法找到一个开始节点st
以及一个终止节点ed
,令他们满足以下两个条件即可:
s[st:ed]
中总数为奇数的字符个数不多于1;因此,我们很快就可以给出一个动态规划的代码样例如下:
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个测试样例而已。
比赛结束之后我研读了大佬们的解答,发现大佬们的算法思路上事实上并没有本质的区别,同样也是去找st
与ed
使之满足上述两个条件。
但是,大佬们的实现手法上和我有着本质的区别,我本质上是用一个二层循环进行遍历,而大佬们的方法是通过一个字典储存历史,对每一个位置,寻找历史中是否存在可以使其成为awesome子串的开始节点,然后比较更新答案为较大值;
另外,大佬们对于各个digit的历史个数统计方面也处理得更好:
据此,我们可以修改我们的代码得到下述的优化代码:
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%,总体效率上也还算过得去。