系列汇总:《刷题系列汇总》
文章目录
——————《剑指offeer》———————
1. 用两个栈实现队列
- 题目描述:用两个栈来实现一个队列,分别完成在队列尾部插入整数(
push
)和在队列头部删除整数(pop)的功能。 队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。
示例:
输入:
[“PSH1”,“PSH2”,“POP”,“POP”]
返回:
1,2
解析:
“PSH1”:代表将1插入队列尾部
“PSH2”:代表将2插入队列尾部
"POP“:代表删除一个元素,先进先出=>返回1
"POP“:代表删除一个元素,先进先出=>返回2
- 优秀思路:负负得正,【a b c】→第一次入栈→【c b a】→第二次入栈→【a b c】,故两次入栈即实现队列的先进先出
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
}
public int pop() {
if (stack2.isEmpty()) { // stack2 为空
while (stack1.size() != 0) {
stack2.push(stack1.pop()); // stack1全部压入stack2
}
}
return stack2.pop(); // stack2 不为空直接弹出
}
}
2. 包含min函数的栈(没看懂)
- 题目描述:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的
min
函数,并且调用min
函数、push
函数 及pop
函数 的时间复杂度都是O(1)
push(value):
将value
压入栈中pop():
弹出栈顶元素top()
:获取栈顶元素min():
获取栈中最小元素
- 优秀思路:
import java.util.Stack;
//使用一个同步辅助栈
public class Solution {
private Stack<Integer> s1 = new Stack();
private Stack<Integer> s2 = new Stack();
public void push(int node) {
s1.push(node);
if(s2.isEmpty()){
s2.push(node);
}else{
s2.push(Math.min(s2.peek(), node));
}
}
public void pop() {
s1.pop();
s2.pop();
}
public int top() {
return s1.peek();
}
public int min() {
return s2.peek();
}
}
3. 栈的压入、弹出序列
- 题目描述:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列
1,2,3,4,5
是某栈的压入顺序,序列4,5,3,2,1
是该压栈序列对应的一个弹出序列,但4,3,5,1,2
就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的) - 优秀思路:【模拟法】新建一个栈按给出的入栈和出栈顺序走一遍,看能不能走的通即可
- 我的实现(86%,优秀):【双指针+模拟栈】
import java.util.*;
public class Solution {
public boolean IsPopOrder(int [] pushA,int [] popA) {
if(pushA.length == 0 || popA.length == 0) return true;
Stack<Integer> stack = new Stack<>();
int p1 = 0, p2 = 0;
while(p1 < pushA.length){
stack.push(pushA[p1]);
p1++;
while(!stack.isEmpty() && stack.peek() == popA[p2]){
stack.pop();
p2++;
}
}
if(!stack.isEmpty()) return false;
return true;
}
}
——————《LeectCode》———————
1. 直方图的水量
-
题目描述:给定一个直方图(也称柱状图),假设有人从上面源源不断地倒水,最后直方图能存多少水量?直方图的宽度为 1。
-
我的思路(99.93%):【有效积水区域法】① 从左到右遍历,记录当前最高柱子索引,若遇到更高的柱子,则说明构成了有效积水区域,当前积水有效,加入结果;② 若最高柱子出现在中间,则其后面的积水无法统计。故还需从右边向左遍历到最高柱子处,按相同方法计算积水面积 。
class Solution {
public int trap(int[] height) {
if(height == null || height.length <= 2) return 0;
int res = 0, curMaxHeightInd_left = 0;
int tempWater = 0;
for(int i = 0;i < height.length;i++){
if(height[i] < height[curMaxHeightInd_left]){
tempWater += height[curMaxHeightInd_left] - height[i];
}else{ // 遇到更高的柱子(构成了有效积水区域),当前积水有效,加入结果
curMaxHeightInd_left = i;
res += tempWater;
tempWater = 0;
}
}
// 若最高柱子出现在中间,则其后面的积水无法统计
// 故还需从右边向左遍历到最高柱子处,按相同方法计算积水面积
int curMaxHeightInd_right = height.length-1;
tempWater = 0;
for(int i = height.length-1;i >= curMaxHeightInd_left;i--){
if(height[i] < height[curMaxHeightInd_right]){
tempWater += height[curMaxHeightInd_right] - height[i];
}else{
curMaxHeightInd_right = i;
res += tempWater;
tempWater = 0;
}
}
return res;
}
}
- 相似思路(99%,更简洁):【先加后减】① 只要是比当前最高柱子矮出的面积都加入答案(所以会额外加上一些并没有构成积水区域的积水面积);② 从右边往左遍历到最高柱子处:减去额外多记录的超出当前柱子的积水面积
class Solution {
public int trap(int[] height) {
int sum = 0;
// l : 记录当前最高柱子的索引
int l = 0, r = height.length - 1;
for (int i = l; i < height.length; i++) {
if (height[i] >= height[l]) l = i;
sum += height[l] - height[i]; // 只要是比当前最高柱子矮出的面积都加入答案(所以会额外加上一些并没有构成积水区域的积水面积)
}
// 从右边往左遍历到最高柱子处:减去额外多记录的超出当前柱子的积水面积
for (int i = r; i > l; i--) {
if (height[i] >= height[r]) r = i;
sum -= height[l] - height[r]; // 最高柱子-当前柱子 即为额外记录的部分
}
return sum;
}
}
- 优秀思路1(44%):【单调栈】维护一个单调栈,单调栈存储的是下标,满足从栈底到栈顶的下标对应的数组
height
中的元素递减。- ① 从左到右遍历数组,遍历到下标 i 时,如果栈内至少有两个元素,记栈顶元素为top,top 的下面一个元素是left,则一定有height[left]≥height[top]。
- ② 如果height[i]>height[top],则得到一个可以接雨水的区域,该区域的宽度是i−left−1,高度是min(height[left],height[i])−height[top],根据宽度和高度即可计算得到该区域能接的水的量。
- ③ 为了得到 left,需要将top 出栈。在对 top 计算能接的水的量之后,left 变成新的top,重复上述操作,直到栈变为空,或者栈顶下标对应的 height 中的元素大于或等于 height[i]。
- 在对下标 i处计算能接的水的量之后,将 i入栈,继续遍历后面的下标,计算能接的水的量。遍历结束之后即可得到能接的水的总量。
class Solution {
public int trap(int[] height) {
int ans = 0;
Deque<Integer> stack = new LinkedList<Integer>();
int n = height.length;
for (int i = 0; i < n; ++i) {
while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
int top = stack.pop();
// 遍历完成
if (stack.isEmpty()) {
break;
}
int left = stack.peek();
int currWidth = i - left - 1;
int currHeight = Math.min(height[left], height[i]) - height[top];
ans += currWidth * currHeight;
}
stack.push(i);
}
return ans;
}
}
2. 反转每对括号间的子串(需重写)
- 题目描述:给出一个字符串 s(仅含有小写英文字母和括号)。请你按照从括号内到外的顺序,逐层反转每对匹配括号中的字符串,并返回最终的结果。注意,您的结果中 不应 包含任何括号。
- 我的思路:找到规律,奇数层的先处理该层后面部分再处理前面部分,偶数层的先处理前面部分再处理后面部分,下面代码不适用于括号没有嵌套的情形,只适用于完全嵌套的情况
class Solution {
public String reverseParentheses(String s) {
if(s == null) return null;
List<Character> list = new ArrayList<>();
int countBracket = 0;
int countLeft = 0,countRIght = 0;
int p1 = 0,p2 = s.length()-1;
// 先加入括号外的
while(p1 < p2){
for(int i = p1;i <= p2;i++){
if(p1 == 0 && s.charAt(p1) != '('){
while(p1 < s.length() && s.charAt(p1) != '(' && s.charAt(p1) != ')'){
list.add(s.charAt(p1));
countLeft++;
p1++;
i++;
}
}else if(s.charAt(i) == '('){ //遇到左括号
if(p2 == s.length()-1 && s.charAt(p2) != ')'){
while(s.charAt(p2) != ')' && s.charAt(p2) != '('){
list.add(list.size()-countRIght,s.charAt(p2));
countRIght++;
p2--;
}
}
countBracket++;
if(countBracket % 2 != 0){ //奇数层,处理顺序:右部分→左部分
while(s.charAt(--p2) != ')' && s.charAt(p2) != '('){ // ① 在list头部加入当前层的右部分字母
list.add(countLeft,s.charAt(p2));
countLeft++;
}
if(p1 >= p2) break;
// ② 在list尾部加入当前层的左部分字母
while(s.charAt(++p1) != '(' && s.charAt(p1) != ')'){
list.add(list.size()-countRIght,s.charAt(p1));
countRIght++;
i++;
}
}else{ // 处理顺序:左部分→右部分
while(s.charAt(++p1) != '(' && s.charAt(p1) != ')'){
list.add(countLeft,s.charAt(p1));
countLeft++;
i++;
}
if(p1 >= p2) break;
while(s.charAt(--p2) != ')' && s.charAt(p2) != '('){
list.add(list.size()-countRIght,s.charAt(p2));
countRIght++;
}
}
}
}
}
StringBuffer sb = new StringBuffer();
for(char c:list){
sb.append(c);
}
return sb.toString();
}
}
- 优秀思路1:【队列】我们从左到右遍历该字符串,使用字符串 str 记录当前层所遍历到的小写英文字母。对于当前遍历的字符:如果是左括号,将 str 插入到栈中,并将str 置为空,进入下一层;如果是右括号,则说明遍历完了当前层,需要将str 反转,返回给上一层。具体地,将栈顶字符串弹出,然后将反转后的str 拼接到栈顶字符串末尾,将结果赋值给str。如果是小写英文字母,将其加到str 末尾。
class Solution {
public String reverseParentheses(String s) {
Deque<String> q = new LinkedList<String>();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
// 遇到左括号:存储当前字符串,清空当前字符串
if (ch == '(') {
q.push(sb.toString());
sb.setLength(0); // 清空sb
}
// 遇到右括号:翻转当前字符串,并将上一层的字符串出队列拼接在当前层前面
else if (ch == ')') {
sb.reverse();
sb.insert(0, q.pop()); //
}
// 遇到字母:存储
else {
sb.append(ch);
}
}
return sb.toString();
}
}
- 优秀思路2:【预处理括号+左右跳转】和我的思路大概一致,沿着某个方向移动,此时遇到了括号,那么我们只需要首先跳跃到该括号对应的另一个括号所在处,然后改变移动方向即可。这个方案同时适用于遍历时进入更深一层,以及完成当前层的遍历后返回到上一层的方案。在实际代码中,我们需要预处理出每一个括号对应的另一个括号所在的位置,这一部分我们可以使用栈解决。当我们预处理完成后,即可在线性时间内完成遍历,遍历的字符串顺序即为反转后的字符串。
class Solution {
public String reverseParentheses(String s) {
int n = s.length();
int[] pair = new int[n];
Deque<Integer> stack = new LinkedList<Integer>();
for (int i = 0; i < n; i++) {
if (s.charAt(i) == '(') {
stack.push(i);
} else if (s.charAt(i) == ')') {
int j = stack.pop();
pair[i] = j;
pair[j] = i;
}
}
StringBuffer sb = new StringBuffer();
int index = 0, step = 1;
while (index < n) {
if (s.charAt(index) == '(' || s.charAt(index) == ')') {
index = pair[index];
step = -step;
} else {
sb.append(s.charAt(index));
}
index += step;
}
return sb.toString();
}
}
3. 每日温度
- 题目描述:请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用
0
来代替。例如,给定一个列表temperatures = [73, 74, 75, 71, 69, 72, 76, 73]
,你的输出应该是[1, 1, 4, 2, 1, 1, 0, 0]
。- 提示:气温 列表长度的范围是
[1, 30000]
。每个气温的值的均为华氏度,都是在[30, 100]
范围内的整数。
- 提示:气温 列表长度的范围是
- 优秀思路:【单调栈】维护一个单调栈,存储温度的索引值。当当前温度大于栈顶温度时,为栈顶索引计算天数,并将栈顶出栈,当前温度入栈。小于栈顶温度则直接入栈。
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int length = temperatures.length;
int[] ans = new int[length];
Deque<Integer> stack = new LinkedList<Integer>();
for (int i = 0; i < length; i++) {
int temperature = temperatures[i];
while (!stack.isEmpty() && temperature > temperatures[stack.peek()]) {
int prevIndex = stack.pop();
ans[prevIndex] = i - prevIndex;
}
stack.push(i);
}
return ans;
}
}
4. 删除字符串中的所有相邻重复项
- 题目描述:给出由小写字母组成的字符串
S
,重复项删除操作会选择两个相邻且相同的字母,并删除它们。在S
上反复执行重复项删除操作,直到无法继续删除。在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。 - 我的思路(63%):【栈】① 建立栈删除重复元素;② 将栈元素存到
StringBuffer
中;③ 将StringBuffer
反序后转换为String
输出:之所以要反序是因为栈后入先出
class Solution {
public String removeDuplicates(String s) {
if(s == null) return null;
Deque<Character> stack = new ArrayDeque<>();
StringBuffer sb = new StringBuffer();
for(char c:s.toCharArray()){
if(stack.isEmpty() || stack.peek() != c){
stack.push(c);
}else stack.pop();
}
for(char c:stack){
sb.append(c);
}
return sb.reverse().toString(); // 由于栈后入先出的特性,需要先颠倒一下
}
}
- 优秀思路1(81%):【利用栈思想】不建立真正的栈,直接用StringBuffer模拟栈
class Solution {
public String removeDuplicates(String s) {
StringBuffer stack = new StringBuffer();
int top = -1; // 存储栈顶元素的位置,小于0说明栈为空
for (int i = 0; i < s.length(); ++i) {
char ch = s.charAt(i);
// 栈不为空 且 栈顶==当前
if (top >= 0 && stack.charAt(top) == ch) {
stack.deleteCharAt(top);
--top;
} else {
stack.append(ch);
++top;
}
}
return stack.toString();
}
}
- 优秀思路2(99%):【妙!坐标回退法】直接在原字符串上进行修改,核心思想时维护一个索引top(初始-1),当遇到相同值时,索引回退1,当遇到不同值时,修改top+1处的字符
举例:”abbacd“
① top = -1
② top = 0,a
③ top = 1,ab
④ 遇到 b= b,top-1 = 0
⑤ 遇到 a = a,top - 1 = -1
⑥ top+1 = 0,c
⑦ top+1 = 1,cd
class Solution {
public String removeDuplicates(String S) {
char[] s = S.toCharArray();
int top = -1;
for (int i = 0; i < S.length(); i++) {
if (top == -1 || s[top] != s[i]) {
s[++top] = s[i];
} else {
top--;
}
}
return String.valueOf(s, 0, top + 1);
}
}
5. 132 模式
- 题目描述:给你一个整数数组
nums
,数组中共有n
个整数。132
模式的子序列 由三个整数nums[i]、nums[j]
和nums[k]
组成,并同时满足:i < j < k
和nums[i] < nums[k] < nums[j]
。如果nums
中存在132
模式的子序列 ,返回true
;否则,返回false
。 - 优秀思路:【单调栈-枚举法】从后往前遍历,维护一个单调递减栈存储
j
。只要当前值小于栈顶最小值,则将最小值出栈赋给k
,只要存在nums[i]<k
,则返回true
。- 注意1:
k
初始MIN_VALUE
,故只要k
存在其他值,一定是出栈来的,故站内一定存在比k
大的j
,即保证了[j,k]
结构的存在,所以只要前面有比k
小的i
,则说明132
结构存在 - 注意2:由于单调递减栈出栈采用的
while
循环,可以保证当前的k
一定是仅次于最大值的数
- 注意1:
class Solution {
public boolean find132pattern(int[] nums) {
int n = nums.length;
// 单调递减栈,从后往前遍历,随时存储最大值
// 若有值被弹出,则赋给k
Deque<Integer> d = new ArrayDeque<>();
int k = Integer.MIN_VALUE;
for (int i = n - 1; i >= 0; i--) {
if (nums[i] < k) return true;
while (!d.isEmpty() && d.peekLast() < nums[i]) {
k = d.pollLast();
}
d.addLast(nums[i]);
}
return false;
}
}
6. 扁平化嵌套列表迭代器(没看懂)
- 题目描述:给你一个嵌套的整型列表。请你设计一个迭代器,使其能够遍历这个整型列表中的所有整数。列表中的每一项或者为一个整数,或者是另一个列表。其中列表的元素也可能是整数或是其他列表。
- 优秀思路:【栈+dfs】可以用一个栈来代替方法一中的递归过程。具体来说,用一个栈来维护深度优先搜索时,从根节点到当前节点路径上的所有节点。由于非叶节点对应的是一个列表,我们在栈中存储的是指向列表当前遍历的元素的指针(下标)。每次向下搜索时,取出列表的当前指针指向的元素并将其入栈,同时将该指针向后移动一位。如此反复直到找到一个整数。循环时若栈顶指针指向了列表末尾,则将其从栈顶弹出。
public class NestedIterator implements Iterator<Integer> {
// 存储列表的当前遍历位置,而不是元素值
private Deque<Iterator<NestedInteger>> stack; // 栈的泛型为该类实现的接口
public NestedIterator(List<NestedInteger> nestedList) {
stack = new LinkedList<Iterator<NestedInteger>>(); // 栈的泛型为该类实现的接口
stack.push(nestedList.iterator()); // nestedList.iterator()表示目标列表nestedList在迭代器中的初始位置,即第一个元素处
}
@Override
public Integer next() {
// 由于保证调用 next 之前会调用 hasNext,直接返回栈顶列表的当前元素
return stack.peek().next().getInteger(); //getInteger()为内置函数,功能为获取列表当前位置的整数值
}
@Override
public boolean hasNext() {
while (!stack.isEmpty()) {
Iterator<NestedInteger> it = stack.peek();
// 递归获得下一位置的状态
// 1、遍历到当前列表末尾:出栈
if (!it.hasNext()) {
stack.pop();
continue;
}
// 2、未到末尾:若取出的元素是整数,则通过创建一个额外的列表将其重新放入栈中
NestedInteger next = it.next(); // 下一元素
if (next.isInteger()) { // isInteger() 为内置函数,功能为判断当前位置的列表元素是否为整数
List<NestedInteger> list = new ArrayList<NestedInteger>();
list.add(next);
stack.push(list.iterator());
return true;
}
stack.push(next.getList().iterator());
}
return false;
}
}
7. 去除重复字母
- 题目描述:给你一个字符串
s
,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。 - 优秀思路:
class Solution {
public String removeDuplicateLetters(String s) {
// 数组记录每个字符出现的次数和在字符串中存在的状态
boolean[] vis = new boolean[26];
int[] num = new int[26];
for (int i = 0; i < s.length(); i++) {
num[s.charAt(i) - 'a']++; // 下标表示字母序数(char-'a'),值表示出现次数
}
// 单调递减栈记录最终返回的字符串
StringBuffer sb = new StringBuffer();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
// 当其在字符串中不存在时
if (!vis[ch - 'a']) {
// 删除字符串尾部那些字典序大于ch的字母
while (sb.length() > 0 && sb.charAt(sb.length() - 1) > ch) {
// 直至某字母出现次数 = 0,即后面没了则保留
if (num[sb.charAt(sb.length() - 1) - 'a'] > 0) {
vis[sb.charAt(sb.length() - 1) - 'a'] = false;
sb.deleteCharAt(sb.length() - 1);
} else {
break;
}
}
// 删除结束后添加该字母到队尾
vis[ch - 'a'] = true; // 更新其存在状态
sb.append(ch);
}
// 无论是否存在,出现次数都-1
num[ch - 'a'] -= 1;
}
return sb.toString();
}
}
8. 移掉 K 位数字
- 题目描述:给你一个以字符串表示的非负整数
num
和一个整数k
,移除这个数中的k
位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。
- 我的思路(优秀,86%):【贪心+单调栈】利用
StringBuffer
模拟一个单调递增栈stack
,若当前数字cur >=
串尾数字last
,则直接入栈。否则删去所有last
。删除停止的条件有三个:① stack为空;② 长度条件:字符串再删除就不够要求的长度了;③ last ≤ cur。是否添加元素到队尾的条件:长度+1 ≤ len-k
(最大长度)。由于最终字符串中前面可能含0
,故从第一位非0
数字开始输出长度边界推导:
sb-1+(剩余长度)= sb-1+(len-i)>= 要求长度=len-k
两边约去得 sb+k-i >= 1,即 sb+k-i > 0
class Solution {
public String removeKdigits(String num, int k) {
if(k == 0) return num;
if(k == num.length()) return "0";
int len = num.length();
StringBuffer sb = new StringBuffer();
for(int i = 0;i < len;i++){
char c = num.charAt(i);
// 长度边界推导:sb-1+(剩余长度)= sb-1+(len-i)>= 要求长度=len-k
// 两边约去得 sb+k-i >= 1,即 sb+k-i > 0
while(sb.length() > 0 && sb.length() + k - i > 0 && sb.charAt(sb.length()-1) > c){
sb.deleteCharAt(sb.length()-1);
}
if(sb.length() < len-k){ // 未超过最大长度
sb.append(c);
}
}
String res = sb.toString();
int i = 0;
while(i < res.length()){
if(res.charAt(i) != '0') break;
i++;
}
// 从第一位非0数字开始输出
return i == res.length()? "0":res.substring(i); // 满足条件说明字符串中没有非零数字
}
}
9. 柱状图中最大的矩形(全场最快)
-
题目描述:给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
-
我的思路(优秀,94%):总体思路:遍历数组,每次以当前柱子的高度curHeight为矩形的高度,左右搜索其他高度不低于curHeight的柱子加入矩形,将当前柱子高度和搜索到的矩形面积中的最大值用于更新结果res。核心:设置一个数组记录preHeight每根柱子上次被搜索时矩形的高度,避免重复搜索。当遇到某根柱子的高度 = 该柱子上次被搜索过的矩形高度时则直接跳过。详情请参考我自己写的题解
class Solution {
int[] preHeight;
public int largestRectangleArea(int[] heights) {
// 特殊情况
if(heights.length == 0) return 0;
if(heights.length == 1) return heights[0];
preHeight = new int[heights.length]; // 存储每根柱子上次被横向搜索时的矩阵高度
int res = 0;
for(int i = 0;i < heights.length;i++){
if(heights[i] != preHeight[i]){
res = Math.max(res,Math.max(heights[i],transverseMaximumArea(heights,i)));
}
}
return res;
}
// 横向搜索到的矩阵面积:以当前柱子的高度为矩形的高度,左右横向搜索矩形其他部分
private int transverseMaximumArea(int[] heights,int position){
int leftLen = 0; // 左边高度不低于当前柱子的柱子数量
int rightLen = 0; // 右边高度不低于当前柱子的柱子数量
preHeight[position] = heights[position];
while(leftLen+1 <= position && heights[position-(leftLen+1)] >= heights[position]){ //搜索左边高度不低于当前柱子的柱子
preHeight[position-(leftLen+1)] = heights[position];
leftLen++;
}
while(position+rightLen+1 < heights.length && heights[position+(rightLen+1)] >= heights[position]){//搜索右边高度不低于当前柱子的柱子
preHeight[position+(rightLen+1)] = heights[position];
rightLen++;
}
return heights[position]*(1+leftLen+rightLen); // 搜索到的矩阵面积:高*宽
}
}
- 优秀思路2(99%):【双指针】与我的思路类似,但避免重复搜索的方式更快更简洁
class Solution {
public int largestRectangleArea(int[] heights) {
int maxarea = 0;
int len = heights.length;
for (int i = 0;i < len;i++){
if (heights[i]*len <= maxarea) continue;// 如果当前元素的值 × 长度都小于当前maxarea则没有继续查找的必要
int left = i;
int right = i;
while (left-1>=0 && heights[left-1] >= heights[i]) left--;
while (right+1 < len && heights[right+1] >= heights[i]) right++;
int area = (right-left+1)*heights[i];
if (area > maxarea) maxarea = area;
}
return maxarea;
}
}
- 优秀思路3(86%):【单调栈】遍历每个柱体,若当前的柱体高度大于等于栈顶柱体的高度,就直接将当前柱体入栈,否则若当前的柱体高度小于栈顶柱体的高度,说明当前栈顶柱体找到了右边的第一个小于自身的柱体,那么就可以将栈顶柱体出栈来计算以其为高的矩形的面积了。
class Solution {
public int largestRectangleArea(int[] heights) {
// 为了代码简便,在柱体数组的头和尾加了两个高度为 0 的柱体。
int[] tmp = new int[heights.length + 2];
System.arraycopy(heights, 0, tmp, 1, heights.length); // 将heights从0开始的部分复制到tmp中1开始的部分
Deque<Integer> stack = new ArrayDeque<>(); // 存储的是下标
int area = 0;
for (int i = 0; i < tmp.length; i++) {
// 对栈中柱体来说,其底下第一个柱体就是其「左边第一个小于自身的柱体」;
// 若当前柱体 i 的高度小于栈顶柱体的高度,说明 i 是栈顶柱体的「右边第一个小于栈顶柱体的柱体」。
// 因此以栈顶柱体为高的矩形的左右宽度边界就确定了,可以计算面积🌶️ ~
while (!stack.isEmpty() && tmp[i] < tmp[stack.peek()]) {
int h = tmp[stack.pop()]; // 栈顶出栈
area = Math.max(area, (i - stack.peek() - 1) * h);
}
stack.push(i);
}
return area;
}
}