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长度。
关键思路
-
最长严格递增子序列(LIS)
-
我们需要找到最长的子序列,使得
h[i] < h[j]对所有i < j成立。 -
由于题目要求严格递增,因此不能有相等的情况。
-
使用 贪心 + 二分查找 的方法计算 LIS,时间复杂度为
O(n log n)。
-
-
最少调整次数
-
最少调整次数 =
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;
}
代码解释
-
输入处理
-
读取测试用例
T,然后逐个处理每个测试数据。 -
每个测试数据包含
n和高度数组h。
-
-
计算 LIS 长度
-
tails数组存储当前可能的 LIS 序列。 -
遍历每个高度
num,使用二分查找确定num在tails中的位置。 -
如果
num比tails的最后一个元素大,则扩展tails;否则替换tails中第一个不小于num的元素。
-
-
输出结果
-
最少调整次数 =
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; // 返回插入位置
}
1411

被折叠的 条评论
为什么被折叠?



