题目

题目描述:

题目背景:

近些年来,我国在沙漠治理方面取得了显著成就。某沙漠实验室在干旱地区实验性地种植了 𝑁 棵胡杨树,这些胡杨树排成一排,编号从 1 到 𝑁。由于环境恶劣,部分胡杨树未能成活。 

任务:
现在实验室得到了 𝐾 棵补种树苗的预算,可以在任意未成活的位置进行补种。
请你计算,在补种不超过 𝐾 棵树苗的情况下,最多可以得到多少棵连续成活的胡杨树? 

输入描述:

  1. 第一行输入一个整数 𝑁,代表胡杨的总种植数量。(1≤𝑁≤100000)

  2. 第二行输入一个整数 𝑀,代表未成活胡杨的数量。(1≤𝑀≤𝑁)

  3. 第三行输入 𝑀 个整数,代表未成活胡杨的编号(编号从小到大排列)。(1≤编号≤𝑁)

  4. 第四行输入一个整数 𝐾,代表可以补种的树苗数量。(0≤𝐾≤𝑀)

输出描述:

输出一个整数,代表补种后最多能够得到的连续成活胡杨树的数量。 

示例:

示例1:

输入: 

5
2
2 4
1

输出: 

3

说明:

共有 5 棵树,编号 1, 2, 3, 4, 5。未成活的是 2 号和 4 号。 

  • 若补种 2 号,成活树变为 1, 2, 3,连续长度为 3。

  • 若补种 4 号,成活树变为 3, 4, 5,连续长度为 3。

最大连续长度为 3。 

示例 2:

输入: 

10
3
2 4 7
1

输出: 

6

说明:

正确逻辑:补种 7 号,连续成活范围为 3, 4(死), 5, 6, 7(补), 8, 9, 10,由于 4 号仍是死的,最长连续是 5, 6, 7, 8, 9, 10,长度为 6。 


解题思路

这道题的核心思想是将“补种 𝐾 棵树”转化为在一个区间内寻找最多能容纳 𝐾 个“死树”的最长连续区间。 

  1. 数据预处理:将原始的 𝑁 棵树看作一个数组,成活的标记为 1,未成活的标记为 0。题目输入中通常只给出未成活树的编号,你需要将这些信息转换成一个方便处理的数组或列表,或者直接利用未成活树编号的有序性。

  2. 双指针维护窗口:使用两个指针 leftright 来定义一个窗口。

    • right 指针向右移动,不断扩大窗口。

    • 在窗口移动过程中,统计当前窗口内“死树”(标记为 0)的数量 dead_count

  3. 窗口收缩:当窗口内“死树”的数量 dead_count 超过了允许补种的数量 𝐾 时,需要移动 left 指针来缩小窗口,直到 dead_count 重新小于或等于 𝐾 为止。

  4. 记录最大值:在每次窗口扩大或收缩后,当前窗口的长度(right - left + 1)就是一个可能的答案,取所有可能长度的最大值即为最终结果。

代码

常规解法:

package com.huaweiod.c.undo;

import java.util.Arrays;
import java.util.Scanner;

/**
 * 补种未成活胡杨树
 */
public class ReplantingUnviablePopulusEuphratica {

    public static int solution(int n, int[] deathIndexes, int k) {
        // 1. 初始化树木状态:1为活,0为死
        int[] trees = new int[n];
        Arrays.fill(trees, 1);
        for (int index : deathIndexes) {
            // 题目编号从1开始,数组下标从0开始,所以需要 index - 1
            trees[index - 1] = 0;
        }

        int left = 0;
        int right = 0;
        int deathCount = 0;
        int maxLength = 0;

        // 2. 标准滑动窗口模板
        while (right < n) {
            // 如果右边界是死树,增加当前窗口内的死树计数
            if (trees[right] == 0) {
                deathCount++;
            }
            // 3. 如果当前窗口内死树超过了补种预算 k,则收缩左边界
            while (deathCount > k) {
                if (trees[left] == 0) {
                    deathCount--;
                }
                left++;
            }

            // 4. 计算当前满足条件的窗口长度,更新最大值
            maxLength = Math.max(maxLength, right - left + 1);

            right++;
        }
        return maxLength;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        int[] deathIndexes = new int[m];
        for (int i = 0; i < m; i++) {
            deathIndexes[i] = sc.nextInt();
        }
        int k = sc.nextInt();
        int length = solution(n, deathIndexes, k);
        System.out.println(length);
    }
}

进阶解法:

我们可以将未成活的树编号看作一段段“围栏”的边界。补种 𝐾 棵树,相当于在死树编号序列中,抹掉连续 𝐾 个死树节点,计算抹掉后左右两侧成活树能连通的最大跨度。

为了方便处理边界,我们在死树编号列表的最左边添加一个虚拟编号 0,最右边添加一个虚拟编号 N+1

package com.huaweiod.c.completed;

import java.util.Scanner;

/**
 * 补种胡杨树进阶版
 */
public class PopulusEuphraticaAdvanced {

    /*
     * 滑动窗口操作死树坐标:
     *
     * 窗口代表补种 k 棵树后覆盖的死树区间。
     * 假设我们补种从 deadPos[i] 到 deadPos[i+k-1] 这 k 棵树。
     * 那么这组连续成活树的最左端受 deadPos[i-1] 限制,最右端受 deadPos[i+k] 限制。
     * 连续长度 = (右边界编号) - (左边界编号) - 1
     * 简化为 = deadPos[i+k] - deadPos[i-1] - 1
     */
    public static int solution(int n, int[] deadPos, int k) {
        int maxLength = 0;
        for (int i = 1; i + k < deadPos.length; i++) {
            // i 是当前补种区间的第一个死树索引
            // i + k 是补种完 k 棵后,遇到的第一个“依然死着”的树索引
            int currentLength = deadPos[i + k] - deadPos[i - 1] - 1;
            maxLength = Math.max(maxLength, currentLength);
        }
        return maxLength;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // 输入处理
        int n = sc.nextInt(); // 总数
        int m = sc.nextInt(); // 死树数

        // 只存死树位置,并补充虚拟边界
        int[] deadPos = new int[m + 2];
        deadPos[0] = 0;   // 虚拟左边界
        for (int i = 0; i < m; i++) {
            deadPos[i] = sc.nextInt();
        }
        deadPos[m + 1] = n + 1; // 虚拟右边界

        // 补种预算
        int k = sc.nextInt();

        // 特殊情况
        if (k >= m) {
            System.out.println(n);
            return;
        }

        System.out.println(solution(n, deadPos, k));
    }
}