基础算法
1 | 评估算法优劣的核心指标是什么? |
时间复杂度
1 | 如何确定算法流程的时间复杂度? |
选择排序
1 | 过程: |
1 | //核心思想: |
冒泡排序
1 | 过程: |
1 | // 0 ~ N-1 |
插入排序
1 | 过程: |
1 | // 0~0 有序的 |
1 | 如果某个算法流程的复杂程度会根据数据状况的不同而不同,那么你必须要按照最差情况来估计。 |
额外空间复杂度
1 | 你要实现一个算法流程,在实现算法流程的过程中,你需要开辟一些空间来支持你的算法流程。 |
1 | 我们会发现,时间复杂度这个指标,是忽略低阶项和所有常数系数的。 |
算法流程的常数项
1 | 放弃理论分析,生成随机数据直接测。 |
二分法
1 | //在一个有序数组中,找某个数是否存在 |
1 | //在一个有序数组中,找>=value最左侧的位置 |
1 | //局部最小值问题(等于找数据的曲线变化转折点,数值连续下降再上升,就算是一个局部最小值) |
异或运算
1 | //如何不用额外变量交换两个数 |
1 | //注意:a 和 b 不能指向同一个地址空间,否则怎么交换都是0 |
1 | //一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数 |
1 | //怎么把一个int类型的数,提取出最右侧的1来 |
1 | //一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这两种数 |
1 | //提取整数n转换成二进制之后,他的中1的数量 |
栈和队列
1 | //用数组实现队列 |
1 | 实现一个特殊的栈,在基本功能的基础上,再实现返回栈中最小元素的功能 |
1 | 思想:用两个Stack,一个存储数据,一个存储最小值 |
1 | //开多个栈,push的时候压入min和push之中的最小值,弹出则都弹出 |
1 | //如何用栈结构实现队列结构 |
1 | //如何用队列结构实现栈结构 |
递归
1 | 递归公式 |
1 | // arr[L..R]范围上求最大值 L ... R N |
哈希表
1 | 1)哈希表在使用层面上可以理解为一种集合结构 |
归并排序
1 | 1)整体是递归,左边排好序+右边排好序+merge让整体有序 |
1 | // arr[L...R]范围上,变成有序的 |
1 | // 非递归方法实现 |
1 | // arr[L..R]既要排好序,也要求小和返回 |
快速排序
1 | //把所有非0的数放左边 |
1 | 荷兰国旗问题 |
1 | // arr[L..R]上,以arr[R]位置的数做划分值 |
1 | 快速排序1.0 |
1 | //V1.0 |
1 | 快速排序2.0 |
1 | //V2.0 |
1 | // arr[L...R] 玩荷兰国旗问题的划分,以arr[R]做为num的值 |
1 | 快速排序3.0(随机快排+荷兰国旗技巧优化) |
1 | //V3.0 |
1 | 1)通过分析知道,划分值越靠近中间,性能越好;越靠近两边,性能越差 |
堆结构
1 | 1)堆结构就是用数组实现的完全二叉树结构 |
1 | //把第0个位置当做根节点,1~2位置当做根节点的左右节点 |
1 | 堆排序 |
1 | // 堆排序额外空间复杂度O(1) |
1 | 已知一个几乎有序的数组。几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离一定不超过k,并且k相对于数组长度来说是比较小的。 |
1 | //时间复杂度O(N * logK) |
前缀树
1 | 1)单个字符串中,字符从前到后的加到一棵多叉树上 |
1 | 例子: |
1 | //通过链表记录链路,index记录字母 |
桶排序
1 | 桶排序思想下的排序:计数排序 & 基数排序 |
1 | 计数排序: |
1 | //排序非负数的十进制 |
总结
1 | 稳定性是指同样大小的样本再排序之后不会改变相对次序 |
时间复杂度 | 额外空间复杂度 | 稳定性 | |
---|---|---|---|
选择排序 | O(N^2) | O(1) | 无 |
冒泡排序 | O(N^2) | O(1) | 有 |
插入排序 | O(N^2) | O(1) | 有 |
归并排序 | O(N*logN) | O(N) | 有 |
随机快排 | O(N*logN) | O(logN) | 无 |
堆排序 | O(N*logN) | O(1) | 无 |
计数排序 | O(N) | O(M) | 有 |
基数排序 | O(N) | O(N) | 有 |
追求稳定性的话,可以选择归并排序,追求额外空间复杂度低的,选择堆排序,两者都不追求时,快排的效率最高。
1 | 1)不基于比较的排序,对样本数据有严格要求,不易改写 |
链表
1 | 快慢指针:快指针走的步数是慢指针的两倍 |
1 | public static class Node { |
1 | // head 头 |
1 | //2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点 |
1 | //3)输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个 |
1 | //4)输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个 |
1 | 给定一个单链表的头节点head,请判断该链表是否为回文结构。 |
1 | // need n extra space |
1 | // need O(1) extra space |
1 | 将单向链表按某值划分成左边小、中间相等、右边大的形式 |
1 | //用6个指针,生成三条链表,最后连起来 |
1 | 一种特殊的单链表节点类描述如下 |
1 | //hash表做法 空间复杂度O(N) |
1 | //拷贝链表结点,并插入到链表中,然后按对获取结点,设置rand值,再分离next指针,返回链表 |
1 | 给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null |
1 | // 找到链表第一个入环节点,如果无环,返回null |
1 | // 如果两个链表都无环,返回第一个相交节点,如果不想交,返回null |
1 | // 两个有环链表,返回第一个相交节点,如果不想交返回null |
二叉树
1 | 先序:任何子树的处理顺序都是,先头节点、再左子树、然后右子树 |
1 | // 实际上就是递归序 |
1 | //用非递归的方式实现 |
1 | //非递归的中序遍历 |
1 | //非递归的后序遍历 |
1 | //只用一个栈实现非递归的后序遍历 |
1 | //实现二叉树的按层遍历 |
1 | //求二叉树最宽的层有多少个节点 |
1 | //不使用hashMap计算二叉树最大宽度 |
1 | 二叉树的序列化和反序列化 |
1 | //先序-序列化 不要忽略空,每个节点都有左右两个NULL值 |
1 | //先序--反序列化 |
1 | //按层遍历序列化 |
1 | 二叉树结构如下定义: |
1 | public static Node getSuccessorNode(Node node) { |
1 | 请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后展开。 |
1 | // 递归过程,来到了某一个节点, |
二叉树的递归套路
1 | 1)假设以X节点为头,假设可以向X左树和X右树要任何信息 |
1 | 给定一棵二叉树的头节点head,返回这颗二叉树是不是平衡二叉树 |
1 | /* |
1 | 给定一棵二叉树的头节点head,任何两个节点之间都存在距离,返回整棵二叉树的最大距离 |
1 | /* |
1 | 给定一棵二叉树的头节点head, |
1 | /* |
1 | public static Info process(Node X) { |
1 | 派对的最大快乐值 |
1 | /* |
1 | public static class Employee { |
1 | public static Info process2(Employee x) |
1 | 给定一棵二叉树的头节点head,返回这颗二叉树中最大的二叉搜索子树的头节点 |
1 | // 每一棵子树 |
1 | 给定一棵二叉树的头节点head,返回这颗二叉树中是不是完全二叉树 |
1 | // 对每一棵子树,是否是满二叉树、是否是完全二叉树、高度 |
贪心算法
1 | 1)最自然智慧的算法 |
1 | 给定一个由字符串组成的数组strs, |
1 | //暴力递归 |
1 | //贪心算法 |
1 | 一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。 |
1 | //暴力递归 |
1 | //贪心算法 |
1 | 给定一个字符串str,只由‘X’和‘.’两种字符构成。 |
1 | // str[index....]位置,自由选择放灯还是不放灯 |
1 | //贪心算法 |
1 | 一块金条切成两半,是需要花费和长度数值一样的铜板的。 |
1 | //暴力递归 |
1 | //贪心算法 |
1 | 输入: 正数数组costs、正数数组profits、正数K、正数M |
1 | 贪心思路: |
1 | //贪心算法 |
并查集
1 | 1)有若干个样本a、b、c、d…类型假设是V |
1 | 思想: |
1 | public class Code01_UnionFind { |
1 | // (1,10,13) (2,10,37) (400,500,37) |
图
1 | 1)由点的集合和边的集合构成 |
1 | // 点结构的描述 A 0 |
1 | //边 |
1 | //图 |
1 | //把各种数据结构的图表达形式转换成固定的算法解析的格式 |
1 | 宽度优先遍历 |
1 | // 从node出发,进行宽度优先遍历 |
1 | //深度优先遍历 |
1 | 图的拓扑排序算法 |
1 | // directed graph and no loop |
1 | 最小权重生成树算法之Kruskal |
1 | //所有边权重排序,并依次使用并查集判断是否需要使用边来合并from和to的节点 |
1 | 最小权重生成树算法之Prim |
1 | public static Set<Edge> primMST(Graph graph) { |
1 | Dijkstra算法 |
1 | public static HashMap<Node, Integer> dijkstra1(Node from) { |
1 | //改进:优化每个节点都需要遍历获取最小路径的问题 |
暴力递归
1 | 暴力递归就是尝试 |
汉诺塔问题
1 | 打印n层汉诺塔从最左边移动到最右边的全部过程 |
1 | //1.汉诺塔简单版本写法 |
1 | /*思想:(from -> to) |
1 | //非递归的版本 |
逆序栈问题
1 | 给你一个栈,请你逆序这个栈, |
1 | /* |
子序列问题
1 | 打印一个字符串的全部子序列 |
1 | /* |
1 | 打印一个字符串的全部子序列,要求不要出现重复字面值的子序列 |
1 | // str index set |
全排列问题
1 | 打印一个字符串的全部排列 |
1 | /* |
1 | 打印一个字符串的全部排列,要求不要出现重复的排列 |
1 | /* |
皇后问题
1 | N皇后问题是指在N*N的棋盘上要摆N个皇后, |
1 | public static int num1(int n) { |
1 | /* |
1 | //两种皇后优化时间的对比 |
动态规划
1 | 从左往右的尝试模型: |
1 | /* |
1 | /* |
背包问题
1 | 从左往右的尝试模型: |
1 | /* |
1 | /* |
1 | /* |
1 | **范围上尝试的模型** |
1 | /* |
1 | /* |
四种模型
1 | **什么暴力递归可以继续优化?** |
1 | **暴力递归和动态规划的关系** |
1 | **如何找到某个问题的动态规划方式?** |
1 | **常见的4种尝试模型** |
1 | **暴力递归到动态规划的套路** |
1 | 假设有排成一行的N个位置,记为1~N,N 一定大于或等于 2 |
1 | /* |
1 | /* |
1 | 给定数组arr,arr中所有的值都为正数且不重复 |
1 | //暴力递归 |
1 | /* |
1 | /* |
1 | 优化的动态规划,省略动态规划的枚举行为 |
1 | /* |
1 | public static void main(String[] args) { |
1 | 给定一个字符串str,给定一个字符串类型的数组arr。 |
1 | /* |
1 | **多样本位置全对应的尝试模型** |
1 | //暴力递归 |
1 | /* |
1 | **寻找业务限制的尝试模型** |
1 | // process(drinks, 3, 10, 0,0) |
1 | /* |
1 | 请同学们自行搜索或者想象一个象棋的棋盘, |
1 | //暴力递归 |
1 | //另一种暴力递归 |
1 | //动态规划 |
最后更新: 2021年03月09日 22:59