数组模拟链表:
更加方便,更加快捷,不需要新建数据结构。大部分都是这样写。
单链表
链表:
两个数组,
有头指针head
e【】//存储当前节点的值
next【】//存储当前节点指向的下一个位置,也是指针
idx //表示当前节点的存储空间
//一个节点存储两个东西:
//head头指针存储下一个位置
ps:可以用O(1)的时间查到下一个点的位置,找不到前一个的位置。
输出时注意,不能按照顺序输出,要按照定义进行输出,未节点最后指向的是空集,也就是-1;
for(int i = head; i != -1; i = next[i]){
System.out.print(e[i] + " ");
}
}
【】一个值,【】一个next指针
插入:任意指针可以插入。
1.head 存储的就是下一个指针
2.每次新开辟一个位置idx是为了给新节点使用。所以每次结尾都需要idx++;
链表:
- 头插法:
head开始=-1;index初始化
插值法:看清出插入点
ps:
插入操作时,一定要先连接后面的数值,否则,记不住前一个节点指向的哪一个点。 - 删除第一个节点时需要特殊判断:
删除的是,head指向的下一个点,head指针。
题目:
模板
// head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
int head, e[N], ne[N], idx;
// 初始化
void init()
{
head = -1;
idx = 0;
}
// 在链表头插入一个数a
void insert(int a)
{
e[idx] = a, ne[idx] = head, head = idx ++ ;
}
// 将头结点删除,需要保证头结点存在
void remove()
{
head = ne[head];
}
java:
package 大学菜;
//1。等于时,使用最好的判断方法是equal
//2。节点位置的判断,插入第k个
//3。输出时,注意按照定义进行输出
//4。初始化是,头指针指向空。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class 数组模拟链表4 {
static int N=100010;
static int[] e = new int[N];
static int[] next = new int[N];
static int head;
static int index;
public static void init(){
head=-1;//最后这个-1会跑到最后
index=0;//index没有必要在开始赋值,初始哈赋值即可
}
public static void Insert(int k,int x){
//插入第k个点,传进来的时候是k-1,下面才是第k个
e[index] = x;
next[index] =next[k];
next[k] =index;
index++;
}
public static void Delete(int k){
//考虑头节点的特殊情况
next[k] = next[next[k]];
}
public static void HeadInsert(int x){
e[index] = x;
next[index] = head;
head = index;
index++;
}
public static void main(String[] args) throws IOException {
init();
//输入:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.valueOf(reader.readLine());
for(int i=0;i<n;i++){
String[] str = reader.readLine().split(" ");
String fir = str[0];
if(fir.equals("H")){//equals判断方法更好
int x = Integer.valueOf(str[1]);
HeadInsert(x);
}else if(fir.equals("I")){
int k = Integer.valueOf(str[1]);
int x = Integer.valueOf(str[2]);
Insert(k-1,x);
}else if(fir.equals("D")){
int k = Integer.valueOf(str[1]);
if(k==0){
head=next[k];
}else {Delete(k-1);}
}
}
//
for(int i=head;i != -1;i=next[i]){//输出遵守定义
System.out.print(e[i]+" ");
}
}
}
双链表
双链表:
三个数组
没有头指针,有最左和最右节点。
idx 当前的位置,也是当前节点的位置。
e【】//存储当前节点的值
l【】//存储当前节点左节点的指针
r【】//存储当前右节点的指针
1.插入节点:
插入右节点,插入左节点,都可以用插入右节点使用。
2.删除节点
第k个实际上是k-1
3.最左,最右插入
模板:
/ e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
int e[N], l[N], r[N], idx;
// 初始化
void init()
{
//0是左端点,1是右端点
r[0] = 1, l[1] = 0;
idx = 2;
}
// 在节点a的右边插入一个数x
void insert(int a, int x)
{
e[idx] = x;
l[idx] = a, r[idx] = r[a];
l[r[a]] = idx, r[a] = idx ++ ;
}
// 删除节点a
void remove(int a)
{
l[r[a]] = l[a];
r[l[a]] = r[a];
}
借用别人的图进行介绍
:初始化有两个节点
所以第三个用,也就是存储数据的第一个用下标2表示。
delete(),add_kl(),add_kr()
函数在内部实现时,是同过下标index来操作的,
所以传参时,要通过关系index=k+1 把正确的index值传进去
题目:
实现一个双链表,双链表初始为空,支持 5 种操作:
在最左侧插入一个数;
在最右侧插入一个数;
将第 k 个插入的数删除;
在第 k 个插入的数左侧插入一个数;
在第 k 个插入的数右侧插入一个数
现在要对该链表进行 M 次操作,进行完所有操作后,从左到右输出整个链表。
注意:题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n 个插入的数。
输入格式
第一行包含整数 M,表示操作次数。
接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:
L x,表示在链表的最左端插入数 x。
R x,表示在链表的最右端插入数 x。
D k,表示将第 k 个插入的数删除。
IL k x,表示在第 k 个插入的数左侧插入一个数。
代码
package 大学菜;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class 数组模拟双链表 {
//e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
static int N = 100010;
static int[] e = new int[N];
static int[] l_next = new int[N];
static int[] r_next = new int[N];
static int index ;
public static void init(){
//直接用数组进行模拟左右端点
r_next[0] = 1;//
l_next[1] = 0;//
index =2;///初始化第1个插入节点下标为2,所以操作第k个数时都要+1 !!
}
public static void Linsert(int x){
//在最左边插入
e[index] = x;
//当前节点的右指针
l_next[index] = 0;
r_next[index] = r_next[0];
l_next[r_next[0]] = index;
r_next[0] = index;
index++;
}
public static void Rinsert(int x){//最右侧插入
e[index] =x;
//新建节点右连接1点【=============葛·8
r_next[index] = 1;
l_next[index] = l_next[1];
r_next[l_next[1]] = index;
l_next[1] = index;
index++;
}
public static void ILinsert(int k,int x){//传入的就是k-1
//
e[index] = x;
l_next[index] = l_next[k];
r_next[index] = k;
r_next[l_next[k]] = index;
l_next[k] = index;
index++;
}
public static void Rlinsert(int k,int x){
e[index] = x;
r_next[index] = r_next[k];
l_next[index] = k;
l_next[r_next[k]] = index;
r_next[k] = index;
index++;
}
public static void Delete(int k ){
r_next[l_next[k]] = r_next[k];
l_next[r_next[k]] = l_next[k];
}
public static void main(String[] args) throws IOException {
//buff
init();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.valueOf(reader.readLine());
for(int i=0;i<n;i++){
String[] str = reader.readLine().split(" ");
String s = str[0];
if(s.equals("L")){
int x = Integer.valueOf(str[1]);
Linsert(x);
}else if(s.equals("R")){
int x= Integer.valueOf(str[1]);
Rinsert(x);
}else if(s.equals("D")){
int k = Integer.valueOf(str[1]);
Delete(k+1);
}else if(s.equals("IL")){
int k = Integer.valueOf(str[1]);
int x = Integer.valueOf(str[2]);
ILinsert(k+1,x);//k=2
}else{//那么最后还是使用else吧
int k = Integer.valueOf(str[1]);
int x = Integer.valueOf(str[2]);
Rlinsert(k+1,x);
}
}
for(int i=r_next[0];i!=1;i=r_next[i]){//第一个数等于的是0de右边第一个
System.out.print(e[i]+" ");
}
}
}
注意:
1.1作为的是最右节点
2.0作为的是最左节点
3.初始化时l[0] =1;r[1] =0;
4.输出时,从0的右边第一个进行输出。结尾从1的左边第一个结尾。
邻接表
存储树和图,使用数组进行存储。
数组模拟栈
栈:先进后出
1.插入:
每次指针都指向最后一个有数据的元素,所以先++,再指向新元素。
2.弹出:
栈顶元素先弹出,再--。
3.判断栈是否为空:tt指针是否为0;相当于下标。
4.查询数组元素:
模板:
// tt表示栈顶
int stk[N], tt = 0;
// 向栈顶插入一个数
stk[ ++ tt] = x;
// 从栈顶弹出一个数
tt -- ;
// 栈顶的值
stk[tt];
// 判断栈是否为空
if (tt > 0)
{
}
题目:
实现一个栈,栈初始为空,支持四种操作:
push x – 向栈顶插入一个数 x;
pop – 从栈顶弹出一个数;
empty – 判断栈是否为空;
query – 查询栈顶元素。
现在要对栈进行 M 个操作,其中的每个操作 3 和操作 4 都要输出相应的结果。
输入格式
第一行包含整数 M,表示操作次数。
接下来 M 行,每行包含一个操作命令,操作命令为 push x,pop,empty,query 中的一种。
输出格式
对于每个 empty 和 query 操作都要输出一个查询结果,每个结果占一行。
其中,empty 操作的查询结果为 YES 或 NO,query 操作的查询结果为一个整数,表示栈顶元素的值。
数据范围
1≤M≤100000,
1≤x≤109
所有操作保证合法。
package 大学菜;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class 模拟栈 {
//数组模拟栈
static int N =100010;
static int[] stack = new int[N];
static int top;//栈顶指针
public static void init(){
top =-1;
}
public static void push(int x){
//top++;
top++;
stack[top] = x;
}
public static void query(){
//查看栈顶元素
System.out.println(stack[top]);
}
public static void pop(){
//删除栈顶
top--;
}
public static boolean empyty(){
if(top==-1){
return true;
}else return false;
}
public static void main(String[] args) throws IOException {
init();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.valueOf(reader.readLine());
for(int i=0;i<n;i++){
String[] str = reader.readLine().split(" ");
//判断
String s0 = str[0];
if(s0.equals("push")){
int x = Integer.valueOf(str[1]);
push(x);
}else if(s0.equals("query")){
query();//询问栈顶元素
}else if(s0.equals("pop")){
pop();//弹出栈顶元素
}else{
if(empyty()){
System.out.println("YES");
}else {
System.out.println("NO");
};//判空
}
}
}
}
队列:先进先出。
两头操作,
插入:
队尾插入元素。
指向的是队尾的元素,
先++,再指向元素
弹出:
++
队头弹出元素。
模板:
// hh 表示队头,tt表示队尾
int q[N], hh = 0, tt = -1;
// 向队尾插入一个数
q[ ++ tt] = x;
// 从队头弹出一个数
hh ++ ;
// 队头的值
q[hh];
// 判断队列是否为空
if (hh <= tt)
{
}
题目:
实现一个队列,队列初始为空,支持四种操作:
push x – 向队尾插入一个数 x;
pop – 从队头弹出一个数;
empty – 判断队列是否为空;
query – 查询队头元素。
现在要对队列进行 M 个操作,其中的每个操作 3 和操作 4 都要输出相应的结果。
输入格式
第一行包含整数 M,表示操作次数。
接下来 M 行,每行包含一个操作命令,操作命令为 push x,pop,empty,query 中的一种。
输出格式
对于每个 empty 和 query 操作都要输出一个查询结果,每个结果占一行。
其中,empty 操作的查询结果为 YES 或 NO,query 操作的查询结果为一个整数,表示队头元素的值。
数据范围
1≤M≤100000,
1≤x≤109,
所有操作保证合法。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
//两个指针
static int N = 100010;
static int Dl[] = new int[N];
static int left;
static int right;
//初始化,指针都指向-1;
public static void init(){
left = -1;//队头
right = -1;//队尾
}
public static void push(int x){
right++;
Dl[right] = x;
//push x – 向队尾插入一个数 x;
}
public static void pop(){
left++;
//pop – 从队头弹出一个数;
}
public static boolean empty(){
//empty – 判断队列是否为空;
if(left==right){return true;
}else return false;
}
public static void query(){
//query – 查询队头元素。
System.out.println(Dl[left+1]);
}
public static void main(String[] args) throws IOException {
//n
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.valueOf(reader.readLine());
for(int i=0;i<n;i++){
String[] str = reader.readLine().split(" ");
String s0 = str[0];
if(s0.equals("push")){
int x = Integer.valueOf(str[1]);
push(x);
}else if(s0.equals("pop")){
pop();
}else if(s0.equals("empty")){
if(empty()){
System.out.println("YES");
}else {System.out.println("NO");};//输出
}
else {
query();//输出
}
}
}
}
循环队列
package 大学菜;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class 数组模拟循环队列 {
//两个指针
static int N = 100010;
static int Dl[] = new int[N];
static int left;
static int right;
//初始化,指针都指向-1;
public static void init(){
left = 0;//队头
right = 0;//队尾
}
public static void push(int x,int n){
right++;//9
Dl[right] = x;
if(right==n){
right=0;
}
//push x – 向队尾插入一个数 x;
}
public static void pop(int n){
left++;
if(left==n){left=0;}
//pop – 从队头弹出一个数;
}
public static boolean empty(){
//empty – 判断队列是否为空;
if(left==right){return true;
}else return false;
}
public static void query(){
//query – 查询队头元素。
System.out.println(Dl[left+1]);
}
public static void main(String[] args) throws IOException {
//n
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.valueOf(reader.readLine());
for(int i=0;i<n;i++){
String[] str = reader.readLine().split(" ");
String s0 = str[0];
if(s0.equals("push")){
int x = Integer.valueOf(str[1]);
push(x,n);
}else if(s0.equals("pop")){
pop(n);
}else if(s0.equals("empty")){
if(empty()){
System.out.println("YES");
}else {System.out.println("NO");};//输出
}
else {
query();//输出
}
}
}
}
单调栈
模板
//int tt = 0;
for (int i = 1; i <= n; i ++ )
{
while (tt && check(stk[tt], i)) tt -- ;
stk[ ++ tt] = i;
}
题目:
给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。
输入格式
第一行包含整数 N,表示数列长度。
第二行包含 N 个整数,表示整数数列。
输出格式
共一行,包含 N 个整数,其中第 i 个数表示第 i 个数的左边第一个比它小的数,如果不存在则输出 −1
q求找到,每一个数,左边离他最近的数。直接将大的数进行删除,栈中存储的是单调的序列,每次更新栈顶
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
static int N = 100010;
static int[] A = new int[N];
static int[] stack = new int[N];
static int top;
public static void init() {
top =0;
stack[0] = -1;
}
public static int Min(int x){
//判断x的大小
while (x<=stack[top]){//当x比当前的栈顶元素小时,则需要出栈
top--;
}
int c = stack[top];
top++;
stack[top] = x;
return c;
}
public static void main(String[] args) throws IOException {
init();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.valueOf(reader.readLine());
String[] str = reader.readLine().split(" ");
for(int i=0;i<n;i++){
A[i] = Integer.valueOf(str[i]);
int c = Min(A[i]);
System.out.print(c+" ");
}
}
}
单调队列
注意:
//第一种会造成死循环
while (left<=right){
if(arr[i]<arr[queue[right]]){
right--;
}
}
//下面这个可以进行跳出循环
while (left<=right){
if(arr[i]<arr[queue[right]]){
right--;
}else {
break;
}
}
模板:
常见模型:找出滑动窗口中的最大值/最小值
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
while (hh <= tt && check_out(q[hh])) hh ++ ;
// 判断队头是否滑出窗口
while (hh <= tt && check(q[tt], i)) tt -- ;
q[ ++ tt] = i;
}
1.求滑动窗口里面的最大值和 最小值
题目:
给定一个大小为 n≤106 的数组。
有一个大小为 k 的滑动窗口,它从数组的最左边移动到最右边。
你只能在窗口中看到 k 个数字。
每次滑动窗口向右移动一个位置。
以下是一个例子:
该数组为 [1 3 -1 -3 5 3 6 7],k 为 3。
java
package 大学菜;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class 单调队列2 {
static int N=100010;
static int[] A = new int[N];
static int[] queue = new int[N];
static int left=0;//队头指针
static int right=-1;//队尾指针
public static void function(int[] arr,int n,int k){
//开始的时候,队列中没有元素
for(int i=0;i<n;i++){
//每次看看是否应该保留当前的queue中的位置,如果当前窗口的首元素位置已经不包含之前的最小元素,就让队头元素出战。
if (left<=right && i-k+1 >queue[left]){
left++;
}
//while (left<=right && arr[i]<arr[queue[right]]) right--;//刚才
while (left<=right){
if(arr[i]<arr[queue[right]]){
right--;
}else {
break;
}
}
//找到合适的地方后
right++;
queue[right] = i;//存储的是下标的位置。
//判断是否已经数字到能k;
if(i+1>=k){
//输出元素
System.out.print(arr[queue[left]]+" ");
}
}
}
//开始的时候,队尾应在-1的位置。下一次加加才可以有元素。
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String[] str = reader.readLine().split(" ");
int n =Integer.valueOf(str[0]);
int k= Integer.valueOf(str[1]);
//System.out.println(n+" " +k);
String[] str1 = reader.readLine().split(" ");
for(int i=0;i<n;i++){
A[i] = Integer.valueOf(str1[i]);//System.out.print(A[i]);
}
//维护最小数值
//queue 存储的是维护的最小值的下标。
//i-k+1 是当前窗口的首元素位置。
//left<right 说明队列里面没有元素。
function(A,n,k);
}
}
注意:
维护一个窗口的队列,然后可以使用弹出弹入 再进行求解大值,和小值
求解最小值时:
存在逆序对时,直接删除前面的大值。
找最小值,直接找对头,与单调队列类似。维护
出队列:
i-k+1;
最大值:维护一个最大的值。
队头维护一个最大的值。
顺序不能改变。
KMP算法
字符串匹配。
分别有一个长串和一个短串,
短串去匹配长串。
1.先求出nextj
2.在进行匹配
先思考朴素做法,在进行思考,更快捷的做法。
需要进行三段进行比较,三段是否相等的地方可以省去一步一步的进行移动,时间可以大量节省。
最长后缀
java
package 大学菜;
import java.io.*;
public class kmp {
static int N = 1000100;
static char[] p = new char[N];
static char[] pp = new char[N];
static char[] s = new char[N];
static char[] ss = new char[N];
static int[] next = new int[N];//
static int[] prefix = new int[N];//nextj数组
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
int np =Integer.valueOf(reader.readLine());
String str = reader.readLine();
pp= str.toCharArray();//利用toCharArray方法转换
for( int i=1;i<=np;i++){
p[i] = pp[i-1];
}
int ns = Integer.valueOf(reader.readLine());
String str2 = reader.readLine();
ss = str2.toCharArray();//从0开始
for(int i=1;i<=ns;i++){
s[i] = ss[i-1];
}
// 构造前缀数组
int prefix[] = new int[np + 1];
for (int i = 2, j = 0; i <= np; i++) {
//i从2开始,因为prefix[1]肯定为0
while (j != 0 && p[i] != p[j + 1])
j = prefix[j];
if (p[i] == p[j + 1])
j++;
prefix[i] = j;
}
// kmp匹配
for (int i = 1, j = 0; i <= ns; i++) {
while (j != 0 && s[i] != p[j + 1]) {
j = prefix[j]; // s[i] != p[j + 1]即不匹配,则往后移动
}
if (s[i] == p[j + 1])
j++; // 匹配时将j++进行下一个字符得匹配
if (j == np) { // 匹配了n字符了即代表完全匹配了
writer.write(i - np +" ");
j = prefix[j]; // 完全匹配后继续搜索
}
}
writer.flush();
writer.close();
reader.close();
}
}