作为一名重在参与的周赛菜鸟,一般只能做出一道题两道题,今天比较顺利的完成了第三题,还是值得记录一下的。
今天的第一题有点意思。
一、装满杯子需要的最短总时长
现有一台饮水机,可以制备冷水、温水和热水。每秒钟,可以装满 2 杯 不同 类型的水或者 1 杯任意类型的水。
给你一个下标从 0 开始、长度为 3 的整数数组 amount ,其中 amount[0]、amount[1] 和 amount[2] 分别表示需要装满冷水、温水和热水的杯子数量。返回装满所有杯子所需的 最少 秒数。
示例 1:
输入: amount = [1,4,2]
输出: 4
解释: 下面给出一种方案:
第 1 秒:装满一杯冷水和一杯温水。
第 2 秒:装满一杯温水和一杯热水。
第 3 秒:装满一杯温水和一杯热水。
第 4 秒:装满一杯温水。
可以证明最少需要 4 秒才能装满所有杯子。
示例 2:
输入: amount = [5,4,4]
输出: 7
解释: 下面给出一种方案:
第 1 秒:装满一杯冷水和一杯热水。
第 2 秒:装满一杯冷水和一杯温水。
第 3 秒:装满一杯冷水和一杯温水。
第 4 秒:装满一杯温水和一杯热水。
第 5 秒:装满一杯冷水和一杯热水。
第 6 秒:装满一杯冷水和一杯温水。
第 7 秒:装满一杯热水。
示例 3:
输入: amount = [5,0,0]
输出: 5
解释: 每秒装满一杯冷水。
提示:
amount.length == 30 <= amount[i] <= 100
解析
一共只有三种水温,可以先考虑特殊情况。
当有一种水温需要的杯数是0 的时候,很容易发现,生下来的两种,哪个数量多,多的那个数字就是最少需要用的时长了,因为可以每次都带上少的那种一起接水。
另一种特殊情况是,剩下两个0,这种情况更容易,只剩下一种水了,就直接是它的数量即可。
那对于都不是0的情况,肯定是可以两杯两杯接水的,直到出现一个0或者两个0。而一旦出现了0,就可以利用上述的特殊情况进行处理。那就看如何两杯两杯地接水。 这里直接用最多的来接就好了。一杯最多的,一杯最少的,算一秒,一杯最多的,一杯次多的,算一秒。
代码对应如下,是周赛时候写的,还有一定优化空间,不过题目数量有限,amount最多是100,也就很容易通过了。
class Solution:
def __init__(self):
self.step = 0
def fillCups(self, amount: List[int]) -> int:
amount = sorted(amount)
if amount.count(0) == 3:
self.step += 0
return self.step
if amount.count(0) == 2:
self.step += sum(amount)
return self.step
if amount.count(0) == 1:
self.step += max(amount)
return self.step
if amount[2] >= 2:
self.step += 2
amount[0] -= 1
amount[1] -= 1
amount[2] -= 2
self.fillCups(amount)
else:
self.step += 2
print(self.step)
return self.step
二、无限集中的最小数字
现有一个包含所有正整数的集合 [1, 2, 3, 4, 5, ...] 。
实现 SmallestInfiniteSet 类:
SmallestInfiniteSet()初始化 SmallestInfiniteSet 对象以包含 所有 正整数。int popSmallest()移除 并返回该无限集中的最小整数。void addBack(int num)如果正整数num不 存在于无限集中,则将一个num添加 到该无限集中。
示例:
输入
["SmallestInfiniteSet", "addBack", "popSmallest", "popSmallest", "popSmallest", "addBack", "popSmallest", "popSmallest", "popSmallest"]
[[], [2], [], [], [], [1], [], [], []]
输出
[null, null, 1, 2, 3, null, 1, 4, 5]
解释
SmallestInfiniteSet smallestInfiniteSet = new SmallestInfiniteSet();
smallestInfiniteSet.addBack(2); // 2 已经在集合中,所以不做任何变更。
smallestInfiniteSet.popSmallest(); // 返回 1 ,因为 1 是最小的整数,并将其从集合中移除。
smallestInfiniteSet.popSmallest(); // 返回 2 ,并将其从集合中移除。
smallestInfiniteSet.popSmallest(); // 返回 3 ,并将其从集合中移除。
smallestInfiniteSet.addBack(1); // 将 1 添加到该集合中。
smallestInfiniteSet.popSmallest(); // 返回 1 ,因为 1 在上一步中被添加到集合中,
// 且 1 是最小的整数,并将其从集合中移除。
smallestInfiniteSet.popSmallest(); // 返回 4 ,并将其从集合中移除。
smallestInfiniteSet.popSmallest(); // 返回 5 ,并将其从集合中移除。
提示:
1 <= num <= 1000- 最多调用
popSmallest和addBack方法 共计1000次
解析
这道题比第一道题目要简单一些,应该和第一题调换一下顺序。这个纯粹就是一些基础语法的考核。当然如果使用的不是python语言可能写起来稍微有点麻烦。
题目虽然说是无限集,但是看提示部分,num最大值也就是1000,而两个方法总计也只是调用了1000次,因此可以设定这个集合最大就1000个数字方便处理。 代码编写起来也就几行而已。还是比较容易完成的。
class SmallestInfiniteSet:
def __init__(self):
self.s = set([i for i in range(1, 1002)])
def popSmallest(self) -> int:
m = min(self.s)
self.s.remove(m)
return m
def addBack(self, num: int) -> None:
self.s.add(num)
三、移动片段得到字符串
给你两个字符串 start 和 target ,长度均为 n 。每个字符串 仅 由字符 'L'、'R' 和 '_' 组成,其中:
- 字符
'L'和'R'表示片段,其中片段'L'只有在其左侧直接存在一个 空位 时才能向 左 移动,而片段'R'只有在其右侧直接存在一个 空位 时才能向 右 移动。 - 字符
'_'表示可以被 任意'L'或'R'片段占据的空位。
如果在移动字符串 start 中的片段任意次之后可以得到字符串 target ,返回 true ;否则,返回 false 。
示例 1:
输入: start = "_L__R__R_", target = "L______RR"
输出: true
解释: 可以从字符串 start 获得 target ,需要进行下面的移动:
- 将第一个片段向左移动一步,字符串现在变为 "L___R__R_" 。
- 将最后一个片段向右移动一步,字符串现在变为 "L___R___R" 。
- 将第二个片段向右移动散步,字符串现在变为 "L______RR" 。
可以从字符串 start 得到 target ,所以返回 true 。
示例 2:
输入: start = "R_L_", target = "__LR"
输出: false
解释: 字符串 start 中的 'R' 片段可以向右移动一步得到 "_RL_" 。
但是,在这一步之后,不存在可以移动的片段,所以无法从字符串 start 得到 target 。
示例 3:
输入: start = "_R", target = "R_"
输出: false
解释: 字符串 start 中的片段只能向右移动,所以无法从字符串 start 得到 target 。
提示:
n == start.length == target.length1 <= n <= 10^5start和target由字符'L'、'R'和'_'组成
解析
这道题目跟以前一道周赛题目比较像,当时的题目是路上的汽车撞来撞去,撞了多少次那种,这个是左移右移。 首先观察题目要求,R只能在它右侧直接就是_的时候才能移动,也就是说一旦R右边出现了L,那它是无法跨越L到右侧的,同理,L左边一旦有R它也是无法跨越R到左边的。
在这样的观察情况下,当把题目的start中的所有的下划线_都去掉的时候,如果start和target两个字符串不一致,那肯定就是无法移动的,要返回False了。
那是不是去掉_之后相等的都可以返回True呢?
答案是否定的!比如一个情况如下:start='_L_L__R_R',对应target='L___L__RR',这里面两个R的位置是符合要求的,最左边的L也是符合要求的,移动到了左边,但是第二个L却进行了右移操作,因此要返回False。
那如何处理这种情况?
需要记住每一个L的起始位置,再看看target中每一个L的位置是否都向左或者没有移动,如果向右了,就可以直接判断为False。同理,记住每一个R的起始位置,看看target中每一个R的位置是否都向右移动或者没有移动,如果发现向左移动了,那也就可以直接判断为False。如果遍历完了,都没有出现False的异常,就可以返回True了。
总体来说这道题目还是比较简单的。
def canChange(self, start: str, target: str) -> bool:
if start.replace("_", "") != target.replace("_", ""):
return False
if start.endswith("R") and not target.endswith("R"):
return False
if start.startswith("L") and not target.startswith("L"):
return False
# 每一个L必须小于等于原来的位置
# 每一个R必须大于等于原来的位置
location = dict()
L_count = 0
R_count = 0
for index, c in enumerate(start):
if c == "L":
location["L%s"%L_count] = index
L_count += 1
elif c == "R":
location["R%s"%R_count] = index
R_count += 1
new_L_count = 0
new_R_count = 0
for index, c in enumerate(target):
if c == "L":
if index > location["L%s"%new_L_count]:
return False
else:
new_L_count += 1
elif c == "R":
if index < location["R%s"%new_R_count]:
return False
else:
new_R_count += 1
return True
感悟
周赛过程中,可以看到很多大神选手,十几分钟就完成了四道题目,确实很感叹。不过像我等普通选手,能够重在参与也是蛮好的,平时的工作当中,并不会追求速度,PM的需求下来了,不可能要求十几分钟就完成,都会有足够的时间去思考去完善。而且,平日的工作当中,要面对的问题复杂程度可能比周赛题目要复杂很多,倒不是需要什么高深的算法,而是相关联依赖的情况会比较多,不像题目这么纯粹,这也就导致了要考虑的情况会多很多,经常会出现一些遗漏从而出现bug。
同样,在日常工作当中,没有题目这么完善的验证测试,都需要自己去写,能否覆盖到足够多的场景,这是一个很难的事情。我们可以完成周赛的题目,但是要从做题选手变成出题选手,把测试用例写得完整,难度跨越不是一点点。而在日常工作中,一名合格的工程师,既得完成题目,还得做好测试用例编写验证,这种能力也是无法在一两场周赛里提现出来的。
- 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。