2760. 最长奇偶子数组 : 抽丝剥茧,图解双指针做法正确性

题目描述

这是 LeetCode 上的 2698. 求一个整数的惩罚数 ,难度为 简单

Tag : 「双指针」、「滑动窗口」

给你一个下标从 <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 0 </math>0 开始的整数数组 nums 和一个整数 threshold

请你从 nums 的子数组中找出以下标 l 开头、下标 r 结尾 ( <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 < = l < = r < n u m s . l e n g t h 0 <= l <= r < nums.length </math>0<=l<=r<nums.length) 且满足以下条件的 最长子数组 :

  • nums[l] % 2 == 0
  • 对于范围 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ l , r − 1 ] [l, r - 1] </math>[l,r−1] 内的所有下标 inums[i] % 2 != nums[i + 1] % 2
  • 对于范围 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ l , r ] [l, r] </math>[l,r] 内的所有下标 inums[i] <= threshold

以整数形式返回满足题目要求的最长子数组的长度。

注意:子数组 是数组中的一个连续非空元素序列。

示例 1:

ini 复制代码
输入:nums = [3,2,5,4], threshold = 5

输出:3

解释:在这个示例中,我们选择从 l = 1 开始、到 r = 3 结束的子数组 => [2,5,4] ,满足上述条件。
因此,答案就是这个子数组的长度 3 。可以证明 3 是满足题目要求的最大长度。

示例 2:

ini 复制代码
输入:nums = [1,2], threshold = 2

输出:1

解释:
在这个示例中,我们选择从 l = 1 开始、到 r = 1 结束的子数组 => [2] 。
该子数组满足上述全部条件。可以证明 1 是满足题目要求的最大长度。

示例 3:

ini 复制代码
输入:nums = [2,3,4,5], threshold = 4

输出:3

解释:
在这个示例中,我们选择从 l = 0 开始、到 r = 2 结束的子数组 => [2,3,4] 。 
该子数组满足上述全部条件。
因此,答案就是这个子数组的长度 3 。可以证明 3 是满足题目要求的最大长度。

提示:

  • <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 < = n u m s . l e n g t h < = 100 1 <= nums.length <= 100 </math>1<=nums.length<=100
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 < = n u m s [ i ] < = 100 1 <= nums[i] <= 100 </math>1<=nums[i]<=100
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 < = t h r e s h o l d < = 100 1 <= threshold <= 100 </math>1<=threshold<=100

双指针

整体题意:找 nums 中的最长的子数组 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ l , r ] [l, r] </math>[l,r],对于任意 <math xmlns="http://www.w3.org/1998/Math/MathML"> n u m s [ i ] nums[i] </math>nums[i] 不超过 threshold,且从 <math xmlns="http://www.w3.org/1998/Math/MathML"> n u m s [ l ] nums[l] </math>nums[l] 开始按照「先偶后奇」顺序交替。

假设子数组的左端点为 i,且"最远的"合法右端点为 j,那么在 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ i , j ] [i, j] </math>[i,j] 之间的任意右端点 k,即使能够使得 <math xmlns="http://www.w3.org/1998/Math/MathML"> n u m s [ i . . . k ] nums[i...k] </math>nums[i...k] 合法,对统计答案而言,也是没有意义的,因为我们求的是最长。

基于此,我们容易想到:找到所有的合法左端点 i,并统计该合法左端点的最远右端点 j。跳过 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ i , j ] [i, j] </math>[i,j] 之间的点作为左端点的情况,直接从结束位置 j 开始找下一个合法左端点。

该做法可将朴素的 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n 2 ) O(n^2) </math>O(n2) 做法优化至 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n)。

但,这做法为什么是正确的?

我们只考虑了 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ i , j ] [i, j] </math>[i,j] 中间点作为右端点的情况,那作为左端点呢?为什么跳过 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ i , j ] [i, j] </math>[i,j] 之间的 <math xmlns="http://www.w3.org/1998/Math/MathML"> k k </math>k 作为左端点,正确性也不受影响?我们不是漏到了某些方案吗?

答案:是漏掉了,但也只是漏掉了那些必不可能是最长子数组的方案

具体的,我们重新整理上述的「双指针」做法:

  • 从前往后扫描 nums,变量 i 作为当前子数组左端点,首先确保 i 的合法性(跳过不满足 nums[i] % 2 = 0nums[i] <= threshold 的位置)
  • 随后在固定左端点 i 前提下,找最远的(第一个不满足要求的)右端点 j(值不超过 threshold,且奇偶性与前值交替)
  • 得到当前连续段长度 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ i , j − 1 ] [i, j - 1] </math>[i,j−1],更新 ans,从当前结束位置 j 开始,重复上述过程,直到处理完 nums

Java 代码

Java 复制代码
class Solution {
    public int longestAlternatingSubarray(int[] nums, int threshold) {
        int n = nums.length, ans = 0, i = 0;
        while (i < n) {
            if ((nums[i] % 2 != 0 || nums[i] > threshold) && ++i >= 0) continue;
            int j = i + 1, cur = nums[i] % 2;
            while (j < n) {
                if (nums[j] > threshold || nums[j] % 2 == cur) break;
                cur = nums[j++] % 2;
            }
            ans = Math.max(ans, j - i);
            i = j;
        }
        return ans;
    }
}

C++ 代码:

C++ 复制代码
class Solution {
public:
    int longestAlternatingSubarray(vector<int>& nums, int threshold) {
        int n = nums.size(), ans = 0, i = 0;
        while (i < n) {
            if ((nums[i] % 2 != 0 || nums[i] > threshold) && ++i >= 0) continue;
            int j = i + 1, cur = nums[i] % 2;
            while (j < n) {
                if (nums[j] > threshold || nums[j] % 2 == cur) break;
                cur = nums[j++] % 2;
            }
            ans = max(ans, j - i);
            i = j;
        }
        return ans;
    }
};

Python 代码:

Python 复制代码
class Solution:
    def longestAlternatingSubarray(self, nums: List[int], threshold: int) -> int:
        n, ans, i = len(nums), 0, 0
        while i < n:
            if nums[i] % 2 != 0 or nums[i] > threshold:
                i += 1
                continue
            j, cur = i + 1, nums[i] % 2
            while j < n:
                if nums[j] > threshold or nums[j] % 2 == cur: break
                cur, j = nums[j] % 2, j + 1
            ans = max(ans, j - i)
            i = j
        return ans

TypeScript 代码:

TypeScript 复制代码
function longestAlternatingSubarray(nums: number[], threshold: number): number {
    let n = nums.length, ans = 0, i = 0
    while (i < n) {
        if ((nums[i] % 2 != 0 || nums[i] > threshold) && ++i >= 0) continue;
        let j = i + 1, cur = nums[i] % 2;
        while (j < n) {
            if (nums[j] > threshold || nums[j] % 2 == cur) break;
            cur = nums[j++] % 2;
        }
        ans = Math.max(ans, j - i);
        i = j;
    }
    return ans;
};
  • 时间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n)
  • 空间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1)

最后

这是我们「刷穿 LeetCode」系列文章的第 No.2760 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour...

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉

相关推荐
鹧鸪yy7 分钟前
认识Node.js及其与 Nginx 前端项目区别
前端·nginx·node.js
张同学的IT技术日记7 分钟前
必看!用示例代码学 C++ 基础入门,快速掌握基础知识,高效提升编程能力
后端
跟橙姐学代码8 分钟前
学Python必须迈过的一道坎:类和对象到底是什么鬼?
前端·python
汪子熙10 分钟前
浏览器里出现 .angular/cache/19.2.6/abap_test/vite/deps 路径究竟说明了什么
前端·javascript·面试
Benzenene!11 分钟前
让Chrome信任自签名证书
前端·chrome
yangholmes888811 分钟前
如何在 web 应用中使用 GDAL (二)
前端·webassembly
jacy13 分钟前
图片大图预览就该这样做
前端
林太白15 分钟前
Nuxt3 功能篇
前端·javascript·后端
YuJie17 分钟前
webSocket Manager
前端·javascript
Mapmost32 分钟前
Mapmost SDK for UE5 内核升级,三维场景渲染效果飙升!
前端