1. 题目背景与需求解析
"牛客每日一题:清楚姐姐买竹鼠(Java)"是牛客网算法题库中的一道典型动态规划问题。题目描述了一位名为清楚姐姐的角色在竹鼠市场进行买卖操作的情景,需要计算在一定约束条件下能够获得的最大利润。这类问题在互联网大厂的笔试面试中出现频率极高,考察的是对动态规划思想的掌握程度。
1.1 问题场景还原
假设清楚姐姐每天可以执行三种操作:
- 买入一只竹鼠(消耗固定成本)
- 卖出一只竹鼠(获得固定收益)
- 不进行任何操作
市场有以下限制条件:
- 每天只能进行一种操作
- 手中最多持有k只竹鼠
- 初始资金为m元
- 需要计算n天后能获得的最大利润
1.2 核心考察点
这道题目主要考察以下几个算法能力:
- 动态规划状态转移方程的建立
- 多维状态的处理技巧
- 边界条件的处理能力
- 空间复杂度的优化意识
2. 动态规划解法详解
2.1 状态定义
我们使用三维DP数组来记录状态:
java复制dp[i][j][l]
// i: 第i天(0 <= i < n)
// j: 当前持有j只竹鼠(0 <= j <= k)
// l: 当前是否持有冷冻期标记(0/1)
2.2 状态转移方程
java复制// 不操作的情况
dp[i][j][0] = max(
dp[i-1][j][0], // 前一天也不操作
dp[i-1][j][1] // 前一天处于冷冻期
)
// 买入操作
if (j > 0 && money >= buyPrice) {
dp[i][j][0] = max(
dp[i][j][0],
dp[i-1][j-1][0] - buyPrice
)
}
// 卖出操作
dp[i][j][1] = dp[i-1][j+1][0] + sellPrice
2.3 初始化处理
边界条件需要特别注意:
java复制// 第0天初始化
dp[0][0][0] = m; // 初始资金
dp[0][1][0] = m - buyPrice; // 第一天买入
3. Java实现与优化
3.1 基础实现代码
java复制public class Solution {
public int maxProfit(int k, int m, int[] buy, int[] sell) {
int n = buy.length;
int[][][] dp = new int[n][k+2][2];
// 初始化
for(int j=0; j<=k; j++) {
Arrays.fill(dp[0][j], -1);
}
dp[0][0][0] = m;
if(m >= buy[0]) {
dp[0][1][0] = m - buy[0];
}
// DP过程
for(int i=1; i<n; i++) {
for(int j=0; j<=k; j++) {
// 不操作的情况
dp[i][j][0] = Math.max(
dp[i-1][j][0],
dp[i-1][j][1]
);
// 买入操作
if(j > 0 && dp[i-1][j-1][0] >= buy[i]) {
dp[i][j][0] = Math.max(
dp[i][j][0],
dp[i-1][j-1][0] - buy[i]
);
}
// 卖出操作
if(j < k && dp[i-1][j+1][0] != -1) {
dp[i][j][1] = dp[i-1][j+1][0] + sell[i];
}
}
}
// 找出最大值
int max = 0;
for(int j=0; j<=k; j++) {
max = Math.max(max, Math.max(dp[n-1][j][0], dp[n-1][j][1]));
}
return max - m; // 返回利润
}
}
3.2 空间优化技巧
由于DP数组只依赖前一天的数值,可以将三维数组优化为二维:
java复制int[][] prev = new int[k+2][2];
int[][] curr = new int[k+2][2];
4. 常见问题与调试技巧
4.1 典型错误排查
-
数组越界问题:
- 确保j的范围在0到k之间
- 特别注意j-1和j+1的边界检查
-
初始状态设置错误:
- 忘记初始化第0天的状态
- 没有处理初始资金不足的情况
-
状态转移遗漏:
- 忘记处理冷冻期状态
- 买入/卖出条件判断不全
4.2 调试建议
- 打印DP表观察状态变化:
java复制System.out.println("Day "+i+" Hold "+j);
System.out.println("No freeze: "+dp[i][j][0]);
System.out.println("Freeze: "+dp[i][j][1]);
- 使用小规模测试用例验证:
java复制int k = 1;
int m = 10;
int[] buy = {5, 3, 2};
int[] sell = {8, 4, 6};
// 预期结果:13
5. 算法复杂度分析
5.1 时间复杂度
基础实现:
- 三重循环:O(n * k * 2) = O(nk)
优化后:
- 空间降维但时间复杂度不变
5.2 空间复杂度
基础实现:
- O(n * k * 2) 三维数组
优化后:
- O(k * 2) 两个二维数组
6. 同类问题扩展
这类动态规划问题有多个变种:
-
无限次交易版本:
- 去掉k的限制
- 状态简化为持有/不持有
-
含手续费版本:
- 每次交易扣除固定手续费
- 需要在卖出时额外扣除
-
冷冻期延长版本:
- 冷冻期可能持续多日
- 需要增加状态维度
在实际面试中,面试官可能会逐步增加这些变种条件来考察候选人的应变能力。建议在掌握基础解法后,主动思考这些变种情况的解决方案。