一个数据结构与算法可视化网站:https://visualgo.net/zh
Kruskal(并查集实现) 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 #define MAXV 1000 //最大点数 #define MAXE 1000 //最大边数 #include <iostream> #include <algorithm> using namespace std; struct Edge //定义边 { int s; int t; int d; } E[MAXE]; bool cmp(Edge& a,Edge& b) { return a.d<b.d; } int find(int x) { while(x!=fa[x]) //路径压缩 { fa[x]=fa[fa[x]]; x=fa[x]; } return x; } void Union(int x,int y) { fa[x]=y; } int main() { int countE,countV,fa[MAXV]; int r[MAXV]={0}; //r用来统计频率 int count=0; //统计加入的边数 int ans=0; /* 输入,存储边 */ sort(E,E+countE,cmp); for(int i=0;i<countV;i++) fa[i]=i; //初始化并查集 for(int i=0;i<countE;i++) { int root_s=find(E[i].s),root_t=find(E[i].t); if(root_s!=root_t) { //Union(root_s,root_t); if(r[root_s]<r[root_t]) { r[root_t]++; fa[root_s]=root_t; } else { r[root_s]++; fa[root_t]=root_s; } count++; ans+=E[i].d; } if(count==countV-1) break; } cout<<ans<<endl; return 0; }
Prim 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 #define MAXV 1000 //最大点数 #define MAXE 1000 //最大边数 #define MAXL 0x3fffffff #include <iostream> #include <iomanip> #include <algorithm> using namespace std; int main() { int d[MAXV],vis[MAXV]={0},G[MAXV][MAXV]; int ans=0; /* 输入,存储边 */ fill(d,d+MAXV,MAXL); for(int i=0;i<MAXV;i++) { int t=-1; for(int j=0;j<MAXV;j++) //寻找加入的点 { if(!vis[j]&&(t==-1||d[t]>d[j])) t=j; } if(i) ans+=d[t]; vis[t]=1; for(int j=0;j<MAXV;j++) //更新距离 { if(!vis[j]&&d[j]>G[t][j]) d[j]=G[t][j]; } } 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 24 25 26 27 28 29 30 31 32 33 34 #define MAXV 1000 #include <iostream> #include <vector> #include <queue> using namespace std; int main() { int count=0; //统计标记的点数 int indgree[MAXV]={0}; //统计每个点的入度和 vector<int> vec[MAXV]; //存储边 (点与点的后继) vector<int> d; //记录路径 queue<int> q; //存储入度为0的点 //priority_queue<int> q; /* 存储边、入度 */ while(!q.empty()) { int cur=q.front(); d.push_back(cur); count++; q.pop(); for(int i=0;i<vec[cur].size();i++) { indgree[vec[cur][i]]--; if(indgree[vec[cur][i]]==0) q.push(indgree[vec[cur][i]]); } } if(count==MAXV) cout<<"Yes"<<endl; else cout<<"No"<<endl; return 0; }
判断回文子串 中心扩散法,时间复杂度O(n2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #define MAXL 10005 #include <iostream> #include <cstring> using namespace std; int main() { string s; cin>>s; int len=s.length(); int dp[MAXL][MAXL]={0}; for(int i=0;i<len;i++) { //每个点向两边扩散 for(int j=i,k=i;j>=0&&k<len&&s[j]==s[k];--j,++k) dp[j][k]=1; for(int j=i,k=i+1;j>=0&&k<len&&s[j]==s[k];--j,++k) dp[j][k]=1; } return 0; }
Manacher算法,马拉车算法,时间复杂度O(n)
,思路:
在字符串两端和每两个字符之间填充#
,字符串长度变为2n+1
,始终为奇数
当在位置 i 开始进行中心拓展时,可以先找到i
关于j
的对称点 2 * j - i
。那么如果点2 * j - i
的臂长等于 n
,就可以知道,点i
的臂长至少为 min(j + length - i, n)
。那么就可以直接跳过i
到i + min(j + length - i, n)
这部分,从 i + min(j + length - i, n) + 1
开始拓展。
最长回文子序列 (动态规划)
最长上升子序列(LIS) 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 <cmath> #include <vector> using namespace std; //暴力求解 O(n^2) int maxIncSub(vector<int>& vec) { int ans=0; vector<int> dp(vec.size(),1); for(int i=1;i<vec.size();i++) { for(int j=i-1;j>=0;j--) { if(vec[i]>vec[j]) dp[i]=max(dp[i],dp[j]+1); } ans=max(ans,dp[i]); } return ans; } //二分求解 O(nlogn) int LIS(vector<int>& vec) { int cur=0; vector<int> dp(vec.size(),0); //记录当前长度最后一个数字的数值,升序 dp[cur]=vec[0]; for(int i=1;i<vec.size();i++) { if(vec[i]>dp[cur]) { cur++; dp[cur]=vec[i]; } else //替换掉dp中第一个大于等于vec[i]的数值,用二分查找该下标 { int l=0,r=cur,mid; while(l<r) { mid=l+(r-l)/2; if(dp[mid]>=vec[i]) r=mid; else l=mid+1; } dp[l]=vec[i]; } } return cur+1; } int main() { 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 public class Solution { /** * retrun the longest increasing subsequence * @param arr int整型一维数组 the array * @return int整型一维数组 */ public int[] LIS (int[] arr) { // write code here int len = arr.length; if(len==0) return arr; int[] dp = new int[len]; int[] nums = new int[len]; dp[0] = arr[0]; nums[0] = 1; // 下标为0的数截止的最长上升子序列的长度是1 int cur = 0; for(int i=1;i<len;i++){ if(arr[i]>dp[cur]){ cur++; dp[cur] = arr[i]; nums[i] = cur+1; } else{ int l=0,r=cur; while(l<r){ int mid = l+(r-l)/2; if(dp[mid]>=arr[i]) r=mid; else l=mid+1; } dp[l] = arr[i]; nums[i] = l+1; } } int[] ans = new int[cur+1]; for(int i=nums.length-1;i>=0;i--){ if(nums[i]==cur+1){ ans[cur] = arr[i]; cur--; } } return ans; } }
树形dp 在树中选取若干节点,其中两两节点间不能相连,求最大值。(打家劫舍Ⅲ)
+ max(dp[i.right][0],dp[i.right][1]).\end{cases})
其中 dp[i][0]
表示不选取 i
节点,dp[i][1]
表示选取 i
节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { int[] dfs(TreeNode root){ if(root==null) return new int[]{0,0}; int[] tmp = new int[2]; var l = dfs(root.left); var r = dfs(root.right); tmp[0] = Math.max(l[0],l[1]) + Math.max(r[0],r[1]); tmp[1] = root.val + l[0] + r[0]; return tmp; } public int rob(TreeNode root) { var res = dfs(root); return Math.max(res[0],res[1]); } }
求所有集合的子集 例如给定1 2 3
,则可能的集合为{}、{1}、{1,2}、 {1,2,3}、{1,3}、{2}、{2,3}、{3}
。
dfs法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution{ public: vector<vector<int>> res; void dfs(int pos,vector<int> nums,vector<int> cur){ if(pos==nums.size()){ res.push_back(cur); return; } //选取 cur.push_back(nums[pos]); dfs(pos+1,nums,cur); cur.pop_back(); //不选取 dfs(pos+1,nums,cur); } vector<vector<int>> subsetGet(vector<int>& nums){ vector<int> cur; dfs(0,nums,cur); return res; } }
二进制法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public: vector<vector<int>> subsetGet(vector<int>& nums) { int len=nums.size(); if(len==0) return res; vector<vector<int>> ans; //开辟二维数组 int all_set = 1 << nums.size(); //所有的可能数 +1 for(int i = 0; i < all_set; i++){ vector<int> item; //开辟一维数组 int cur=0; for(int j = 0; j < nums.size(); j++){ if(i & (1 << j)){ //某位置元素是否存在的条件 item.push_back(nums[j]); } } ans.push_back(item); } return ans; } };
全排列 例如给定1 2 3
,输出{1 2 3}、{1 3 2}、{2 1 3}、{2 3 1}、{3 1 2}、{3 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 class Solution { public: vector<vector<int>> ans; void swap(int from,int to,vector<int> cur) { if(from==to) ans.push_back(cur); else { for(int i=from;i<=to;i++) { int ok=1; for(int j=from;j<i;j++){ if(cur[j]==cur[i]){ ok=0; break; } } if(ok==0) continue; //去重,按照任意顺序输出 int tmp=cur[i]; cur[i]=cur[from]; cur[from]=tmp; swap(from+1,to,cur); cur[from]=cur[i]; cur[i]=tmp; } } } vector<vector<int>> permute(vector<int>& nums) { int len=nums.size(); if(!len) return ans; //sort(nums.begin(),nums.end()); swap(0,len-1,nums); return ans; } };
含有重复数字的全排列
按照字典序输出结果。
方法一
count计数法,使用一个哈希表记录每个数字出现的次数,然后深度优先遍历该哈希表。
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 class Solution { List<List<Integer>> ans = new ArrayList <>(); void dfs (int [] nums,List<Integer> cur,Map<Integer,Integer> map,int len) { if (cur.size()==len) { ans.add(new ArrayList (cur)); return ; } else { for (Map.Entry<Integer,Integer> it:map.entrySet()) { if (it.getValue()>0 ) { map.put(it.getKey(),map.get(it.getKey())-1 ); cur.add(it.getKey()); dfs(nums,cur,map,len); cur.remove(cur.size()-1 ); map.put(it.getKey(),map.get(it.getKey())+1 ); } } } } public List<List<Integer>> permuteUnique (int [] nums) { int len = nums.length; if (len==0 ) return ans; Map<Integer,Integer> map = new HashMap <>(); Arrays.sort(nums); for (int i=0 ;i<len;i++) { map.put(nums[i],map.getOrDefault(nums[i],0 )+1 ); } dfs(nums,new ArrayList <>(),map,len); return ans; } }
方法二
先排序,对于相同的数字,保证每次都是拿从左往右第一个未被填过的数字。
时间复杂度O(n × 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 class Solution { int [] vis; void dfs (int [] nums,List<Integer> list,int cur,List<List<Integer> > ans) { if (cur == nums.length) { ans.add(new ArrayList (list)); return ; } else { for (int i=0 ;i<nums.length;i++) { if (vis[i]==1 ||(i>0 &&nums[i]==nums[i-1 ]&&vis[i-1 ]==0 )) continue ; list.add(nums[i]); vis[i]=1 ; dfs(nums,list,cur+1 ,ans); list.remove(list.size()-1 ); vis[i]=0 ; } } } public List<List<Integer>> permuteUnique (int [] nums) { List<List<Integer> > ans = new ArrayList <>(); int len = nums.length; if (len==0 ) return ans; Arrays.sort(nums); vis = new int [len]; Arrays.fill(vis,0 ); dfs(nums,new ArrayList <>(),0 ,ans); return ans; } }
0-1背包 有 N
件物品和一个容量是 V
的背包。每件物品只能使用一次。
第 i
件物品的体积是 vi
,价值是 wi
。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
一般解法
dp[i][j]
表示前 i
件物品体积为 j
的最大价值,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Main { public static void main (String[] args) { Scanner scan = new Scanner (System.in); int N,V; int v,w; int [][] dp = new int [1001 ][1001 ]; N=scan.nextInt(); V=scan.nextInt(); for (int i=1 ;i<=N;i++){ v=scan.nextInt(); w=scan.nextInt(); for (int j=V;j>=0 ;j--){ if (j<v) dp[i][j]=dp[i-1 ][j]; else dp[i][j]=Math.max(dp[i-1 ][j],dp[i-1 ][j-v]+w); } } System.out.println(dp[N][V]); } }
一维优化
容量要逆序枚举。
1 2 3 4 5 6 7 for (int i=1 ;i<=N;i++){ v=scan.nextInt(); w=scan.nextInt(); for (int j=V;j>=v;j--){ dp[j]=Math.max(dp[j],dp[j-v]+w); } }
多重背包 第 i
种物品最多有 si
件 。
一般解法
二进制优化
如果同一种物品的数量有很多,算法的复杂度过高。
将相同种类的多个物品重组,转化为0-1背包问题。
例如一种物品有11件,可以分解为11=1+2+4+4。
一种物品11件 → 四种物品各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 38 class Main { public static void main (String[] args) { Scanner scan = new Scanner (System.in); int N,V; int v,w,s; int cnt = 0 ; int [] volume = new int [12001 ]; int [] worth = new int [12001 ]; int [] dp = new int [2001 ]; N=scan.nextInt(); V=scan.nextInt(); for (int i=1 ;i<=N;i++){ v=scan.nextInt(); w=scan.nextInt(); s=scan.nextInt(); int k = 1 ; while (k<=s){ cnt++; volume[cnt]=v*k; worth[cnt]=w*k; s-=k; k*=2 ; } if (s>0 ){ cnt++; volume[cnt]=v*s; worth[cnt]=w*s; } } for (int i=1 ;i<=cnt;i++){ for (int j=V;j>=volume[i];j--){ dp[j]=Math.max(dp[j],dp[j-volume[i]]+worth[i]); } } System.out.println(dp[V]); } }
单调队列优化
以后补上。
完全背包 每种物品有无限件可用。
一般解法
一维优化
容量要顺序枚举。
更多种背包问题见《背包九讲》
快速幂
1 2 3 4 5 6 7 8 9 10 11 12 13 int pow_mod (int a,int n,int m) { long ans = 1L ; while (n>0 ){ if ((n&1 )==1 ){ ans*=a; ans%=m; } a*=a; a%=m; n>>=1 ; } return (int )ans; }
快排 递归:
1 2 3 4 5 6 7 8 9 10 11 12 13 void quicksort (int [] arr,int left,int right) { if (left >= right) return ; int tmp = arr[left],l = left,r = right; while (l<r){ while (l<r && tmp<=arr[r]) r--; arr[l] = arr[r]; while (l<r && tmp>=arr[l]) l++; arr[r] = arr[l]; } arr[l] = tmp; quicksort(arr,left,l-1 ); quicksort(arr,l+1 ,right); }
非递归:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void quickSort (int [] arr,int left,int right) { Deque<Integer> s = new ArrayDeque <>(); s.push(left); s.push(right); while (!s.isEmpty()) { int rindex = s.poll(),lindex = s.poll(); int l = lindex,r = rindex; int tmp = arr[l]; while (l<r&&arr[r]>=tmp) r--; arr[l] = arr[r]; while (l<r&&arr[l]<=tmp) l++; arr[r] = arr[l]; arr[l] = tmp; if (l-1 >lindex) { s.push(lindex); s.push(l-1 ); } if (l+1 <rindex) { s.push(l+1 ); s.push(rindex); } } }
快排实现稳定排序:
快排是不稳定的,可以使用一个辅助数组,从前往后把小于基准数的放进辅助数组,再把基准数放进去,然后再遍历一遍把大于基准数的放进辅助数组,避免交换操作就可以稳定了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void quicksort (int [] arr,int left,int right) { if (left>=right) return ; int len = right-left+1 ; int [] tmp = new int [len]; int target = arr[left]; int cnt = 0 ,mid = 0 ; for (int i=left+1 ;i<=right;i++){ if (arr[i]<target) tmp[cnt++] = arr[i]; } tmp[cnt++] = target; mid = left+cnt-1 ; for (int i=left+1 ;i<=right;i++){ if (arr[i]>=target) tmp[cnt++] = arr[i]; } for (int i=left;i<=right;i++){ arr[i] = tmp[i-left]; } quicksort(arr,left,mid-1 ); quicksort(arr,mid+1 ,right); }
归并排序 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 void mergSort (int [] arr,int start,int end) { if (start >= end) return ; int mid = (start + end)/2 ; mergSort(arr,start,mid); mergSort(arr,mid+1 ,end); merge(arr,start,mid,end); } void merge (int []arr,int start,int mid,int end) { int [] tmp = new int [end-start+1 ]; int i = start,j = mid + 1 ; int count = 0 ; while (i<=mid && j<=end) { if (arr[i]<=arr[j]) { tmp[count++] = arr[i++]; } else { tmp[count++] = arr[j++]; } } while (i<=mid) { tmp[count++] = arr[i++]; } while (j<=end) { tmp[count++] = arr[j++]; } for (int k=0 ;k<count;k++) { arr[start+k] = tmp[k]; } }
堆排 堆排序是一种不稳定的排序,平均时间复杂度和最坏时间复杂度都是O(nlogn)
。
小顶堆的实现:
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 void adjust (int [] nums,int i,int len) { int j = 2 *i+1 ; while (j<len){ if (j+1 <len && nums[j+1 ]>nums[j]) j++; if (nums[j]<nums[i]) break ; else { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; i=j; j=2 *i+1 ; } } } void HeapSort (int [] nums) { int len = nums.length; for (int i=len/2 -1 ;i>=0 ;i--) adjust(nums,i,len); for (int i=len-1 ;i>=1 ;i--){ int tmp = nums[0 ]; nums[0 ] = nums[i]; nums[i] = tmp; adjust(nums,0 ,i); } } public static void main (String[] args) { int [] arr = {4 ,1 ,6 ,2 ,5 ,3 ,7 }; HeapSort(arr); }
差分 将区间 [l,r]
整体增加一个值 v
。
dif[l] += v
:对于所有下标大于等于 l
的位置都增加了 v
;
dif[r+1] -= v
:对下标大于 r
的位置减少 v
,抵消影响。
练习:1109.航班预定统计, 995.K连续位的最小翻转次数,2779数组的最大美丽值
概率 rand7() → rand10()
使用两个rand7()
构造1-49
的均匀分布,然后去除1-9
的数。
1 2 3 4 5 6 7 class Solution extends SolBase { public int rand10 () { int a = (rand7()-1 )*7 ,b = rand7(); if (a+b<10 ) return rand10(); return (a+b)%10 +1 ; } }
约瑟夫环 0,1,···,n-1
这n
个数字排成一个圆圈,从数字0
开始,每次从这个圆圈里删除第m
个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
方法一:模拟
使用ArrayList模拟,时间复杂度 O(mn)
,空间复杂度O(n)
。
方法二:递归+反推
时间复杂度O(n)
,空间复杂度O(n)
。
当删除了第m%n
个元素后,剩下一个长度为n-1
的序列。递归地求解f(n-1,m)
,令x=f(n-1,m)
。长度为n
的序列最后一个删除的元素,应当是从m%n
开始数的第x
个元素:
f(n,m) = (m%n+x)%n = (m+x)%n
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public int lastRemaining (int n, int m) { return f(n, m); } public int f (int n, int m) { if (n == 1 ) { return 0 ; } int x = f(n - 1 , m); return (m + x) % n; } }
另解:
令f(n,m)为n个数,每次删除第m个时该问题的解
f(n,m) = (f(n-1,m)+m)%n
直到 f(1,m) = 0
时间复杂度O(n),空间复杂度O(n)。
1 2 3 4 int lastNum (int n, int m) { if (n==1 ) return 0 ; else return (lastNum(n-1 ,m)+m)%n; }
方法三:迭代+反推
时间复杂度O(n)
,空间复杂度O(1)
。
(当前index
+m
)%上一轮剩余数字的个数
1 2 3 4 5 6 7 8 9 10 class Solution { public int lastRemaining (int n, int m) { int ans = 0 ; for (int i = 2 ; i <= n; i++) { ans = (ans + m) % i; } return ans; } }
取石子
奇异局势:当玩家面临奇异局势时会失败。
巴什博弈
游戏双方轮流取石子(共N
颗)
每人每次取走若干颗石子(最少取1
颗,最多取K
颗)
石子取光,则游戏结束
最后取石子的一方获胜
1 2 if (N%(K+1 )==0 ) return false ;else return true ;
尼姆博弈
K
堆各 N1,N2,...,Nk
颗石子
每次至少取一个,多者不限
最后取走的一方获胜
解析
(0,0,0,...,0)
是奇异局势,(1,1,0,...,0)
也是奇异局势。
奇异局势时,后手只需要取相等数量的石子,先手必败。
奇异局势时,二进制每一比特位上1
的个数是偶数,使用异或运算求解。
1 2 if (N1^N2^...^Nk==0 ) return false ;else return true ;
威佐夫博弈
两堆各a,b颗石子
每次从一堆或者同时从两堆取同样多个石子,每次至少取一个,多者不限
最后取走的一方获胜
解析
前几个奇异局势:(0,0)、(1,2)、(3,5)、(4,7)、(6,10)
。
其中,a[0]=b[0]=0
,a[k]
是未在前面出现过的最小自然数,b[k]=a[k]+k
。
换一种表达方式:
\frac{\sqrt{5}+1}{2}\right&space;\rfloor,a[k]\leq&space;b[k])
奇异局势的性质:
① 任何自然数都包含在一个且仅有一个奇异局势中;
② 任意操作都可将奇异局势变为非奇异局势 ;
③ 采用适当的方法,可以将非奇异局势变为奇异局势 。
求质数 线性筛法
如果一个数是质数,那么它的整数倍(大于等于2倍)就一定不是质数。例如2是质数,4、6、8不是质数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> #include <cstring> #include <string> using namespace std; int prime[10000001]; //存储素数 bool vis[10000001]; //每个数是否是质数,false表示是质数 int main(){ int n,cnt = 0; cin>>n; memset(vis,false,sizeof(vis)); memset(prime,0,sizeof(prime)); vis[1] = true; for(int i = 2;i <= n;i++){ if(!vis[i]){ prime[cnt++] = i; for(int j = 2;j*i<=n;j++) vis[j*i] = true; } } return 0; }
欧拉筛法
是线性筛法的改进。例如6不是质数,在判断2的时就已经筛过了,到判断3的时候不用再筛了。
1 2 3 4 5 6 7 8 9 for (int i = 2 ;i <= n;i++){ if (!vis[i]){ prime[cnt++] = i; for (int j=0 ;j<cnt && i*prime[j]<=n;j++){ vis[i*prime[j]] = true ; if (i % prime[j] == 0 ) break ; } } }
LRU 哈希表+双向链表
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 class LRUCache {public : struct DLink { int key,value; DLink* prev; DLink* next; DLink (){} DLink (int _key,int _value){ this ->key = _key; this ->value = _value; } }; map<int ,DLink*> m; int maxsize; DLink* head; DLink* tail; LRUCache (int capacity) { head = new DLink (); tail = new DLink (); maxsize = capacity; head->next = tail; tail->prev = head; } int get (int key) { if (m.count (key)){ DLink* node = m[key]; node->prev->next = node->next; node->next->prev = node->prev; node->next = head->next; head->next->prev = node; head->next = node; node->prev = head; return m[key]->value; } else return -1 ; } void put (int key, int value) { if (m.count (key)){ DLink* node = m[key]; node->value = value; node->prev->next = node->next; node->next->prev = node->prev; node->next = head->next; head->next->prev = node; head->next = node; node->prev = head; } else { DLink* newNode = new DLink (key,value); head->next->prev = newNode; newNode->next = head->next; head->next = newNode; newNode->prev = head; m.insert (pair <int ,DLink*>(key,newNode)); if (m.size ()>maxsize){ m.erase (tail->prev->key); DLink* curNode = tail->prev; curNode->prev->next = tail; tail->prev = curNode->prev; } } } };
KMP算法 设模式串pattern
的长度为n
,主串str
的长度为m
,时间复杂度为O(m+n)
。
next
数组表示模式串的子串的前缀和后缀相同的最长长度,next[i]
表示最大的x
,满足pattern[0:x-1]
是pattern[0:i-1]
的后缀。
KMP的详细过程参考https://segmentfault.com/a/1190000008575379
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 public int kmp (String S, String T) { int [] next = getNext(S); int i = 0 ; int j = 0 ; int len1 = T.length(); int len2 = S.length(); while (i<len1&&j<len2){ if (j==-1 ||S.charAt(j)==T.charAt(i)){ i++; j++; } else j = next[j]; } if (j==len2) return i-j; else return -1 ; } int [] getNext(String S){ int len = S.length(); int [] next = new int [len]; int i = 0 ; int j = -1 ; next[0 ] = -1 ; while (i<len){ if (j==-1 ||S.charAt(i)==S.charAt(j)){ i++; j++; next[i] = j; } else j = next[j]; } }
双向BFS 朴素BFS可能造成搜索空间爆炸,瓶颈在于搜索空间中的最大宽度。
当前问题: 从源点开始搜索,直到找到目标节点,得到最短路径。
转换问题: 同时从源点和汇点开始搜索,一旦搜索到相同的值,即可得到最短路径。
方法:
创建两个队列分别用于两个方向的搜索;
创建两个哈希表用于解决相同节点重复搜索和记录转换次数;
为了尽可能让两个搜索方向“平均”,每次从队列中取值进行扩展时,先判断哪个队列容量较少;
如果在搜索过程中搜索到对方搜索过的节点,说明找到了最短路径。
参考自leetcode752 [宫水三叶]题解。
相关题目:127 单词接龙、752 打开转盘锁
单例模式 uniqueInstance
采用volatile
关键字修饰,uniqueInstance = new Singleton();
代码分三步执行:
为uniqueInstance
分配内存空间
初始化uniqueInstance
将uniqueInstance
指向分配的内存地址
使用volatile
可以禁止JVM的指令重排,多线程下也能正常运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Singleton { private volatile static Singleton uniqueInstance; private Singleton () { } public static Singleton getUniqueInstance () { if (uniqueInstance == null ) { synchronized (Singleton.class) { if (uniqueInstance == null ) { uniqueInstance = new Singleton (); } } } return uniqueInstance; } }
字符串哈希 使用两个不同的mod值来计算Hash,如果两个Hash值都相等才认为是同一个字符串。
原字符串为s,pre是一个大整数,可以取为233333,base数组存储pre的幂。
\%mod\end{matrix}\right.&space;)
区间Hash值:
1 2 3 4 5 6 7 8 9 10 11 12 13 long [] hash = new long [len+1 ];long [] base = new long [len+1 ];long pre = 233333 ; base[0 ] = 1 ; for (int i = 1 ; i <= len; i++){ hash[i] = hash[i - 1 ] * pre + str[i-1 ] - 'a' + 1 ; base[i] = base[i - 1 ] * pre; } hashLR = hash[R] - hash[L - 1 ] * base[R - L + 1 ];
参考:
https://blog.csdn.net/qq_45778406/article/details/113920372
1044 最长重复子串
生产者消费者问题
参考自https://blog.csdn.net/qq_32505207/article/details/116662834
使用wait/notify实现
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 static class Producer implements Runnable { private LinkedList<Integer> list; private int maxL = 10 ; private Random random; Producer(LinkedList<Integer> linkedList){ this .list = linkedList; random = new Random (); } @Override public void run () { while (true ) { synchronized (list) { try { while (list.size()==maxL){ System.out.println("生产者" +Thread.currentThread().getName()+" list达到最大,进行wait()" ); list.wait(); System.out.println("生产者" +Thread.currentThread().getName()+" wait()结束" ); } int p = random.nextInt(); System.out.println("生产者" +Thread.currentThread().getName()+"生产了" +p); list.add(p); list.notifyAll(); } catch (Exception e) { e.printStackTrace(); } } } } } static class Consumer implements Runnable { private LinkedList<Integer> list; public Consumer (LinkedList<Integer> linkedList) { this .list = linkedList; } @Override public void run () { while (true ) { synchronized (list) { try { while (list.isEmpty()){ System.out.println("消费者" +Thread.currentThread().getName()+" list为空,进行wait()" ); list.wait(); System.out.println("消费者" +Thread.currentThread().getName()+" wait()结束" ); } Integer p = list.remove(0 ); System.out.println("消费者" +Thread.currentThread().getName()+"消费了" +p); list.notifyAll(); }catch (Exception e){ e.printStackTrace(); } } } } } public static void main (String[] args) { LinkedList<Integer> list = new LinkedList <>(); ExecutorService service = Executors.newFixedThreadPool(15 ); for (int i=0 ;i<5 ;i++) { service.execute(new Producer (list)); } for (int i=0 ;i<10 ;i++) { service.execute(new Consumer (list)); } }
使用BlockingQueue实现
使用BlockingQueue的put、take实现。
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 private static LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue <>();static class Productor implements Runnable { private BlockingQueue queue; private Random random; public Productor (LinkedBlockingQueue<Integer> queue) { this .queue = queue; random = new Random (); } @Override public void run () { while (true ){ try { int p = random.nextInt(); System.out.println("生产者" +Thread.currentThread().getName()+" 生产了" +p); queue.put(p); } catch (InterruptedException e) { e.printStackTrace(); } } } } static class Consumer implements Runnable { private BlockingQueue queue; public Consumer (LinkedBlockingQueue<Integer> queue) { this .queue = queue; } @Override public void run () { while (true ){ try { Integer p = (Integer) queue.take(); System.out.println("消费者" +Thread.currentThread().getName()+" 消费了" +p); }catch (Exception e){ e.printStackTrace(); } } } } public static void main (String[] args) { ExecutorService service = Executors.newFixedThreadPool(15 ); for (int i = 0 ; i < 5 ; i++) { service.execute(new Productor (queue)); } for (int i = 0 ; i < 10 ; i++) { service.execute(new Consumer (queue)); } }
使用Lock的Condition的await/signal消息通知机制
GCD 求最大公约数。
辗转相除法 。时间复杂度为O(logn)。
1 2 3 4 5 6 7 public int gcd (int a,int b) { if (b==0 ) { return a; } else { return gcd(b,a%b); } }
判断两个数是否互质
1 2 3 4 5 boolean f (int a,int b) { if (a==1 ||b==1 ) return true ; if (a%b==0 ) return false ; else return f(b,a%b); }
二进制GCD
=\left{\begin{matrix} a &,a=b \ gcd(\frac{a}{2},\frac{b}{2}) &,both \ a\ and\ b\ are\ even\ gcd(\frac{a}{2},b) &,a\ is\ even,b\ is\ odd \ gcd(\frac{a-b}{2},b) &,both\ a\ and\ b\ are\ odd \ \end{matrix}\right.)
参考https://zhuanlan.zhihu.com/p/553890800
有序数组中的单一元素 二分法。有序数组中的相同元素一定相邻。
分界点性质:
对于下标x的左边下标y,如果nums[y]=nums[y+1],则y一定是偶数
对于下标x的右边下标z,如果nums[z]=nums[z+1],则z一定是奇数
比较条件:
如果mid是偶数,则比较nums[mid]和nums[mid+1]是否相等
如果mid是奇数,则比较nums[mid-1]和nums[mid]是否相等
相邻数获取:
当mid是偶数时,mid+1=(mid^1)
当mid是奇数时,mid-1=(mid^1)
时间复杂度O(logn)
。