算法

时间复杂度

  1. 线性时间复杂度算法
    1. 单调栈
    2. 前缀和
    3. 差分数组
    4. 双指针
    5. 滑动窗口

排序

桶排序

leetcode-164 最大间距

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 将数组拆分乘斐波那契序列

位运算

leetcode-5620 连接连续二进制数字

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)

数论

计算排列组合

leetcode-62 不同路径

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;
}
posted @ 2021-01-03 20:54  AlenDou  阅读(128)  评论(0编辑  收藏  举报