【Q287】(hd) 柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。
图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。
示例:输入: [2,1,5,6,2,3] 输出: 10
class Solution {
/*
* 【暴力法——中心扩散】
*
* 关键思路:以此时的heights[i]为固定的高h,只要两侧比内部高,宽度w就向外扩散1(w初始值为1)
*
* 每次遍历到某个高度,其意义是:完全覆盖第i根柱子(从0开始)的最大矩形面积
*/
public int largestRectangleArea(int[] heights) {
int len = heights.length;
int maxArea = 0;
for(int i = 0; i < len; i++) {
int h = heights[i];
int w = 1;
int j = i - 1;
int k = i+ 1;
while(j >= 0 && heights[j] >= heights[i]) {
w++;
j--;
}
while(k < len && heights[k] >= heights[i]) {
w++;
k++;
}
maxArea = Math.max(maxArea, w * h);
}
return maxArea;
}
// for的内部有O(n)的while,总的时间复杂度为O(n²)所以称为暴力法
// ~~其实不是特别暴力~~
}
class Solution_84_2 {
/*
* 【单调栈】
* 解该题的关键点是不变的:以某个位置的高度为中心,向两边扩散,直到被比它严格小的柱子挡住
* 在暴力法中,我们通过时间复杂度为O(n)的while来寻找某个高度,的左边和右边"第一个严格小于它的柱子"
* 有没有可能对这个"找左右第一个严格小的柱子"的方法进行优化呢?
*
* 下面引入【单调栈】
* 1.单调栈分为单调递增栈和单调递减栈
* 11. 单调递增栈即栈内元素保持单调递增的栈
* 12. 单调递减栈即栈内元素保持单调递减的栈
*
* 2.操作规则(★☆★)(下面都以单调递增栈为例)
* 21. 如果新的元素比栈顶元素大(或等于),就入栈
* 22. 如果新的元素较小,那就一直把栈内元素弹出来,直到栈顶比新元素小
*
* 3.加入这样一个规则之后,会有什么效果(一定要理解!!)
* 31. 栈内的元素总是是递增的
* 32. 新元素导致出栈时,说明该新元素是出栈元素右边第一个严格小于它的
* 33. 新元素导致出栈时,出栈一次后,此时栈顶的元素是刚刚出栈的元素左边第一个严格小于它的
*
*
*【补充细节】
* 1.单调栈不是严格单调的,比如[1, 3, 5, 5, 9]
* 2.单调栈中存的是下标而不是高度,因为出栈时还要根据下标计算宽度
* 3.触发出栈的条件是,此时遍历到的元素严格小于栈顶元素
* 4.单调栈需要两个【哨兵】,即在原数组的基础上,左端加0,右端加0:
* 左端加0是为了,划定左边界,因为高度0只能被peek使用到,而不可能被pop掉
* 右端加0是为了,保证栈中所有高度都能被pop掉(除了左右0),毕竟它是0,作为右边界清空掉栈的残余
*
*/
public int largestRectangleArea(int[] heights) {
Deque<Integer> stack = new ArrayDeque<>();
int len = heights.length;
int[] arr = new int[len + 2];
int maxArea = 0;
System.arraycopy(heights, 0, arr, 1, len); // 数组补0【哨兵】
for(int i = 0; i < arr.length; i++) {
while(!stack.isEmpty() && arr[i] < arr[stack.peek()]) {
int h = arr[stack.pop()]; // 此时栈顶的下标对应的高度,它左边和右边“严格小于”它的柱子,已经确定
int w = i - stack.peek() -1; // i是右边第一个严格小于的柱子,stack.peek()是左边第一个小于的柱子;矩形在它们之间(不包括它们,它们只是"限制")
maxArea = Math.max(maxArea, h * w);
}
stack.push(i); // 不要忘了包把此时的i入栈(经过上面的出栈,它完全可保证使单调栈是单调增的)
}
return maxArea;
}
// 线性的时间复杂度O(n)
}
【Q101】(ez) 对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。1 / \ 2 2 / \ / \ 3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1 / \ 2 2 \ \ 3 3
进阶: 你可以运用递归和迭代两种方法解决这个问题吗?
class Solution {
/*
* 【递归】
* 1.终止条件有两种
* 2.返回值表达式有点长
*/
public boolean isSymmetric(TreeNode root) {
return judge(root, root); // 这个填入两个根节点的写法太巧妙了
}
private boolean judge(TreeNode node1, TreeNode node2) { //node1和node2永远可以保证是位置对称的两个节点
if(node1 == null && node2 == null) { // 当两个节点都是null时,作为递归终止条件,返回true
return true;
}
if(node1 == null || node2 == null) { // 接着上一个if的语义,当两个节点的其中一个是null时,递归终止,返回false;
return false; // 其实这一句是为了保证下面的node.left和node.right都存在
}
return node1.val == node2.val && judge(node1.left, node2.right) && judge(node1.right, node2.left);
// 位置对称的节点的值相同 && 位置对称的这两个节点,子节点也满足
}
}
lass Solution {
/*
* 【迭代】
* 1.递归改为迭代的方法,通常就是利用一个辅助队列
* 2.每次进队两个节点,这两个节点是位置对称的;取出的时候它们也成对取出(!关键)
*/
public boolean isSymmetric(TreeNode root) {
return judge(root, root);
// 其实这里写成:
// if(root == null) return false;
// return judge(root.left, root.right)会更好
}
private boolean judge(TreeNode node1, TreeNode node2) {
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(node1);
queue.offer(node2);
while(!queue.isEmpty()) {
TreeNode p = queue.poll();
TreeNode q = queue.poll();
if(p == null && q == null) {
continue;
}
if((p == null || q ==null) || p.val != q.val) {
return false;
}
queue.offer(p.left);
queue.offer(q.right);
queue.offer(p.right);
queue.offer(q.left);
}
return true;
}
}
Qs from https://leetcode-cn.com
♦ loli suki
♥ end