单调栈本质上还是栈,表示的是一种特殊的数据结构,用来解决某类问题。单调栈,即存放在该栈中的元素是按照单调递增或单调递减的顺序存放。当有新的元素要进栈时,栈在调整的过程中,会打破原有的单调性,并重新建立单调性。所以单调栈的时间复杂度是O(n)。
1.栈内的元素保持单调性。
2. 元素在入栈前,会将破坏栈内元素单调性的元素进行出栈。
掌握某个知识点的最佳方法就是在运用到题目中,并能做到基本理解。将某个知识点的解题思想,转化为自己解决某类问题的新思路。
1.下一个更大的元素(496. 下一个更大元素 I - 力扣(LeetCode))
解决该题可以用暴力法,也就是我们用最常见最普通的方法去解决
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
// 入参判断
if(nums1 == null || nums2 == null || nums1.length == 0){
return null;
}
int[] res = new int[nums1.length];
Arrays.fill(res, -1);
for (int i = 0; i < nums1.length; i++) {
for (int j = 0; j < nums2.length; j++) {
if(nums1[i] == nums2[j]){
while( j +1 < nums2.length){
if(nums2[j + 1] > nums1[i]) {
res[i] = nums2[j + 1];
break;
}else{
j++;
}
}
}
}
}
return res;
}
}
上述方法的时间复杂度为O(n^2)。接下来,重点说的是用单调栈去解决该题。
思路:首先要明白,该如何去查找一个元素下一个比他大的元素,并记录结果。
可以先将要比较的元素的下一个元素放到栈中,当进行比较的时候直接用当前元素和栈顶元素进行比较。这样做的目的,在方便比较的同时,避免了对数组进行过多的操作,并且在出栈时也方便了许多。可以创建一个数组用来存放比较的结果。
每次栈内存放的元素,或者说是栈顶元素,一定是下一次要进行比较的元素的下一个元素。只要当前元素小于栈顶元素,说明栈顶元素就是该元素的下一个最大的元素。
最后在对两个数组进行遍历,找到目标数字在查找数组中的位置,直接获取该元素在存放比较的结果中的数组中的值,就是最终的结果。
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
// 入参判断
if (nums1 == null || nums2 == null || nums1.length == 0) {
return null;
}
// 用来记录在nums数组中,在当前元素的后方是否有比当前元素大的元素
int[] result = new int[nums2.length];
Stack stack = new Stack<>();
// 遍历数组(因为是寻找下一个更大的元素,所以从数组的最后方开始遍历,将当下要比较的元素的下一个元素先入栈)
for (int i = nums2.length - 1; i >= 0; i--) {
// 如果当前元素大于栈顶元素,就将栈顶元素出栈
while (!stack.isEmpty() && stack.peek() <= nums2[i]) {
stack.pop();
}
result[i] = stack.isEmpty() ? -1 : stack.peek(); //如果没有比当前元素的大的元素,存放-1,否则将比当前元素大的元素存放到该数组中
stack.push(nums2[i]); //入栈
}
//创建该数组用于记录最终比较的结果
int [] res = new int[nums1.length];
for (int i = 0; i < nums1.length; i++) {
for (int j = 0; j < nums2.length; j++) {
// 遍历找到nums数组中的nums1,并将上一步记录在数组中的值赋给新数组,对应的就是该元素下一个最大的元素
if(nums1[i] == nums2[j]){
res[i] = result[j];
break;
}
}
}
return res;
}
}
public static void main(String[] args) { int[] nums1 = {4, 1, 2}; int[] nums2 = {1, 3, 4, 2}; Demo demo = new Demo(); System.out.println(Arrays.toString(demo.nextGreaterElement(nums1,nums2))); }
2.去除重复字母(316. 去除重复字母 - 力扣(LeetCode))
该题同样也是利用单调栈来解决,还涉及到了字典序的概念。
字典序:通常是指按照字典中字符出现的先后顺序排列的 。这个概念最初用于英文单词在字典中的排列顺序。
该题总结下来大概三点:
① 去重,去除字符串中的重复字符。
② 在去重后的字符串中不能打乱原字符串中每个字符的相对顺序。
③ 在上述的两条要求中,要求最后返回的结果字典序最小。
例如:一个字符串 "babc" ,在去重后有两种结果,"bac"和"abc",因为"abc"的字典序更小,所以最终的结果应该是"abc"。
代码如下:
class Solution {
public String removeDuplicateLetters(String s) {
StringBuilder sb = new StringBuilder(); //用来做输出
// 入参判断
if (s == null || s.length() < 2) {
return s;
}
Stack stack = new Stack<>();
// 输入字符为对应的ASCII值(0~256)
int[] count = new int[256];
// 统计每个字符出现的次数,并记录在count数组中
for (int i = 0; i < s.length(); i++) {
count[s.charAt(i)]++;
}
// boolean数组初始值为false,当有字符入栈时,就把该字符标记为true
boolean[] inStack = new boolean[256];
for (int i = 0; i < s.length(); i++) {
// 遍历过某个字符,就将该字符的数量-1
count[s.charAt(i)]--;
// 某个字符在boolean数组中为true,说明该字符已入栈,直接continue跳出本次循环
if (inStack[s.charAt(i)]) {
continue;
}
// 判断是否要进行出栈
while (!stack.isEmpty() && stack.peek() > s.charAt(i)) {
// 如果满足该条件,说明后面再没有该元素,就不能进行出栈
if (count[stack.peek()] == 0) {
break;
} else {
// 否则出栈,并把该字符标记为false
inStack[stack.pop()] = false;
}
}
// 如果不满足if(inStack[s.charAt(i)]),就将该字符入栈,并把其标记为true
stack.push(s.charAt(i));
inStack[s.charAt(i)] = true;
}
// 将栈中的元素附加到此序列
while (!stack.isEmpty()) {
sb.append(stack.pop());
}
return sb.reverse().toString(); //将该字符串序列反转并返回一个字符串对象
}
}