栈
栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,我们叫作顺序栈,用链表实现的栈,我们叫作链式栈
实现方法:
package com.smao.leetcode;
/**
* 数组式链表
*/
public class ArrayStack {
private String[] arrayStack;
private int size;//栈中元素个数
private int count;//栈的容量
public ArrayStack(int count) {
this.arrayStack = new String[count];
this.count = count;
this.size = 0;
}
public boolean push(String item){
if(this.count == this.size){
String[] arrayStackNew = new String[this.count*2];
for(int i=0;i<arrayStack.length;i++){
arrayStackNew[i] = arrayStack[i];
this.count = this.count*2;
}
this.arrayStack = arrayStackNew;
}
arrayStack[this.size] = item;
this.size++;
return true;
}
public String pop(){
if(this.size==0){
return null;
}
String temp = arrayStack[size-1];
size--;
return temp;
}
public static void main(String[] args) {
ArrayStack arrayStack = new ArrayStack(10);
arrayStack.push("1");
arrayStack.push("2");
arrayStack.push("3");
arrayStack.push("4");
arrayStack.push("5");
System.out.println(arrayStack.size);
arrayStack.push("1");
arrayStack.push("2");
arrayStack.push("3");
arrayStack.push("4");
arrayStack.push("5");
arrayStack.push("5");
arrayStack.pop();
System.out.println(arrayStack.size);
}
}
package com.smao.leetcode;
import com.cc.common.base.ListNode;
/**
* 链表实现栈
*/
public class ListNodeStack {
private ListNode listNode;
private int size;//栈内元素个数
private ListNode end;//结尾
private ListNode pre;//end前面节点
public ListNodeStack(int root) {
this.listNode = new ListNode(root);
this.end = this.listNode;
this.pre = null;
this.size++;
}
public boolean push(int node){
this.pre = this.end;
this.end.next = new ListNode(node);
this.end = end.next;
this.size++;
return true;
}
public void pop(){
this.end = pre;
this.pre.next = null;
this.size--;
}
public static void main(String[] args) {
ListNodeStack listNodeStack = new ListNodeStack(1);
listNodeStack.push(2);
listNodeStack.push(3);
listNodeStack.push(4);
listNodeStack.push(5);
System.out.println(listNodeStack.size);
listNodeStack.pop();
System.out.println(listNodeStack.size);
}
}
对于出栈操作来说,我们不会涉及内存的重新申请和数据的搬移,所以出栈的时间复杂度仍然是O(1)。但是,对于入栈操作来说,情况就不一样了。当栈中有空闲空间时,入栈操作的时间复杂度为O(1)。但当空间不够时,就需要重新申请内存和数据搬移,所以时间复杂度就变成了O(n)。把耗时多的入栈操作的时间均摊到其他入栈操作上,平均情况下的耗时就接近O(1)。
课后思考
1. 我们在讲栈的应用时,讲到用函数调用栈来保存临时变量,为什么函数调用要用“栈”来保存临时变量呢?用其他数据结构不行吗?
其实,我们不一定非要用栈来保存临时变量,只不过如果这个函数调用符合后进先出的特性,用栈这种数据结构来实现,是最顺理成章的选择。
从调用函数进入被调用函数,对于数据来说,变化的是什么呢?是作用域。所以根本上,只要能保证每进入一个新的函数,都是一个新的作用域就可以。而要实现这个,用栈就非常方便。在进入被调用函数的时候,分配一段栈空间给这个函数的变量,在函数结束的时候,将栈顶复位,正好回到调用函数的作用域内。(仅供参考)
2. 我们都知道, JVM内存管理中有个“堆栈”的概念。栈内存用来存储局部变量和方法调用,堆内存用来存储Java中的对象。那JVM里面的“栈”跟我们这里说的“栈”是不是一回事呢?如果不是,那它为什么又叫作“栈”呢?
内存中的堆栈和数据结构堆栈不是一个概念,可以说内存中的堆栈是真实存在的物理区,数据结构中的堆栈是抽象的数据存储结构。
内存空间在逻辑上分为三部分:代码区、静态数据区和动态数据区,动态数据区又分为栈区和堆区。
代码区:存储方法体的二进制代码。高级调度(作业调度)、中级调度(内存调度)、低级调度(进程调度)控制代码区执行代码的切换。
静态数据区:存储全局变量、静态变量、常量,常量包括final修饰的常量和String常量。系统自动分配和回收。
栈区:存储运行方法的形参、局部变量、返回值。由系统自动分配和回收。
堆区: new一个对象的引用或地址存储在栈区,指向该对象存储在堆区中的真实数据。(仅供参考)
队列
先进先出
使用数组实现队列
package com.smao.leetcode.queue;
/**
* 用数组实现一个队列
*/
public class ArrayQueue {
private String[] queue;
private int head;
private int tail;
private int n;
public ArrayQueue(int count) {
this.queue = new String[count];
n = count;
}
public boolean push(String num){
if(n==tail){
if(head==0){
return false;
}
for(int i=head;i<tail;++i){
queue[i-head] = queue[i];
}
head = 0;
tail-=head;
}
queue[tail] = num;
++tail;
return true;
}
public String pop(){
if(head==tail){
return null;
}
String item = queue[head];
++head;
return item;
}
public static void main(String[] args) {
ArrayQueue arrayQueue = new ArrayQueue(10);
arrayQueue.push("1");
System.out.println(arrayQueue.pop());
arrayQueue.push("2");
System.out.println(arrayQueue.pop());
arrayQueue.push("3");
System.out.println(arrayQueue.pop());
arrayQueue.push("4");
System.out.println(arrayQueue.pop());
arrayQueue.push("5");
arrayQueue.push("1");
arrayQueue.push("2");
arrayQueue.push("3");
arrayQueue.push("4");
arrayQueue.push("5");
arrayQueue.push("5");
}
}
使用链表实现队列
package com.smao.leetcode.queue;
import com.cc.common.base.ListNode;
/**
* 用链表实现一个队列
*/
public class ListNodeQueue {
private ListNode listNode;
private ListNode tail;
private ListNode head;
public ListNodeQueue(int root) {
listNode = new ListNode(root);
tail = listNode;
head = listNode;
}
public void push(int num){
tail.next = new ListNode(num);
tail = tail.next;
}
public Integer pop(){
int temp = head.val;
head = head.next;
return temp;
}
public static void main(String[] args) {
ListNodeQueue listNodeQueue = new ListNodeQueue(0);
listNodeQueue.push(1);
listNodeQueue.push(2);
listNodeQueue.push(3);
listNodeQueue.push(4);
listNodeQueue.push(5);
listNodeQueue.push(6);
listNodeQueue.push(7);
listNodeQueue.push(8);
listNodeQueue.push(9);
listNodeQueue.push(10);
listNodeQueue.push(11);
listNodeQueue.pop();
}
}
实现循环队列(当队满时, (tail+1)%n=head)
package com.smao.leetcode.queue;
/**
* 循环队列
*/
public class CircularQueue {
public String[] item;
public int head;
public int tail;
public int n;
public CircularQueue(int count) {
this.n = count;
item = new String[count];
}
public boolean push(String str){
if((tail+1)%n == head){
return false;
}
item[tail] = str;
tail = (tail+1)%n;
return true;
}
public String pop(){
if(head == tail){
return null;
}
String str = item[head];
head = (head+1)%n;
return str;
}
}
阻塞队列
就是在队列为空的时候,从队头取数据会被阻塞。因为此时还没有数据可取,直到队列中有了数据
才能返回;如果队列已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后再返回。
递归
只要同时满足以下三个条件,就可以用递归来解决。
1.一个问题的解可以分解为几个子问题的解
2.这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样
3.存在递归终止条件
递归需要注意的问题:
1、递归代码要警惕堆栈溢出
函数调用会使用栈来保存临时变量。每调用一个函数,都会将临时变量封装为栈帧压入内存栈,等函数执行完成返回时,才出栈。系统栈或者虚拟机栈空间一般都不大。如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险。
2、递归代码要警惕重复计算
可以通过公共变量存储已经计算过的结果
3、怎么将递归代码改写为非递归代码?
递归有利有弊,利是递归代码的表达力很强,写起来非常简洁;而弊就是空间复杂度高、有堆栈溢出的风险、存在重复计算、过多的函数调用会耗时较多等问题。所以,在开发过程中,我们要根据实际情况来选择是否需要用递归的方式来实现。
练习题
package com.smao.leetcode;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
/**
* 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
*
* 有效字符串需满足:
*
* 左括号必须用相同类型的右括号闭合。
* 左括号必须以正确的顺序闭合。
* 注意空字符串可被认为是有效字符串。
*
* 示例 1:
*
* 输入: "()"
* 输出: true
* 示例 2:
*
* 输入: "()[]{}"
* 输出: true
* 示例 3:
*
* 输入: "(]"
* 输出: false
* 示例 4:
*
* 输入: "([)]"
* 输出: false
* 示例 5:
*
* 输入: "{[]}"
* 输出: true
*
* 链接:https://leetcode-cn.com/problems/valid-parentheses
*/
public class ValidParentheses {
public static boolean isValid(String s) {
Stack stack = new Stack();
Map<Character, Character> map = new HashMap<>();
map.put(')', '(');
map.put('}', '{');
map.put(']', '[');
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (stack == null || stack.size()==0) {
if (map.keySet().contains(c)) {
return false;
} else {
stack.push(c);
continue;
}
}
if (map.keySet().contains(c)) {
if(map.get(c)!=stack.pop()){
return false;
}
}else{
stack.push(c);
}
}
return stack.size() == 0;
}
public static void main(String[] args) {
System.out.print(isValid("()"));
}
}
package com.smao.leetcode;
import java.util.HashMap;
import java.util.Map;
/**
* 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
*
* 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
*
* 注意:给定 n 是一个正整数。
*
* 示例 1:
*
* 输入: 2
* 输出: 2
* 解释: 有两种方法可以爬到楼顶。
* 1. 1 阶 + 1 阶
* 2. 2 阶
* 示例 2:
*
* 输入: 3
* 输出: 3
* 解释: 有三种方法可以爬到楼顶。
* 1. 1 阶 + 1 阶 + 1 阶
* 2. 1 阶 + 2 阶
* 3. 2 阶 + 1 阶
*
* 来源:力扣(LeetCode)
* 链接:https://leetcode-cn.com/problems/climbing-stairs
* 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
*/
public class ClimbingStairs {
public static int climbStairs(int n) {
int[] array = new int[n+1];
return clambStairsNew(n,array);
}
private static int clambStairsNew(int n,int[] array) {
if(n==1){
return 1;
}
if(n==2){
return 2;
}
if(array[n]!=0){
return array[n];
}
array[n] = clambStairsNew(n-1,array)+clambStairsNew(n-2,array);
return array[n];
}
public static void main(String[] args) {
System.out.println(climbStairs(44));
}
}
来源:CSDN
作者:怂喵
链接:https://blog.csdn.net/jin343229836/article/details/103398991