如何利用滑动窗口的思想有效地解决水果进篮子问题

164 阅读6分钟

在这篇文章中,我们解释了如何利用滑动窗口的思想有效地解决水果进篮子的问题。

目录:

  1. 问题陈述:水果放入篮子里

  2. 解决方法
    2.1.天真的方法(蛮力)
    2.2.优化的解决方案(滑动窗口)
    2.3.伪代码
    2.4.时间和空间复杂度

  3. 类似问题

先决条件。滑动窗口

这个问题与Leetcode问题904相似,水果放入篮子里。

问题陈述:把水果装进篮子里

水果入筐问题是基于一个农场的想法,这个农场有一排各种水果的树。这一排树用一个列表表示,即trees,其中trees[i]指的是这一排的第i棵树生产的水果类型。

这里的问题是,你想从这些树上采摘尽可能多的水果,但农场的规则规定,你不能采摘超过两种类型的水果。同时还规定,你只能从每棵树上采摘一个水果。

此外,你必须挑选一棵起始树,每次向右移动一棵树,从每棵新树上摘取一个水果。我们的最终目标是找到理想的起始点,这样我们就可以尽可能多地采摘水果。

例如,如果我们有一个由列表[0,2,2,3,2]指定的树行,其中0、2和3是水果的类型,我们可以采摘总共4个水果,因为我们可以从索引1开始,从[2,2,3,2]中采摘,这总共是四个水果。

在另一个例子中,如果我们有一个列表[3,4,2,2,3,2,2,3,4,5,6,7],我们可以挑选总共7个水果,因为我们从索引2开始,挑选[2,2,3,2,3],这是总共7个水果。

更简单地说,这个冗长的问题实质上是要求找到最长的有两个整数的连续子数组。

解决方法

天真方法(蛮力)

天真的蛮力解决方法基本上是检查输入数组的每一个或然子数组,过滤那些最多只有两个元素的子数组,并找到最大的这样的数组。这种方法是非常不方便的,因为你会做很多多余的检查。运行时间将达到(n^3)的数量级,因为你需要一个嵌套的for循环来收集所有的子数组,然后扫描它们,找出只有两个唯一数字的子数组,最终找到最大数。

你可以想象,我们可以找到一个更有效的解决方案。

优化的解决方案(滑动窗口)

这个问题适合采用滑动窗口类型的解决方案。我们基本上需要找到最大的有2种水果的连续子阵列。为了实现这个方法,我们将创建一个滑动窗口,从索引0开始,每次迭代都向右增长。每次我们在滑动窗口中添加一个新元素时,我们将在一个辅助数据结构中计算它的出现次数,如哈希图。

如果这个哈希图的长度大于2,那么我们将缩小窗口,并删除一个从窗口中删除的字符的出现次数。如果哈希图中的一个字符的出现次数为零,我们就删除它。我们这样做直到哈希图的长度恢复到2。

在每次迭代结束时,我们都会跟踪一个运行中的最大值,最后我们会返回。

滑动窗口方法的可视化!Sliding-Window-Demo

我们可以看到窗口是如何根据辅助数据结构的内容而动态变化的。另外,请注意,运行中的最大值是在任何可能的收缩完成后才计算出来的,因为有可能窗口最初覆盖了一个无效的子阵列,但它会在之后立即自我修复。

另外,如果问题改变了,允许的水果类型的数量是某个变量K,我们可以很容易地改变我们的代码,通过用len(basket)>k取代len(basket)>2的检查来说明这一点。

伪代码

  1. 通过使用左和右索引来表示窗口的开始和结束来初始化一个窗口。这两个索引将被初始化为零。
  2. 初始化一个哈希图,它将用于跟踪当前窗口中的水果。
  3. 将窗口的长度增加1。并在上述的哈希图中加入一个加入到窗口中的新字符的出现次数。
  4. 现在,我们将检查哈希图的长度是否大于2(在最初的几次迭代中不可能),因为我们在上一步更新了哈希图。
  5. 如果哈希图的长度大于2,那么我们就将窗口缩小一个,并相应地从哈希图中删除一个被删除的字符(缩小窗口后被切断的字符)。我们这样做,直到哈希图的长度达到所需的2。
  6. 我们将滚动最大值更新为滚动最大值和当前窗口长度的最大值。
  7. 这种情况一直持续到窗口的末尾撞到列表的末尾。然后我们可以返回输出,该输出应该有正确的长度,即初始列表中只有两个元素的最大或然数子阵列。

时间和空间复杂度

这个算法的时间复杂度是O(n),n是输入列表的长度。这种情况下,它只经过一次列表。而且我们不需要遍历哈希图来访问其元素,因为它有恒定的查找时间。

这个算法的空间复杂度是恒定的,或者说是O(1),因为哈希图的长度最多只有3个,不会随着输入列表的长度增加而增加。

Python的实现

def totalFruit(self, fruits):
    window_start = 0
    max_so_far = 0
    fruit_basket = {}
    for window_end in range(len(fruits)):
        right_fruit = fruits[window_end]
        if right_fruit not in fruit_basket:
            fruit_basket[right_fruit] = 1
        else:
            fruit_basket[right_fruit] += 1

        while len(fruit_basket) > 2:
            left_fruit = fruits[window_start]
            fruit_basket[left_fruit] -= 1
            if fruit_basket[left_fruit] == 0:
                del fruit_basket[left_fruit]
            window_start += 1
        max_so_far = max(max_so_far, window_end - window_start+1)
    return max_so_far

类似问题

这个问题与其他一些更普遍的Leetcode问题相似。例如,最长的子串(至少有两个不同的字符)和最长的无重复字符子串(LongestSubstirng)。

所有这些问题的共同点是使用一个动态的滑动窗口。

  • 你逐步增加窗口的大小
  • 检查新窗口是否违反了某些标准
  • 如果需要就缩小
  • 这样做,直到你碰到数组的末端
  • 然后返回你一直在跟踪的滚动最大值。

通过OpenGenus的这篇文章,你一定对水果进篮子的问题有了完整的了解。