两栈顶部可移除最大元素数问题:我的DP解法为何仅过部分测试用例?
问题分析与解决方案
你的代码遇到的核心问题是栈的引用传递导致递归状态被污染,另外递归逻辑的局限性也会让你错过最优的组合方案,我来一步步拆解:
1. 核心问题:递归中的栈状态没有回溯
Java里的Stack是引用类型,当你在递归分支中弹出栈顶元素后,没有在递归返回时把元素重新push回去。举个例子:
- 当你先计算
ansA时,弹出了栈A的元素,递归调用返回后,栈A已经少了这个元素; - 此时再计算
ansB,你使用的已经是被修改过的栈A,这会导致后续的计算完全偏离预期,无法正确遍历所有可能的元素组合(比如先选几个A再选几个B的情况)。
2. 递归逻辑的局限性
当前的递归每次只能选择从A或B的栈顶拿一个元素,但最优解往往是混合选择(比如拿2个A+2个B),而状态污染会让这种组合无法被正确计算。另外,递归的时间复杂度是指数级的,对于稍大的数组会直接超时。
更好的解决方案:前缀和+双指针/遍历
其实我们不需要真正用栈来模拟操作,因为从栈顶移除元素的过程,本质上就是从原数组的末尾开始累加元素。我们可以先计算两个数组的前缀和数组(从末尾开始累加),然后通过遍历找到满足总和不超过maxSum的最大元素数量。
实现步骤:
- 计算
sumA数组:sumA[i]表示从栈A的栈顶移除i个元素的总和(也就是原数组A最后i个元素的和); - 计算
sumB数组:同理,sumB[j]表示从栈B的栈顶移除j个元素的总和; - 遍历所有可能的
i(移除A的元素数量),找到最大的j使得sumA[i]+sumB[j] <= maxSum,记录i+j的最大值。
修正后的代码(双指针版,时间复杂度O(n+m))
static int maxStacks(int maxSum, int[] a, int[] b) { // 计算栈A的前缀和(从栈顶开始累加,sumA[0]=0,sumA[1]是栈顶元素,sumA[2]是栈顶+次顶...) int[] sumA = new int[a.length + 1]; for (int i = 0; i < a.length; i++) { sumA[i+1] = sumA[i] + a[a.length - 1 - i]; // 提前终止,如果当前总和已经超过maxSum,后面的更大,不用计算 if (sumA[i+1] > maxSum) { break; } } // 计算栈B的前缀和 int[] sumB = new int[b.length + 1]; for (int i = 0; i < b.length; i++) { sumB[i+1] = sumB[i] + b[b.length - 1 - i]; if (sumB[i+1] > maxSum) { break; } } int maxCount = 0; int j = sumB.length - 1; // 从B的最大可能数量开始 // 遍历A的所有可能移除数量 for (int i = 0; i < sumA.length; i++) { if (sumA[i] > maxSum) { break; } // 调整j到最大的满足sumA[i]+sumB[j] <= maxSum的位置 while (j > 0 && sumA[i] + sumB[j] > maxSum) { j--; } // 更新最大数量 maxCount = Math.max(maxCount, i + j); } return maxCount; }
如果一定要修复递归代码(仅作学习用,不推荐用于大数组)
如果坚持用递归,需要在递归返回时回溯栈的状态,也就是弹出元素后,递归结束要把元素重新push回去:
static int maxStacks(int maxSum, int[] a, int[] b) { Stack<Integer> stackA = new Stack<>(); Stack<Integer> stackB = new Stack<>(); for(int i=a.length-1;i>=0;i--) { stackA.push(a[i]); } for(int i=b.length-1;i>=0;i--) { stackB.push(b[i]); } return solve(stackA, stackB, maxSum, 0); } static int solve(Stack<Integer> a, Stack<Integer> b, int maxSum, int currSum) { int ansA = 0; if(!a.isEmpty()) { int peek = a.peek(); if(currSum + peek <= maxSum) { // 弹出元素 a.pop(); ansA = 1 + solve(a, b, maxSum, currSum + peek); // 回溯:把元素放回栈 a.push(peek); } } int ansB = 0; if(!b.isEmpty()) { int peek = b.peek(); if(currSum + peek <= maxSum) { b.pop(); ansB = 1 + solve(a, b, maxSum, currSum + peek); // 回溯 b.push(peek); } } // 返回两种选择中的最大值 return Math.max(ansA, ansB); }
这个修复后的递归代码可以正确计算,但时间复杂度很高,只适合小范围的测试用例。
内容的提问来源于stack exchange,提问作者Turtle
相关产品推荐
相关产品推荐

