从‘最长平台’到‘最大连续子数组’:用动态规划打通算法思路(以洛谷B2097为例)
从‘最长平台’到动态规划用算法思维拆解序列问题在算法学习的道路上很多初学者都会遇到一个共同的困惑为什么看似简单的问题却需要复杂的解法比如计算一个序列中连续相同元素的最大长度即最长平台问题表面上看只需要遍历统计但深入思考会发现它蕴含着动态规划、双指针等核心算法思想。本文将以洛谷B2097题为例带你从多个维度重新审视这个简单问题并揭示其与经典算法问题的深层联系。1. 问题本质与暴力解法最长平台问题的描述非常简单给定一个整数序列找出其中最长的连续相同数字子序列的长度。比如序列[1,2,2,3,3,3,2]的最长平台是3因为数字3连续出现了3次。1.1 直观解法分析最直接的思路是遍历数组同时维护几个关键变量lastNum记录上一个数字currentLen当前平台的连续长度maxLen已发现的最大平台长度int findMaxPlatform(vectorint nums) { if(nums.empty()) return 0; int lastNum nums[0], currentLen 1, maxLen 1; for(int i 1; i nums.size(); i) { if(nums[i] lastNum) { currentLen; maxLen max(maxLen, currentLen); } else { lastNum nums[i]; currentLen 1; } } return maxLen; }这种解法的时间复杂度是O(n)空间复杂度是O(1)已经相当高效。但如果我们止步于此就错过了深入理解算法设计的机会。1.2 解法的局限性思考虽然上述解法能正确解决问题但它存在几个值得思考的局限无法记录多个平台信息只能得到最大长度无法知道哪些数字形成了这个平台难以扩展如果问题变为求所有平台长度的平均值或找出长度大于k的平台代码结构需要大幅修改缺乏通用性这种特殊解法难以迁移到其他类似问题上提示优秀的算法设计应该考虑可扩展性和通用性这正是我们需要探索更优解法的原因。2. 双指针技术的应用双指针Two Pointers是处理序列问题的强大技术让我们看看如何用它解决最长平台问题。2.1 双指针解法实现int findMaxPlatform(vectorint nums) { if(nums.empty()) return 0; int maxLen 1; for(int left 0; left nums.size(); ) { int right left; while(right nums.size() nums[right] nums[left]) { right; } maxLen max(maxLen, right - left); left right; } return maxLen; }2.2 双指针解法的优势这种解法相比暴力方法有几个显著优点特性暴力解法双指针解法代码可读性一般更好边界处理需要额外注意更自然扩展性较差更容易扩展适用场景单一问题多种序列问题2.3 双指针技术的通用性双指针技术不仅适用于最长平台问题还能解决许多类似问题有序数组的两数之和移除排序数组中的重复项盛最多水的容器问题最小覆盖子串问题理解这一点后你就掌握了一个解决序列问题的通用工具而不仅限于特定题目。3. 动态规划视角解析动态规划DP是算法设计的核心思想之一让我们从DP的角度重新审视最长平台问题。3.1 状态定义与转移方程定义dp[i]表示以第i个元素结尾的最长平台长度。状态转移方程为dp[i] { dp[i-1] 1, 如果 nums[i] nums[i-1] 1, 否则 }初始条件dp[0] 13.2 DP解法实现int findMaxPlatform(vectorint nums) { if(nums.empty()) return 0; vectorint dp(nums.size(), 1); int maxLen 1; for(int i 1; i nums.size(); i) { if(nums[i] nums[i-1]) { dp[i] dp[i-1] 1; maxLen max(maxLen, dp[i]); } } return maxLen; }3.3 DP解法的空间优化注意到dp[i]只依赖于dp[i-1]可以进行空间优化int findMaxPlatform(vectorint nums) { if(nums.empty()) return 0; int prev 1, maxLen 1; for(int i 1; i nums.size(); i) { int current (nums[i] nums[i-1]) ? prev 1 : 1; maxLen max(maxLen, current); prev current; } return maxLen; }3.4 DP思想的通用性这种DP思路可以推广到许多类似问题最大子数组和Kadane算法最长递增子序列最长公共子序列股票买卖最佳时机问题理解这种状态定义和转移方程的构建方式是掌握动态规划的关键。4. 算法思维进阶问题变体与扩展掌握了基础解法后让我们思考几个问题变体以深化算法理解。4.1 变体一找出所有最长平台不仅要找出最长平台的长度还要找出所有达到这个长度的数字。vectorint findAllMaxPlatforms(vectorint nums) { vectorint result; if(nums.empty()) return result; int currentLen 1, maxLen 1; for(int i 1; i nums.size(); i) { if(nums[i] nums[i-1]) { currentLen; if(currentLen maxLen) { maxLen currentLen; result.clear(); result.push_back(nums[i]); } else if(currentLen maxLen) { result.push_back(nums[i]); } } else { currentLen 1; if(maxLen 1) { result.push_back(nums[i]); } } } // 处理第一个元素 if(maxLen 1 find(result.begin(), result.end(), nums[0]) result.end()) { result.push_back(nums[0]); } return result; }4.2 变体二二维平台问题考虑二维矩阵中的最长平台在m×n矩阵中找出行或列方向上的最长连续相同元素序列。int findMaxPlatform2D(vectorvectorint matrix) { if(matrix.empty() || matrix[0].empty()) return 0; int m matrix.size(), n matrix[0].size(); int maxLen 1; // 检查行方向 for(int i 0; i m; i) { int currentLen 1; for(int j 1; j n; j) { if(matrix[i][j] matrix[i][j-1]) { currentLen; maxLen max(maxLen, currentLen); } else { currentLen 1; } } } // 检查列方向 for(int j 0; j n; j) { int currentLen 1; for(int i 1; i m; i) { if(matrix[i][j] matrix[i-1][j]) { currentLen; maxLen max(maxLen, currentLen); } else { currentLen 1; } } } return maxLen; }4.3 变体三带限制的平台问题找出满足特定条件的最长平台比如平台中的数字必须大于某个阈值。int findMaxPlatformWithThreshold(vectorint nums, int threshold) { if(nums.empty()) return 0; int currentLen 0, maxLen 0; for(int num : nums) { if(num threshold) { currentLen; maxLen max(maxLen, currentLen); } else { currentLen 0; } } return maxLen; }5. 从具体问题到通用算法思维最长平台问题虽然简单但它体现了算法设计的几个核心思想5.1 问题分解与抽象将具体问题抽象为通用模型是算法设计的关键步骤。最长平台问题可以抽象为序列中的连续相同元素段识别极值统计问题状态转移问题5.2 算法选择与优化针对同一问题不同算法有不同特性算法时间复杂度空间复杂度适用场景暴力遍历O(n)O(1)简单场景双指针O(n)O(1)需要扩展功能动态规划O(n)O(n)/O(1)教学理解5.3 知识迁移与应用理解最长平台问题的解法后可以将其应用到更复杂的问题中DNA序列分析中的重复片段检测时间序列数据中的稳定期识别图像处理中的连续区域检测在实际编程竞赛中我经常发现许多复杂问题本质上都是简单问题的组合或扩展。比如有一次遇到一个关于游戏地图区域划分的问题核心就是二维平台问题的变体通过将问题分解识别最终用类似双指针的方法高效解决了。