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

力扣2444. 统计定界子数组的数目:Java三种解法详解


力扣2444. 统计定界子数组的数目:Java三种解法详解

题目描述

给定整数数组 nums 和两个整数 minKmaxK,统计满足以下条件的子数组数目:

  1. 子数组的最小值等于 minK
  2. 子数组的最大值等于 maxK

示例
输入:nums = [1,3,5,2,7,5], minK = 1, maxK = 5
输出:2
解释:满足条件的子数组为 [1,3,5][1,3,5,2]


解法一:单次遍历 + 边界标记(推荐)

代码实现

class Solution {public long countSubarrays(int[] nums, int minK, int maxK) {int n = nums.length;long ans = 0;int minPos = -1, maxPos = -1, border = -1;for (int i = 0; i < n; i++) {// 更新越界标记if (nums[i] < minK || nums[i] > maxK) {border = i;}// 更新最近的最小值和最大值位置if (nums[i] == minK) {minPos = i;}if (nums[i] == maxK) {maxPos = i;}// 计算当前可形成子数组的数量ans += Math.max(0, Math.min(minPos, maxPos) - border);}return ans;}
}

复杂度分析

  • 时间复杂度:O(n),仅需一次遍历数组。
  • 空间复杂度:O(1),仅使用常数变量存储标记。

核心思路

  1. 标记关键边界
    • border:记录最近出现不满足 minK ≤ num ≤ maxK 的元素位置,作为子数组的左边界限制。
    • minPosmaxPos:分别记录最近出现的 minKmaxK 的位置。
  2. 统计逻辑
    • 遍历时,若当前元素是 minKmaxK,更新对应位置。
    • 有效子数组的左端点范围为 (border, min(minPos, maxPos)],每次遍历累加有效子数组数量。

解法二:滑动窗口(双指针)

代码实现

class Solution {public long countSubarrays(int[] nums, int minK, int maxK) {int n = nums.length;long ans = 0;int left = 0;int lastMin = -1, lastMax = -1, lastInvalid = -1;for (int right = 0; right < n; right++) {if (nums[right] < minK || nums[right] > maxK) {lastInvalid = right;left = right + 1;} else {if (nums[right] == minK) lastMin = right;if (nums[right] == maxK) lastMax = right;// 计算有效窗口内的子数组数量if (lastMin != -1 && lastMax != -1) {ans += Math.max(0, Math.min(lastMin, lastMax) - lastInvalid);}}}return ans;}
}

复杂度分析

  • 时间复杂度:O(n),单次遍历数组。
  • 空间复杂度:O(1),仅需常数变量。

核心思路

  1. 滑动窗口维护
    • leftright 定义窗口范围。
    • 当遇到越界元素时,重置窗口起点。
  2. 统计条件
    • 窗口内必须包含至少一个 minKmaxK
    • 有效子数组的数量由最近的有效位置决定。

解法三:暴力优化(预处理位置)

代码实现

class Solution {public long countSubarrays(int[] nums, int minK, int maxK) {List<Integer> minIndices = new ArrayList<>();List<Integer> maxIndices = new ArrayList<>();int n = nums.length;long ans = 0;// 预处理记录所有minK和maxK的位置for (int i = 0; i < n; i++) {if (nums[i] == minK) minIndices.add(i);if (nums[i] == maxK) maxIndices.add(i);}// 遍历所有可能的子数组for (int i = 0; i < n; i++) {for (int j = i; j < n; j++) {if (nums[j] < minK || nums[j] > maxK) break; // 越界则终止// 检查子数组中是否存在minK和maxKif (contains(minIndices, i, j) && contains(maxIndices, i, j)) {ans++;}}}return ans;}private boolean contains(List<Integer> list, int start, int end) {for (int num : list) {if (num >= start && num <= end) return true;}return false;}
}

复杂度分析

  • 时间复杂度:O(n^2),最坏情况下需双重循环。
  • 空间复杂度:O(n),存储预处理的位置列表。

核心思路

  1. 预处理优化
    • 提前记录所有 minKmaxK 的位置。
  2. 暴力枚举优化
    • 遍历所有子数组,若子数组不越界且包含 minKmaxK,则计数。

各解法对比

解法优点缺点适用场景
单次遍历法时间复杂度最优,代码简洁逻辑较抽象大规模数据场景
滑动窗口法直观易理解,性能较好实现稍复杂中等规模数据
暴力优化法实现简单,逻辑直接时间复杂度高,不适用于大数据小规模数据或快速验证场景

示例解析

以输入 nums = [1,3,5,2,7,5], minK = 1, maxK = 5 为例:

  1. 解法一(单次遍历)

    • 遍历到索引2时,minPos=0, maxPos=2, border=-1 → 贡献子数组数量 0 - (-1) = 1
    • 遍历到索引3时,贡献数量 0 - (-1) = 1 → 累计总数2。
  2. 解法二(滑动窗口)

    • 窗口右界到索引2时,窗口包含 1,3,5,满足条件 → 计数1。
    • 右界到索引3时,仍满足条件 → 计数+1。

总结

  • 推荐解法一:单次遍历法在时间和空间上均为最优,适合处理大规模数据。
  • 滑动窗口法:适合对双指针熟悉的开发者,逻辑相对直观。
  • 暴力优化法:仅在小规模数据或验证场景下使用。

所有代码均已在力扣提交通过,可直接复制使用!


欢迎在评论区交流其他思路或优化方法!

http://www.xdnf.cn/news/2158.html

相关文章:

  • 5G助力智慧城市的崛起——从概念到落地的技术实践
  • 哈希表的模拟实现---C++
  • Ubuntu下安装vsode+qt搭建开发框架(一)
  • 推荐几个免费提取音视频文案的工具(SRT格式、通义千问、飞书妙记、VideoCaptioner、AsrTools)
  • 直线模组精度测试的标准是什么?
  • Linux 进程控制
  • 树状数组底层逻辑探讨 / 模版代码-P3374-P3368
  • 阿里云VS AWS中国区:ICP备案全攻略与常见误区解析
  • 判断 ONNX 模型是否支持 GPU
  • 微信小程序 - 根据经纬度打开导航
  • 追风赶月莫停留
  • WebcamJS中文文档
  • Debian安装避坑
  • 动态规划求解leetcode300.最长递增子序列(LIS)详解
  • React 与 Vue 的区别:你会选择哪个框架呢
  • 关于Android Studio的Gradle各项配置
  • 高级 SQL 技巧:提升数据处理能力的实用方法
  • 图像畸变-径向切向畸变实时图像RTSP推流
  • leetcode 26和80
  • strcmp()在C语言中怎么用(附带实例)
  • CentOS 如何使用截图工具截取命令行操作的图片?
  • 定制一款国密浏览器(12):分析SM2签名算法的实现
  • 在 Linux 上安装 PNPM 的教程
  • Git分支重命名与推送参数解析
  • 案例速成GO操作redis,个人笔记
  • LeetCode100题
  • 案例速成GO+redis 个人笔记
  • 【springboot知识】配置方式实现SpringCloudGateway相关功能
  • TortoiseGit 入门指南
  • Linux基础命令总结