2022-01-09 每日打卡:力扣周赛第 275 场
写在前面
“这些事儿在熟练之后,也许就像喝口水一样平淡,但却能给初学者带来巨大的快乐,我一直觉得,能否始终保持如初学者般的热情、专注,决定了在做某件事时能走多远,能做多好。” 该系列文章由python编写,所刷题目共三个来源:之前没做出来的 ;Leetcode中等,困难难度题目; 周赛题目;某个专题的经典题目,所有代码已AC。每日1-3道,随缘剖析,希望风雨无阻,作为勉励自己坚持刷题的记录。
5976. 检查是否每一行每一列都包含全部整数
第一道,简单题,充分利用题目信息即可。每次判断不仅仅要满足和的关系,还有去重。
class Solution:
def checkValid(self, matrix: List[List[int]]) -> bool:
n = len(matrix)
return all(len(set(row)) == n
for row in matrix) and
all(len(set(col)) == n
for col in zip(*matrix))
5977. 最少交换次数来组合所有的 1 II
想的思路一定要经过每个测试用例的验证!再开始编码!
统计1的个数,最开始想的是统计连缀的1的个数,其实应该是统计滑动窗口中1的个数。窗口大小为1的总个数,最后结果就是总个数-窗口中1个数的最大值
关于成环的问题,比较典型的两个解决方法:
- 后缀添加相同数组
- 切片
而不是模拟,模拟可能带来边界处理的麻烦。
class Solution:
def minSwaps(self, nums: List[int]) -> int:
n, cnt, size = len(nums), 0, nums.count(1)
for _ in range(size):
cnt+=nums[_]
l, r, ans = 0, size, 0
nums += nums
while l<n:
if nums[l]:
cnt-=1
if nums[r]:
cnt+=1
l+=1
r+=1
ans=max(cnt,ans)
return size-ans
5978. 统计追加字母可以获得的单词数
类似于从一组原材料到另一组目标的问题,一般做法有两种:
- 为原材料进行哈希编码,组成目标(常常会用到拓扑排序)
- 为目标进行哈希编码,从原材料中选取所需要的
class Solution:
def wordCount(self, startWords: List[str], targetWords: List[str]) -> int:
all_set = set(''.join(sorted(w)) for w in startWords)
ans = 0
for w in targetWords:
w2 = ''.join(sorted(w))
if any((w2[:i] + w2[i+1:]) in all_set
for i in range(len(w2))):
ans += 1
return ans
比完赛学习了一下别人写的,思路大概是两条:
- 状态压缩: 因为题目说每个字符串不包含重复的字母,因此可以用一个25位的位图来表示一个字符串
- 反向查找: 在确认一个target是否可以由start集合中的字符串变换得到时,不要遍历start集合,因为这个集合可能非常大;我们可以通过删除target中的一个字母来看集合中是否存在相等的字符串来反向查找。因为字母的个数最多26个,而startSet可能非常非常大。
然后位运算的状态压缩原理是这样的:用一位数代表一个字母,26个字母对应的分别是把1左移0~26位。比如a是1(1),那b就是10(2),c是100(4)… 这样我们只需要关注一个字符串中出现过的字母(题目已经给了这些字母互不重复),而不受其顺序的影响。
class Solution:
def wordCount(self, startWords: List[str], targetWords: List[str]) -> int:
s = set()
# 状态压缩存储出现的字符串
# 譬如abc将存储为"000...000111",即26位中后三位为1
# 每一个mask都是列表中的一项
for word in startWords:
mask = 0
for ch in word:
mask |= 1 << (ord(ch) - ord('a'))
s.add(mask)
ans = 0
for word in targetWords:
mask = 0
# 每一个mask都是列表中的一项,计算要匹配的状态压缩后的数
for ch in word:
mask |= 1 << (ord(ch) - ord('a'))
for ch in word:
# 遍历当前项,按顺序去掉一个字符
if mask ^ (1 << (ord(ch) - ord('a'))) in s:
ans += 1
# 防止重复计算
break
return ans
5979. 全部开花的最早一天
大概知道是贪心算法,但是这个题将一盆花分为了两个阶段,可以发现,无论怎么种,前面的播种时间都是无法重合的,也就是说种植时间是一定的。
那么我们就尽可能的使得生长时间保持最短,使用贪心策略即可。下面是比较严谨的理论证明。可以简单理解为,每个活动的开始时间不同了,但开始时间取决于种植时间,渴望总时间最短,将 时间可以重合 的部分安排为最长的最先开始重合即可。
下面这个图是模拟了[1,2]和[3,2]两盆花的种植情况,可以发现,种植顺序并不影响最后的结尾,因为种植时长一定,爱种哪个种哪个,让长的慢的先长就可以啦:
class Solution:
def earliestFullBloom(self, plantTime: List[int], growTime: List[int]) -> int:
N = len(growTime)
grow_sort = sorted(range(N), key=lambda x: growTime[x], reverse=True)
s = 0
ans = 0
for i in grow_sort:
s += plantTime[i]
ans = max(ans, s + growTime[i])
return ans