前言
数据结构与算法概述
数据结构和算法的关系
- 数据 data 结构(structure)是一门研究组织数据方式的学科,有了编程语言也就有了数据结构.学好数据结构可以 编写出更加漂亮,更加有效率的代码。
- 要学习好数据结构就要多多考虑如何将生活中遇到的问题,用程序去实现解决.
- 程序 = 数据结构 + 算法
- 数据结构是算法的基础, 换言之,想要学好算法,需要把数据结构学到位。
几个实际编程中遇到的问题
问题一:字符串替换问题
小结:需要使用到单链表数据结构
问题二:五子棋程序问题
如何判断游戏的输赢,并可以完成存盘退出和继续上局的功能
-
棋盘 二维数组=>(稀疏数组)-> 写入文件【存档功能】
-
读取文件->稀疏数组->二维数组 -> 棋盘 【接上局】
问题三:约瑟夫(Josephu)问题(丢手帕问题)
-
Josephu 问题为:设编号为 1,2,… n 的 n 个人围坐一圈,约定编号为 k(1<=k<=n)的人从 1 开始报数,数 到 m 的那个人出列,它的下一位又从 1 开始报数,数到 m 的那个人又出列,依次类推,直到所有人出列为止, 由此产生一个出队编号的序列。
-
提示:用一个不带头结点的循环链表来处理 Josephu 问题:先构成一个有 n 个结点的单循环链表(单向环形链 表),然后由 k 结点起从 1 开始计数,计到 m 时,对应结点从链表中删除,然后再从被删除结点的下一个结点 又从 1 开始计数,直到最后一个结点从链表中删除算法结束。
-
小结:完成约瑟夫问题,需要使用到单向环形链表 这个数据结构
其他常见算法问题
-
修路问题 => 最小生成树(加权值)【数据结构】+ 普利姆算法
-
最短路径问题 => 图+弗洛伊德算法
-
汉诺塔 => 分支算法
-
八皇后问题 => 回溯法
线性结构和非线性结构
数据结构包括:线性结构和非线性结构。
线性结构
-
线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系
-
线性结构有两种不同的存储结构,即顺序存储结构(数组)和链式存储结构(链表)。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的
-
链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息
-
线性结构常见的有:数组、队列、链表和栈
非线性结构
非线性结构包括:二维数组,多维数组,广义表,树结构,图结构
稀疏数组和队列
稀疏 sparsearray 数组
看一个实际需求
编写的五子棋程序中,有存盘退出和续上盘的功能。
分析问题:
因为该二维数组的很多值是默认值 0, 因此记录了很多没有意义的数据.->稀疏数组。
基本介绍
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
稀疏数组的处理方法是:
-
记录数组一共有几行几列,有多少个不同的值
-
把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
稀疏数组举例说明
应用实例
-
使用稀疏数组,来保留类似前面的二维数组(棋盘、地图等等)
-
把稀疏数组存盘,并且可以从新恢复原来的二维数组数
-
整体思路分析
-
代码实现
package com.atguigu.sparsearray; @SuppressWarnings("all") public class SparseArray { public static void main(String[] args){ //创建一个原始的二维数组 //0:表示没有棋子 1 表示 黑子 ,2 表示 蓝子 int chessArr[][] = new int[11][11]; chessArr[1][2] = 1; chessArr[2][3] = 2; System.out.println("原始的二维数组:"); for (int[] row : chessArr) { for (int data : row) { System.out.printf("%d\t",data); } System.out.println(); } //将二维数组转为稀疏数组 //1. 先遍历二维数组,得到非0数据的个数 int sum = 0; int length = chessArr.length; for (int i = 0; i <length; i++) { for (int j = 0; j < chessArr[i].length; j++) { if (chessArr[i][j] != 0){ sum++; } } } //System.out.println("sum = "+sum); //2. 创建对应的稀疏数组 int sparseArr[][] = new int[sum+1][3]; //给稀疏数组赋值 sparseArr[0][0] = 11; sparseArr[0][1] = 11; sparseArr[0][2] = sum; //count 用于记录是第几个非 0 数据 int count = 0; for (int i = 0; i < chessArr.length; i++) { for (int j = 0; j < chessArr[i].length; j++) { if (chessArr[i][j] != 0){ count++; sparseArr[count][0] = i; sparseArr[count][1] = j; sparseArr[count][2] = chessArr[i][j]; // sparseArr[?][0] = i; // sparseArr[?][1] = j; // sparseArr[?][2] = chessArr[i][j]; } } } //3. 输出稀疏数组 System.out.println(); System.out.println("得到的稀疏数组为:"); for (int i = 0; i < sparseArr.length; i++) { System.out.printf("%d\t%d\t%d\t\n",sparseArr[i][0],sparseArr[i][1],sparseArr[i][2]); } int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]]; for (int i = 1; i < sparseArr.length; i++) { chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2]; } System.out.println(); System.out.println("恢复后的二维数组:"); // for (int i = 0; i < chessArr2.length; i++) { // for (int j = 0; j < chessArr2[i].length; j++) { // System.out.printf("%d\t",chessArr2[i][j]); // } // System.out.println(); // } for (int[] row :chessArr2) { for (int dtat : row) { System.out.printf("%d\t",dtat); } System.out.println(); } } }
课后练习
要求:
-
在前面的基础上,将稀疏数组保存到磁盘上,比如 map.data
-
恢复原来的数组时,读取 map.data 进行恢复
分析:
只需要在原来的基础上增加两个方法:
- saveSparseArrToFile(sparseArr, filePath):把稀疏数组保存到本地文件中
- getSparseArrFromFile(filePath):读取文件,将读取到的稀疏数组转化为二维数组
通用方法
代码实现:
public static void saveSparseArrToFile(int[][] sparse, String filePath) throws IOException {
//写入文件
FileOutputStream fos = null;
DataOutputStream dos = null;
try {
fos = new FileOutputStream(new File(filePath));
dos = new DataOutputStream(fos);
for (int[] row : sparse) {
for (int data : row) {
dos.writeInt(data);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (dos != null) {
dos.close();
}
if (fos != null) {
fos.close();
}
}
}
public static int[][] getSparseArrFromFile(String filePath) throws IOException {
int[][] arr = {};
FileInputStream fis = null;
DataInputStream dis = null;
try {
fis = new FileInputStream(new File(filePath));
dis = new DataInputStream(fis);
//读取数据
int row = dis.readInt();
int column = dis.readInt();
int count = dis.readInt();
//稀疏数组第一行有三个数字 分别代表原数组的 有几行 有几列 有多少个非空数据
arr = new int[row][column];
//根据count 给 arr赋值
for (int i = 0; i < count; i++) {
arr[dis.readInt()][dis.readInt()] = dis.readInt();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭流
if (dis != null) {
dis.close();
}
if (fis != null) {
fis.close();
}
}
return arr;
}
推荐阅读文章:Java IO流之DataInputStream和DataOutputStream分析
只针对稀疏数组
代码实现:
public static void saveSparseArrToFile(int[][] sparse, String filePath) throws IOException {
//写入文件
FileOutputStream fos = null;
try {
fos = new FileOutputStream(filePath);
for (int i = 0; i < sparse.length; i++) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(sparse[i][0] + "\t" + sparse[i][1] + "\t" + sparse[i][2] + "\n");
//System.out.println(stringBuffer);
fos.write(stringBuffer.toString().getBytes("utf-8"));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
fos.close();
}
}
public static int[][] getSparseArrFromFile(String filePath) throws IOException {
int[][] arr = null;
FileReader fileReader = null;
BufferedReader bufferedReader = null;
try {
fileReader = new FileReader(filePath);
bufferedReader = new BufferedReader(fileReader);
String line = null;
int countSparse = 0;
int number = 0;
while ((line = bufferedReader.readLine()) != null) {
//System.out.println(line);
// 将每一行转为数组
String[] split = line.split("\t");
Integer row = Integer.valueOf(split[0].trim());
Integer col = Integer.valueOf(split[1].trim());
Integer value = Integer.valueOf(split[2].trim());
if (countSparse == 0) {
arr = new int[row][col];
} else {
number++;
arr[row][col] = value;
}
countSparse++;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
bufferedReader.close();
fileReader.close();
}
return arr;
}
}
基本代码如下(上述两种方法二选一)
package com.atguigu.sparsearray;
import java.io.*;
@SuppressWarnings("all")
public class SparseArrayIO {
public static void main(String[] args) throws IOException {
//1. 创建一个原始的二维数组
int chessArr[][] = new int[11][11];
chessArr[1][2] = 1;
chessArr[2][3] = 2;
System.out.println("原始的二维数组:");
for (int[] ints : chessArr) {
for (int i : ints) {
System.out.printf("%d\t", i);
}
System.out.println();
}
//2. 遍历二维数组,得到二维数组非 0 数据的个数
int count = nums(chessArr);
//3. 将二维数组转化为稀疏数组
int index = 1;
int sparseArr[][] = new int[count + 1][3];
//稀疏数组 第 1 行
sparseArr[0][0] = chessArr.length;
sparseArr[0][1] = 11;
sparseArr[0][2] = count;
//稀疏数组 第二行及以后
for (int i = 0; i < chessArr.length; i++) {
for (int j = 0; j < chessArr[i].length; j++) {
if (chessArr[i][j] != 0) {
sparseArr[index][0] = i;
sparseArr[index][1] = j;
sparseArr[index][2] = chessArr[i][j];
index++;
}
}
}
//打印稀疏数组
System.out.println("转换后的稀疏数组:\n----------------------------");
printNums(sparseArr);
//4.将稀疏数组保存到文件中
String filePath = "E:\\myTemp\\123.txt";
saveSparseArrToFile(sparseArr, filePath);
//5.将文件数据读取到稀疏数组中
int[][] fileSparseArr = getSparseArrFromFile(filePath);
//打印读取的稀疏数组
System.out.println("打印读取转换后的二维数组:\n----------------------------");
printNums(fileSparseArr);
}
public static int nums(int[][] num) {
int count = 0;
for (int i = 0; i < num.length; i++) {
for (int j = 0; j < num[i].length; j++) {
if (num[i][j] != 0) {
count++;
}
}
}
return count;
}
public static void printNums(int[][] nums) {
for (int[] ints : nums) {
for (int i : ints) {
System.out.printf("%d\t", i);
}
System.out.println();
}
}
队列
队列的一个使用场景
银行排队的案例:
队列介绍
-
队列是一个有序列表,可以用数组或是链表来实现。
-
遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出
-
示意图:(使用数组模拟队列示意图)
数组模拟队列思路
-
队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图, 其中 maxSize 是该队 列的最大容量。
-
因为队列的输出、输入是分别从前后端来处理,因此需要两个变量 front 及 rear 分别记录队列前后端的下标, front 会随着数据输出而改变,而 rear 则是随着数据输入而改变,如图所示:
-
当我们将数据存入队列时称为”addQueue”,addQueue 的处理需要有两个步骤:思路分析
-
将尾指针往后移:rear+1 , 当 front == rear 【空】
-
若尾指针 rear 小于队列的最大下标 maxSize-1,则将数据存入 rear 所指的数组元素中,否则无法存入数据。 rear == maxSize - 1[队列满]
-
-
代码实现
package com.atguigu.queue;
import java.util.Scanner;
public class ArrayQueueDemo {
public static void main(String[] args) {
//测试
//创建一个队列
ArrayQueue arrayQueue = new ArrayQueue(3);
char key = ' '; //接受用户输入
Scanner scanner = new Scanner(System.in);
boolean loop = true;
//输出一个菜单
while(loop){
System.out.println("---------数组模拟队列--------");
System.out.println("s(show): 显示队列");
System.out.println("e(exit): 退出程序");
System.out.println("a(add): 添加数据到队列");
System.out.println("g(get): 从队列取出数据");
System.out.println("h(head): 查看队列头的数据");
System.out.println("--------------------------");
key = scanner.next().charAt(0); //接收一个字符
switch (key){
case 's':
arrayQueue.showQueue();
break;
case 'a':
System.out.println("清输入要添加的数:");
int value = scanner.nextInt();
arrayQueue.addQueue(value);
break;
case 'g':
try {
int res = arrayQueue.getQueue();
System.out.printf("取出的数据是:%d\n",res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'h':
try {
int head = arrayQueue.headQueue();
System.out.printf("队列头为:%d\n",head);
}catch(Exception e){
System.out.println(e.getMessage());
}
break;
case 'e':
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("---------退出程序----------");
}
}
// 使用数组模拟队列
//编写一个 ArrayQueue类
class ArrayQueue{
//数组最大容量
private int maxSize;
//队列头
private int front;
//队列尾
private int rear;
//该数组用于存放数据,模拟队列
private int[] arr;
//创造队列的构造器
public ArrayQueue(int arrMaxSize){
maxSize = arrMaxSize;
arr = new int[maxSize];
front = -1; //指向队列头部,分析书 front 是 指向队列头的前一个位置
rear = -1; //指向队列尾,指向队列尾的数据(队列最后一个数据)
}
//判断队列是否满
public boolean isFull(){
return rear == maxSize-1;
}
//判断队列是否为空
public boolean isEmpty(){
return front == rear;
}
//添加数据到队列
public void addQueue(int n){
//判断队列是否满
if (isFull()){
System.out.println("队列满,不能添加数据!");
return;
}
rear++; //让 rear 后移
arr[rear] = n;
}
//获取队列的数据,出队列
public int getQueue(){
//判断队列是否为空
if (isEmpty()){
//通过异常抛出
throw new RuntimeException("对列空,不能获取数据!");
}
front++; //front后移
return arr[front];
}
//显示队列所有数据
public void showQueue(){
//遍历
if (isEmpty()){
System.out.println("队列空,没有数据!");
return;
}
// 这里老师写的有点问题,详见分析
// for (int i = 0; i < arr.length; i++) {
// System.out.printf("arr[%d] = %d\n",i,arr[i]);
// }
for (int i = front + 1; i <= rear; i++) {
System.out.printf("arr[%d] = %d\n",i,arr[i]);
}
}
//显示队列定头数据
public int headQueue(){
//判断
if (isEmpty()){
throw new RuntimeException("队列空的,没有数据~");
}
return arr[front+1];
}
}
-
问题分析并优化
-
目前数组使用一次就不能用, 没有达到复用的效果
- 将这个数组使用算法,改进成一个环形的队列 取模:%
数组模拟环形队列
对前面的数组模拟队列的优化,充分利用数组. 因此将数组看做是一个环形的。(通过取模的方式来实现即可)
分析说明:
-
尾索引的下一个为头索引时表示队列满,即将队列容量空出一个作为约定,这个在做判断队列满的 时候需要注意 (rear + 1) % maxSize == front 满]
-
rear == front [空]
-
分析示意图:
-
代码实现
package com.atguigu.queue; import java.util.Scanner; public class CircleArrayQueueDemo { public static void main(String[] args) { //测试一把 System.out.println("测试数组模拟环形队列的案例~~~"); // 创建一个环形队列 CircleArray arrayQueue = new CircleArray(4); //说明设置 4, 其队列的有效数据最大是 3 char key = ' '; // 接收用户输入 Scanner scanner = new Scanner(System.in);// boolean loop = true; // 输出一个菜单 while(loop){ System.out.println("-------数组模拟环形队列------"); System.out.println("s(show): 显示队列"); System.out.println("e(exit): 退出程序"); System.out.println("a(add): 添加数据到队列"); System.out.println("g(get): 从队列取出数据"); System.out.println("h(head): 查看队列头的数据"); System.out.println("--------------------------"); key = scanner.next().charAt(0); //接收一个字符 switch (key){ case 's': arrayQueue.showQueue(); break; case 'a': System.out.println("清输入要添加的数:"); int value = scanner.nextInt(); arrayQueue.addQueue(value); break; case 'g': try { int res = arrayQueue.getQueue(); System.out.printf("取出的数据是:%d\n",res); } catch (Exception e) { System.out.println(e.getMessage()); } break; case 'h': try { int head = arrayQueue.headQueue(); System.out.printf("队列头为:%d\n",head); }catch(Exception e){ System.out.println(e.getMessage()); } break; case 'e': scanner.close(); loop = false; break; default: break; } } System.out.println("---------退出程序----------"); } } class CircleArray{ //数组最大容量 private int maxSize; //变量的含义做一个调整: front 就指向队列的第一个元素, 也就是说 arr[front] 就是队列的第一个元素 //front 的初始值 = 0 private int front; //变量的含义做一个调整:rear 指向队列的最后一个元素的后一个位置. 因为希望空出一个空间做为约定 //rear 的初始值 = 0 private int rear; //该数组用于存放数据,模拟队列 private int[] arr; public CircleArray(int arrMaxSize){ maxSize = arrMaxSize; arr = new int[maxSize]; // front = 0; // rear = 0 } //判断队列是否满 public boolean isFull(){ return (rear + 1) % maxSize == front; } //判断是否为空 public boolean isEmpty(){ return rear == front; } //添加数据到队列 public void addQueue(int n){ //判断队列是否满 if (isFull()){ System.out.println("队列满,不能加入数据~"); return; } //直接将数据加入 arr[rear] = n; //将 rear 后移,这里必须考虑取模 rear = (rear + 1) % maxSize; } //获取队列的数据,出队列 public int getQueue(){ //判断队列是否为空 if (isEmpty()){ // 通过抛出异常 throw new RuntimeException("队列空,不能取数据"); } // 这里需要分析出 front 是指向队列的第一个元素 // 1. 先把 front 对应的值保留到一个临时变量 // 2. 将 front 后移, 考虑取模 // 3. 将临时保存的变量返回 int value = arr[front]; front = (front + 1)% maxSize; return value; } //显示队列的所有数据 public void showQueue(){ //遍历 if (isEmpty()){ System.out.println("队列空的,没有数据~~"); return; } // 思路:从 front 开始遍历,遍历多少个元素 // 动脑筋 for (int i = front; i < front+size(); i++) { System.out.printf("arr[%d] = %d\n",i%maxSize,arr[i%maxSize]); } } //求出当前队列有效数据个数 public int size(){ //rear = 2 //front = 1 //maxSize = 3 return (rear + maxSize - front) % maxSize; } // 显示队列的头数据, 注意不是取出数据 public int headQueue(){ // 判断 if (isEmpty()) { throw new RuntimeException("队列空的,没有数据~~"); } return arr[front]; } }
分析
数组模拟队列
问题 && 解决
-
只添加一个数据:10,输入 s 回车后发现一共 3 个元素,只不过其他元素为 0
-
取出数据后,按 s 回车查看队列发现还在,虽然 h 查看队列头数据已改为 20 ,但感觉不妥
-
修改 showQueue() 方法
首先看定义:
front = -1; //指向队列头部,分析书 front 是 指向队列头的前一个位置 rear = -1; //指向队列尾,指向队列尾的数据(队列最后一个数据)
这里 for 循环中,i 修改为 front + 1(本质还是 0),长度变为 i <= rear
rear 就是指向队尾最后一个元素,所以队列数据就是从 front 到 rear(包括自身)
//显示队列所有数据 public void showQueue(){ //遍历 if (isEmpty()){ System.out.println("队列空,没有数据!"); return; } // for (int i = 0; i < arr.length; i++) { // System.out.printf("arr[%d] = %d\n",i,arr[i]); // } for (int i = front + 1; i <= rear; i++) { System.out.printf("arr[%d] = %d\n",i,arr[i]); } }
-
测试
向队列中添加数据后查看:
从队列中取数据后查看:
测试成功!
数组模拟环形队列
前提条件
首先理解:% 运算符
x % y = z,不管 x 如何变化(递增等等)z 一定是 0到 y-1(循环等等),很神奇,这是算法的精妙所在!!!
//数组最大容量
private int maxSize;
//变量的含义做一个调整: front 就指向队列的第一个元素, 也就是说 arr[front] 就是队列的第一个元素
//front 的初始值 = 0
private int front;
//变量的含义做一个调整:rear 指向队列的最后一个元素的后一个位置. 因为希望空出一个空间做为约定
//rear 的初始值 = 0
private int rear;
//该数组用于存放数据,模拟队列
private int[] arr;
如何判断队列是否满/空?
代码:
//判断队列是否满
public boolean isFull(){
return (rear + 1) % maxSize == front;
}
//判断是否为空
public boolean isEmpty(){
return rear == front;
}
推荐阅读文章:循环队列:判断队列空和满的3种方法
队列满的条件:(rear + 1) % maxSize == front
由rear 变量的含义可知:rear = maxsize - 1,队列满时,其实最后一个位置是空的,并未存储数据,所以 rear + 1 = maxsize,以代码中 maxSize = 4 为例,队列满时,最后一个数据位置为 2 (0,1,2,3),rear = 2+1=3,front = 0,套入公式:(3+1)% 4 == 0
队列空的条件:rear == front
当队列front、rear指针重合时,表明队列里没有数据
如何添加数据到队列?
代码:
public void addQueue(int n){
//判断队列是否满
if (isFull()){
System.out.println("队列满,不能加入数据~");
return;
}
//直接将数据加入
arr[rear] = n;
//将 rear 后移,这里必须考虑取模
rear = (rear + 1) % maxSize;
}
分析:
队列未满时,直接将数据赋值给 arr[rear],然后 rear 后移一位即可。rear 后移,不能直接 rear ++。 因为是环形队列,这样会有数组下标越界异常,还是以 maxsize = 4 为例:当队列中 arr[2],arr[3]存放数据,(arr[0],arr[1] 为空时),再次添加数据存放位置为:arr[0]
将参数n赋值给数组arr中rear的位置,由于rear在环形队列中指rear后一位,故无需考虑数组arr[0]的问题,此时的rear的位置将会由(rear+1)%maxSize所赋值过去,由于rear本身自带+1属性,故在表达式中的”1”是给即将添加的数据准备的,而rear本身自带的+1是为腾出一个空间准备的
如何获取队列的数据,出队列?
代码:
public int getQueue(){
//判断队列是否为空
if (isEmpty()){
// 通过抛出异常
throw new RuntimeException("队列空,不能取数据");
}
// 这里需要分析出 front 是指向队列的第一个元素
// 1. 先把 front 对应的值保留到一个临时变量
// 2. 将 front 后移, 考虑取模
// 3. 将临时保存的变量返回
int value = arr[front];
front = (front + 1)% maxSize;
return value;
}
分析:
如果不为空,则定义一个变量value作为用户取出的数值,同时将*arr[front]*赋值给value,并将其作为取出的数值。由于front自带+1的属性(定义),故无需考虑数组本身的特性
front后移一位留出一空,符合环形队列的约定,将其用于与数组最大容量maxSize取模,得出此时环形队列front应该在的位置,最终return value
如何显示队列的所有数据?
代码:
public void showQueue(){
//遍历
if (isEmpty()){
System.out.println("队列空的,没有数据~~");
return;
}
// 思路:从 front 开始遍历,遍历多少个元素
// 动脑筋
for (int i = front; i < front+size(); i++) {
System.out.printf("arr[%d] = %d\n",i%maxSize,arr[i%maxSize]);
}
}
分析:
arr[%d]可以用i自增时候的数值去对该环形队列进行取模,得出该数组的实际位置,同时通过arr数组锁定数值
如何求出当前队列有效数据个数?
代码:
public int size(){
//rear = 2
//front = 1
//maxSize = 3
return (rear + maxSize - front) % maxSize;
}
推荐阅读文章:数组实现环形队列-获取有效数据个数
如何显示队列的头数据, 注意不是取出数据?
代码:
public int headQueue(){
// 判断
if (isEmpty()) {
throw new RuntimeException("队列空的,没有数据~~");
}
return arr[front];
}
学以致用
LeetCode数组相关题目
-
难度简单: 283. 移动零
-
难度简单: 26. 删除有序数组中的重复项
-
难度简单:88. 合并两个有序数组
-
难度中等: 75. 颜色分类
LeetCode队列相关题目
-
难度中等:1695. 删除子数组的最大得分
-
难度中等:438. 找到字符串中所有字母异位词
-
难度中等:3. 无重复字符的最长子串
-
难度困难:76. 最小覆盖子串
-
难度困难:84. 柱状图中最大的矩形
-
难度困难:239. 滑动窗口最大值
数据结构和算法系列文章:持续更新中!!!