1、队列
1.1 队列:先入先出的数据结构
队列实现一般采用循环队列的形式,可以有效的节约空间,提高空间利用率。
下面给出循环队列的实现,完成插入,删除,取出,判断是否为空,是否满的操作:
class MyCircularQueue {
//模拟队列
private int[] queue;
//队列长度,开始位置和插入的数据长度
private int head;
private int count;
private int capacity;
/** Initialize your data structure here. Set the size of the queue to be k. */
public MyCircularQueue(int k) {
this.queue = new int[k];
this.count = 0;
this.head = 0;
this.capacity = k;
}
/** Insert an element into the circular queue. Return true if the operation is successful. */
public boolean enQueue(int value) {
if(this.count == this.capacity){
return false;
}
this.queue[(this.head + this.count) % this.capacity] = value;
this.count += 1;
return true;
}
/** Delete an element from the circular queue. Return true if the operation is successful. */
public boolean deQueue() {
if(this.count == 0){
return false;
}
this.head = (this.head + 1) % this.capacity;
this.count -= 1;
return true;
}
/** Get the front item from the queue. */
public int Front() {
if(this.count == 0){
return -1;
}
return this.queue[this.head];
}
/** Get the last item from the queue. */
public int Rear() {
if(this.count == 0){
return -1;
}
return this.queue[(this.head + this.count - 1) % this.capacity];
}
/** Checks whether the circular queue is empty or not. */
public boolean isEmpty() {
if(this.count == 0){
return true;
}
return false;
}
/** Checks whether the circular queue is full or not. */
public boolean isFull() {
if(this.count == this.capacity){
return true;
}
return false;
}
}
1.2 队列和广度优先搜索
队列的重点一般和广度优先搜索(BFS)放在一起,利用BFS找到从起始节点到目标节点的路径,特别是最短节点,所以,看到题目中如果出现赵最短路径的问题,一般想到的就是BFS,下面给出这类题目的BFS模版:
/**
* Return the length of the shortest path between root and target node.
*/
int BFS(Node root, Node target) {
Queue<Node> queue; // store all nodes which are waiting to be processed
int step = 0; // number of steps neeeded from root to current node
// initialize
add root to queue;
// BFS
while (queue is not empty) {
step = step + 1;
// iterate the nodes which are already in the queue
int size = queue.size();
for (int i = 0; i < size; ++i) {
Node cur = the first node in queue;
return step if cur is target;
for (Node next : the neighbors of cur) {
add next to queue;
}
remove the first node from queue;
}
}
return -1; // there is no path from root to target
}
有时候,要确保节点不会被多次访问,造成死循环,因此加上一个哈希集统计访问过的节点:
/**
* Return the length of the shortest path between root and target node.
*/
int BFS(Node root, Node target) {
Queue<Node> queue; // store all nodes which are waiting to be processed
Set<Node> used; // store all the used nodes
int step = 0; // number of steps neeeded from root to current node
// initialize
add root to queue;
add root to used;
// BFS
while (queue is not empty) {
step = step + 1;
// iterate the nodes which are already in the queue
int size = queue.size();
for (int i = 0; i < size; ++i) {
Node cur = the first node in queue;
return step if cur is target;
for (Node next : the neighbors of cur) {
if (next is not in used) {
add next to queue;
add next to used;
}
}
remove the first node from queue;
}
}
return -1; // there is no path from root to target
}
下面的三题针对这个问题进行学习:
先从第一题岛屿数量开始,需要找到“1”旁边所有的“1”来确定这一个岛屿的大小,因此采用BFS非常合适,直接套用上面的模版,因为本题不需要最短路径,所以不需要统计步数:
class Solution {
public int numIslands(char[][] grid) {
if(grid == null || grid.length == 0){
return 0;
}
int count = 0;
int nr = grid.length;
int nc = grid[0].length;
for(int i = 0; i < nr ;i++){
for(int j = 0; j < nc; j++){
if(grid[i][j] == '1'){
count++;
grid[i][j] = '0';
Queue<Integer> queue = new LinkedList<>();
queue.offer(nc * i + j);
while(!queue.isEmpty()){
int id = queue.poll();
int row = id / nc;
int colum = id % nc;
if(row - 1 >= 0 && grid[row - 1][colum] == '1'){
queue.offer((row - 1) * nc + colum);
grid[row - 1][colum] = '0';
}
if(row + 1 < nr && grid[row + 1][colum] == '1'){
queue.offer((row + 1) * nc + colum);
grid[row + 1][colum] = '0';
}
if(colum - 1 >= 0 && grid[row][colum - 1] == '1'){
queue.offer((row) * nc + colum - 1);
grid[row][colum - 1] = '0';
}
if(colum + 1 < nc && grid[row][colum + 1] == '1'){
queue.offer((row) * nc + colum + 1);
grid[row][colum + 1] = '0';
}
}
}
}
}
return count;
}
}
打开转盘锁就完全对应上述第二个模版了,将死亡的数字放入已经访问过的集合中,直接跳过就可以了。
class Solution {
public int openLock(String[] deadends, String target) {
Set<String> visited = new HashSet<>();
for(String s : deadends){
visited.add(s);
}
Queue<String> queue = new LinkedList<>();
queue.offer("0000");
int step = 0;
while(!queue.isEmpty()){
int sz = queue.size();
for(int i = 0; i < sz; i++){
String cur = queue.poll();
if(visited.contains(cur)){
continue;
}
if(cur.equals(target)){
return step;
}
visited.add(cur);
for(int j = 0; j < 4; j++){
char[] ch = cur.toCharArray();
if(ch[j] == '9'){
ch[j] = 0;
}else{
ch[j]++;
}
String tmp = new String(ch);
if(!visited.contains(tmp)){
queue.offer(tmp);
}
char[] ch2 = cur.toCharArray();
if(ch2[j] == '0'){
ch2[j] = 9;
}else{
ch2[j]--;
}
String tmp2 = new String(ch2);
if(!visited.contains(tmp2)){
queue.offer(tmp2);
}
}
}
step++;
}
return -1;
}
}
完全平方数也是直接套用就行了:
class Solution {
public int numSquares(int n) {
Set<Integer> visited = new HashSet<>();
Queue<Integer> queue = new LinkedList<>();
visited.add(0);
queue.add(0);
int step = 0;
while(!queue.isEmpty()){
int sz = queue.size();
for(int i = 0; i < sz; i++){
int cur = queue.poll();
if(cur == n){
return step;
}
for(int j = 1; j * j + cur <= n; j++){
int next = j * j + cur;
if(next <= n && !visited.contains(next)){
queue.add(next);
visited.add(next);
}
}
}
step++;
}
return step;
}
}
2、栈
2.1 栈:后入先出的数据结构
栈与队列的先入先出的数据结构不同,栈是先入后出(LIFO)的方式,通常push()代表往栈中插入数据,pop()代表从栈中删除数据。
栈的实现就比较简单了,一个动态数组就可以了:
class MyStack {
private List<Integer> data;
public MyStack() {
data = new ArrayList<>();
}
/** Insert an element into the stack. */
public void push(int x) {
data.add(x);
}
/** Checks whether the queue is empty or not. */
public boolean isEmpty() {
return data.isEmpty();
}
/** Get the top item from the queue. */
public int top() {
return data.get(data.size() - 1);
}
/** Delete an element from the queue. Return true if the operation is successful. */
public boolean pop() {
if(data.isEmpty()){
return false;
}
data.remove(data.size() - 1);
return true;
}
};
下面通过四道练习题来学习栈的使用:
最小栈这一题采用一个辅助栈来存放当前存放数据中的最小值,这样保证在栈中数据弹出的时候,辅助栈中的数据也按同样的顺序弹出:
class MinStack {
Deque<Integer> stack;
Deque<Integer> minStack;
/** initialize your data structure here. */
public MinStack() {
stack = new LinkedList<>();
minStack = new LinkedList<>();
minStack.push(Integer.MAX_VALUE);
}
public void push(int x) {
stack.push(x);
minStack.push(Math.min(minStack.peek(), x));
}
public void pop() {
stack.pop();
minStack.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
有效的括号这一题就是标准的利用栈的先入后出结构,后进来的括号需要先进行匹配,这里需要一个Map集合用来存储括号对应关系来进行辅助:
class Solution {
public boolean isValid(String s) {
int len = s.length();
if(len % 2 == 1){
return false;
}
Deque<Character> stack = new LinkedList<>();
Map<Character, Character> map = new HashMap<>(){{
put(')','(');
put(']','[');
put('}','{');
}};
for(int i = 0; i < len; i++){
char c = s.charAt(i);
if(map.containsKey(c)){
if(stack.isEmpty() || map.get(c) != stack.peek()){
return false;
}
stack.pop();
}else{
stack.push(c);
}
}
return stack.isEmpty();
}
}
每日温度就是当数据比当前小的话就放入,一直遇到比他大的:
class Solution {
public int[] dailyTemperatures(int[] T) {
int len = T.length;
int[] res = new int[len];
Deque stack = new LinkedList<>();
for(int i = 0; i < len; i++){
int tmp = T[i];
if(!stack.isEmpty() && tmp > T(stack.peek())){
int pre = stack.pop();
res[pre] = i - pre;
}
stack.push(i);
}
return res;
}
}
逆波兰表达式就是纯粹的栈问题了,把数据全部存到栈中,遇到符号就取出两个数字,将结果再放入栈中:
class Solution {
public int evalRPN(String[] tokens) {
Deque<Integer> stack = new LinkedList<>();
Integer op1;
Integer op2;
for(String s : tokens){
switch (s){
case "+":
op1 = stack.pop();
op2 = stack.pop();
stack.push(op1 + op2);
break;
case "-":
op1 = stack.pop();
op2 = stack.pop();
stack.push(op2 - op1);
break;
case "*":
op1 = stack.pop();
op2 = stack.pop();
stack.push(op1 * op2);
break;
case "/":
op1 = stack.pop();
op2 = stack.pop();
stack.push(op2 / op1);
break;
default:
stack.push(Integer.valueOf(s));
break;
}
}
return stack.pop();
}
}