目的:只是为了帮助一些上课的同学整理笔记,有什么错误也欢迎指出。
课程详情:活动 - AcWing
目录
1.单链表
package 链表;
public class 单链表 {
static int N = 100010;
//head表示头结点的下标(非固定)
//e[i]表示节点i的值
//ne[i]表示节点i的next指针是多少
//idx 存储当前已经用到了哪个点
static int head,idx;
static int[] e = new int[N];
static int[] ne = new int[N];
public static void main(String[] args) {
}
public void init() {
head = -1;
idx = 0;
}
//头插法:第一个插入的点的下标是0
public void add_head(int x) {
e[idx] = x; //存储节点值
ne[idx] = head; //将插入的节点与下一个节点连接(下一个节点是head)
head = idx; //更新head值
idx++;
}
//在单链表中间 将b插入到下标是a的点后面
public void ad(int a, int b) {
e[idx] = b;
ne[idx] = ne[a];
ne[a] = idx;
idx++;
}
//将下标是a的点后面的b删除
public void remove(int a, int b) {
ne[a] = ne[b];
}
//将下标为k的点后面的点删除
public void remove1(int k) {
ne[k] = ne[ne[k]];
}
}
案例:
package 杂;
import java.util.Scanner;
/*
* 由于该中的计数方式是使用插入的顺序,所以需要一个数组来记录数的顺序,而e刚好可以记录,但是注意的是e是从0开始的
*/
public class test {
static int N = 100010;
static int[] e = new int[N];
static int[] ne = new int[N];
//初始化
static int head = -1, idx = 0;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int M = sc.nextInt();
while (M-- > 0) {
String lable = sc.next();
switch(lable) {
case "H" :
int x = sc.nextInt();
add_head(x);
break;
case "D" :
int k = sc.nextInt();
remove(k);
break;
case "I":
int a = sc.nextInt();
int b = sc.nextInt();
add(a, b);
break;
}
}
sc.close();
//遍历输出
for (int i = head; i != -1; i = ne[i]) {
System.out.print(e[i] + " ");
}
}
private static void add(int a, int b) {
a -= 1;
e[idx] = b;
ne[idx] = ne[a];
ne[a] = idx;
idx++;
}
private static void remove(int k) {
k -= 1;
if (k == 0){
head = ne[head];
}else if (k == -1){
ne[head] = -1;
head = -1;
}else{
ne[k] = ne[ne[k]];
}
}
private static void add_head(int x) {
e[idx] = x;
ne[idx] = head;
head = idx;
idx++;
}
}
//10
//H 9
//I 1 1
//D 1
//D 0
//H 6
//I 3 6
//I 4 5
//I 4 5
//I 3 4
//D 6
2.双链表
与单链表思路相似,只是每个结点从原本的存储节点值和下一位节点地址,修改为“该节点前驱节点的下标地址,节点值,后继节点下标地址”,这里我们可以将ne数组修改成l,r数组,r依旧是单链表ne数组作用,l就作为ne数组记录前驱下标作用。(这段代码借用了csdn的资料,自己的被typora卡没了)
head:0 tail:1
0表示左端点,1表示右端点
#include<bits/stdc++.h>
using namespace std;
const int N =1e5+10;
int e[N],r[N],l[N],idx;
//e用来存储数值,l[]用来存储前驱(左边数的下标),r[]用来存储后继(右边数的下标),idx用来记录当前下标
void init()//初始化
{
//0是左端点,1是右端点
r[0]=1;
l[0]=0;
idx=2;
}
void add_to_right(int k,int x)//在k右边插入x
{
e[idx]=x;//将x存入当前下标的数值数组中
r[idx]=r[k];//当前下标的后继为k的后继
l[idx]=k;//当前下标的前驱为k
l[r[k]]=idx;//k的后继节点的前驱为idx
r[k]=idx++; //k的后继节点为idx; idx++;
}
void add_to_left(int k,int x)//在k的左边插入x
{
add_to_right(l[k],x);//等价于在k的前驱的右边插入x
}
void remove(int k)//删除第k个结点
{
r[l[k]]=r[k];//k的前驱的后继为k的后继
l[r[k]]=l[k];//k发后继发前驱为k的前驱
}
void add_to_head(int x )//在链表的最左端插入数x
{
add_to_right(0,x);//在0的后面插入x
}
void add_to_tail(int x)//在链表的最右端插入数x
{
add_to_right(l[1],x);
}
int main()
{
int m;
init();
scanf("%d",&m);
while(m--)
{
string c;
cin>>c;
if(c=="L")
{
int x;
scanf("%d",&x);
add_to_head(x);
}
else if(c=="R")
{
int x;
scanf("%d",&x);
add_to_tail(x);
}
else if(c=="D")
{
int k;
scanf("%d",&k);
remove(k+1);
}
else if(c=="IL")
{
int k,x;
scanf("%d%d",&k,&x);
add_to_left(k+1,x);
}
else
{
int k,x;
scanf("%d%d",&k,&x);
add_to_right(k+1,x);
}
}
for(int i=r[0];i!=1;i=r[i])
{
printf("%d ",e[i]);
}
printf("\n");
return 0;
}
3.栈 & 队列
栈:先进后出
队列:先进先出
因为是c++语言的做法,这部分可以仅做参考,Java语言可以考虑java自带类(个人参考:栈和队列(超详细Java实现)_java栈和队列_努力写代码的菜鸟的博客-CSDN博客)
-
Stack类:栈类 过时 (不建议,建议使用动态数组)
-
Queue:队列类 Queue queue = new LinkedList<Integer>(); (建议用于实现队列,但是需要注意其中方法的区别:add和offer,remove和poll,element和peek)
-
Deque:双端队列(建议用于实现栈,队列)(java关于Deque的使用_java deque_onedegree的博客-CSDN博客)
栈(数组构建):
int stk[N], tt; //栈底从0开始 即在插入操作之中先对其自增,也代表从1开始
//插入操作
stk[ ++tt ] = x;
//弹出操作
tt--;
//判断是否为空
if (tt > 0) {
//not empty
}else {
//empty
}
//栈顶
stk[tt];
队列:
//在队尾插入元素,在队头弹出元素
int q[N], hh, tt = -1;
//q[]代表队列数组,而hh代表头,tt代表尾
//插入 先自增再插入,保证tt一直是指向队列最后一位的下标,便于判断队列时候为空
q[ ++tt ] = x;
//弹出
hh++;
//判断队列是否为空
if (hh <= tt) not empty
else empty
//取出队头元素
q[ hh ]
//取出队尾元素
q[ tt ]
循环队列:(注意tt一直指向的是最后一位的下一位,而单调队列是指最后一位)
// hh 表示队头,tt表示队尾的后一个位置
int q[N], hh = 0, tt = 0;
// 向队尾插入一个数 注意这里是先读取tt下标的数组值,再自增tt 和单调队列区分!!
//所以tt一直代表的是队列最后一位下标的下一位!!
q[tt ++ ] = x;
// 这里判断循环队列是否已经达到末尾,条件达成则返回数组最初位
if (tt == N) tt = 0;
// 从队头弹出一个数
hh ++ ;
//循环条件设置
if (hh == N) hh = 0;
// 队头的值
q[hh];
// 队尾的值
t = tt-1;
q[t]
// 判断队列是否为空
if (hh != tt)
{
//不为空
}
单调栈
—— 模板题 AcWing 830. 单调栈
常见模型:找出每个数左边离它最近的比它大/小的数
import java.util.Scanner;
public class 单调栈 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int N = 10010;
int[] stk = new int[N];
int tt = 0,num = 0;
//逐个插入栈中,判断是当前栈顶的大小对比,如果新插入的元素小于栈顶元素,则不断弹出栈顶元素,直到栈中元素单调递增
for (int i = 0; i < n; i ++) {
num = sc.nextInt();
//当栈元素在新增的元素后不再单调递减时,需要将所有大于或等于新增元素的元素先弹出
while (tt > 0 && stk[tt] >= num) {
tt--;
}
//判断输出元素
if (tt == 0) {
System.out.print(-1 + " ");
}else {
System.out.print(stk[tt] + " ");
}
//记得将新遍历的元素压入栈中
stk[ ++tt ] = num;
}
}
}
单调队列
—— 模板题 AcWing 154. 滑动窗口
常见模型:找出滑动窗口中的最大值/最小值
package 单调队列;
import java.io.*;
public class 滑动窗口 {
public static void main(String[] args) throws IOException {
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
String[] s = bf.readLine().split(" ");
int n = Integer.parseInt(s[0]);
int k = Integer.parseInt(s[1]);
int[] q = new int[n];
int[] a = new int[n];
int tt = -1, hh = 0;
int[] min = new int[n];
int[] max = new int[n];
int mi = 1000010, ma = -1000010;
//队列中储存的是数组的下标
s = bf.readLine().split(" ");
for (int i = 0; i < n; i++) {
//初始化
a[i] = Integer.parseInt(s[i]);
}
//最小值
for (int i = 0; i < n; i++) {
//弹出队列头
if (tt >= hh && i - k + 1 > q[hh]) {
hh++;
}
//判断队列当前是否是单调递减,否则让队列尾前移一位
while (tt >= hh && a[q[tt]] >= a[i]) {
tt--;
}
//将新元素压入队列
q[ ++tt ] = i;
//输出
if ( i - k + 1 >= 0) {
System.out.print(a[q[hh]] + " ");
}
}
System.out.println(" ");
//最大值
for (int i = 0; i < n; i++) {
//弹出头
if (tt >= hh && i - k + 1 > q[hh]) {
hh++;
}
//递减
while (hh <= tt && a[q[hh]] <= a[i] ) {
tt--;
}
q[ ++tt ] = i;
if (i - k + 1 >= 0) {
System.out.print(a[q[hh]] + " ");
}
}
//8 3
//1 3 -1 -3 5 3 6 7
}
}