GPT摘要 
这篇文章是算法与数据结构学习的大纲,涵盖了从基础算法到高级算法的多个领域。主要内容包括基础算法(排序、二分、高精度计算、前缀和、双指针等)、数据结构(链表、栈、队列、树、Trie树、并查集、堆等)、图论算法(深度优先搜索、广度优先搜索、最短路、最小生成树等)、数学算法(质数筛选、约数、快速幂、逆元、组合数学等)、动态规划(背包问题、线性动态规划、状态动态规划、记忆化搜索)以及贪心算法。文章还列出了LeetCode的Hot 100题单和按类别分类的刷题建议,包括找规律题、基础算法题、数据结构应用题、搜索算法题和动态规划题等。此外,还提到了一些高级算法和数据结构的实现,如马拉车算法、非递归快速排序、非递归中序遍历等。整体来看,这是一份较为全面的算法与数据结构学习路线图,适合系统性学习和刷题准备。
 
0.特殊 leetcode  10^7^
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 n<= 12       n! n<= 30        2 ^n            dfs+剪枝 100 ~ 300   n ^3             floyd 10 ^3         n ^2             dp dij 二分   10 ^4         n*根号n      块状链表 10 ^5 ~ 6     nlogn         排序 线段树 树状数组 set/map heap dij+heap spfa 二分 求凸包 求半平面交 10 ^6          n               hash 双指针  kmp ac自动机   	小常nlogn   sort 树状数组 heap+dij spfa 10 ^7          n                hash 双指针  kmp ac自动机  线性筛质素 10 ^9          根号n         判断质素 10 ^18         logn          欧几里得  快速幂 
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 #include <utility>  map #include <cstring>  memset(h,-1,sizeof h); memset(dis,0x3f,sizeof dis); int  idx = lower_bound (alls.begin (), alls.end (), x)-alls.begin ();int , vector<int >, less<int > > q;  int ,vector<int >,greater<int > >q;  static  bool  mycmp (ListNode* l1, ListNode* l2)   return  l1->val>l2->valbool (ListNode*, ListNode*)>> pq (mycmp);struct  comp  {bool  operator () (ListNode* a, ListNode* b)  return  a->val > b->val;setiosflags (ios::fixed)<<setprecision (6 )sort (alls.begin (),alls.end ());erase (unique (alls.begin (),alls.end ()),alls.end ());vector<int > a (b)  	vector<int > a  = {1 ,2 ,3 }初始化swap (b)			交换push_back ({x,c})  size () empty () clear () begin () end ()insert ()find ()count () erase (x)删除所有x  k+logn     erase (迭代器) 删除当前     lower_bound (x)  删除大于等于x最小的       upper_bound (x)  删除大于x中最小的 map multimap     insert ({'a' ,1 })  erase ()  参数pair     添加M["str"] =1  ,如果没有该key,默认值为0 auto 遍历时first,second取数,和pair遍历一样O (1 ) 不支持 lower_bound upper_boundlower_bound (a,a+n,x)-a;lower_bound (v.begin (),v.end (),x)-v.begin ();  srand ((int )time (0 ));rand ()%100 << " " ;static  bool  cmp (int  a,int  b) return  a>b;}sort (a,a+n,cmp);sort (a,a+n,greater <int >());
1.基础 1.0排序 
快速排序
 
	选取中点为划分,左边<=,右边>=。再分别排序两边  空间O(logn)   不稳定 边界选择 
1 2 3 4 5 6 7 8 9 10 11 int  i=l-1  ,j=r+1  mid=a[l + r >> 1 ];while (i<j){while (a[i]<mid) i ++;while (a[j]>mid) j --;if (i<j)swap (a[i], a[j]);quickSort (l, j);        quickSort (j+1 , r);return  ;
	partition :  l的左边小于key,r的右边大于key。[l:i)的数等于key
l为下一个放小于key的地方,r为下一个放大于key的地方
最后0l-1小于key , lr等于key , r+1到n-1大于key
1 2 3 4 5 6 7 8 9 10 11 12 l=0 ,r=n-1 ,i=lwhile (i<=r){if (nums[i]<key){swap (nums[i],nums[l]);else  if (nums[i]>key){swap (nums[i],nums[r]);else {
归并排序
 
	先排好中点左边,再排中点右边,然后merge两边,  空间O(n)  稳定
1 2 3 4 int  mid = l+r >> 1 ;merge_sort (l,mid);merge_sort (mid+1 ,r);
其他
 
1 2 3 4 5 6 7 8 冒泡 每次交换出一个最大
1 2 3 4 5 6 7 8 9 10 11 12 排序算法	平均时间复杂度	最坏时间复杂度	空间复杂度	稳定性QuickSort 	O ( n  log  n ) 	O ( n ^ 2 ) 		O ( log  n ) 不稳定Merge  Sort 	O ( n  log  n ) 	O ( n  log  n ) 	O ( n ) 稳定Heap  Sort 	O ( n  log  n ) 	O ( n  log  n ) 	O ( 1 ) 不稳定Timsort 		O ( n  log  n ) 	O ( n  log  n ) 	O ( n ) 稳定  归并+ 插入Dual - Pivot  	O ( n  log  n ) 	O ( n ^ 2 ) 		O ( log  n ) 不稳定  java  int  char  long [ ] Insertion  	O ( n ^ 2 ) 	O ( n ^ 2 ) 			O ( 1 ) 稳定   每次向有序中插入一个元素 常数小!Bubble  Sort 	O ( n ^ 2 ) 	O ( n ^ 2 ) 			O ( 1 ) 稳定   冒泡 每次交换出一个最大Selection  	O ( n ^ 2 ) 	O ( n ^ 2 ) 			O ( 1 ) 不稳定  每次在无序中,选出一个最小的Counting  	O ( n  +  k ) 	O ( n  +  k ) 	O ( k ) 稳定  	0 ~ k 个桶 统计数量,适用于小范围整数。Radix  		O ( d ( n  +  k ) ) 	O ( d ( n  +  k ) ) 	O ( n  +  k ) 稳定 	按位排序,适用于整数和固定长度字符串。Bucket  		O ( n  +  k ) 	O ( n ^ 2 ) 		O ( n  +  k ) 稳定	数据分桶[ 0 ,  0.1 ) ,  [ 0.1 ,  0.2 ) , 后排序,再合并所有桶。
TimSort 的过程: 
分块 : TimSort 首先将数组分成若干个小的子数组,这些子数组称为 Run 。每个 Run 的大小通常在 32 到 64 之间,具体取决于数组的大小和实现。对每个 Run 进行排序 : 对每个 Run 使用插入排序进行排序,因为插入排序在小规模数据上效率高。合并 Run : 将这些有序的 Run 通过归并排序的方式合并在一起,形成一个更大的有序数组。TimSort 会根据实际情况决定何时合并哪些 Run,以保持整体效率。 
1.1二分 分界点右边都满足一种性质,左边都不满足
1 2 3 4 5 6 7 8 9 10 l=0  ,r=n-1 ;while (l<r){1 ;if (a[mid]>=aim){else {+1 ;
1 2 3 4 5 6 7 8 9 while (l<r){+1 >>1 ;        if (a[mid]<=aim){else {-1 ;
1 2 3 4 5 6 while (r-l>0.00000001 ){        double  mid=(l+r)/2 ;if (mid*mid*mid<=n) l=mid;else  r=mid;
0.旋转数组找旋转点
1.机器人跳跃 1e5 n a[i],从起点跳到终点 
1 2 每次跳跃res= res*2 -a[i];   res始终要大于零,求最小的起始res。1e5 ,遍历[1 ,1e5 ]个数超时,二分遍历即可解决
2.分巧克力
1 2 对于每一种大小的切法,1e5时间能求出是否满足数量
3.炸弹问题:矩阵和
4.四平方和 四个数的平方和为x,求出序列最小的  x< 5*1e6 
1 2 3 4 求出三个数,就能得出最后一个,但是时间上最多求出两个a  b ,查看x -a ^2 -b ^2 是否在存下的数中(二分,hash)
5.k倍区间  一个数组中子串为给定数k的倍数,求共有多少个这样的区间  1e5
1 2 3 4 5 6 7 8 9 两个循环加前缀和 但依旧会超时0  相当于  s[r]与s[l]余数相同,所以每次是在找余数为固定值的个数if (s[r]==0 )res ++;-1 );
1.2高精度 
除了加法都要去除前面的零    while(C.size()>1&&C.back()==0)C.pop_back(); 
乘法每一位直接拿小的数乘 
除法从高位开始除,最后反转 reverse(C.begin(),C.end()); 
 
1 2 3 4 5 6 7 8 9 10 11 for (int  i=0 ;i<a1.size();i++){for (int  j=0 ;j<a2.size();j++){for (int  i=0 ;i<res.length-1 ;i++){1 ] += res[i]/10 ;10 ;
java中,string到List<Integer>需要-‘0’,而sb.append 可以直接append数字
1.3前缀和  差分 ==前缀和==:前缀和一个值代表着一个区间的性质
普通前缀和  询问区间  
矩阵和        求出每个到0,0点的矩阵的和   询问矩阵  
前缀积 2438. 二的幂数组中查询范围内的乘积 - 力扣(LeetCode)   需要结合逆元 
 
==差分==:差分数组一个值的改变影响一个区间
差分数组   多次区间操作后,询问结果  
差分矩阵b求和等于a,不用考虑b如何出来的,只需要考虑b如何改变a的
 
 
1.4双指针 双指针
将i j 两种枚举n^2的情况,优化为O(n);
将一个英文句子的每个单词输出。
1 2 3 4 5 for  (int  i = 0 , j = 0 ; i < n; i ++ )while  (j < i && check (i, j)) j ++ ;
连续最长不重复子串
两个升序数组,求和为x的两个数组的下标
判断是否是字串
 
1.5位运算 1 2 3 4 5 #define  lowbit(x) x&-x 1 
1.6离散化 原来的下标太大了,将大数,转为排序数组的下标
将大的数保存到vector中后,排序,去重
1 2 sort (alls.begin (),alls.end ());erase (unique (alls.begin (),alls.end ()),alls.end ());
将大值和下标映射用二分查找 或unordered_map 映射
询问上的点也要先加入到alls中
如果只想将大数映射到小数,也可也不去重,同一个数每次查找时会获取出同一个idx
1 2 3 4 
2.数据结构 单链表 int h=-1, e[], ne[], idx
栈 s[],  t=-1
单调栈 单调增:栈内元素单调递增,遍历到i时,导致某元素出栈,则i是右边第一个比该元素小的,新栈顶为左边第一个比该元素小的。
最大矩阵面积 
 
队列 q[], t=-1, h=0;     t进h出   q[++t]=x;  h++;
单调队列:把最小的始终保存在队头,实现滑动窗口求最小 
 
树遍历 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 297  树的序列化与反序列化void  middle_order (Node Node) :	if(Node = = NULL )return ;middle_order (Node->left);middle_order (Node->right);def inorderTraversal (self, root: TreeNode)  -> List[int ]:     WHITE, GRAY =  0 , 1 while  stack:pop ()if  node is None: continue if  color == WHITE:append ((WHITE, node.right)) # 先输出的后入栈append ((GRAY, node))append ((WHITE, node.left))else :append (node.val)return  res
搜索树 树:边的个数加+ 1 = ∑节点*度 + 1 = 节点个数
https://www.bilibili.com/video/BV1Tb4y197Fe 
Binary Search Tree: BST    ->退化->   AVL  (左右高度差不超过1)>频繁增删>  RBT 
如果失去平衡, 找到第一个失去平衡的点左右旋转使得平衡 LL,RR(直接旋转)  LR(先左旋)
 RBT:根是黑色(空结点也是黑色)、红节点不连续、黑节点到达空指针进过的黑点个数相同
B树 (数据库):一个节最多m-1个值(连续,访问磁盘次数就会更少),m个孩子节点。m为树的阶
		非叶子节点最少有(m+1)/2子树,最多m
		叶子节点为空节点,都在同一层
		查找 :和BST类似       范围查询:中序遍历
		插入 :当节点个数多于(m+1)/2时,进行分裂
		删除 :终端节点:大于(m+1)/2-1个节点:直接删
										等于(m+1)/2-1,去兄弟节点借,借不到就合并:上一层挪下来一个
					非终端:和相邻关键字(前驱、后驱)互换,就变成了终端节点
B+树: 
		n个节点n个子树
		非叶子节点只能索引,叶子节点才指向记录。
		叶子节点被串成了一个单链表,可以线性访问,头指针被保存。
KMP p 在 s中所有的起始位置
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 ne:p当前匹配失败后,j跳转的位置(j-1 的最大匹配长度) ababc ne[3 ]=1   ne[4 ]=2 void  get (){int  i=0 ,j=-1 ;0 ]=-1 ;while (i<n){if (j==-1 ||p[i]==p[j]){else {void  kmp () int  i=0 ,j=0 ;while (i<m){if (j==-1 ||s[i]==p[j]){if (j==n){" " ;else {get ();kmp ();
Trie树 字符串出现次数
1 2 3 4 5 6 7 8 9 10 11 int  son[N][26 ],cnt[N],idx;   void  insert (char  str[]) int  p=0 ;for (int  i=0 ;str[i];i++){int  u=str[i]-'a' ;if (!son[p][u])son[p][u]=++idx;return  ;
并查集 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int  find (int  x) if (x!=p[x]){find (p[x]);return  p[x];void  merge (int  x,int  y) int  fx=find (x),fy=find (y);if (fx!=fy){     return  ;
堆 用数列保存,从1开始存 儿子(2u,2 u+1)
down: 左右儿子已经是堆,加上直接再构成堆    n/2开始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 void  down (int  u) int  t=u;if (u*2 <=si&&h[u*2 ]<h[t])t=u*2 ;if (u*2 +1 <=si&&h[u*2 +1 ]<h[t])t=u*2 +1 ;if (u!=t){swap (h[u],h[t]);down (t);void  up (int  i) if (i!=1  && heap[i]<heap[i/2 ]){swap (heap[i],heap[i/2 ]);up (i/2 );void  insert (int  p) up (now);void  pop () 1 ]=heap[now];down (1 );
无扩容java堆,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 class  MyPriorityQueue <E>{private  int  size;private  Object[] queue;private  final  Comparator<? super  E> comparator;public  MyPriorityQueue (int  initialCapacity, Comparator<? super  E> comparator) {this .queue = new  Object [initialCapacity+1 ];0 ;this .comparator = comparator;public  void  offer (E e) {1 );public  E peek () {return  size == 0  ? null : (E)queue[0 ];public  E poll () {if (size == 0 ){return  null ;E  res  =  (E)queue[0 ];0 ] = queue[size-1 ];0 );return  res;private  void  down (int  i) {int  u  =  i;if (i*2 +1 <size && comparator.compare((E)queue[u], (E)queue[i*2 +1 ]) > 0 ){2 +1 ;if (i*2 +2 <size && comparator.compare((E)queue[u], (E)queue[i*2 +2 ]) > 0 ){2 +2 ;if (u != i){Object  t  =  queue[i];private  void  up (int  i) {if (i == 0 ) return  ;int  p  =  (i-1 ) / 2 ;if (comparator.compare((E)queue[i], (E)queue[p]) < 0 ){Object  t  =  queue[i];public  int  size () {return  size;
Hash 方法1:线性探测表,开两倍地址空间 null=0x3f3f3f3f
//返回x对应下标,没有x就返回应该放的地方
1 2 3 4 5 6 7 8 int  find (int  x) int  k=(x%N+N)%N;while (a[k]!=null&&a[k]!=x){if (k==N)k=0 ;return  k;
方法2:链地址法,也是存图的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int  h[N],e[N],ne[N],idx;int  mod=100003 ;void  insert (int  x) int  k=(x%N+N)%N; bool  query (int  x) int  k=(x%N+N)%N;for (int  i=h[k];i!=-1 ;i=ne[i])if (e[i]==x)return  true ;return  false ;
字符串hash 将一段字符串 用一个数字 表示,字符串低位为数字高位  左高右低
hash值用ULL保存typedef unsigned long long ULL;
1 2 3 const  int  P=131 ;
存入时,计算字符串hash
1 2 3 4 for (int  i=1 ;i<=n;i++){-1 ]*P+s[i];-1 ]*P;
计算式,L到R之间的字符串的hash值为
1 ULL t=h[R]-h[L-1 ]*p[R-L+1 ];
java:214. 最短回文串  前添加字符使得字符串回文
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 int  base  =  131 ;int  mod  =  (int )1e9 +7 ;int  mul  =  1 ;int  prefix  =  0 , suffix = 0 ;int  idx  =  0 ;int  n  =  s.length();for (int  i=0 ;i<n;i++){int  c  =  s.charAt(i) - 'a'  + 1 ;int )(((long ) prefix * base + c)%mod); int )(((long ) mul * c + suffix)%); int )(((long ) mul * base) % mod);if (prefix == suffix){if (idx==n-1  || n==0 ){return  s;return  new  StringBuilder (s.substring(idx+1 )).reverse().toString() + s;
回文串 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 class  Solution  {public  int  countSubstrings (String s)  {int  n  =  s.length();StringBuffer  t  =  new  StringBuffer ("$#" );for  (int  i  =  0 ; i < n; ++i) {'#' );'!' );int [] f = new  int [n];int  iMax  =  0 , rMax = 0 , ans = 0 ;for  (int  i  =  1 ; i < n; ++i) {1 , f[2  * iMax - i]) : 1 ;while  (t.charAt(i + f[i]) == t.charAt(i - f[i])) {if  (i + f[i] - 1  > rMax) {1 ;2 ;1 );return  ans;
树状数组 
逆序对数量
单点更新  update(x, delta): 把序列 x 位置的数加上一个值 delta;  logn
前缀和查询  query(x):查询序列 [1,...x] 区间的区间和,即位置 x 的前缀和。logn
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int  n;int  a[1005 ],c[1005 ]; int  lowbit (int  x) return  x&(-x);void  updata (int  i,int  k) while (i <= n){lowbit (i);int  getsum (int  i) int  res = 0 ;while (i > 0 ){lowbit (i);return  res;
建树规则:c[i] 管理着 从i开始的前lowbit(i)个数的和 
求前 10010010的和 只需要拿出c[10010010] (最后两个数) + [sum(10010000) = c[10010000] + c[10000000] ]
当一个数修改后 1010,会影响管理者他的c:c[1010]  c[1100] c[10000] c[100000] …
单点修改+区间查询      (单纯前缀和不能在这两种操作混合时发挥作用)
区间修改+单点查询      维护差分数组   (单纯差分数组在这两种操作混合时发挥作用)
 
3.图 建图 1 2 3 4 5 6 7 8 9 10 11 12 13 int  h[N],ne[N],e[N],w[N];int  idx;memset (h,-1 ,sizeof  h);void  add (int  a,int  b,int  c) for (int  i=h[a];i!=-1 ;i=ne[i]){int  j=e[i];
dfs: 1 2 3 4 5 6 7 8 9 10 11 void  dfs (int  u)  true ; for  (int  i = h[u]; i != -1 ; i = ne[i]) {int  j = e[i];if  (!st[j]) {dfs (j);
bfs: st[] 和 queue
求距离,先进距离短 先出
 d[]保存距离,同时判断是否访问过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 d[1 ]=0 ;int > q;push (1 );while (q.size ()){int  x=q.front ();q.pop ();for (int  i=h[x];~i;i=ne[i]){int  j=e[i];if (d[j]==-1 ){+1 ;push (j);
最短路 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 无负边 (从顶点角度出发每次取出最近顶点)
dij 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 dis[1 ]=0 ;priority_queue <PII,vector <PII>,greater<PII> > p;0 ,1 });while (p.size()){if (st[tmp.second])continue ;int  x = tmp.second;1 ;for (int  i=h[x];i!=-1 ;i=ne[i]){int  j = e[i];if (dis[j]>dis[x]+w[i]){  
bellmanford 1 2 3 4 5 6 7 8 9 dis[1 ]=0 ;for (int  i=0 ;i<k;i++){memcpy (backup,dis,sizeof  dis); for (int  j=0 ;j<m;j++){int  a=edges[j].a,b=edges[j].b,c=edges[j].c;min (dis[b],backup[a]+c);
如果k=n时还更新了,就是存在包含点1的负环
spfa 当一个点距离变小后,所有以该点的出边才可能更新。st数组防止重复添加(不用也能过)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 dis[1 ]=0 ;push (1 );1 ]=1 ;while (q.size ()){int  t=q.front ();pop ();0 ;for (int  i=h[t];i!=-1 ;i=ne[i]){int  j=e[i];if (dis[j]>dis[t]+w[i]){if (st[j]==0 ){push (j);1 ;
判负环 最短路包含边数大于n-1
设置一个虚拟源节点,到所有阶段距离都为0,原图有负环等价于现在的图有负环,
第一次spfa等价于所以开始时所有点都加入
最小生成树 1 2 普里姆:	 加最近点,有点像dij    n ^2 克鲁斯卡尔: 加最小的边     		E ^2      并查集 
4.数学 筛质素 诶氏筛法 O(nloglogn):小于等于 n 的质数的倒数和大约是 loglogn
1 2 3 4 5 6 7 8 void  get_primes1 () {for (int  i=2 ;i<=n;i++){if (!st[i]){for (int  j=i;j<=n;j+=i) st[j]=true ;
线性筛法 O(n):每个数只被最小的质素筛 https://www.acwing.com/solution/content/2559/ 
i=15  会筛掉 15* 2 15* 3 ,但15*5不会被15筛,而是被25 * 3筛, 所以primes[j]只到 i 的最小因子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void  get_primes (int  n) for (int  i = 2 ; i <= n; i++)if (!st[i]) primes[cnt++] = i;for (int  j = 0 ; primes[j] <= n / i; j ++)true ;if (i % primes[j] == 0 ) break ;
约数 n 个正整数 ai,请你输出这些数的乘积的约数个数
求出所有质因子p以及个数x:N = (p1^x1^)(p2^x2^)(p3^x3^)…(pk^xk^)   
约数个数: (x1+1)(x2+1)(x3+1)…(xk+1)
约数和=(1+p1^1^+p1^2^+…+p1^x1^)×(1+p2^1^+p2^2^+…+p2^x2^)…×(1+pk^1^+pk^2^+…+pk^xk^)   每一个括号里取出一个数相乘就得到一个约数,最后求和
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 unordered_map <int , int > primes;while  (n -- )int  x;cin  >> x;for  (int  i = 2 ; i <= x / i; i ++ )while  (x % i == 0 )if  (x > 1 ) primes[x] ++ ;1 ;for  (auto  p : primes)1 ;while  (b -- ) t = (t * a + 1 ) % mod;cout  << res << endl ;
秦九韶算法: 计算多项式朴素方法需要2n乘法(xn-1 * x * a)n次加法,秦九韶只需要n次乘n次加
快速幂 1 2 3 4 5 6 7 8 9 10 11 12 求 m^k mod p,时间复杂度 O (logk)。int  qmi (int  m, int  k, int  p) int  res = 1  % p, t = m;while  (k)if  (k&1 ) res = res * t % p;1 ;return  res;
矩阵快速幂 将递推式写成矩阵形式:斐波那契数列 O(n)-> O(m^3*logn)
矩阵快速幂 - Virtual Judge (vjudge.net) 
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 const  int  N=6 ;                  const  int  n=6 ;                  const  int  mod=123456789 ;        long  long  int  t[N][N];          long  long  int  tmp[N][N];         void  multi (long  long  int  a[][N],long  long  int  b[][N],long  long  int  n) memset (tmp,0 ,sizeof  tmp);for (int  i=0 ;i<n;i++)for (int  j=0 ;j<n;j++)for (int  k=0 ;k<n;k++)for (int  i=0 ;i<n;i++)for (int  j=0 ;j<n;j++)long  long  int  result[N][N];                     void  Pow (long  long  int  a[][N],long  long  int  x) memset (result,0 ,sizeof  result);             for (int  i=0 ;i<N;i++) result[i][i]=1 ;while (x){if (x&1 )multi (result,a,n);              multi (a,a,N);                       1 ;int  main () int  x;while (cin>>x&&x!=-1 ){0 ][0 ]=t[0 ][1 ]=t[1 ][0 ]=1 ;1 ][1 ]=0 ;pow (t,x);0 ][1 ]<<endl;
逆元 
(ab)modp=(amodp)∗(bmodp)modp 
(a+b)modp=((amodp)+(bmodp))modp 
(a−b)modp=((amodp)−(bmodp)+p)modp 
注意(a/b)modp≠((amodp)/(bmodp))modp 这也是为什么需要求乘法逆元  
若整数 b,m互质,并且对于任意的整数 a,如果满足 b|a,则存在一个整数 x,使得 a/b≡a×x(modm),则称 x为 b的模 m 乘法逆元,记为 b−1(modm)。
b存在乘法逆元的充要条件是 b与模数 m互质:
1 2 3 4 5 6 7 8 a / b ≡ a * x (mod  n )mod  n )1  ≡ b * x (mod  n )1  (mod  n )n 为质数时 且b n  互质n  - 1 ) ≡ 1  (mod  n )n  - 2 ) ≡ 1  (mod  n )n 为质数时,b的乘法逆元 x = b ^ (n  - 2 )
逆元可拆分:(a!)-1 =(a-1! * a)-1 = ( a-1! )-1 * ( a )-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 import  java.io.*;class  Main {static  BufferedReader  read  =  new  BufferedReader (new  InputStreamReader (System.in));static  int  N  =  100010 , p = (int ) 1e9  + 7 ;static  int [] fact = new  int [N], infact = new  int [N];public  static  int  qmi (int  a, int  k, int  p) {long  res  =  1 ;while ( k > 0 ){if ( (k & 1 ) != 0  ) res = res * a % p;int ) ((long ) a * a % p);1 ;return  (int ) res;public  static  void  main (String[] args)  throws  Exception{int  t  =  Integer.valueOf(read.readLine());0 ] = infact[0 ] = 1 ;for (int  i  =  1 ; i < N; i++){int ) ((long )fact[i - 1 ] * i % p);int ) ((long ) infact[i - 1 ] * qmi(i, p - 2 , p ) % p);while (t -- > 0 ){" " );int  a  =  Integer.valueOf(ss[0 ]);int  b  =  Integer.valueOf(ss[1 ]);int  res  =  (int ) ((long ) fact[a] * infact[a - b] % p * infact[b] % p);
容斥原理 https://www.acwing.com/solution/content/29702/ 
1 2 n = 10 , p1 =2 ,p2 =3 1 -10 中能满足能整除p1 或p2 的个数, 即2 ,3 ,4 ,6 ,8 ,9 ,10 ,共7 个
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>  std ;typedef  long  long  LL;const  int  N = 20 ;int  p[N], n, m;int  main ()  {cin  >> n >> m;for (int  i = 0 ; i < m; i++) cin  >> p[i];int  res = 0 ;for (int  i = 1 ; i < 1  << m; i++) {int  t = 1 ;             int  s = 0 ;             for (int  j = 0 ; j < m; j++){if (i >> j & 1 ){if ((LL)t * p[j] > n){    -1 ;break ;if (t == -1 ) continue ;  if (s & 1 ) res += n / t;              else  res -= n / t;                      cout  << res << 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 n元1次方程组-1 ...-1 ...最后一行1个) 唯一解             r(A)=r(A,b)=n-1 -1 行中,第c列的数最大的那一行(为零就continue),移到第r行上去(最上面)+1 ~n-1 行,如果第c列不是零,将第c列化为零-1  ~0,+1 ~n-1  -1 
5.dp 1 2 3 4 5 6 1. 每一个状态为一个集合,分集合思想后,求转移方程是通过大的划分出小的(不重不漏)2. 代码实现时,是从小的推导出大的,一定要保证转移方程中小的项已经求出来了。自底向上逐步填充数组3. 初始化,按题意给出有效的初始值1 ,无效为0 0   负无穷或正无穷
5.1背包问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 https:01 背包    每种只有一个             1000  *1000  * 1000  暴力tle,f[i][j]=max (f[i-1 ][j],f[i][j-v[i]] +w[i]) 用本层求出来的值优化1000 * 2000 * 2000  暴力tle,二进制优化(像快速幂),打包后1000 * 2000 * 12 转化为01 背包  100 * 100 * 100 直接暴力1. 一维数组存储,用上一层的值j就从大到小,用本层的j从小到大2. 完全背包 和 01 背包 的区别仅在于状态更新时的遍历顺序。(即01 是逆序,完全是顺序)3. 不超过容量 和 恰好装满 的区别仅在于二者的初始化~0 ;也就是全有效0 ] = 0 (第一列),其余全为负无穷,一维:除f[0 ]为0 外,其余f[j]都是负无穷)
5.2线性dp: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1. 三角形2. a最长上升子序列 f[i] 以i结尾的长度     优化:f[i]长度为i的序列结尾的数字3. a、b最长公共子序列 f[i][j] a[i],b[j]结尾的最大长度   以a[i],b[j]是否出现四次划分11  10  01  00 4. 编辑距离       f[i][j]a前i个变成b前j个的最小操作次数	 只操作i的最后一个字母(3 种)5. 最短编辑距离 		同上6. 石头合并最小代价(最优矩阵相乘)  f[i][j] i到j的代价  最后一次的分界线为分类(因为该区间最后一定是某两个区间合并出来的)7. 整数划分1.  转化为体积为1 ~n的!恰好 !为n的完全背包物体 计数问题2.  f[i][j] 总和为i,总数为j   分成有无1 两类 f[i][j]=(f[i-1 ][j-1 ]+f[i-j][j])%mod;8. 计数问题
5.3状态dp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 AcWing 291 0 表示无1 表示有1. 满足与第i-1 列不冲突     (j&k)==0 2. i-1 列剩下的格子,没有连续奇数个格子   st[ j|k ]==1 memset (f,0 ,sizeof  f);0 ][0 ]=1 ;for (int  i=1 ;i<=m;i++)     for (int  j=0 ;j< 1 <<n ;j++)      for (int  k=0 ;k< 1 <<n ;k++)     if ((j&k)==0  && st[j|k])-1 ][j];11 * 2 ^11  *2 ^11  =4  * 10 ^7 
1 2 3 4 5 6 7 acwing 91  最短hamilton哈密顿距离1 ≤n≤20  int  f[1 <<N][N];for  状态for  终点for  倒数第二个点
5.4记忆化搜索 dfs的同时,返回前记录下当前状态值。
没有上司的舞会、最长滑雪长度 零钱兑换 (需要注意什么情况下是无解 什么条件是还没求 什么时候是中止)
1 2 3 4 5 6 int  get (int  x) if (f[x])return  f[x];return  f[x];
题目 1 2 3 4 5 6 7 322 Coin Change:给定无数个定值硬币,最少数量凑出amount。x 需要的个数 me[x] , 画出求解树,存在重叠,需要记忆优化
6.贪心 0.合并区间
1.区间选点使得每个区间至少一个点。
2.安排课表,使得上的课最多[区间不重叠]
3.区间分组,组内互不相交      优先队列+左端点排序
4.选择区间覆盖指定区间。     左端点+贪心
5.排序
6.牛的最大忍耐度最小     对两头牛交换,会导致结果变大还是变小
leetcode Hot 100 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 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 1 两数之和 hash  双指针	求出每个i能够接到的雨水数 	单调栈 46 全排列 48 旋转矩阵 49.  字母异位词分组 输入vector<string > 	1.map<string,[s1,s2]> string为排序后 s1,s2为原来 	2.将字母映射一个质素double a[26]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101}; 		将string转为质素相乘,其他与1一样,少一个排序时间 		小心溢出,用long long后取余(余数要大)或者用double 	3.如果是string需要完全相同,可以字符串哈希映射为一个数字 	4.统计个数后,map<string,[s1,s2]> string是每一个元素的个数 'a:x b:y' 	后续题目:2321. 拼接数组的最大分数  或者转为状态dp  62.  【dp】或者 记忆化搜索 或者 排列组合	滑动窗口的套路 	先找到一个可行解,再优化这个可行解。 	优化到不能优化,产生出一个可能的最优解。 	继续找新的可行解,再优化这个可行解。 	直接用数组代替map映射需要的字母的数量,用额外count记下还需要匹配多少个,免去遍历过程 121.  买卖股票的最佳时机 直接求124.  二叉树中的最大路径和  注意dfs的返回值的含义!	1.暴力:计算以每一个数为开头的长度n  2.优化:只计算区间起点的长度 3.并查集  4.dp 139.  单词拆分  给wordDict 拼出s,可重复使用	1.暴力 超时  2.hash优化字符串比较+记忆化(存在重复 所以想到)  3.dp 142.  环形链表 II  并返回入环点148.  排序链表 1.归并排序 logn空间  2.O1不会152.  乘积最大子数组 dp155.  最小栈 栈,但能获取到最小值160.  相交链表 求相交点 1.hash 2.求多出来的长度 3.相互连接起来169.  多数元素 删除不同的两个数 还是多数元素198.  打家劫舍200.  岛屿数量 DFS||BFS206.  反转链表207.  课程表 图是否有环  拓扑图 ||  DFS搜索看是否回来208.  实现 Trie (前缀树) 215.  数组中的第K个最大元素221.  最大正方形  dp || 85. 直接用长方形来解答的,没用答案的正方形性质235.  二叉搜索树的最近公共祖先  非搜索树236也可也解,递归,函数返回结果为是否包含p或q238.  除自身以外数组的乘积  不用除法、On时间 On空间  前缀和240.  搜索二维矩阵 II  在搜索矩阵中高效找一个数279.  完全平方数  dp | 记忆化283.  移动零287.  寻找重复数 有一个数出现两次及以上  二分 | 位运算 | 快慢指针297.  二叉树的序列化与反序列化 解析时删除元素300.  最长递增子序列301.  删除无效的括号  可能的方案?309.  买卖股票的最佳时机含冷冻期  dp[i ][3 ]*. 戳气球 区间dp 以区间最后一个气球为准 322. 零钱兑换  dp  见分类刷题-dp 337. 打家劫舍 III  树上 记忆化搜索  可以不要考虑两种情直接求 338. 比特位计数 dp 347. 前 K 个高频元素 堆 394. 字符串解码 递归 399. 除法求值 构建为图  List<List<Pair<Integer, Double> >> edges  最外层是点,存List<Pair >  406. 根据身高重建队列 贪心排序 + 暴力找位置 or  树状数组+二分 (想知道1~x区间和,同时也会增加1,二分找区间和>=k+1的地方  logn *  logn)416.  分割等和子集 选择一些数和等于sum/2 dp 注意顺序,数组在内部循环会导致错误(元素重复使用)  零钱兑换是需要重复使用437.  路径总和 III 暴力  || 前缀和+hash438.  找到字符串中所有字母异位词 双指针448.  找到所有数组中消失的数字  交换到应该的位置 |  +n标记494.  目标和 添加正负号和为target  dfs -> 记忆化 -> dp  || 转为数组中选取元素,之和等于 target 416.538.  把二叉搜索树转换为累加树 反中序遍历543.  二叉树的直径  二叉树任意两个点的最大距离 dfs  定义好dfs的返回值的意义! 同 二叉树中的最大路径和560.  和为 K 的子数组 数量  暴力 | 前缀和 + hash(不关心具体下标,只关心等于 k 的前缀和之差出现的次数c)  遍历右维护左581.  最短无序连续子数组 将数组部分排序后有序 nlogn排序-二分  找规律技巧617.  合并二叉树 dfs621.  任务调度器 模拟题647.  回文子串 中心点暴力
1 2 3 4 5 6 7 24  两两交换链表节点  		递归26  删除有序vector 重复项     双指针	172  阶层中零的个数202  将一个数每一位求平方和,判断最后是否收敛到1   19 ->82 ->68 ->100 ->1   
卡特兰数:n对括号正确匹配数目 (22),n个节点二叉搜索树(96),出栈次序,矩阵相乘
$$
分类刷题 doocs/leetcode: 😏 LeetCode solutions 
0.找规律 
1. 基础算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 在排序数组中查找元素的第一个和最后一个位置 - 二分查找 1  的个数 - 位运算、lowbit
枚举右,维护左 哈希表维护左边全部(找等值关系) , 或者单变量维护最值(买卖股票1)  (找最值关系 )
前缀和+维护左
二分 分享丨【题单】二分算法(二分答案/最小化最大值/最大化最小值/第K小) - 力扣(LeetCode) 
2. 数据结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 设计链表 -  单链表、指针引用、数组实现I  -  单调栈-  单调栈-  单调栈-  单调栈II  -  单调栈   ×-  单调栈-  单调栈-  单调队列-  单调队列 ×K  的最短子数组 -  单调队列   尝试单调栈-> 无单调性?  前缀和+ 单调队列-  动态规划、单调队列优化II  -  哈希表、回溯 ×-  字符串哈希-  字符串哈希-  字符串哈希、二分查找-  字符串哈希
滑动窗口(双指针) 查找满足某要求的字串、子序列;解题时看到字串子序列:先考虑所有以i结尾的串,在考虑j的单调 
(此外还有一种二分的解法 nlogn:每一个right结尾时,二分寻找left的最优)
基本思想 :随着 i的增大,满足 某表达式 的j 值是单调的。
        1.我遍历了全部以i结尾的串。确保不遗漏j~i窗口是一个可行解,i往后移,j不能回头 。该单调性可以优化掉O(n^2^)
步骤 
左右指针ji控制窗口,遍历右指针i向右边移动,以获取所有的答案(理解为以i结尾的所有串) 
目前ji是可行解 
当前窗口i++,不是可行解或最优解(窗口大小,字符数量) 
j++,直到可行更新res 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 for  (int  i  =  0 ; i < n; i++) {for  (int  j  =  0 ; j < i; j++) {if  (check(i, j)) {for  (int  i  =  0 , j = 0 ; i < n; i ++ )while  (j < i && check(i, j)) j ++ ;
基础: 
长度最小且sum大于k的子数组 :j~i以i结尾长度最小且大于k,j ~ i+1 一定大于但可能不是最短; 除了双指针还可以二分j乘积小于 K 的子数组 :有多少个子串乘积后严格小于k。j~i < k,j ~ i+1不一定<k, 但j一定要往后走
拓展:字串加减乘除(不能有负数:反例 ) 满足 大于小于某个值:长度最小且sum大于k的子数组  
 
3097. 或值至少为 K 的最短子数组 II  将求和改成了或,需要记录每一位上的数量无重复字符的最长子串 :j~i以i结尾无重复且最长,j ~ i+1  不一定无重复,但j一定是往后移动缩小窗口,使得无重复
长度为 K 的无重复字符子串  的个数 多一个长度缩小 
字符串的排列 :s2是否存在的某个子串刚好包含s1全部字符  ;新加进来的不能多+直接长度判断是否满足438. 找到字符串中所有字母异位词  同上,需要找出所有的  **最小覆盖子串 **:s中字串能包含t全部字符,且最小(可以多,所以不能用长度判断 要多一个need记录)。j~i覆盖且最小,j ~ i+1  覆盖但不一定最小,但j一定是往后移动缩小窗口,使得更小;用一个变量记录下一共要多少个字符、一个hash记录每个字符需要多少(也可也直接用长为26字母遍历check) 
统计定界子数组的数目  满足最大为min 最小为max的子数组的数量
维护两个指针,i是以i结尾的子数组。 
当i不越界时,数量关系取决于最后一个min 和 max的下标 
当i越界时,两指针都跳到i+1 
 
最大连续1的个数 III :最多存在k个0,求数组连续1的个数 
相向: 
167. 两数之和 II - 输入有序数组 15. 三数之和   三数和为0,并且去重 ; 排序去重 然后双指针(有序)找值,当然也可也hash维护找(空间消耗)数组元素的目标和 两个升序数组,求和为x的两个数组的下标
一个i指针一直往大了走,说明窗口是变大的,那么j的移动方向就是使得窗口值变小 
因此推断i=0  -> n ;  j=m-1 -> 0  
if  v(i,j)>x,j一直移动变小,直到满足<=x       (v(i,j)>x,那么v(i+1,j)>x,不会遗漏) 
如果ij小于x,x变大,并且j 
 
 
 
42. 接雨水 - 力扣(LeetCode) 11. 盛最多水的容器 - 力扣(LeetCode)  
恰好型 恰好为k,拆分成>=k 以及 >=k+1
单调栈 
想知道每个元素前后第一个比它大(或小)的元素;  当新来元素很大时,前面比他小的都找到下一个更大了 于是出栈,所以栈中是一个递减的序列
pre ~ i ~ next时(两边只包含一个) i 都是最大,就可以知道所有子数组中,i 当了(k-i)(i-j)次老大
经常要求所有子数组的个数或者子数组求和,需要注意越界问题
 
求下一个更大:暴力:每个元素都可能要遍历后面的所有;优化:维护一个栈,当目前元素比栈顶更大时,栈顶就找到了下一个更大。一直pop,最后栈顶是i的前一个更大
模板1:当前元素必须进栈,进栈后影响栈内元素  可以见https://leetcode.cn/problems/sum-of-subarray-ranges/ 如果都想要严格或者非严格就再求一次 
1 2 3 4 5 6 7 8 9 10 11 12 for  (int  i  =  0 ; i < nums.length; i++) {while  (!stack.isEmpty() && nums[stack.peek()] <= nums[i]) {1  : stack.peek();  while (!stack.isEmpty()){
模板2:当前元素可能不进栈,进栈后会影响后面未进栈的。左边小的可以让后面大的都不进栈,相当于从右到左模板1的弹出过程,用的比较少,看题目具体情况(最大宽度坡)
1 2 3 4 5 for  (int  i  =  0 ; i < n; i++) {if  (stack.isEmpty() || nums[stack.peek() > nums[i]]) {
模板1从左到右和从右到左得到结果不一样 
从左到右模板2的结果和从右到左模板1并反序,栈内元素会一样 
 
例题:
下一个更大元素 I 
求所有子数组中最小值,并求和  907
	方法一 :  dp[i] 以i结尾的所有子数组,最小和:a[i]能影响到上一个比a[i]小数a[j] 单调栈 ,dp[i] = dp[j] + (i-j)*a[i]  如果这一步不dp记录下来,就会导致超时
	方法二 :每个元素 nums[i] 作为最小值出现在了多少个子数组中:pre j、next k (k-i)(i-j)。单调栈! 
	方法三 :排序后,从最小数的下标开始  Arrays.sort(B, (i, j) -> ((Integer) A[i]).compareTo(A[j]));
最大宽度坡  满足 A[i] <= A[j] 的最大 j - i 值
	需要从左往右建立一个特殊的栈,小的元素会导致后面大的元素直接不进栈,建立后从右往左找规律求结果
最多能完成排序的块 II  排序子数组使得整体有序
	1、找规律 max nums[0:i-1] < min nums[i:n-1]   和单调栈无关
	2、和排序的对比字符数
	3、单调栈
子数组范围和   
	求全部子数组(max-min),并求和,同2,但多一个最pre nextMax。
子数组最小乘积的最大值 
	求全部子数组中,min(a) * sum(a)最大的。mina 还是用单调栈,注意这里是越宽越好,所以pre next都用非严格的两次去求(单次同时求出非严格需要跳跃 代码 )。(其实一个非严格一个严格也可以,因为第一个元素的nextBig会包裹相同元素,1111相当于以第一个1为代表)。
柱状图中最大的矩形   模板
 
单调队列 	为什么要引入队列?有的题目前后都要弹出!!元素满足单调性
理解:通过单调性来移出不优的元素,例如在“和至少为 K 的最短子数组”,对于每个点作为左节点j,如果来了一个新值j‘,并且更小,那么左边的旧值j就永远不需要看了;作为左节点j如果j~i满足条件,那么右边的j~i'就不用再看了
可以获取最值的队列   可以在队尾插入,队头取出;并且O1最大值
维护全局一个最大值,但该元素出队列后找不到下一个最大值  × 
维护有效最大值列表,使得最大出列后还能找到第二大:
有效的特点:当队尾来了一个很大的数,最大值列表中的小值都可以被忽略了,所以是一个递减的 
队头也可也删除元素,当普通队列的元素和最大值队列队头相同时,最大值队列需要出队 
 
 
类似题目:可以获取最小值的栈   同样是维护一个最小值列表 
 
[滑动窗口最大值](https://github.com/doocs/leetcode/blob/main/solution/0200-0299/0239.Sliding  Window Maximum/README.md)
	求滑动窗口的最大值,当新来元素很大时,前面比他小的都可以忽略了,所以窗口中是一个递减的序列,最大值就在peekLast  (此外还可以维护一个优先队列(堆) O(logn)取出最值)
1 2 3 4 5 6 7 8 9 10 11 12 while (deque.peekLast().index<i-k+1 ){while (!deque.isEmpty() && deque.peekFirst().value<nums[i]){new  Pair (nums[i], i));
和至少为 K 的最短子数组  :如果全是正数,那么用双指针可以把空间复杂度也降低到O1
使用前缀和的差来计算子数组的和;
使用一种数据结构,将当前前缀和i与最前面的前缀和j作差,如果满足>=k的条件,那么j在之后就可以不用看了。【因为即使后面也有满足条件的,长度也会更长,所以需要将j从前面弹出】;
第2步完成了之后,当前的i也要放入数据结构,那么如果数据结构中有前缀和j比前缀和i大,j也可以不用看了。【因为即使后面有满足条件的,与i作差肯定也满足条件,并且长度更短,所以需要将大于等于i的从后面弹出】。
	前面后面都要弹出,压入的元素满足单调性,所以使用单调队列。
数组存在负数,所以不能用二分查找i结尾的起点在哪。
第二次做:其实是找满足条件的j~i,不能暴力遍历就找规律:对于左起点j以及右终点i,如果j~i当前满足了,那j不需要再去找后面的i了;i以后作为左起点 对于左起点,如果当前比左边的数小,那么左边的数都不可能再作为左起点了
 
带限制的子序列和   求max ( sum[子序列] ) , 且子序列中的最大下标间隔不能超过k
dp[i] 代表以i结尾的子序列的最大和,保证不遗漏dp[i] = max(dp [i-k] ~ dp[i-1])  如果遍历k的话,超时;分析目前是想得到前k个元素的最大值维护一个窗口大小为k的单调队列! 
 
树状数组 
3.搜索 BFS:有很多结果,需要最优的,所以需要BFS同时尝试所有的,并且最先遇到的就是最优解
BFS BFS :状态转换 + 最短路/最小步数/最小操作 ;空间范围有限
有一个全局的visit代表是否访问过,用来保证不走回头路 ,可以用距离(使用距离的话,入队的点就不用额外保存距离) 
入队时 标记而不是出队时标记,防止同一个节点入队两次尽量只将有意义的节点入队,同时入队时判断结束条件最好:转化数字的最小运算数  
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 grid[n-1 ][m-1 ] = -1 ; new  int []{n-1 , m-1 , 1 });while (!deque.isEmpty()){int [] tmp = deque.pollFirst();int  x  =  tmp[0 ], y = tmp[1 ], dis = tmp[2 ];if (x==0 &&y==0 )return  dis; int  []dx = {-1 ,0 ,1 ,0 ,-1 ,-1 , 1 , 1 };int  []dy = {0 ,1 ,0 ,-1 ,-1 ,1 ,1 ,-1 };for (int  i=0 ;i<8 ;i++){int  xx  =  x + dx[i];int  yy  =  y + dy[i];if (xx>=0 &&yy>=0 &&xx<n&&yy<m&&grid[xx][yy]==0 ){ 1 ; new  int []{xx, yy, dis+1 });
优化:双向DFS ,可以节约百倍空间复杂度[模板](1091. 二进制矩阵中的最短路径 - 力扣(LeetCode) ) 模板code   
1+2+4+8+16+32+64+128+256+512  ->    1+2+4+8+16  + (相遇)  16+8+4+2+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 35 36 37 queue1、queue2 为两个方向的队列while (!deque1.isEmpty() && !deque2.isEmpty()){int  res=-1 ;if (deque1.size()<deque2.size()){else {if (res!=-1 )return  res;return -1 public  int  update (Deque<Integer> deque, int  []dis, int  []other) {int  num  =  deque.size();while (num-- != 0 ){int  x  =  deque.pollFirst();if (other[x] != -1 ){return  dis[x] + other[x];for (int  i=0 ;i<4 ;i++){if (...&&dis[now]==-1 ){return  -1 ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 图像渲染- BFS、DFS、Flood  Fill 算法、连通性模型BFS、DFS、Flood  Fill 算法01  矩阵 - 多源 BFS     					 注意只能多源BFS!!DFS实现失败    还可以dp,但没看BFS  和上面一模一样BFS、最短路模型    			暴力或者双向BFS BFS、最短路模型      模板  如果入队没有标记而是出队标记 会超时BFS、最短路模型      模板BFS、最短路模型            从左上到右下最短路,可以删除k个障碍;这里即便访问过,后来的也可也访问:入队额外记录剩余删除个数,如果比全局的更多(后来的距离一定更大,想要继续走就必须要剩余更多删除),就可以入队BFS、A*  IDA*  四位旋转到目标数  看上去题目复杂,但搜索空间只有1 e5 直接暴力 dfs第一次遇到的是最优的,set去重。  双向BFS优化模板 BFS    同上 仔细分析搜索空间其实不是特别大  最多也就5 k个词BFS BFS、最小步数模型、A*  算法BFS、最小步数模型、A*  算法BFS、A*  算法BFS BFS 
DFS 		进入时修改状态,回溯后改回状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public  boolean  dfs (int  []matchsticks, int  target, int  idx, int  now, int  start) {if (idx == 4 ) return  true ;if (now == target) return  dfs(matchsticks, target, idx+1 , 0 , 0 );for (int  i=start;i<matchsticks.length;i++){if (i>start && matchsticks[i] == matchsticks[i-1 ]) continue ;if (matchsticks[i]==-1 )continue ;if (matchsticks[i] + now > target) continue ;int  t  =  matchsticks[i];1 ;if  (dfs(matchsticks, target, idx, now + t, i+1 )){return  true ;return  false ;
记忆化搜索 : 自顶向下,并记录。可以改写dp
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 迷宫 - DFS、连通性模型、Flood Fill 算法15           数组组合成一个正方形,暴力遍历 1. 从组成边的角度出发:(2 ^n)^k used标记 在组成某一条边时,是组合问题而不是排列!所以需要起始idx2. 从使用每一个数字出发: 每个数字可以放4 个位置 4 ^n 剪枝:优先遍历大的数字,这样遍历空间更小 edge一样剪纸3. 状态压缩 + 动态规划16     同上, 数组从划分4 个子数组改为k个1. 从组成子集角度 n!   对于每一个子集,是组合问题,需要使用begin 控制搜索2. 从使用每一个数字:k^n   超时!  加上edge[i+1 ] == edge[i]优化可以过8    数组划分为k个,求划分使得最小化:max(sum(子数组)) 同下1. 无法从组成子集角度 2. 分配每一个数字即可, n=8 不需要剪枝12    数组划分为k个,求划分使得最小化:max(sum(子数组))1. 从分配每一个任务角度:k^n   超时!   剪枝:维护一个全局最优  降序排列0 号工人,其实是最差情况,我们希望分配平均一点1 :先用优先队列贪心求取出一个res,缩小dfs范围2 :edge[i+1 ] == edge[i]时 二者等价  直接跳过3.  优先分配给未分配的工人(和2 一样); 因为未分配都一样,所以就分配给未分配的第一个人1 4.  对分配排序,每次取出最小的:问题1  直接排序下一层影响这一层,使用idx2  每次都排序复杂度太高了,排序行不通2.  状态压缩 dp3.  二分+转为存在问题add  回溯时removebegin   如果可重复begin 下次还是i 不可重复i+1 40.  组合总和 II] 需要去重  set 直接去重超时  改成排序后一次处理同一种数begin 标记0 , i) + "--" + state.substring(i + 2 );if  ((mask & (1  << i)) == 0  || (mask & (1  << (i + 1 ))) == 0 ) {continue ;if  (dfs(mask ^ (1  << i) ^ (1  << (i + 1 )))) {continue ;cost (i->j))1  当前立即结束代表一种解法
4.DP dp有两种
分享丨【题单】动态规划(入门/背包/状态机/划分/区间/状压/数位/树形/数据结构优化) - 力扣(LeetCode) 
01背包
dp:
记忆化:当前物品有选和不选两种情况
1 2 dfs(now+nums[idx], idx+1 );1 );
 
完全背包
dp:
从使用每一个物品出发,x个	dp[i][j] = 最优 dp[i-1][j-x * [w]]  -> 优化为当前层取值   ->  优化为1维 
从组成的数出发,可重复使用的物品循环在内。 组合总和 Ⅳ   ; 零钱兑换   完全平方数  dp只有一维(不考虑顺序可从完全背包出发, 但组合总数考虑了所有不可以) 
 
记忆化:
1 2 3 4 5 6 7 8 从使用每一个物品出发  选了但idx不变1 , target);for  i in n:
 
 
找到所有可能的解决方案一般要dfs 39. 组合总和 
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 杨辉三角 - 线性 DP、数字三角形模型
1 2 139 . 单词拆分 使用给定String []  拼接出s ,能不能拼接出来2707 . 字符串中的额外字符 同上,但目标是剩余字符最少  dp [i] : 0 ~i   暴力- >trie  || 字符串hash 优化查找字符串
入门 
网格 
背包 01背包 一些物品 选或者不选;dp[i][j] = Math.max(dp[i][j], dp[i-1][j-nums[i-1]]); -> 优化为逆序1维
目标和   选或者不选,凑出目标的方案数 dfs(target, nums.length-1);  前idx个数 凑出的方案分割等和子集    能否凑出  dfs(target, nums.length-1);  前idx个数 能否凑出target和为目标值的最长子序列的长度   凑出的最大长度 dfs(target, nums.length-1);  前idx个数 凑出target最大长度将一个数字表示成幂的和的方案数   dfs(target, nums.length-1) 前idx个数 凑出target的方案 
1 2 3 4 5 6 7 8 9 取或者不取1 , target);1 , target-nums.get(idx));1   1   无效0 0  无效inf1  无效0 
完全背包 
从使用每一个物品出发,x个	dp[i][j] = 最优 dp[i-1][j-x * [w]]  -> 优化为当前层取值   ->  优化为1维 
从组成的数出发,可重复使用的物品循环在内。 
 
不考虑顺序,完全背包
考虑顺序,从使用的最后一个数出发, 因为考虑顺序,不可以用完全背包解决
线性dp 
记忆化从idx=0或者n-1开始记忆化都可以,本质都一样(只是看数组的方向变了) 
当前结束状态 和当前进入状态  有较大区别,1比较通用,2有时比较好理解
当前结束状态(通常n-1开始) 更方便改写dp(前缀和优化 3251  、空间优化、二分优化) ,需要逆向思考状态转移方程  
进入状态(0开始 ) 更方便理解 (最长子序列、3098  、1955. 012数组个数 ) ,从最初始的状态 按照题目逻辑  转移到后续状态 
 
 
相邻相关 最长上升子序列  需要多保存一个相邻关系(可以理解为状态)
300. 最长递增子序列 - 力扣(LeetCode) 
暴力记忆化时 dfs(idx, pre),用hashmap会超时  在使用idx~n-1,且pre情况下的最长长度 
或者dfs(idx, next) 在使用0~idx, 且next情况下的最长长度 
 
3098. 求出所有子序列的能量和 - 力扣(LeetCode)   https://www.bilibili.com/video/BV19t421g7Pd 
dfs(idx, pre, minnow, lennow)  n * n * n^2 * k  在使用0~idx,且pre情况下的值选或不选、枚举选哪个 都可以 
 
3251. 单调数组对的数目 II - 力扣(LeetCode)  拆分成一个递增一个递减数组  bili 
需要保存上一个数是什么,有点像状态dp  dfs(idx, pre1) 在下一个数取了pre1的情况下, 0~idx的方案数 
不好改写dp,换第二种记忆化(当前值)dfs(idx, j) 在idx取了j的情况下 0~idx的方案数 
改写成dp后,前缀和优化 
 
1955. 012数组个数  
dfs(nums, 0, 0)  从idx=0开始,初始state=0表示不包含0, 从初始状态转移到其他状态dfs(nums, n-1, 2)   前n-1个数,且由(state=0 0;state=1 01; state=2 012)组成的子序列   更难获得状态转移方程 
一个字符串,每次可以将char+1,求最小 操作使得任意两个相邻的字符不同 
 
 
dp[i][j] 为s[:i] 和t[:j] 的结果, 转移考虑最后一个i j
状态dp 
题单刷 分享|如何科学刷题? - 力扣(LeetCode) 
分享丨【题单】常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树) - 力扣(LeetCode) 
其他 周赛补题 3097. 或值至少为 K 的最短子数组 II :长度最小且或后大于k的子数组;和求sum很相似
暴力 :以每一个i结尾,往前找left超时;双指针优化 ,但和求和不同,求和left移动直接减就行,这里需要记录下每一位1的个数还是暴力每一个结尾i,但以i结尾能筹出来的或最多32个,也就是i -> i+1的转换为O32的,而不是On的,这和求和不同 
 
3098. 求出所有子序列的能量和 :
子序列
相邻无关 01背包 
相邻相关 最长上升子序列  多一个相邻关系 
 
 
排序后,dfs  -> 记忆化优化  50^5  但有一些系数的减小(变量有大小关系,相当于在求组合数,组合数下面有阶乘系数) 
 
1 2 3 4 5 6 7 8 9 10 给你一个长度为 n  < 50  的整数数组 nums 和一个 正 整数 k 。109  + 7  取余 后返回。n  * n  * n ^2  * k
100244. 带权图里旅途的最小代价 : 图中两点距离为 & 值,求x y之间的最小距离,可以重复访问
and是越多越小 ,所以尽量访问x和y之间全部的边,因此一个连通块为一个结果并查集 or  dfs 
初始化数组太大会超时,n或者只大max一点 
 
3290. 最高乘法得分 - 力扣(LeetCode) 
dp,注意溢出,key选择string可以,List作为key会超时 
 
3291. 形成目标字符串需要的最少字符串数 I - 力扣(LeetCode) 
记忆化搜索 dfs(i)代表从i~n-1需要多少个字符,但由于每次需要尝试n次,n^2复杂度 
改成跳跃游戏;如何求每一个idx可以到达的最长长度? 二分长度 + 字符串hash(字符串普通hash是On的,字符串hash是O1比较) 
 
Q4. 单调数组对的数目 II :将数组nums[] 拆分成一个递增的 一个递减的
经典子序列题,记忆化解决  可以从当前状态可以推出哪些,也可也从转移,转移才可以改写dp优化 
改写成dp后,前缀和优化 
 
3307. 找出第 K 个字符 II - 力扣(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 26 27 28 29 30 31 32 33 public  static  void  quickSort_no_Recursion (int [] q) {new  ArrayDeque <Integer>();0 );1 );int  l, r, x, i, j;while (st.size() != 0 ){if (l>=r) continue ;1 ];1 ; j = r+1 ;while (i<j){while (q[i] < x) i++;while (q[j] > x) j--;if (i<j){int  t  =  q[i];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 public  List<Integer> inorderTraversal (TreeNode root)  {new  ArrayList <>();new  ArrayDeque <Node>();new  Node (root, 0 ));while (deque.size() > 0 ){Node  tmp  =  deque.pollLast();TreeNode  r  =  tmp.root;int  flag  =  tmp.flag;if (r == null )continue ;if (flag == 0 ){new  Node (r.right, 0 ));new  Node (r, 1 )); new  Node (r.left, 0 ));else {return  res;
补充 https://leetcode.cn/problems/climbing-stairs/solutions/286022/pa-lou-ti-by-leetcode-solution/ : 矩阵快速幂
马拉车 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 public  String longestPalindrome (String s)  {StringBuilder  sb  =  new  StringBuilder ();'#' );for  (int  i  =  0 ; i < s.length(); i++) {'#' );String  str  =  sb.toString();int  n  =  str.length();int [] p = new  int [n]; int  center  =  0 , right = 0 ; int  maxLen  =  0 ; int  maxCenter  =  0 ; for  (int  i  =  0 ; i < n; i++) {if  (i < right) {2  * center - i]);while  (i - p[i] - 1  >= 0  && i + p[i] + 1  < n && str.charAt(i - p[i] - 1 ) == str.charAt(i + p[i] + 1 )) {if  (i + p[i] > right) {if  (p[i] > maxLen) {int  start  =  (maxCenter - maxLen) / 2 ; return  s.substring(start, start + maxLen);
操作 快读 1 2 3 4 5 6 7 8 9 10 BufferedReader  br  =  new  BufferedReader (new  InputStreamReader (System.in));" " );0 ]);1 ]);PrintWriter  out  =  new  PrintWriter (new  OutputStreamWriter (System.out));
stringbuilder删除的时候,从后往前删防止影响,传入整数自动转为字符串
new Integer('0')  得到的是ASCII, char可以自动转integer,反过来不行 new Character((char) 48)
集合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 set去重int []是比较地址char 加上一个int 需要强转 char  c  =  (char )('a'  + j);int ,里面默认值为0 ,如果是object,还需要额外new   left [0 ][1 ] = new  Person ();int [][] left = new  int [26 ][2 ];  0 ].length);  int [][] left = new  int [26 ][]; 0 ]);  0 ] = new  int [2 ];  1 ] = new  int [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 数组    .length  int [][] arr = new  int [3 ][]; new  ArrayList <>(Arrays.asList(ints))new  int [n][]) int [] 转List :  new 在堆中完成,返回堆内存地址32 位)引用空间 栈中,由于未赋值,因此并不指向任何对象new  Object [10 ]  堆中创建一个长度为10 的Object 类型数组对象(也是对象),分配了10 个连续的内存空间,各自占用(32 位的)Object用空间,但现在默认指向null new  Object () 在堆中为 Object 类型的对象分配一段内存空间,将引用存储在arr[i]
排序 重写排序,Array.sorts(a) 排序数组同理。注意int不能重写,需要逆序就先排序再反转
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 ArrayList:0 ] - b[0 ]);  new  Comparator <Person>() {@Override public  int  compare (Person o1, Person o2)  {return  o1.getAge() - o2.getAge();class  MyClass  implements  Comparable <MyClass>{public  int  compareTo (MyClass other)  {return  Integer.compare(this .priority, other.priority);new  Comparator <Person>() {@Override public  int  compare (Person o1, Person o2)  {return  o1.getAge() - o2.getAge();
stack 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 栈 底层数组,适合两端插入删除;LinkedList是链表new  ArrayDeque <Integer>();  或 Stackfor  (Map.Entry<Integer, Integer> entry : cnt.entrySet()) map.forEach((key, value) -> sout)new  PriorityQueue <>(); 3 ); add()
1 2 3 4 5 6 7 8 9 10 static  class  console  {public  static  <T> void  log (T... input) {StringBuilder  sb  =  new  StringBuilder ();for  (T i: input) {' ' );
重写hashmap 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class  Node {int  state;public  Node (TreeNode t, int  state) {this .t = t;this .state = state;public  boolean  equals (Object o)  {Node  x  =  (Node) o;return  t == x.t && state == x.state;public  int  hashCode ()  {return  Objects.hash(t, state);  
randomfile 1 2 3 4 5 6 7 8 9 10 randomAccess = new  RandomAccessFile (filepath, "rw" );byte [] buffer = ...new  RandomAccessFile (filepath, "r" );byte [] buffer = new  byte [pageSize]return  new  HeapPage ((HeapPageId) pid, buffer);