算法训练营记录

2020-10-12  本文已影响0人  重望沐

算法训练营

时间复杂度、空间复杂度

常数阶O(1)
对数阶O(logN)
线性阶O(n)
线性对数阶O(nlogN)
平方阶O(n²)
立方阶O(n³)
K次方阶O(n^k)
指数阶(2^n)

一维数据结构:线性表(array、stack、listNode、queue)、散列表(set、dictionary)

二维数据结构:堆、树、图

树是特殊的图:每个结点下有两个子结点
链表是特殊的树:每个结点下只有1个子节点

堆就是树,堆分为最大堆或者最小堆(根节点最大或者最小)
1、堆中某个节点的值总是不大于或者不小于其父节点的值
2、堆总是一颗完全二叉树

二叉树的 前序、中序、后续
前序:根-左-右
中序:左-根-右
后续:左-右-根

二叉搜素树、二叉查找树、有序二叉树、排序二叉树,是指一颗空树或者具有下列性质的二叉树
1、左子树上所有结点的值均小于它的根结点的值
2、右子树上所有结点的值均大于它的根结点的值
3、以此类推:左、右子树也分别为二叉查找树

中序遍历:升序遍历

广度优先
深度优先

递归代码模板:

    func recursion(level,param1,param2,...) {
        // recursion terminator 递归终结条件
        if level > MAX_LEVEL
            process_result
            return
        
        // pricess logic in current level 处理当前层级的业务逻辑
        process(level,data,...)
        
        // drill down 进入到下一层去
        self.recursion(level + 1,param1,param2,...)
        
        // reverse the current level status if needed 如果需要清理当前层
        
    }

递归的三个思维要点

1、抵制人肉递归
2、找最近重复性
3、数学归纳法思维

分治和回溯

分治和回溯是特殊的递归

二分查找的前提

1、目标函数单调性(递增或者递减)
2、存在上下界(bounded)
3、能够通过索引访问(index accessible)

动态规划

和递归或者分治 没有根本上的区别(关键看有无最优的子结构)
共性:找到重复子问题
差异性:最优子结构、中途可以淘汰次优解

关键点

1、最优子结构 opt[n] = best_of(opt[n-1],opt[n-2],...)
2、存储中间状态:opt[i]
3、递推公式(美名其曰:状态转移方程或者DP方程)
Fib: opt[n] = opt[n-1] + opt[n-2]
二维路径: opt[i,j] = opt[i+1,j] + opt[i,j+1];(且判断a[i,j]是否空地)

动态规划小结

1、打破自己的思维惯性,形成机械思维
2、理解复杂逻辑的关键
3、职业进阶的要点要领,信任下属并merge反馈

字典树、trie树

并查集

适用场景:组团和配对问题
group or not ?

平衡二叉树

AVL树、红黑树、B树

AVL树

平衡因子:深度不超过绝对值1 【-1,0,1】
旋转操作:
右右子树 -> 左旋
左左子树 -> 右旋
左右子树 -> 左右旋
右左子树 -> 右左
不足:结点需要存储额外信息,操作太频繁

红黑树:近似平衡二叉树

它能够确保任何一个结点的左右子树的==高度差小于两倍==。

位运算

运算符:

tips

异或运算的特征

常用整理:

布隆过滤器

一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中

LRUCache

排序算法

排序时间复杂度.jpg

比较类排序

非比较类排序

高级排序

1、取数组标杆pivot,将小元素放pivot左边的子数组,大元素放右边的子数组;
2、依次对两个子数组进行快排,以达到整个序列有序。

public static void quickSort(int[] array, int begin, int end) {
    if (end <= begin) return;
    int pivot = partition(array, begin, end);
    quickSort(array, begin, pivot - 1);
    quickSort(array, pivot + 1, end);
}
// 找出标杆的位置并将其左右排好序
static int partition(int[] a, int begin, int end) {
    // pivot: 标杆位置,counter: 小于pivot的元素的个数
    int pivot = end, counter = begin;  
    for (int i = begin; i < end; i++) {
        if (a[i] < a[pivot]) {
            int temp = a[counter]; a[counter] = a[i]; a[i] = temp;
            counter++;
        }
    }
    int temp = a[pivot]; a[pivot] = a[counter]; a[counter] = temp;
    return counter;
}

1、把长度为n的输入序列分成两个长度为n/2的子序列;
2、对着两个子序列分别采用归并排序;
3、将两个排序好的子序列合并成一个最终的排序序列;

public static void mergeSort(int[] array, int left, int right) {
    if (right <= left) return;
    //(left + right) / 2 位运算更加快速
    int mid = (left + right) >> 1;
    
    mergeSort(array, left, mid);
    mergeSort(array, mid + 1, right);
    merge(array, left, mid, right);
}
// 将排好序的两个数组合并
public static void merge(int[] arr, int left, int mid, int right) {
    // 中间数组,额外的空间复杂度
    int [] temp = new int[right - left + 1];
    int i = left, j = mid + 1, k = 0;
    
    while (i <= mid && j <= right) {
        temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
    }
    
    while (i <= mid) temp[k++] = arr[i++];
    while (j <= right) temp[k++] = arr[j++];
    // 将排序好的数组放回arr中
    for (int p = 0 ; p < temp.lent; p++) {
        arr[left + p] = temp[p];
    }
}

1、 数组元素一次建立小顶堆
2、依次取堆顶元素,并删除

void heap_sort(int a[], int len) {
    priority_queue<int, vector<int>, greater<int>> q;
    
    for(int i = 0; i < len; i++){
        q.push(a[i]);
    }
    
    for(int i = 0; i < len; i++) {
        a[i] = q.pop();
    }
}

归并 和 快排 具有相似性,但步骤顺序相反
归并: 先排序左右子数组,然后合并两个有序子数组
快排: 先调配处左右两个子数组,然后对左右子数组进行排序

上一篇 下一篇

猜你喜欢

热点阅读