浅浅记录一下自己刷这道题的过程以及和MarsCodeAI交互的感受。
16. 最大矩形面积问题(难度:中等)
问题描述
小S最近在分析一个数组 h1,h2,...,hN,数组的每个元素代表某种高度。小S对这些高度感兴趣的是,当我们选取任意k 个相邻元素时,如何计算它们所能形成的最大矩形面积。
对于 k 个相邻的元素,我们定义其矩形的最大面积为:
R(k)=k×min(h[i],h[i+1],...,h[i+k−1])
即,R(k) 的值为这k个相邻元素中的最小值乘以k。现在,小S希望你能帮他找出对于任意k,R(k) 的最大值。
解法思路
1. 单调栈
刚看到这题直接想到的就是单调栈,和力扣的最大矩形的题目很相似。
-
思路:
对于数组的每一个元素下标i,当前元素作为它所形成的矩形高度,则这个矩形的宽度取决于右边第一个小于它的坐标,和左边第一个大于它的坐标,即当前元素为高的矩形面积为。
维持一个单调栈:从栈底到栈顶单调递增。栈中存储元素的坐标。从左到右依次遍历数组,更新栈,则栈顶存储的元素永远是左边最大的元素。一旦遇到当前元素小于栈顶元素,那么就出栈,直到栈顶元素小于当前元素,将当前元素入栈。
每次出栈时,计算出该出栈元素(记为下标top)所形成的矩形面积,此时该元素左边第一个大于它的坐标为栈顶元素,右边第一个小于它的坐标为当前下标。
然后再更新最大矩形面积。
边界处理:
- 避免整个数组是单调递增(如测试用例1)的导致遇不到出栈的条件。扩充原数组最右边加入元素0
- 避免nums[0]出栈时,当前栈已经空,无法获取栈顶坐标。扩充原数组最左边加入元素0
- 加入元素0是因为*0始终为0,不影响矩形面积计算。
-
代码:
public static int solution(int n, int[] array) {
if (n==0) return 0;
if (n==1) return array[0];
int[] newArray = new int[n+2];
System.arraycopy(array, 0, newArray, 1, n);
// System.out.println(Arrays.toString(newArray));
//单调栈 从栈底到栈顶单调递增 存储下标
Deque<Integer> stack = new ArrayDeque<>();
stack.push(0);
int res = 0;
int now = 0;
for(int i=1;i<=n+1;i++){
if(newArray[i] >= newArray[stack.peek()]) stack.push(i);
else{
while(!stack.isEmpty()&&newArray[i]<newArray[stack.peek()]){
int top = stack.pop();
now = (i-stack.peek()-1)*newArray[top];
res = Math.max(res,now);
// System.out.println("top is out:"+top+", now:"+now);
}
stack.push(i);
}
}
return res;
}
2. 双指针解法
题目给的分类是双指针,考虑一下如何用双指针做。先是询问了MarsCodeAI让它帮忙给思路,有所提示但并不能解决问题,最后自己借鉴了一点它的提示逻辑和之前单调栈解法的矩形计算原则,写了一个结果并通过了测试用例。
与MarsCodeAI交互的经历
1. AI的方案提示
问了一下MarsCodeAI,它给出的代码方案是这样的:
初始化左右指针为边界,计算当前窗口的最大矩形:(right - left + 1) * min(array[left], array[right]),移动指针,更新窗口。
def solution(n, array):
left, right = 0, n - 1
maxArea = 0
while left <= right:
currentArea = (right - left + 1) * min(array[left], array[right])
maxArea = max(maxArea, currentArea)
if array[left] < array[right]:
left += 1
else:
right -= 1
return maxArea
这个能过整个数组单调递增的样例1,但是过不了样例2:
测试用例2
输入:
n = 6, array = [5, 4, 3, 2, 1, 6]
输出:9
2. 让AI针对错误修改
将报错报告给AI,它给出的修改建议:
修改建议
移动指针的逻辑:
- 当前代码中,你通过比较
array[left]
和array[right]
来决定移动哪个指针。这>种做法在某些情况下可能会错过更大的矩形面积。- 应该考虑移动指针时,选择移动高度较小的那个指针,因为移动高度较大的指针可能会导致>面积减小。
- 计算面积的逻辑:
- 确保每次移动指针后,重新计算当前窗口的矩形面积,并更新最大面积。
然后它给出了和原来代码一模一样的的修改代码。
接着,我问了一下它给出的代码和原来的区别在哪里,它是这样回答的,关键部分如下:
区别分析
移动指针的逻辑:
- 当前代码:你通过比较
array[left]
和array[right]
来决定移动哪个指针。这种做法在某些情况下可能会错过更大的矩形面积。- 修改后的代码:同样通过比较
array[left]
和array[right]
来决定移动哪个指针。这种做法在某些情况下可能会错过更大的矩形面积。计算面积的逻辑:
- 当前代码:每次计算当前窗口的矩形面积,并更新最大面积。
- 修改后的代码:同样每次计算当前窗口的矩形面积,并更新最大面积。
关键区别
实际上,你当前的代码逻辑是正确的,但在某些情况下可能会错过更大的矩形面积。关键在于移动指针的逻辑。
从它啰嗦的描述中好像有所区别,但具体对应过去并没有区别:用词为:“同样”。然后它又给出了和原来一样的代码。此时已经陷入了提问死循环。
3. 调试之后,向AI指出这个代码的关键错误在于并没有正确获取所在窗口的最小高度
它道歉之后给出了修改思路,将min(array[left], array[right])改成了min(array[left:right+1])。
但是实际上,窗口指针移动的逻辑还是不能正确解出这道题,因为每次缩小窗口删去左右指针的最大值,可能错过以该值为高度的更大的矩形面积。
但是此时AI已经再也无法给出跳出这个逻辑的解题思路了。
总结
当前这个AI并不是特别好用,在一些特定的题上它给的思路并不能完全解决问题。当在错误的逻辑中,它会延续之前的记忆,陷入循环。
优势:
- 文本生成,如注释生成,已经思路总结汇总。
- 解题思路提示。可以参考启发(但并不能完全依赖它),比如这题,它指出的启发性思路:通过指针的移动计算固定窗口中的最大矩形。
我自己写的和AI提示的思路类似的解法
- 思路
通过遍历数组中的每个元素,以该元素为最小高度,向左右扩展窗口,直到找到比当前元素小的元素为止。然后计算当前窗口的矩形面积,并更新最大面积。这样可以确保指针能够正确找到当前区域的最小高度,从而计算出最大矩形面积。
- 代码
def solution(n, array):
maxArea = 0
left,right=0,0
for i in range(n): ##遍历数组
#窗口
left,right = i,i
#扩展窗口,直到找到比当前元素小的元素为止
while(right<n and array[right]>=array[i]):
right+=1
while left>=0 and array[left]>=array[i]:
left-=1
#以i为最小高度的当前窗口最大值
now = (right-left-1) * array[i]
# print(i)
# print(f"left:{left},right:{right},now:{now}")
maxArea = max(now,maxArea)
return maxArea
if __name__ == "__main__":
# Add your test cases here
# print(solution(5, [1, 2, 3, 4, 5]) == 9)
print(solution(6,[5, 4, 3, 2, 1, 6]) == 9)