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

动态规划经典题解:最长递增子序列 乘积最大子数组

目录

一、最长递增子序列(LIS)

题目描述

思路分析

1. 动态规划解法(基础版)

2. 优化解法(贪心 + 二分)

代码实现(Java)

二、乘积最大子数组

题目描述

思路分析

代码实现(Java)

三、两道题的核心对比与面试考点

四、总结


大家好!今天我们来拆解两道动态规划的经典中等难度题目:最长递增子序列乘积最大子数组。这两道题都是大厂面试里的高频考点,而且非常能体现动态规划的核心思想 —— 如何通过子问题的最优解,一步步推导出全局最优解。


一、最长递增子序列(LIS)

题目描述

给你一个整数数组nums,找到其中最长严格递增子序列的长度。子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]是数组[0,3,1,6,2,2,7]的子序列。

思路分析

1. 动态规划解法(基础版)
  • 定义状态dp[i]表示以nums[i]结尾的最长递增子序列的长度。
  • 状态转移方程:对于每个i,遍历0i-1的所有j:如果nums[i] > nums[j],则dp[i] = max(dp[i], dp[j] + 1)
  • 初始状态:每个元素自身都是一个长度为 1 的子序列,所以dp[i] = 1
  • 结果dp数组中的最大值,就是整个数组的最长递增子序列长度。
2. 优化解法(贪心 + 二分)

动态规划的时间复杂度是 O(n2),我们可以用贪心 + 二分将复杂度优化到 O(nlogn)。

  • 维护一个数组tails,其中tails[k]表示长度为k+1的最长递增子序列的最小可能的末尾元素。
  • 遍历nums中的每个数x
    • 如果x大于tails最后一个元素,直接追加到tails末尾;
    • 否则,找到tails中第一个大于等于x的位置,替换为x
  • 最终tails的长度就是 LIS 的长度。

代码实现(Java)

java

运行

// 动态规划 O(n²) 版本 public int lengthOfLIS(int[] nums) { if (nums == null || nums.length == 0) return 0; int[] dp = new int[nums.length]; Arrays.fill(dp, 1); int maxLen = 1; for (int i = 1; i < nums.length; i++) { for (int j = 0; j < i; j++) { if (nums[i] > nums[j]) { dp[i] = Math.max(dp[i], dp[j] + 1); } } maxLen = Math.max(maxLen, dp[i]); } return maxLen; } // 贪心 + 二分 O(n log n) 版本 public int lengthOfLISOpt(int[] nums) { if (nums == null || nums.length == 0) return 0; List<Integer> tails = new ArrayList<>(); for (int x : nums) { if (tails.isEmpty() || x > tails.get(tails.size() - 1)) { tails.add(x); } else { // 二分查找第一个 >= x 的位置 int left = 0, right = tails.size() - 1; while (left < right) { int mid = (left + right) / 2; if (tails.get(mid) >= x) { right = mid; } else { left = mid + 1; } } tails.set(left, x); } } return tails.size(); }

二、乘积最大子数组

题目描述

给你一个整数数组nums,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

思路分析

这道题和经典的「最大子数组和」很像,但乘积有个特殊的问题:负数的存在会让最小值变成最大值。比如:当前是-2,后面跟着一个-3,那么-2 * -3 = 6就会变成正数,比之前的正数乘积更大。

  • 定义状态
    • maxDp[i]:以nums[i]结尾的最大乘积子数组的乘积。
    • minDp[i]:以nums[i]结尾的最小乘积子数组的乘积。
  • 状态转移方程

    plaintext

    maxDp[i] = max(nums[i], maxDp[i-1] * nums[i], minDp[i-1] * nums[i]) minDp[i] = min(nums[i], maxDp[i-1] * nums[i], minDp[i-1] * nums[i])
    因为nums[i]可能是负数,乘以之前的最小值反而可能得到最大值,所以必须同时维护最大和最小值。
  • 初始状态maxDp[0] = minDp[0] = nums[0]
  • 结果:遍历maxDp数组,取最大值即可。

代码实现(Java)

java

运行

public int maxProduct(int[] nums) { if (nums == null || nums.length == 0) return 0; int n = nums.length; int maxDp = nums[0]; int minDp = nums[0]; int result = nums[0]; for (int i = 1; i < n; i++) { // 必须先保存,避免被覆盖 int currMax = maxDp; int currMin = minDp; maxDp = Math.max(nums[i], Math.max(currMax * nums[i], currMin * nums[i])); minDp = Math.min(nums[i], Math.min(currMax * nums[i], currMin * nums[i])); result = Math.max(result, maxDp); } return result; }

三、两道题的核心对比与面试考点

表格

题目核心难点优化技巧面试常见追问
最长递增子序列如何定义状态、如何优化时间复杂度贪心 + 二分,将 O(n2) 优化到 O(nlogn)请输出最长递增子序列本身,而不是长度
乘积最大子数组负数导致的 “最大 / 最小值反转” 问题同时维护maxmin两个状态变量如果数组全是负数,怎么处理?如果包含 0 呢?

四、总结

  1. 最长递增子序列
    • 基础版 DP 是理解动态规划状态定义和转移的绝佳例子。
    • 优化版的贪心 + 二分,体现了如何用额外的空间和更高效的算法,突破 DP 的时间复杂度瓶颈。
  2. 乘积最大子数组
    • 核心是打破思维定势,不像 “最大子数组和” 只维护一个最大值,而是要同时维护最大和最小值,因为负数的存在会让最小值变成最大值。
http://www.jsqmd.com/news/664067/

相关文章:

  • Translumo:三分钟掌握免费实时屏幕翻译,游戏外语学习效率提升300%
  • 代码出错不再重启,不再查日志,不再等PR——智能生成+实时自愈如何将MTTR从小时级压缩至2.7秒,一线大厂SRE团队已全面部署
  • 从‘炼丹’到‘调参’:手把手教你复现HAN超分网络(附PyTorch代码与消融实验分析)
  • CloudWatch 告警 AI 智能分析系统 — 从 0 到 1 全实战
  • 2026年3月口碑好的烤全羊品牌推荐,烤全羊服务推荐精选国内优质品牌分析 - 品牌推荐师
  • mysql如何配置插件以提升查询性能_安装启用memcached插件
  • Windows音频转换终极指南:7种格式一键转换的免费神器FlicFlac
  • AI智能体科普:从概念到实践,一文读懂数字员工的工作原理
  • 给自动化与控制方向研究生的投稿指南:从IEEE到国内核心,这些期刊你得知道
  • 【代码质量守门员升级计划】:为什么91%的团队在第3周就弃用Copilot审查插件?这4个未公开的规则引擎配置才是关键
  • 2026年质量好的通过式抛丸机/网带式抛丸机精选推荐公司 - 品牌宣传支持者
  • 手把手教你用Python脚本实现Keil编译后自动AES加密(附工程目录陷阱解析)
  • 京东抢购自动化终极指南:如何用JDspyder轻松抢到热门商品
  • 手把手教你用TensorFlow Lite在安卓端部署一个简单的关键词唤醒(KWS)模型
  • AI算力全解析:定义、数据与产业现状
  • Go语言的testing-quick随机测试与属性测试在函数契约验证中的使用
  • React 与 WebGPU:探索下一代图形接口在 React 数据可视化组件中的高性能集成
  • Golang reflect反射怎么用_Golang反射教程【通俗】
  • 终极指南:在Windows 10/11上直接安装Android应用的三种简单方法
  • ECharts图形标记全攻略:从内置形状到自定义SVG(最新版)
  • 智慧巡检-基于 YOLOv8 的轴承缺陷检测系统,实现从数据训练到多源检测、结果可视化的完整流程 YOLOV8预训练模型如何训练轴承缺陷检测数据集
  • 告别CPU搬运工:手把手教你用PL330 DMA指令集优化Exynos 4412数据传输
  • K8s Operator 的开发入门
  • 006、挑战:Transformer的算力之殇——注意力机制的二次方复杂度问题
  • 保姆级教程:用Thonny IDE给ESP32-CAM烧录MicroPython固件(含CH340驱动安装)
  • React Forget 编译器:深度分析自动化 Memoization 对 React 手动性能调优的革命性影响
  • 当Copilot遇上Git Rebase:智能生成代码冲突的8种反直觉模式(附可落地的Pre-Commit Hook检测清单)
  • PyTorch训练时遇到CUDA device-side assert错误?别慌,先检查你的标签和模型输出维度
  • 别再手动算堆栈了!STM32上这个自动检测方法,帮你省下80%调试时间
  • 终极视频修复指南:使用Untrunc快速拯救损坏的MP4/MOV文件 [特殊字符]