给你一个下标从 0 开始的 正 整数数组 nums
。
如果 nums
的一个子数组满足:移除这个子数组后剩余元素 严格递增 ,那么我们称这个子数组为 移除递增 子数组。比方说,[5, 3, 4, 6, 7]
中的 [3, 4]
是一个移除递增子数组,因为移除该子数组后,[5, 3, 4, 6, 7]
变为 [5, 6, 7]
,是严格递增的。
请你返回 nums
中 移除递增 子数组的总数目。
注意 ,剩余元素为空的数组也视为是递增的。
子数组 指的是一个数组中一段连续的元素序列。
示例 1:
输入:nums = [1,2,3,4] 输出:10 解释:10 个移除递增子数组分别为:[1], [2], [3], [4], [1,2], [2,3], [3,4], [1,2,3], [2,3,4] 和 [1,2,3,4]。移除任意一个子数组后,剩余元素都是递增的。注意,空数组不是移除递增子数组。
示例 2:
输入:nums = [6,5,7,8] 输出:7 解释:7 个移除递增子数组分别为:[5], [6], [5,7], [6,5], [5,7,8], [6,5,7] 和 [6,5,7,8] 。 nums 中只有这 7 个移除递增子数组。
示例 3:
输入:nums = [8,7,6,6] 输出:3 解释:3 个移除递增子数组分别为:[8,7,6], [7,6,6] 和 [8,7,6,6] 。注意 [8,7] 不是移除递增子数组因为移除 [8,7] 后 nums 变为 [6,6] ,它不是严格递增的。
首先我们对题干进行简单的分析:首先简单解释一下移除递增子数组的概念:存也一个数组 nums
的一个子数组满足移除这个子数组后剩余元素 严格递增 。而该题的目的是返回所提供的数组nums
中移除递增子数组的总数目。
那么该如何解决这个问题呢?详细的解题思路如下:已知题干要求是计算一个数组移除任意一个子数组后剩余数组仍然严格递增的情况数目。因此我们需要实现以下功能:1.判断数组是否严格递增。2.计算符合条件的数目情况。
首先第一步定义一个函数用于判断数组是否严格递增,这里我们只需要判断数组内后一项是否严格大于前一项,如果不满足这个条件则不符合严格递增。相关代码如下:
- // 定义一个函数用于判断一个数组是否严格递增
- bool isStrictlyIncreasing(int* arr, int size) {
- for (int i = 1; i < size; i++) {
- if (arr[i] <= arr[i - 1]) {
- return false;
- }
- }
- return true;
- }
第二步就是定义一个函数用于计算以上 符合条件(即严格递增的数组数量)的数组数量:这里我们的代码设计思路为:
1.定义一个计数器用于统计符合条件的情况数目。
2.使用两个嵌套的循环来枚举该数组的所有子数组。
3.计算子数组以及剩余数组的大小
4.构建剩余数组(即将去除子数组的剩余数组重新构造成一个新的数组)。
5.检查剩余数组是否是为严格递增数组,如果是计数器加一
相关代码如下:
- int countIncreasingSubarrays(int* nums, int numsSize) {
- int count = 0;
- // 进行双重循环枚举每个子数组
- for (int i = 0; i < numsSize; i++) {
- for (int j = i; j < numsSize; j++) {
- // 计算子数组的长度
- int subarraySize = j - i + 1;
- // 计算剩余子数组的长度
- int remainingSize = numsSize - subarraySize;
- // 动态分配内存用于存储移除子数组后的剩余子数组
- int* remainingArray = (int*)malloc(remainingSize * sizeof(int));
- int index = 0;
-
- // 构建移除子数组后的剩余数组
- for (int k = 0; k < numsSize; k++) {
- if (k < i || k > j) {
- remainingArray[index++] = nums[k];
- }
- }
- // 检查剩余的数组是否严格递增
- if (isStrictlyIncreasing(remainingArray, remainingSize)) {
- count++;
- }
- // 释放动态分配的内存
- free(remainingArray);
- }
- }
- return count;
- }
上述代码进过运行发现基本可以满足题目要求但是运行时间过长,那么我们就又可以在上述代码的基础上进行更迭。相关的代码优化思路如下:
1.原本我们的思路是将去除子数组后的剩余数组重新构造为一个数组,并判断该数组是否严格递增。想要提高代码的运行效率就可以从这里入手,这里我们可以通过直接在原数组中检查移除子数组后的部分,而不是构造一个新的数组,这样我们就减少了内存操作和不必要的数组拷贝,从而提高了效率。
2.在检查数组是否严格递增时一旦发现不满足条件的情况,就立刻终止检查从而避免不必要的计算。
- //初步优化后的算法函数
- int incremovableSubarrayCount(int* nums, int numsSize) {
- int count = 0;
-
- //遍历每个起点
- for (int start = 0; start < numsSize; start++)
- {
- //遍历每个终点
- for (int end = start; end < numsSize; end++)
- {
- //这里我们设计检查移除[start, end]子数组后剩余部分是否严格递增
- bool valid = true;
- //检查移除子数组后左半部分是否递增
- if (start > 0)
- {
- for (int i = 1; i < start; i++) {
- if (nums[i] <= nums[i - 1])
- {
- valid = false;
- break;
- }
- }
- }
- // 检查移除子数组后的右半部分是否递增
- if (end < numsSize - 1) {
- for (int i = end + 2; i < numsSize; i++) {
- if (nums[i] <= nums[i - 1]) {
- valid = false;
- break;
- }
- }
- }
- //检查移除子数组后两端是否连接递增
- if (start > 0 && end < numsSize - 1)
- {
- if (nums[start - 1] >= nums[end + 1]) {
- valid = false;
- }
- }
- if (valid)
- {
- count++;
- }
- }
- }return count;
- }
通过以上操作,代码的效率有了一定的提升,但是整体效果并不明显。接下来我们将分析官方所提供的相关题解。
我们上面的两个算法基本上都是采用了暴力求解算法,苏安然思路简单但是效率不高,而官方的题解算法则要简单很多:
- //官方题解
- int incremovableSubarrayCount(int* nums, int numsSize) {
- int res = 0;//初始化结果变量
- int l = 1;//初始化左指针,用于记录最长递增前缀的长度
- //从右向左找到最长的递增前缀
- while (l < numsSize && nums[l - 1] < nums[l])
- {
- l++;
- }
- //计算初始满足条件的子数组数量
- res += l + (l < numsSize);
- //从左向右遍历数组
- for (int r = numsSize - 2; r >= 0; r--)
- {
- //调整左指针的位置以确保移除子数组后剩余部分满足严格递增条件
- while (l > 0 && nums[l - 1] >= nums[r + 1])
- {
- l--;
- }
-
- //计算当前满足条件的子数组数量
- res += l + (l <= r);
-
- //如果当前元素不满足递增条件,退出循环
- if (nums[r] >= nums[r + 1])
- {
- break;
- }
-
- }
- return res;//返回结果
- }
官方的解题思路利用双指针和贪心算法来高效解决这个问题。主要的解题思想是先从左向右找到最长的递增前缀,然后从右向左找到最长的递增前缀,最后通过双指针法来计算符合条件的子数组。
已知题干最后仅需要编写关键函数即可,因此在力扣代码编写相关函数即可。相关的运行结果: