每日一题:巧用单调栈求解后缀极大位置

日期: 2025.10.30 标签: 算法, 数据结构, 单调栈, XOR

1. 问题背景

我们今天讨论的这类问题,通常需要我们找出一系列“后缀极大位置”。

后缀极大位置:指的是这样一个位置,它右边的所有数都比它小。

题目可能要求我们动态维护这些位置,并计算它们的某种总和,比如“异或和”。

2. 复杂度分析与约束

  • 问题规模 (N): 通常在 级别。
  • 时间限制: 计算机一秒钟大约能进行 次运算。
  • 推论: 这意味着我们需要一个时间复杂度为 的算法。 的暴力解法是无法通过的。

3. 核心思路:如何想到单调栈?

让我们来分析一下“后缀极大位置”的性质。

  1. 关键性质: 如果我们从右到左(或者在动态添加元素的场景下,从左到右)观察这些“后缀极大位置”,会发现它们构成了一个单调递减的序列
  2. 思维飞跃: 一个只在“一端”进出,并且内部元素保持单调性的数据结构——这不就是单调栈吗?

我们可以维护一个单调递减栈,栈中存储所有“后缀极大位置”的索引(或值,取决于题目要求)。

4. 算法流程

假设我们正从左到右处理一个数组 nums,并动态维护一个栈 stack 来存储所有后缀极大位置的索引:

  1. 遍历数组,处理当前元素 nums[i](索引为 i)。
  2. 维护单调性:
    • 当栈不为空,且栈顶索引 j 对应的元素 nums[j] 小于或等于 nums[i] 时(nums[j] <= nums[i]):
    • 这意味着 nums[j] 不再是后缀极大位置了,因为它的右边出现了一个比它更大(或相等)的数 nums[i]
    • 因此,我们将 j 从栈顶弹出
    • 重复这个过程,直到栈顶元素大于 nums[i] 或栈为空。
  3. 加入新元素:
    • 完成上述操作后,将当前索引 i 压入栈中
    • 此时,i 成为了一个新的“后缀极大位置”的候选者,而栈中在它之下的所有元素都比它大。

5. 巧妙处理“异或和”

如果题目要求我们动态维护栈中所有位置(索引)的异或和 current_xor_sum,我们要如何高效地更新呢?

这里要用到一个关键的数学技巧。

数学技巧: 异或运算的自反性 一个数异或同一个数两次,结果等于它本身。

利用这个性质:

  • 当一个索引 j 出栈时: 这意味着我们要从异或和中“移除”j。我们只需要执行 current_xor_sum = current_xor_sum ^ j
  • 当一个索引 i 入栈时: 这意味着我们要向异或和中“加入”i。我们执行 current_xor_sum = current_xor_sum ^ i

通过这种方式,current_xor_sum 始终精确地反映了当前栈中所有索引的异或和。

6. 最终时间复杂度分析

  • 我们遍历整个数组 nums,这部分是
  • 在遍历过程中,每个元素的索引最多入栈一次,也最多出栈一次
  • 因此,所有栈操作(push 和 pop)的总次数加起来也是

总时间复杂度: (均摊),完美符合 的要求。

简而言之:

时间复杂度分析:

计算机一秒钟运算

如何想到单调栈:读一遍题目每个后缀极大位置指的是这个位置右边的所有数都比它小。然后题目里要求把所有的位置求异或和,我们想怎么能让每次算出的结果和之前有一些联系,对于每次询问,后缀极大位置一定是一个单调递减的数列,把他放到一个栈里面就是单调栈,然后如果下一个加入询问的数让这些位置都不满足条件了,那么就先判断这些位置是不是不行,然后再把这个数加入栈。

数学技巧:

异或一个数两回就相当于没有异或它:a^b^b=a