常见算法模板总结
文章目录
- 一、二叉树
- 1. DFS
- 2. BFS
- 二、回溯模板
- 三、记忆化搜索
- 四、动态规划
- 1. 01背包
- 朴素版本
- 滚动数组优化
- 2. 完全背包
- 朴素版本
- 滚动数组优化
- 3. 最长递增子序列LIS
- 朴素版本
- 贪心+二分优化
- 4. 最长公共子序列
- 5. 最长回文子串
- 五、滑动窗口
- 六、二分查找
- 七、单调栈
- 八、单调队列
- 九、图论
- 1. 建图
- 邻接矩阵
- 邻接表
- 2. 图的遍历
- DFS
- BFS
- 3. 拓朴排序
- 4. 并查集
- 5. 最小生成树
- Kruskal
- Prim
- 6. BFS层序遍历求最短路
- 7. 迪杰斯特拉
- 朴素版本
- 堆优化版本
- 8. 弗洛伊德
- 9. 强连通分量
- 十、区间相关
- 1. 前缀和
- 2. 二维前缀和
- 3. 差分
- 4. 二维差分
- 5. 树状数组
- 6. 线段树
- 十一、杂项
- 1. 求质数/素数
- 2. 求约数
- 3. 快速幂
- 4. 离散化
- 5. 优先队列
- 6. 同余取模
一、二叉树
适用场景
一般情况下,两种方式是可以互相转换的,DFS代码会更加简洁。
DFS依赖于递归实现(栈)
BFS依赖于迭代实现(队列)
1. DFS
C++:
void dfs(TreeNode* node) {if (!node) return;//相应的处理dfs(node->left);dfs(node->right);
}
Java:
void dfs(TreeNode node) {if (node == null) return;//相应的处理dfs(node.left);dfs(node.right);
}
Python:
def dfs(node):if not node: return//相应的处理dfs(node.left)dfs(node.right)
2. BFS
C++:
void bfs(TreeNode* root) {queue <TreeNode*> q;q.push(root);while (!q.empty()) {int currentLevelSize = q.size();for (int i = 1; i <= currentLevelSize; ++i) {auto node = q.front(); q.pop();ret.back().push_back(node->val);if (node->left) q.push(node->left);if (node->right) q.push(node->right);}}
}
Java:
void bfs(TreeNode root) {Deque<TreeNode> queue = new LinkedList();queue.addLast(root);while(!queue.isEmpty()) {int n = queue.size();for (int i = 0 ; i < n ; i++) {TreeNode node = queue.pollFirst();if (node.left != null) queue.addLast(node.left);if (node.right != null) queue.addLast(node.right);}}
}
Python:
def bfs(root):queue = deque()if root: queue.append(root)while len(queue):n = len(queue)for i in range(n):node = queue.popleft()if node.left : queue.append(node.left)if node.right: queue.append(node.right)
二、回溯模板
tip 适用场景
用于解决暴力枚举的场景,例如枚举组合、排列等。
C++:
vector<vector<int>> res;
vector<int> path;
void dfs(参数) {if (满足递归结束) {res.push_back(path);return;}//递归方向for (xxx) {path.push_back(val);dfs();path.pop_back();}
}
Java:
List<List<Integer>> res;
List<Integer> path;void dfs(参数) {if(满足递归结束) {res.add(new LinkedList(path));return;}//递归方向for (xxx) {path.add(val);dfs();path.remove(path.size() - 1);}
}
Python:
res = []
path = []def dfs(参数) :if 满足递归结束:res.append(list(path))return# 递归方向for (xxxx):path.append(val)dfs()path.pop()
三、记忆化搜索
适用场景
用于解决枚举过程中存在重复计算的场景问题。
此类题型一般也可以使用动态规划进行求解。
C++:
int dp[]; //初始化为-1,dp数组的维度取决于dfs函数的参数个数。int dfs(int i) {if (递归终止) return 0; //具体返回什么值要看题目的含义if (dp[i] != -1) return dp[i];int cnt = 0;for (递归方向) {cnt += dfs(xxx); //如果是计数,一般是叠加,也有可能是取最大或者最小}return dp[i] = cnt;
}
Java:
int[] dp; //初始化为-1,dp数组的维度取决于dfs函数的参数个数。int dfs(int i) {if (递归终止) return 0; //具体返回什么值要看题目的含义if (dp[i] != -1) return dp[i];int cnt = 0;for (递归方向) {cnt += dfs(xxx); //如果是计数,一般是叠加,也有可能是取最大或者最小}return dp[i] = cnt;
}
Python:
from functools import cache
@cache #缓存,避免重复运算
def dfs(i)->int:if 终止: return 0 #具体返回什么值要看题目的含义cnt = 0for 递归方向:cnt += dfs(xxx) #如果是计数,一般是叠加,也有可能是取最大或者最小return cnt
四、动态规划
1. 01背包
适用场景
给出若干个物品,每个物品具有一定的价值和价格,求解在限定的总额下可以获取的最大价值,注意,每个物品只能选取一次。
朴素版本和滚动数组优化的区别主要在于空间复杂度上,时间复杂度差不多,所以笔试的时候基本上没差别(空间很少会被卡)。
朴素版本
C++:
int n, C; //n个物品, C表示背包容量
int v[], w[]; //v[i]表示第i个物品的价格/体积 w[i]表示第i个物品的价值
int dp[n+1][C+1]; //容器规模
//初始化 dp[0][j] j∈[0,C]
for (int i = 1 ; i <= n ; i++) {for (int j = 0 ; j <= C ; j++) {if (j >= v[i - 1]) dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i-1]] + w[i - 1]);else dp[i][j] = dp[i - 1][j];}
}
return dp[n][C];
Java:
int n, C; //n个物品, C表示背包容量
int[] v, w; //v[i]表示第i个物品的价格/体积 w[i]表示第i个物品的价值
int[][] dp = new int[n + 1][C + 1]; //容器规模
//初始化 dp[0][j] j∈[0,C]
for (int i = 1 ; i <= n ; i++) {for (int j = 0 ; j <= C ; j++) {if (j >= v[i - 1]) dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-v[i-1]] + w[i - 1]);else dp[i][j] = dp[i - 1][j];}
}
return dp[n][C];
Python:
n, C; #n个物品, C表示背包容量
v, w; #v[i]表示第i个物品的价格/体积 w[i]表示第i个物品的价值
dp = [[0 for _ in range(C+1)] for _ in range(n+1)] #容器规模
#初始化 dp[0][j] j∈[0,C]
for i in range(1, n+1):for j in range(C+1):if j>=v[i-1]: dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i-1]]+W[i-1])else: dp[i][j] = dp[i-1][j]
return dp[n][C];
滚动数组优化
C++:
int n, C; //n个物品, C表示背包容量
int v[], w[]; //v[i]表示第i个物品的价格/体积 w[i]表示第i个物品的价值
int dp[C+1]; //容器规模
//初始化 dp[j] j∈[0,C]
for (int i = 1 ; i <= n ; i++) {for (int j = C ; j >= v[i - 1] ; j--) {dp[j] = max(dp[j], dp[j-v[i-1]] + w[i - 1]);}
}
return dp[C];
Java:
int n, C; //n个物品, C表示背包容量
int[] v, w; //v[i]表示第i个物品的价格/体积 w[i]表示第i个物品的价值
int dp[C+1]; //容器规模
//初始化 dp[j] j∈[0,C]
for (int i = 1 ; i <= n ; i++) {for (int j = C ; j >= v[i - 1] ; j--) {dp[j] = Math.max(dp[j], dp[j-v[i-1]] + w[i - 1]);}
}
return dp[C];
Python:
n, C; //n个物品, C表示背包容量
v, w; //v[i]表示第i个物品的价格/体积 w[i]表示第i个物品的价值
dp = [0 for _ in range(C+1)] //容器规模
//初始化 dp[j] j∈[0,C]
for i in range(1, n+1):for j in range(C,v[i-1]-1,-1):dp[j] = max(dp[j], dp[j-v[i-1]]+w[i-1])
return dp[C];
2. 完全背包
适用场景
给出若干个物品,每个物品具有一定的价值和价格,求解在限定的总额下可以获取的最大价值,注意,每个物品不限制选取次数。
朴素版本和滚动数组优化的区别主要在于空间复杂度上,时间复杂度差不多,所以笔试的时候基本上没差别(空间很少会被卡)。
朴素版本
C++:
int n, C; //n个物品, C表示背包容量
int v[], w[]; //v[i]表示第i个物品的价格/体积 w[i]表示第i个物品的价值
int dp[n + 1][C + 1]; //容器规模
//初始化 dp[0][j] j∈[0,C]
for (int i = 1 ; i <= n ; i++) {for (int j = 0 ; j <= C ; j++) {if (j >= v[i - 1]) dp[i][j] = max(dp[i-1][j], dp[i][j-v[i-1]] + w[i - 1]);else dp[i][j] = dp[i - 1][j];}
}
return dp[n][C];
Java:
int n, C; //n个物品, C表示背包容量
int[] v, w; //v[i]表示第i个物品的价格/体积 w[i]表示第i个物品的价值
int[][] dp = new int[n + 1][C + 1]; //容器规模
//初始化 dp[0][j] j∈[0,C]
for (int i = 1 ; i <= n ; i++) {for (int j = 0 ; j <= C ; j++) {if (j >= v[i - 1]) dp[i][j] = max(dp[i-1][j], dp[i][j-v[i-1]] + w[i - 1]);else dp[i][j] = dp[i - 1][j];}
}
return dp[n][C];
Python:
n, C; #n个物品, C表示背包容量
v, w; #v[i]表示第i个物品的价格/体积 w[i]表示第i个物品的价值
dp = [[0 for _ in range(C+1)] for _ in range(n+1)] #容器规模
#初始化 dp[0][j] j∈[0,C]
for i in range(1, n+1):for j in range(C+1):if j>=v[i-1]: dp[i][j] = max(dp[i-1][j], dp[i][j-v[i-1]]+W[i-1])else: dp[i][j] = dp[i-1][j]
return dp[n][C];
滚动数组优化
C++:
int n, C; //n个物品, C表示背包容量
int v[], w[]; //v[i]表示第i个物品的价格/体积 w[i]表示第i个物品的价值
int dp[C+1]; //容器规模
//初始化 dp[j] j∈[0,C]
for (int i = 1 ; i <= n ; i++) {for (int j = v[i-1] ; j <= C ; j++) {dp[j] = max(dp[j], dp[j-v[i-1]] + w[i - 1]);}
}
return dp[C];
Java:
int n, C; //n个物品, C表示背包容量
int[] v, w; //v[i]表示第i个物品的价格/体积 w[i]表示第i个物品的价值
int dp[C+1]; //容器规模
//初始化 dp[j] j∈[0,C]
for (int i = 1 ; i <= n ; i++) {for (int j = v[i-1] ; j <= C ; j++) {dp[j] = Math.max(dp[j], dp[j-v[i-1]] + w[i - 1]);}
}
return dp[C];
Python:
n, C; //n个物品, C表示背包容量
v, w; //v[i]表示第i个物品的价格/体积 w[i]表示第i个物品的价值
dp = [0 for _ in range(n+1)] //容器规模
//初始化 dp[j] j∈[0,C]
for i in range(1, n+1):for j in range(C+1):dp[j] = max(dp[j], dp[j-v[i-1]]+w[i-1])
return dp[C];
3. 最长递增子序列LIS
适用场景
给定一个数组,求数组最长上升子序列的长度。
朴素版本可以求解的数据规模约为 1000。如果题目数据给到了10000或者更大,需要使用贪心+二分进行优化。
朴素版本
C++:
int lengthOfLIS(vector<int>& nums) {int dp[nums.size()];int ans = 1;dp[0] = 1;for (int i = 1 ; i < nums.size() ; i++) {dp[i] = 1;for (int j = 0 ; j < i ; j++) {if (nums[j] < nums[i]) {dp[i] = max(dp[j] + 1, dp[i]);}}ans = max(ans, dp[i]);}return ans;
}
Java:
public int lengthOfLIS(int[] nums) {int[] dp = new int[nums.length];Arrays.fill(dp, 1);int ans = 1;for (int i = 1 ; i < nums.length ; i++) {for (int j = i - 1 ; j >= 0 ; j--) {if (nums[i] > nums[j]) {dp[i] = Math.max(dp[i], dp[j] + 1);ans = Math.max(ans, dp[i]);}}}return ans;
}
Python:
def lengthOfLIS(self, nums: List[int]) -> int:dp = [1 for _ in range(len(nums))]for i in range(1, len(nums)):for j in range(i):if nums[i] > nums[j]:dp[i] = max(dp[i], dp[j] + 1)return max(dp)
贪心+二分优化
C++:
int lengthOfLIS(vector<int>& nums) {vector<int> ls;for (int num : nums) {auto it = lower_bound(ls.begin(), ls.end(), num);if (it == ls.end()) ls.push_back(num);else *it = num;}return ls.size();
}
Java:
int lengthOfLIS(int[] nums) {List<Integer> ls = new ArrayList<>();for (int num : nums) {int i = Collections.binarySearch(ls, num);if (i < 0) i = -(i + 1);if (i == ls.size()) ls.add(num);else ls.set(i, num);}return ls.size();
}
Python:
def lengthOfLIS(self, nums: List[int]) -> int:ls = []for num in nums:x = bisect_left(ls, num)if x == len(ls):ls.append(num)else:ls[x] = numprint(ls)return len(ls)
4. 最长公共子序列
适用场景
求两个数组或者字符的最长公共的子序列的长度。时间复杂度为O(n^2)
C++:
int longestCommonSubsequence(string text1, string text2) {int len1 = text1.size(), len2 = text2.size();vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));for (int i = 1 ; i <= len1 ; i++) {for (int j = 1 ; j <= len2 ;j++) {if (text1[i - 1] == text2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);}}return dp[len1][len2];
}
Java:
int longestCommonSubsequence(String text1, String text2) {int len1 = text1.length();int len2 = text2.length();int[][] dp = new int[len1 + 1][len2 + 1];for (int i = 1 ; i <= len1 ; i++) {for (int j = 1 ; j <= len2 ; j++) {if (text1.charAt(i - 1) == text2.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1] + 1;else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);}}return dp[len1][len2];
}
Python:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:len1, len2 = len(text1), len(text2)dp = [[0] *(len2 + 1) for _ in range(len1 + 1)]for i in range(1, len1 + 1):for j in range(1, len2 + 1):dp[i][j] = dp[i - 1][j - 1] + 1 if text1[i - 1] == text2[j - 1] else max(dp[i-1][j],dp[i][j-1])return dp[len1][len2]
5. 最长回文子串
适用场景
求解一个数组/字符串的最长回文子串的长度,时间复杂度为O(n^2)。
C++:
int longestPalindrome(string s) {int n = s.size();bool dp[n][n];int mxlen = -1;for (int j = 0 ; j < n ; j++) {for (int i = j ; i >= 0 ; i--) {if (i == j) dp[i][j] = true;else if (i + 1 == j) dp[i][j] = s[i] == s[j];else {dp[i][j] = s[i] == s[j] && dp[i + 1][j - 1];}if (dp[i][j] && j - i + 1 > mxlen) {mxlen = j - i + 1;}}}return mxlen;
}
Java:
int longestPalindrome(String s) {int n = s.length();boolean[][] dp = new boolean[n][n];char[] cs = s.toCharArray();int mxlen = 0;for (int j = 0 ; j < n ; j++) {for (int i = j ; i >= 0 ; i--) {if (i == j) dp[i][j] = true;else if (i + 1 == j) dp[i][j] = cs[i] == cs[j];else {dp[i][j] = cs[i] == cs[j] && dp[i + 1][j - 1];}if (dp[i][j] && j - i + 1 > mxlen) {mxlen = j - i + 1;}}}return mxlen;
}
Python:
def longestPalindrome(self, s: str) -> str:n = len(s)dp = [[False] * n for _ in range(n)]maxlen = 0for j in range(n):for i in range(j + 1):if i == j:dp[i][j] = Trueelif i + 1 == j:dp[i][j] = s[i] == s[i + 1]else:dp[i][j] = s[i] == s[j] and dp[i + 1][j - 1]if dp[i][j] and j - i + 1 >= maxlen:maxlen = j - i + 1return mxlen
五、滑动窗口
适用场景
求解数组/字符串 满足某个约束的最长/最短 的子数组/子串。需要满足二段性才可以使用。
C++:
for (int l = 0, r = 0 ; r < n ; r++) {//如果右指针的元素加入到窗口内后,根据题目判断进行滑动左指针while (l <= r && check()) l++;
}
六、二分查找
适用场景
满足二段性的数列中,求某一个值的位置、大于某个值的最小值、小于某个值的最大值。时间复杂度为O(logn)。
C++:
// 区间划分为[l,mid] 和 [mid+1,r],选择此模板
int bsec1(int l, int r)
{while (l < r){int mid = (l + r)/2;if (check(mid)) r = mid;else l = mid + 1;}return r;
}// 区间划分为[l,mid-1] 和 [mid,r],选择此模板
int bsec2(int l, int r)
{while (l < r){int mid = ( l + r + 1 ) /2;if (check(mid)) l = mid;else r = mid - 1;}return r;
}
七、单调栈
适用场景
求序列中下一个更大、更小的元素。时间复杂度O(n)
C++:
stack<int> s;
for (int i = 0; i < n; ++i) {while (!s.empty() && nums[i] > nums[s.top()]) {int top = s.top();s.pop();//此时说明 nums[top]的下一个更大的元素为nums[i]}s.push(i);
}
Java:
Stack<Integer> stack = new Stack();
for (int i = 0 ; i < nums.length ; i++) {while(!stack.isEmpty() && nums[stack.peek()] < nums[i]) {int top = stack.pop();//此时说明 nums[top]的下一个更大的元素为nums[i]}stack.push(i);
}
Python:
stack = []
for i in range(len(nums)):while stack and nums[stack[-1]] < nums[i]:p = stack.pop()# 此时说明 nums[top]的下一个更大的元素为nums[i]stack.append(i)
八、单调队列
适用场景
求解移动区间的最值问题。时间复杂度O(n)
C++:
vector<int> res(nums.size() - k + 1); //存储长长度为k的每一个区间的最值
deque<int> queue;
for (int i = 0; i < nums.size(); i++) {if (!queue.empty() && i - k + 1 > queue.front()) queue.pop_front();while (!queue.empty() && nums[queue.back()] < nums[i]) queue.pop_back();queue.push_back(i);if (i >= k - 1) res[i - k + 1] = nums[queue.front()];
}
return res;
Java:
int n = nums.length;
int[] res = new int[n - k + 1];
Deque<Integer> q = new LinkedList();
for (int i = 0 ; i < n ; i++) {if (!q.isEmpty() && i - q.getFirst() > k - 1) q.pollFirst();while (!q.isEmpty() && nums[q.getLast()] < nums[i]) q.pollLast();q.addLast(i);if (i >= k - 1) res[i - k + 1] = nums[q.getFirst()];
}
return res;
Python:
res = [0] * (len(nums) - k + 1)
queue = deque()
for i in range(len(nums)):if queue and i - k + 1 > queue[0]:queue.popleft()while queue and nums[queue[-1]] < nums[i]:queue.pop()queue.append(i)if i >= k - 1:res[i - k + 1] = nums[queue[0]]
return res
九、图论
1. 建图
建图的方式一般有两种,邻接矩阵和邻接表;链式前向星有些许晦涩,不一定要掌握。
1.邻接矩阵,适用于稠密图【边的数量远大于点】
2.邻接表,适用于稀疏图【点的数量远大于边】
邻接矩阵
C++:
int graph[n][n];
for (int i = 0 ; i < m ; i++) {int a,b,w; //a和b存在一条边,权重为wcin >> a >> b >> w;graph[a][b] = graph[b][a] = w; // 如果是有向图则不需要建立双向边
}
Java:
int[][] graph = new int[n][n];
for (int i = 0 ; i < m ; i++) {int a,b,w; //a和b存在一条边,权重为wa = scanner.nextInt();b = scanner.nextInt();w = scanner.nextInt();graph[a][b] = graph[b][a] = w; // 如果是有向图则不需要建立双向边
}
Python:
graph = [[0 for _ in range(n)] for _ in range(n)]
for i in range(n):a,b,w = map(int, input().split())graph[a][b] = graph[b][a] = w # 如果是有向图则不需要建立双向边
邻接表
C++:
vector<vector<pair<int,int>>> graph(n, vector<int>(0));
for (int i = 0 ; i < m ; i++) {int a,b,w; //a和b存在一条边,权重为wgraph[a].push_back({b,w});graph[b].push_back({a,w}); // 如果是有向图则不需要建立双向边
}
Java:
List<List<int[]>> graph = new ArrayList();
for (int i = 0 ; i <= n ;i++) graph.add(new LinkedList());
for (int i = 0 ; i < m ; i++) {int a,b,w; //a和b存在一条边,权重为wa = scanner.nextInt();b = scanner.nextInt();w = scanner.nextInt();graph.get(a).add(new int[]{b,w});graph.get(b).add(new int[]{a,w});// 如果是有向图则不需要建立双向边
}
Python:
graph = defaultdict(list)
for i in range(m):a,b,w = map(int, input().split())graph[a].append([b,w])graph[b].append([a,w])
2. 图的遍历
DFS
C++:
vector<vector<pair<int,int>> graph;
vector<bool> vst;
void dfs(int node) {for (auto p: graph[node]) {int next = p.first, weight = p.second;if (!vst[next]) {vst[next] = true;dfs(next);// 如果需要回溯的话 , vst[next] = false;}}
}
Java:
List<List<int[]>> graph;
boolean[] vst;
void dfs(int node) {for (auto p: graph[node]) {int next = p[0], weight = p[1];if (!vst[next]) {vst[next] = true;dfs(next);// 如果需要回溯的话 , vst[next] = false;}}
}
Python:
graph
vst = [False for _ in range(n)]
def dfs(node):for next,weight in graph[node]:if not vst[next]:vst[next] = Truedfs(next)# 如果需要回溯的话 , vst[next] = false;
BFS
C++:
vector<vector<pair<int,int>> graph;
vector<bool> vst;
void bfs() {queue<int> q;q.push(start);vst[start] = true;while (!q.size()) {int node = q.front();q.pop();for (int p : graph[node]) {int next = p.first, weight = p.second;if (!vst[next]) {vst[next] = true;q.push_back(next);}}}
}
Java:
List<List<int[]>> graph;
boolean[] vst;
void bfs() {Deque<Integer> q;q.addLast(start);vst[start] = true;while (!q.isEmpty()) {int node = q.pollFirst();for (int[] arr : graph.get(node)) {int next = arr[0], weight = arr[1];if (!vst[next]) {vst[next] = true;q.addLast(next);}}}
}
Python:
graph
vst = [False for _ in range(n)]
def bfs():q = deque()q.append(start)vst[start] = Truewhile q:node = q.popleft()for next,weight in graph[node]:if not vst[next]:vst[next] = Trueq.append(next)
3. 拓朴排序
适用场景
求解有向图的拓扑序、有向图判断是否成环
C++:
vector<vector<int> graph;
vector<int> indegre; //存储每个节点的入度
queue<int> q;
for (int i = 0 ; i < n ; i++) {if (indegre[i] == 0)q.push(i);
}while(q.size()) {int node = q.front(); q.pop();for (int next : graph[node]) {indegre[next]--;if (indegre[next] == 0) q.push(next);}
}
Java:
List<List<Integer>> graph;
int[] indegre; //存储每个节点的入度
Deque<Integer> q = new LinkedList();
for (int i = 0 ; i < n ; i++) {if (indegre[i] == 0) q.addLast(i);
}while (!q.isEmpty()) {int node = q.pollFirst();for (int next : graph.get(node)) {indegre[next]--;if (indegre[next] == 0) q.addLast(next);}
}
Python:
graph = [[] for _ in range(n)]
indegre = [0] * n#存储每个节点的入度
q = deque()
for i in range(n):if indegre[i]==0: q.append(i)while q:node = q.popleft()for next in graph[node]:indegre[next]-=1if indegre[next] == 0: q.append(next)
4. 并查集
适用场景
用于解决 连通性问题。比如a和b相邻,b和c相邻,可以判断出a和c相邻。
C++:
int N = 1e5; //点的数量
int fa[N];void init(int n) {for (int i = 0;i < n ; i++) fa[i] = i;
}//找到x的根节点
int find(int x) {return x == fa[x] ? x : (fa[x] = find(fa[x]));
}//合并两个节点
void union(int x, int y) {fa[find(x)] = find(y);
}
Java:
int[] fa;void init(int n) {fa = new int[n];for (int i = 0; i < n; i++) fa[i] = i;
}
//找到x的根节点
int find(int x) {return x == fa[x] ? x : (fa[x] = find(fa[x]));
}
//合并两个节点
void union(int x, int y) {fa[find(x)] = find(y);
}
Python:
fa = [i for i in range(n)]#找到x的根节点
def find(x):if x == fa[x]: return xfa[x] = find(fa[x])return fa[x]#合并两个节点
def union(x,y):fa[find(x)] = find(y)
5. 最小生成树
适用场景
连接无向图中所有的节点的最小费用。
常见的算法有2种:
- kruskal:稀疏图,时间复杂度是O(mlogm)。
- prim:稠密图,时间复杂度是O(n^2)。
ps:n是点的数量,m是边的数量
Kruskal
C++:
int N = 1e5; //点的数量
int fa[N];void init(int n) {for (int i = 0;i < n ; i++) fa[i] = i;
}//找到x的根节点
int find(int x) {return x == fa[x] ? x : (fa[x] = find(fa[x]));
}//合并两个节点
void union(int x, int y) {fa[find(x)] = find(y);
}int kruskal(vector<vector<int>>& edges, int n, int m) {// edges[i] = {a,b,w} 表示a和b之间存在有一条边,权重为winit(n);sort(edges.begin(), edges.end(), [](const vector<int>& a, const vector<int>& b){return a[2]<b[2];});int ans = 0;for (auto arr : edges) {int a = arr[0], b = arr[1], w = arr[2];if (find(a) != find(b)) {union(a,b);ans += w;}}return ans;
}
Java:
int[] fa;void init(int n) {fa = new int[n];for (int i = 0; i < n; i++) fa[i] = i;
}
//找到x的根节点
int find(int x) {return x == fa[x] ? x : (fa[x] = find(fa[x]));
}
//合并两个节点
void union(int x, int y) {fa[find(x)] = find(y);
}int kruskal(int[][] edges, int n) {// edges[i] = {a,b,w} 表示a和b之间存在有一条边,权重为winit(n);Arrays.sort(edges, (a,b)->a[2]-b[2]);int ans = 0;for (int[] arr: edges) {int a = arr[0], b = arr[1], w = arr[2];if (find(a) != find(b)) {union(a,b);ans += w;}}return ans;
}
Python:
def kruskal(edges:List[List[int]], n:int,m:int) -> int:edges.sort(key=lambda x : x[2])fa = [i for i in range(n)]#找到x的根节点def find(x):if x == fa[x]: return xfa[x] = find(fa[x])return fa[x]#合并两个节点def union(x,y):fa[find(x)] = find(y) ans = 0for a,b,w in edges:if find(a) != find(b):union(a,b)ans += wreturn ans
Prim
C++:
int prim(vector<vector<int>>& graph, int n) {vector<int> dis(n,INT_MAX);vector<bool> vst(n, false);int res = 0;for (int i = 0 ; i < n ; i++) {int min_index = -1;for (int j = 0 ; j < n ; j++) {if (!vst[j] && (min_index == -1 || dis[min_index] > dis[j]) min_index = j;}if (i != 0) res += dis[min_index];vst[min_index] = true;for (int j = 0 ; j < n ; j++) dis[j] = min(dis[j], graph[min_index][j]);}return res;
}
Java:
int prim(int[][] graph, int n) {int[] dis = new int[n];boolean[] vst = new boolean[n];int res = 0;Arrays.fill(dis, Integer.MAX_VALUE);for (int i = 0 ; i < n ; i++) {int min_index = -1;for (int j = 0 ; j < n ; j++) {if (!vst[j] && (min_index == -1 || dis[min_index] > dis[j]) min_index = j;}if (i != 0) res += dis[min_index];vst[min_index] = true;for (int j = 0 ; j < n ; j++) dis[j] = Math.min(dis[j], graph[min_index][j]);}return res;
}
Python:
def prim(graph: List[List[int]], n: int) -> int:dis = [inf for _ in range(n)]vst = [False for _ in range(n)]res = 0for i in range(n):min_index = -1for j in range(n):if not vst[j] and (min_index == -1 or dis[min_index] > dis[j]) min_index = jif i != 0: res += dis[min_index]vst[min_index] = Truefor j in range(n): dis[j] = min(dis[j], graph[min_index][j])return res
6. BFS层序遍历求最短路
适用场景
如果图中的节点的不存在边权(边权均为1),那么直接BFS即可求出最短路。
C++:
// 返回从st到达target的最短路径
int bfs(int st, int target, int n, vector<vector<int>>& graph) {queue<int> q;vector<bool> vst(n, false);q.push(st);vst[st] = true;int cnt = 0while (!q.size()) {int size = q.size();while(size--) {int node = q.front();q.pop();if (node == target) return cnt;for (int next: graph[node]) {if (vst[next]) continue;vst[next] = true;q.push(next);}}cnt++;}return -1;
}
Java:
// 返回从st到达target的最短路径
int bfs(int st, int target, int n, List<List<Integer>> graph) {Deque<Integer> q;boolean[] vst = new boolean[n];q.addLast(st);vst[st] = true;int cnt = 0;while (!q.isEmpty()) {int size = q.size();while (size-- > 0) {int node = q.pollFirst();if (node == target) return cnt;for (int next: graph.get(node)) {if (vst[next]) continue;vst[next] = true;q.addLast(next);}}cnt++;}return -1;
}
Python:
def bfs(st: int, target: int, n: int, graph: dict) -> int:q = deque()vst = [False for _ in range(n)]q.append(st)vst[st] = Truecnt = 0while q:size = len(q)for _ in range(size):node = q.popleft()if node == target: return cntfor next in graph[node]:if vst[next]: continuevst[next] = Trueq.append(next)cnt+=1return -1
7. 迪杰斯特拉
朴素版本
C++:
//求st为起点的最短路
//graph[i][j]: i到j的距离,不存在则初始化成最大值
//n表示节点的数量
void dijkstra(int st, int n, vector<vector<int>>& graph) {vector<int> dis(n, INT_MAX);vector<bool> vst(n, false);dis[st] = 0;for (int i = 0 ; i < n ; i++) {int x = -1;for (int j = 0 ; j < n ; j++) {if (!vst[j] && (x == -1 || dis[j] < dis[x])) x=j;}vst[x] = true;for (int j = 0 ; j < n ; j++) {dis[j] = min(dis[j], dis[x] + graph[x][j]);}}
}
Java:
//求st为起点的最短路
//graph[i][j]: i到j的距离,不存在则初始化成最大值
//n表示节点的数量
void dijskra(int[][] G, int st, int ed, int n) {int[] dis = new int[n + 1];Arrays.fill(dis, 100010);dis[st] = 0;boolean[] vst = new boolean[n + 1];for (int i = 0; i < n; i++) {int x = -1;for (int y = 0; y < n; y++) {if (!vst[y] && (x == -1 || dis[y] < dis[x])) x = y;}vst[x] = true;for (int y = 0; y < n; y++) dis[y] = Math.min(dis[y], dis[x] + G[x][y]);}}
Python:
def dijkstra(st: int, n: int, graph: List[List[int]]):dis = [inf for _ in range(n)]vst = [False for _ in range(n)]dis[st] = 0for i in range(n):x = -1for y in range(n):if not vst[y] and (x==-1 or dis[y] < dis[x]): x = yvst[x] = Truefor y in range(n):dis[y] = min(dis[y], dis[x] + graph[x][y])
堆优化版本
C++:
void dijkstra(int st, int n, vector<vector<pair<int,int>>>& graph) {vector<int> dis(n, INT_MAX);vector<bool> vst(n, false);dis[st] = 0;priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq; pq.push({0, st});while (!pq.size()) {int d = pq.top().first();int u = pq.top().second();pq.pop();if (vst[u]) continue;vist[u] = true;for (auto [v,w] : graph[u]) {if (dis[v] > dis[u] + w) {dis[v] = dis[u] + w;pq.push({dis[v], v});}}}
}
Java:
void dijkstra(int st, int n, List<List<int[]>> graph) {int[] dis = new int[n];Arrays.fill(dis, Integer.MAX_VALUE);boolean[] vst = new boolean[n];dis[st] = 0;PriorityQueue<int[]> pq = new PriorityQueue<>((a,b)->a[0]-b[0]);pq.add(new int[]{0, st});while (!pq.isEmpty()) {int[] arr = pq.poll();int d = arr[0], u = arr[1];if (vst[u]) continue;vst[u] = true;for (int[] A: graph.get(u)) {int v = A[0], w = A[1];if (dis[v] > dis[u] + w) {dis[v] = dis[u] + w;pq.add(new int[]{dis[v], v});}}}
}
Python:
def dijkstra(st: int, n: int, graph: dict):dis = [inf for _ in range(n)]vst = [False for _ in range(n)]dis[st] = 0h = []heapq.heappush(h, [0, st])while h:d,u = heapq.heappop(h)if vst[u]: continuevst[u] = Truefor v,w in graph[u]:if dis[v] > dis[u] + w:dis[v] = dis[u] + wheapq.heappush(h, [dis[v], v])
8. 弗洛伊德
适用场景
多源最短路,可以在O(n^3)的时间内求出任意两个点的最短距离。
C++:
vector<vector<int>> dp;
for (int i = 0 ; i < n ; i++) {for (int j = 0 ; j < n ; j++) {dp[i][j] = graph[i][j];}
}for (int k = 0 ; k < n ; k++) {for (int i = 0 ; i < n ; i++) {for (int j = 0 ; j < n ; j++) {dp[i][j] = min(dp[i][j], dp[i][k]+dp[k][j]);}}
}
Java:
int[][] dp = new int[n][n];
for (int i = 0 ; i < n ; i++) {for (int j = 0 ; j < n ; j++) {dp[i][j] = graph[i][j];}
}for (int k = 0 ; k < n ; k++) {for (int i = 0 ; i < n ; i++) {for (int j = 0 ; j < n ; j++) {dp[i][j] = Math.min(dp[i][j], dp[i][k]+dp[k][j]);}}
}
Python:
dp = [[graph[i][j] for i in range(n)] for j in range(n)]for k in range(n):for i in range(n):for j in range(n):dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j])
9. 强连通分量
可以用tarjan算法找到图中强连通分量的大小\每个节点属于哪个强连通分析.
ps:
强连通分量 (SCC)
在有向图中,一个强连通分量是最大的子图,其中任何两个顶点 u 和 v 都是相互可达的,即存在从 u 到 v 的路径,也存在从 v 到 u 的路径。
Python:
def tarjan(n, adj):dfn = [0] * n # 访问节点时的时间戳low = [0] * n # 节点可达的最低时间戳in_stack = [False] * n # 布尔数组,用于检查节点是否在栈中stack = [] # 用于存储节点的栈dfncnt = 1 # 用于给访问的节点分配唯一编号的计数器scc = [0] * n # 存储每个节点所属的强连通分量(SCC)编号的数组sc = 0 # 找到的SCC数量的计数器sz = [0] * n # 每个SCC的大小def dfs(u):nonlocal dfncnt, scdfn[u] = low[u] = dfncnt # 给节点分配时间戳dfncnt += 1stack.append(u) # 将当前节点加入栈in_stack[u] = True # 标记节点为在栈中for v in adj[u]: # 遍历每个相邻节点if dfn[v] == 0: # 如果节点未被访问dfs(v)low[u] = min(low[u], low[v]) # 更新当前节点的最低可达时间戳elif in_stack[v]: # 如果相邻节点在栈中low[u] = min(low[u], dfn[v]) # 更新当前节点的最低可达时间戳,仅包括在栈中的节点# 如果当前节点是SCC的根节点if dfn[u] == low[u]:sc += 1while True:v = stack.pop() # 弹出节点in_stack[v] = False # 标记节点不在栈中scc[v] = sc # 分配SCC编号sz[sc - 1] += 1 # 增加SCC大小if v == u: # 如果回到根节点,结束循环break# 从每个未访问的节点运行DFSfor i in range(n):if dfn[i] == 0:dfs(i)return scc, sz # 返回SCC编号和每个SCC的大小
十、区间相关
1. 前缀和
适用场景
多次求区间和。O(n)的时间预处理出前缀和数组后,可以O(1)求出区间的和。不支持区间修改。
C++:
vector<int> nums;
int n;vector<int> pre_sum(n + 1, 0);for (int i = 1 ; i <= n ; i++ ) pre_sum[i] = pre_sum[i-1] +nums[i-1];//查询区间和[left, right], 其中left,right是下标。
int sum = pre_sum[right+1] - pre_sum[lef t];
Java:
int[] nums;
int n;int[] pres = new int[n + 1];for (int i = 1; i <= n; i++) {pre_sum[i] = pre_sum[i-1] +nums[i-1];
}//查询区间和[left, right], 其中left,right是下标。
int sum = pre_sum[right+1] - pre_sum[left];
Python:
#python语法糖可以求前缀和
pres = list(accumulate(a,initial=0))
2. 二维前缀和
C++:
vector<vector<int>> matrix;
int m = matrix.size(), n = matrix[0].size();
vector<vector<int>> pre(m + 1, vector<int>(n + 1));
for (int i = 1; i <= m; i++) {for (int j = 1; j <= n; j++) {pre[i][j] = pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1] + matrix[i - 1][j - 1];}
}# 查询子矩阵的和 [x1,y1] [x2,y2]表示子矩阵的左上和右下两个顶点
int sum = pre[x2 + 1][y2 + 1] - pre[x1][y2 + 1] - pre[x2 + 1][y1] + pre[x1][y1];
Java:
int[][] matrix;
int m = matrix.length, n = matrix[0].length;
int[][] pre = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {for (int j = 1; j <= n; j++) {pre[i][j] = pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1] + matrix[i - 1][j - 1];}
}# 查询子矩阵的和 [x1,y1] [x2,y2]表示子矩阵的左上和右下两个顶点
int sum = pre[x2 + 1][y2 + 1] - pre[x1][y2 + 1] - pre[x2 + 1][y1] + pre[x1][y1];
Python:
matrix # 原二维矩阵
m, n = len(matrix), len(matrix[0])
pre = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):for j in range(1, n + 1):pre[i][j] = pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1] + matrix[i - 1][j - 1]# 查询子矩阵的和 [x1,y1] [x2,y2]表示子矩阵的左上和右下两个顶点
sum_ = pre[x2 + 1][y2 + 1] - pre[x1][y2 + 1] - pre[x2 + 1][y1] + pre[x1][y1];
3. 差分
适用场景
给定一个数组和多次区间修改的操作,求修改后的数组。
C++:
int diff[10001];void update(int l, int r, int val) {diff[l] += v;if (r+1 <n) diff[r+1] -= v;
}int main() {int nums[] = {1,3,2,4,5};int n = sizeof(nums) / sizeof(int);diff[0] = nums[0];for(int i = 1; i < n; i++) diff[i] = nums[i] - nums[i - 1];//多次调用update后,对diff数组求前缀和可以得出 多次修改后的数组int* res = new int[n]; //修改后的数组res[0] = diff[0];for (int i=1 ; i<=n ; i++) res[i] += res[i-1] + diff[i];
}
Java:
int[] nums = {1,3,2,4,5};
int n = nums.length;int[] diff = new int[n];
diff[0] = nums[0];
for (int i=0; i<n;i++) diff[i] = nums[i] - nums[i-1];void update(int l, int r, int v) {diff[l] += v;if (r+1<n) diff[r+1] -=v;
}int[] res = new int[n];
res[0] = diff[0];
for(int i=1;i<n;i++)res[i]=res[i-1]+diff[i];
Python:
nums = [1,3,2,4,5]
n = len(nums)
diff = [1 for _ in range(n)]
for i in range(1, n):diff[i] = nums[i] - nums[i-1]#将区间[l,r]的元素都加上v
def update(l, r, v):diff[l] += vif r+1 < n:diff[r+1] -= v'''多次调用update后,对diff数组求前缀和可以得出 多次修改后的数组'''
res = [0 for _ in range(n)]
res[0] = diff[0]
for i in range(1,n):res[i] += res[i-1] + diff[i]
4. 二维差分
C++:
vector<vector<int>> matrix; //原数组int n, m ; //长宽vector<vector<int>> diff(n+1, vector<int>(m+1, 0));void insert(int x1, int y1, int x2, int y2, int d) {diff[x1][y1] += d;diff[x2+1][y1] -= d;diff[x1][y2+1] -= d;diff[x2+1][y2+1] += d;
}for (int i = 1 ; i <= n ; i++ ) {for (int j = 1 ; j <= m ; j++) {insert(i,j,i,j,matrix[i][j]);}
}int q; //修改次数
cin >> q;
while (q--) {int x1,y1,x2,y2,d;cin >> x1 >> y1 >> x2 >> y2 >> d;insert(x1,y1,x2,y2,d);
}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] - matrix[i-1][j-1] + diff[i][j];}
}// matrix就是复原后的数组
Java:
int[][] matrix; //原数组
int n,m; // 原数组的行列
int[][] diff; //差分数组void insert(int x1, int y1, int x2, int y2, int d) {diff[x1][y1] += d;diff[x2+1][y1] -= d;diff[x1][y2+1] -= d;diff[x2+1][y2+1] += d;
}void solution() {diff = new int[n+1][m+1];for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {insert(i,j,i,j, matrix[i][j]);}}int q ; //修改次数while (q-- > 0) {int x1,y1,x2,y2,d; // 输入需要修改的子矩阵顶点insert(x1,y1,x2,y2,d);}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] - matrix[i-1][j-1] + diff[i][j];}}}
Python:
'''二维矩阵依然可以进行差分运算'''n, m = 0,0 #行和列
a = [[0 for _ in range(m+1)] for _ in range(n+1)] #原数组
diff = [[0 for _ in range(m+1)] for _ in range(n+1)]
def insert(x1, y1,x2,y2,d):diff[x1][y1] += ddiff[x2+1][y1] -= ddiff[x1][y2+1] -= ddiff[x2+1][y2+1] += d#差分数组初始化
for i in range(1, n+1):for j in range(1,m+1):insert(i,j,i,j,a[i][j])q = 0 #修改次数while q:q-=1x1,y1,x2,y2,d = 0,0,0,0,0 #对于矩阵的值增加dinsert(x1,y1,x2,y2,d)#还原数组
for i in range(1,n+1):for j in range(1,m+1):a[i][j] = a[i-1][j] + a[i][j-1] -a[i-1][j-1]+ diff[i][j]print(a)
5. 树状数组
适用场景
当我们进行 单点修改,然后进行区间 区间查询、单点查询的时候,适用树状数组可以在logn的复杂度求解。
C++:
vector<int> tree(MAXN, 0); int lowbit(int x) {return x&(-x);
}
// 单点修改,第i个元素增加x
inline void update(int i, int x)
{for (int pos = i; pos < MAXN; pos += lowbit(pos))tree[pos] += x;
}
// 查询前n项和
inline int query(int n)
{int ans = 0;for (int pos = n; pos; pos -= lowbit(pos))ans += tree[pos];return ans;
}// 查询区间[a,b]的和
inline int query(int a, int b)
{return query(b) - query(a - 1);
}
Java:
int[] tree; //长度为n,初始化为0int N;
int lowbit(int x) {return x&(-x);}
// 单点修改,第i个元素增加x
void update(int i, int x) {for (int p = i ; p < N ; p+=lowbit(p)) tree[p] += x;
}
// 查询前n项和
int query(int n) {int ans = 0;for (int p = n ; p; p -= lowbit(p)) ans += tree[p];return ans;
}
// 查询区间[a,b]的和
int query(int a, int b) {return query(b) - query(a-1);
}
Python:
class BIT:def __init__(self, n):self.MXN = n+1self.tree = [0 for _ in range(self.MXN)]def lowbit(self,x):return x & (-x)# 下标为index的元素新增xdef update(self,index, x):i = index+1 #树状数组的下标从1开始while i < self.MXN:self.tree[i] += xi += self.lowbit(i)# 查询前n项总和def queryPre(self,n):ans = 0while n:ans += self.tree[n]n -= self.lowbit(n)return ans# 查询区间[a,b]的和def query(self,a, b):return self.queryPre(b+1) - self.queryPre(a)
6. 线段树
适用场景
当我们需要区间修改、区间查询、单点查询的时候,可以使用线段树,能够在logn的复杂度下求解。
C++:
#define MAXN 100005
typedef long long ll;
ll n, m, A[MAXN], tree[MAXN * 4], mark[MAXN * 4]; // A原数组、 tree线段树数组、mark懒标记数组
inline void push_down(ll p, ll len)
{mark[p * 2] += mark[p];mark[p * 2 + 1] += mark[p];tree[p * 2] += mark[p] * (len - len / 2);tree[p * 2 + 1] += mark[p] * (len / 2);mark[p] = 0;
}
// 建树
void build(ll l = 1, ll r = n, ll p = 1)
{if (l == r)tree[p] = A[l];else{ll mid = (l + r) / 2;build(l, mid, p * 2);build(mid + 1, r, p * 2 + 1);tree[p] = tree[p * 2] + tree[p * 2 + 1];}
}
void update(ll l, ll r, ll d, ll p = 1, ll cl = 1, ll cr = n)
{if (cl > r || cr < l)return;else if (cl >= l && cr <= r){tree[p] += (cr - cl + 1) * d;if (cr > cl)mark[p] += d;}else{ll mid = (cl + cr) / 2;push_down(p, cr - cl + 1);update(l, r, d, p * 2, cl, mid);update(l, r, d, p * 2 + 1, mid + 1, cr);tree[p] = tree[p * 2] + tree[p * 2 + 1];}
}
ll query(ll l, ll r, ll p = 1, ll cl = 1, ll cr = n)
{if (cl > r || cr < l)return 0;else if (cl >= l && cr <= r)return tree[p];else{ll mid = (cl + cr) / 2;push_down(p, cr - cl + 1);return query(l, r, p * 2, cl, mid) + query(l, r, p * 2 + 1, mid + 1, cr);}
}
/**
1.输入数组A,注意下标从[1,n]。
2.调用update(l,r,d)函数,这里的l和r并不是下标。
3.调用query(l,r) 这里的l和r并不是下标
*/
Java:
int MXN = 10005 ;
int n;
int[] A,tree,mark;// 初始化
void init(int n) {this.n = n;A = new int[MXN];tree = new int[MXN *4];mark = new int[MXN *4];
}
void push_down(int p, int len) {mark[p * 2] += mark[p];mark[p * 2 + 1] += mark[p];tree[p * 2] += mark[p] * (len - len / 2);tree[p * 2 + 1] += mark[p] * (len / 2);mark[p] = 0;
}
//构建线段树
void build() {build(1,n,1);
}
void build(int l, int r, int p) {if (l==r) tree[p] = A[l];else {int mid = (l+r) / 2;build(l,mid, p*2);build(mid+1,r,p*2+1);tree[p] = tree[p*2] + tree[p*2+1];}
}
// 区间[l,r]的值新增d
void update(int l, int r, int d) {update(l,r,d, 1,1,n);
}
void update(int l, int r, int d, int p, int cl, int cr) {if (cl > r || cr < l) return;else if (cl >= l && cr <= r) {tree[p] += (cr - cl + 1) * d;if (cr > cl) mark[p] += d;}else {int mid = (cl+cr) / 2;push_down(p, cr-cl+1);update(l,r,d,p*2,cl,mid);update(l,r,d,p*2+1,mid+1,cr);tree[p] = tree[p*2] + tree[p*2 + 1];}
}// 查询区间[l,r]的和
int query(int l, int r) {return query(l, r,1,1,n);
}
int query(int l, int r, int p, int cl, int cr) {if (cl > r || cr < l) return 0;else if (cl >= l && cr <= r) return tree[p];else {int mid = (cl + cr) / 2;push_down(p, cr-cl+1);return query(l,r,p*2,cl,mid) + query(l,r,p*2+1,mid+1,cr);}
}void solution() {Scanner sc = new Scanner(System.in);int n = sc.nextInt();init(n);for (int i = 1; i <= n; i++) {A[i] = sc.nextInt(); // 输入元素}build();update(1,2,2); // 更新区间[0,1],新增2, 这里并不是下标!!!System.out.println(query(1,5)); // 查询区间[l,r]的总和,这里并不是下标!!!}
Python:
MXN = int(1e5 + 5)
n = int(input())#数组长度
A = [0] + [int(c) for c in input().split(" ")]
tree = [0 for _ in range(MXN * 4)]
mark = [0 for _ in range(MXN * 4)]def push_down(p, len):mark[p * 2] += mark[p]mark[p * 2 + 1] += mark[p]tree[p * 2] += mark[p] * (len - len // 2)tree[p * 2 + 1] += mark[p] * (len // 2)mark[p] = 0def build(l=1, r=n,p=1):if l==r: tree[p] = A[l]else:mid = (l+r) // 2build(l,mid,p*2)build(mid+1,r,p*2+1)tree[p] = tree[p*2] + tree[p*2 + 1]def update(l,r,d,p=1,cl=1,cr=n):if cl > r or cr < l: returnelif cl >= l and cr <= r:tree[p] += (cr - cl + 1) * dif cr > cl: mark[p] += delse:mid = (cl + cr) // 2push_down(p, cr-cl+1)update(l,r,d,p*2,cl,mid)update(l,r,d,p*2+1,mid+1,cr)tree[p] = tree[p*2] + tree[p*2+1]def query(l,r,p=1,cl=1,cr=n):if cl > r or cr < l: return 0elif cl >= l and cr <= r: return tree[p]else:mid = (cl + cr) // 2push_down(p, cr-cl+1)return query(l,r,p*2,cl,mid) + query(l,r,p*2+1,mid+1,cr)'''
1.输入数组A,注意下标从[1,n]。
2.调用update(l,r,d)函数,这里的l和r并不是下标。
3.调用query(l,r) 这里的l和r并不是下标
'''
十一、杂项
1. 求质数/素数
适用场景
筛法求质数,时间复杂度约为O(n)。
C++:
int cnt;
vector<int> primes;
bool st[N];
void get_primes(int n) {for (int i = 2 ; i <= n ; i++) {if (!st[i]) {primes.push_back(i);for (int j = i + i ; j <= n ; j += i) st[j] = true;}}
}
Java:
int cnt;
List<Integer> primes;
boolean[] st = new boolean[N];
void get_primes(int n) {for (int i = 2 ; i <= n ; i++) {if (!st[i]) {primes.add(i);for (int j = i+i ; j <= n ; j+=i) st[j] = true;}}
}
Python:
maxCNt
primes = [] #存储了组后的素数
st = [False for _ in range(maxCNt)]
index = 0
for i in range(2, maxCNt):if not st[i]:primes.append(i)for j in range(i+i, maxCNt, i): st[j] = True
2. 求约数
适用场景
根号N的时间复杂度下求出一个数字的所有约数。
C++:
vector<int> get_divisors(int n) {vector<int> res;for (int i = 1; i <= n / i ; i++) {if (n % i == 0) {res.push_back(i);if (i != n / i) res.push_back(n / i);}}sort(res.begin(), res.end());return res;
}
Java:
List<Integer> get_divisors(int n) {List<Integer> res = new LinkedList();for (int i = 1 ; i <= n /i ; i++) {if (n%i == 0) {res.add(i);if (i!=n/i) res.add(n/i);}}Collections.sort(res);return res;
}
Python:
def get_divisors(n: int):res = []i = 1while i <= n//i:if n%i==0:res.append(i)if i!=n//i: res.append(n//i)i+=1res.sort()return res
3. 快速幂
适用场景
快速的求出x的y次方。时间复杂度O(logn)
C++:
long long fast_pow(int x, int y, int mod) {long long res = 1;while (y > 0) {if (y % 2 == 1) {res = (long long)res * x % mod;}x = (long long)x * x % mod;y /= 2;}return res;
}
Java:
long fastPow(int x, int y, int mod) {long res = 1;while (y > 0) {if (y % 2 == 1) {res = ((long)res * x % mod);}x = ((long)x * x % mod);y /= 2;}return res;
}
Python:
def fast_pow(x, y, mod):res = 1while y > 0:if y % 2 == 1:res = (res * x) % modx = (x * x) % mody //= 2return res
4. 离散化
适用场景
当数据值比较大的时候,可以映射成更小的值。例如[101,99,200] 可以映射成[1,0,2]。
C++:
int A[MAXN], C[MAXN], L[MAXN]; //原数组为A
memcpy(C, A, sizeof(A)); // 复制原数组到C中
sort(C, C + n); // 数组排序
int l = unique(C, C + n) - C; // 去重
for (int i = 0; i < n; ++i)L[i] = lower_bound(C, C + l, A[i]) - C + 1; // 二分查找
Java:
int[] A = new int[MAXN];
int[] C = new int[MAXN];
int[] L = new int[MAXN];System.arraycopy(A, 0, C, 0, A.length); // 复制原数组到C中
Arrays.sort(C); // 数组排序
int l = Arrays.stream(C).distinct().toArray().length; // 去重
for (int i = 0; i < n; ++i)L[i] = Arrays.binarySearch(C, 0, l, A[i]) + 1; // 二分查找
Python:
a = [] #原数组
as = sorted(list(set(a)))
LS = [bisect.bisect_left(as,a[i]) for i in range(n)]
#其中,LS[i]表示的就是a[i]对应的离散后的下标。
5. 优先队列
C++:
priority_queue<int, vector<int>, [](int a, int b) {return a>b;}; // 队列存储int类型比较
priority_queue<int, vector<vector<int>>, [](const vector<int>& a, const vector<int>& b) {return a[1]>b[1];}; // 队列存储vector类型,按照第二个元素进行排序
Java:
PriorityQueue<Integer> pq = new PriorityQueue<Integer>((a,b)->a-b);// 队列存储int类型比较
PriorityQueue<int[]> pq = new PriorityQueue<int[]>((a,b)->a[1]-b[1]); // 队列存储vector类型,按照第二个元素进行排序
Python:
h = []
heapq.heappush(h, x) #插入元素
heapq.heappop(h) #弹出最小元素
6. 同余取模
你需要将最终答案表示成一个分数 a/b,其中a和b是互质的整数,但是题目要求你不是直接输出这个分数, 而是一个整数x,满足:
Python:
MOD = 10**9 + 7
# 使用费马小定理快速计算逆元(只适用于m是质数的情况)
def fast_inv(a, m=MOD):return pow(a, m-2, m)# 计算a/b mod MOD的值
def compute(a, b):# 计算b的逆元b_inv = fast_inv(b)# 计算并返回结果return (a * b_inv) % MOD# 示例
a = 2
b = 3
result = compute(a, b)
print(f"The result is: {result}")
相关文章:
常见算法模板总结
文章目录 一、二叉树1. DFS2. BFS 二、回溯模板三、记忆化搜索四、动态规划1. 01背包朴素版本滚动数组优化 2. 完全背包朴素版本滚动数组优化 3. 最长递增子序列LIS朴素版本贪心二分优化 4. 最长公共子序列5. 最长回文子串 五、滑动窗口六、二分查找七、单调栈八、单调队列九、…...
生产者消费者模型
目录 一、生产者消费者模型 1. 生产者消费者模型是什么? 2. 为什么使用生产者消费者模型? 3. 生产者消费者模型的特点(321原则) 🌵3种关系 🌵2种角色 🌵1个交易场所 二、基于BlockingQue…...
linux里怎么禁用 其他用户使用sudo添加定时器,例如创建的tomcat用户禁止使用 sudo crontab -u tomcat -e 添加定时器
要禁止 tomcat 用户通过 sudo crontab -u tomcat -e 添加定时任务,需从 sudo 权限控制和 crontab 访问限制两方面入手。以下是具体步骤: 一、核心思路 禁止 tomcat 用户使用 sudo 提权执行 crontab 修改 /etc/sudoers 配置,移除 tomcat 用户…...
函数作为返回值输出
实际上,函数当做返回值输出的应用场景也很多,也更能体现函数式变成的巧妙,让函数继续返回一个可执行的函数,意味着运算过程是可延续的。 判断数据的类型 常见的判断一个数据的类型的函数: 单例模式 下面是一个单例模…...
黑马 SpringAI+DeepSeek 实战:从对话机器人到企业级知识库的大模型开发全攻略
附完整代码 项目案例,3 天吃透大模型应用开发核心技术 需要完整项目学习视频以及源码的私信博主,谢谢~大家一起加油呐!! 01.认识AI和大模型 小结 AI的发展过程 符号主义 机器学习 深度学习——自然语言处理(NLP…...
word表格间隔设置
1.怎么解决word表格间隔达不到我们想要的要求 其实很简单, 我们直接在word表格里面, 全选表格中里面的内容。接着,我们选择自动调整---->根据内容自动调整表格,即可达到我们想要的要求...
Windows批处理脚本,bat 循环数组进入文件夹进行后续操作
文章目录 前言一、脚本功能解析1.2、定义数组1.2、遍历数组1.2、处理每个数组元素1.2、循环控制1.2、结束脚本 二、之前编写的脚本三、优化后的脚本代码四、总结五、感谢 前言 Windows批处理脚本,主要功能是遍历一个预定义的数组,并对每个数组元素执行cd…...
TurtleBot3 Package turtlebot3_drive source code read
前言 此处阅读简单的 turtlebot3_drive 代码。 从ROS的角度,作为一个demo,它足够小、简单,可以从中看见ROS的 NodeHandle如何使用。此外,我们也能简单地看到 “自动避障功能的实现”。 从C的角度,它实际上并不复杂&…...
机器学习的下一个前沿是因果关系吗?
如今,越来越多研究人员意识到,将因果关系融入机器学习,或许会是该领域实现重大突破的关键所在。 机器学习凭借先进的预测能力,已为诸多行业带来了显著变革,但也暴露出了一定的局限性。而因果关系,作为理解…...
Mujoco xml模型
Mujoco xml模型 一个例子compileroptionassetmesh default基本使用childclass与class多个class worldbodybody关系inertialjointgeom XML主要分为以下三个部分: < asset> : 用 tag导入STL文件;< worldbody>:用tag定义…...
MyBatis 详解及代码示例
MyBatis 是一个 半自动 ORM 框架,主要用于 Java 与数据库之间的持久化操作,它本质是对 JDBC 的封装 全名:MyBatis(前身 iBATIS)核心作用:自动将 SQL 执行结果映射为 Java 对象;也可以将 Java 对…...
STL-list链表
STL-list链表实现 STL中采用双向带头循环链表来实现 list,下面将使用 C++ 实现 STL list 链表。 list 类中包含两个主要部分,一个是指向哨兵位头节点的指针(_head),另一个是结构体类型的迭代器(__list_iterator)。 哨兵位头节点本身是不存储数据的,它只是用于简化代码…...
R语言中的rvest库写个视频爬虫通用代码
朋友让我用R语言的rvest库写一个通用的视频爬虫代码示例。首先,我需要回忆一下rvest库的主要功能,它主要是用来做网页抓取和解析的,类似于Python的BeautifulSoup。但是视频爬虫的话,可能需要处理动态加载的内容,或者找…...
SQLite 中日期型数据定义及处理(Delphi 版本)
在使用SQLite的时候,肯定需要使用到日期型数据类型,但是SQLite没有直接支持日期类型,比如在其他数据库中支持的DateTime类型,在Delphi中是TDateTime类型。 那么实际处理中应该如何处理呢? 可以使用两种方式类在SQLit…...
4.9复习记
1.地宫取宝--记忆化搜索,可以先写void dfs,然后在改成ll 形式的,边界条件return 0/1; 记忆化数组与dfs元素保持一致,记得记忆化剪枝 这个题特殊在value可能是0,不取的时候应该记为-1 https://mpbeta.cs…...
Flink基础
Flink基础 目录 Flink简介核心概念编程模型核心功能应用场景部署模式生态系统最佳实践学习资源实践案例高级特性 1. Flink简介 1.1 什么是Flink Apache Flink是一个开源的分布式流处理和批处理系统。它能够处理有界(批处理)和无界(流处理…...
SASE、零信任安全理念的发展脉络
SASE(安全访问服务边缘)与零信任架构的发展脉络,是云安全理念从 “边界防御” 向 “动态信任” 跃迁的典型缩影。两者的演进既独立又交织,共同推动网络安全从静态合规走向主动治理。以下从技术起源、理念突破、产业实践到未来趋势展开深度解析: 一、零信任:从理论构想到…...
CompletableFuture 和 List<CompletableFuture> allOf() join() get() 使用经验
CompletableFuture<Map<Menu, Map<IntentDetail, Double>>> xxx CompletableFuture.supplyAsync(() -> {Map<Menu, Map<IntentDetail, Double>> scores new ConcurrentHashMap<>();// 存储结果scores.computeIfAbsent(menu, k -> n…...
Vue.js组件化开发实战:从工程化到安全纵深设计
文章目录 开篇:现代前端组件化演进之路 组件设计核心:高内聚低耦合实践 工程化基石:从Webpack到Monorepo 安全纵深设计:RASP在组件层的实现 实战:动态表单组件的三次进化 进阶篇:组件工厂模式与策略模…...
【深度解析】SkyWalking 10.2.0版本安全优化与性能提升实战指南
前言 Apache SkyWalking 作为云原生可观测性领域的佼佼者,在微服务架构监控中扮演着至关重要的角色。然而,官方版本在安全性、镜像体积和功能扩展方面仍有优化空间。本文将分享一套完整的 SkyWalking 10.2.0 版本优化方案,从安全漏洞修复到镜…...
NOIP2011提高组.玛雅游戏
目录 题目算法标签: 模拟, 搜索, d f s dfs dfs, 剪枝优化思路*详细注释版代码精简注释版代码 题目 185. 玛雅游戏 算法标签: 模拟, 搜索, d f s dfs dfs, 剪枝优化 思路 可行性剪枝 如果某个颜色的格子数量少于 3 3 3一定无解因为要求字典序最小, 因此当一个格子左边有…...
常微分方程求解全解析:从基础到矩阵方法深度实践
常微分方程求解全解析:从基础到矩阵方法深度实践 一、常微分方程基础与解法体系 1.微分方程基本概念解析 常微分方程的阶数指方程中未知函数导数的最高阶数。通解是包含任意常数且常数个数与方程阶数相同的解,特解则是通解中任意常数取特定值得到的解。以自由落体运动为例…...
Go 微服务框架 | 中间件
文章目录 定义中间件前置中间件后置中间件路由级别中间件 定义中间件 中间件的作用是给应用添加一些额外的功能,但是不会影响原有应用的编码方式,想用的时候直接添加,不想用的时候也可以轻松去除,实现所谓的可插拔。中间件的实现…...
【HarmonyOS Next之旅】DevEco Studio使用指南(十二)
目录 1 -> Code Linter代码检查 2 -> 配置代码检查规则 3 -> 查看/处理代码检查结果 1 -> Code Linter代码检查 Code Linter针对ArkTS/TS代码进行最佳实践/编程规范方面的检查。 可根据扫描结果中告警提示手工修复代码缺陷,或者执行一键式自动修复…...
Java设计模式之桥接模式:从入门到架构级实践
1. 什么是桥接模式? 桥接模式(Bridge Pattern) 是一种结构型设计模式,其核心目标是将抽象部分与实现部分分离,使它们能够独立变化。通过这种方式,桥接模式解决了多层继承带来的复杂性,并增强了…...
Jupyter Lab 无法启动 Kernel 问题排查与解决总结
📄 Jupyter Lab 无法启动 Kernel 问题排查与解决总结 一、问题概述 🚨 现象描述: 用户通过浏览器访问远程服务器的 Jupyter Lab 页面(http://xx.xx.xx.xx:8891/lab)后,.ipynb 文件可以打开,但无…...
【LeetCode 热题100】73:矩阵置零(详细解析)(Go语言版)
🚀 力扣热题 73:矩阵置零(详解 多种解法) 📌 题目描述 给定一个 m x n 的整数矩阵 matrix,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请你 原地 使用常量空间解决。 Ἲ…...
OminiAdapt:学习跨任务不变性,实现稳健且环境-觉察的机器人操作
25年3月来自中科大、北理工和中科院自动化所的论文“OminiAdapt: Learning Cross-Task Invariance for Robust and Environment-Aware Robotic Manipulation”。 随着具身智能的快速发展,利用大规模人体数据对人形机器人进行高水平的模仿学习,成为学术界…...
Vue3中父组件将一个ref定义的对象类型传递给子组件的解包机制
在Vue3中,当父组件将一个ref定义的对象类型传递给子组件时,子组件接收到的不是原始的Ref类型,而是该ref的.value值,即被解包后的响应式对象。具体行为如下: 关键点解析: 自动解包机制: Vue3在模…...
批量将 SVG 转换为 jpg/png/Word/PDF/ppt 等其它格式
SVG(可缩放矢量图形)是一种广泛使用的图像格式,因其矢量特性在不同分辨率下都能保持清晰,但在某些情况下,用户可能需要将 SVG 格式的图片转换为更常见的位图格式,如 JPG、PNG 等,以适应不同平台…...
微服务篇——SpringCloud
服务注册 Spring Cloud5大组件有哪些? 服务注册和发现是什么意思?Spring Cloud如何实现服务注册发现? nacos与eureka的区别 负载均衡 如何实现负载均衡? Ribbon负载均衡的策略有哪些? 如何自定义负载均衡的策略&…...
Windows 11 家庭中文版 安装docker desktop 无法开启自启动问题处理
前言 我在某台Windows 11家庭中文版的电脑上安装Docker Desktop后,老是无法开机启动,已经按照Docker Desktop 设置调整的方式设置了开机启动,但是重启后发现还是无法自启动,需要手动点击启动。然后使用任务计划程序新建一个开机启…...
蓝桥杯备考
先浅学一遍数据结构,不会拉倒,找点简单题练练语法基础 然后边学边刷二分查找和双指针 递归和暴力,边学边刷 学习贪心,练个几十道 再去过下数据结构 开始算法:搜索,动态规划, 搜索很重要,深…...
Elasticsearch 系列专题 - 第一篇:Elasticsearch 入门
Elasticsearch 是一个功能强大的开源分布式搜索和分析引擎,广泛应用于日志分析、实时搜索、数据可视化等领域。本篇将带你了解 Elasticsearch 的基本概念、安装方法以及简单操作,帮助你快速上手。 1. 什么是 Elasticsearch? 1.1 Elasticsearch 的定义与核心概念 Elasticse…...
【LeetCode 题解】数据库:1321.餐馆营业额变化增长
一、问题描述 本题给定了一个名为 Customer 的表,记录了餐馆顾客的交易数据,包括顾客 ID、姓名、访问日期和消费金额。作为餐馆老板,我们的任务是分析营业额的变化增长情况,具体来说,就是计算以 7 天(某日…...
Apache Nifi安装与尝试
Apache NIFI中文文档 地址:https://nifichina.github.io/ 下载安装配置 1、环境准备 Nifi的运行需要依赖于java环境,所以本机上需要安装java环境,并配置环境变量。 1.1查看本机是否已经存在java环境 请先执行以下命令找出系统中真实可用…...
【Git 常用操作指令指南】
一、初始化与配置 1. 设置全局账户信息 git config --global user.name "用户名" # 设置全局用户名 git config --global user.email "邮箱" # 设置全局邮箱 --global 表示全局生效,若需针对单个仓库配置,可省略该参数 2.…...
Django 生成PDF文件
在这里,我们将学习如何使用Django视图设计和生成PDF文件。我们将使用ReportLab Python PDF库生成PDF,该库可以创建定制的动态PDF文件。 这是一个开源库,可以通过在Ubuntu中使用以下命令轻松下载。 $ pip install reportlab Python Copy …...
多账户使用Github的场景,设置 SSH 多账号使用特定 key
遇到多账户使用Github的场景,常难以管理ssh文件 解决方案: 你可以通过配置 ~/.ssh/config 文件,生成多个SSH key 让 Git 识别不同 key 来对应不同 GitHub 账号。 ✅ 正确的 key 类型有这些常见选项: rsa:老牌算法&a…...
js中this指向问题
在js中,this关键字的指向是一个比较重要的概念,它的值取决于函数的调用方式。 全局状态下 //全局状态下 this指向windowsconsole.log("this", this);console.log("thiswindows", this window); 在函数中 // 在函数中 this指向win…...
BabelDOC ,开源的 AI PDF 翻译工具
BabelDOC 是一款开源智能 PDF 翻译工具,专门为科学论文的翻译而设计。它能够在原文旁边生成翻译文本,实现双语对照,用户无需频繁切换窗口,极大提升了阅读的便利性。此外,BabelDOC 能够完整保留数学公式、表格和图形&am…...
Dify 生成提示词的 Prompt
Dify 生成提示词的 Prompt **第1次提示词****第2次提示词****第3次提示词**总结 Dify 生成提示词是,会和LLM进行3次交互,下面是和LLM进行交互是的Prompt。 以下是每次提示词的概要、目标总结以及原始Prompt: 第1次提示词 概要: …...
在nvim的snippet补全片段中增加函数注释的功能
一、补全片段路径 如果使用nvim,应当在nvim的snippet的插件中增加对应补全的片段,目前我所用的补全的片段路径如下: /home/zhaoky/.local/share/nvim/site/pack/packer/start/vim-snippets.git/snippets我当前补全的是c语言所以使用的片段是c.snippets…...
阿里云负载均衡为何费用高昂?——深度解析技术架构与市场定价策略
本文深度解析阿里云负载均衡(SLB)产品的定价体系,从技术架构、安全防护、合规成本三个维度揭示费用构成逻辑。通过2023年某跨国企业遭受的混合型DDoS攻击案例,结合Gartner最新安全支出报告,给出企业级负载均衡成本优化…...
大数据(7)Kafka核心原理揭秘:从入门到企业级实战应用
目录 一、大数据时代的技术革命1.1 消息中间件演进史1.2 Kafka核心设计哲学 二、架构深度解构2.1 核心组件拓扑2.1.1 副本同步机制(ISR) 2.2 生产者黑科技2.3 消费者演进路线 三、企业级应用实战3.1 金融行业实时风控3.2 物联网数据管道 四、生产环境优化…...
01背包 Java
① 记忆化搜索解法: import java.util.*; import java.io.*;public class Main {static int n, m;static int[] v, w;static int[][] memory; // 记忆化数组public static void main(String[] args) throws Exception {BufferedReader br new BufferedReader(new …...
【Kafka基础】消费者命令行完全指南:从基础到高级消费
Kafka消费者是消息系统的关键组成部分,掌握/export/home/kafka_zk/kafka_2.13-2.7.1/bin/kafka-console-consumer.sh工具的使用对于调试、测试和监控都至关重要。本文将全面介绍该工具的各种用法,帮助您高效地从Kafka消费消息。 1 基础消费模式 1.1 从最…...
Seq2Seq - 编码器(Encoder)和解码器(Decoder)
本节实现一个简单的 Seq2Seq(Sequence to Sequence)模型 的编码器(Encoder)和解码器(Decoder)部分。 重点把握Seq2Seq 模型的整体工作流程 理解编码器(Encoder)和解码器(…...
@SchedulerLock 防止分布式环境下定时任务并发执行
背景 在一个有多个服务实例的分布式系统中,如果你用 Scheduled 来定义定时任务,所有实例都会执行这个任务。ShedLock 的目标是只让一个实例在某一时刻执行这个定时任务。 使用步骤 引入依赖 当前以redisTemplate为例子,MongoDB、Zookeeper…...
【力扣hot100题】(077)跳跃游戏
我最开始的想法还是太单纯了,最开始想着用回溯法,然后想到上一题的经验又想到了动态规划,虽然知道贪心题不太可能会这么复杂但实在想不出别的办法……果然我的智商做贪心题的极限就只能达到找零问题那种水平…… 最开始的方法,击…...