系列文章目录
刷题笔记(一)–数组类型:二分法
刷题笔记(二)–数组类型:双指针法
刷题笔记(三)–数组类型:滑动窗口
刷题笔记(四)–数组类型:模拟
刷题笔记(五)–链表类型:基础题目以及操作
刷题笔记(六)–哈希表:基础题目和思想
刷题笔记(七)–字符串:经典题目
刷题笔记(八)–双指针:两数之和以及延伸
刷题笔记(九)–字符串:KMP算法
前言
这一篇博客是关于栈和队列的基本使用的,也是基础的一些操作。
栈
这里关于栈的性质主要就是是一个:先进先出。具体的可以看我这篇博客
如果感觉再看比较麻烦的话,那就看下面的这个图吧。
队列
关于队列的性质:先进后出。具体的可以看我这篇博客
其中包括了我们普通的队列:
循环队列:
当然还有双端队列。这里双端队列就不给图了,双端队列和普通的队列主要的差别就是:双端队列都可以入队和出队。
题录
232. 用栈实现队列
题目链接如下;
题目截屏如下:
这道题思路其实很简单,就是用两个栈来回的倒腾。但是呢,仔细想,这个倒腾就很有学问。如果说方式不对的话,那么就会耗费很多功夫。
public class 用栈实现队列 {
Stack<Integer> a;
Stack<Integer> b;
public 用栈实现队列() {
a = new Stack();//用来入队
b = new Stack();//用来出队
}
public void push(int x) {
a.push(x);
}
public int pop() {
while(b.empty()){
while(!a.empty()){
b.push(a.pop());
}
}
return b.pop();
}
public int peek() {
while(b.empty()){
while(!a.empty()){
b.push(a.pop());
}
}
return b.peek();
}
public boolean empty() {
return a.empty() && b.empty();
}
}
225. 用队列实现栈
题目链接如下:
题目截图如下:
其实还是一个倒腾的过程,和上面的题一样的思想。
public class 用队列实现栈 {
Queue<Integer> a = null;//用来入栈
Queue<Integer> b = null;//用来出栈
public 用队列实现栈() {
a = new LinkedList<>();
b = new LinkedList<>();
}
public void push(int x) {
//push方法其实就是注意一个地方,你要保证a永远为null
a.offer(x);
while(!b.isEmpty()){
a.offer(b.poll());
}
Queue tmp = a;
a = b;
b = tmp;
}
public int pop() {
return b.poll();
}
public int top() {
return b.peek();
}
public boolean empty() {
return b.isEmpty();
}
}
20. 有效的括号
题目链接如下:
题目截图如下:
这个题就是一个很基础的对于栈的特性考察的题目,如果是左括号就入栈,是右括号就判断要不要出栈,或者说是报错。
public class 有效的括号 {
public boolean isValid(String s) {
//定义一个栈用来判断括号是否匹配
Stack<Character> stack = new Stack<>();
char[] arr = s.toCharArray();
for (int i = 0; i < arr.length; i++) {
//如果是左括号统一入栈
if (arr[i] == '(' || arr[i] == '[' || arr[i] == '{') {
stack.push(arr[i]);
continue;
} else {
//如果不是左括号,判断是否栈空,如果空就直接报错,因为匹配不上了
if(stack.empty()){
return false;
}
char tmp = stack.peek();
//不空的话就进行是否匹配的判断,不匹配也报错,因为又匹配不上了
if ((arr[i] == ']' && tmp == '[') ||
(arr[i] == ')' && tmp == '(') ||
(arr[i] == '}' && tmp == '{')) {
stack.pop();
}else {
return false;
}
}
}
//最后如果是栈空,那么才证明匹配
return stack.empty();
}
}
1047. 删除字符串中的所有相邻重复项
题目链接如下:
题目截图如下:
emmmm,这个题吧,怎么说呢,两种解法,第一种解法:双指针法
public class 删除字符串中所有的相邻重复项__双指针法 {
public String removeDuplicates(String s) {
char[] arr = s.toCharArray();
int i = -1; //默认(前边数据,i)是已经处理好的数据
int j = 0;//j用来遍历
while(j < arr.length){
//如果说重复了,那么就说明当前i下标的值我们不需要,回退就好
if(i >= 0 && arr[i] == arr[j]){
i--;
}else{
//如果不重复,那就说明需要,暂时先进行赋值
i++;
arr[i] = arr[j];
}
j++;
}
//现在(0,i]都是不重复的了
return String.copyValueOf(arr,0,i+1);
}
}
第二种方法:栈
public class 删除字符串中所有的相邻重复项__栈的使用 {
public static String removeDuplicates(String s) {
char[] arr = s.toCharArray();
//用一个栈来实现题目要求
Stack<Character> stack = new Stack<>();
for (int i = 0; i < arr.length; i++) {
//如果说栈为空或者说栈顶元素和当前下标元素不同就入栈
if(stack.empty() || stack.peek() != arr[i]){
stack.push(arr[i]);
}else{//相同就出栈
stack.pop();
}
}
//最后定义一个字符拼接器来接收结果
StringBuilder str = new StringBuilder();
while(!stack.empty()) str.append(stack.pop());
//接收完之后返回的时候别忘了反转一下
return str.reverse().toString();
}
public static void main(String[] args) {
String a = "abbaca";
System.out.println(removeDuplicates(a));
}
}
150. 逆波兰表达式求值
题目链接如下:
题目截图如下:
还是对于栈的性质的运用
public class 逆波兰表达式 {
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < tokens.length; i++) {
String s = tokens[i];
if(isNumber(s)){
//如果不是运算符,就直接入栈就行
stack.push(Integer.valueOf(s));
}else{
//如果是运算符,出栈两个元素,进行运算
int a = stack.pop();
int b = stack.pop();
//这里注意,b在前,a在后
if(s.equals("+")) stack.push(a + b);
if(s.equals("-")) stack.push(b - a);
if(s.equals("*")) stack.push(a * b);
if(s.equals("/")) stack.push(b / a);
}
}
return stack.pop();
}
//判断一下当前字符是不是一个数字
public boolean isNumber(String s){
return !((s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")));
}
}
239. 滑动窗口最大值
题目链接如下:
题目截图如下:
这道题有两种解法,一种就是暴力遍历,每一个窗口当中都有最大值
一般来说,我们确实可以通过这种方法求解,笔者第一时间也是这样子解决的,这样解决的时间复杂度是O(m * n),但是嗷,这样有一个很大的问题,就是当这个数组很大很大的时候,这个时候恰好k也不小,那么时间复杂度就很大,就会超出时间的限制。其中有一个测试用例就是50000大小的滑动窗口,这简直就是很离谱。
所以我们这里要用另一种方法来解决,也就是单调队列这种思想。先上代码
public class 滑动窗口最大值 {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> dq = new LinkedList<>();//定义一个双端链表用来筛选出最大值
int len = nums.length;
int[] arr = new int[len - k + 1];//定义一个刚好可以完全接收当前窗口最大值的数组
for (int i = 0; i < len; i++) {
//头:首先就是判断一下当前窗口的大小,如果说当前窗口大于k了,就要丢掉队头的元素
if(!dq.isEmpty() && i - k + 1 > dq.getFirst()){
dq.poll();//如果说当前的窗口值大于k,就丢弃掉头元素
}
//尾:如果说当前要入队的下标对应数组元素,大于当前队尾下标对应数组元素,就把这个队尾元素删除
while(!dq.isEmpty() && nums[i] > nums[dq.getLast()]){
dq.pollLast();
}
//尾:现在这么一搞之后,就可以确定队首元素对应的下标值为最大
dq.add(i);
//头:让arr来记录当前窗口最大值
if(i >= k - 1){
arr[i - k + 1] = nums[dq.getFirst()];
}
}
return arr;
}
}
这里主要说明一个事情,就是双端队列dq里面存的是数组的下标,是下标,是下标,重要的事情说三遍!!!不是存的元素。为什么要存下标呢?
存下标原因有两个:1.可以通过下标直接操作或者表示数组元素。 2.用下标来存储的话该队列是一个单调队列,可以很轻易的判断出当前滑动窗口的大小。
笔者在这里改了又改,删了又删,最后还是没有找到一个比较合理的方式来进行讲解。先停一下吧这里,笔者的解题思路其实已经放在了代码当中,其中需要注意的地方也进行了说明。这里的话如果后面能找到合适的方式去讲解的话,笔者再把这里补上。
总结
其实没有什么特别需要说的,都是一些比较基础的题目,加油吧!