栈和队列
20. 有效的括号(栈的简单应用)
class Solution {
public boolean isValid(String s) {
/**
分析:
括号匹配问题是栈的知识
*/
// 若是奇数,则必然不会匹配
if(s.length() % 2 == 1){
return false;
}
Stack<Character> stack = new Stack<>();
for(int i = 0 ;i < s.length(); i++){
if(s.charAt(i) == '(' || s.charAt(i) == '{'||s.charAt(i) == '['){
stack.push(s.charAt(i));
}else{
// 出现 ) ] } 但是栈空了
if(stack.isEmpty()){
return false;
}
// 取栈顶指针,栈空是不允许 pop 的,所以前面要预判
char top = stack.pop();
if(s.charAt(i) == ')' && top != '(' ) return false;
if(s.charAt(i) == ']' && top != '[' ) return false;
if(s.charAt(i) == '}' && top != '{' ) return false;
}
}
return stack.isEmpty();
}
}
71. 简化路径(双端队列)
class Solution {
public String simplifyPath(String path) {
/**
分析:
首先考虑到的是字符串分割api,数据结构采用双端队列,然后根据已知条件进行边界处理。
若遇到 “”或者 “.”则表示本层路径,不进队列
若遇到 “..”且队列内不空,则弹出上一层字符串
若遇到“..”且队列为空,则跳过该次循环,不进队列
若遇正常的字符,则进队列
最后整个队列为空,直接返回 “/”
否则,取出队列中的元素,构建绝对路径字符串
*/
String[] dirs = path.split("/");
// 使用双端队列
ArrayDeque<String> deque = new ArrayDeque<>();
// 遍历
for(String dir:dirs){
if(dir.equals("") || dir.equals(".")){
continue;
}else if(dir.equals("..") && !deque.isEmpty()){
deque.pop();
}else if(dir.equals("..") && deque.isEmpty()){
continue;
}else{
deque.push(dir);
}
}
// 判断队列是否为空
if(deque.isEmpty()){
return "/";
}
// 构建绝对路径
StringBuilder sb = new StringBuilder();
while(!deque.isEmpty()){
sb.append("/");
sb.append(deque.removeLast());
}
return sb.toString();
}
}
151. 翻转字符串里的单词(双端队列)
注意:本题的解法,时间复杂度是O(n),空间复杂度为O(n),题目要求空间复杂度为O(1),所以考虑使用快慢指针原地修改数组
class Solution {
public String reverseWords(String s) {
/**
分析:
首先使用字符串分割api,
*/
String[] words = s.split(" ");
// 构建双端队列
ArrayDeque<String> deque = new ArrayDeque<>();
for(String word:words){
if(word.equals("")){
continue;
}else{
// 这里是当做队列用了
deque.offer(word);
}
}
StringBuilder sb = new StringBuilder();
int size = deque.size();
for(int i = 0; i < size - 1 ; i++){
sb.append(deque.removeLast());
sb.append(" ");
}
sb.append(deque.removeLast());
return sb.toString();
}
}
227. 基本计算器 II(栈的应用)
注意:
本题的一些小细节,首先是两个字符或者三个字符如何进位累加
其次是默认使用 + 号,使用sign保存当前字符,用于下一轮的判断
class Solution {
public int calculate(String s) {
/**
分析:
栈的应用场景:计算器
乘除的优先级高于加减法,所以把乘除的结果压入栈内,其次减法可以看做负数压入栈内,然后使用加法计算。
*/
// 保存上一个符号,初始为 +
char sign = '+';
Stack<Integer> numStack = new Stack<>();
// 保存当前数字,如12是两个字符,需要进位累加
int num = 0;
int result = 0;
for(int i = 0; i < s.length(); i++){
char cur = s.charAt(i);
if(cur >= '0'){
// 记录当前数字
num = num * 10 + cur - '0';
}
if( (cur < '0' && cur != ' ') || i == s.length() - 1){
// 判断上一个符号是什么
switch(sign){
// 当前符号前的数字直接压栈
case '+':
numStack.push(num);break;
case '-':
numStack.push(-num);break;
case '*':
numStack.push(numStack.pop() * num); break;
case '/':
numStack.push(numStack.pop() / num);break;
}
// 记录当前符号
sign = cur;
// 数字清零
num = 0;
}
}
// 将栈内剩余数字累加
while(!numStack.isEmpty()){
result += numStack.pop();
}
return result;
}
}
739. 每日温度(单调栈)
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
/**
分析:暴力法
*/
int[] res = new int[temperatures.length];
for(int i = 0; i < temperatures.length - 1; i++){
for(int j = i + 1; j < temperatures.length; j++){
if(temperatures[j] > temperatures[i]){
res[i] = j - i;
break;
}
}
}
return res;
}
}
单调栈
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
/**
分析:
如何想到单调栈???
通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置。
本题就是找到一个元素右边第一个比自己大的元素---单调增栈
明确两点:
1.单调栈里存放元素是什么? 是元素的下标
2.单调栈里元素应该是递增还是递减? 从栈顶到栈底看是递增的(因为要寻找右边第一个比自己大的元素)
*/
// 使用栈
Stack<Integer> stack = new Stack<>();
// 定义结果
int [] res = new int[temperatures.length];
// 遍历数组
for(int i = 0; i < temperatures.length; i++){
// 栈必须不空,然后比较后续的温度大小,后面温度比栈顶温度大的话,则找到了目标
while(!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]){
// 栈顶弹出
int id = stack.pop();
// 计算 右边第一个比自己大的距离
res[id] = i - id;
}
// 储存下标到栈
stack.push(i);
}
return res;
}
}
503. 下一个更大元素 II(单调栈+%思想)
class Solution {
public int[] nextGreaterElements(int[] nums) {
/**
分析:
求解右边第一个更大的元素 ==》使用单调栈
这道题比起739每日温度,也就多了一个条件,那就是循环数组。
循环无非就循环2次,那么这个条件也就是限定死了, 2 * length即可。循环常见的操作就是 % 操作
*/
Stack<Integer> stack = new Stack<>();
int len = nums.length;
int[] res = new int[len];
// 初始化res为 -1的数组
Arrays.fill(res,-1);
// 遍历数组
for(int i = 0; i < 2 * len; i++){
// 单调栈操作,栈内存储的是下标
while(!stack.isEmpty() && nums[i % len] > nums[stack.peek()]){
int id = stack.pop();
res[id] = nums[ i % len];
}
// 进栈
stack.push(i % len);
}
return res;
}
}
496. 下一个更大元素 I(单调栈+哈希表)
自己ac的垃圾代码…
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
/**
分析:
从739每日温度可以得知,单调栈的使用场景:求解最左/右边第一个比自己大/小的值
本题就是使用单调栈求解。
亲们记住,一但要求下一个更大的元素,就是用单调栈解,力扣题库相似的题目都是这个解法。
*/
int [] res = new int[nums1.length];
// 构建nums1和nums2下标的映射
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0; i < nums1.length; i++){
for(int j = 0; j < nums2.length; j++){
if(nums1[i] == nums2[j]){
map.put(i,j);
}
}
}
// 遍历map
for(int key:map.keySet()){
// 使用栈
Stack<Integer> stack = new Stack<>();
// 进栈
stack.push(map.get(key));
for(int i = map.get(key); i < nums2.length; i++){
if(!stack.isEmpty() && nums2[i] > nums2[stack.peek()]){
res[key] = nums2[i];
break;
}
}
}
// 将数组中为0的数设置为-1
for(int i = 0; i < res.length; i++){
if(res[i] == 0){
res[i] = -1;
}
}
return res;
}
}
优化后的代码
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
/**
分析:
从739每日温度可以得知,单调栈的使用场景:求解最左/右边第一个比自己大/小的值
本题就是使用单调栈求解。
亲们记住,一但要求下一个更大的元素,就是用单调栈解,力扣题库相似的题目都是这个解法。
*/
// 优化一下上面代码
// 不要一开始就使用map来隐射两者的下标,这样不够高效
// map用来映射nums2中右边第一个大于该数的值,然后遍历nums1取值就ok了
// 本质上还是单调栈,只不过使用了map做了一下映射
int []res = new int[nums1.length];
Map<Integer,Integer> map = new HashMap<>();
// 单调栈
Stack<Integer> stack = new Stack<>();
// 遍历nums2
for(int num:nums2){
// 构建递增栈
while(!stack.isEmpty() && num > stack.peek()){
// 存入哈希表
// 映射关系为栈顶值-右边第一个大于该值的数
map.put(stack.pop(),num);
}
stack.push(num);
}
// 遍历nums1,取值
for(int i = 0; i < nums1.length; i++){
res[i] = map.getOrDefault(nums1[i],-1);
}
return res;
}
}
946. 验证栈序列(栈的应用)
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
/**
分析:
使用栈解决问题,若最后栈空则返回true
*/
Stack<Integer> stack = new Stack<>();
// 定义poped数组的下标
int index = 0;
// 遍历push数组
for(int num:pushed){
stack.push(num);
while(!stack.isEmpty() && popped[index] == stack.peek()){
// 取出数据
stack.pop();
index++;
}
}
// 判断最后结果
return stack.isEmpty();
}
}
单调栈总结
public class LeftLastLarger {
/*
题目:找出数组中左边离我最近比我da的元素
一个整数数组 nums,找到每个元素:左边第一个比我da的下标位置,没有则用 -1 表示。
输入:[1, 2]
输出:[-1, -1]
解释:
因为元素 2 的左边离我最近且比我da的位置应该是 -1,
第一个元素 1 左边没有比 1 小的元素,所以应该输出 -1。
*/
public int[] findLeftLastLarge(int[] nums) {
int[] ans = new int[nums.length];
// 提前设置 -1值
// Arrays.fill(ans,-1);
ArrayDeque<Integer> stack = new ArrayDeque<>();
// 时间复杂度:O(n)
for (int i = nums.length - 1; i >= 0; i--) {
int x = nums[i];
// 单调递减栈
while (!stack.isEmpty() && x > nums[stack.peek()]) {
ans[stack.peek()] = i;
stack.pop();
}
stack.push(i); // 索引
}
while (!stack.isEmpty()) {
ans[stack.peek()] = -1;
stack.pop();
}
return ans;
}
}
public class LeftLastSmaller {
/*
题目:找出数组中左边离我最近比我小的元素
一个整数数组 nums,找到每个元素:左边第一个比我小的下标位置,没有则用 -1 表示。
输入:[1, 2]
输出:[-1, 0]
解释:
因为元素 2 的左边离我最近且比我小的位置应该是 nums[0],
第一个元素 1 左边没有比 1 小的元素,所以应该输出 -1。
*/
public int[] findLeftLastSmall(int[] nums) {
int[] ans = new int[nums.length];
ArrayDeque<Integer> stack = new ArrayDeque<>();
// 时间复杂度:O(n)
for (int i = nums.length - 1; i >= 0; i--) {
int x = nums[i];
// 单调递增栈
while (!stack.isEmpty() && x < nums[stack.peek()]) {
ans[stack.peek()] = i;
stack.pop();
}
stack.push(i); // 索引
}
while (!stack.isEmpty()) {
ans[stack.peek()] = -1;
stack.pop();
}
return ans;
}
}
public class RightFirstLarger {
/*
题目:找出数组中右边第一个比我大的元素
一个整数数组 nums,找到每个元素:右边第一个比我大的下标位置,没有则用 -1 表示。
输入:[5, 6]
输出:[1, -1]
解释:
因为元素 5 的右边离我最近且比我大的位置应该是 nums[1],
最后一个元素 6 右边没有比 6 小的元素,所以应该输出 -1。
*/
public int[] findRightLarge(int[] nums) {
int[] ans = new int[nums.length];
ArrayDeque<Integer> stack = new ArrayDeque<>();
// 时间复杂度:O(n)
for (int i = 0; i < nums.length; i++) {
int x = nums[i];
// 单调递减栈
while (!stack.isEmpty() && x > nums[stack.peek()]) {
ans[stack.peek()] = i;
stack.pop();
}
stack.push(i); // 索引
}
while (!stack.isEmpty()) {
ans[stack.peek()] = -1;
stack.pop();
}
return ans;
}
}
public class RightFirstSmaller {
/*
题目:找出数组中右边第一个比我小的元素
一个整数数组 nums,找到每个元素:右边第一个比我小的下标位置,没有则用 -1 表示。
输入:[5, 2]
输出:[1, -1]
解释:
因为元素 5 的右边离我最近且比我小的位置应该是 nums[1],
最后一个元素 2 右边没有比 2 小的元素,所以应该输出 -1。
*/
public int[] findRightSmall(int[] nums) {
int[] ans = new int[nums.length];
ArrayDeque<Integer> stack = new ArrayDeque<>();
// 时间复杂度:O(n)
for (int i = 0; i < nums.length; i++) {
int x = nums[i];
// 单调递增栈
while (!stack.isEmpty() && x < nums[stack.peek()]) {
ans[stack.peek()] = i;
stack.pop();
}
stack.push(i); // 索引
}
while (!stack.isEmpty()) {
ans[stack.peek()] = -1;
stack.pop();
}
return ans;
}
}