連結串列、棧與佇列、kmp;
陣列模擬單連結串列:
用的最多的是鄰接表--就是多個單連結串列:
作用:儲存樹與圖
需要明確相關定義:
為什麼需要使用陣列模擬連結串列
- 比使用結構體 或者類來說 速度更快
- 程式碼簡潔
- 演算法題:空間換時間
圖解:
head儲存連結串列頭,e[]儲存節點的值,ne[]儲存節點的next指標,idx表示當前用到了哪個節點
import java.util.*;
public class Main{
static int N = 100010;
static int idx,head;
static int[] e = new int[N];
static int[] next = new int[N];
// 初始化
static void init(){
head = -1;
idx = 0;
}
// 向連結串列頭插入一個數
static void insertHead(int x){
e[idx] = x;
next[idx] = head;
head = idx++;
}
// 向k位置插入x
static void insert(int k,int x){
e[idx] = x;
next[idx] = next[k];
next[k] = idx++;
}
// 刪除k位置的數
static void delete(int k){
next[k] = next[next[k]];
}
// 主函式
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
init();
int n = sc.nextInt();
while(n-- != 0){
String s = sc.next();
if(s.equals("H")){
int x = sc.nextInt();
insertHead(x);
}else if(s.equals("D")){
int k = sc.nextInt();
if(k == 0) head=next[head];
else delete(k-1);
}else if(s.equals("I")){
int k = sc.nextInt();
int x = sc.nextInt();
insert(k-1,x);
}
}
for(int i =head;i != -1;i=next[i]){
System.out.print(e[i]+" ");
}
}
}
陣列模擬雙連結串列:
作用:優化某些問題
棧:
先進後出
// tt表示棧頂
int stk[N], tt = 0;
// 向棧頂插入一個數
stk[ ++ tt] = x;
// 從棧頂彈出一個數
tt -- ;
// 棧頂的值
stk[tt];
// 判斷棧是否為空
if (tt > 0)
{
}
佇列:
先進先出
1. 普通佇列:
// hh 表示隊頭,tt表示隊尾
int q[N], hh = 0, tt = -1;
// 向隊尾插入一個數
q[ ++ tt] = x;
// 從隊頭彈出一個數
hh ++ ;
// 隊頭的值
q[hh];
// 判斷佇列是否為空
if (hh <= tt)
{
}
2. 迴圈佇列
// hh 表示隊頭,tt表示隊尾的後一個位置
int q[N], hh = 0, tt = 0;
// 向隊尾插入一個數
q[tt ++ ] = x;
if (tt == N) tt = 0;
// 從隊頭彈出一個數
hh ++ ;
if (hh == N) hh = 0;
// 隊頭的值
q[hh];
// 判斷佇列是否為空
if (hh != tt)
{
}
單調棧:
每個數找到左邊離自己最近且比自己小的數:
暴力演算法 :
import java.util.*;
import java.io.*;
public class Main{
static int N = 100010;
static int[] q = new int[N];
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
for(int i = 0;i < n; i++){
q[i] = sc.nextInt();
}
for(int i = 0;i < n;i++){
int index =0;
for(int j = i-1;j >=0;j--){
if(q[i]>q[j]){
index = j;
System.out.print(q[j]+" ");
break;
}
}
if(q[i]<=q[index]){
System.out.print("-1 ");
}
}
}
}
AC程式碼:
import java.util.*;
public class Main{
static int N = 100010;
static int[] stk = new int[N];
static int tt=0;
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
for(int i = 0; i < n; i++){
int x = sc.nextInt();
// 如果棧中還有元素並且棧頂元素大於x的話,從棧頂彈出;
while(tt != 0 && stk[tt] >= x) tt--;
// 上面結束以後是棧頂元素小於輸入的元素; stk[tt] < x; 這樣就保證了x元素在左邊並且小於本身的元素是棧頂元素;
//如果棧中有元素,輸出棧頂元素
if(tt != 0) System.out.print(stk[tt]+" ");
// 否則棧中沒有元素
else System.out.print("-1"+" ");
// 將元素加入棧中
stk[++tt] = x;
}
}
}
單調佇列:
解題思路:
- 判斷隊頭出沒出視窗 if true-->hh++;
- 求最小值,保持佇列單調上升,判斷隊尾元素tt與當前元素a[i]的大小,若tt >=a[i],剔除隊尾元素;
- 求最大值,保持佇列單調下降,判斷隊尾元素tt與當前元素a[i]的大小,若tt <=a[i],剔除隊尾元素;
- 將當前元素下標加入隊尾;
- 如果滿足條件輸出
注:佇列是先進先出模式,只有在佇列中保持單調性 才能保證,隊頭為最小值或者最大值;
import java.io.*;
public class Main{
static int N = 1000010;
// 原陣列
static int[] a = new int[N];
// 佇列
static int[] q = new int[N];
static int hh=0,tt=-1;
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
String[] num = br.readLine().split(" ");
// a陣列中由n個元素
int n = Integer.parseInt(num[0]);
// 滑動視窗的大小
int k = Integer.parseInt(num[1]);
String[] nums = br.readLine().split(" ");
// 初始化a陣列
for(int i = 0; i < n; i++) a[i] = Integer.parseInt(nums[i]);
// 查詢最小值
for(int i = 0; i < n; i++){
// 判斷當前視窗是否大於滑動視窗的大小
if(hh <= tt && i-q[hh]+1 > k) hh++;
// 判斷如果有元素並且隊尾元素大於入隊元素,則捨棄隊尾元素來保證單調上升的佇列;
while(hh <= tt && a[q[tt]] >= a[i]) tt--;
//向佇列加入元素下標
q[++tt] = i;
// 如果下標大於等於視窗大小,那麼輸出隊頭元素
if(i+1 >=k) bw.write(a[q[hh]]+" ");
}
bw.write("\n");
hh = 0;
tt = -1;
// 查詢最大值
for(int i = 0; i < n; i++){
// 判斷當前視窗是否大於滑動視窗的大小
if(hh <= tt && i-q[hh]+1 > k) hh++;
// 判斷如果有元素並且隊尾元素小於入隊元素,則捨棄隊尾元素來保證單調上升的佇列;
while(hh <= tt && a[q[tt]] <= a[i]) tt--;
//向佇列加入元素下標
q[++tt] = i;
// 如果下標大於等於視窗大小,那麼輸出隊頭元素
if(i+1 >=k) bw.write(a[q[hh]]+" ");
}
bw.flush();
br.close();
bw.close();
}
}
KMP演算法:
KMP定義:是取自三個發明人的首字母組成的;
作用:是一個字串匹配演算法,對暴力的一種優化
kmp實現的方式:求next[]、以及匹配字串
明確其中的概念:
對字串的匹配,需要兩個字串:長字串為模板字串,短的字串為匹配字串;
字首與字尾的概念:(很重要)
舉個例子:列舉
ababa
其字首為:
a,ab,aba,abab
其字尾:
baba,aba,ba,a
其中字首與字尾的最長且相同的元素字串是:aba
;長度length:3。
模板串的匹配:
匹配失敗的話,假設p1串往後移動到p2串位置,表明1串=2.2串;
如果匹配失敗,p串最少往右移動多少(看next[]陣列),可以使得p串與s串相等,由圖可知:往後移動多少是看p串,如果我們能預處理來這個東西:使得p1-2.1串能與p1.3串相等;這個相等的最大值是多少,值越大,則表示往後移動p串的距離越少;
// kmp匹配過程,遍歷s模板每個元素
for(int i =1, j =0; i<=m; i++ ){
// 如果j回退到0或者i位置元素與j+1位置的元素不相同,那麼執行回退操作,
//j退回next[i]處,即字首與字尾相同的區間最後元素位置
while(j != 0 && s[i] != p[j+1]) j = next[j];
if(s[i] == p[j+1]) j++;
// 如果匹配成功
if(j ==n){
// 輸出匹配元素在s模板中的起始位置
bw.write(i-n+" ");
// 繼續匹配;
j = next[j];
}
也就是p串的字首與字尾相同的最大字串是多少;----也就是next[]陣列;
next[]陣列(難點)
其中next[j]陣列表示的是:子串p[1~j]的最長相等前字尾的字首最後一位的下標。
對 p = “abcab”
p a b c a b
下標 1 2 3 4 5
next[ ] 0 0 0 1 2
對next[ 1 ] :字首 = 空集—————字尾 = 空集—————next[ 1 ] = 0; (特殊點)
對next[ 2 ] :字首 = { a }—————字尾 = { b }—————next[ 2 ] = 0;
對next[ 3 ] :字首 = { a , ab }—————字尾 = { c , bc}—————next[ 3 ] = 0;
對next[ 4 ] :字首 = { a , ab , abc }—————字尾 = { a . ca , bca }—————next[ 4 ] = 1;
對next[ 5 ] :字首 = { a , ab , abc , abca }————字尾 = { a , ab , cab , bcab}————next[ 5 ] = 2;
// 實現next陣列(找到字首與字尾相同的最大元素長度)
int[] next = new int[n+1];
//next[1] = 0;
for(int i = 2,j =0; i<= n;i++){
// 如果j沒有回退到0並且i位置元素與j+1位置的元素不相同,那麼執行回退操作,
//j退回next[i]處,即字首與字尾相同的區間最後元素位置
while(j !=0 && p[i] != p[j+1]) j = next[j];
// 如果i與j+1相同,那麼移動j向後匹配
if(p[i] == p[j+1]) j++;
// p[1,j] = p[i-j+1,i];字首與字尾相同;i表示終點
next[i] = j;
}
完整程式碼:
import java.util.*;
import java.io.*;
//下標為什麼從1開始,簡化程式碼的複雜度;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
// 對模式串p進行操作
int n = Integer.parseInt(br.readLine());
char[] p = new char[n+1];
// 將輸入的字元儲存到緩衝區
String pstr = br.readLine();
for(int i = 1; i <=n;i++){
// 取出的字串下標-1;
p[i] = pstr.charAt(i-1);
}
// 對模板串進行操作
int m = Integer.parseInt(br.readLine());
char[] s = new char[m+1];
String sstr = br.readLine();
for(int i = 1;i <= m;i++){
s[i] = sstr.charAt(i-1);
}
// 實現next陣列
int[] next = new int[n+1];
//next[1] = 0;
for(int i = 2,j =0; i<= n;i++){
// 如果j回退到0或者i位置元素與j+1位置的元素不相同,那麼執行回退操作,
//j退回next[i]處,即字首與字尾相同的區間最後元素位置
while(j !=0 && p[i] != p[j+1]) j = next[j];
// 如果i與j+1相同,那麼移動j向後匹配
if(p[i] == p[j+1]) j++;
// p[1,j] = p[i-j+1,i];字首與字尾相同;i表示終點
next[i] = j;
}
// kmp匹配過程,遍歷s模板每個元素
for(int i =1, j =0; i<=m; i++ ){
// 如果j回退到0或者i位置元素與j+1位置的元素不相同,那麼執行回退操作,
//j退回next[i]處,即字首與字尾相同的區間最後元素位置
while(j != 0 && s[i] != p[j+1]) j = next[j];
if(s[i] == p[j+1]) j++;
// 如果匹配成功
if(j ==n){
// 輸出匹配元素在s模板中的起始位置
bw.write(i-n+" ");
// 繼續匹配;
j = next[j];
}
}
bw.flush();
br.close();
bw.close();
}
}
結束:
感謝大家看到最後,如果有錯誤,歡迎指正!
參考文獻:https://www.acwing.com/solution/content/14666/