排序
sort函数(sort和cmp配合使用)
sort函数(c++)可以对数据进行排序和自定义排序(cmp配合使用)
1 2 3 4 5 从小到大排序可以写成sort (a,a+n,less <要进行排序的数据类型>()) 从大到小排序可以写成sort (a,a+n,greater <要进行排序的数据类型>())
sort可以和cmp函数配合使用进行自定义的结构体排序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 typedef struct { char name[100 ]; double a,b,c; double sum; }student;bool cmp (const student &a,const student &b) { if (a.sum!=b.sum) return a.sum>b.sum; else if (a.a!=b.a) return a.a>b.a; else if (a.b!=b.b) return a.b>b.b; else return a.c>b.c; }int main () { student a[10 ]; sort (a,a+n,cmp); }
堆排序
分析1:为什么从无序数组创建堆要从n/2开始?
因为堆为完全二叉树,完全二叉树中父亲和儿子的地址拥有如下关系:父亲X2=左儿子,父亲X2+1=右儿子,左儿子/2=右儿子/2=父亲 。
分析2:升序和降序如何选择大根堆或者小根堆?
在升序中通常采用小根堆,在降序中采用大根堆。因为用数组模拟堆时,删除首元素比较麻烦,但是删除最后一个元素非常方便,直接让首元素等于最后一个元素,然后在不断down首元素得到正确的堆
分析3:为什么在down()函数中要不断递归呢?
因为创建堆的时候,当父亲比两个儿子都大的时候,我们找到三个中最小的一个,并交换位置,但是我们并不知道父亲在整个堆的位置,所以我们要不断down来正确找到父亲的位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> #include <string> #include <queue> using namespace std;const int N=1e5 +10 ;int h[N],tt;void down (int x) { int t=x; if (2 *x<=tt&&h[2 *x]<h[t]) t=2 *x; if (2 *x+1 <=tt&&h[2 *x+1 ]<h[t]) t=2 *x+1 ; if (t!=x) { swap (h[x],h[t]); down (t); } }int main () { int n,m; cin>>n>>m; ios::sync_with_stdio (false ); tt=n; for (int i=1 ;i<=n;i++) cin>>h[i]; for (int i=n/2 ;i>0 ;i--) down (i); for (int i=1 ;i<=m;i++) { cout<<h[1 ]<<" " ; h[1 ]=h[tt--]; down (1 ); } return 0 ; }
桶排序
桶排序可以解决数据过多导致超时的情况,也可以计算计算得票数量类的题型
整个过程:
image-20220123164040471
例题:
1 2 3 4 5 6 为了找出林大2020新生中最擅长编写代码的同学,学校发起了一场投票。通过同学报名、前期遴选等环节,共提名了100名同学作为选举人进行评选,假设他们的编号从1到100。现在学院已经采集到了n名同学的投票结果,请你找出得票最多的程序员获得的票数(注:就是让你找相同数字的个数的最大值)。 输入样例1: 6 1 2 4 7 7 7 4 5 5 5 5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <bits/stdc++.h> #include <algorithm> using namespace std;int a[200 ];int main () { int n,t; while (cin>>n) { memset (a,0 ,sizeof (a)); for (int i=1 ;i<=n;i++){ cin>>t; a[t]++; } int ans=0 ; for (int i=1 ;i<=100 ;i++) ans=max (ans,a[i]); cout<<ans<<endl; } return 0 ; }
快速排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 785 .快速排序 给定你一个长度为 n 的整数数列。 请你使用快速排序对这个数列按照从小到大进行排序。 并将排好序的数列按顺序输出。 输入格式 输入共两行,第一行包含整数 n 。 第二行包含 n 个整数(所有整数均在 1 ∼109 范围内),表示整个数列。 输出格式 输出共一行,包含 n 个整数,表示排好序的数列。 数据范围1 ≤n ≤100000 输入样例:5 3 1 2 4 5 输出样例:1 2 3 4 5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <bits/stdc++.h> using namespace std;const int N=1e5 +10 ;int a[N];void quick_sort (int a[],int l,int r) { if (l>=r) return ; int i=l-1 ,j=r+1 ,x=a[l+r>>1 ]; while (i<j) { do i++;while (a[i]<x); do j--;while (a[j]>x); if (i<j) swap (a[i],a[j]); } quick_sort (a,l,j); quick_sort (a,j+1 ,r); }int main () { int n; cin>>n; for (int i=0 ;i<n;i++) scanf ("%d" ,&a[i]); quick_sort (a,0 ,n-1 ); for (int i=0 ;i<n;i++) i==n-1 ?cout<<a[i]<<endl:cout<<a[i]<<" " ; return 0 ; }
归并排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 787 .归并排序 给定你一个长度为 n 的整数数列。 请你使用归并排序对这个数列按照从小到大进行排序。 并将排好序的数列按顺序输出。 输入格式 输入共两行,第一行包含整数 n 。 第二行包含 n 个整数(所有整数均在 1 ∼109 范围内),表示整个数列。 输出格式 输出共一行,包含 n 个整数,表示排好序的数列。 数据范围1 ≤n ≤100000 输入样例:5 3 1 2 4 5 输出样例:1 2 3 4 5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> using namespace std;const int N=1e6 +10 ;int q[N],tmp[N];void merge_sort (int l,int r) { if (l>=r) return ; int mid=l+r>>1 ; merge_sort (l,mid),merge_sort (mid+1 ,r); int k=0 ,i=l,j=mid+1 ; while (i<=mid&&j<=r) { if (q[i]<q[j]) tmp[k++]=q[i++]; else tmp[k++]=q[j++]; } while (i<=mid) tmp[k++]=q[i++]; while (j<=r) tmp[k++]=q[j++]; for (k=0 ,i=l;i<=r;i++,k++) q[i]=tmp[k]; }int main () { int n; cin>>n; for (int i=0 ;i<n;i++) cin>>q[i]; merge_sort (0 ,n-1 ); for (int i=0 ;i<n;i++) i==n-1 ?cout<<q[i]<<endl:cout<<q[i]<<" " ; return 0 ; }
选择排序O(N^2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 787 .归并排序 给定你一个长度为 n 的整数数列。 请你使用归并排序对这个数列按照从小到大进行排序。 并将排好序的数列按顺序输出。 输入格式 输入共两行,第一行包含整数 n 。 第二行包含 n 个整数(所有整数均在 1 ∼109 范围内),表示整个数列。 输出格式 输出共一行,包含 n 个整数,表示排好序的数列。 数据范围1 ≤n ≤100000 输入样例:5 3 1 2 4 5 输出样例:1 2 3 4 5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> using namespace std;const int N=1e6 ;int a[N];int main () { int n; cin>>n; for (int i=0 ;i<n;i++) cin>>a[i]; for (int i=0 ;i<n-1 ;i++) { for (int j=i+1 ;j<n;j++) { if (a[i]>a[j]) swap (a[i],a[j]); } } for (int i=0 ;i<n;i++) cout<<a[i]<<" " ; return 0 ; }
冒泡排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <string> #include <algorithm> using namespace std;#define MAXSIZE 1000; int a[1000 ];void bubble_sort (int n) { for (int i=0 ;i<=n;i++) { bool flag=true ; for (int j=0 ;j<n-i;j++){ if (a[j]>a[j+1 ]){ swap (a[j],a[j+1 ]); flag=false ; } } if (flag) break ; } }int main () { int n=0 ; int data; while (scanf ("%d" ,&data)!=EOF) { if (!data){ n--; break ; } else a[n++]=data; } bubble_sort (n); for (int i=0 ;i<=n;i++) cout<<a[i]<<" " ; cout<<endl; return 0 ; }
直接插入算法
算法核心
从第二个元素开始,一旦有元素a[j-1]>a[j],那么就需要调整顺序。
先将a[j]存储到a[0],然后将a[j]=a[j-1],接着从j-2后往前比对,如果a[j]>a[0],那么就将a[j+1]=a[j],如果a[j]<=a[0],那么a[j+1]=a[0],结束本轮循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <algorithm> #include <string> using namespace std;#define MAXSIZE 1000; int a[1000 ];void DirectInsertSort (int n) { int j; for (int i=2 ;i<=n;i++) { if (a[i]<a[i-1 ]) { a[0 ]=a[i]; a[i]=a[i-1 ]; for (j=i-2 ;a[0 ]<a[j];j--) a[j+1 ]=a[j]; a[j+1 ]=a[0 ]; } } }int main () { int n=1 ; int data; while (scanf ("%d" ,&data)!=EOF) { if (!data){ n--; break ; } else a[n++]=data; } DirectInsertSort (n); for (int i=1 ;i<=n;i++) cout<<a[i]<<" " ; cout<<endl; return 0 ; }
二进制枚举
详细介绍二进制枚举
1.含有N个元素的集合的一切子集个数有2^n种,二进制采用0和1来表示数字。于是我们可以利用二进制特性,将含n个元素都用0和1来表示选和不选,于是就可以得到每一种子集的情况。
2.那我们如何找到每个位置是否选和不选?可以采用位运算和与运算结合的方式。<<运算相当于在01串的尾巴处加上0(左移一位相当于乘以2),而与运算的规则是全为1才可以得到1。那么我们就可以通过对1不断进行左移运算和与运算得到每个位置的选择情况
1 2 3 4 5 6 7 1 <<1 =2 (10 ); 1000 &0010 =0000 1 <<2 =4 (100 ); 1000 &0100 =0000 1 <<3 =8 (1000 ); 1000 &1000 =1000 1 <<4 =16 (10000 ); 01000 &10000 =00000
例题:
和为K--二进制枚举
1 2 3 4 5 6 7 8 9 10 11 12 Description 给出长度为n的数组,求能否从中选出若干个,使他们的和为K.如果可以,输出:Yes,否则输出No Input 第一行:输入N,K,为数组的长度和需要判断的和(2<=N<=20,1<=K<=10^9) 第二行:N个值,表示数组中元素的值(1<=a[i]<=10^6) Output 输出Yes或No Sample Input 5 13 2 4 6 8 10 Sample Output No
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <bits/stdc++.h> using namespace std;int main () { int n,k; while (cin>>n>>k) { int a[n]; for (int i=0 ;i<n;i++) cin>>a[i]; int flag=1 ; for (int i=0 ;i<(1 <<n);i++) { int sum=0 ; for (int j=0 ;j<n;j++) { if (i&(1 <<j)) sum+=a[j]; } if (sum==k){ cout<<"Yes" <<endl; flag=0 ; break ; } } if (flag) cout<<"No" <<endl; } return 0 ; }
陈老师加油-二进制枚举
1 2 3 4 5 6 7 8 9 10 11 12 Description 陈老师经常开车在哈尔滨的大街上行走,假设刚开始油箱里有T升汽油,每看见加油站陈老师就要把汽油的总量翻倍(就是乘2);每看见十字路口气油就要减少1升;最后的时候陈老师的车开到一个十字路口,然后车就没油了------就熄火了,陈老师好痛苦啊~~~! 然后他就开始回忆,一路上一共遇到5个加油站,10个十字路口,问造成这种惨烈的境遇有多少种可能? Input 输入一个T ,(1<=T<=100); Output 输出可能的方案数。 Sample Input 1 Sample Output 10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <bits/stdc++.h> #include <algorithm> using namespace std;int main () { int n; while (cin>>n) { int ans=0 ; for (int i=0 ;i<(1 <<15 );i++) { int a=0 ,b=0 ,tmp=n; for (int j=0 ;j<15 ;j++) { if (i&(1 <<j)){ tmp*=2 ; a++; } else { tmp-=1 ; b++; } if (tmp<=0 ) break ; } if (tmp==0 &&a==5 &&b==10 ) ans++; } cout<<ans<<endl; } return 0 ; }
权利指数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Description 在选举问题中,总共有n个小团体,每个小团体拥有一定数量的选票数。如果其中m个小团体的票数和超过总票数的一半,则此组合为“获胜联盟”。n个团体可形成若干个获胜联盟。一个小团体要成为一个“关键加入者”的条件是:在其所在的获胜联盟中,如果缺少了这个小团体的加入,则此联盟不能成为获胜联盟。一个小团体的权利指数是指:一个小团体在所有获胜联盟中成为“关键加入者”的次数。请你计算每个小团体的权利指数。 Input 输入数据的第一行为一个正整数T,表示有T组测试数据。每一组测试数据的第一行为一个正整数n(0<n<=20)。第二行有n个正整数,分别表示1到n号小团体的票数。 Output 对每组测试数据,在同一个行按顺序输出1到n号小团体的权利指数。 Sample Input 2 1 10 7 5 7 4 8 6 7 5 Sample Output 1 16 22 16 24 20 22 16
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <bits/stdc++.h> #include <algorithm> using namespace std;int main () { int k; cin>>k; while (k--) { int x,a[20 ],sum=0 ,flag[20 ],b[20 ],ans; cin>>x; memset (b,0 ,sizeof (b)); for (int i=0 ;i<x;i++){ cin>>a[i]; sum+=a[i]; } for (int i=0 ;i<(1 <<x);i++) { memset (flag,0 ,sizeof (flag)); ans=0 ; for (int j=0 ;j<x;j++) { if (i&(1 <<j)) { ans+=a[j];flag[j]=1 ;} } if (ans<=sum/2 ){ for (int z=0 ;z<x;z++) { if (flag[z]==0 &&ans+a[z]>sum/2 ) b[z]++; } } } for (int i=0 ;i<x-1 ;i++) cout<<b[i]<<" " ; cout<<b[x-1 ]<<endl; } return 0 ; }
二分
整数二分
二分模板1:(求下标最小的x)
1 2 3 4 5 6 7 8 while (l<r) { int mid=l+r>>1 ; if (a[mid]>=x) r=mid; else l=mid+1 ; }
二分模板2:(求下标最大的x)
1 2 3 4 5 6 7 8 while (l<r) { int mid=l+r+1 >>1 ; if (a[mid]<=x) l=mid; else r=mid-1 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 789 数的范围 给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。 对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。 如果数组中不存在该元素,则返回 -1 -1 。 输入格式 第一行包含整数 n 和 q,表示数组长度和询问个数。 第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。 接下来 q 行,每行包含一个整数 k,表示一个询问元素。 输出格式 共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。 如果数组中不存在该元素,则返回 -1 -1 。 数据范围 1≤n≤100000 1≤q≤10000 1≤k≤10000 输入样例: 6 3 1 2 2 3 3 4 3 4 5 输出样例: 3 4 5 5-1 -1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> using namespace std;const int N=1e5 +10 ;int q[N],n,m;int main () { cin>>n>>m; for (int i=0 ;i<n;i++) scanf ("%d" ,&q[i]); while (m--) { int k; cin>>k; int l=0 ,r=n-1 ; while (l<r){ int mid=l+r>>1 ; if (q[mid]>=k) r=mid; else l=mid+1 ; } if (q[l]!=k) cout<<"-1 -1" <<endl; else { cout<<l<<" " ; int l=0 ,r=n-1 ; while (l<r){ int mid=l+r+1 >>1 ; if (q[mid]<=k) l=mid; else r=mid-1 ; } cout<<l<<endl; } } return 0 ; }
浮点数二分
模板
1 2 3 4 5 6 7 while (r-l>1e-6 ) { mid=(l+r)/2 ; if (check (mid)) r=mid; else l=mid; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 给定一个浮点数 n ,求它的三次方根。 输入格式 共一行,包含一个浮点数 n 。 输出格式 共一行,包含一个浮点数,表示问题的解。 注意,结果保留 6 位小数。 数据范围 −10000 ≤n ≤10000 输入样例:1000.00 输出样例:10.000000
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> using namespace std;int main () { double x; cin>>x; double l=-10000 ,r=10000 ; while (r-l>1e-8 ) { double mid=(l+r)/2 ; if ((mid*mid*mid)>=x) r=mid; else l=mid; } printf ("%.6lf" ,l); return 0 ; }
高精度
高精度加法
高精度加法关键在于将每一位数字拆解,然后逆向存入数组里(比如输入进来1234,在传入数组中的时候值为4321,然后再利用小学数学加法的知识,每一位数字的值为A[I]+B[I]+t(t为进位的值),循环进行直到遍历到数组a和b的结束位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 给定两个正整数(不含前导 0),计算它们的和。 输入格式 共两行,每行包含一个整数。 输出格式 共一行,包含所求的和。 数据范围 1≤整数长度≤100000 输入样例: 12 23 输出样例: 35
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> #include <vector> #include <string> using namespace std;vector<int > add (const vector<int >&A,const vector<int >&B) { vector<int > C; int t=0 ; for (int i=0 ;i<A.size ()||i<B.size ();i++) { if (i<A.size ()) t+=A[i]; if (i<B.size ()) t+=B[i]; C.push_back (t%10 ); t/=10 ; } if (t) C.push_back (1 ); return C; }int main () { string a,b; vector<int >A,B; cin>>a>>b; for (int i=a.size ()-1 ;i>=0 ;i--) A.push_back (a[i]-'0' ); for (int i=b.size ()-1 ;i>=0 ;i--) B.push_back (b[i]-'0' ); auto C=add (A,B); for (int i=C.size ()-1 ;i>=0 ;i--) cout<<C[i]; return 0 ; }
leetcode66题
1 2 3 4 5 6 66. 加一 给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。 你可以假设除了整数 0 之外,这个整数不会以零开头。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : vector<int > plusOne (vector<int >& digits) { vector<int > ans; int t=1 ; for (int i=digits.size ()-1 ;i>=0 ;i--) { t+=digits[i]; ans.push_back (t%10 ); t/=10 ; } if (t) ans.push_back (1 ); reverse (ans.begin (),ans.end ()); return ans; } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : vector<int > plusOne (vector<int >& digits) { int i=digits.size ()-1 ; while (i>=0 &&digits[i]==9 ) { digits[i]=0 ; i--; } if (i==-1 ) digits.insert (digits.begin (),1 ); else digits[i]+; return digits; };
leetcode第二题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 2 . 两数相加 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这两个数都不会以 0 开头。 示例 1 : 输入:l1 = [2,4,3] , l2 = [5,6,4] 输出:[7,0,8] 解释:342 + 465 = 807 . 示例 2 : 输入:l1 = [0] , l2 = [0] 输出:[0] 示例 3 : 输入:l1 = [9,9,9,9,9,9,9] , l2 = [9,9,9,9] 输出:[8,9,9,9,0,0,0,1] 提示: 每个链表中的节点数在范围 [1, 100] 内0 <= Node.val <= 9 题目数据保证列表表示的数字不含前导零
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution {public : ListNode* addTwoNumbers (ListNode* l1, ListNode* l2) { ListNode* head=new ListNode (-1 ),*pMove=head; int t=0 ; while (l1||l2||t) { if (l1) t+=l1->val; if (l2) t+=l2->val; pMove->next=new ListNode (t%10 ); t/=10 ; pMove=pMove->next; if (l1) l1=l1->next; if (l2) l2=l2->next; } return head->next; } };
高精度减法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 给定两个正整数(不含前导 0),计算它们的差,计算结果可能为负数。 输入格式 共两行,每行包含一个整数。 输出格式 共一行,包含所求的差。 数据范围 1≤整数长度≤105 输入样例: 32 11 输出样例: 21
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <iostream> #include <vector> #include <string> using namespace std;bool cmp (vector<int >&A,vector<int >&B) { if (A.size ()!=B.size ()) return A.size ()>B.size (); else for (int i=A.size ();i>=0 ;i--) if (A[i]!=B[i]) return A[i]>B[i]; return true ; }vector<int > sub (const vector<int >&A,const vector<int >&B) { vector<int >C; for (int i=0 ,t=0 ;i<A.size ();i++) { t=A[i]-t; if (i<B.size ()) t-=B[i]; C.push_back ((t+10 )%10 ); if (t<0 ) t=1 ; else t=0 ; } while (C.size ()>1 &&C.back ()==0 ) C.pop_back (); return C; }int main () { string a,b; vector<int > A,B; cin>>a>>b; for (int i=a.size ()-1 ;i>=0 ;i--) A.push_back (a[i]-'0' ); for (int i=b.size ()-1 ;i>=0 ;i--) B.push_back (b[i]-'0' ); if (cmp (A,B)) { auto C=sub (A,B); for (int i=C.size ()-1 ;i>=0 ;i--) cout<<C[i]; } else { auto C=sub (B,A); cout<<"-" ; for (int i=C.size ()-1 ;i>=0 ;i--) cout<<C[i]; } return 0 ; }
## 高精度乘法
高精度X低精度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <iostream> #include <vector> using namespace std; vector <int > mul (vector <int > & A, int b) { vector <int > C; int t = 0 ; for (int i = 0 ; i < A.size (); i ++) { t += A[i] * b; C.push_back (t % 10 ); t /= 10 ; } while (t) { C.push_back (t % 10 ); t /= 10 ; } while (C.size () > 1 && C.back () == 0 ) C.pop_back (); return C; }int main () { string a; int b; cin >> a >> b; vector <int > A; for (int i = a.size () - 1 ; i >= 0 ; i --) A.push_back (a[i] - '0' ); auto C = mul (A, b); for (int i = C.size () - 1 ; i >= 0 ; i --) { cout << C[i]; } return 0 ; }
高精度X高精度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> #include <vector> using namespace std;vector<int > mul (vector<int > &A, vector<int > &B) { vector<int > C (A.size() + B.size() + 7 , 0 ) ; for (int i = 0 ; i < A.size (); i++) for (int j = 0 ; j < B.size (); j++) C[i + j] += A[i] * B[j]; int t = 0 ; for (int i = 0 ; i < C.size (); i++) { t += C[i]; C[i] = t % 10 ; t /= 10 ; } while (C.size () > 1 && C.back () == 0 ) C.pop_back (); return C; }int main () { string a, b; cin >> a >> b; vector<int > A, B; for (int i = a.size () - 1 ; i >= 0 ; i--) A.push_back (a[i] - '0' ); for (int i = b.size () - 1 ; i >= 0 ; i--) B.push_back (b[i] - '0' ); auto C = mul (A, B); for (int i = C.size () - 1 ; i >= 0 ; i--) cout << C[i]; return 0 ; }
acwing例题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 793 高精度乘法 给定两个非负整数(不含前导 0 ) A 和 B ,请你计算 A ×B 的值。 输入格式 共两行,第一行包含整数 A ,第二行包含整数 B 。 输出格式 共一行,包含 A ×B 的值。 数据范围1 ≤A 的长度≤100000 ,0 ≤B ≤10000 输入样例:2 3 输出样例:6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 高精度乘以低精度#include <iostream> #include <vector> #include <string> using namespace std;vector<int > mul (vector<int >&A,int b) { vector<int > C; int t=0 ; for (int i=0 ;i<A.size ();i++) { t+=A[i]*b; C.push_back (t%10 ); t/=10 ; } while (t) { C.push_back (t%10 ); t/=10 ; } while (C.size ()>1 &&C.back ()==0 ) C.pop_back (); return C; }int main () { string a; int b; vector<int > A; cin>>a>>b; for (int i=a.size ()-1 ;i>=0 ;i--) A.push_back (a[i]-'0' ); auto C=mul (A,b); for (int i=C.size ()-1 ;i>=0 ;i--) cout<<C[i]; return 0 ; }
高精度除法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 给定两个非负整数(不含前导 0 ) A ,B ,请你计算 A /B 的商和余数。 输入格式 共两行,第一行包含整数 A ,第二行包含整数 B 。 输出格式 共两行,第一行输出所求的商,第二行输出所求余数。 数据范围1 ≤A 的长度≤100000 ,1 ≤B ≤10000 ,B 一定不为 0 输入样例:7 2 输出样例:3 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> #include <vector> #include <string> #include <algorithm> using namespace std;vector<int > divide (vector<int >&A,int b,int &r) { vector<int > C; for (int i=A.size ()-1 ;i>=0 ;i--) { r=r*10 +A[i]; C.push_back (r/b); r%=b; } reverse (C.begin (),C.end ()); while (C.size ()>1 &&C.back ()==0 ) C.pop_back (); return C; }int main () { string a; int b,r=0 ; cin>>a>>b; vector<int > A; for (int i=a.size ()-1 ;i>=0 ;i--) A.push_back (a[i]-'0' ); auto C=divide (A,b,r); for (int i=C.size ()-1 ;i>=0 ;i--) cout<<C[i]; cout<<endl<<r<<endl; return 0 ; }
leetcode67题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 67 . 二进制求和 给你两个二进制字符串,返回它们的和(用二进制表示)。 输入为 非空 字符串且只包含数字 1 和 0 。 示例 1 : 输入: a = "11" , b = "1" 输出: "100" 示例 2 : 输入: a = "1010" , b = "1011" 输出: "10101" 提示: 每个字符串仅由字符 '0' 或 '1' 组成。1 <= a .length , b .length <= 10 ^4 字符串如果不是 "0" ,就都不含前导零。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : string addBinary (string a, string b) { vector<int >A,B; for (int i=a.size ()-1 ;i>=0 ;i--) A.push_back (a[i]-'0' ); for (int i=b.size ()-1 ;i>=0 ;i--) B.push_back (b[i]-'0' ); string C; int t=0 ; for (int i=0 ;i<A.size ()||i<B.size ();i++) { if (i<A.size ()) t+=A[i]; if (i<B.size ()) t+=B[i]; C+=(t%2 +'0' ); t/=2 ; } if (t) C+=(1 +'0' ); reverse (C.begin (),C.end ()); return C; } };
前缀和
一维前缀和
将A[1]+......A[n]
(n为变量)的值一一单独存入另一个数组中,然后查找区间的前缀和时,只要S[R]-S[L-1]即可得到区间前缀和(为啥是l-1,因为S[R]=A[1]+....A[R],S[L-1]=A[1]+....+A[L-1],两个相减就可以得到A[L]+...+A[R]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 795 前缀和 输入一个长度为 n 的整数序列。 接下来再输入 m 个询问,每个询问输入一对 l,r。 对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。 输入格式 第一行包含两个整数 n 和 m。 第二行包含 n 个整数,表示整数数列。 接下来 m 行,每行包含两个整数 l 和 r,表示一个询问的区间范围。 输出格式 共 m 行,每行输出一个询问的结果。 数据范围1 ≤l≤r≤n ,1 ≤n ,m≤100000 , −1000 ≤数列中元素的值≤1000 输入样例:5 3 2 1 3 6 4 1 2 1 3 2 4 输出样例:3 6 10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> using namespace std;const int N = 1e5 +10 ;int q[N],s[N];int main () { ios::sync_with_stdio (false ); int n,m; cin>>n>>m; for (int i=1 ;i<=n;i++) cin>>q[i]; for (int i=1 ;i<=n;i++) s[i]=s[i-1 ]+q[i]; while (m--) { int l,r; cin>>l>>r; cout<<s[r]-s[l-1 ]<<endl; } return 0 ; }
leetcode303
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 303. 区域和检索 - 数组不可变 给定一个整数数组 nums,处理以下类型的多个查询: 计算索引 left 和 right (包含 left 和 right )之间的 nums 元素的 和 ,其中 left <= right 实现 NumArray 类: NumArray(int [] nums) 使用数组 nums 初始化对象int sumRange(int i, int j) 返回数组 nums 中索引 left 和 right 之间的元素的 总和 ,包含 left 和 right 两点(也就是 nums[left ] + nums[left + 1 ] + ... + nums[right ] ) 示例 1 : 输入: ["NumArray", "sumRange", "sumRange", "sumRange"] [[[-2 , 0 , 3 , -5 , 2 , -1 ]], [0 , 2 ], [2 , 5 ], [0 , 5 ]] 输出: [null , 1 , -1 , -3 ] 解释: NumArray numArray = new NumArray([-2 , 0 , 3 , -5 , 2 , -1 ]); numArray.sumRange(0 , 2 ); / / return 1 ((-2 ) + 0 + 3 ) numArray.sumRange(2 , 5 ); / / return -1 (3 + (-5 ) + 2 + (-1 )) numArray.sumRange(0 , 5 ); / / return -3 ((-2 ) + 0 + 3 + (-5 ) + 2 + (-1 )) 提示:1 <= nums.length <= 104 -105 <= nums[i] <= 105 0 <= i <= j < nums.length 最多调用 104 次 sumRange 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class NumArray {public : vector<int >sum; NumArray (vector<int >& nums) { sum.push_back (0 ); for (int i=1 ;i<=nums.size ();i++) sum.push_back (sum[i-1 ]+nums[i-1 ]); } int sumRange (int left, int right) { return sum[right+1 ]-sum[left]; } };
二维前缀和
二维前缀和推导 如图:
紫色面积是指(1,1)左上角到(i,j-1)右下角的矩形面积,
绿色面积是指(1,1)左上角到(i-1, j
)右下角的矩形面积。每一个颜色的矩形面积都代表了它所包围元素的和。
从图中我们很容易看出,整个外围蓝色矩形面积s[i] [j] = 绿色面积s[i-1]
[j] + 紫色面积s[i] [j-1] - 重复加的红色的面积s[i-1]
[j-1]+小方块的面积a[i] [j];
因此得出二维前缀和预处理公式
s[i] [j] = s[i-1] [j] + s[i] [j-1 ] + a[i] [j] - s[i-1] [ j-1]
(是离散的数,不是面积哈)
接下来回归问题去求以(x1,y1)为左上角和以(x2,y2)为右下角的矩阵的元素的和。
如图:
紫色面积是指 ( 1,1 )左上角到(x1-1,y2)右下角的矩形面积
,黄色面积是指(1,1)左上角到(x2,y1-1)右下角的矩形面积;
不难推出:
绿色矩形的面积 = 整个外围面积s[x2, y2] - 黄色面积s[x2, y1 - 1] -
紫色面积s[x1 - 1, y2] + 重复减去的红色面积 s[x1 - 1, y1 - 1]
因此二维前缀和的结论为:
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为: s[x2, y2] - s[x1
- 1, y2] - s[x2, y1 - 1] + s[x1 - 1, y1 - 1]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 796 二维前缀和 输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。 对于每个询问输出子矩阵中所有数的和。 输入格式 第一行包含三个整数 n,m,q。 接下来 n 行,每行包含 m 个整数,表示整数矩阵。 接下来 q 行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。 输出格式 共 q 行,每行输出一个询问的结果。 数据范围 1≤n,m≤1000, 1≤q≤200000, 1≤x1≤x2≤n, 1≤y1≤y2≤m, −1000≤矩阵内元素的值≤1000 输入样例: 3 4 3 1 7 2 4 3 6 2 8 2 1 2 3 1 1 2 2 2 1 3 4 1 3 3 4 输出样例: 17 27 21
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> using namespace std;const int N=1e3 +10 ;int a[N][N],s[N][N];int main () { int n,m,q; cin>>n>>m>>q; for (int i=1 ;i<=n;i++) for (int j=1 ;j<=m;j++) scanf ("%d" ,&a[i][j]); for (int i=1 ;i<=n;i++) for (int j=1 ;j<=m;j++) s[i][j]=s[i-1 ][j]+s[i][j-1 ]-s[i-1 ][j-1 ]+a[i][j]; while (q--) { int x1,x2,y1,y2; cin>>x1>>y1>>x2>>y2; printf ("%d\n" ,s[x2][y2]-s[x1-1 ][y2]-s[x2][y1-1 ]+s[x1-1 ][y1-1 ]); } return 0 ; }
leetcode304
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 304. 二维区域和检索 - 矩阵不可变 给定一个二维矩阵 matrix,以下类型的多个请求: 计算其子矩形范围内元素的总和,该子矩阵的 左上角 为 (row1, col1) ,右下角 为 (row2, col2) 。 实现 NumMatrix 类:NumMatrix (int[][] matrix) 给定整数矩阵 matrix 进行初始化 int sumRegion(int row1, int col1, int row2, int col2) 返回 左上角 (row1, col1) 、右下角 (row2, col2) 所描述的子矩阵的元素 总和 。 示例 1 : 自己取leetcode上看吧 输入: ["NumMatrix" ,"sumRegion" ,"sumRegion" ,"sumRegion" ] [[[[3 ,0 ,1 ,4 ,2 ],[5 ,6 ,3 ,2 ,1 ],[1 ,2 ,0 ,1 ,5 ],[4 ,1 ,0 ,1 ,7 ],[1 ,0 ,3 ,0 ,5 ]]],[2 ,1 ,4 ,3 ],[1 ,1 ,2 ,2 ],[1 ,2 ,2 ,4 ]] 输出: [null, 8 , 11 , 12 ] 解释:NumMatrix numMatrix = new NumMatrix ([[3 ,0 ,1 ,4 ,2 ],[5 ,6 ,3 ,2 ,1 ],[1 ,2 ,0 ,1 ,5 ],[4 ,1 ,0 ,1 ,7 ],[1 ,0 ,3 ,0 ,5 ]]); numMatrix.sumRegion(2 , 1 , 4 , 3 ); // return 8 (红色矩形框的元素总和) numMatrix.sumRegion(1 , 1 , 2 , 2 ); // return 11 (绿色矩形框的元素总和) numMatrix.sumRegion(1 , 2 , 2 , 4 ); // return 12 (蓝色矩形框的元素总和) 提示: m == matrix.length n == matrix[i].length1 <= m, n <= 200 -105 <= matrix[i][j] <= 105 0 <= row1 <= row2 < m0 <= col1 <= col2 < n 最多调用 104 次 sumRegion 方法 通过次数86 ,115 提交次数148 ,070
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class NumMatrix {public : int sum[2000 ][2000 ]; NumMatrix (vector<vector<int >>& matrix) { int n=matrix.size (),m=matrix[0 ].size (); if (n>0 ){ for (int i=1 ;i<=n;i++) for (int j=1 ;j<=m;j++) sum[i][j]=sum[i-1 ][j]+sum[i][j-1 ]-sum[i-1 ][j-1 ]+matrix[i-1 ][j-1 ]; } } int sumRegion (int row1, int col1, int row2, int col2) { return sum[row2+1 ][col2+1 ]-sum[row1][col2+1 ]-sum[row2+1 ][col1]+sum[row1][col1]; } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class NumMatrix {public : vector<vector<int >> sum; NumMatrix (vector<vector<int >>& matrix) { int n=matrix.size (),m=matrix[0 ].size (); if (n>0 ){ sum.resize (n+1 ,vector <int >(m+1 ,0 )); for (int i=1 ;i<=n;i++) for (int j=1 ;j<=m;j++) sum[i][j]=sum[i-1 ][j]+sum[i][j-1 ]-sum[i-1 ][j-1 ]+matrix[i-1 ][j-1 ]; } } int sumRegion (int row1, int col1, int row2, int col2) { return sum[row2+1 ][col2+1 ]-sum[row1][col2+1 ]-sum[row2+1 ][col1]+sum[row1][col1]; } };
差分
一维差分
详细知识链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 输入一个长度为 n 的整数序列。 接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。 请你输出进行完所有操作后的序列。 输入格式 第一行包含两个整数 n 和 m。 第二行包含 n 个整数,表示整数序列。 接下来 m 行,每行包含三个整数 l,r,c,表示一个操作。 输出格式 共一行,包含 n 个整数,表示最终序列。 数据范围 1≤n,m≤100000, 1≤l≤r≤n, −1000≤c≤1000, −1000≤整数序列中元素的值≤1000 输入样例: 6 3 1 2 2 1 2 1 1 3 1 3 5 1 1 6 1 输出样例: 3 4 5 3 4 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> using namespace std;const int N=1e5 +10 ;int a[N],b[N];int main () { ios::sync_with_stdio (false ); int n,m; cin>>n>>m; for (int i=1 ;i<=n;i++) cin>>a[i]; for (int i=1 ;i<=n;i++) b[i]=a[i]-a[i-1 ]; while (m--) { int l,r,c; cin>>l>>r>>c; b[l]+=c; b[r+1 ]-=c; } for (int i=1 ;i<=n;i++) a[i]=a[i-1 ]+b[i]; for (int i=1 ;i<=n;i++) cout<<a[i]<<" " ; return 0 ; }
y总版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> using namespace std;const int N=1e5 +10 ;int a[N],b[N];void insert (int l,int r,int c) { b[l]+=c; b[r+1 ]-=c; }int main () { int n,m; cin>>n>>m; ios::sync_with_stdio (false ); for (int i=1 ;i<=n;i++) cin>>a[i]; for (int i=1 ;i<=n;i++) insert (i,i,a[i]); while (m--) { int l,r,c; cin>>l>>r>>c; insert (l,r,c); } for (int i=1 ;i<=n;i++) b[i]+=b[i-1 ]; for (int i=1 ;i<=n;i++) cout<<b[i]<<" " ; return 0 ; }
二维差分
二维差分知识点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 798. 差分矩阵 题目 提交记录 讨论 题解 视频讲解 输入一个 n 行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c,其中 (x1,y1) 和 (x2,y2) 表示一个子矩阵的左上角坐标和右下角坐标。 每个操作都要将选中的子矩阵中的每个元素的值加上 c。 请你将进行完所有操作后的矩阵输出。 输入格式 第一行包含整数 n,m,q。 接下来 n 行,每行包含 m 个整数,表示整数矩阵。 接下来 q 行,每行包含 5 个整数 x1,y1,x2,y2,c,表示一个操作。 输出格式 共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。 数据范围 1≤n,m≤1000, 1≤q≤100000, 1≤x1≤x2≤n, 1≤y1≤y2≤m, −1000≤c≤1000, −1000≤矩阵内元素的值≤1000 输入样例: 3 4 3 1 2 2 1 3 2 2 1 1 1 1 1 1 1 2 2 1 1 3 2 3 2 3 1 3 4 1 输出样例: 2 3 4 1 4 3 4 1 2 2 2 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> using namespace std;const int N=1e3 +10 ;int a[N][N],b[N][N],matrix[N][N];int main () { int n,m,q; cin>>n>>m>>q; ios::sync_with_stdio (false ); for (int i=1 ;i<=n;i++) for (int j=1 ;j<=m;j++) cin>>a[i][j]; while (q--) { int x1,y1,x2,y2,c; cin>>x1>>y1>>x2>>y2>>c; b[x1][y1]+=c; b[x2+1 ][y1]-=c; b[x1][y2+1 ]-=c; b[x2+1 ][y2+1 ]+=c; } for (int i=1 ;i<=n;i++) for (int j=1 ;j<=m;j++) matrix[i][j]=matrix[i-1 ][j]+matrix[i][j-1 ]+b[i][j]-matrix[i-1 ][j-1 ]; for (int i=1 ;i<=n;i++){ for (int j=1 ;j<=m;j++) { cout<<a[i][j]+matrix[i][j]<<" " ; } cout<<endl; } return 0 ; }
双指针算法
找双指针算法可以先写一个暴力的o(n^2),然后找i和j是否满足单调性,如果满足就可以使用双指针算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 799 . 最长连续不重复子序列 题目 提交记录 讨论 题解 视频讲解 给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。 输入格式 第一行包含整数 n 。 第二行包含 n 个整数(均在 0 ∼105 范围内),表示整数序列。 输出格式 共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。 数据范围1 ≤n ≤105 输入样例:5 1 2 2 3 5 输出样例:3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> using namespace std;const int N=1e5 +10 ;int a[N],s[N];int main () { ios::sync_with_stdio (false ); int n; cin>>n; int res=0 ; for (int i=0 ;i<n;i++) cin>>a[i]; for (int i=0 ,j=0 ;i<n;i++) { s[a[i]]++; while (s[a[i]]>1 ) { s[a[j]]--; j++; } res=max (res,i-j+1 ); } cout<<res<<endl; return 0 ; }
链表的定义
链表是物理存储单元上非连续 的、非顺序 的存储结构,它是由一个个结点,通过指针来联系起来的,其中每个结点包括数据和指针。
屏幕截图 2022-01-06 160632
链表在内存中采用每个结点都分配在非连续 的位置,结点与结点之间通过指针连在了一起,查找元素时需要遍历查找。
链表的表示
定义头节点:
由于链表的特点(查询或删除元素都要从头结点开始),所以我们只要在链表中定义头结点即可(我学习采用的是头节点无数据型的链表表示):
1577669607-4eeed00317fbd8a
1 2 3 4 5 6 7 8 9 10 11 typedef struct Node { int data; struct Node * next ; }Node; Node* createList () { Node* headNode; headNode=(Node*)malloc (sizeof (Node)); headNode->next=NULL ; return headNode; }
创建节点:
创建完头节点后我们需要创建新的节点用来存储数据:
1 2 3 4 5 6 7 8 Node* createNode (int data) { Node* newNode; newNode=(Node*)malloc (sizeof (Node)); newNode->data=data; newNode->next=NULL ; return newNode; }
连接节点:
链接节点可以采用两种方式:1. 头插法 2.尾插法
头插法:
1577669607-2500a8c3aad50ab
具体步骤如上图所示,将将前一个节点指向插入的节点,将插入的节点指向原下一个节点
1 2 3 4 5 6 7 void insertList (Node* headNode,int data) { Node* newNode=createNode(data); newNode->next=headNode->next; headNode->next=newNode; }
尾插法(一):
观察头插法会发现,插入节点时永远会在头节点插入,导致数据是逆序的。那么只要数据都是在链表的最后插入就不会有这个问题。尾插法在头插法的基础上,设置了一个单独的结构体指针保证结构体在插入时永远是在尾巴插入,这样数据存储就是顺序的。
这种尾插法有一个缺点,就是只能连续插入,不能分开插入,所以我们就有了第二种尾插法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Node* LinkList () { Node* pMove; Node* headNode; headNode=pMove=(Node*)malloc (sizeof (Node)); pMove->next=NULL ; while (1 ) { int data; printf ("请输入您的数据:" ); scanf ("%d" ,&data); if (data==5 ) break ; Node* newNode=createNode(data); pMove->next=newNode; pMove=newNode; } return headNode; }
尾插法(二)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int main () { Node* headNode=createList(); Node* tail=headNode; LinkList(&tail,1 ); LinkList(&tail,2 ); LinkList(&tail,3 ); printList(headNode); return 0 ; } Node* LinkList (Node **ptail,int data) { Node* newNode=createNode(data); (*ptail)->next=newNode; (*ptail)=newNode; return (*ptail); }
在任意位置插入节点
在任意处插入节点的重要点就在于要先找到要插入位置的地址,因为链表只能知道下一个节点,所以必须要遍历查找,并返回节点的地址,然后插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 Node* foundNode (Node* headNode,int searchdata) { Node* pFound; int isFound=0 ; for (pFound=headNode->next;pFound;pFound=pFound->next) { if (pFound->data==searchdata){ printf ("找到了\n" ); isFound=1 ; return pFound; } } if (!isFound) printf ("没找到\n" ); return NULL ; }void insertNode (Node** pFound,int data) { Node* newNode=createNode(data); newNode->next=(*pFound)->next; (*pFound)->next=newNode; }int main () { Node* pFound; Node* headNode=createList(); insertList(headNode,1 ); insertList(headNode,2 ); insertList(headNode,3 ); insertList(headNode,4 ); pFound=foundNode(headNode,2 ); insertNode(&pFound,6 ); printList(headNode); return 0 ; }
打印链表
链表只能循环打印
1 2 3 4 5 6 7 8 9 10 11 void printList (Node* headNode) { Node* pMove=headNode->next; printf ("打印链表\n" ); while (pMove) { printf ("%d->" ,pMove->data); pMove=pMove->next; } printf ("NULL\n" ); }
查找链表
查找链表我暂时只学到遍历查找,等后续学到了会更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void searchNode (Node* headNode,int searchdata) { int isFound=0 ; Node* pMove=headNode->next; while (pMove) { if (pMove->data==searchdata) { printf ("在此链表中找到了!\n" ); isFound=1 ; break ; } pMove=pMove->next; } if (!isFound) printf ("对不起没有找到!\n" ); }
删除节点
一样的只会循环查找后删除,后续更新新的方法
删除节点有几个要注意的地方:
一:首先要定义两个指针,因为删除节点需要该节点的前节点链接到该节点的下一个节点,而单链表只能知道下一个节点,所以必须要两个指针来删除
二:就是要注意,当删除的节点是头节点,那么就必须特殊对待,直接将headNode指向删掉节点的下一个节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void deleteNode (Node* headNode,int deletenumber) { Node *q,*p; int isFound=0 ; for (q=NULL ,p=headNode->next;p;q=p,p=p->next) { if (p->data==deletenumber) { isFound=1 ; if (!q) headNode=p->next; else q->next=p->next; free (p); break ; } } if (!isFound) printf ("未删除成功\n" ); else printf ("删除成功\n" ); }
静态链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> using namespace std;const int N=1e5 ;int val[N],ne[N],head[N],idx;void init () { head=-1 ; idx=0 ; }void add-to-head (int x) { e[idx]=x; ne[idx]=head; head=idx; idx++; }void add (int pos,int x) { e[idx]=x; ne[idx]=ne[pos]; ne[pos]=idx; idx++; }void remove (int pos) { ne[k]=ne[ne[k]]; }int main () { init (); return 0 ; }
826. 单链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 题目 提交记录 讨论 题解 视频讲解 实现一个单链表,链表初始为空,支持三种操作: 向链表头插入一个数; 删除第 k 个插入的数后面的数; 在第 k 个插入的数后插入一个数。 现在要对该链表进行 M 次操作,进行完所有操作后,从头到尾输出整个链表。 注意: 题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n 个插入的数。 输入格式 第一行包含整数 M ,表示操作次数。 接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:H x ,表示向链表头插入一个数 x 。D k ,表示删除第 k 个插入的数后面的数(当 k 为 0 时,表示删除头结点)。I k x ,表示在第 k 个插入的数后面插入一个数 x (此操作中 k 均大于 0 )。 输出格式 共一行,将整个链表从头到尾输出。 数据范围1 ≤M ≤100000 所有操作保证合法。 输入样例: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 输出样例:6 4 6 5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include <iostream> #include <algorithm> using namespace std;const int N=1e5 +10 ;int val[N],ne[N],head,idx;void init () { head=-1 ; idx=0 ; }void add_to_head (int x) { val[idx]=x; ne[idx]=head; head=idx; idx++; }void deletex (int k) { ne[k]=ne[ne[k]]; }void add (int k,int x) { val[idx]=x; ne[idx]=ne[k]; ne[k]=idx; idx++; }int main () { ios::sync_with_stdio (false ); init (); char oper; int n,x,k; cin>>n; while (n--) { cin>>oper; if (oper=='H' ){ cin>>x; add_to_head (x); } else if (oper=='I' ){ cin>>k>>x; add (k-1 ,x); } else { cin>>x; if (x==0 ) head=ne[head]; deletex (x-1 ); } } int index=head; while (index!=-1 ) { cout<<val[index]<<" " ; index=ne[index]; } return 0 ; }
## 双链表
双链表具体详解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 827. 双链表 题目 提交记录 讨论 题解 视频讲解 实现一个双链表,双链表初始为空,支持 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 个插入的数左侧插入一个数。 IR k x,表示在第 k 个插入的数右侧插入一个数。 输出格式 共一行,将整个链表从左到右输出。 数据范围 1≤M≤100000 所有操作保证合法。 输入样例: 10 R 7 D 1 L 3 IL 2 10 D 3 IL 2 7 L 8 R 9 IL 4 7 IR 2 2 输出样例: 8 7 7 3 2 9
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <iostream> #include <string> using namespace std;const int N=1e5 +10 ;int val[N],l[N],r[N],idx;void insert (int k,int x) { val[idx]=x; l[idx]=k; r[idx]=r[k]; l[r[k]]=idx; r[k]=idx; idx++; }void init () { r[0 ]=1 ; l[1 ]=0 ; idx=2 ; }void remove (int k) { r[l[k]]=r[k]; l[r[k]]=l[k]; }int main () { init (); int n; cin>>n; while (n--) { string oper; int k,x; cin>>oper; if (oper=="L" ) { cin>>x; insert (0 ,x); } else if (oper=="R" ) { cin>>x; insert (l[1 ],x); } else if (oper=="IL" ) { cin>>k>>x; insert (l[k+1 ],x); } else if (oper=="IR" ) { cin>>k>>x; insert (k+1 ,x); } else { cin>>k; remove (k+1 ); } } int cnt=r[0 ]; while (cnt!=1 ) { cout<<val[cnt]<<" " ; cnt=r[cnt]; } return 0 ; }
栈和单调栈
表达式求值(栈的应用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 828 . 模拟栈 题目 提交记录 讨论 题解 视频讲解 实现一个栈,栈初始为空,支持四种操作: 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 所有操作保证合法。 输入样例:10 push 5 query push 6 pop querypop empty push 4 queryempty 输出样例:5 5 YES4 NO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <iostream> #include <stack> #include <cstring> #include <algorithm> #include <unordered_map> #include <cctype> using namespace std; stack<int > num; stack<int > op;void eval () { int ans=0 ; int b=num.top ();num.pop (); int a=num.top ();num.pop (); char operation=op.top ();op.pop (); if (operation=='+' ) ans=a+b; if (operation=='-' ) ans=a-b; if (operation=='*' ) ans=a*b; if (operation=='/' ) ans=a/b; num.push (ans); }int main () { unordered_map<char ,int > h={{'+' ,1 },{'-' ,1 },{'*' ,2 },{'/' ,2 }}; string s; cin>>s; for (int i=0 ;i<s.size ();i++) { if (isdigit (s[i])){ int j=i,a=0 ; while (j<s.size ()&&isdigit (s[j])) { a=a*10 +s[j]-'0' ; j++; } num.push (a); i=j-1 ; } else if (s[i]=='(' ) op.push (s[i]); else if (s[i]==')' ) { while (op.top ()!='(' ) eval (); op.pop (); } else { while (op.size ()&&h[op.top ()]>=h[s[i]]) eval (); op.push (s[i]); } } while (op.size ()) eval (); cout<<num.top ()<<endl; return 0 ; }
模拟队列和单调队列
模拟队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 实现一个队列,队列初始为空,支持四种操作: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 , 所有操作保证合法。 输入样例:10 push 6 empty querypop emptypush 3 push 4 pop querypush 6 输出样例: NO6 YES4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <iostream> #include <string> using namespace std;const int N=1e5 +10 ;int queue[N],head,tt=-1 ;void push (int x) { queue[++tt]=x; }void pop () { head++; }int empty () { if (head<=tt) return 0 ; else return 1 ; }int query () { return queue[head]; }int main () { string operation; int n; cin>>n; while (n--) { cin>>operation; if (operation=="push" ) { int x; cin>>x; push (x); } if (operation=="pop" ) { pop (); } if (operation=="empty" ) { if (!empty ()) cout<<"NO" <<endl; else cout<<"YES" <<endl; } if (operation=="query" ) { cout<<query ()<<endl; } } return 0 ; }
单调队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 154. 滑动窗口 题目 提交记录 讨论 题解 视频讲解 给定一个大小为 n≤106 的数组。 有一个大小为 k 的滑动窗口,它从数组的最左边移动到最右边。 你只能在窗口中看到 k 个数字。 每次滑动窗口向右移动一个位置。 以下是一个例子: 该数组为 [1 3 -1 -3 5 3 6 7],k 为 3。 窗口位置 最小值 最大值 [1 3 -1 ] -3 5 3 6 7 -1 3 1 [3 -1 -3 ] 5 3 6 7 -3 3 1 3 [-1 -3 5] 3 6 7 -3 5 1 3 -1 [-3 5 3] 6 7 -3 5 1 3 -1 -3 [5 3 6] 7 3 6 1 3 -1 -3 5 [3 6 7] 3 7 你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。 输入格式 输入包含两行。 第一行包含两个整数 n 和 k,分别代表数组长度和滑动窗口的长度。 第二行有 n 个整数,代表数组的具体数值。 同行数据之间用空格隔开。 输出格式 输出包含两个。 第一行输出,从左至右,每个位置滑动窗口中的最小值。 第二行输出,从左至右,每个位置滑动窗口中的最大值。 输入样例: 8 3 1 3 -1 -3 5 3 6 7 输出样例:-1 -3 -3 -3 3 3 3 3 5 5 6 7
思路:
最小值和最大值分开来做,两个for循环完全类似,都做以下四步:
解决队首已经出窗口的问题;
解决队尾与当前元素a[i]不满足单调性的问题;
将当前元素下标加入队尾;
如果满足条件则输出结果;
需要注意的细节:
上面四个步骤中一定要先3后4,因为有可能输出的正是新加入的那个元素
队列中存的是原数组的下标,取值时要再套一层,a[q[]];
算最大值前注意将hh和tt重置;
此题用cout会超时,只能用printf;
hh从0开始,数组下标也要从0开始。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include <string> using namespace std;const int N=1e6 +10 ;int hh,tt=-1 ,a[N],q[N];int main () { int n,k; cin>>n>>k; for (int i=0 ;i<n;i++) cin>>a[i]; for (int i=0 ;i<n;i++) { if (hh<=tt&&k<i-q[hh]+1 ) hh++; while (hh<=tt&&a[q[tt]]>=a[i]) tt--; q[++tt]=i; if (i>=k-1 ) cout<<a[q[hh]]<<" " ; } cout<<endl; hh=0 ,tt=-1 ; for (int i=0 ;i<n;i++){ if (hh<=tt&&k<i-q[hh]+1 ) hh++; while (hh<=tt&&a[q[tt]]<=a[i]) tt--; q[++tt]=i; if (i>=k-1 ) cout<<a[q[hh]]<<" " ; } return 0 ; }
堆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #include <iostream> #include <string> using namespace std;const int N=1e5 +10 ;int h[N],hp[N],ph[N],cur_size,m;void heap_swap (int a,int b) { swap (ph[hp[a]],ph[hp[b]]); swap (hp[a],hp[b]); swap (h[a],h[b]); }void up (int x) { while (x/2 &&h[x/2 ]>h[x]) { heap_swap (x,x/2 ); x>>=1 ; } }void down (int x) { int t=x; if (x*2 <=cur_size&&h[2 *x]<h[t]) t=2 *x; if (x*2 +1 <=cur_size&&h[2 *x+1 ]<h[t]) t=2 *x+1 ; if (x!=t) { heap_swap (t,x); down (t); } }int main () { int n; cin>>n; while (n--) { string op; cin>>op; if (op=="I" ) { int x; cin>>x; cur_size++; m++; ph[m]=cur_size,hp[cur_size]=m; h[cur_size]=x; up (cur_size); } else if (op=="PM" ) cout<<h[1 ]<<endl; else if (op=="DM" ) { heap_swap (1 ,cur_size); cur_size--; down (1 ); } else if (op=="D" ) { int x; cin>>x; int k=ph[x]; heap_swap (ph[x],cur_size); cur_size--; down (k); up (k); } else { int k,x; cin>>k>>x; h[ph[k]]=x; down (ph[k]); up (ph[k]); } } return 0 ; }
KMP
KMP全称为Knuth Morris
Pratt算法,三个单词分别是三个作者的名字。KMP是一种高效的字符串匹配算法,用来在主字符串中查找模式字符串的位置(比如在“hello,world”主串中查找“world”模式串的位置)。
KMP算法的高效体现在哪
高效性是通过和其他字符串搜索算法对比得到的,在这里拿BF(Brute
Force)算法做一下对比。BF算法是一种最朴素的暴力搜索算法。它的思想是在主串的[0,
n-m]区间内依次截取长度为m的子串,看子串是否和模式串一样(n是主串的长度,m是子串的长度)。
BF的时间复杂度是O(N*N),存在很大优化空间。当模式串和主串匹配时,遇到模式串中某个字符不能匹配的情况,对于模式串中已经匹配过的那些字符,如果我们能找到一些规律,将模式串多往后移动几位,而不是像BF算法一样,每次把模式串移动一位,就可以提高算法的效率。比如说在“ababaababacd”中查找“ababac”,可以避免一些字符之间的比较。
下面通过一个具体的例子来看看可以跳过的情况。比如主模式串是”ababaeaba”,模式串是”ababacd”,在BF算法中,遇到不匹配的情况是这样处理的:
1 2 3 main: "ababaeaba" // 例如这两个串,当sub 为"ababaea" 时和"ababacd" 进行对pattern: "ababacd" // 比,当main[i]为e时,发现和pattern[j]的值e不一致,BF // 的做法是去下一个sub ,即用"babaeab" 和pattern进行比较。
我没希望找到一些规律,遇到两个字符不匹配的情况时,希望可以多跳几个字符,减少比较次数。KMP算法的思想是:在模式串和主串匹配过程中,当遇到不匹配的字符时,对于主串和模式串中已对比过相同的前缀字符串,找到长度最长的相等前缀串,从而将模式串一次性滑动多位,并省略一些比较过程。在上个例子,KMP算法中,是这样处理的:
1 2 3 4 5 6 7 8 9 10 11 12 13 main: "ababaeaba" // 比如main中的"ababa" 子串,对标为[2 ~4 ]的"aba" 和pattern中下 pattern: "ababacd" // 标为[0 ~2 ]的"aba" 相同,此时可以滑动j-k位,即j=j-k。(其中j是 // pattern中"c" 的下标,k是"abc" 的长度)。 "ababaeaba" // 比较过程中,main[5 ]为"e" 和pattern[5 ]为"c" 不匹配,但是两个 "ababacd" // 串中都有相同的"aba" 前缀,所以可以滑动j-k位 | ∨ "ababaeaba" "ababacd" | // 滑动j-k位后发现main[5 ]和patterb[3 ]不相同,需要再次滑动 ∨ "ababaeaba" "ababacd" // 滑动过程和上次类似。
通过这个例子可以看出,每次滑动的位数是j-k,滑动位数和主串无关,仅通过模式串就可以求出。在KMP算法中通过next数组来存储当两个字符不相等时模式串应该移动的位数。
如何KMP算法的next数组 再次明确next数组的含义 :
next数组用来存模式串中每个前缀最长的能匹配前缀子串的结尾字符的下标。
next[i] = j
表示下标以i-j为起点,i为终点的后缀和下标以0为起点,j为终点的前缀相等,且此字符串的长度最长。用符号表示为p[0~j]
== p[i-j~i]。下面以”ababacd”模式串为例,给出这个串的next数组。
模式前缀
前缀结尾下标
最长能匹配前缀子串结尾字符的下标
next数组的取值
匹配情况
a
0
-1
next[0] = -1
无
ab
1
-1
next[1] = -1
无
aba
2
0
next[2]=0
pattern[2]==pattern[0]
abab
3
1
next[3]=1
pattern[2:4]==pattern[0:2]
ababa
4
2
next[4]=2
pattern[2:5]==pattern[0:3]
ababac
5
-1
next[5]=-1
无
KMP的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream> #include <string> #include <algorithm> using namespace std;const int N=100010 ,M=1000010 ;int n,m,ne[N];char p[N],s[M];int main () { cin>>n>>p+1 >>m>>s+1 ; for (int i=2 ,j=0 ;i<=n;i++) { while (j&&p[i]!=p[j+1 ]) j=ne[j]; if (p[i]==p[j+1 ]) j++; ne[i]=j; } for (int i=1 ,j=0 ;i<=m;i++) { while (j&&s[i]!=p[j+1 ]) j=ne[j]; if (s[i]==p[j+1 ]) j++; if (j==n) { cout<<i-n<<" " ; } } return 0 ; }
Trie
哈希表
拉链法
https://cdn.acwing.com/media/article/image/2021/01/17/2675_9b33804c58-4.jpg
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> #include <cstring> using namespace std;const int N=1e5 +3 ;int h[N],ne[N],e[N],idx,n;void insert (int x) { int k=(x%N+N)%N; e[idx]=x; ne[idx]=h[k]; h[k]=idx++; }bool query (int x) { bool flag=false ; int k=(x%N+N)%N; for (int i=h[k];i!=-1 ;i=ne[i]) { if (e[i]==x){ flag=true ; break ; } } return flag; }int main () { memset (h,-1 ,sizeof h); cin>>n; while (n--) { int x; string op; cin>>op>>x; if (op=="I" ) insert (x); else { if (query (x)) cout<<"Yes" <<endl; else cout<<"No" <<endl; } } return 0 ; }
开放寻址法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> #include <cstring> using namespace std;const int N=200003 ,null=0x3f3f3f3f ;int h[N],n;int find (int x) { int k=(x%N+N)%N; while (h[k]!=null&&h[k]!=x) { k++; if (k==N) k=0 ; } return k; }int main () { memset (h,0x3f ,sizeof h); cin>>n; while (n--) { string op; int x; cin>>op>>x; if (op=="I" ) { int k=find (x); h[k]=x; } else { if (h[find (x)]==null) cout<<"No" <<endl; else cout<<"Yes" <<endl; } } return 0 ; }
字符串哈希表
(字符串哈希) O(n)+O(m)
全称字符串前缀哈希法,把字符串变成一个p进制数字(哈希值),实现不同的字符串映射到不同的数字。
对形如 X1X2X3⋯Xn−1Xn的字符串,采用字符的ascii 码乘上 P
的次方来计算哈希值。
映射公式 (X1×Pn−1+X2×Pn−2+⋯+Xn−1×P1+Xn×P0)modQ 注意点:
任意字符不可以映射成0,否则会出现不同的字符串都映射成0的情况,比如A,AA,AAA皆为0
冲突问题:通过巧妙设置P (131 或 13331) , Q
(264)(264)的值,一般可以理解为不产生冲突。
问题是比较不同区间的子串是否相同,就转化为对应的哈希值是否相同。
求一个字符串的哈希值就相当于求前缀和,求一个字符串的子串哈希值就相当于求部分和。
前缀和公式 h[i+1]=h[i]×P+s[i]h[i+1]=h[i]×P+s[i] i∈[0,n−1]i∈[0,n−1]
h为前缀和数组,s为字符串数组 区间和公式
h[l,r]=h[r]−h[l−1]×Pr−l+1h[l,r]=h[r]−h[l−1]×Pr−l+1 区间和公式的理解:
ABCDE 与 ABC 的前三个字符值是一样,只差两位, 乘上 P2P2 把 ABC 变为
ABC00,再用 ABCDE - ABC00 得到 DE 的哈希值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> #include <cstring> using namespace std;typedef unsigned long long ull;const int N=1e5 +10 ,P=131 ;char str[N]; ull p[N],h[N];int get_val (int l, int r) { return h[r]-h[l-1 ]*p[r-l+1 ]; }int main () { int n,m; cin>>n>>m; scanf ("%s" ,str+1 ); p[0 ]=1 ; for (int i=1 ;i<=n;i++) { p[i]=p[i-1 ]*P; h[i]=h[i-1 ]*P+str[i]; } while (m--) { int l1,r1,l2,r2; cin>>l1>>r1>>l2>>r2; if (get_val (l1,r1)==get_val (l2,r2)) cout<<"Yes" <<endl; else cout<<"No" <<endl; } return 0 ; }
在计算最短路时,Dijkstra算法不能处理带有负权的图,bellman_ford和spfa可以处理带有负权边的图,spfa是对bellman_ford的优化。
图论
Dijkstra算法
在Dijkstra算法中,稠密图使用邻接矩阵,稀疏图使用邻接表
朴素Dijkstra算法
在使用Dijkstra算法时,如果有向图中出现重边或者是有环的话,只采用代价最小的边
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 给定一个 n 个点 m条边的有向图,图中可能存在重边和自环,所有边权均为正值。 请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1 。 输入格式 第一行包含整数 n 和 m。 接下来 m行每行包含三个整数 x,y,z,表示存在一条从点 x到点 y的有向边,边长为 z。 输出格式 输出一个整数,表示 1 号点到 n 号点的最短距离。 如果路径不存在,则输出 −1 。 数据范围1 ≤n ≤500 ,1 ≤m≤105 , 图中涉及边长均不超过10000 。 输入样例:3 3 1 2 2 2 3 1 1 3 4 输出样例:3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> #include <algorithm> #include <cstring> using namespace std;const int N=510 ;int n,m,g[N][N],dist[N];bool st[N];int dijkstra () { memset (dist,0x3f ,sizeof dist); dist[1 ]=0 ; for (int i=1 ;i<=n;i++){ int t=-1 ; for (int j=1 ;j<=n;j++) if (!st[j]&&(t==-1 ||dist[t]>dist[j])) t=j; st[t]=true ; for (int j=1 ;j<=n;j++) dist[j]=min (dist[j],dist[t]+g[t][j]); } if (dist[n]==0x3f3f3f3f ) return -1 ; else return dist[n]; }int main () { cin>>n>>m; memset (g,0x3f ,sizeof g); while (m--) { int a,b,c; cin>>a>>b>>c; g[a][b]=min (g[a][b],c); } int t = dijkstra (); cout<<t<<endl; return 0 ; }
堆优化Dijkstra算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 给定一个 n 个点 m条边的有向图,图中可能存在重边和自环,所有边权均为非负值。 请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1 。 输入格式 第一行包含整数 n 和 m。 接下来 m行每行包含三个整数 x,y,z,表示存在一条从点 x到点 y的有向边,边长为 z。 输出格式 输出一个整数,表示 1 号点到 n 号点的最短距离。如果路径不存在,则输出 −1 。 数据范围1 ≤n ,m≤1.5 ×105 , 图中涉及边长均不小于 0 ,且不超过 10000 。数据保证:如果最短路存在,则最短路的长度不超过 109 。 输入样例:3 3 1 2 2 2 3 1 1 3 4 输出样例:3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <iostream> #include <cstring> #include <algorithm> #include <queue> using namespace std;const int N=2e5 ;int h[N],n,m,ne[N],val[N],idx,w[N],dist[N];bool state[N];typedef pair<int ,int > PII;void add (int a,int b,int c) { val[idx]=b; w[idx]=c; ne[idx]=h[a]; h[a]=idx++; }int dijkstra () { memset (dist,0x3f ,sizeof dist); dist[1 ]=0 ; priority_queue<PII,vector<PII>,greater<PII>>q; q.push ({0 ,1 }); while (q.size ()) { auto t = q.top (); q.pop (); int ver = t.second,distance = t.first; if (state[ver]) continue ; state[ver]=true ; for (int i=h[ver];i!=-1 ;i=ne[i]) { int j=val[i]; if (dist[j]>dist[ver]+w[i]){ dist[j]=dist[ver]+w[i]; q.push ({dist[j],j}); } } } if (dist[n]==0x3f3f3f3f ) return -1 ; else return dist[n]; }int main () { memset (h,-1 ,sizeof h); cin>>n>>m; while (m--) { int a,b,c; cin>>a>>b>>c; add (a,b,c); } auto t=dijkstra (); cout<<t<<endl; return 0 ; }
bellman_ford
Bellman-ford
算法是求含负权图的单源最短路径的一种算法,效率较低,代码难度较小。其原理为连续进行松弛,在每次松弛时把每条边都更新一下,若在
n-1
次松弛后还能更新,则说明图中有负环,因此无法得出结果,否则就完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 给定一个 n 个点 m条边的有向图,图中可能存在重边和自环, 边权可能为负数。 请你求出从 1 号点到 n 号点的最多经过 k条边的最短距离,如果无法从 1 号点走到 n 号点,输出 impossible。 注意:图中可能 存在负权回路 。 输入格式 第一行包含三个整数 n ,m,k。 接下来 m行,每行包含三个整数 x,y,z,表示存在一条从点 x到点 y的有向边,边长为 z。 点的编号为 1 ∼n 。 输出格式 输出一个整数,表示从 1 号点到 n 号点的最多经过 k条边的最短距离。 如果不存在满足条件的路径,则输出 impossible。 数据范围1 ≤n ,k≤500 ,1 ≤m≤10000 ,1 ≤x,y≤n , 任意边长的绝对值不超过 10000 。 输入样例:3 3 1 1 2 1 2 3 1 1 3 3 输出样例:3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N=510 ,M=1e5 +10 ;int dist[N],backup[N],n,m,k;struct edge { int a,b,w; }edge[M];void bellman_ford () { memset (dist,0x3f ,sizeof dist); dist[1 ]=0 ; for (int i=0 ;i<k;i++) { memcpy (backup,dist,sizeof dist); for (int j=0 ;j<m;j++) { int a=edge[j].a,b=edge[j].b,w=edge[j].w; if (dist[b]>backup[a]+w) dist[b]=backup[a]+w; } } }int main () { cin>>n>>m>>k; for (int i=0 ;i<m;i++) { int a,b,w; cin>>a>>b>>w; edge[i]={a,b,w}; } bellman_ford (); if (dist[n]>0x3f3f3f3f /2 ) cout<<"impossible" <<endl; else cout<<dist[n]<<endl; return 0 ; }
spfa算法
Bellman_ford算法会遍历所有的边,但是有很多的边遍历了其实没有什么意义,我们只用遍历那些到源点距离变小的点所连接的边即可,只有当一个点的前驱结点更新了,该节点才会得到更新;因此考虑到这一点,我们将创建一个队列每一次加入距离被更新的结点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 给定一个 n 个点 m条边的有向图,图中可能存在重边和自环, 边权可能为负数。 请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 impossible。 数据保证不存在负权回路。 输入格式 第一行包含整数 n 和 m。 接下来 m行每行包含三个整数 x,y,z,表示存在一条从点 x到点 y的有向边,边长为 z。 输出格式 输出一个整数,表示 1 号点到 n 号点的最短距离。 如果路径不存在,则输出 impossible。 数据范围1 ≤n ,m≤105 , 图中涉及边长绝对值均不超过 10000 。 输入样例:3 3 1 2 5 2 3 -3 1 3 4 输出样例:2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <iostream> #include <cstring> #include <queue> using namespace std;const int N=2e5 ;int head[N],val[N],ne[N],w[N],idx,dist[N],n,m;bool state[N];void add (int a,int b,int c) { val[idx]=b; w[idx]=c; ne[idx]=head[a]; head[a]=idx++; }void spfa () { memset (dist,0x3f ,sizeof dist); dist[1 ]=0 ; queue<int > q; state[1 ]=true ; q.push (1 ); while (q.size ()) { int t=q.front (); q.pop (); state[t]=false ; for (int i=head[t];i!=-1 ;i=ne[i]) { int j = val[i]; if (dist[j]>dist[t]+w[i]) { dist[j]=dist[t]+w[i]; if (!state[j]){ q.push (j); state[j]=true ; } } } } }int main () { memset (head,-1 ,sizeof head); cin>>n>>m; for (int i=0 ;i<m;i++) { int a,b,c; cin>>a>>b>>c; add (a,b,c); } spfa (); if (dist[n]==0x3f3f3f3f ) cout<<"impossible" <<endl; else cout<<dist[n]<<endl; return 0 ; }
树
先序创建二叉树
算法思想
先序创建二叉树采用递归的方式实现,先传入二叉树的根节点指针的地址,然后依次递归读入二叉树节点,当读入的是'@'时,将递归得到的根节点赋予NULL,当读入的不为'@'时,将该值赋值到根节点值,并依此递归左儿子和右儿子。
关键问题1:为什么要传入根节点的地址?
因为我们已知的是根节点指针,它指向malloc开辟的BiTree空间的首地址,当传入的是根节点指针而不是根节点指针的地址时,在函数中会copy一个该根节点指针指向的空间。但我们如果传入的是根节点指针的地址,函数内部不会重新copy一个空间。
其思想与函数传入实参和形参相似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <algorithm> #include <string> #include "Traverser.cpp" using namespace std;typedef struct BiTree { char data; struct BiTree *lchild,*rchild; }BiTree;void CreateBiTree (BiTree* &T) { char ch; cin>>ch; if (ch=='@' ) T=NULL ; else { T=(BiTree*)malloc (sizeof (BiTree)); T->data=ch; CreateBiTree (T->lchild); CreateBiTree (T->rchild); } }void PreOrderTraverser (BiTree* &T) { if (T) { cout<<T->data<<" " ; PreOrderTraverser (T->lchild); PreOrderTraverser (T->rchild); } }int main () { BiTree* root; CreateBiTree (root); PreOrderTraverser (root); return 0 ; }
遍历二叉树
代码部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 #include <iostream> #include <algorithm> #include <string> #include <stack> #include <algorithm> #include <string> using namespace std;typedef struct BiTree { char data; struct BiTree *lchild,*rchild; }BiTree;void CreateBiTree (BiTree* &T) { char ch; cin>>ch; if (ch=='@' ) T=NULL ; else { T=(BiTree*)malloc (sizeof (BiTree)); T->data=ch; CreateBiTree (T->lchild); CreateBiTree (T->rchild); } }void PreOrderTraverser (BiTree *T) { if (T) { cout<<T->data<<" " ; PreOrderTraverser (T->lchild); PreOrderTraverser (T->rchild); } }void InOrderTraverser (BiTree *T) { if (T) { InOrderTraverser (T->lchild); cout<<T->data<<" " ; InOrderTraverser (T->rchild); } }void PostOrderTraverser (BiTree *T) { if (T) { PostOrderTraverser (T->lchild); PostOrderTraverser (T->rchild); cout<<T->data<<" " ; } }void InOrderTraverser_2 (BiTree* T) { stack<BiTree*> op; BiTree* p=T; while (p||op.size ()) { if (p){ op.push (p); p=p->lchild; } else { p=op.top (); op.pop (); cout<<p->data<<" " ; p=p->rchild; } } }void PreOrderTraverser_2 (BiTree* T) { stack<BiTree*>op; BiTree* p=T; while (p||op.size ()) { if (p){ cout<<p->data<<" " ; op.push (p); p=p->lchild; } else { p=op.top (); op.pop (); p=p->rchild; } } }int main () { BiTree* root; CreateBiTree (root); PreOrderTraverser (root); cout<<endl; PreOrderTraverser_2 (root); cout<<endl; InOrderTraverser (root); cout<<endl; InOrderTraverser_2 (root); cout<<endl; PostOrderTraverser (root); cout<<endl; return 0 ; }
计算二叉树
代码部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <iostream> #include <string> #include <algorithm> using namespace std;typedef struct BiTree { char data; struct BiTree *lchild,*rchild; }BiTree;void CreateBiTree (BiTree* &T) { char ch; cin>>ch; if (ch=='@' ) T=NULL ; else { T=(BiTree*)malloc (sizeof (BiTree)); T->data=ch; CreateBiTree (T->lchild); CreateBiTree (T->rchild); } }int BiTreeDepth (BiTree* T) { if (!T) return 0 ; else { int m=BiTreeDepth (T->lchild); int n=BiTreeDepth (T->rchild); return m>n?(m+1 ):(n+1 ); } }int CountNode (BiTree* T) { if (T==NULL ) return 0 ; else return CountNode (T->lchild)+CountNode (T->rchild)+1 ; }int CountLeaves (BiTree* T) { int cnt=0 ; if (T==NULL ) return 0 ; if ((!T->lchild)&&(!T->rchild)) cnt++;; int lcnt=CountLeaves (T->lchild); int rcnt=CountLeaves (T->rchild); cnt+=lcnt+rcnt; return cnt; }int main () { BiTree *root; CreateBiTree (root); cout<<BiTreeDepth (root)<<endl; return 0 ; }