- 什么是链表、队列、栈?
他们都是一种逻辑上的数据结构,都可以使用数组来实现
- 链表:是一种拥有后或前后节点指针的数据结构,前者为单向链表,后者为双向链表,链表查找慢,增删快
- 队列:特点是先进先出,查找和增删都慢
- 栈:特点是先进后出,查找和增删都慢
- 栈和队列常见问题
- 如何使用固定数组实现队列、栈?
- 队列:使用三个变量分别维护,size队列中元素的数量,begin出队元素索引,end添加到队列元素索引,是否可以向队列中添加元素,判断size的大小即可,如果size小于队列长度即可添加,反之不可;队列添加元素,end++,如果end大于了队列长度-1,则end=0,arr[end] = 元素,size++;队列出队元素,arr[begin]即出队元素,begin++,如果end大于了队列长度-1,则end=0,size–;
代码实现如下
- 队列:使用三个变量分别维护,size队列中元素的数量,begin出队元素索引,end添加到队列元素索引,是否可以向队列中添加元素,判断size的大小即可,如果size小于队列长度即可添加,反之不可;队列添加元素,end++,如果end大于了队列长度-1,则end=0,arr[end] = 元素,size++;队列出队元素,arr[begin]即出队元素,begin++,如果end大于了队列长度-1,则end=0,size–;
public class ArrayToQueue<T> {
/**
* 队列中元素数量
*/
private int size;
/**
* 出队元素索引
*/
private int begin;
/**
* 添加到队列元素索引
*/
private int end;
/**
* 数组
*/
private T[] arr;
public ArrayToQueue(int length) {
arr = (T[]) new Object[length];
}
/**
* 是否为空
* @return true or false
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 获取队列元素数量
* @return 队列元素数量
*/
public int getSize() {
return this.size;
}
/**
* 添加元素至队列中
*/
public void add(T obj) {
if (size >= arr.length) {
throw new RuntimeException("队列已满");
}
// 队列元素+1
size++;
// 添加到数组中end位置
arr[end] = obj;
// 获取end所在数组的下一个索引
end = getIndex(end);
}
/**
* 获取并移除队列第一个元素
* @return 队列第一个元素
*/
public T poll() {
if (size <= 0) {
throw new RuntimeException("队列无数值");
}
// 队列元素-1
size--;
T result = arr[begin];
// 获取begin所在数组的下一个索引
begin = getIndex(begin);
return result;
}
/**
* 获取所在数组的下一个索引
* @param currentIndex 当前索引
* @return 下一个索引
*/
private int getIndex(int currentIndex) {
return currentIndex < arr.length - 1 ? currentIndex + 1 : 0;
}
public static void main(String[] args) {
ArrayToQueue<Integer> queue = new ArrayToQueue<>(5);
queue.add(1);
queue.add(2);
System.out.println(queue.poll());
queue.add(3);
queue.add(4);
System.out.println(queue.poll());
queue.add(5);
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
}
}
栈:使用一个index维护当前数据中最大位置的索引,向栈中添加元素时,给arr[index]赋值,index不能大于数组长度-1,弹出元素时,arr[index]=0,index减一,index不能小于0;
public class ArrayToStack<T> {
/**
* 数组存在元素最大位置索引
*/
private int index;
/**
* 数组
*/
private T[] arr;
/**
* 栈是否为空
* @return true or false
*/
public boolean isEmpty() {
return index == 0;
}
/**
* 栈是否满了
* @return true or false
*/
public boolean isFull() {
return index == arr.length;
}
public ArrayToStack(int size) {
index = 0;
arr = (T[]) new Object[size];
}
/**
* 压栈
* @param obj 压栈元素
*/
public void push(T obj) {
if (isFull()) {
throw new RuntimeException("栈已满");
}
arr[index] = obj;
index++;
}
/**
* 出栈
* @return 出栈元素
*/
public T pop() {
if (isEmpty()) {
throw new RuntimeException("栈中无元素");
}
index--;
T result = arr[index];
return result;
}
/**
* 获取栈顶元素但不移除
* @return 栈顶元素
*/
public T peek() {
if (isEmpty()) {
throw new RuntimeException("栈中无元素");
}
return arr[index - 1];
}
public static void main(String[] args) {
ArrayToStack<Integer> stack = new ArrayToStack<>(5);
stack.push(1);
System.out.println(stack.pop());
stack.push(2);
System.out.println(stack.pop());
stack.push(3);
stack.push(4);
stack.push(5);
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
}
}
- 设计一个栈,让pop、push、getMin操作的时间复杂度都是O(1)
pop、push不在赘述,getMin即获取当前栈中最小的元素,思路就是额外使用一个栈维护当前栈中最小的元素,比如压栈1,最小栈同时压1,压栈2,最小栈还是压1,压栈3,最小栈还是压1,压栈0,最小栈压0,依此,栈 1 - 2 - 3 - 0,最小栈就是1 - 1 - 1 -0,弹栈的同时最小栈也弹栈,利用空间换时间想法。
public class MinStack {
/**
* 正常操作的栈
*/
private ArrayToStack<Integer> opStack;
/**
* 记录当前栈中最小元素的栈
*/
private ArrayToStack<Integer> minStack;
public MinStack(int size) {
opStack = new ArrayToStack<>(size);
minStack = new ArrayToStack<>(size);
}
/**
* 弹栈
* @return 栈顶元素
*/
private Integer pop() {
Integer result = opStack.pop();
// 最小栈也弹栈
minStack.pop();
return result;
}
/**
* 入栈
* @param obj 入栈元素
*/
public void push(Integer obj) {
// 压栈
opStack.push(obj);
// 正常操作栈压一个元素 最小栈也压一个元素
if (minStack.isEmpty()) {
// 最小栈为空直接压栈
minStack.push(obj);
} else {
// 最小栈不为空 则将obj与最小栈顶元素比较 最小的入栈
minStack.push(minStack.peek() > obj ? obj : minStack.peek());
}
}
/**
* 获取当前栈最小元素
* @return 当前栈最小元素
*/
private Integer getMin() {
return minStack.peek();
}
public static void main(String[] args) {
MinStack minStack = new MinStack(5);
minStack.push(3);
System.out.println(minStack.getMin());
minStack.push(2);
System.out.println(minStack.getMin());
minStack.push(4);
System.out.println(minStack.getMin());
minStack.push(1);
System.out.println(minStack.getMin());
minStack.push(5);
System.out.println(minStack.getMin());
System.out.println("--------");
System.out.println(minStack.pop());
System.out.println(minStack.pop());
System.out.println(minStack.pop());
System.out.println(minStack.pop());
System.out.println(minStack.pop());
}
}
- 如何使用栈实现队列?
栈先进先出、队列先进后出,使用两个栈,一个pop栈,一个push栈,默认都是空,如果入队元素则向push栈中压,压完栈,立刻弹栈则从push栈弹出,按照弹出的顺序压栈到pop栈,只有pop栈为空时,push才可以向pop压栈,获取元素则从pop栈弹栈获取,没有则从push弹栈到pop栈再弹出。
public class StackToQueue<T> {
/**
* 弹栈的栈
*/
private ArrayToStack<T> popStack;
/**
* 压栈的栈
*/
private ArrayToStack<T> pushStack;
public StackToQueue(int size) {
popStack = new ArrayToStack<T>(size);
pushStack = new ArrayToStack<T>(size);
}
/**
* 入队
* @param obj 入队元素
*/
public void add(T obj) {
// 压push栈
pushStack.push(obj);
pushToPop();
}
/**
* 出队
* @return 出队元素
*/
public T get() {
if (pushStack.isEmpty() && popStack.isEmpty()) {
throw new RuntimeException("队列为空");
}
pushToPop();
return popStack.pop();
}
/**
* push栈导向pop栈
*/
public void pushToPop() {
// pop栈为空才可以将push栈全部弹栈压入pop栈
if (popStack.isEmpty()) {
// 将push栈弹入pop栈
while (!pushStack.isEmpty()) {
popStack.push(pushStack.pop());
}
}
}
public static void main(String[] args) {
StackToQueue<Integer> stackToQueue = new StackToQueue<>(5);
stackToQueue.add(1);
System.out.println(stackToQueue.get());
stackToQueue.add(2);
stackToQueue.add(2);
System.out.println(stackToQueue.get());
stackToQueue.add(3);
System.out.println(stackToQueue.get());
stackToQueue.add(4);
System.out.println(stackToQueue.get());
System.out.println(stackToQueue.get());
}
}
- 如何使用队列实现栈
栈先进先出、队列先进后出,使用两个队列,入队时,默认向某一个添加元素,出队时,将默认队列中只留一个元素,剩下全部出队在入队到另一个队列,返回默认队列最后一个元素,再交换两个队列的值。
public class QueueToStack<T> {
private ArrayToQueue<T> queue;
private ArrayToQueue<T> help;
public QueueToStack(int size) {
queue = new ArrayToQueue<T>(size);
help = new ArrayToQueue<T>(size);
}
/**
* 压栈
* @param obj 压栈元素
*/
public void push(T obj) {
queue.add(obj);
}
/**
* 弹栈
* @return 栈顶元素
*/
public T pop() {
if (queue.isEmpty()) {
throw new RuntimeException("栈中无元素");
}
// queue只留一个元素 用来弹栈
while (queue.getSize() > 1) {
help.add(queue.poll());
}
T result = queue.poll();
ArrayToQueue<T> temp = queue;
queue = help;
help = temp;
return result;
}
public static void main(String[] args) {
QueueToStack<Integer> stack = new QueueToStack<>(5);
stack.push(1);
stack.push(2);
System.out.println(stack.pop());
stack.push(3);
stack.push(4);
System.out.println(stack.pop());
stack.push(5);
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
}
}
- 递归
将一个问题拆为n多个同样的子问题,子问题的解决使用的都是同一个方法
举个例子,获取数组中最大的元素,获取[1,2,3,4]最大元素,按照递归的思路,获取0~3最大元素,拆分为,获取0-1最大元素,获取2-3最大元素,两者的最大值即为数组中最大元素,0-1最大元素,又是0-0和1-1两个最大元素中最大值,依此类推
递归最重要在于找到base case,即(子)问题何时结束,新手一定要画递归图,分析并理解递归。
public class Recurrence {
public static void main(String[] args) {
int[] arr = new int[]{1,2,200,4,5,6,7};
System.out.println(getMax(arr, 0, arr.length - 1));;
}
private static int getMax(int[] arr, int begin, int end) {
if (begin == end) {
return arr[begin];
}
int mid = begin + (end - begin) / 2;
int leftMax = getMax(arr, begin, mid);
int rightMax = getMax(arr, mid + 1, end);
return Math.max(leftMax, rightMax);
}
}
递归图如下,最后得到最大值为200。
- Master公式
用来推算递归算法的时间复杂度,前提是递归算法的子问题的规模都是一致的时候才可以使用