1. 题目解析与核心思路
1.1 乘积最大子数组(152题)
这道题要求我们找到一个连续子数组,使得其乘积最大。与常规的最大子数组和问题不同,乘积的最大值可能由两个负数相乘得到,因此需要同时记录当前的最大值和最小值。
动态规划解法:
- 维护两个变量:
max_product和min_product - 状态转移方程:
temp_max = max(nums[i], max_product * nums[i], min_product * nums[i]) min_product = min(nums[i], max_product * nums[i], min_product * nums[i]) max_product = temp_max - 时间复杂度O(n),空间复杂度O(1)
注意:当遇到0时,乘积会重置,这是本题的关键点之一。
1.2 分割等和子集(416题)
这是一个典型的0-1背包问题变种,需要判断数组是否能被分成两个和相等的子集。
动态规划解法:
- 计算数组总和sum,如果sum为奇数直接返回false
- 目标target = sum / 2
- 建立dp数组,dp[i]表示能否组成和为i的子集
- 状态转移方程:
for num in nums: for i in range(target, num-1, -1): dp[i] = dp[i] or dp[i - num] - 时间复杂度O(n*target),空间复杂度O(target)
1.3 最长有效括号(32题)
这道题要求找到最长的有效括号子串长度。有效括号串的特点是每个左括号都有对应的右括号。
栈解法:
- 初始化栈,压入-1作为基准
- 遍历字符串:
- 遇到'('时压入当前索引
- 遇到')'时弹出栈顶元素
- 如果栈为空,压入当前索引
- 否则计算当前索引与栈顶元素的差,更新最大值
- 时间复杂度O(n),空间复杂度O(n)
动态规划解法:
- 定义dp[i]表示以i结尾的最长有效括号长度
- 状态转移:
- 当s[i]=')'且s[i-1]='('时:
dp[i] = dp[i-2] + 2 - 当s[i]=')'且s[i-1]=')'时: 如果s[i-dp[i-1]-1]='(',则:
dp[i] = dp[i-1] + 2 + dp[i-dp[i-1]-2]
- 当s[i]=')'且s[i-1]='('时:
- 时间复杂度O(n),空间复杂度O(n)
1.4 不同路径(62题)
这是一个典型的组合数学问题,也可以使用动态规划解决。
动态规划解法:
- 定义dp[i][j]表示到达(i,j)的路径数
- 状态转移方程:
dp[i][j] = dp[i-1][j] + dp[i][j-1] - 初始化:第一行和第一列都为1
- 时间复杂度O(mn),空间复杂度O(mn)(可优化到O(n))
组合数学解法:
- 总路径数 = C(m+n-2, n-1)
- 计算组合数时注意避免整数溢出
2. 解题技巧与优化
2.1 动态规划问题解题框架
- 定义状态:明确dp数组的含义
- 确定状态转移方程:如何从子问题推导出当前问题
- 初始化:边界条件的处理
- 确定遍历顺序:确保子问题已解决
- 举例验证:用简单例子验证算法正确性
2.2 栈问题的常见模式
括号匹配类问题:
- 使用栈来维护未匹配的括号
- 注意栈底可能需要特殊标记
单调栈问题:
- 维护一个单调递增或递减的栈
- 常用于解决"下一个更大元素"类问题
2.3 空间优化技巧
- 滚动数组:当当前状态只依赖有限的前几个状态时
- 状态压缩:用位运算等方式减少空间使用
- 原地修改:在不影响结果的情况下复用输入空间
3. 常见错误与调试技巧
3.1 动态规划常见错误
- 状态定义不明确导致转移方程错误
- 边界条件处理不当
- 遍历顺序错误导致子问题未解决
- 空间优化时覆盖了还需要使用的值
调试建议:打印出dp表格,与手动计算的结果对比
3.2 栈操作常见错误
- 栈空时执行pop操作
- 忘记处理栈底标记
- 匹配逻辑错误(如不同类型的括号匹配)
3.3 组合数学问题注意事项
- 整数溢出问题:使用更大的数据类型或取模
- 阶乘计算的预处理优化
- 除法转为乘法逆元(在取模情况下)
4. 代码实现示例
4.1 乘积最大子数组Python实现
def maxProduct(nums): if not nums: return 0 max_prod = min_prod = result = nums[0] for i in range(1, len(nums)): if nums[i] < 0: max_prod, min_prod = min_prod, max_prod max_prod = max(nums[i], max_prod * nums[i]) min_prod = min(nums[i], min_prod * nums[i]) result = max(result, max_prod) return result4.2 分割等和子集Java实现
public boolean canPartition(int[] nums) { int sum = 0; for (int num : nums) sum += num; if (sum % 2 != 0) return false; int target = sum / 2; boolean[] dp = new boolean[target + 1]; dp[0] = true; for (int num : nums) { for (int i = target; i >= num; i--) { dp[i] = dp[i] || dp[i - num]; } } return dp[target]; }4.3 最长有效括号C++实现
int longestValidParentheses(string s) { stack<int> st; st.push(-1); int max_len = 0; for (int i = 0; i < s.length(); i++) { if (s[i] == '(') { st.push(i); } else { st.pop(); if (st.empty()) { st.push(i); } else { max_len = max(max_len, i - st.top()); } } } return max_len; }5. 进阶练习建议
- 尝试用不同方法解决同一问题(如用动态规划和栈分别解决最长有效括号)
- 修改问题条件后的变种练习(如乘积最大子数组改为绝对值最大)
- 尝试解决这些问题的follow-up问题:
- 输出具体的子数组/子集
- 处理更大的输入规模
- 增加额外的约束条件
在实际面试中,建议先明确问题要求,与面试官确认边界条件,再选择合适的方法实现。对于动态规划问题,可以先从暴力解法开始,逐步优化;对于栈问题,要特别注意边界条件的处理。