避免洪水泛滥

标签: 贪心 数组 哈希表 二分查找 堆(优先队列)

难度: Medium

你的国家有无数个湖泊,所有湖泊一开始都是空的。当第 n 个湖泊下雨前是空的,那么它就会装满水。如果第 n 个湖泊下雨前是 满的 ,这个湖泊会发生 洪水 。你的目标是避免任意一个湖泊发生洪水。

给你一个整数数组 rains ,其中:

  • rains[i] > 0 表示第 i 天时,第 rains[i] 个湖泊会下雨。
  • rains[i] == 0 表示第 i 天没有湖泊会下雨,你可以选择 一个 湖泊并 抽干 这个湖泊的水。

请返回一个数组 ans ,满足:

  • ans.length == rains.length
  • 如果 rains[i] > 0 ,那么ans[i] == -1 。
  • 如果 rains[i] == 0 ,ans[i] 是你第 i 天选择抽干的湖泊。

如果有多种可行解,请返回它们中的 任意一个 。如果没办法阻止洪水,请返回一个 空的数组 。

请注意,如果你选择抽干一个装满水的湖泊,它会变成一个空的湖泊。但如果你选择抽干一个空的湖泊,那么将无事发生。

示例 1:

输入:rains = [1,2,3,4]
输出:[-1,-1,-1,-1]
解释:第一天后,装满水的湖泊包括 [1]
第二天后,装满水的湖泊包括 [1,2]
第三天后,装满水的湖泊包括 [1,2,3]
第四天后,装满水的湖泊包括 [1,2,3,4]
没有哪一天你可以抽干任何湖泊的水,也没有湖泊会发生洪水。

示例 2:

输入:rains = [1,2,0,0,2,1]
输出:[-1,-1,2,1,-1,-1]
解释:第一天后,装满水的湖泊包括 [1]
第二天后,装满水的湖泊包括 [1,2]
第三天后,我们抽干湖泊 2 。所以剩下装满水的湖泊包括 [1]
第四天后,我们抽干湖泊 1 。所以暂时没有装满水的湖泊了。
第五天后,装满水的湖泊包括 [2]。
第六天后,装满水的湖泊包括 [1,2]。
可以看出,这个方案下不会有洪水发生。同时, [-1,-1,1,2,-1,-1] 也是另一个可行的没有洪水的方案。

示例 3:

输入:rains = [1,2,0,1,2]
输出:[]
解释:第二天后,装满水的湖泊包括 [1,2]。我们可以在第三天抽干一个湖泊的水。
但第三天后,湖泊 1 和 2 都会再次下雨,所以不管我们第三天抽干哪个湖泊的水,另一个湖泊都会发生洪水。

提示:

  • 1 <= rains.length <= 105
  • 0 <= rains[i] <= 109

Submission

运行时间: 113 ms

内存: 32.4 MB

class Solution:
    def avoidFlood(self, rains: List[int]) -> List[int]:
        # counter=Counter(rains)
        # pool_cnt=len(counter)-1
        # tot_days=len(rains)
        # no_rain_days=counter[0]
        # rain_days=tot_days-no_rain_days
        # if rain_days-pool_cnt>no_rain_days:
        #     return []
        
        full_pools={}
        can_removed=deque()
        ret=[-1]*len(rains)

        for i,p in enumerate(rains):
            if p==0:
                if len(full_pools)==0:
                    ret[i]=1
                elif len(full_pools)==1:
                    k,_=full_pools.popitem()
                    ret[i]=k
                    continue
                can_removed.append([i,True])
                continue
            if p in full_pools:
                while can_removed and can_removed[0][1]==False:
                    can_removed.popleft()
                for entry in can_removed:
                    if entry[1]==False:
                        continue
                    if entry[0]>full_pools[p]:                        
                        ret[entry[0]]=p
                        entry[1]=False
                        break
                else:
                    return []
            full_pools[p]=i
        for idx,flag in can_removed:
            if not flag:
                continue
            ret[idx]=1
        return ret
            

Explain

此题解的核心思路是使用散列表(字典)来跟踪当前哪些湖泊已经满了,以及它们最后一次被填满的时间。此外,使用一个双端队列(deque)来记录可以抽水的日子,并在需要时找到最近的、合适的日子来抽水,以避免洪水。遍历输入数组,对于每一天:1) 如果是晴天(rains[i] == 0),记录这一天可以抽水;2) 如果是下雨天(rains[i] > 0),检查该湖泊是否已经满了。如果已满,则需要在之前记录的可以抽水的日子中找到一个合适的日子来抽水;如果找不到合适的日子,直接返回空数组。如果湖泊未满,则标记为已满,并记录其下雨的时间。最后,对于所有未使用的晴天,可以随意抽干任何湖泊(避免特殊情况,通常抽干湖泊1)。

时间复杂度: O(n)

空间复杂度: O(n)

class Solution:
    def avoidFlood(self, rains: List[int]) -> List[int]:
        full_pools = {}  # 记录湖泊填满的状态和最近一次下雨的天数
        can_removed = deque()  # 可以抽水的日子列表
        ret = [-1] * len(rains)  # 初始化返回数组,下雨天为-1

        for i, p in enumerate(rains):
            if p == 0:  # 晴天,记录可以抽水
                can_removed.append([i, True])
                continue
            if p in full_pools:  # 如果湖泊已满,需要抽水
                while can_removed and can_removed[0][1] == False:  # 移除无效的抽水日子
                    can_removed.popleft()
                for entry in can_removed:  # 在有效的抽水日子中找到合适的一天
                    if entry[1] == False:  # 已经使用过的抽水日子
                        continue
                    if entry[0] > full_pools[p]:  # 找到了合适的抽水日子
                        ret[entry[0]] = p
                        entry[1] = False  # 标记为已使用
                        break
                else:  # 如果没有找到可用的抽水日子,返回空数组
                    return []
            full_pools[p] = i  # 标记湖泊为已满
        for idx, flag in can_removed:  # 处理剩余的未使用的晴天
            if not flag:
                continue
            ret[idx] = 1  # 默认抽干湖泊1
        return ret

Explore

在算法中,我们使用一个字典 `full_pools` 来记录每个湖泊最后一次下雨的日期。当湖泊再次下雨时,我们检查 `full_pools` 字典来确认湖泊是否已满。如果已满,则需要在 `can_removed` 双端队列中找到一个晴天来抽水。我们遍历 `can_removed`,寻找一个未被使用(`entry[1] == True`)且日期大于 `full_pools[p]` 的晴天(`entry[0] > full_pools[p]`)。这样确保了选定的抽水日子在该湖泊最后一次下雨之后,避免了在湖泊尚未满时抽水的逻辑错误。

这种情况发生在当一个湖泊在之前已经被填满,并且在该湖泊再次下雨之前没有任何有效的晴天来进行抽水时。例如,考虑输入数组 `rains = [1, 2, 1]`。第一天和第三天湖泊1下雨,而第二天湖泊2下雨。在第三天需要抽干湖泊1才能再次接受雨水,但由于之前没有晴天记录,没有可用的日子来抽水,因此返回空数组。

在处理剩余的晴天时,选择默认抽干湖泊1主要是为了简化实现和确保算法的正确性,因为如果不指定抽水的湖泊,将存在多种可能的输出结果。选择抽干哪个湖泊在这种情况下不会影响算法的正确性,因为这些晴天是在所有必要的抽水操作之后的额外日子,此时所有需要抽水的湖泊都已处理完毕。因此,选择任何一个不存在或不需要抽水的湖泊都是有效的,这里选择湖泊1主要是为了保持输出的一致性。