算法
时间复杂度
- 线性时间复杂度算法
- 单调栈
- 前缀和
- 差分数组
- 双指针
- 滑动窗口
排序
桶排序
max_, min_ = max(nums), min(nums)
bucket_len = max(1, (max_ - min_) // (n - 1)) # 确定桶的长度 最小为1
bucket_num = (max_ - min_) // bucket_len + 1 # 确定桶的数量 要加一
buckets = [[] for _ in range(bucket_num)] # 初始化所有桶
for num in nums: # 将所有元素添加到桶内
buckets[(num - min_) // bucket_len].append(num)
归并排序
将数组分为两个子数组,子数组各自排序后再合并
leetcode-493 翻转对
def merge(nums, lo, mid, hi): # 合并两个排序好的数组
aux = nums[lo : hi + 1] # 备份 储存原始值
i, j = lo, mid + 1 # 左右两个子数组的起点
for k in range(lo, hi + 1):
if (i > mid): # 左边数组已经全部合并
nums[k] = aux[j - lo]
j += 1
elif (j > hi): # 右边数组已经全部合并
nums[k] = aux[i - lo]
i += 1
elif aux[i - lo] < aux[j - lo]:
nums[k] = aux[i - lo]
i += 1
else:
nums[k] = aux[j - lo]
j += 1
def merge_sort(nums, lo, hi): # 归并排序 包括lo到hi的全部数字 列表表示应该是range(lo, hi+1)
if lo < hi:
mid = (lo + hi) // 2
merge_sort(nums, lo, mid)
merge_sort(nums, mid+1, hi)
merge(nums, lo, mid, hi)
二分
leetcode-4 寻找两个正序数组的中位数
leetcode-1712 将数组分成三个子数组的方案数
回溯
leetcode-46 全排列
leetcode-90 子集Ⅱ:集合去重
leetcode-842 将数组拆分乘斐波那契序列
位运算
a = a << 1 # 左移
a = a >> 1 # 右移
a = a & b # 与
a = a | b # 或
a = a ^ b # 异或 若a等于b^c 则有b等于a^c
~a # 非
int a = 22; (10110)
# 遍历所有1的组合
for (int x = a; x > 0; x = a & (x - 1)) {
cout << x << endl;
}
#输出22(10110),20(10100),18(10010),16(10000),6(00110),4(00100),2(00010)
# 遍历每个1
for (int x = a; x > 0; x = x & (x - 1)) {
cout << x << endl;
}
#输出22(10110),20(10100),16(10000)
数论
计算排列组合
def C(total, t):
ans = 1
for i in range(1, t + 1):
ans = ans * (total - i + 1) // i
return ans
快速幂
时间复杂度O(logN)。
leetcode-50 Pow(x,n)
def myPow(x, n): # 计算x^n n>=0
ans = 1.0
x_contribute = x
while n > 0:
if n % 2 == 1:
ans = ans * x_contribute
x_contribute *= x_contribute
n = n // 2
return ans
线性筛
O(n)时间复杂度内找到小于n的素数。
leetcode-204 计数质数
def countPrimes(n):
is_prime = [1] * n # 判断当前数字是否为素数
is_prime[0] = is_prime[1] = 0
primes = []
for i in range(2, n):
if is_prime[i]:
primes.append(i)
for prime in primes:
if prime * i < n:
is_prime[prime * i] = 0
if i % prime == 0: break # 后面的数一定可以被prime筛到 退出循环
else: break # 后面的素数*i也一定大于n 退出循环
return len(primes)
动态规划
leetcode-53 最大子序和:O(n)时间内求数组最大连续子数组和
leetcode-188 买卖股票的最佳时机IV
状态dp
leetcode-5619 最小不兼容性
字符串
找回文子串
动态规划O(N^2 ),中心扩展O(N^2 ),Manacher算法(N)
leetcode-5 最长回文子串
// valid[i][j]:字符串s[i]~s[j]是否为回文字符串
string s;
n = s.size();
vector<vector<bool>> valid(n, vector<bool>(n, true));
for (int i = n - 1; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
valid[i][j] = (s[i] == s[j]) & (valid[i + 1][j - 1]);
}
}
数据结构
优先队列
能够完成下列操作的数据结构叫做优先队列:
(1)插入一个数值
(2)取出最小的数值(获得数值,并且删除)
使用堆来进行解决,准确的说是二叉堆。
leetcode-1046 最后一块石头的重量
leetcode-295 数据流的中位数
leetcode-480 滑动窗口中位数
栈
leetcode-402 移掉K位数字
leetcode-316 去除重复字母
单调栈
线性时间复杂度下,维护一个单调递增或者递减的栈。
在原始顺序下,针对每一个元素找到下一个恰好大于或小于它的元素。
leetcode-42 接雨水
leetcode-84 柱状图中的最大矩形
leetcode-85 最大矩形
leetcode-321 拼接最大数
leetcode-5614 找出最具竞争力的子序列
leetcode-239 滑动窗口最大值
stack = []
heights = [0] + heights + [0] # 处理好边界
ans = 0
for i in range(len(heights)):
while stack and heights[stack[- 1]] > heights[i]:
cur = stack.pop()
ans = max(ans, heights[cur] * (i - 1 - stack[-1])) # 出栈时进行相应计算
stack.append(i)
return ans
单调队列
leetcode-1438 绝对差不超过限制的最长连续子数组
二叉搜索树
二叉搜索树能够高效进行如下操作:
(1)插入一个数值
(2)查询是否包含某个数值
(3)删除某个数值
二叉搜索树所有的节点满足左子树上的所有节点都比自己小,右子树上的所有节点都比自己大。
leetcode-98 验证二叉搜索树
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
# 二叉树的中序遍历
stack = []
while stack or root:
while root:
stack.append(root)
root = root.left
root = stack.pop()
print(root.val)
root = root.right
字典树
利用嵌套哈希表来构建字典树
0-1字典树
leetcode-421 数组中两个数的最大异或值
并查集
一种管理元素分组情况的数据结构,可以高效的进行一下操作:
(1)查询元素a和元素b是否属于同一组
(2)合并元素a和元素b所在的组
leetcode-1202 交换字符串中的元素
leetcode-684 冗余连接
leetcode-947 移除最多的同行或同列石头
# 初始化
for i in range(n):
father[i] = i
# 查询函数
def find(x): # 查询函数
if x == father[x]:
return x
else:
father[x] = find(father[x]) # 带路径压缩
return father[x]
# 合并函数
def merge(x, y, w):
father[father[x]] = father[y]
图论
Dijkstra
点的数量为n,边的数量为m。朴素的Dijkstra时间复杂度为\(O(n^2)\),使用堆优化的时间复杂度为\(O(mlogn)\)。
struct Edge {
int to, next, w;
} myEdges[80010];
int cnt = 0;
vector<int> head = vector<int>(20010, 0);
inline void addMyEdge(int p, int q, int w) {
myEdges[++cnt].to = q;
myEdges[cnt].w = w;
myEdges[cnt].next = head[p];
head[p] = cnt;
}
int solution(int n, vector<vector<int>>& edges) {
for (auto edge : edges) {
addMyEdge(edge[0] - 1, edge[1] - 1, edge[2]);
addMyEdge(edge[1] - 1, edge[0] - 1, edge[2]);
}
vector<int> dist(n, INT_MAX); // 距离
vector<bool> vis(n, false); // 是否访问过
typedef pair<int, int> P; // 堆中的元素要用pair类型
priority_queue<P, vector<P>, greater<P>> q; // 小根堆
dist[0] = 0; // 初始化起点的距离
q.push(make_pair(dist[0], 0)); // 将起点添加进堆里
while (!q.empty()) {
int p = q.top().second;
q.pop();
if (vis[p]) continue;
vis[p] = true;
for (int j = head[p]; j != 0; j = myEdges[j].next) { // 遍历所有相邻节点
int to = myEdges[j].to;
dist[to] = min(dist[to], dist[p] + myEdges[j].w); // 松弛距离
if (!vis[to]) {
q.push( make_pair(dist[to], to) );
}
}
}
}
SPFA
#include <iostream>
using namespace std;
struct Edge {
int to, w;
Edge (int to, int w) : to(to), w(w) {}
}
int MAX_N = 1000;
vector<Edge> edges[MAX_N];
void add(int from, int to, int w)
{
Edge e = {to, w};
edges[from].push_back(e);
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
add(u, v, w);
add(v, u, w);
}
vector<int> dist(n, 99999999);
dist[0] = 0;
queue<int> q;
vector<bool> inQueue(n, false);
inQueue[0] = true;
q.push(0);
while (!q.empty()) {
int node = q.front();
q.pop();
inQueue[node] = false;
for (auto& edge : edges[node]) {
if (dist[edge.to] > dist[node] + edge.w) {
dist[edge.to] = dist[node] + edge.w;
if (!inQueue[edge.to]) {
inQueue[edge.to] = true;
q.push(edge.to);
}
}
}
}
cout << dist[n - 1] << endl;
}
拓扑排序
vector<vector<int>> g(n); // 邻接表
vector<int> inDeg(n, 0); // 入度
for (auto& edge : edges) {
inDeg[edge[1]]++;
g[edge[0]].push_back(edge[1]); // 有向边
}
queue<int> q;
for (int i = 0; i < n; i++) {
if (inDeg[i] == 0) {
q.push(i);
}
}
int count = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
count++;
for (auto& node : g[u]) {
inDeg[node]--;
if (inDeg[node] == 0) {
q.push(node);
}
}
}
if (count != n) {
cout << "有环" << endl;
}