# 算法基础
# java中的数据结构
哈希表: https://www.cnblogs.com/chen-lhx/p/8432422.html
- HashMap(无序,允许Key、value为null)
- TreeMap(有序,默认Key升序)
- LinkedHashMap(有序,按插入顺序记录)
- LinkedList(有序集合)
# TreeMap的使用
# 复杂度
# 时间复杂度
# 参阅
时间复杂度中的log(n)底数到底是多少? (opens new window) O(log n) 怎么算出来的 (opens new window) 时间复杂度 O(log n) 意味着什么? (opens new window)
# 空间复杂度
# 双指针
遍历右指针,左指针同时向右收缩:713. 乘积小于K的子数组 (opens new window)
# 图
# 图(graph)中的几个基本概念
- 顶点(vertex):图中的元素节点
- 边(edge):两个顶点间存在的连接关系
- 度(degree):与顶点相连接边的条数
- 入度:有多少条边指向此顶点
- 出度:有多少条边是以此顶点为起点指向其他顶点
- 带权图
- 稀疏图、稠密图
# 如何存储图?
邻接矩阵
使用二维数组存储图,adj[A][B] = 1二维的两个下标表示从节点A到节点B存在一个有向边,若是无向图,则需要反过来同样表示adj[B][A] = 1。
邻接矩阵的优点是简单,缺点是浪费空间,若是稀疏图,则非常浪费空间。

邻接表、逆邻接表
使用Map<Integer, List<Integer>>的结构存储,顶点作为hash表的key,与顶点相连的所有节点都存储到链表中。
邻接表可以很方便的查找一个顶点为起点,指向了多少个其他顶点,但是无法方便知道有哪些顶点指向某一个顶点,因此就有了逆邻接表。
逆邻接表中,同样是顶点作为hash表的key,但值是存储了所有指向顶点key的节点。
邻接表比邻接矩阵节约空间,但查找效率不及邻接矩阵,不过可以加红黑树优化(或者平衡二叉树、跳表、散列表等)。

大数据量常见如何存储图?
利用数据分片,将顶点分片存储。
关系型数据库如何存储图?

# 如何遍历图?
通常使用
- BFS
- DFS
# 如何判断一个有向图是否存在环?
- 快慢指针(单向边找环)
- 递归、dfs
- 拓扑排序
# 栈
# 单调栈、单调队列
单调栈:指从栈顶到栈底是严格递增还是严格递减的栈。
单调递增
- [5,4,3,2,1] 左边栈底,右边栈顶
- 当前元素 >= 栈顶元素,弹出栈顶元素,直到小于时,将当前元素放置栈顶。
- 当前元素 < 栈顶元素,放置栈顶。
- 如果向右遍历数组,能在栈中找到左边第一个大于当前元素的值。
单调递减
- [1,2,3,4,5] 左边栈底,右边栈顶
- 当前元素 <= 栈顶元素,弹出栈顶元素,直到大于时,将当前元素放置栈顶。
- 当前元素 > 栈顶元素,放置栈顶。
- 如果向右遍历数组,能在栈中找到左边第一个小于当前元素的值。
# 回溯
回溯其实就是一种枚举,回溯一般可以构造成一个递归树。
求解组合问题时,如果要求解集不能重复,需要排序,相同的跳过相同的元素。
【46. 全排列】是典型的回溯算法题,其实是构建一个递归树
# 枚举
枚举三个数时,使用三层循环嵌套,每一层的指针起始位置等于上一层+1
for i := 0; i < n; i++ { for j := i+1; j < n; j++ { for k := j+1; k < n; k++ { } } }
# 优先队列
# 例题
# 两个双端队列动态获取区间内最值
# 并查集
- 可以用来判断图中是否有环
- 统计分组数
# 并查集相关题目
- 684. 冗余连接
- 547. 省份数量
# 并查集模板
count记录孤立集合有多少,parent数组表示领头元素集合。
class UnionFind {
private int count = 0;
private int[] parent;
public UnionFind(int n) {
count = n;
parent = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
public int find(int p) {
while (p != parent[p]) {
parent[p] = parent[parent[p]];
p = parent[p];
}
return p;
}
public boolean connected(int p, int q) {
return find(p) == find(q);
}
public void union(int p, int q) {
int rp = find(p);
int rq = find(q);
if (rp == rq) return;
parent[rp] = rq;
count--;
}
}
# 前缀和
# 前缀和题目
- 274. H 指数
# 二分法、二分查找
# 拓扑排序
toposort
算法实现有:Kahn算法、DFS
# kahn算法(贪心)
pre[0] = {0, numcourses-1} pre[1] = {0, numcourses-1}
map<n, degree> 统计入度
找出0度的,从0度的数开始,将其指向的所有数拿出来,每当一个顶点为0时,加入计数,并重复以上步骤,拿出其所有指向的数出来。
public void toposortByKahn() {
int[] inDegree = new int[v]; // 统计每个顶点的入度
for (int i = 0; i < v; ++i) {
for (int j = 0; j < adj[i].size(); ++j) {
int w = adj[i].get(j); // i->w
inDegree[w]++;
}
}
LinkedList<Integer> queue = new LinkedList<>();
for (int i = 0; i < v; ++i) {
if (inDegree[i] == 0) queue.add(i);
}
while (!queue.isEmpty()) {
int i = queue.remove();
System.out.print("->" + i);
for (int j = 0; j < adj[i].size(); ++j) {
int k = adj[i].get(j);
inDegree[k]--;
if (inDegree[k] == 0) queue.add(k);
}
}
# 拓扑DFS
# 拓扑排序题目
802. 找到最终的安全状态 (opens new window) 207. 课程表 210. 课程表 II 457. 环形数组是否存在循环
# 动态规划
- 序列DP
- 区间DP
- 数位DP
# 常用标记方式-数组
# 用数组做映射和哈希表
# 如果数组两两比较,循环时从下标1开始,可以避免最后多余判断
# 解题时,懵逼时怎么办
- 想想有没有暴力解(朴素解)
- 简化问题,罗列基本情况,找最近的、重复的子问题,或者说寻找可以优化的过程。
- 分析需要优化的过程,先列出需要哪些特性,再思考哪些数据结构可以提供这些特性(仍然需要熟悉数据结构的特性)。
- 需要了解Java针对各种数据结构所提供的支持。
- 平时要多练习将题意转成另一种方便理解的变种,或者其他原题。
# 进制、数制
十进制使用最为广泛的原因与人类有10个手指头是密不可分的。
在我国古代使用过十六进制,成语半斤八两指的是八两是半斤,一斤就是十六两,也就是以十六为基数。
不同进制的前缀表示:
- 没有前缀表示的一般为十进制
- 二进制:0b
- 八进制:0o或0
- 十六进制:0x
b、o、x分别是各自数制的英文单词:binary、octonary、hexadecimal
十进制英文:decimal

# 进制转换的方法
1.换基法
2.除余法
3.按位拆分法、按位合并法
4.数制转换图
# 位运算
# 位运算方式=按位运算
按位运算符针对
布尔值与逻辑运算符效果一样, 但是不会出现短路的情况.
按位运算满足
交换律和结合律
# 异或运算
相同的为0, 不同的为1, 因此被称为不进位的二进制加法.
异或的运算法则为:0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同为0,异为1) 且满足交换律 (即不进位加法) 任何数与1异或,奇数-1,偶数+1。 任何数与自己异或皆为自己。 自己与自己异或
异或的效果:
- x^0
无论x为什么, 都值都保持不变.
- x^1
结果是将x值进行翻转
- x^x
结果为0
异或运算的用法:
- 判断两个值是否相等
a^b=0则a与b相等, 因为必须每一位都相等, 最后结果才可能为0.
- 指定某位进行翻转
a. 将需要翻转的位设为1, 目标值与设定值进行^
b. 0或1的数取反使用异或^1
- 交换两个值(不需要临时变量)
a=a^b
b=b^a
a=a^b
- 用于加密解密
用一个字符x对其原文的每一个小单位进行
^, 则是加密, 再重复同样操作则是解密.
假设: result=a^x
则result^x=a^x^x, result^x=a;
# 与运算
只要有一个为0就为0
与运算作用:
- 将某一位清零(置0)
与运算的用法:
去掉最低位的1
n = n & (n-1); // 或者写作 n &= (n-1);
# 或运算
只要有一个是1, 则为1
或运算的效果:
- x|0
无论x为什么, 都值都保持不变, 这一点与异或
^相同.
或运算用法:
- 常用于关闭位, 将指定位置1
# 非运算
进行取反, ~0=1, ~1=0
# 与等号一起的组合运算符
与关系运算一样, 可以配合
=一起使用, 用于合并运算及赋值
1. &=
为什么&=是获得余数??
2. |=
为什么|=是没满右侧的数字则是右侧数字减一(右侧是1023则是这样).
# 移位运算
# 左移位
左移位规则: 丢弃最高位, 最低位补0. 左移位的意义: 在没有溢出的前提下, 左移n位, 等于乘以2的n次方.
value << num
value为被移位值 num是移动位数
# 位运算在日常开发中的使用
&用来减去一定值. 因为&可以将某一位置0, 所以可以达到减法的效果.|和<<用来赋值x|0结果不变, 所有可以用来赋值操作, 然后移位, 补零之后继续赋值.利用一个int字段存储二进制数据, 一个字段就可以表示n个属性 设计一个指定长度的二进制数据, 并将每一段设计为表达不同的内容, 利用
<<和|来组合内容. 利用&来拆分/取出数据.int type = (1<<31) | (1<<16) |5用二进制数代替数组或者map。 假设需要标记小写英文字母在一个数组中是否出现过,可以使用26位的二进制数标记:
mask |= 1 << int_char两个数字进行或运算,相当于两个数每一个有1的位都会使用结果的对应位为1,可以理解成合并两个数。
可以通过
&运算将一个数限制在(2^n)-1大小的范围内,该结果通过可以用作数组的下标(这就要求数据的大小是2^n),也就是针对数组取模。参考Java中的ThreadLocal代码
int i = key.threadLocalHashCode & (len-1);,i就是对key的hashcode取模后找到在数组中的位置。
# 其他资料
# TopK问题
解决该问题有很多方法:
- 先排序,后遍历 O(nlogn)
- 优先队列、小顶堆 O(nlogk)
- 快速选择算法 平均O(n) 最坏O(n^2)
- BFPRT 最坏 O(n)
# 快速选择算法 求TopK
平均时间复杂度为O(n)
假设数组是已排序的,并且每次取pivot都是用最小的或者最大的,搜索范围只能缩小1,因此最坏时间复杂度为O(n^2)
快速选择只能找第k个,不能在包含重复数据的数组中找第k大的元素,但可以先去重。
public class QuickSelect {
public int quickSelect(int[] nums, int k) {
return quickSelect(nums, k, 0, nums.length-1);
}
public int quickSelect(int[] nums, int k, int lo, int hi) {
// [hi,lo]中随机取一个
int p = (int)Math.floor(Math.random() * (hi-lo+1)) + lo;
// 交换pivot到最高位
swap(nums, p, hi);
// 定义双指针i、j
// 当j元素小于等于hi元素,交换i与j元素,i与j下标同时右移一位
// 当大于时,仅j下标右移一位
// 当遍历完一次后,所有小于等于hi元素的都在i下标的左边,i下标右边为大于hi元素的数
int i = lo, j = lo;
while (j < hi) {
if (nums[j] <= nums[hi]) {
swap(nums, i, j);
i++;
}
j++;
}
// 交换元素,以i为中心,左边都是小于等于i的,右边皆为大于i的
// 判断i是否为第k大的元素,如果不是,搜索范围缩小到左边或者右边继续递归
swap(nums, i, hi);
if (i+k-1 == hi) return nums[i];
if (i+k-1 > hi) return quickSelect(nums, k-(hi-i+1), lo, i-1);
return quickSelect(nums, k, i+1, hi);
}
public void swap(int[] nums, int a, int b) {
int tmp = nums[a];
nums[a] = nums[b];
nums[b] = tmp;
}
public static void main(String[] args) {
QuickSelect qs = new QuickSelect();
int res = qs.quickSelect(new int[]{3,2,1,5,6,4}, 3);
System.out.println(res);
}
}
# 竞赛
# 算法课程
慕课网-yubobobo
# 数算
# 两位数乘法
十位数相同,个位数相加等于0时:
xy1*xy2 = x*(x+1) 拼接 y1*y2
例如:
45*45 = 4*5 拼接 5*5 = 2025
43*47 = 4*5 拼接 3*7 = 2021
← 散题汇总