水果成篮

LeetCode 904

水果成篮

在一排树中,第i棵树产生tree[i]型的水果。
你可以从你选择的任何树开始,然后重复执行以下步骤:

  1. 把这棵树上的水果放进你的篮子里。如果你做不到,就停下来。
  2. 移动到当前树右侧的下一棵树。如果右边没有树,就停下来。

请注意,在选择一颗树后,你没有任何选择:你必须执行步骤 1,然后执行步骤 2,然后返回步骤 1 ,然后执行步骤 2,依此类推,直至停止。

你有两个篮子,每个篮子可以携带任何数量的水果,但你希望每个篮子只携带一种类型的水果。
用这个程序你能收集的水果总量是多少?

解法1:两层for循环

解题思路:

从一个起点开始,遍历后面的元素,如果有一个元素既不等于起点又不等于前面不等于起点的元素,我们就停止遍历

代码如下:

class Solution {
    public int totalFruit(int[] tree) {
        int res = 0;
        int i, j;
        for(i=0; i<tree.length; i++)
        {
            int pre=0; //用pre来保存前面一个元素跟起点的差
            for(j=i+1; j<tree.length; j++)
            {
                if(tree[j]-tree[i]!=0 && tree[j]-tree[i]!=pre)
                {
                    if(pre==0) //如果前面一个元素跟起点相同
                        pre=tree[j]-tree[i]; //则当前元素为第二个元素
                    else 
                    //如果当前元素跟起点的差不等于前面一个元素跟起点的差,说明两者不相等,当前元素为第三个元素,退出循环
                        break;
                }
            
            }
            res = Math.max(res,j-i); //保存结果
        }
        return res;
    }
}

时间复杂度为O(N^2)

空间复杂度为O(1)

解法2:按块扫描

解题思路:

我们把一段连续的数字称为块,我们用一个block数组来保存每一个块的首地址,如

tree = [(1,1),(2,2),(1,1),(2),(3,3,3)] //括号表示一块连续数字
block = [0,2,4,6,7] //分别代表每一块的首地址

我们把一个块里面的值称为块元素

那么我们的任务就是找到由两种块元素组成的最大长度的块,而且这两种类型元素的块必须是相邻的

注意:我们遍历block数组时,当找完两种元素的块,只要从前面一个块重新开始找就可以了,
如果从前面两个块开始找,那就没啥意义了,因为已经存在两种元素了,没办法继续往下找

代码如下:

class Solution {
    public int totalFruit(int[] tree) {
        List<Integer> blockLefts = new ArrayList();

    
        for (int i = 0; i < tree.length; ++i)//存放每一块的首地址
            if (i == 0 || tree[i-1] != tree[i])
                blockLefts.add(i);

        // 代表块的结尾
        blockLefts.add(tree.length);

        int ans = 0, i = 0;
            search: while (true) {
                //用来保存每一个块元素
                Set<Integer> types = new HashSet();
                int weight = 0; //组合块的长度

                遍历块
                for (int j = i; j < blockLefts.size() - 1; ++j) {
                    // 加入块元素
                    types.add(tree[blockLefts.get(j)]);
                    weight += blockLefts.get(j+1) - blockLefts.get(j);
                    //下一个块的首地址减去当其块的首地址等于当前块的长度
                    // 如果有三种块元素
                    if (types.size() >= 3) {
                        i = j - 1; //返回到上一个块开始遍历
                        continue search;
                    }

                    //保存组合块的最大长度
                    ans = Math.max(ans, weight);
                }

                break;
            }

            return ans;
        }
    }

时间复杂度为O(N)

空间复杂度为O(N)

解法3:滑动窗口

解题思路:

下标:[0,1,2,3,4,5,6,7,8,9]

数值:[1,1,6,5,6,6,1,1,1,1]

从左往右先找到2种树,即(1, 6)
当end下标为3时,遇到了第3种树(5),而第3种树(5)左边的树是6,将2种树的状态更新为(6,5)
以此类推,当下标为6时,遇到了第3种树(1),而第3种树(1)左边的树是6,将2种树的状态更新为(6,1)

即每次2种树的状态更新为(第3种树的左边的树, 第3种树)

当理解上述思路后,就开始计数由这2种树所构成的连续子数组。

(1, 6) -> [1, 1, 6] ->连续子数组长度3

(6, 5) -> [6, 5, 6, 6] ->连续子数组长度为4

(6, 1) -> [6, 6, 1, 1, 1, 1] ->连续子数组长度为6

取连续子数组的最长长度作为返回结果,也就是6

代码如下:

class Solution {
    public int totalFruit(int[] tree) {
        int res = 0, len = tree.length;
        int one = tree[0], two, begin = 0, end = 1; 
        //2种树的状态(one, two), one初始化为tree数组的第1个元素
        while (end < len && tree[end] == one)   
        //寻找two的初始值,以构成初始(one, two)
            ++end;
        if (end >= len-1) return len; 
        //若整个数组的元素都由初始的(one, two)所构成,则直接返回数组长度
        two = tree[end++];  //构成初始的(one, two)
        for (; end < len; ++end) {
            if (one != tree[end] && two != tree[end]) { 
                //遇到了第3种树
                res = Math.max(res, end - begin);   
                //更新最终返回结果
                one = tree[end - 1];    
                //(one, two)更新为(第3种树的左边的树, 第3种树)
                two = tree[end];
                begin = end - 1;    
                //更新由当前(one, two)所构成的连续子数组的左边界
                while (tree[begin - 1] == one)   
                //向左寻找由one构成的连续子数组
                    --begin;
            }
        }
        return Math.max(res, end - begin);
    }
}

时间复杂度为O(N)
空间复杂度为O(1)
解法3来源:

作者:gfu
链接:https://leetcode-cn.com/problems/fruit-into-baskets/solution/java-hua-dong-chuang-kou-5ms-beat-100-by-gfu/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

### Java 实现水果成篮问题的解题思路 #### 1. **滑动窗口算法** 该问题可以通过滑动窗口(Sliding Window)的方法来高效求解。滑动窗口是一种动态调整区间的技巧,适用于处理连续子数组或字符串的问题。 具体来说,在本问题中,我们需要找到一个最长的连续区间 `[left, right]`,使得这个区间内的元素种类不超过两种。为了实现这一点,可以维护一个 `HashMap<Integer, Integer>` 来记录当前窗口内每种水果的数量[^1]。 #### 2. **核心逻辑** 以下是解决问题的核心逻辑: - 初始化变量:定义两个指针 `left` 和 `right` 分别表示窗口的起始位置和结束位置;定义一个哈希表用于存储当前窗口内不同水果及其对应的数量。 - 扩展窗口:逐步增加右边界 `right` 并更新哈希表中的计数值。 - 缩小窗口:如果哈希表中水果种类超过两种,则逐渐增大左边界 `left`,并减少对应水果的数量直到满足条件为止。 - 更新最大值:每次循环结束后计算当前窗口大小 `(right - left + 1)`,并与之前的结果比较以保留更大的值作为最终答案。 ```java import java.util.HashMap; import java.util.Map; public class FruitBasket { public static int totalFruit(int[] fruits) { Map<Integer, Integer> fruitCount = new HashMap<>(); int left = 0, maxFruits = 0; for (int right = 0; right < fruits.length; right++) { // 将新水果加入窗口 fruitCount.put(fruits[right], fruitCount.getOrDefault(fruits[right], 0) + 1); // 当水果种类大于2时缩小窗口 while (fruitCount.size() > 2) { fruitCount.put(fruits[left], fruitCount.get(fruits[left]) - 1); if (fruitCount.get(fruits[left]) == 0) { fruitCount.remove(fruits[left]); } left++; } // 计算当前窗口的最大水果数 maxFruits = Math.max(maxFruits, right - left + 1); } return maxFruits; } public static void main(String[] args) { int[] fruits = {1, 2, 1}; System.out.println(totalFruit(fruits)); // 输出: 3 } } ``` 以上代码实现了基于滑动窗口的思想解决水果成篮问题的功能[^1]。 #### 3. **时间复杂度分析** 由于每个索引最多被访问两次——一次由 `right` 增加时访问,另一次可能因 `left` 的移动而再次访问,因此整体的时间复杂度为 O(n),其中 n 是输入数组的长度。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值