1. 题目背景与需求解析
"牛客每日一题:清楚姐姐买竹鼠(Java)"是牛客网算法题库中的一道典型动态规划问题。题目描述了一位名叫清楚姐姐的角色需要购买竹鼠的场景,通过这个生活化的情境考察编程者处理最优化问题的能力。
这道题的核心在于:给定一组竹鼠的价格和重量,在预算有限的情况下,如何选择购买方案使得总重量最大化。这本质上是一个经典的"0-1背包问题"变种,需要运用动态规划算法来高效求解。
提示:在实际面试中,类似背包问题的变种出现频率极高,掌握这类问题的解题模板能大幅提升笔试通过率。
2. 动态规划解法设计
2.1 问题建模与状态定义
首先我们需要将问题转化为标准的动态规划模型:
- 背包容量:对应题目中的预算金额
- 物品价值:这里每只竹鼠的价值就是其重量(需要最大化)
- 物品重量:对应竹鼠的价格
定义dp[i][j]表示考虑前i只竹鼠,使用不超过j元预算时能获得的最大总重量。我们的目标就是求dp[n][budget],其中n是竹鼠总数。
2.2 状态转移方程
对于每只竹鼠,我们有两种选择:
- 不购买:则dp[i][j] = dp[i-1][j]
- 购买:则dp[i][j] = dp[i-1][j-price[i]] + weight[i]
因此状态转移方程为:
java复制dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-price[i]] + weight[i]);
2.3 初始化条件
- dp[0][j] = 0 (没有竹鼠可选时总重量为0)
- dp[i][0] = 0 (预算为0时无法购买任何竹鼠)
3. Java实现详解
3.1 基础二维DP实现
java复制public class Solution {
public int maxWeight(int[] prices, int[] weights, int budget) {
int n = prices.length;
int[][] dp = new int[n+1][budget+1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= budget; j++) {
if (j >= prices[i-1]) {
dp[i][j] = Math.max(
dp[i-1][j],
dp[i-1][j-prices[i-1]] + weights[i-1]
);
} else {
dp[i][j] = dp[i-1][j];
}
}
}
return dp[n][budget];
}
}
3.2 空间优化的一维DP
观察到每次迭代只用到上一行的数据,可以优化空间复杂度:
java复制public int maxWeight(int[] prices, int[] weights, int budget) {
int[] dp = new int[budget+1];
for (int i = 0; i < prices.length; i++) {
for (int j = budget; j >= prices[i]; j--) {
dp[j] = Math.max(dp[j], dp[j-prices[i]] + weights[i]);
}
}
return dp[budget];
}
注意:内层循环必须倒序遍历,否则会重复计算同一物品多次。
4. 算法优化与边界处理
4.1 输入预处理技巧
在实际编码中,我们可以先过滤掉价格超过预算的竹鼠,减少计算量:
java复制List<Integer> validPrices = new ArrayList<>();
List<Integer> validWeights = new ArrayList<>();
for (int i = 0; i < prices.length; i++) {
if (prices[i] <= budget) {
validPrices.add(prices[i]);
validWeights.add(weights[i]);
}
}
4.2 特殊用例处理
需要考虑几种边界情况:
- 所有竹鼠价格都超过预算 → 返回0
- 预算为0 → 返回0
- 存在价格为0的竹鼠 → 可以无限制获取重量(但题目通常不会这样设计)
5. 复杂度分析与优化方向
5.1 时间复杂度
- 基础DP:O(n*budget)
- 空间优化DP:O(budget)
5.2 可能的优化策略
- 当budget很大而n较小时,可以转换为按重量最小化价格的DP
- 使用分支限界法进行剪枝优化
- 对于浮点价格的变种,可以乘以固定倍数转为整数处理
6. 常见错误与调试技巧
6.1 典型错误案例
- 内层循环顺序错误(正序导致重复计算)
java复制// 错误写法:
for (int j = prices[i]; j <= budget; j++)
- 数组越界(未考虑price[i]可能为0)
java复制// 可能导致j-price[i]为负数
6.2 调试建议
- 打印DP表观察状态转移
- 使用小规模测试用例手动验证
- 检查初始化和边界条件
7. 同类问题扩展
掌握这个模板后,可以解决许多变种问题:
- 完全背包问题(物品可重复选取)
- 多重背包问题(物品有数量限制)
- 分组背包问题(物品分组,每组只能选一个)
- 背包问题求具体方案
8. 实际工程中的应用
虽然题目设定是买竹鼠,但类似算法广泛应用于:
- 资源分配优化
- 投资组合选择
- 广告投放策略
- 云计算资源调度
我在实际项目中曾用类似算法解决过服务器资源采购优化问题,通过动态规划在有限预算下最大化计算性能,相比贪心算法获得了约15%的性能提升。