填充低洼区域

1、题目描述

多多想要举办一场助农音乐节,音乐节将在收获果实的风景优美的山区举行,以提供独特的视听体验。然而,山区的自然地形往往起伏不平。为了确保观众的安全和良好的视听效果,场地的地形高度需要进行合理调整,确保每个位置的高度都要高于前一个位置,从而避免因地形起伏造成的摔倒或视线遮挡。
在实际地形中,可能存在一些低洼区域需要填充,以及一些过高的区域需要切削。为了解决这一问题,多多希望通过尽量少的地形调整(即调整最少的测量点高度),使整个场地的高度呈现严格上升的形态。请你帮多多计算为了确保音乐节的成功举办,最少需要对多少个位置进行地形调整注意:数据范围仅限制输入值,调整后的数值可以超出范围。如调整后的高度可以为负数(允许变为谷底)

输入描述
第一行为一个整数T, 表示共有T个测试数据(1 <= T<= 10)每组测试数据:第一行为一个整数n,表示山区的测量点高度总数(1<=n<= 100000)第二行有n个整数hi,表示每个测星点i的地形高度(1<= hi<= 100000)


输出描述
每组数据输出一个结果,每个结果占一行

补充说明
对于20%的数据存:1<=n<=1000

对于100%的数据有:1<=n<=100000

数据范围仅限制输入值,调整后的数值可以超出范国。如调整后的高度可以为负数(允许变为谷底)

示例 1
输入
1
4
5 3 6 2


输出
2


说明
最少的地形调整次数为2,可将下标为0的5调整为2,下标为3的2调整为7

2、解题思路

我们需要将给定的地形高度序列调整为严格递增序列,且调整的点数最少。换句话说,我们需要找到最长的严格递增子序列(LIS),然后剩下的点就是需要调整的点。因此,最少调整次数为 n - LIS长度

关键思路

  1. 最长严格递增子序列(LIS)

    • 我们需要找到最长的子序列,使得 h[i] < h[j] 对所有 i < j 成立。

    • 由于题目要求严格递增,因此不能有相等的情况。

    • 使用 贪心 + 二分查找 的方法计算 LIS,时间复杂度为 O(n log n)

  2. 最少调整次数

    • 最少调整次数 = n - LIS长度

算法选择

  • 标准 LIS 问题:直接套用贪心 + 二分查找的解法。

  • 严格递增处理:在二分查找时,如果 h[i] == tails[mid],仍然要继续向左搜索(因为严格递增不允许相等)。

public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int T = scanner.nextInt();
        while (T-- > 0) {
            int n = scanner.nextInt();
            int[] h = new int[n];
            for (int i = 0; i < n; i++) {
                h[i] = scanner.nextInt();
            }
            System.out.println(n - lengthOfLIS(h));
        }
    }

    // 计算严格递增的 LIS 长度
    private static int lengthOfLIS(int[] nums) {
        //定义数组存储当前可能的LIS序列
        int[] tails = new int[nums.length];
        int size = 0;
        //找到严格递增子序列,贪心 + 二分法
        for (int num : nums) { // 5 3 6 2
            int left = 0, right = size;
            while (left < right) {
                int mid = left + (right - left) / 2;
                //由于题目要求严格递增,因此不能有相等的情况。
                if (tails[mid] < num) {
                    left = mid + 1;
                } else {
                    right = mid;
                }
            }
            tails[left] = num;
            if (left == size) {
                size++;
            }
        }
        return size;
    }

代码解释

  1. 输入处理

    • 读取测试用例 T,然后逐个处理每个测试数据。

    • 每个测试数据包含 n 和高度数组 h

  2. 计算 LIS 长度

    • tails 数组存储当前可能的 LIS 序列。

    • 遍历每个高度 num,使用二分查找确定 num 在 tails 中的位置。

    • 如果 num 比 tails 的最后一个元素大,则扩展 tails;否则替换 tails 中第一个不小于 num 的元素。

  3. 输出结果

    • 最少调整次数 = n - LIS长度

复杂度分析

  • 时间复杂度O(n log n)(每个元素二分查找一次)。

  • 空间复杂度O(n)(存储 tails 数组)。

其中求LIS最长子序列的长度,也是使用下面的贪心+二分法实现,比较容易理解:

public static int greedy(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        List<Integer> min = new ArrayList<>();
        min.add(nums[0]);
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] > min.get(min.size() - 1)) {
                min.add(nums[i]);
            } else {
                int index = binarySearch(min, nums[i]);
                min.set(index, nums[i]);
            }
        }
        return min.size();
    }

    // 迭代实现二分查找,找到第一个 >= target 的位置(严格递增)
    private static int binarySearch(List<Integer> nums, int target) {
        int left = 0, right = nums.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums.get(mid) < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return left; // 返回插入位置
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值