日渐头秃的代码日记 -- 第301场周赛前三题

194 阅读5分钟

作为一名重在参与的周赛菜鸟,一般只能做出一道题两道题,今天比较顺利的完成了第三题,还是值得记录一下的。

今天的第一题有点意思。

一、装满杯子需要的最短总时长

现有一台饮水机,可以制备冷水、温水和热水。每秒钟,可以装满 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 == 3
  • 0 <= 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.length
  • 1 <= n <= 10^5
  • start 和 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。

同样,在日常工作当中,没有题目这么完善的验证测试,都需要自己去写,能否覆盖到足够多的场景,这是一个很难的事情。我们可以完成周赛的题目,但是要从做题选手变成出题选手,把测试用例写得完整,难度跨越不是一点点。而在日常工作中,一名合格的工程师,既得完成题目,还得做好测试用例编写验证,这种能力也是无法在一两场周赛里提现出来的。