当前位置: 首页 > news >正文

力扣每日一题--2025.7.16

📚 力扣每日一题–2025.7.16

📚 3201. 找出有效子序列的最大长度 I(中等)

今天我们要解决的是力扣上的第 3201 题——找出有效子序列的最大长度 I。这道题虽然标记为中等难度,但只要掌握了正确的思路,就能轻松解决!

📝 题目描述

在这里插入图片描述
在这里插入图片描述

🤔 思路分析

核心需求推导过程 📝

题目要求子序列中所有相邻元素之和的奇偶性必须相同,即:

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

这个条件可以从两个角度理解:

  1. 数学本质:所有相邻元素对的和具有相同的奇偶性
  2. 序列特性:子序列中的元素必须形成一种特定的奇偶性模式

通过进一步推理,我们可以得出两种基本的有效序列模式:

模式一:全同奇偶性序列

  • 当要求相邻元素之和为偶数时,所有元素必须具有相同的奇偶性
  • 证明:若 a+b 为偶数且 b+c 为偶数,则 a 和 b 奇偶性相同,b 和 c 奇偶性相同,因此 a 和 c 奇偶性相同,以此类推

模式二:交替奇偶性序列

  • 当要求相邻元素之和为奇数时,元素必须交替出现奇偶性
  • 证明:若 a+b 为奇数且 b+c 为奇数,则 a 和 b 奇偶性不同,b 和 c 奇偶性不同,因此 a 和 c 奇偶性相同,形成交替模式

让我们通过具体示例验证这些模式:

  • 全奇序列 [1,3,5,7]:所有相邻和都为偶数 (4,8,12),符合模式一
  • 奇偶交替序列 [1,2,3,4]:所有相邻和都为奇数 (3,5,7),符合模式二
  • 混合序列 [1,2,2,3]:相邻和为 3(奇)、4(偶)、5(奇),不符合任何模式

所以,有效子序列有两种基本类型:

  1. 所有相邻元素之和都为偶数
  2. 所有相邻元素之和都为奇数

我们需要分别找出这两种类型的最长子序列,然后取较大值作为答案。

📑 解法探讨

方法一:暴力枚举(理解问题本质)

最直观的思路是枚举所有可能的子序列,检查它们是否有效,并记录最长有效子序列的长度。

这个官方的测试用例可以通过,但是提交的时候会超时,我这边只是给大家提供一个思路方法。

/*** 方法一:暴力枚举法* 思路:枚举所有可能的子序列起点和两种类型的有效子序列,*       构建并检查每个子序列的有效性,记录最长有效子序列长度* 注意:此方法仅用于理解问题本质,实际提交可能会超时*/
public class Solution {/*** 找出最长有效子序列的长度* @param nums 输入的整数数组* @return 最长有效子序列的长度*/public int maximumLength(int[] nums) {int n = nums.length;// 边界情况处理:空数组或单元素数组,直接返回数组长度if (n <= 1) return n;// 最小有效子序列长度为1(单个元素本身就是有效子序列)int maxLen = 1;// 枚举所有可能的子序列起点for (int i = 0; i < n; i++) {// 尝试两种类型的有效子序列// type=0: 相邻元素之和为偶数// type=1: 相邻元素之和为奇数for (int type = 0; type <= 1; type++) {int len = 1;          // 当前子序列长度,至少包含起点元素int last = nums[i];   // 记录子序列最后一个元素// 从起点后一个元素开始构建子序列for (int j = i + 1; j < n; j++) {// 计算当前元素与子序列最后一个元素之和的奇偶性int sumMod = (last + nums[j]) % 2;// 如果符合当前类型要求,则加入子序列if (sumMod == type) {len++;last = nums[j]; // 更新子序列最后一个元素}}// 更新最长有效子序列长度maxLen = Math.max(maxLen, len);}}return maxLen;}
}

复杂度分析

  • 时间复杂度:O(n²),其中 n 是数组长度
  • 空间复杂度:O(1),只使用了常数额外空间
方法二:贪心算法(最优解法)

通过观察,我们可以发现两种类型的有效子序列具有明显的模式:

  1. 类型 0(相邻元素之和为偶数):所有元素必须具有相同的奇偶性

    • 全是偶数,或全是奇数
    • 最长长度 = max(偶数元素个数, 奇数元素个数)
  2. 类型 1(相邻元素之和为奇数):元素必须交替出现奇偶性

    • 奇偶奇偶…或偶奇偶奇…
    • 最长长度取决于两种模式中较长的一个

基于这些观察,我们可以设计出 O(n)时间复杂度的贪心算法:

/*** 方法二:贪心算法(最优解法)* 思路:通过分析有效子序列的两种模式,分别计算其最大长度*       类型0(相邻和为偶数):所有元素奇偶性相同,取较多的那种奇偶性元素个数*       类型1(相邻和为奇数):元素奇偶性交替出现,计算两种起始模式的最大长度* 时间复杂度:O(n),空间复杂度:O(1)*/
public class Solution {/*** 找出最长有效子序列的长度* @param nums 输入的整数数组* @return 最长有效子序列的长度*/public int maximumLength(int[] nums) {int n = nums.length;// 边界情况处理:空数组或单元素数组,直接返回数组长度if (n <= 1) return n;// 统计数组中奇数和偶数的个数int oddCount = 0, evenCount = 0;for (int num : nums) {if (num % 2 == 0) evenCount++;else oddCount++;}// 类型0的最大长度:所有元素奇偶性相同,取较多的那种int type0Max = Math.max(oddCount, evenCount);// 如果只有一种奇偶性,无法形成类型1的子序列(需要交替出现)if (oddCount == 0 || evenCount == 0) {return type0Max;}// 计算类型1的两种模式的最大长度// 模式1:以奇数开始的交替序列(奇-偶-奇-偶...)int type1Max1 = calculateAlternatingLength(nums, true);// 模式2:以偶数开始的交替序列(偶-奇-偶-奇...)int type1Max2 = calculateAlternatingLength(nums, false);// 取两种模式中的较大值int type1Max = Math.max(type1Max1, type1Max2);// 返回两种类型中的最大值return Math.max(type0Max, type1Max);}/*** 计算交替奇偶性序列的长度* @param nums 输入的整数数组* @param startWithOdd 是否以奇数开始* @return 交替序列的长度*/private int calculateAlternatingLength(int[] nums, boolean startWithOdd) {int length = 0;               // 序列长度boolean expectedOdd = startWithOdd; // 期望的下一个元素的奇偶性// 遍历数组,构建交替序列for (int num : nums) {boolean isOdd = num % 2 == 1; // 当前元素是否为奇数// 如果当前元素符合期望的奇偶性,则加入序列if (isOdd == expectedOdd) {length++;expectedOdd = !expectedOdd; // 切换期望的奇偶性}}return length;}
}

复杂度分析

  • 时间复杂度:O(n),只需遍历数组几次
  • 空间复杂度:O(1),只使用了常数额外空间
方法三:动态规划(更通用的解决方案)

这个也超时了,但是方法思路应该是这样的,之后我再想办法去优化一下。

我们也可以使用动态规划来解决这个问题,定义两个状态:

  • dp0[i]:以第 i 个元素结尾,且相邻元素之和为偶数的最长有效子序列长度
  • dp1[i]:以第 i 个元素结尾,且相邻元素之和为奇数的最长有效子序列长度
/*** 方法三:动态规划(更通用的解决方案)* 思路:定义两个状态数组,分别记录以每个位置结尾的两种类型子序列的最大长度*       dp[i][0]:以第i个元素结尾,相邻和为偶数的最长子序列长度*       dp[i][1]:以第i个元素结尾,相邻和为奇数的最长子序列长度* 时间复杂度:O(n²),空间复杂度:O(n)*/
public class Solution {/*** 找出最长有效子序列的长度* @param nums 输入的整数数组* @return 最长有效子序列的长度*/public int maximumLength(int[] nums) {int n = nums.length;// 边界情况处理:空数组或单元素数组,直接返回数组长度if (n <= 1) return n;// 动态规划数组定义:// dp[i][0]:以第i个元素结尾,且相邻元素之和为偶数的最长有效子序列长度// dp[i][1]:以第i个元素结尾,且相邻元素之和为奇数的最长有效子序列长度int[][] dp = new int[n][2];// 初始化:第一个元素本身就是长度为1的有效子序列dp[0][0] = 1; // 以第一个元素结尾的类型0子序列dp[0][1] = 1; // 以第一个元素结尾的类型1子序列int maxLen = 1; // 记录最长有效子序列长度// 从第二个元素开始计算for (int i = 1; i < n; i++) {// 默认情况下,子序列只包含当前元素,长度为1dp[i][0] = 1;dp[i][1] = 1;// 检查前面所有元素,看是否可以形成更长的有效子序列for (int j = 0; j < i; j++) {// 计算nums[j]和nums[i]之和的奇偶性int sumMod = (nums[j] + nums[i]) % 2;// 如果前面元素j和当前元素i的和的奇偶性为sumMod// 则可以将i添加到以j结尾的sumMod类型子序列后面if (dp[j][sumMod] + 1 > dp[i][sumMod]) {dp[i][sumMod] = dp[j][sumMod] + 1;}}// 更新最长有效子序列长度maxLen = Math.max(maxLen, Math.max(dp[i][0], dp[i][1]));}return maxLen;}
}

复杂度分析

  • 时间复杂度:O(n²),其中 n 是数组长度
  • 空间复杂度:O(n),需要存储 dp 数组

🚀 优化后的贪心算法

我们可以进一步优化贪心算法,只需要一次遍历就能计算出两种类型 1 子序列的最大长度:

/*** 方法四:优化后的贪心算法* 思路:在基础贪心算法的基础上,通过一次遍历同时计算两种类型1子序列的长度*       减少了遍历次数,进一步优化了性能* 时间复杂度:O(n),空间复杂度:O(1)*/
public class Solution {/*** 找出最长有效子序列的长度* @param nums 输入的整数数组* @return 最长有效子序列的长度*/public int maximumLength(int[] nums) {int n = nums.length;// 边界情况处理:空数组或单元素数组,直接返回数组长度if (n <= 1) return n;// 统计数组中奇数和偶数的个数int oddCount = 0, evenCount = 0;for (int num : nums) {if (num % 2 == 0) evenCount++;else oddCount++;}// 类型0的最大长度:所有元素奇偶性相同,取较多的那种int type0Max = Math.max(oddCount, evenCount);// 如果只有一种奇偶性,无法形成类型1的子序列(需要交替出现)if (oddCount == 0 || evenCount == 0) {return type0Max;}// 一次遍历同时计算两种类型1子序列的最大长度int type1OddStart = 0;  // 以奇数开始的交替序列长度(奇-偶-奇-偶...)int type1EvenStart = 0; // 以偶数开始的交替序列长度(偶-奇-偶-奇...)boolean expectOddForOddStart = true;  // 奇开始模式下期望的下一个奇偶性boolean expectOddForEvenStart = false; // 偶开始模式下期望的下一个奇偶性// 遍历数组,同时构建两种交替模式的子序列for (int num : nums) {boolean isOdd = num % 2 == 1; // 当前元素是否为奇数// 处理奇-偶-奇-偶...模式if (isOdd == expectOddForOddStart) {type1OddStart++;expectOddForOddStart = !expectOddForOddStart; // 切换期望的奇偶性}// 处理偶-奇-偶-奇...模式if (isOdd == expectOddForEvenStart) {type1EvenStart++;expectOddForEvenStart = !expectOddForEvenStart; // 切换期望的奇偶性}}// 类型1的最大长度为两种模式中的较大值int type1Max = Math.max(type1OddStart, type1EvenStart);// 返回两种类型中的最大值return Math.max(type0Max, type1Max);}
}

复杂度分析

  • 时间复杂度:O(n),只需一次遍历
  • 空间复杂度:O(1),只使用了常数额外空间

🔍 算法执行流程可视化

开始
统计奇数和偶数个数
计算类型0最大长度
是否同时有奇数和偶数?
返回类型0最大长度
计算两种类型1子序列长度
比较类型0和类型1的最大值
返回最终结果

📊 示例分析

让我们通过几个示例来理解算法的工作原理:

示例 1:nums = [1,2,3,4,5]

  • 奇数: 1,3,5 (count=3)
  • 偶数: 2,4 (count=2)
  • 类型 0 最大长度: max(3,2)=3
  • 类型 1(奇-偶-奇-偶…): 1,2,3,4,5 → 长度 5
  • 类型 1(偶-奇-偶-奇…): 2,3,4,5 → 长度 4
  • 最终结果: max(3,5)=5

示例 2:nums = [1,3,5,7]

  • 奇数: 1,3,5,7 (count=4)
  • 偶数: 0 (count=0)
  • 无法形成类型 1 子序列
  • 最终结果: 4

示例 3:nums = [1,2,2,2,2]

  • 奇数: 1 (count=1)
  • 偶数: 2,2,2,2 (count=4)
  • 类型 0 最大长度: max(1,4)=4
  • 类型 1(奇-偶-奇-偶…): 1,2 → 长度 2
  • 类型 1(偶-奇-偶-奇…): 2 → 长度 1
  • 最终结果: max(4,2)=4

💡 拓展思考

问题变体
  1. 如果要求子序列中相邻元素之和的余数等于某个特定值 k(而不只是所有余数相等),该如何解决?(这个是紧挨着的 力扣3202 题)
  2. 如果允许子序列中有一个"错误"(即有一处相邻元素之和的余数与其他不同),最长有效子序列的长度会是多少?
实际应用

这个问题在信号处理和模式识别中有实际应用。例如,在分析时间序列数据时,我们可能需要找到具有特定模式的最长子序列。

算法选择策略
  • 对于小规模数据,任何算法都能胜任
  • 对于大规模数据,贪心算法是最佳选择,因为它具有 O(n)时间复杂度
  • 动态规划方法虽然复杂度较高,但具有更好的通用性,可以解决更复杂的变体问题

📝 总结

本题考察了对子序列概念的理解以及贪心算法的应用。通过分析相邻元素之和的奇偶性规律,我们发现有效子序列有两种基本类型,并分别计算了它们的最大长度。

贪心算法是解决本题的最优选择,它基于以下关键洞察:

  1. 类型 0 子序列要求所有元素具有相同的奇偶性
  2. 类型 1 子序列要求元素的奇偶性交替出现

通过计算这两种类型的最大长度并取较大值,我们得到了问题的解。

希望今天的讲解能帮助你更好地理解这个问题和贪心算法的应用!如果你有任何疑问或想法,欢迎在评论区留言讨论。明天见!👋

http://www.lryc.cn/news/589803.html

相关文章:

  • MAC 苹果版Adobe Photoshop 2019下载及保姆级安装教程!!
  • 第六章 OBProxy 路由与使用运维
  • 【基于PaddlePaddle训练的车牌识别系统】
  • http协议学习-1
  • vue的provide和inject
  • 基于 Docker 环境的 JupyterHub 详细部署手册
  • 论文导读--PQ3D:通过分段级分组实现多模态特征融合和 MTU3D:在线查询表示学习与动态空间记忆
  • cell2location复现
  • xss-labs练习
  • Android-EDLA【CTS】CtsTetheringTest存在fail
  • 探究Netty 4.2.x版本
  • 动态规划题解——分割等和子集【LeetCode】
  • Spring Boot 整合 Nacos 实战教程:服务注册发现与配置中心详解
  • docker的搭建
  • 导入无人机航拍屋顶,10分钟智能铺设光伏板
  • RICE-YOLO:基于改进YOLOv5的无人机稻穗检测新方法
  • 分布式缓存击穿以及本地击穿解决方案
  • 【tower】Rust tower库原理详解以及axum限流实战
  • 硅基计划2.0 学习总结 玖 图书管理系统 初版
  • 推荐《Python 编程:从入门到实践》之Python编程的基础知识
  • FastStone Capture (屏幕截图)v10.9 汉化版
  • 本地部署分布式系统服务发现与配置管理Consul,并实现无公网IP简单操作让外部访问
  • FATFS文件系统原理及其移植详解
  • Elasticsearch+Logstash+Kibana部署
  • iptables的配置与使用
  • Image 和 IMU 时间戳同步
  • 从零开发足球比分APP:REST API与WebSocket的完美搭配
  • MyBatis 之分页四式传参与聚合、主键操作全解
  • 现代人工智能综合分类:大模型时代的架构、模态与生态系统
  • 【电脑】显示器的基础知识