在这篇文章中,我们解释了如何利用滑动窗口的思想有效地解决水果进篮子的问题。
目录:
-
问题陈述:水果放入篮子里
-
解决方法
2.1.天真的方法(蛮力)
2.2.优化的解决方案(滑动窗口)
2.3.伪代码
2.4.时间和空间复杂度 -
类似问题
先决条件。滑动窗口
这个问题与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。
在每次迭代结束时,我们都会跟踪一个运行中的最大值,最后我们会返回。
滑动窗口方法的可视化!
我们可以看到窗口是如何根据辅助数据结构的内容而动态变化的。另外,请注意,运行中的最大值是在任何可能的收缩完成后才计算出来的,因为有可能窗口最初覆盖了一个无效的子阵列,但它会在之后立即自我修复。
另外,如果问题改变了,允许的水果类型的数量是某个变量K,我们可以很容易地改变我们的代码,通过用len(basket)>k取代len(basket)>2的检查来说明这一点。
伪代码
- 通过使用左和右索引来表示窗口的开始和结束来初始化一个窗口。这两个索引将被初始化为零。
- 初始化一个哈希图,它将用于跟踪当前窗口中的水果。
- 将窗口的长度增加1。并在上述的哈希图中加入一个加入到窗口中的新字符的出现次数。
- 现在,我们将检查哈希图的长度是否大于2(在最初的几次迭代中不可能),因为我们在上一步更新了哈希图。
- 如果哈希图的长度大于2,那么我们就将窗口缩小一个,并相应地从哈希图中删除一个被删除的字符(缩小窗口后被切断的字符)。我们这样做,直到哈希图的长度达到所需的2。
- 我们将滚动最大值更新为滚动最大值和当前窗口长度的最大值。
- 这种情况一直持续到窗口的末尾撞到列表的末尾。然后我们可以返回输出,该输出应该有正确的长度,即初始列表中只有两个元素的最大或然数子阵列。
时间和空间复杂度
这个算法的时间复杂度是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的这篇文章,你一定对水果进篮子的问题有了完整的了解。