栈、队列、链表、数组和矩阵结构介绍及常见面试题讲解(上)
题目一:用数组结构实现大小固定的队列和栈
实现栈结构:
栈结构是先进后出的,只需要一个数组和一个记录位置的变量size,当进来一个元素,size就++,出去一个元素size就--。
实现队列结构:
相对栈结构要难搞一些,队列的先进先出的,需要一个数组和三个变量,size记录已经进来了多少个元素,end记录刚进来的元素应该放在哪个位置,start表示用户要求弹出的元素所在的位置。size的作用不止于此,它还是end与start的操作的关键信息,使得end与start解耦,避免的很多的麻烦,当end或者start达到底部的时候就跳回0处。
代码:
/**
* 用数组实现大小固定的栈和队列
*/
public class Array_To_Stack_Queue {
public static class ArrayStack {
private Integer[] arr;
private Integer next;
public ArrayStack(int initSize) {
if (initSize < 0) {
throw new IllegalArgumentException("The init size is less than 0");
}
arr = new Integer[initSize];
next = 0;
}
public Integer peek() {
if (next == 0) {
return null;
}
return arr[next - 1];
}
public void push(int obj) {
if (next == arr.length) {
throw new ArrayIndexOutOfBoundsException("This stack is full");
}
arr[next++] = obj;
}
public Integer pop() {
if (next == 0) {
throw new ArrayIndexOutOfBoundsException("This stack is empty");
}
return arr[next--];
}
}
public static class ArrayQueue {
private Integer[] arr;
private Integer size;
private Integer start;
private Integer end;
public ArrayQueue(int initSize) {
if (initSize < 0) {
throw new IllegalArgumentException("The init size is less than 0");
}
arr = new Integer[initSize];
size = 0;
start = 0;
end = 0;
}
public Integer peek() {
if (size == 0) {
return null;
}
return arr[start];
}
public void push(int obj) {
if (size == arr.length) {
throw new ArrayIndexOutOfBoundsException("This queue is full");
}
size++;
arr[end] = obj;
end = end == arr.length - 1 ? 0 : end + 1;
}
public Integer poll() {
if (size == 0) {
throw new ArrayIndexOutOfBoundsException("This queue is empty");
}
size--;
int temp = start;
start = start == arr.length - 1 ? 0 : start + 1;
return arr[temp];
}
}
public static void main(String[] args) {
}
}
题目二:实现一个特殊的栈,在实现栈的基础功能的基础上,再实现返回栈中最小元素的操作。
要求:
- pop、push、getMin操作的时间复杂度都是O(1)
- 设计的栈类型可以使用现成的栈结构.
思路:
设计两个栈,一个dataStack,一个minStack。起初,第一个入dataStack的时候,minStack中null,也入,以后的newNum,dataStack正常入栈,minStack用栈顶元素num和newNum比较,如果num小于newNum,minStack重复入num,如果num 大于newNum,入新元素,即newNum。就要保证两个栈中元素个数一致。getMin的时候,minStack调用peek函数,只返回,不弹出。真正的弹出要调用dataStack的pop函数,这个时候minStack也要pop一个,只不过不return而已
代码:
/**
* 实现一个特殊的栈,在实现栈的基础功能的基础上,再实现返回栈中最小元素的操作。
*/
public class GetMinStack {
public static class MyStack1 {
private Stack<Integer> stackData;
private Stack<Integer> stackMin;
public MyStack1() {
this.stackData = new Stack<>();
this.stackMin = new Stack<>();
}
public void push(int newNum) {
//同步更新
if (this.stackMin.isEmpty()) {
this.stackMin.push(newNum);
} else if (newNum < this.getmin()) {//插入的数小于最小值,则将最小值插入栈
this.stackMin.push(newNum);
} else {//否则,找出最小值,插入栈
int newMin = this.stackMin.peek();
this.stackMin.push(newMin);
}
this.stackData.push(newNum);
}
public int pop() {
//同步弹出
if (this.stackData.isEmpty()) {
throw new RuntimeException("Your stack is empty");
}
this.stackMin.pop();
return this.stackData.pop();
}
public int getmin() {
if (this.stackMin.isEmpty()) {
throw new RuntimeException("Your stack is empty");
}
return this.stackMin.peek();
}
}
public static class MyStack2 {
private Stack<Integer> stackData;
private Stack<Integer> stackMin;
public MyStack2() {
this.stackData = new Stack<>();
this.stackMin = new Stack<>();
}
public void push(int newNum) {
if (this.stackMin.isEmpty()) {
this.stackMin.push(newNum);
} else if (newNum <= this.getMin()) {
this.stackMin.push(newNum);
}
this.stackData.push(newNum);
}
public int pop() {
if (this.stackData.isEmpty()) {
throw new RuntimeException("Your stack is empty");
}
int value = this.stackData.pop();
//当数据栈弹出值等于最小栈顶数的时候,弹出
if (value == this.getMin()) {
this.stackMin.pop();
}
return value;
}
public int getMin() {
if (this.stackMin.isEmpty()) {
throw new RuntimeException("Your stack is empty");
}
return this.stackMin.peek();
}
}
}
题目三:如何仅用队列结构实现栈结构?如何仅用栈结构实现队列结构?
仅用队列实现栈
思路 :
一个队列只存放数据data,一个队列只用来辅助保持数据;
比如1,2,3,4,5入了data队列,当要取出元素的时候,把1,2,3,4存到help里,这样队列data就只有一个5了~然后data.poll返回。然后在互换data和help的引用,使得原来有四个数据的help队列为data,新换完的help队列没内容。每次add数据只操作data队列,取出也是,只不过需要help辅助存储
代码:
/**
* 使用队列结构实现栈结构
*/
public static class TwoQueueStack {
private Queue<Integer> queue;
private Queue<Integer> help;
public TwoQueueStack() {
queue = new LinkedList<>();
help = new LinkedList<>();
}
public void push(int pushInt) {
queue.add(pushInt);
}
public int peek() {
if (queue.isEmpty()) {
throw new RuntimeException("Queue is empty");
}
while (queue.size() != 1) {
help.add(queue.poll());
}
int res = queue.poll();
help.add(res);
swap();
return res;
}
public int pop() {
if (queue.isEmpty()) {
throw new RuntimeException("Queue is empty");
}
while (queue.size() > 1) {
help.add(queue.poll());
}
int res = queue.poll();
swap();
return res;
}
private void swap() {
Queue<Integer> temp = help;
help = queue;
queue = temp;
}
}
仅用栈实现队列
思路:
用两个栈,一个是push栈,一个是pop栈,将数据压入push栈,要取数据时,将push栈中的数据全部倒入pop栈中,最后将栈顶数据弹出,且只有在pop栈为空时,才能倒,而且一次性倒完。
代码:
/**
* 用栈结构实现队列结构
*/
public static class TwoStacksQueue {
private Stack<Integer> stackPush;
private Stack<Integer> stackPop;
public TwoStacksQueue() {
stackPush = new Stack<>();
stackPop = new Stack<>();
}
public void push(int pushInt) {
stackPush.push(pushInt);
dao();
}
public int poll() {
if (stackPush.isEmpty() && stackPop.isEmpty()) {
throw new RuntimeException("empty");
}
dao();
return stackPop.pop();
}
public int peek() {
if (stackPush.isEmpty() && stackPop.isEmpty()) {
throw new RuntimeException("empty");
}
dao();
return stackPop.peek();
}
public void dao() {
if (stackPop.isEmpty()) {//为空
while (!stackPush.isEmpty()) {//一次倒完
stackPop.push(stackPush.pop());
}
}
}
}
转圈打印矩阵
题目:
给定一个整型矩阵matrix,请按照转圈的方式打印它。例如:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,打印结果为:1,2,3,4,5,12,16,15,14,13,9,5,6,7,11,10
要求:
额外空间复杂度为O(1)
思路:
矩阵分圈处理。在矩阵中用左上角的坐标(tR,tC)和右下角的坐标(dR,dC)就可以表示一个矩阵,比如,题目中的矩阵,当(tR,tC)=(0,0),(dR,dC)=(3,3)时,表示的子矩阵就是整个矩阵,那么这个子矩阵的最外层的部分如下:
1 2 3 4
5 8
9 12
13 14 15 16
如果能把这个子矩阵的外层转圈打印出来,那么在(tR,tC)=(0,0)、(dR,dC)=(3,3)时,打印结果为:1,2,3,4,8,12,16,15,14,13,9,5。接下来令tR和tC加1,即(tR,tC)=(1,1),令dR和dC减1,即(dR,dC)=(2,2),此时表示的子矩阵如下:
6 7
10 11
再把这个子矩阵转圈打印出来,结果为:6,7,11,10。把tR和tC加1,即(tR,tC)=(2,2),令dR和dC减1,即(dR,dC)=(1,1)。如果发现左上角坐标跑到右下角坐标的右方或下方,整个过程就停止。已经打印的所有结果连接起来就是我们要求的打印结果。
代码:
/**
* 转圈打印矩阵
*/
public class PrintMatrixSpiralOrder {
public void spiralOrderPrint(int[][] matrix) {
int tR = 0;
int tC = 0;
int dR = matrix.length - 1;
int dC = matrix[0].length - 1;
while (tR <= dR && tC <= dC) {
printEdge(matrix, tR++, tC++, dR--, dC--);
}
}
//转圈打印一个子矩阵的外层,左上角点(tR,tC),右下角点(dR,dC)
private void printEdge(int[][] matrix, int tR, int tC, int dR, int dC) {
if (tR == dR) {//子矩阵只有一行时
for (int i = tC; i <= dC; i++) {
System.out.print(matrix[tR][i] + " ");
}
} else if (tC == dC) {//子矩阵只有一列时
for (int i = tR; i <= dR; i++) {
System.out.print(matrix[i][tC] + " ");
}
} else {//一般情况
int curCol = tC;
int curRow = tR;
while (curCol != dC) {//从左向右
System.out.print(matrix[tR][curCol] + " ");
curCol++;
}
while (curRow != dR) {//从上到下
System.out.print(matrix[curRow][dC] + " ");
curRow++;
}
while (curCol != tC) {//从右到左
System.out.print(matrix[dR][curCol] + " ");
curCol--;
}
while (curRow != tR) {//从下到上
System.out.print(matrix[curRow][tC] + " ");
curRow--;
}
}
}
}
将正方形矩阵顺时针转动90°
题目:
给定一个N×N的矩阵matrix,把这个矩阵调整成顺时针转动90°后的形式。
例如:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
顺时针转动90°后为:
13 9 5 1
14 10 6 2
15 11 7 3
16 12 8 4
要求:
额外空间复杂度为O(1).
思路:
仍然使用分圈处理的方式,
在矩阵中用左上角的坐标(tR,tC)和右下角的坐标(dR,dC)就可以表示一个矩阵,比如,题目中的矩阵,当(tR,tC)=(0,0),(dR,dC)=(3,3)时,表示的子矩阵就是整个矩阵,那么这个子矩阵的最外层的部分如下:
1 2 3 4
5 8
9 12
13 14 15 16
在这个外圈中,1,4,16,13为一组,然后让1占据4的位置,4占据16的位置,16占据13的位置,13占据1的位置,一组就调整完了。然后2,8,15,9为一组,继续占据调整的过程,最后3,12,14,5为一组,继续占据调整的过程。然后(tR,tC)=(0,0)、(dR,dC)=(3,3)的子矩阵外层就调整完毕,接下来令tR和tC加1,即(tR,tC)=(1,1),令dR和dC减1,即(dR,dC)=(2,2),此时表示的子矩阵如下:
6 7
10 11
这个外层只有一组,就是6,7,11,10,占据调整之后即可。所以,如果子矩阵的大小是M×M,一共就有M-1组,分别进行占据调整即可。
代码:
/**
* 将正方形矩阵顺时针旋转90°
*/
public class RotateMatrix {
public static void rotate(int[][] matrix) {
int tR = 0;
int tC = 0;
int dR = matrix.length - 1;
int dC = matrix[0].length - 1;
while (tR < dR) {
rotateEdge(matrix, tR++, tC++, dR--, dC--);
}
}
private static void rotateEdge(int[][] matrix, int tR, int tC, int dR, int dC) {
int times = dC - tC;//times就是总的组数
int temp = 0;
for (int i = 0; i != times; i++) {//一次循环就是一组占据调整
temp = matrix[tR][tC + i];
matrix[tR][tC + i] = matrix[dR - i][tC];
matrix[dR - i][tC] = matrix[dR][dC - i];
matrix[dR][dC - i] = matrix[tR - i][dC];
matrix[tR - i][dC] = temp;
}
}
}
“之”字形打印矩阵
题目:
给定一个矩阵matrix,按照“之”字形的方式打印矩阵,例如:
1 2 3 4
5 6 7 8
9 10 11 12
“之”字形打印的结果为:1,2,5,9,6,3,4,7,10,11,8,12
要求:
额外空间复杂度为O(1)
思路;
- 上坐标(tR,tC)初始化为(0,0),先沿着矩阵第一行移动(tC++),当到达第一行最右边的元素后,在沿着矩阵最后一列移动(tR++)。
- 下坐标(dR,dC)初始为(0,0),先沿着矩阵第一列移动(dR++),当到达第一列最下边的元素时,再沿着矩阵最后一行移动(dC++)。
- 上坐标与下坐标同步移动,每次移动后的上坐标与下坐标的连线就是矩阵中的一条斜线,打印斜线上的元素即可。
- 如果上次斜线是从左下向右上打印的,这次一定是从右上向左下打印,反之亦然。总之,可以把打印的方向用boolean值表示,每次取反即可。
代码:
/**
* 之字形打印矩阵
*/
public class PrintMatrixZigZag {
public static void printMatrixZigZag(int[][] matrix) {
int tR = 0;
int tC = 0;
int dR = 0;
int dC = 0;
int endRow = matrix.length - 1;
int endCol = matrix[0].length - 1;
boolean fromUp = false;
while (tR != endRow + 1) {
printLevel(matrix, tR, tC, dR, dC, fromUp);
tR = tC == endCol ? tR + 1 : tR;
tC = tC == endCol ? tC : tC + 1;
dR = dR == endRow ? dR : dR + 1;
dC = dR == endRow ? dC + 1 : dC;
fromUp = !fromUp;
}
System.out.println();
}
private static void printLevel(int[][] matrix, int tR, int tC, int dR, int dC, boolean fromUp) {
if (fromUp) {
while (tR != dR + 1) {//从左下到右上
System.out.print(matrix[tR++][tC--] + " ");
}
} else {
while (dR != tR - 1) {//从右上到左下
System.out.print(matrix[dR--][dC++] + " ");
}
}
}
}
在行列都排好序的矩阵中找数
题目:
给定一个有N×M的整型矩阵matrix和一个整数K,matrix的每一行和每一列都是排好序的。实现一个函数,判断K是否在matrix中。
例如:
0 1 2 5
2 3 4 7
4 4 4 8
5 7 7 9
如果K为7,返回true,如果K为6,返回false
要求:
时间复杂度为O(N+M),额外空间复杂度为O(1)。
思路:
1.从矩阵最右上角的数开始寻找(row=0,col=M-1)。
2.比较当前数matrix[row][col]与K的关系:
-
如果与K相等,说明已找到,直接返回true
-
如果比K大,因为矩阵每一列都已排好序,所以在当前数所在的列中,处于当前数下方的数都会比K大,则没有必要继续在第col列上寻找,令col=col-1,重复步骤2.
-
如果比K小,因为矩阵每一行都已排好序,所以在当前数所在的行中,处于当前数左方的数都会比K小,则没有必要继续在第row行上寻找,令row=row+1,重复步骤2.
3.如果找到越界都没有发现与K相等的数,则返回false。
或者可以从矩阵的最左下角的数开始寻找(row=N-1,col=0),具体过程类似。
代码:
/**
* 在行列都排好序的矩阵中找数
*/
public class IsContains {
public boolean isContains(int[][] matrix, int K) {
int row = 0;
int col = matrix[0].length - 1;
while (row < matrix.length && col > -1) {
if (matrix[row][col] == K) {
return true;
} else if (matrix[row][col] > K) {
col--;
} else {
row++;
}
}
return false;
}
}