LeetCode 3201.找出有效子序列的最大长度 I:分类统计+贪心(一次遍历)

【LetMeFly】3201.找出有效子序列的最大长度 I:分类统计+贪心(一次遍历)

力扣题目链接:https://leetcode.cn/problems/find-the-maximum-length-of-valid-subsequence-i/

给你一个整数数组 nums

nums 的子序列 sub 的长度为 x ,如果其满足以下条件,则称其为 有效子序列

  • (sub[0] + sub[1]) % 2 == (sub[1] + sub[2]) % 2 == ... == (sub[x - 2] + sub[x - 1]) % 2

返回 nums最长的有效子序列 的长度。

一个 子序列 指的是从原数组中删除一些元素(也可以不删除任何元素),剩余元素保持原来顺序组成的新数组。

 

示例 1:

输入: nums = [1,2,3,4]

输出: 4

解释:

最长的有效子序列是 [1, 2, 3, 4]

示例 2:

输入: nums = [1,2,1,1,2,1,2]

输出: 6

解释:

最长的有效子序列是 [1, 2, 1, 2, 1, 2]

示例 3:

输入: nums = [1,3]

输出: 2

解释:

最长的有效子序列是 [1, 3]

 

提示:

  • 2 <= nums.length <= 2 * 105
  • 1 <= nums[i] <= 107

解题方法:分类统计

子序列如果是有效子序列(相邻两个元素之和 的奇偶性相同),只有以下三种可能:

  1. 全奇
  2. 全偶
  3. 一奇一偶交替排列

怎么一次遍历分别统计出这三种情况最长有效子序列呢?

  • 使用一个变量 o d d odd odd,统计数组钟奇数元素的个数(那么偶数元素的个数就是 l e n ( n u m s ) − o d d len(nums) - odd len(nums)odd);
  • 使用一个变量 l a s t last last,代表上一个选中元素是奇是偶;并使用一个元素 c n t cnt cnt统计选中了多少个奇偶交替排列的元素。

所谓贪心,体现在当方案为奇偶交替时,遇到和上一个元素奇偶不同的元素一定选(不选白不选),遍历结束则能得到奇偶交替方案的最长长度。

举个例子: 2 , 0 , 3 , 5 , 4 2, 0, 3, 5, 4 2,0,3,5,4这个数组,遍历时遇到2,选还是不选?

选,不选干嘛以奇数开头吗?没必要,以奇数开头不就少选了个偶数吗。

而一次遍历过程中顺便统计下奇数个数就很简单了。

  • 时间复杂度 O ( x ) O(x) O(x)
  • 空间复杂度 O ( 1 ) O(1) O(1)

AC代码

C++
/*
 * @Author: LetMeFly
 * @Date: 2025-07-16 13:16:29
 * @LastEditors: LetMeFly.xyz
 * @LastEditTime: 2025-07-16 13:19:44
 */
#if defined(_WIN32) || defined(__APPLE__)
#include "_[1,2]toVector.h"
#endif

class Solution {
public:
    int maximumLength(vector<int>& nums) {
        int ans = 0;
        int odd = 0;
        bool last = nums[0] % 2 ? false : true;
        for (int t : nums) {
            if (t % 2) {
                odd++;
                if (!last) {
                    last = true;
                    ans++;
                }
            } else {
                if (last) {
                    last = false;
                    ans++;
                }
            }
        }
        return max(ans, max(odd, int(nums.size()) - odd));
    }
};
Python
'''
Author: LetMeFly
Date: 2025-07-16 13:16:29
LastEditors: LetMeFly.xyz
LastEditTime: 2025-07-16 13:36:53
'''
from typing import List

class Solution:
    def maximumLength(self, nums: List[int]) -> int:
        ans = odd = 0
        last = False if nums[0] % 2 else True
        for t in nums:
            if t % 2:
                odd += 1
                if not last:
                    last = True
                    ans += 1
            else:
                if last:
                    last = False
                    ans += 1
        return max(ans, odd, len(nums) - odd)
Java
/*
 * @Author: LetMeFly
 * @Date: 2025-07-16 13:16:29
 * @LastEditors: LetMeFly.xyz
 * @LastEditTime: 2025-07-16 13:41:23
 */
class Solution {
    public int maximumLength(int[] nums) {
        int ans = 0;
        int odd = 0;
        boolean last = nums[0] % 2 == 0;
        for (int t : nums) {
            if (t % 2 == 0) {
                if (last) {
                    last = false;
                    ans++;
                }
            } else {
                odd++;
                if (!last) {
                    last = true;
                    ans++;
                }
            }
        }
        return Math.max(ans, Math.max(odd, nums.length - odd));
    }
}
Go
/*
 * @Author: LetMeFly
 * @Date: 2025-07-16 13:16:29
 * @LastEditors: LetMeFly.xyz
 * @LastEditTime: 2025-07-16 13:40:18
 */
package main

func maximumLength(nums []int) (ans int) {
    odd := 0
    last := nums[0] % 2 == 0
    for _, t := range nums {
        if t % 2 == 0 {
            if last {
                last = false
                ans++
            }
        } else {
            odd++
            if !last {
                last = true
                ans++
            }
        }
    }
    return max(ans, max(odd, len(nums) - odd))
}

同步发文于CSDN和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~

千篇源码题解已开源

<think>好的,用户现在想了解如何用Python实现“解法二:贪心+二分优化(O(n log n))”来解决最长递增子序列问题,特别是在处理二维数据(如长方形的长和宽)时的应用。用户之前已经询问过为什么在高度相等的情况下要按宽度降序排列,现在他们可能是在处理类似俄罗斯套娃信封或者需要二维排序的问题。 首先,我需要确定用户的需求。他们可能是在解决LeetCode上的某个问题,比如第354题“俄罗斯套娃信封问题”,这题需要将信封按长和宽排序后,转化为最长递增子序列问题。解法二中的排序预处理,按长升序、宽降序,这样可以保证在长相等时,宽降序,这样后续处理时不会出现长相同的情况,从而正确应用贪心+二分的优化方法。 接下来,用户希望具体的Python实现步骤。我需要先解释排序的逻辑,然后是如何维护tail数组,以及二分查找的过程。需要注意的是,在Python中可以使用bisect模块来简化二分查找的实现。 然后,我需要考虑用户可能的疑问点。例如,为什么排序时要按长升序、宽降序?这是因为当长度相同时,宽度较大的排在前面,这样在后续处理宽度时,不会出现相同长度的信封被错误地计算为递增的情况,从而保证每个长度只保留一个最小的宽度,避免重复。 另外,用户可能对tail数组的作用不太清楚,需要解释tail数组保存的是长度为i+1的子序列的最小末尾值,这样可以在后续的元素中更容易地形成更长的子序列。 在代码实现部分,需要展示如何导入bisect模块,如何排序,遍历宽度数组,以及如何处理每个元素。同时,要提到bisect.bisect_left的使用,以及替换或追加元素到tail数组的逻辑。 最后,可能需要给出一个具体的例子,比如用户提到的排序后的宽度数组,然后逐步演示tail数组的变化过程,这样用户能更直观地理解算法是如何工作的。 此外,还要考虑到用户可能对时间复杂度的疑问,解释为什么这种方法的时间复杂度是O(n log n),主要是因为排序的时间是O(n log n),而遍历和二分查找的时间也是O(n log n)。 总结来说,用户需要一个清晰的步骤解释和可运行的Python代码示例,同时希望理解背后的原理和每一步的作用。需要确保代码正确,并且解释到位,避免他们在实际应用中出现错误。</think>以下是针对**最长递增子序列(LIS)**问题的贪心+二分优化解法(时间复杂度 $O(n \log n)$)的逐步解释及Python实现,适用于二维排序场景(如俄罗斯套娃信封问题): --- ### **1. 问题背景** 在二维排序问题中(例如信封的长和宽),若需找到**严格递增的子序列**,需先将元素按特定规则排序,再转化为一维的LIS问题。例如: - **排序规则**:按长升序排列,若长相等则按宽降序排列(避免相同长的元素干扰后续计算)。 --- ### **2. 算法核心思想** 1. **排序预处理**:将元素按长升序、宽降序排列。 2. **贪心+二分维护递增序列**:维护一个数组 `tail`,`tail[i]` 表示长度为 `i+1` 的递增子序列的最小末尾值。 - 若当前元素大于 `tail` 末尾,直接追加。 - 否则,用二分查找替换 `tail` 中第一个不小于当前元素的值。 3. **结果**:`tail` 的长度即为最长递增子序列的长度。 --- ### **3. Python实现代码** ```python import bisect def max_envelopes(envelopes): # 1. 排序预处理:长升序,长相同则宽降序 envelopes.sort(key=lambda x: (x[0], -x[1])) # 2. 提取宽度序列,转化为LIS问题 widths = [env[1] for env in envelopes] # 3. 贪心+二分维护递增序列 tail = [] for w in widths: idx = bisect.bisect_left(tail, w) # 找到第一个 >=w 的位置 if idx == len(tail): tail.append(w) else: tail[idx] = w # 替换为更小的w,优化后续可能性 return len(tail) ``` --- ### **4. 逐步解析** #### **步骤1:排序预处理** ```python envelopes.sort(key=lambda x: (x[0], -x[1])) ``` - **长升序**:保证后续只需关注宽度的递增。 - **长相同则宽降序**:避免相同长的元素形成无效递增(例如 `(5,4)` 和 `(5,6)`,若宽升序排列会导致误判)。 #### **步骤2:提取宽度序列** ```python widths = [env[1] for env in envelopes] ``` - 排序后,问题转化为在宽度序列 `widths` 中找**最长递增子序列**。 #### **步骤3:贪心维护递增序列** ```python tail = [] for w in widths: idx = bisect.bisect_left(tail, w) if idx == len(tail): tail.append(w) else: tail[idx] = w ``` - **bisect_left**:找到 `tail` 中第一个 `>=w` 的位置。 - **追加或替换**: - 若 `w` 比 `tail` 末尾大,直接追加(形成更长的子序列)。 - 否则,替换 `tail` 中第一个 `>=w` 的值,使 `tail` 的末尾尽可能小,为后续留出空间。 --- ### **5. 示例演示** 假设输入 `envelopes = [[5,4],[6,4],[6,7],[2,3]]`: 1. **排序后**:`[[2,3], [5,4], [6,7], [6,4]]`(长升序,长相同则宽降序)。 2. **提取宽度**:`widths = [3, 4, 7, 4]`。 3. **维护 `tail`**: - `w=3` → `tail = [3]` - `w=4` → 追加 → `tail = [3,4]` - `w=7` → 追加 → `tail = [3,4,7]` - `w=4` → 替换 `tail[1]` → `tail = [3,4,7] → [3,4,7]` 4. **结果**:`tail` 长度为3,即最长递增子序列为 `[3,4,7]`。 --- ### **6. 时间复杂度** - **排序**:$O(n \log n)$ - **遍历+二分查找**:$O(n \log n)$ - **总复杂度**:$O(n \log n)$ --- ### **7. 应用场景** - **俄罗斯套娃信封问题**:需同时满足长和宽递增。 - **任务调度**:按特定维度排序后找最长可行序列。 - **动态规划优化**:将 $O(n^2)$ 的DP解法优化为 $O(n \log n)$。 --- ### **总结** 通过排序预处理将二维问题转化为一维LIS,再利用贪心+二分的策略维护递增序列,既保证了时间复杂度最优,又简化了问题逻辑。此方法在LeetCode算法题中广泛适用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tisfy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值