力扣2760 C++滑动窗口解法

问题分析

题目要求:给定一个整数数组 nums 和一个整数 threshold,寻找最长的奇偶子数组。子数组需满足以下条件:

  1. 子数组的第一个元素是偶数
  2. 子数组中的元素奇偶性交替 (即 nums[i] % 2 != nums[i+1] % 2)。
  3. 子数组中的每个元素 都不大于 threshold

核心约束在于,子数组必须同时满足奇偶性交替元素值上限两个条件。一旦某个元素不满足任一条件,当前子数组的连续性即被破坏,需要从新的位置开始寻找。

算法设计

1. 滑动窗口 (双指针)

这是最直观且高效的解法。使用两个指针 leftright 来定义当前考察的子数组窗口 [left, right]。我们不断尝试扩展右边界 right,并在每次扩展后检查新加入的元素 nums[right] 是否破坏了子数组的合法性。如果破坏,则收缩左边界 leftright 的位置,并重新开始构建窗口。

关键点

  • 合法性检查 :对于窗口 [left, right],需要检查 nums[right] 是否满足:
    1. nums[right] <= threshold
    2. 如果 right == left,则它是窗口的第一个元素,必须是偶数。
    3. 如果 right > left,则 nums[right] 的奇偶性必须与 nums[right-1] 相反。
  • 窗口维护 :当 nums[right] 合法时,扩展窗口 (right++);不合法时,将窗口重置 (left = right, right = left),但需注意如果 nums[right] 本身不满足条件1,则 leftright 都应跳到下一位 (left = right + 1, right = left)。

2. 模拟 (一次遍历)

滑动窗口的变体,可以简化为一次遍历。我们维护一个当前有效子数组的长度 currentLength 和全局最大长度 maxLength。遍历数组,根据当前元素 nums[i] 与上一个元素 nums[i-1] 的关系以及阈值条件,决定是延续当前子数组还是开始一个新的子数组。

状态判断逻辑

  1. 如果 nums[i] > threshold,当前元素绝对非法,currentLength 重置为0。
  2. 否则,如果 currentLength == 0,说明我们正在尝试开始一个新的子数组。此时需要 nums[i] % 2 == 0 才能成功开始,currentLength = 1;否则保持为0。
  3. 否则 (currentLength > 0),说明我们处于一个有效的子数组中。此时需要检查奇偶交替条件:nums[i] % 2 != nums[i-1] % 2。如果满足,currentLength++;否则,说明奇偶性没有交替,但 nums[i] 本身可能可以作为下一个 子数组的起点(如果它是偶数),因此 currentLength 应重置为 (nums[i] % 2 == 0) ? 1 : 0

C++ 代码实现

方案一:滑动窗口 (双指针)

cpp 复制代码
class Solution {
public:
    int longestAlternatingSubarray(vector<int>& nums, int threshold) {
        int n = nums.size();
        int maxLen = 0;
        int left = 0, right = 0;

        while (right < n) {
            // 条件1:元素值不超过阈值
            if (nums[right] > threshold) {
                // 当前元素非法,左右指针都跳到下一个位置
                left = right + 1;
                right = left;
                continue;
            }

            // 条件2 & 3:奇偶性检查
            if (right == left) {
                // 子数组第一个元素必须是偶数
                if (nums[right] % 2 != 0) {
                    left = right + 1;
                    right = left;
                    continue;
                }
            } else {
                // 后续元素必须奇偶交替
                if (nums[right] % 2 == nums[right - 1] % 2) {
                    // 不满足交替,从right开始新的窗口
                    left = right;
                    // right指针不动,下一轮循环会以right为left重新判断
                    continue;
                }
            }

            // 当前窗口 [left, right] 合法,更新最大长度
            maxLen = max(maxLen, right - left + 1);
            right++; // 尝试扩展右边界
        }

        return maxLen;
    }
};

代码解析

  • leftright 指针初始化为0。
  • 外层 while 循环控制右指针 right 遍历数组。
  • 内部分三步检查合法性:
    1. 首先检查阈值条件 nums[right] > threshold。如果违反,则 leftright 都移动到 right+1,彻底跳过这个非法元素 。
    2. 然后检查是否是窗口起点 (right == left)。如果是,则要求 nums[right] 为偶数,否则重置窗口 。
    3. 最后检查奇偶交替性 (nums[right] % 2 == nums[right-1] % 2)。如果相同,说明交替性被破坏,将 left 移动到 right,准备以当前位置为起点构建新窗口 。
  • 只有当所有条件都满足时,才计算当前窗口长度并更新 maxLen,然后 right++ 继续扩展。

方案二:模拟 (一次遍历)

cpp 复制代码
class Solution {
public:
    int longestAlternatingSubarray(vector<int>& nums, int threshold) {
        int maxLen = 0;
        int currentLen = 0;
        int n = nums.size();

        for (int i = 0; i < n; ++i) {
            if (nums[i] > threshold) {
                // 违反阈值条件,当前子数组终结
                currentLen = 0;
            } else if (currentLen == 0) {
                // 尝试开始一个新的子数组,首元素必须为偶数
                currentLen = (nums[i] % 2 == 0) ? 1 : 0;
            } else {
                // 已在子数组中,检查奇偶是否交替
                if (nums[i] % 2 != nums[i - 1] % 2) {
                    currentLen++;
                } else {
                    // 没有交替,当前元素可能作为新子数组的起点
                    currentLen = (nums[i] % 2 == 0) ? 1 : 0;
                }
            }
            maxLen = max(maxLen, currentLen);
        }
        return maxLen;
    }
};

代码解析

  • currentLen 记录以当前位置 i 结尾的、满足条件的最长子数组长度。
  • maxLen 记录全局最大值。
  • 遍历中的三个分支对应三种状态:
    1. 元素值超标 :直接清零 currentLen,因为任何包含该元素的子数组都不合法 。
    2. 当前无有效子数组 (currentLen == 0):尝试以 nums[i] 作为新起点。只有当它是偶数时,才能成功开启一个长度为1的子数组 。
    3. 已有有效子数组 (currentLen > 0):检查奇偶交替性。如果交替,长度加1;如果不交替,说明连续性中断。但 nums[i] 本身如果为偶数,它可以立即作为下一个子数组的起点(长度为1),否则长度归零 。
  • 每次循环后更新 maxLen

复杂度与方案对比

特性 滑动窗口 (双指针) 模拟 (一次遍历)
时间复杂度 O(n),每个元素最多被访问两次 (左指针和右指针) 。 O(n),严格一次遍历。
空间复杂度 O(1),只使用了常数个额外变量。 O(1),只使用了常数个额外变量。
核心思想 显式地维护一个合法的窗口 [left, right],通过移动左右指针来动态调整窗口。 隐式地维护当前有效子数组的长度 currentLen,根据规则进行状态转移。
代码逻辑 指针移动的逻辑稍显复杂,需要仔细处理窗口重置的边界条件。 逻辑更简洁,类似于动态规划的状态机,容易理解和实现。
推荐度 直观展示了"窗口"概念,适合理解滑动窗口算法。 更优。代码更简洁,运行效率相同,是本题更常见的解法 。

示例演示

nums = [3, 2, 5, 4], threshold = 5 为例,演示模拟法的过程:

i nums[i] 条件判断 currentLen 变化 maxLen
0 3 `
3 > 5? 否。currentLen==0
3%2!=0,所以 currentLen=0`。 0 0
1 2 `
2 > 5? 否。currentLen==0
2%2==0,所以 currentLen=1`。 1 1
2 5 `
5 > 5? 否。currentLen>0。检查奇偶:
5%2=1,
2%2=0,交替成立,currentLen++`。 2 2
3 4 `
4 > 5? 否。currentLen>0。检查奇偶:
4%2=0,
5%2=1,交替成立,currentLen++`。 3 3

最终找到的最长子数组为 [2, 5, 4],长度为3 。


参考来源

相关推荐
IronMurphy1 小时前
【算法四十四】322. 零钱兑换
算法
Hesionberger1 小时前
LeetCode96: 不同的二叉搜索树(多解)
算法
_深海凉_1 小时前
LeetCode热题100-不同路径
算法·leetcode·职场和发展
ZPC82101 小时前
CPU 核心隔离 + 线程绑核 + 实时优先级 SCHED_FIFO
人工智能·算法·计算机视觉·机器人
ximu_polaris1 小时前
设计模式(C++)-行为型模式-访问者模式
c++·设计模式·访问者模式
andafaAPS1 小时前
安达发|aps自动排产排程排单软件:日化生产高效运转“数字魔法”
大数据·人工智能·算法·aps软件·安达发aps·aps自动排产排程排单软件
黎阳之光2 小时前
全域实景立体管控:数字孪生与视频孪生技术体系白皮书
大数据·人工智能·算法·安全·数字孪生
血玥珏2 小时前
血玥珏-多WAV/MP3混音合成小工具2.0.0.5
c++·音视频
Shadow(⊙o⊙)2 小时前
初识Qt+经典方式实现hello world!的交互
开发语言·c++·后端·qt·学习