滑动窗口:摘水果问题


题目描述

在一个无限的 X 坐标轴上,有许多水果分布在其中某些位置。给你一个二维整数数组 fruits ,其中 fruits[i] = [position_i, amount_i] 表示共有 amount_i 个水果放置在 position_i 上。fruits 已经按 position_i 升序排列 ,每个 position_i 互不相同
另给你两个整数 startPosk 。最初,你位于 startPos 。从任何位置,你可以选择 向左或者向右 走。在 X 轴上每移动 一个单位 ,就记作 一步 。你总共可以走 最多 k 步。你每达到一个位置,都会摘掉全部的水果,水果也将从该位置消失(不会再生)。
返回你可以摘到水果的 最大总数

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/maximum-fruits-harvested-after-at-most-k-steps
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例&提示

示例1:
在这里插入图片描述

输入:fruits = [[2,8],[6,3],[8,6]], startPos = 5, k = 4
输出:9
解释:
最佳路线为:
- 向右移动到位置 6 ,摘到 3 个水果
- 向右移动到位置 8 ,摘到 6 个水果
移动 3 步,共摘到 3 + 6 = 9 个水果

示例2:
在这里插入图片描述

输入:fruits = [[0,9],[4,1],[5,7],[6,2],[7,4],[10,9]], startPos = 5, k = 4
输出:14
解释:可以移动最多 k = 4 步,所以无法到达位置 0 和位置 10 。
最佳路线为:
- 在初始位置 5 ,摘到 7 个水果
- 向左移动到位置 4 ,摘到 1 个水果
- 向右移动到位置 6 ,摘到 2 个水果
- 向右移动到位置 7 ,摘到 4 个水果
移动 1 + 3 = 4 步,共摘到 7 + 1 + 2 + 4 = 14 个水果

示例3:
在这里插入图片描述

输入:fruits = [[0,3],[6,4],[8,5]], startPos = 3, k = 2
输出:0
解释:最多可以移动 k = 2 步,无法到达任一有水果的地方

提示:

1 < = f r u i t s . l e n g t h < = 1 0 5 1 <= fruits.length <= 10^5 1<=fruits.length<=105
f r u i t s [ i ] . l e n g t h = = 2 fruits[i].length == 2 fruits[i].length==2
0 < = s t a r t P o s , p o s i t i o n i < = 2 ∗ 1 0 5 0 <= startPos, position_i <= 2 * 10^5 0<=startPos,positioni<=2105
对 于 任 意 i > 0 , p o s i t i o n i − 1 < p o s i t i o n i 均 成 立 ( 下 标 从 0 开 始 计 数 ) 对于任意 i > 0 ,position_{i-1} < position_i 均成立(下标从 0 开始计数) i>0positioni1<positioni0
1 < = a m o u n t i < = 1 0 4 1 <= amount_i <= 10^4 1<=amounti<=104
0 < = k < = 2 ∗ 1 0 5 0 <= k <= 2 * 10^5 0<=k<=2105

题目分析

根据题目描述,在已知出发位置startPos、从该点出发(可以向左或者向右走)采摘水果总共可以走的步数k的情况下,希望可以采摘到最大总数的水果。同时每到达1个位置,就会摘掉该位置的全部水果。我们可以尝试采用滑动窗口算法,思考在既定最大步数k的情况下,确定某个区间,若采摘区间内的水果需要的步数不超过最大步数k,则该区间为可行窗口;若某个可行窗口内可以采摘到水果的总数最大时,则该区间为最佳窗口
首先,我们定义左指针left和右指针right两个指针确定窗口的左右边界,即确定初始窗口;计算滑动窗口区间采摘水果所需最小步数

■ 如果滑动窗口区间所需最小步数大于k窗口长度大于1,则需要收缩窗口
■ 如果滑动窗口区间所需最小步数大于k窗口长度等于1,则需要整体滑动窗口,即同时移动左右指针;
■ 如果滑动窗口区间所需最小步数小于等于k,则需要扩张窗口。

算法分析

算法选择分析

滑动窗口,本质是双指针法中的对撞指针(左右指针)法,可以理解成是可以左右滑动的窗口,每次滑动都会记录下当前窗口的状态,再找出符合条件的适合的窗口。它可以将双层嵌套的循环问题,转换为单层遍历的循环问题。算法使用左指针left和右指针right两个指针构成窗口,可以将二维循环的问题转化成一维循环一次遍历,使得时间复杂度 O ( n 2 ) O(n^2) O(n2)降低至 O ( n ) O(n) O(n),常见的有经典字符串查找算法Rabin-Karp指纹字符串查找算法,它本质上也使用了滑动窗口的思想,通过公式推导降低窗口滑动时计算子串哈希值的复杂度。

算法过程分析

(1) 初始化(以示例2为例):
在这里插入图片描述
(2) 当前窗口区间所需最小步数大于k窗口长度等于1,需要整体滑动窗口
在这里插入图片描述
(3) 当前窗口区间所需最小步数小于k,需要扩张窗口:
在这里插入图片描述
(4) 当前窗口区间所需最小步数小于k,需要扩张窗口
在这里插入图片描述
(5) 当前窗口区间所需最小步数小于k,需要扩张窗口
在这里插入图片描述
(6) 当前窗口区间所需最小步数小于k,需要扩张窗口
在这里插入图片描述
(7) 当前窗口区间所需最小步数大于k窗口长度大于1,需要收缩窗口
在这里插入图片描述
(8) 当前窗口区间所需最小步数大于k窗口长度大于1,需要收缩窗口
在这里插入图片描述
(9) 当前窗口区间所需最小步数大于k窗口长度大于1,需要收缩窗口
在这里插入图片描述
(10) 当前窗口区间所需最小步数大于k窗口长度大于1,需要收缩窗口
在这里插入图片描述
(11) 当前窗口区间所需最小步数大于k窗口长度等于1,需要整体滑动窗口
在这里插入图片描述
(12) 此时右指针right以超出范围,程序结束
在这里插入图片描述

算法复杂度分析

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)

测试案例

测试案例1:

输入:fruits = [[2,8],[6,3],[8,6]],startPos = 5,k = 4
输出:9

测试案例2

输入:fruits = [[0,9],[4,1],[5,7],[6,2],[7,4],[10,9]],startPos = 5,k = 4
输出:14

测试案例2

输入:fruits = [[0,3],[6,4],[8,5]],startPos = 3,k = 2
输出:0

在这里插入图片描述

完整代码

C++ 代码实现

#include <iostream>
#include <algorithm>
#include <vector>
#include <string.h>
int cost(int lower, int upper, int startPos) {
	return std::min(abs(startPos - lower), abs(startPos - upper)) + upper - lower;
}

int maxTotalFruits(std::vector<std::vector<int>>& fruits, int startPos, int k) {
	int left = 0, right = 0;
	int winSum = 0, ans = 0;
	while (left < fruits.size()) {
		winSum += fruits[right][1];
		while (cost(fruits[left][0], fruits[right][0], startPos) > k and left < right) {
			winSum -= fruits[left][1];
			left += 1;
		}
		if (cost(fruits[left][0], fruits[right][0], startPos) > k) {
			winSum -= fruits[left][1];
			right += 1;
			left += 1;
		} else {
			ans = std::max(ans, winSum);
			right += 1;
		}
	}
	return ans;
}

int main() {
	std::vector<std::vector<int>> fruits;
	std::vector<int> fruit;
	int temp;
	int startPos, k;
	std::cout << "请输入草莓数据(格式:草莓位置,草莓数量,以空格隔开),回车结束:" << std::endl;
	while (1) {
		fruit.clear();
		for (int i = 0; i < 2; i++) {
			std::cin >> temp;
			fruit.push_back(temp);
		}
		fruits.push_back(fruit);
		if (std::cin.get() == '\n') break;
	}
	std::cout << "请输入起点位置:";
	std::cin >> startPos;
	std::cout << "请输入最大步数:";
	std::cin >> k;
	std::cout << "最大可以摘到的草莓数量:";
	std::cout << maxTotalFruits(fruits, startPos, k);
	system("pause");
	return 0;
}

C++ 运行结果
在这里插入图片描述

Python 代码实现

def maxTotalFruits(fruits, startPos, k) -> int:
    def cost(lower, upper):
        # 以startPos为起点,走完[lower,upper]区间所需的最小步数
        return min(abs(startPos - lower), abs(startPos - upper)) + upper - lower

    # 滑动窗口区间[left,right],winSum为区间内的草莓总数
    left, right, winSum = 0, 0, 0
    ans = 0
    while right < len(fruits):
        winSum += fruits[right][1]
        # 如果滑动窗口区间所需最小步数大于k且窗口长度大于1 收缩窗口
        while cost(fruits[left][0], fruits[right][0]) > k and left < right:
            winSum -= fruits[left][1]
            left += 1
        # 如果滑动窗口区间所需最小步数大于k且窗口长度等于1 整体滑动窗口
        if cost(fruits[left][0], fruits[right][0]) > k:
            # 左右指针要同时移动
            winSum -= fruits[left][1]
            right += 1
            left += 1
        # 如果滑动窗口区间所需最小步数小于等于k
        else:
            # 如果滑动窗口区间内的草莓总数大于ans,则更新ans
            ans = max(ans, winSum)
            # 右指针移动 扩张窗口
            right += 1
    return ans


if __name__ == '__main__':
    fruits = []
    # 输入草莓数据
    while True:
        fruit = input('请输入草莓数据(格式:草莓位置,草莓数量,以空格隔开),双击回车结束:').split()
        if not fruit:
            break
        fruits.append([int(fruit[0]), int(fruit[1])])
    # 输入起点位置
    startPos = int(input('请输入起点位置:'))
    # 输入行进步长
    k = int(input('请输入最大步数:'))
    print('最大可以摘到的草莓数量:', maxTotalFruits(fruits, startPos, k))

Python 运行结果
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小李不咕咕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值