时间在2021年6月12号,由于端午节回家了,只有一个轻薄本,做不了我被虐待体无完肤的微信赛。加上前几天看了下重楼大哥打leetcode周赛的视频,看的我热血澎湃,由于重楼大哥答题速度很快,于是我看的也很快,给了我一种我上我也行的错觉,于是今天下午就登上了好久不登录的leetcode账号。实打实的菜鸟一枚。
由于是周六,所以并没有周赛,所以只能做做上周(244周)的题:
首先进入第一题:
看了几眼后明白了这个题目就判断一个正方形的矩阵旋转0°,90°,180°,270°之后与目标的target是否一样。
一顿思考之后拿出了我的笔写下了几个矩阵的旋转公式:
#旋转180度值得就是,行的啊a[i][j]=a[n-i][n-j]
#旋转90度为:a[i][j]=> a[j][n-i]
#旋转270度: a[i][j]=> a[n-i]a[n-j]=>a[n-j][i]
还有就是旋转0°,直接判断两个矩阵相等就可以了
接下来就简单了,就是简单粗暴的代码了:
(有点又丑又长)
class Solution(object):
def findRotation(self, mat, target):
"""
:type mat: List[List[int]]
:type target: List[List[int]]
:rtype: bool
"""
#旋转180度值得就是,行的i=n-i,j=n-j
#旋转90度为:a[i][j]=> a[j][n-i]
#旋转270度: a[i][j]=> a[n-i]a[n-j]=>a[n-j][i]
n=len(mat)-1
Flag1=True
Flag2=False
for i in range(n+1):
for j in range(n+1):
if mat[i][j]!=target[j][n-i]:
Flag2=True
Flag1=False
break
if Flag2:
break
if Flag1:
return Flag1
Flag2=False
Flag1=True
for i in range(n+1):
for j in range(n+1):
if mat[i][j]!=target[n-i][n-j]:
Flag2=True
Flag1=False
break
if Flag2:
break
if Flag1:
return Flag1
Flag1=True
Flag2=False
for i in range(n+1):
for j in range(n+1):
if mat[i][j]!=target[n-j][i]:
Flag2=True
Flag1=False
break
if Flag2:
break
if Flag1:
return Flag1
Flag1=True
Flag2=False
for i in range(n+1):
for j in range(n+1):
if mat[i][j]!=target[i][j]:
Flag2=True
Flag1=False
break
if Flag2:
break
return Flag1
第一题虽然没有重楼大哥的风范,但是也顺利解决了,easy果然就是easy。
接下来就是第二题:
这题简单来说就是从最大的数开始减到次小的数,一直减到最小的数。比如1,3,5的话,5先减到3,然后两个3分别减到1就ok了。思路也很简单:
class Solution(object):
def reductionOperations(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
d={}
sets=sorted(list(set(nums)))
if len(sets)==1:
return 0
for i in nums:
if i in d:
d[i]+=1
else:
d[i]=1
result=0
for i in range(1,len(sets)):
result+=d[sets[i]]*i
return result
将给定数组取set,,然后排序,计算每个数出现的次数,存字典。然后将第n小的数与该数出现的次数相乘(d[set(i)]:出现的次数,i:第n-1小的数,即减少几次后可以减少到最小的数。)
这题一次过,很完美!
但可惜的是这只是前奏,接下来就是我超过两个小时的受虐历程了。
以上是第三题的题目,思考了若干时间后,我采用了以下的错误方法:
def counts(s,s1):
res=0
for i in range(len(s)):
if s[i]!=s1[i]:
res+=1
return res
class Solution(object):
def minFlips(self, s):
"""
:type s: str
:rtype: int
"""
res=99999999
if len(s)%2==0:
tem="01"*(len(s)//2)
tem1="10"*(len(s)//2)
res=min(counts(tem,s),counts(tem1,s))
else:
tem="01"*(len(s)//2)
re=counts(tem+"0",s)
res=min(re,res)
re=counts(tem+"1",s)
res=min(re,res)
re=counts("1"+tem,s)
res=min(re,res)
re=counts("0"+tem,s)
res=min(re,res)
tem="10"*(len(s)//2)
re=counts(tem+"0",s)
res=min(re,res)
re=counts(tem+"1",s)
res=min(re,res)
re=counts("1"+tem,s)
res=min(re,res)
re=counts("0"+tem,s)
res=min(re,res)
return res
注意以上是错误方法,我基于最后01所有的排列方式与当前01的排列进行比较有多少个不同的值,然后取最小的方案,其实也考虑了类型一操作的影响,但是只考虑了操作一次的情况!比如进行若干次类型二操作后是00101和1101010这种情况,之后经过若干历程之后我无奈地看了答案,由于官方有更好的答案,我就直接贴地址了,感兴趣的可以看看,用的dp。
https://leetcode-cn.com/problems/minimum-number-of-flips-to-make-the-binary-string-alternating/solution/shi-er-jin-zhi-zi-fu-chuan-zi-fu-jiao-ti-i52p/
最后一题按照惯例是hard,下面是题目的介绍:
这题读了两遍,一度以为自己读错了,从逻辑上来看很简单的题目,就是遍历所有的供应商,看看采用每种供应商浪费的空间大小。
于是,我很快地写了一个三层遍历地代码:
def get_counts(pack,box):
if max(pack)>max(box):
return -1
res=0
# pack.sort()
box.sort()
for i in pack:
#二分查找最适合的箱子
if i<=box[0]:
bo=box[0]
else:
left=0
right=len(box)-1
while left<right-1:
mid=(left+right)//2
if box[mid]>=i:
right=mid
else:
left=mid
bo=box[right]
res=(res+bo-i)
res=res%(10**9+7)
return res
class Solution(object):
def minWastedSpace(self, packages, boxes):
"""
:type packages: List[int]
:type boxes: List[List[int]]
:rtype: int
"""
t=[0 for _ in range(len(boxes))]##这么多可以选择的供应商
for i in range(len(boxes)):
t[i]=get_counts(packages[:],boxes[i])
if max(t)==-1:
return -1
else:
return min([i for i in t if i!=-1])
很显然,超时了,42个算例只能完成31个,算法的时间复杂度为:O(nml),其实n为商品数量,m为供应商包裹数量,l为供应商数量。
接下来只要优化算法的时间复杂度就ok了,比起毫无思路的题目,这种还是可以接受的,于是一个小时之后:
好吧,看答案吧。
https://leetcode-cn.com/problems/minimum-space-wasted-from-packaging/solution/zhuang-bao-guo-de-zui-xiao-lang-fei-kong-90lk/
以上就是整个比赛的历程。你以为这就结束了?刚好看到晚上10点半有个双周赛,头铁的我又去参加了,先上最终比赛的结果。
看上去还可以,4题做了三题,除了爆炸时间外花费时间1个小时,但是,大家可能看到了最后一题的情况,大佬的平均时间在15-20分钟,那么本菜鸡花了多少时间呢?
从11点30多开始做这一题,最后到接近2点解决。
现在开始分享下这个双周赛的赛题的解题历程。
惯例的第一题easy
ranges中是各种区间,left和ight也是一个区间,题目的要求是判断ranges中的所有区间是否可以覆盖left到right区间内所有的值。
class Solution:
def isCovered(self, ranges: List[List[int]], left: int, right: int) -> bool:
for i in range(left,right+1):
Flag=False
for j in ranges:
if i>=j[0] and i<=j[1]:
Flag=True
if Flag:
break
if Flag is False:
break
return Flag
两层遍历判断就可以,提交的两次错误主要是Flag没有定义好。导致一些情况没有考虑到。
接下来是第二题:
这题就是一个一个遍历,看看最后谁上去的时候粉笔不够用了,唯一需要注意的是,可以先求所有学生使用粉笔的和苏幕,然后将k对sum1取余,这样的话最多只需要遍历一轮就可以发现哪个学生没有取到粉笔了。
class Solution:
def chalkReplacer(self, chalk: List[int], k: int) -> int:
sum1=sum(chalk)
k=k%sum1
if k==0:
return 0
for i in range(len(chalk)):
k=k-chalk[i]
if k<0:
return i
接下来是第三题,
给定一个很大的数组,让你判断最大的幻方,并返回最大幻方的周长。
看到这题后,我想的就是一个三层遍历。
第一双层遍历数组,作为幻方的坐上角,
第二遍历周长,然后判断取到的是否为题目要求的幻方。
首先,我们需要一个判断所取的是否为幻方的函数:
def is_true(grid,x,y,l):
"""
grid:给定的矩阵
x:开始的纵坐标
y:开始的横坐标
l:选定幻方的长度
"""
sum1=sum(grid[x][y:y+l])
sum2=sum([grid[x+i][y+i] for i in range(l)])
sum3=sum([grid[x+l-1-i][y+i] for i in range(l)])
if sum1!=sum2:
return False
if sum1!=sum3:
return False
for k in range(x+1,x+l):
tem=sum(grid[k][y:y+l])
if tem!=sum1:
return False
for k in range(y,y+l):
tem=sum([grid[i][k] for i in range(x,x+l)])
if tem!=sum1:
return False
return True
下面我们就可以直接遍历了:
class Solution:
def largestMagicSquare(self, grid: List[List[int]]) -> int:
m,n=len(grid),len(grid[0])
res=1
def getK_k(grid,x,y):
max1=min(m-x,n-y)
if max1==1:
return 1
for i in range(max1,res,-1):
if is_true(grid,x,y,i):
return i
return 1
for i in range(m):
for j in range(n):
res=max(getK_k(grid,i,j),res)
if res>=n-j:
break
if res>=m-i:
break
return res
注意的是有一些小的可以减少时间复杂度的操作,
第一:如果从当前坐标出发,就算后面的是幻方,那也不能超过当前我们取得的幻方大小,那么就不需要进行判断了。
也就是上文代码中的两个if break操作
第二:从某个坐标出发的时候,我们应该从该坐标开始下可能最大的幻方遍历到我们目前最大的幻方。也就是上文代码中的for i in range(max1,res,-1)操作。
前三题总体来说,做的还是相对顺利,虽然不能和一个大佬那样10-20分钟完成,但是对于我来说也算不错的成绩,最后就是那个虐了我2个多小时的最后一题:
从题目上来看,就是把本来逻辑计算为1的变成逻辑计算为0,或者反过来。
看完这题后我毫无思路,于是只能先写一个函数来计算它的逻辑值:
from functools import lru_cache
@lru_cache(None)
def get_is(s):
a = []
b = []
for i in s:
if i == "(":
b.append(i)
elif i == ")":
num = b.pop()
b.pop()
b.append(num)
elif i == "1" or i == "0":
b.append(i)
elif i == "|":
b.append("|")
elif i == "&":
b.append("&")
if b[-1] == "0" or b[-1] == "1" and len(b) >= 3:
while len(b) >= 3 and (b[-2] == "&" or b[-2] == "|"):
if b[-2] == "&" and b[-1] == "1" and b[-3] == "1":
b.pop()
b.pop()
b.pop()
b.append("1")
elif b[-2] == "|" and (b[-1] == "1" or b[-3] == "1"):
b.pop()
b.pop()
b.pop()
b.append("1")
else:
b.pop()
b.pop()
b.pop()
b.append("0")
return b[0]
注:@lru_cache是python的一个操作,可以加载缓存,避免重复的计算,这个是求助场外人士后添加的,本来几个由于时间没有通过的算例,加了这个后就通过了。
写好计算函数之后,就需要判断反转的次数了,慢慢地写了几个之后,发现这题可以用递归的方法解决:
class Solution:
def minOperationsToFlip(self, expression: str) -> int:
while expression != dele(expression):
expression = dele(expression)
if expression=="":
return 0
result = get_is(expression)
a = []
f = ""
for i in range(len(expression) - 1, -1, -1):
if expression[i] == ")":
a.append(expression[i])
if expression[i] == "(":
a.pop()
if (expression[i] == "|" or expression[i] == "&") and len(a) == 0:
left, right = expression[:i], expression[i + 1:]
f = expression[i]
break
if f == "":
return 1
if f == "|" and result == "0":
tem = self.minOperationsToFlip(right)
if tem == 1:
return 1
else:
return min(self.minOperationsToFlip(left), tem)
elif f == "|" and result == "1":
if get_is(left) == "0" or get_is(right) == "0":
return 1
else:
tem = self.minOperationsToFlip(right)
if tem == 1:
return 2
else:
return min(self.minOperationsToFlip(left), tem) + 1
elif f == "&" and result == "0":
if get_is(left) == "0" and get_is(right) == "0":
tem = self.minOperationsToFlip(right)
if tem == 1:
return 2
else:
return min(self.minOperationsToFlip(left), tem) + 1
else:
return 1
elif f == "&" and result == "1":
tem = self.minOperationsToFlip(right)
if tem == 1:
return 1
else:
return min(self.minOperationsToFlip(left), tem)
主要的思想是将整个字符串根据某一个|或者&符号进行切割(切割的时候别把一对括号切分开了)。
然后我们就面临四种情况:
1:切割符号是"|",最后的值为0,那么很明显,左右两边的值都是0,那么我们只要把左右某一个变成1就好了。然后就算计算把左边或者右边变成1的最小操作次数。这不就是一个完美的递归公式么?
2:切割符号是"|",最后的值为1,分两种情况,如果左右两边都是1,那么需要将左边或者右边变成0,再将符号变成&就好了,如果左右两边有一个0,直接把符号变成&就好。
3:切割符号是"&",最后的值为0,也分为两种情况,如果左右两边都是0,那么需要将左右两边都一个变为1,然后将符号变成"|"。如果左右两边有一个1,那么只要把符号变成"|“就好了。
4:切割符号是”&",最后的值为1,只要把左右两边某一边变成0就好。
以上就是递归的逻辑,但是要解决整个题目还需要注意几点:
1:进行符号切割的时候,需要从右边遍历,因为它逻辑计算的时候是从左边开始,顺序计算。对于以下这个算例:
((0&(0&0)&(0|(0)&1&0))),将最左边的两个0&改成1|最终的结果还是0,并不能改变整体的值。从右边切割才能切割成两个可以影响整体的部分。
2:给定的字符串中有很多无效的括号,其中有一个算例(别问我为什么知道),左边49999个1,右边49999个1,所以我们需要将一些无效的括号删除,以下就是代码:
def dele(x):
a=[]
for i in range(len(x)):
if x[i]=="(":
a.append(x[i])
else:
break
b=[]
for j in range(len(x)-1,-1,-1):
if x[j]==")":
b.append(x[j])
else:
break
l=min(len(a),len(b))
while l>0:
c = []
flag =True
for i in range(l,len(x)-l):
if x[i]=="(":
c.append(x[i])
elif x[i] == ")":
if len(c) > 0 and a[-1] == "(":
c.pop()
else:
l=l-1
flag=False
break
if flag:
break
return x[l:len(x)-l]
以上就是此次回家刷leetcode的辛酸经历,发现自己的代码水平亟待提升,以后要多刷刷了,有一起的也可以相互交流。