Q53-code1438- 绝对差不超过限制的最长连续子数组 + Q54- code895- 最大频率栈

Q53- code1438- 绝对差不超过限制的最长连续子数组

实现思路

1 方法1:双单调队列 + 滑动窗口

S1:理解问题本质

原问题:找最长连续子数组,任意两元素绝对差 ≤ limit 等价于:找最长连续子数组,最大值 - 最小值 ≤ limit

  • 如果 max - min ≤ limit,那么任意两元素差都 ≤ limit

S2:寻找规律

  • 如果 [i...j] 满足条件,那么 [i...j-1] 也满足
  • 如果 [i...j] 不满足条件,那么 [i...j+1] 也不满足

S3:单调性

  • 对于固定的 left,存在一个最大的 right,使得 [left...right] 满足条件
  • 当 left 增加时,这个最大的 right 不会减少(单调性)
    • 当 原left为最大值/最小值时, 最大差值会变小
    • 当 原left不是最大值/最小值时,最大差值会不变

即:

  • 如果 [left...right] 不满足条件(差值 > limit)
  • 那么 [left+1...right] 的差值 ≤ 原差值
  • 所以 [left+1...right] 可能满足条件

所以 当窗口不满足条件时,left++ 后, 窗口可能重新满足条件,所以 right 不需要回退!

S4.1:滑动窗口使用场景

  • 特征:具有"窗口越长越难满足条件"的单调性质
  • 当窗口不满足条件时,需要收缩左边界而不是回退右边界,因为继续扩展右边界只会让情况更糟

S4.2:单调队列的作用

  • 需要维护:窗口内的最大值和最小值
  • 频繁操作:窗口滑动时需要快速获取最值
  • 单调队列优势:O(1)时间获取最值,O(1)时间维护

参考文档

01- 方法1参考文档

02- 更多复杂方法参考实现

代码实现

1 方法1: 双单调队列 + 滑动窗口

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
ts 复制代码
function longestSubarray(nums: number[], limit: number): number {
  // 两个空数组,存储索引
  let minQ: number[] = [], maxQ: number[] = [];
  // ans记录最长长度,left是窗口左边界
  let left = 0, ans = 0;

  nums.forEach((num, right) => {
    // S1 保证 新元素总是从右边进入窗口的

    // 维护最小值队列(单调递增)
    // 如果 当前元素 ≤ 队列最后一个元素对应的值
    // 则移除队列最后一个元素(保持单调递增)
    while (minQ.length && num <= nums[minQ.at(-1)]) minQ.pop();
    minQ.push(right);

    // 维护最大值队列(单调递减)
    // 如果 当前元素 ≥ 队列最后一个元素对应的值
    // 则移除队列最后一个元素(保持单调递减)
    while (maxQ.length && num >= nums[maxQ.at(-1)]) maxQ.pop();
    maxQ.push(right);

    // S2 收缩窗口
    // 如果当前窗口的最大值-最小值 > limit
    // 需要收缩窗口
    while (nums[maxQ[0]] - nums[minQ[0]] > limit) {
      left++;
      // 如果最小值队列的队首索引 < left(已过期)
      // 则移除队首
      while (minQ[0] < left) minQ.shift();
      // 如果最大值队列的队首索引 < left(已过期)
      // 则移除队首
      while (maxQ[0] < left) maxQ.shift();
    }
    // 更新最长长度
    // 当前窗口长度 = right - left + 1
    ans = Math.max(ans, right - left + 1);
  });
  return ans;
}

2 方法2:滑动窗口 + Map:注意 这种方法实际提交会超时

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)
ts 复制代码
function longestSubarray(nums: number[], limit: number): number {
  let left = 0, ans = 0;
  // Map<元素值, 出现次数>
  const record = new Map();

  // 固定右边界扩展策略,逐步扩大窗口,当窗口不满足条件时再收缩左边界
  for (let right = 0; right < nums.length; right++) {
    const x = nums[right];
    record.set(x, (record.get(x) ?? 0) + 1);
    // 获取窗口内的最值
    const min = Math.min(...record.keys());
    const max = Math.max(...record.keys());
    // 如果窗口内的最值差大于limit,则收缩左边界
    if (max - min > limit) {
      // 获取将要移出窗口的元素, 更新它的出现次数
      const lVal = nums[left];
      const freq = record.get(lVal) - 1;
      freq === 0 ? record.delete(lVal) : record.set(lVal, freq);
      // 收缩左边界
      left++;
    }
    // 更新最长长度
    ans = Math.max(ans, right - left + 1);
  }
  return ans;
}

Q54- code895- 最大频率栈

实现思路

1 方法1:buckets + Map

1.1 分析题目要求

  • 需要记录频率
  • 频率相同时要取最近的
  • 最难的是 "频率相同时取最近的" 这个要求
  • 这暗示了我们需要某种方式保存元素的 "时序信息"

1.2 常见数据结构分析

  • 堆?可以,但处理相同频率时需要额外信息
  • 栈?天然保持时序,但不好处理频率
  • Map?可以记录频率,但不保持时序

1.3 关键突破点

  • 如果能把 "相同频率" 的元素放在一起
  • 又能保持它们的入栈顺序
  • 那不就自然解决了问题吗?

1.4 灵感来源

  • 这类题目的一个常见技巧是 "分组"
  • 比如桶排序就是按数值分组
  • 这里我们可以按频率分组!

这种解法的巧妙之处在于:

  • 把频率作为分组依据,自然解决了"找最高频率"的问题
  • 每个频率组用数组存储,自然保持了入栈顺序

参考文档

01- 方法1参考文档

代码实现

1 方法1: buckets + Map

  • 时间复杂度:O(1)
  • 空间复杂度:O(n)
ts 复制代码
class FreqStack {
  // idx是 频率freq,val是 出现的元素栈
  private buckets: number[][] = [];
  // key是num, val是freq
  private freqs = new Map<number, number>();

  push(val: number): void {
    const freq = (this.freqs.get(val) ?? 0) + 1;
    this.freqs.set(val, freq);
    // 如果当前频率超过现有栈数,创建新栈
    (this.buckets[freq] ??= []).push(val);
  }

  pop(): number {
    const val = this.buckets.at(-1).pop();
    // 如果最高频率栈为空,移除该栈
    if (!this.buckets.at(-1).length) this.buckets.pop();
    // 更新频率
    const freq = this.freqs.get(val) - 1;
    freq ? this.freqs.set(val, freq) : this.freqs.delete(val);
    return val;
  }
}
相关推荐
3Katrina7 分钟前
《Stitch的使用指南以及AI新开发模式杂谈》
前端
无羡仙9 分钟前
按下回车后,网页是怎么“跳”出来的?
前端·node.js
喝拿铁写前端10 分钟前
Vue 实战:构建灵活可维护的菜单系统
前端·vue.js·设计模式
ZzMemory13 分钟前
一套通关CSS选择器,玩转元素定位
前端·css·面试
圆心角16 分钟前
小米面挂了
前端·面试
墨染点香16 分钟前
LeetCode 刷题【31. 下一个排列】
算法·leetcode·职场和发展
我的小月月17 分钟前
Vue移动端"回到顶部"组件深度解析:拖拽、动画与性能优化实践
前端
wrynhyxa18 分钟前
力扣热题100——子串
算法·leetcode·哈希算法
前端康师傅20 分钟前
你还在相信前端加密吗?前端密码加密安全指南
前端·安全
小白白一枚11124 分钟前
HTML5的新特性
前端·html·html5