当前位置: 首页 > news >正文

检测相邻递增子数组1 2(LeetCode 3349 3350)

1 介绍

本文介绍检测相邻递增子数组1 & 2的思路以及解法。

2 检测相邻递增子数组1

2.1 原题

2.2 思路

由于范围不大,数组长度只有100,可以直接考虑暴力算法。

具体来说,就是遍历数组中的每个下标i,然后再遍历一次[i,i+k)[i+k,i+2*k),需要两个子数组都是严格递增的。

代码如下,带有详细注释。

class Solution {
public:bool hasIncreasingSubarrays(vector<int> &nums, int k) {// i表示当前遍历的下标// i的范围是[0,n-2*k],因为当i=n-2*k时,i+2*k刚好是最后一个,注意取值不会取到i+2*k,只会取到i+2*k-1for (int i = 0, n = static_cast<int>(nums.size()); i + 2 * k <= n; ++i) {// 表示是否严格递增bool valid = true;// 判断第一个数组是否严格递增for (int j = i + 1; j < i + k; ++j) {if (nums[j] <= nums[j - 1]) {// 如果不是严格递增,记录false,然后跳过剩下的直接breakvalid = false;break;}}// 如果不是严格递增,从下一个下标开始处理,直接continueif (!valid) {continue;}// 第二个数组处理类似,只不过范围变成[i+k,i+2*k)for (int j = i + k + 1; j < i + 2 * k; ++j) {if (nums[j] <= nums[j - 1]) {valid = false;break;}}if (!valid) {continue;}// 如果找到了,直接返回truereturn true;}return false;}
};

2.3 Java版本

public class Solution {public boolean hasIncreasingSubarrays(List<Integer> nums, int k) {for (int i = 0, n = nums.size(); i + 2 * k <= n; i++) {boolean valid = true;for (int j = i + 1; j < i + k; j++) {if (nums.get(j) <= nums.get(j - 1)) {valid = false;break;}}if (!valid) {continue;}for (int j = i + k + 1; j < i + 2 * k; j++) {if (nums.get(j) <= nums.get(j - 1)) {valid = false;break;}}if (!valid) {continue;}return true;}return false;}
}

2.4 Go版本

func hasIncreasingSubarrays(nums []int, k int) bool {for i, n := 0, len(nums); i+2*k <= n; i++ {valid := truefor j := i + 1; j < i+k; j++ {if nums[j] <= nums[j-1] {valid = falsebreak}}if !valid {continue}for j := i + k + 1; j < i+2*k; j++ {if nums[j] <= nums[j-1] {valid = falsebreak}}if !valid {continue}return true}return false
}

3 检测相邻递增子数组2

3.1 原题

3.2 思路

题目描述不同的是需要求最长的k,并且范围扩大了,变成了2 * 10^5

在第一题的思路中,可以发现其实有非常多的无效计算,导致了时间复杂度是O(n^2)

实际上,在每次计算的时候,可以算出从当前下标开始,有多少个值是连续严格递增的。

例如例子中的[2,5,7,8,9,2,3,4,3,1],可以手动计算一下:

  • 下标为0,从2开始,只有2个数连续递增
  • 下标为1,从5开始,只有1个数连续递增(1个数也算)
  • 下标为2,从7开始,只有3个数连续递增
  • 下标为3,从8开始,只有2个数连续递增
  • 下标为4,从9开始,只有1个数连续递增
  • 下标为5,从2开始,只有3个数连续递增
  • 下标为6,从3开始,只有2个数连续递增
  • 下标为7,从4开始,只有1个数连续递增
  • 下标为8,从3开始,只有1个数连续递增
  • 下标为9,从1开始,只有1个数连续递增

可以看到,从下标2和下标5开始,都有3个数连续递增,并且两个子数组是相邻的,以及此时的k最大,所以答案就是3

所以这里的思路就是计算“有几个数连续递增”,这个很容易做到,在一边遍历数组的时候一边赋值即可,使用一个叫cnt的数组存储这些值。

另一方面,最长递增的相邻数组有两种情况:

  • 第一种情况是像例子中的一样,两个子数组拼在一起不是连续递增的,例如[7,8,9,2,3,4]
  • 第二种情况是,两个子数组拼在一起连续递增,这样的话长度就是取一半,例如[1,2,3,4,5,6],这样的话k=3

所以,得出cnt数组之后,对cnt数组进行如下处理:

  • 遍历数组每一个下标i
  • 判断是否是第一种情况,可以通过i+cnt[i] < n && cnt[i] <= cnt[i+cnt[i]]判断
  • 判断是否是第二种情况,可以通过i+cnt[i]/2 < n && cnt[i]/2 <= cnt[i+cnt[i]/2]判断
  • 如果符合其中一种,计算当前的值并与结果值比较,最后返回最大值

这里解释一下第一条式子:

  • i+cnt[i] < n:由于位于i时,最长能取到的递增长度是cnt[i],而i+cnt[i]就是下一个相邻下标的值,这个值需要在数组范围内,也就是i+cnt[i]<n
  • cnt[i] <= cnt[i+cnt[i]]cnt[i]是当前能取到的最大的值,而cnt[i+cnt[i]]表示下标i+cnt[i]能取到的最大值,这里的判断意思就是类似[7,8,9,1,2,3,4]这样的情况,其中cnt[0]=3,cnt[3]=4,当i=0时,cnt[0] <= cnt[0+3],也就是cnt[0] <= cnt[3]

第二条式子:

  • i+cnt[i]/2 < n:位于i时,最长能取到的是cnt[i],这里是处理第二种情况,所以cnt[i]需要取一半,i+cnt[i]/2就是第二个数组的起始下标
  • cnt[i]/2 <= cnt[i+cnt[i]/2]:和cnt[i] <= cnt[i+cnt[i]]类似,只不过值都是除2,一个例子是[1,2,3,4,5,6,7],当i=0时,cnt[0]=7i+cnt[i]/23cnt[i+cnt[i]/2]4,符合题目要求,此时k=3

代码如下:

class Solution {
public:int maxIncreasingSubarrays(vector<int> &nums) {const int n = static_cast<int>(nums.size());// cnt数组vector<int> cnt(n);// l是辅助变量,控制上一次的第一个数组的起始位置,用于后面赋值cnt数组int l = 0;// 结果int res = 0;for (int i = 1; i < n; ++i) {// 不断遍历数组,直接遇到非严格递增的if (nums[i] > nums[i - 1]) {continue;}// 起始位置是l,长度cur=i-l,然后赋值给cnt数组for (int j = l, cur = i - l; j < i; ++j) {cnt[j] = cur--;}l = i;}// 兜底处理,用于处理整个数组都是递增数组的情况,如[1,2,3,4,5]// 这种情况下,进入不到上面for循环的处理cnt的部分,l一直是0for (int j = l, cur = n - l; j < n; ++j) {cnt[j] = cur--;}// 遍历cnt数组for (int i = 0; i < n; ++i) {// 第一种情况,公式详解见上面描述if (i + cnt[i] < n && cnt[i] <= cnt[i + cnt[i]]) {// 第一种情况下最大是cnt[i]res = max(res, cnt[i]);} else if (i + cnt[i] / 2 < n && cnt[i] / 2 <= cnt[i + cnt[i] / 2]) {// 第二种情况,最大是cnt[i]/2res = max(res, cnt[i] / 2);}}return res;}
};

3.3 O(1)空间优化

实际上,上面的做法中,可以不存储cnt的值,从而将空间进一步优化到O(1)

思路就是计算cnt的同时,使用一个变量保存上一次cnt的值:

  • 遍历到非递增的时候,保存上一次的cnt,并重新累计cnt
  • 遍历到递增的时候,计算当前的最大值

其中“计算当前的最大值”也包含了上面分析的两种情况:

  • 第一种情况:值是min(last_cnt,cnt),因为两者只能取小值
  • 第二种情况:值是cnt/2,分析与上面类似,就是取一半

最终答案就是两者取最大值,max(cnt/2,min(last_cnt,cnt))

代码如下:

class Solution {
public:int maxIncreasingSubarrays(vector<int> &nums) {// 结果最小是1int res = 1;// 下标从1开始,cnt表示当前递增的长度,last_cnt保存上一个cntfor (int i = 1, cnt = 1, last_cnt = 1, n = static_cast<int>(nums.size()); i < n; ++i) {// 如果遇到非递增的if (nums[i] <= nums[i - 1]) {// 保存上一次cnt的值last_cnt = cnt;// cnt重新计算cnt = 1;continue;}// 遇到递增的,累加cnt++cnt;// 计算最终结果res = max(res, max(cnt / 2, min(last_cnt, cnt)));}return res;}
};

3.4 二分

这种做法可能比较难想,但是实际上是可以的。

对于一道题能否使用二分的做法取处理,核心就是看是否具有单调性。

从上面的做法分析可以知道:

  • k越大,得到答案的概率越低,例如k=10^5,除非数组是有两个10^5的子数组构成的,否则就不成立
  • k越小,得到答案的概率越高,例如k=2的情况肯定比k=10^5更容易找到子数组符合情况

这样就符合了单调性的条件,所以思路就是直接二分答案kk的范围是[1,n/2]

每次二分得到k,然后参考上面的解法,计算是否存在max(cnt/2,min(last_cnt,cnt)) >= k

代码如下:

class Solution {
public:int maxIncreasingSubarrays(vector<int> &nums) {const int n = static_cast<int>(nums.size());// 二分auto check = [&](int m) -> bool {// cnt初始化为1,last_cnt初始化为0int cnt = 1;int last_cnt = 0;// 从下标1开始for (int i = 1; i < n; ++i) {// 如果没有递增,保存cnt,并且重置cntif (nums[i] <= nums[i - 1]) {last_cnt = cnt;cnt = 1;} else {// 否则累加cnt++cnt;}// 这里参考上面的解法,分别对应第一种情况一和第二种情况// 如果找到>=m的,直接returnif (max(cnt / 2, min(last_cnt, cnt)) >= m) {return true;}}return false;};// k的范围是[1,n/2]int l = 1;int r = n / 2;while (l <= r) {// 二分const int m = (l + r) >> 1;// 如果存在符合条件的子数组,表明可以继续扩大kif (check(m)) {// 扩大kl = m + 1;} else {// 如果没有符合条件的子数组,缩小k的范围r = m - 1;}}return r;}
};

遍历的复杂度是O(n),再加上二分kk的范围是[1,n/2],所以时间复杂度就是O(n log n)

所以这个做法是比上面的做法(O(n))要慢的,这里仅仅提供一种二分的思路,空间复杂度的话是一样的,都是O(1)

遍历做法:

二分做法:

3.5 Java版本

3.5.1 遍历解法

public class Solution {public int maxIncreasingSubarrays(List<Integer> nums) {int res = 1;for (int i = 1, cnt = 1, lastCnt = 0, n = nums.size(); i < n; i++) {if (nums.get(i) <= nums.get(i - 1)) {lastCnt = cnt;cnt = 1;continue;}++cnt;res = Math.max(res, Math.max(cnt / 2, Math.min(lastCnt, cnt)));}return res;}
}

3.6.2 二分解法

public class Solution {private boolean check(int m, List<Integer> nums) {for (int i = 1, cnt = 1, lastCnt = 0, n = nums.size(); i < n; i++) {if (nums.get(i) <= nums.get(i - 1)) {lastCnt = cnt;cnt = 1;} else {++cnt;}if (Math.max(cnt / 2, Math.min(lastCnt, cnt)) >= m) {return true;}}return false;}public int maxIncreasingSubarrays(List<Integer> nums) {int l = 1;int r = nums.size() / 2;while (l <= r) {int m = (l + r) >> 1;if (check(m, nums)) {l = m + 1;} else {r = m - 1;}}return r;}
}

3.6 Go版本

3.6.1 遍历解法

func maxIncreasingSubarrays(nums []int) int {res := 1for i, cnt, lastCnt := 1, 1, 0; i < len(nums); i++ {if nums[i] <= nums[i-1] {lastCnt, cnt = cnt, 1continue}cnt++res = max(res, max(cnt/2, min(lastCnt, cnt)))}return res
}

3.6.2 二分解法

func maxIncreasingSubarrays(nums []int) int {n := len(nums)check := func(m int) bool {for i, cnt, lastCnt := 1, 1, 0; i < n; i++ {if nums[i] <= nums[i-1] {lastCnt, cnt = cnt, 1} else {cnt++}if max(cnt/2, min(lastCnt, cnt)) >= m {return true}}return false}l, r := 1, n/2for l <= r {m := (l + r) >> 1if check(m) {l = m + 1} else {r = m - 1}}return r
}

4 总结

本文介绍了检测相邻递增子数组的两种解法:

  • 一种是遍历,时间复杂度O(n),空间复杂度O(1)
  • 另一种是二分,时间复杂度O(n log n),空间复杂度O(1)

尽管二分的做法更慢,但是提供了另一种思路去解决问题。

5 附录

  • 检测相邻递增子数组1
  • 检测相邻递增子数组2
http://www.dtcms.com/a/535736.html

相关文章:

  • 《算法闯关指南:优选算法--前缀和》--25.【模板】前缀和,26.【模板】二维前缀和
  • 快速搭建网站2020缅甸新闻最新消息
  • 搜索网站做推广全网推广平台推荐
  • 仓颉编程(16)泛型类型
  • 「小有可为」AI 开源公益创新挑战赛
  • 《 Linux 点滴漫谈: 四 》文件权限与用户管理
  • 评估虚拟机资源规划
  • 深入理解 SO_REUSEADDR:从“Address already in use”到服务器瞬间重启
  • 机器人中的多模态——RoboBrain
  • MySQL 8.0.x 全平台安装指南:Windows、CentOS、Ubuntu 详细步骤与问题解决
  • YOLO!!
  • 电子电气架构 --- 汽车座舱行业背景综述
  • C++(23):通过print和printIn进行输出
  • 获取网站访客qq号成都网站建设优点
  • 做一个同城便民信息网站怎么做公司给别人做的网站违法吗
  • 微算法科技(NASDAQ MLGO)探索自适应差分隐私机制(如AdaDP),根据任务复杂度动态调整噪声
  • 入选大模型一体机产业图谱,云从科技以全栈能力推动AI落地新范式
  • 十六、STM32的TIM(七)(PWM直流电机)
  • TCP与UDP深度理解
  • 万界星空科技MES系统功能介绍及实施指南
  • 中国软件出海,为何优选亚马逊云科技Marketplace?
  • StarRocks Community Monthly Newsletter (Sep)
  • HarmonyOS 微服务与 OpenHarmony 开发:构建模块化与开源生态应用
  • autojs----2025淘宝淘金币跳一跳自动化
  • 什么网站可以做兼职赚钱吗互联网商城建设
  • 地方网站系统建模素材免费网站
  • 东莞百度网站快速排名怎么用.net做网站
  • IP5306 2.4A放电 2.1A充电 高集成度移动电源SOC
  • Qt5与Qt6的详细区别
  • Sui 主网升级至 V1.58.3