图是由顶点(vertex)和边(edge)组成的数据结构,例如
该图有四个顶点:A、B、C、D 以及四条有向边,有向图中,边是单向的
如果是无向图,那么边是双向的,下面是一个无向图的例子
度是指与该顶点相邻的边的数量
例如上图中
有向图中,细分为入度和出度,参见下图
边可以有权重,代表从源顶点到目标顶点的距离、费用、时间或其他度量。
路径被定义为从一个顶点到另一个顶点的一系列连续边,例如上图中【北京】到【上海】有多条路径
路径长度
在有向图中,从一个顶点开始,可以通过若干条有向边返回到该顶点,那么就形成了一个环
如果两个顶点之间存在路径,则这两个顶点是连通的,所有顶点都连通,则该图被称之为连通图,若子图连通,则称为连通分量
比如说,下面的图
用邻接矩阵可以表示为:
A B C D
A 0 1 1 0
B 1 0 0 1
C 1 0 0 1
D 0 1 1 0
用邻接表可以表示为:
A -> B -> C
B -> A -> D
C -> A -> D
D -> B -> C
有向图的例子
A B C D
A 0 1 1 0
B 0 0 0 1
C 0 0 0 1
D 0 0 0 0
A - B - C
B - D
C - D
D - empty
顶点
public class Vertex {
String name;
List<Edge> edges;
// 拓扑排序相关
int inDegree;
int status; // 状态 0-未访问 1-访问中 2-访问过,用在拓扑排序
// dfs, bfs 相关
boolean visited;
// 求解最短距离相关
private static final int INF = Integer.MAX_VALUE;
int dist = INF;
Vertex prev = null;
}
边
public class Edge {
Vertex linked;
int weight;
public Edge(Vertex linked) {
this(linked, 1);
}
public Edge(Vertex linked, int weight) {
this.linked = linked;
this.weight = weight;
}
}
public class Dfs {
public static void main(String[] args) {
Vertex v1 = new Vertex("v1");
Vertex v2 = new Vertex("v2");
Vertex v3 = new Vertex("v3");
Vertex v4 = new Vertex("v4");
Vertex v5 = new Vertex("v5");
Vertex v6 = new Vertex("v6");
v1.edges = List.of(new Edge(v3), new Edge(v2), new Edge(v6));
v2.edges = List.of(new Edge(v4));
v3.edges = List.of(new Edge(v4), new Edge(v6));
v4.edges = List.of(new Edge(v5));
v5.edges = List.of();
v6.edges = List.of(new Edge(v5));
dfs1(v1);
}
private static void dfs2(Vertex v) {
LinkedList<Vertex> stack = new LinkedList<>();
stack.push(v);
while (!stack.isEmpty()) {
Vertex pop = stack.pop();
pop.visited = true;
System.out.println(pop.name);
for (Edge edge : pop.edges) {
if (!edge.linked.visited) {
stack.push(edge.linked);
}
}
}
}
private static void dfs1(Vertex v) {
v.visited = true;
System.out.println(v.name);
for (Edge edge : v.edges) {
if (!edge.linked.visited) {
dfs(edge.linked);
}
}
}
}
public class Bfs {
public static void main(String[] args) {
Vertex v1 = new Vertex("v1");
Vertex v2 = new Vertex("v2");
Vertex v3 = new Vertex("v3");
Vertex v4 = new Vertex("v4");
Vertex v5 = new Vertex("v5");
Vertex v6 = new Vertex("v6");
v1.edges = List.of(new Edge(v3), new Edge(v2), new Edge(v6));
v2.edges = List.of(new Edge(v4));
v3.edges = List.of(new Edge(v4), new Edge(v6));
v4.edges = List.of(new Edge(v5));
v5.edges = List.of();
v6.edges = List.of(new Edge(v5));
bfs(v1);
}
private static void bfs(Vertex v) {
LinkedList<Vertex> queue = new LinkedList<>();
v.visited = true;
queue.offer(v);
while (!queue.isEmpty()) {
Vertex poll = queue.poll();
System.out.println(poll.name);
for (Edge edge : poll.edges) {
if (!edge.linked.visited) {
edge.linked.visited = true;
queue.offer(edge.linked);
}
}
}
}
}
public class TopologicalSort {
public static void main(String[] args) {
Vertex v1 = new Vertex("网页基础");
Vertex v2 = new Vertex("Java基础");
Vertex v3 = new Vertex("JavaWeb");
Vertex v4 = new Vertex("Spring框架");
Vertex v5 = new Vertex("微服务框架");
Vertex v6 = new Vertex("数据库");
Vertex v7 = new Vertex("实战项目");
v1.edges = List.of(new Edge(v3)); // +1
v2.edges = List.of(new Edge(v3)); // +1
v3.edges = List.of(new Edge(v4));
v6.edges = List.of(new Edge(v4));
v4.edges = List.of(new Edge(v5));
v5.edges = List.of(new Edge(v7));
v7.edges = List.of();
List<Vertex> graph = List.of(v1, v2, v3, v4, v5, v6, v7);
// 1. 统计每个顶点的入度
for (Vertex v : graph) {
for (Edge edge : v.edges) {
edge.linked.inDegree++;
}
}
/*for (Vertex vertex : graph) {
System.out.println(vertex.name + " " + vertex.inDegree);
}*/
// 2. 将入度为0的顶点加入队列
LinkedList<Vertex> queue = new LinkedList<>();
for (Vertex v : graph) {
if (v.inDegree == 0) {
queue.offer(v);
}
}
// 3. 队列中不断移除顶点,每移除一个顶点,把它相邻顶点入度减1,若减到0则入队
List<String> result = new ArrayList<>();
while (!queue.isEmpty()) {
Vertex poll = queue.poll();
// System.out.println(poll.name);
result.add(poll.name);
for (Edge edge : poll.edges) {
edge.linked.inDegree--;
if (edge.linked.inDegree == 0) {
queue.offer(edge.linked);
}
}
}
if (result.size() != graph.size()) {
System.out.println("出现环");
} else {
for (String key : result) {
System.out.println(key);
}
}
}
}
public class TopologicalSortDFS {
public static void main(String[] args) {
Vertex v1 = new Vertex("网页基础");
Vertex v2 = new Vertex("Java基础");
Vertex v3 = new Vertex("JavaWeb");
Vertex v4 = new Vertex("Spring框架");
Vertex v5 = new Vertex("微服务框架");
Vertex v6 = new Vertex("数据库");
Vertex v7 = new Vertex("实战项目");
v1.edges = List.of(new Edge(v3));
v2.edges = List.of(new Edge(v3));
v3.edges = List.of(new Edge(v4));
v6.edges = List.of(new Edge(v4));
v4.edges = List.of(new Edge(v5));
v5.edges = List.of(new Edge(v7));
v7.edges = List.of();
List<Vertex> graph = List.of(v1, v2, v3, v4, v5, v6, v7);
LinkedList<String> result = new LinkedList<>();
for (Vertex v : graph) {
if(v.status==0) {
dfs(v, result);
}
}
System.out.println(result);
}
private static void dfs(Vertex v, LinkedList<String> result) {
if (v.status == 2) {
return;
}
if (v.status == 1) {
throw new RuntimeException("发现环");
}
v.status = 1;
for (Edge edge : v.edges) {
dfs(edge.linked, result);
}
v.status = 2;
result.push(v.name);
}
}
Edsger Wybe Dijkstra
艾兹格·维布·迪克斯特拉(Edsger Wybe Dijkstra,/ˈdaɪkstrə/ DYKE-strə;荷兰语:[ˈɛtsxər ˈʋibə ˈdɛikstra] 1930年5月11日-2002年8月6日)是一位荷兰计算机科学家、程序员、软件工程师、系统科学家和科学散文家。他因对开发结构化编程语言做出的基础贡献而获得了1972年的图灵奖,并担任德克萨斯大学奥斯汀分校的斯伦贝谢百年计算机科学主席,任职时间从1984年到2000年。在他于2002年去世前不久,他因其在程序计算的自稳定性方面的工作而获得了ACM PODC分布式计算有影响力论文奖。为了纪念他,该年度奖项在接下来的一年更名为迪克斯特拉奖。
迪克斯特拉在计算机科学领域的贡献
- 最短路径算法,也称为迪克斯特拉算法,现代计算机科学本科课程中广泛教授
- Shunting yard算法
- THE OS 操作系统
- 银行家算法
- 用于协调多个处理器和程序的信号量构造
- 在分布式计算领域提出概念:自稳定性
算法描述:
public class Dijkstra {
public static void main(String[] args) {
Vertex v1 = new Vertex("v1");
Vertex v2 = new Vertex("v2");
Vertex v3 = new Vertex("v3");
Vertex v4 = new Vertex("v4");
Vertex v5 = new Vertex("v5");
Vertex v6 = new Vertex("v6");
v1.edges = List.of(new Edge(v3, 9), new Edge(v2, 7), new Edge(v6, 14));
v2.edges = List.of(new Edge(v4, 15));
v3.edges = List.of(new Edge(v4, 11), new Edge(v6, 2));
v4.edges = List.of(new Edge(v5, 6));
v5.edges = List.of();
v6.edges = List.of(new Edge(v5, 9));
List<Vertex> graph = List.of(v1, v2, v3, v4, v5, v6);
dijkstra(graph, v1);
}
private static void dijkstra(List<Vertex> graph, Vertex source) {
ArrayList<Vertex> list = new ArrayList<>(graph);
source.dist = 0;
while (!list.isEmpty()) {
// 3. 选取当前顶点
Vertex curr = chooseMinDistVertex(list);
// 4. 更新当前顶点邻居距离
updateNeighboursDist(curr, list);
// 5. 移除当前顶点
list.remove(curr);
}
for (Vertex v : graph) {
System.out.println(v.name + " " + v.dist);
}
}
private static void updateNeighboursDist(Vertex curr, ArrayList<Vertex> list) {
for (Edge edge : curr.edges) {
Vertex n = edge.linked;
if (list.contains(n)) {
int dist = curr.dist + edge.weight;
if (dist < n.dist) {
n.dist = dist;
}
}
}
}
private static Vertex chooseMinDistVertex(ArrayList<Vertex> list) {
Vertex min = list.get(0);
for (int i = 1; i < list.size(); i++) {
if (list.get(i).dist < min.dist) {
min = list.get(i);
}
}
return min;
}
}
改进 - 优先级队列
public class DijkstraPriorityQueue {
public static void main(String[] args) {
Vertex v1 = new Vertex("v1");
Vertex v2 = new Vertex("v2");
Vertex v3 = new Vertex("v3");
Vertex v4 = new Vertex("v4");
Vertex v5 = new Vertex("v5");
Vertex v6 = new Vertex("v6");
v1.edges = List.of(new Edge(v3, 9), new Edge(v2, 7), new Edge(v6, 14));
v2.edges = List.of(new Edge(v4, 15));
v3.edges = List.of(new Edge(v4, 11), new Edge(v6, 2));
v4.edges = List.of(new Edge(v5, 6));
v5.edges = List.of();
v6.edges = List.of(new Edge(v5, 9));
List<Vertex> graph = List.of(v1, v2, v3, v4, v5, v6);
dijkstra(graph, v1);
}
private static void dijkstra(List<Vertex> graph, Vertex source) {
PriorityQueue<Vertex> queue = new PriorityQueue<>(Comparator.comparingInt(v -> v.dist));
source.dist = 0;
for (Vertex v : graph) {
queue.offer(v);
}
while (!queue.isEmpty()) {
System.out.println(queue);
// 3. 选取当前顶点
Vertex curr = queue.peek();
// 4. 更新当前顶点邻居距离
if(!curr.visited) {
updateNeighboursDist(curr, queue);
curr.visited = true;
}
// 5. 移除当前顶点
queue.poll();
}
for (Vertex v : graph) {
System.out.println(v.name + " " + v.dist + " " + (v.prev != null ? v.prev.name : "null"));
}
}
private static void updateNeighboursDist(Vertex curr, PriorityQueue<Vertex> queue) {
for (Edge edge : curr.edges) {
Vertex n = edge.linked;
if (!n.visited) {
int dist = curr.dist + edge.weight;
if (dist < n.dist) {
n.dist = dist;
n.prev = curr;
queue.offer(n);
}
}
}
}
}
问题
按照 Dijkstra 算法,得出
事实应当是
public class BellmanFord {
public static void main(String[] args) {
// 正常情况
/*Vertex v1 = new Vertex("v1");
Vertex v2 = new Vertex("v2");
Vertex v3 = new Vertex("v3");
Vertex v4 = new Vertex("v4");
Vertex v5 = new Vertex("v5");
Vertex v6 = new Vertex("v6");
v1.edges = List.of(new Edge(v3, 9), new Edge(v2, 7), new Edge(v6, 14));
v2.edges = List.of(new Edge(v4, 15));
v3.edges = List.of(new Edge(v4, 11), new Edge(v6, 2));
v4.edges = List.of(new Edge(v5, 6));
v5.edges = List.of();
v6.edges = List.of(new Edge(v5, 9));
List graph = List.of(v4, v5, v6, v1, v2, v3);*/
// 负边情况
/*Vertex v1 = new Vertex("v1");
Vertex v2 = new Vertex("v2");
Vertex v3 = new Vertex("v3");
Vertex v4 = new Vertex("v4");
v1.edges = List.of(new Edge(v2, 2), new Edge(v3, 1));
v2.edges = List.of(new Edge(v3, -2));
v3.edges = List.of(new Edge(v4, 1));
v4.edges = List.of();
List graph = List.of(v1, v2, v3, v4);*/
// 负环情况
Vertex v1 = new Vertex("v1");
Vertex v2 = new Vertex("v2");
Vertex v3 = new Vertex("v3");
Vertex v4 = new Vertex("v4");
v1.edges = List.of(new Edge(v2, 2));
v2.edges = List.of(new Edge(v3, -4));
v3.edges = List.of(new Edge(v4, 1), new Edge(v1, 1));
v4.edges = List.of();
List<Vertex> graph = List.of(v1, v2, v3, v4);
bellmanFord(graph, v1);
}
private static void bellmanFord(List<Vertex> graph, Vertex source) {
source.dist = 0;
int size = graph.size();
// 1. 进行 顶点个数 - 1 轮处理
for (int i = 0; i < size - 1; i++) {
// 2. 遍历所有的边
for (Vertex s : graph) {
for (Edge edge : s.edges) {
// 3. 处理每一条边
Vertex e = edge.linked;
if (s.dist != Integer.MAX_VALUE && s.dist + edge.weight < e.dist) {
e.dist = s.dist + edge.weight;
e.prev = s;
}
}
}
}
for (Vertex v : graph) {
System.out.println(v + " " + (v.prev != null ? v.prev.name : "null"));
}
}
}
负环
如果在【顶点-1】轮处理完成后,还能继续找到更短距离,表示发现了负环
public class FloydWarshall {
public static void main(String[] args) {
Vertex v1 = new Vertex("v1");
Vertex v2 = new Vertex("v2");
Vertex v3 = new Vertex("v3");
Vertex v4 = new Vertex("v4");
v1.edges = List.of(new Edge(v3, -2));
v2.edges = List.of(new Edge(v1, 4), new Edge(v3, 3));
v3.edges = List.of(new Edge(v4, 2));
v4.edges = List.of(new Edge(v2, -1));
List<Vertex> graph = List.of(v1, v2, v3, v4);
/*
直接连通
v1 v2 v3 v4
v1 0 ∞ -2 ∞
v2 4 0 3 ∞
v3 ∞ ∞ 0 2
v4 ∞ -1 ∞ 0
k=0 借助v1到达其它顶点
v1 v2 v3 v4
v1 0 ∞ -2 ∞
v2 4 0 2 ∞
v3 ∞ ∞ 0 2
v4 ∞ -1 ∞ 0
k=1 借助v2到达其它顶点
v1 v2 v3 v4
v1 0 ∞ -2 ∞
v2 4 0 2 ∞
v3 ∞ ∞ 0 2
v4 3 -1 1 0
k=2 借助v3到达其它顶点
v1 v2 v3 v4
v1 0 ∞ -2 0
v2 4 0 2 4
v3 ∞ ∞ 0 2
v4 3 -1 1 0
k=3 借助v4到达其它顶点
v1 v2 v3 v4
v1 0 -1 -2 0
v2 4 0 2 4
v3 5 1 0 2
v4 3 -1 1 0
*/
floydWarshall(graph);
}
static void floydWarshall(List<Vertex> graph) {
int size = graph.size();
int[][] dist = new int[size][size];
Vertex[][] prev = new Vertex[size][size];
// 1)初始化
for (int i = 0; i < size; i++) {
Vertex v = graph.get(i); // v1 (v3)
Map<Vertex, Integer> map = v.edges.stream().collect(Collectors.toMap(e -> e.linked, e -> e.weight));
for (int j = 0; j < size; j++) {
Vertex u = graph.get(j); // v3
if (v == u) {
dist[i][j] = 0;
} else {
dist[i][j] = map.getOrDefault(u, Integer.MAX_VALUE);
prev[i][j] = map.get(u) != null ? v : null;
}
}
}
print(prev);
// 2)看能否借路到达其它顶点
/*
v2->v1 v1->v?
dist[1][0] + dist[0][0]
dist[1][0] + dist[0][1]
dist[1][0] + dist[0][2]
dist[1][0] + dist[0][3]
*/
for (int k = 0; k < size; k++) {
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
// dist[i][k] + dist[k][j] // i行的顶点,借助k顶点,到达j列顶点
// dist[i][j] // i行顶点,直接到达j列顶点
if (dist[i][k] != Integer.MAX_VALUE &&
dist[k][j] != Integer.MAX_VALUE &&
dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
prev[i][j] = prev[k][j];
}
}
}
// print(dist);
}
print(prev);
}
static void path(Vertex[][] prev, List<Vertex> graph, int i, int j) {
LinkedList<String> stack = new LinkedList<>();
System.out.print("[" + graph.get(i).name + "," + graph.get(j).name + "] ");
stack.push(graph.get(j).name);
while (i != j) {
Vertex p = prev[i][j];
stack.push(p.name);
j = graph.indexOf(p);
}
System.out.println(stack);
}
static void print(int[][] dist) {
System.out.println("-------------");
for (int[] row : dist) {
System.out.println(Arrays.stream(row).boxed()
.map(x -> x == Integer.MAX_VALUE ? "∞" : String.valueOf(x))
.map(s -> String.format("%2s", s))
.collect(Collectors.joining(",", "[", "]")));
}
}
static void print(Vertex[][] prev) {
System.out.println("-------------------------");
for (Vertex[] row : prev) {
System.out.println(Arrays.stream(row).map(v -> v == null ? "null" : v.name)
.map(s -> String.format("%5s", s))
.collect(Collectors.joining(",", "[", "]")));
}
}
}
负环
如果在 3 层循环结束后,在 dist 数组的对角线处(i==j 处)发现了负数,表示出现了负环
public class Prim {
public static void main(String[] args) {
Vertex v1 = new Vertex("v1");
Vertex v2 = new Vertex("v2");
Vertex v3 = new Vertex("v3");
Vertex v4 = new Vertex("v4");
Vertex v5 = new Vertex("v5");
Vertex v6 = new Vertex("v6");
Vertex v7 = new Vertex("v7");
v1.edges = List.of(new Edge(v2, 2), new Edge(v3, 4), new Edge(v4, 1));
v2.edges = List.of(new Edge(v1, 2), new Edge(v4, 3), new Edge(v5, 10));
v3.edges = List.of(new Edge(v1, 4), new Edge(v4, 2), new Edge(v6, 5));
v4.edges = List.of(new Edge(v1, 1), new Edge(v2, 3), new Edge(v3, 2),
new Edge(v5, 7), new Edge(v6, 8), new Edge(v7, 4));
v5.edges = List.of(new Edge(v2, 10), new Edge(v4, 7), new Edge(v7, 6));
v6.edges = List.of(new Edge(v3, 5), new Edge(v4, 8), new Edge(v7, 1));
v7.edges = List.of(new Edge(v4, 4), new Edge(v5, 6), new Edge(v6, 1));
List<Vertex> graph = List.of(v1, v2, v3, v4, v5, v6, v7);
prim(graph, v1);
}
static void prim(List<Vertex> graph, Vertex source) {
ArrayList<Vertex> list = new ArrayList<>(graph);
source.dist = 0;
while (!list.isEmpty()) {
Vertex min = chooseMinDistVertex(list);
updateNeighboursDist(min);
list.remove(min);
min.visited = true;
System.out.println("---------------");
for (Vertex v : graph) {
System.out.println(v);
}
}
}
private static void updateNeighboursDist(Vertex curr) {
for (Edge edge : curr.edges) {
Vertex n = edge.linked;
if (!n.visited) {
int dist = edge.weight;
if (dist < n.dist) {
n.dist = dist;
n.prev = curr;
}
}
}
}
private static Vertex chooseMinDistVertex(ArrayList<Vertex> list) {
Vertex min = list.get(0);
for (int i = 1; i < list.size(); i++) {
if (list.get(i).dist < min.dist) {
min = list.get(i);
}
}
return min;
}
}
public class Kruskal {
static class Edge implements Comparable<Edge> {
List<Vertex> vertices;
int start;
int end;
int weight;
public Edge(List<Vertex> vertices, int start, int end, int weight) {
this.vertices = vertices;
this.start = start;
this.end = end;
this.weight = weight;
}
public Edge(int start, int end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
@Override
public int compareTo(Edge o) {
return Integer.compare(this.weight, o.weight);
}
@Override
public String toString() {
return vertices.get(start).name + "<->" + vertices.get(end).name + "(" + weight + ")";
}
}
public static void main(String[] args) {
Vertex v1 = new Vertex("v1");
Vertex v2 = new Vertex("v2");
Vertex v3 = new Vertex("v3");
Vertex v4 = new Vertex("v4");
Vertex v5 = new Vertex("v5");
Vertex v6 = new Vertex("v6");
Vertex v7 = new Vertex("v7");
List<Vertex> vertices = List.of(v1, v2, v3, v4, v5, v6, v7);
PriorityQueue<Edge> queue = new PriorityQueue<>(List.of(
new Edge(vertices,0, 1, 2),
new Edge(vertices,0, 2, 4),
new Edge(vertices,0, 3, 1),
new Edge(vertices,1, 3, 3),
new Edge(vertices,1, 4, 10),
new Edge(vertices,2, 3, 2),
new Edge(vertices,2, 5, 5),
new Edge(vertices,3, 4, 7),
new Edge(vertices,3, 5, 8),
new Edge(vertices,3, 6, 4),
new Edge(vertices,4, 6, 6),
new Edge(vertices,5, 6, 1)
));
kruskal(vertices.size(), queue);
}
static void kruskal(int size, PriorityQueue<Edge> queue) {
List<Edge> result = new ArrayList<>();
DisjointSet set = new DisjointSet(size);
while (result.size() < size - 1) {
Edge poll = queue.poll();
int s = set.find(poll.start);
int e = set.find(poll.end);
if (s != e) {
result.add(poll);
set.union(s, e);
}
}
for (Edge edge : result) {
System.out.println(edge);
}
}
}
public class DisjointSet {
int[] s;
// 索引对应顶点
// 元素是用来表示与之有关系的顶点
/*
索引 0 1 2 3 4 5 6
元素 [0, 1, 2, 3, 4, 5, 6] 表示一开始顶点直接没有联系(只与自己有联系)
*/
public DisjointSet(int size) {
s = new int[size];
for (int i = 0; i < size; i++) {
s[i] = i;
}
}
// find 是找到老大
public int find(int x) {
if (x == s[x]) {
return x;
}
return find(s[x]);
}
// union 是让两个集合“相交”,即选出新老大,x、y 是原老大索引
public void union(int x, int y) {
s[y] = x;
}
@Override
public String toString() {
return Arrays.toString(s);
}
}
public int find(int x) { // x = 2
if (x == s[x]) {
return x;
}
return s[x] = find(s[x]); // 0 s[2]=0
}
想法:
*将一个顶点个数少的连接到个数顶点多的集合里
****减少找老大距离
public class DisjointSetUnionBySize {
int[] s;
int[] size;
public DisjointSetUnionBySize(int size) {
s = new int[size];
this.size = new int[size];
for (int i = 0; i < size; i++) {
s[i] = i;
this.size[i] = 1;
}
}
// find 是找到老大 - 优化:路径压缩
public int find(int x) { // x = 2
if (x == s[x]) {
return x;
}
return s[x] = find(s[x]); // 0 s[2]=0
}
// union 是让两个集合“相交”,即选出新老大,x、y 是原老大索引
public void union(int x, int y) {
// s[y] = x;
if (size[x] < size[y]) {
int t = x;
x = y;
y = t;
}
s[y] = x;
size[x] = size[x] + size[y];
}
@Override
public String toString() {
return "内容:"+Arrays.toString(s) + "\n大小:" + Arrays.toString(size);
}
public static void main(String[] args) {
DisjointSetUnionBySize set = new DisjointSetUnionBySize(5);
set.union(1, 2);
set.union(3, 4);
set.union(1, 3);
System.out.println(set);
}
}
题目编号 | 题目标题 | 算法思想 |
---|---|---|
547 | 省份数量 | DFS、BFS、并查集 |
797 | 所有可能路径 | DFS、BFS |
1584 | 连接所有点的最小费用 | 最小生成树 |
743 | 网络延迟时间 | 单源最短路径 |
787 | K 站中转内最便宜的航班 | 单源最短路径 |
207 | 课程表 | 拓扑排序 |
210 | 课程表 II | 拓扑排序 |
称之为贪心算法或贪婪算法,核心思想是
贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。这种算法通常用于求解优化问题,如最小生成树、背包问题等。
贪心算法的应用:
常见问题及解答:
几个贪心的例子
// ...
while (!list.isEmpty()) {
// 选取当前【距离最小】的顶点
Vertex curr = chooseMinDistVertex(list);
// 更新当前顶点邻居距离
updateNeighboursDist(curr);
// 移除当前顶点
list.remove(curr);
// 标记当前顶点已经处理过
curr.visited = true;
}
// ...
while (!list.isEmpty()) {
// 选取当前【距离最小】的顶点
Vertex curr = chooseMinDistVertex(list);
// 更新当前顶点邻居距离
updateNeighboursDist(curr);
// 移除当前顶点
list.remove(curr);
// 标记当前顶点已经处理过
curr.visited = true;
}
// ...
while (list.size() < size - 1) {
// 选取当前【距离最短】的边
Edge poll = queue.poll();
// 判断两个集合是否相交
int i = set.find(poll.start);
int j = set.find(poll.end);
if (i != j) { // 未相交
list.add(poll);
set.union(i, j); // 相交
}
}
其它贪心的例子
选择排序、堆排序
拓扑排序
并查集合中的 union by size 和 union by height
哈夫曼编码
钱币找零,英文搜索关键字
任务编排
求复杂问题的近似解
public class Leetcode518 {
public int change(int[] coins, int amount) {
return rec(0, coins, amount, new LinkedList<>(), true);
}
/**
* 求凑成剩余金额的解的个数
*
* @param index 当前硬币索引
* @param coins 硬币面值数组
* @param remainder 剩余金额
* @param stack -
* @param first -
* @return 解的个数
*/
public int rec(int index, int[] coins, int remainder, LinkedList<Integer> stack, boolean first) {
if(!first) {
stack.push(coins[index]);
}
// 情况1:剩余金额 < 0 - 无解
int count = 0;
if (remainder < 0) {
print("无解:", stack);
}
// 情况2:剩余金额 == 0 - 有解
else if (remainder == 0) {
print("有解:", stack);
count = 1;
}
// 情况3:剩余金额 > 0 - 继续递归
else {
for (int i = index; i < coins.length; i++) {
count += rec(i, coins, remainder - coins[i], stack, false);
}
}
if (!stack.isEmpty()) {
stack.pop();
}
return count;
}
private static void print(String prompt, LinkedList<Integer> stack) {
ArrayList<Integer> print = new ArrayList<>();
ListIterator<Integer> iterator = stack.listIterator(stack.size());
while (iterator.hasPrevious()) {
print.add(iterator.previous());
}
System.out.println(prompt + print);
}
public static void main(String[] args) {
Leetcode518 leetcode = new Leetcode518();
// int count = leetcode.coinChange(new int[]{1, 5, 10, 25}, 41);
// int count = leetcode.coinChange(new int[]{25, 10, 5, 1}, 41);
// int count = leetcode.coinChange(new int[]{5, 2, 1}, 5);
// int count = leetcode.coinChange(new int[]{1, 2, 5}, 5);
int count = leetcode.change(new int[]{15, 10, 1}, 21);
System.out.println(count);
}
}
public class Leetcode322 {
static int min = -1; // 需要的最少硬币数 2 3
public int coinChange(int[] coins, int amount) {
rec(0, coins, amount, new AtomicInteger(-1), new LinkedList<>(), true);
return min;
}
// count 代表某一组合 钱币的总数
public void rec(int index, int[] coins, int remainder, AtomicInteger count, LinkedList<Integer> stack, boolean first) {
if (!first) {
stack.push(coins[index]);
}
count.incrementAndGet(); // count++
if (remainder == 0) {
System.out.println(stack);
if (min == -1) {
min = count.get();
} else {
min = Integer.min(min, count.get());
}
} else if (remainder > 0) {
for (int i = index; i < coins.length; i++) {
rec(i, coins, remainder - coins[i], count, stack, false);
}
}
count.decrementAndGet(); // count--
if (!stack.isEmpty()) {
stack.pop();
}
}
public static void main(String[] args) {
Leetcode322 leetcode = new Leetcode322();
// int count = leetcode.coinChange(new int[]{5, 2, 1}, 5);
int count = leetcode.coinChange(new int[]{25, 10, 5, 1}, 41);
// int count = leetcode.coinChange(new int[]{2}, 3);
// int count = leetcode.coinChange(new int[]{15, 10, 1}, 21);
System.out.println(count);
}
}
public class Leetcode322 {
public int coinChange(int[] coins, int amount) {
int remainder = amount;
int count = 0;
for (int coin : coins) {
while (remainder - coin > 0) {
remainder -= coin;
count++;
}
if (remainder - coin == 0) {
remainder = 0;
count++;
break;
}
}
if (remainder > 0) {
return -1;
} else {
return count;
}
}
public static void main(String[] args) {
Leetcode322 leetcode = new Leetcode322();
int count = leetcode.coinChange(new int[]{5, 2, 1}, 5);
// int count = leetcode.coinChange(new int[]{25, 10, 5, 1}, 41);
// int count = leetcode.coinChange(new int[]{2}, 3);
// 问题1 没有回头,导致找到更差的解
// int count = leetcode.coinChange(new int[]{15, 10, 1}, 21);
// 问题2 没有回头,导致无解
// int count = leetcode.coinChange(new int[]{15, 10}, 20);
System.out.println(count);
}
}
什么是编码?
简单说就是建立【字符】到【数字】的对应关系,如下面大家熟知的 ASC II 编码表,例如,可以查表得知字符【a】对应的数字是十六进制数【0x61】
\ | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 0a | 0b | 0c | 0d | 0e | 0f |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0000 | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 0a | 0b | 0c | 0d | 0e | 0f |
0010 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 1a | 1b | 1c | 1d | 1e | 1f |
0020 | 20 | ! | " | # | $ | % | & | ’ | ( | ) | * | + | , | - | . | / |
0030 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | : | ; | < | = | > | ? |
0040 | @ | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O |
0050 | P | Q | R | S | T | U | V | W | X | Y | Z | [ | \ | ] | ^ | _ |
0060 | ` | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o |
0070 | p | q | r | s | t | u | v | w | x | y | z | { | | | } | ~ | 7f |
注:一些直接以十六进制数字标识的是那些不可打印字符
传输时的编码
现在希望找到一种最节省字节的传输方式,怎么办?
假设传输的字符中只包含 a,b,c 这 3 个字符,有同学重新设计一张二进制编码表,见下图
现在还是传递 abbccccccc 这 10 个字符
不行,因为解码会出现问题,因为 10 会被错误的解码成 ba,而不是 c
怎么解决?必须保证编码后的二进制数字,要能区分它们的前缀(prefix-free)
用满二叉树结构编码,可以确保前缀不重复
再来试一遍
现在还是传递 abbccccccc 这 10 个字符
这回解码没问题了,但并非最少字节,因为 c 的出现频率高(7 次)a 的出现频率低(1 次),因此出现频率高的字符编码成短数字更经济
考察下面的树
现在还是传递 abbccccccc 这 10 个字符
public class HuffmanTree {
/*
Huffman 树的构建过程
1. 将统计了出现频率的字符,放入优先级队列
2. 每次出队两个频次最低的元素,给它俩找个爹
3. 把爹重新放入队列,重复 2~3
4. 当队列只剩一个元素时,Huffman 树构建完成
*/
static class Node {
Character ch; // 字符
int freq; // 频次
Node left;
Node right;
String code; // 编码
public Node(Character ch) {
this.ch = ch;
}
public Node(int freq, Node left, Node right) {
this.freq = freq;
this.left = left;
this.right = right;
}
int freq() {
return freq;
}
boolean isLeaf() {
return left == null;
}
@Override
public String toString() {
return "Node{" +
"ch=" + ch +
", freq=" + freq +
'}';
}
}
String str;
Map<Character, Node> map = new HashMap<>();
public HuffmanTree(String str) {
this.str = str;
// 功能1:统计频率
char[] chars = str.toCharArray();
for (char c : chars) {
/*if (!map.containsKey(c)) {
map.put(c, new Node(c));
}
Node node = map.get(c);
node.freq++;*/
Node node = map.computeIfAbsent(c, Node::new);
node.freq++;
}
// 功能2: 构造树
PriorityQueue<Node> queue = new PriorityQueue<>(Comparator.comparingInt(Node::freq));
queue.addAll(map.values());
while (queue.size() >= 2) {
Node x = queue.poll();
Node y = queue.poll();
int freq = x.freq + y.freq;
queue.offer(new Node(freq, x, y));
}
Node root = queue.poll();
// 功能3:计算每个字符的编码, 功能4:字符串编码后占用 bits
int sum = dfs(root, new StringBuilder());
for (Node node : map.values()) {
System.out.println(node + " " + node.code);
}
System.out.println("总共会占用 bits:" + sum);
}
private int dfs(Node node, StringBuilder code) {
int sum = 0;
if (node.isLeaf()) {
node.code = code.toString();
sum = node.freq * code.length();
} else {
sum += dfs(node.left, code.append("0"));
sum += dfs(node.right, code.append("1"));
}
if (code.length() > 0) {
code.deleteCharAt(code.length() - 1);
}
return sum;
}
public static void main(String[] args) {
new HuffmanTree("abbccccccc");
}
}
注意
- Node::new 是一个 Function,根据 key(即字符)生成 Node 对象
- 对应的是 public Node(Character ch) 有参构造
补充两个方法,注意为了简单期间用了编解码都用字符串演示,实际应该按 bits 编解码
public class HuffmanTree {
// ...
// 编码
public String encode() {
char[] chars = str.toCharArray();
StringBuilder sb = new StringBuilder();
for (char c : chars) {
sb.append(map.get(c).code);
}
return sb.toString();
}
// 解码
public String decode(String str) {
/*
从根节点,寻找数字对应的字符
数字是 0 向左走
数字是 1 向右走
如果没走到头,每走一步数字的索引 i++
走到头就可以找到解码字符,再将 node 重置为根节点
*/
char[] chars = str.toCharArray();
int i = 0;
StringBuilder sb = new StringBuilder();
Node node = root;
while (i < chars.length) {
if (!node.isLeaf()) { // 非叶子
if(chars[i] == '0') { // 向左走
node = node.left;
} else if(chars[i] == '1') { // 向右走
node = node.right;
}
i++;
}
if (node.isLeaf()) {
sb.append(node.ch);
node = root;
}
}
return sb.toString();
}
@SuppressWarnings("all")
public static void main(String[] args) {
HuffmanTree tree = new HuffmanTree("abbccccccc");
String encoded = tree.encode();
System.out.println(encoded);
System.out.println(tree.decode(encoded));
}
}
注意
- 循环中非叶子节点 i 要自增,但叶子节点 i 暂不自增
- 第一个非叶子的 if 判断结束后,仍需要第二个叶子的 if 判断,因为在第一个 if 内 node 发生了变化
题目编号 | 题目标题 | 算法思路 |
---|---|---|
1167(Plus 题目) | 连接棒材的最低费用 | Huffman 树、贪心 |
public class ActivitySelectionProblem {
/*
要在一个会议室举办 n 个活动
- 每个活动有它们各自的起始和结束时间
- 找出在时间上互不冲突的活动组合,能够最充分利用会议室(举办的活动次数最多)
例1
0 1 2 3 4 5 6 7 8 9
|-------)
|-------)
|-------)
例2
0 1 2 3 4 5 6 7 8 9
|---)
|---)
|-----------------------)
|-------)
|---)
|---------------)
几种贪心策略
1. 优先选择持续时间最短的活动
0 1 2 3 4 5 6 7 8 9
|---------------)
|-------)
|---------------)
2. 优先选择冲突最少的活动
0 1 2 3 4 5 6 7 8 9
|-------) 3
|-------) 4
|-------) 4
|-------) 4
|-------) 4
|-------) 2
|-----------) 4
|-------) 4
|-------) 4
|-------) 4
|-------) 3
3. 优先选择最先开始的活动
0 1 2 3 4 5 6 7 8 9
|-----------------------------------)
|---)
|---)
|---)
4. 优先选择最后结束的活动
*/
static class Activity {
int index;
int start;
int finish;
public Activity(int index, int start, int finish) {
this.index = index;
this.start = start;
this.finish = finish;
}
@Override
public String toString() {
return "Activity(" + index + ")";
}
}
public static void main(String[] args) {
Activity[] activities = new Activity[]{
new Activity(0, 1, 3),
new Activity(1, 2, 4),
new Activity(2, 3, 5)
};
// Activity[] activities = new Activity[]{
// new Activity(0, 1, 2),
// new Activity(1, 3, 4),
// new Activity(2, 0, 6),
// new Activity(3, 5, 7),
// new Activity(4, 8, 9),
// new Activity(5, 5, 9)
// };
select(activities, activities.length);
}
public static void select(Activity[] activities, int n) {
List<Activity> result = new ArrayList<>();
int i, j;
i = 0;
result.add(activities[i]);
for (j = 1; j < n; j++) {
if (activities[j].start >= activities[i].finish) {
result.add(activities[j]);
i = j;
}
}
System.out.println(result);
}
}
题目编号 | 题目标题 | 算法思路 |
---|---|---|
435 | 无重叠区间 | 贪心 |
题解
// 下面代码为 Leetcode 435 题解
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals, Comparator.comparingInt(a -> a[1]));
int i, j;
i = 0;
int count = 1;
for (j = 1; j < intervals.length; j++) {
if (intervals[j][0] >= intervals[i][1]) {
i = j;
count++;
}
}
return intervals.length - count;
}
public class FractionalKnapsackProblem {
/*
1. n个物品都是液体,有重量和价值
2. 现在你要取走 10升 的液体
3. 每次可以不拿,全拿,或拿一部分,问最高价值是多少
编号 重量(升) 价值
0 4 24 水
1 8 160 牛奶 选中 7/8
2 2 4000 五粮液 选中
3 6 108 可乐
4 1 4000 茅台 选中
8140
简化起见,给出的数据都是【价值/重量】能够整除,避免计算结果中出现小数,增加心算难度
*/
static class Item {
int index;
int weight;
int value;
public Item(int index, int weight, int value) {
this.index = index;
this.weight = weight;
this.value = value;
}
int unitPrice() {
return value / weight;
}
@Override
public String toString() {
return "Item(" + index + ")";
}
}
public static void main(String[] args) {
Item[] items = new Item[]{
new Item(0, 4, 24),
new Item(1, 8, 160),
new Item(2, 2, 4000),
new Item(3, 6, 108),
new Item(4, 1, 4000),
};
select(items, 10);
}
static void select(Item[] items, int total) {
Arrays.sort(items, Comparator.comparingInt(Item::unitPrice).reversed());
int remainder = total;
int max = 0;
for (Item item : items) {
if (remainder - item.weight > 0) {
max += item.value;
remainder -= item.weight;
} else {
max += remainder * item.unitPrice();
break;
}
}
System.out.println("最高价值为:" + max);
}
}
可能得不到最优解
public class KnapsackProblem {
/*
1. n个物品都是固体,有重量和价值
2. 现在你要取走不超过 10克 的物品
3. 每次可以不拿或全拿,问最高价值是多少
编号 重量(g) 价值(元)
0 1 1_000_000 钻戒一枚
1 4 1600 黄金一块
2 8 2400 红宝石戒指一枚
3 5 30 白银一块
*/
static class Item {
int index;
int weight;
int value;
public Item(int index, int weight, int value) {
this.index = index;
this.weight = weight;
this.value = value;
}
public int unitValue() {
return value / weight;
}
@Override
public String toString() {
return "Item(" + index + ")";
}
}
public static void main(String[] args) {
Item[] items = new Item[]{
new Item(0, 1, 1_000_000),
new Item(1, 4, 1600),
new Item(2, 8, 2400),
new Item(3, 5, 30)
};
select(items, 10);
}
static void select(Item[] items, int total) {
Arrays.sort(items, Comparator.comparingInt(Item::unitValue).reversed());
int max = 0; // 最大价值
for (Item item : items) {
System.out.println(item);
if (total >= item.weight) { // 可以拿完
total -= item.weight;
max += item.value;
} else { // 拿不完
// max += total * item.unitValue();
// break;
}
}
System.out.println("最大价值是:" + max);
}
}
问题名称 | 是否能用贪心得到最优解 | 替换解法 |
---|---|---|
Dijkstra(不存在负边) | ✔️ | |
Dijkstra(存在负边) | ❌ | Bellman-Ford |
Prim | ✔️ | |
Kruskal | ✔️ | |
零钱兑换 | ❌ | 动态规划 |
Huffman 树 | ✔️ | |
活动选择问题 | ✔️ | |
分数背包问题 | ✔️ | |
0-1 背包问题 | ❌ | 动态规划 |
Dynamic-Programming 指动态编程
Programming是指使用数学方法找出根据子问题求解当前的公式
Dynamic是指缓存上一步的结果根据上一步的结果求解当前结果
动态规划
package Algorithm.DynamicProgramming;
package Algorithm.DynamicProgramming;
/**
* 求斐波那契数列的第n项(使用动态规划)
*/
public class FIbonaccl {
/*public static int fibonacci(int n) {
int[] dp = new int[n + 1];//用来缓存结果
if (n == 0) {
dp[0] = 0;
return 0;
}
if (n == 1) {
dp[1] = 1;
return 1;
}
for (int i = 2; i < n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}*/
public static int fibonacci(int n) {
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
//使用两个变量代替数组
int a = 0;
int b = 1;
for (int i = 2; i <= n; i++) {
int c = b + a;
a = b;
b = c;
}
return b;
}
public static void main(String[] args) {
System.out.println(fibonacci(13));
}
}
与之前一样都是属于动态规划
import java.util.*;
import java.util.stream.Collectors;
/**
* Bellman-Ford 算法,可以处理负边
不能出现负环(自我解决)
*/
public class BellmanFord {
static class Edge {
int from;
int to;
int weight;
public Edge(int from, int to, int weight) {
this.from = from;
this.to = to;
this.weight = weight;
}
}
/*
f(v)表示从起点出发,到v这个顶点的最短距离
初始时
f(v) = 0 当v==起点 时
f(v) = 无穷 当v !=起点 时
之后
新 旧 所有from
f(to) = min(f(to),f(from)+from.weight)
v1 v2 v3 v4 v5 v6
0 无穷 无穷 无穷 无穷 无穷
*/
public static void main(String[] args) {
List<Edge> edges = List.of(
new Edge(6,5,9),
new Edge(4,5,6),
new Edge(1,6,14),
new Edge(3,6,2),
new Edge(3,4,11),
new Edge(2,4,15),
new Edge(1,3,9),
new Edge(1,2,7)
);
int[] dp = new int[7];//一维数组缓存结果
dp[1] = 0;
for (int i = 2; i < dp.length; i++) {
dp[i] = Integer.MAX_VALUE;
}
for (int i = 0; i < edges.size() - 1; i++) {
for (Edge e: edges){
if (dp[e.from] != Integer.MAX_VALUE){
dp[e.to] = Integer.min(dp[e.to],dp[e.from]+e.weight );
}
}
}
print(dp);
}
static void print(int[] dp){
System.out.println(Arrays.stream(dp)
.mapToObj(i->i == Integer.MAX_VALUE?"∞":String.valueOf(i))
.collect(Collectors.joining(",","[","]")));
}
}
import java.util.Arrays;
import java.util.stream.IntStream;
public class UniquePaths {
//二维
/*public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
for (int i = 0; i < m; i++) {
dp[i][0] = 1;
}
for (int i = 0; i < n; i++) {
dp[0][i] = 1;
}
for (int i = 1; i < m; i++) {
for (int j = 1; j
/**使用一维数组
* 思路:
* 用一位数组自己加上前一个更新的值
* @param m
* @param n
* @return
*/
public int uniquePaths(int m, int n) {
int[] dp = new int[n];
Arrays.fill(dp, 1);
for (int i = 1; i < m; i++) {
for (int j = 1; j <n; j++) {
dp[j] = dp[j]+dp[j-1];
}
}
System.out.println(Arrays.toString(dp));
return dp[n-1];
}
static void print(int[][] dp){
System.out.println("-".repeat(20));
Object[] array = IntStream.range(0,dp[0].length+1).boxed().toArray();
System.out.printf(("%2d".repeat(dp[0].length))+"%n",array);
for (int[]d:dp){
array = Arrays.stream(d).boxed().toArray();
System.out.printf(("%2d".repeat(dp[0].length))+"%n",array);
}
}
public static void main(String[] args) {
System.out.println(new UniquePaths().uniquePaths(3,5));
}
}
import java.util.Arrays;
public class KnapsackProblem {
/*
1. n个物品都是固体,有重量和价值
2. 现在你要取走不超过 10克 的物品
3. 每次可以不拿或全拿,问最高价值是多少
编号 重量(g) 价值(元) 简称
0 1 1_000_000 钻戒一枚 D
1 4 1600 黄金一块 A
2 8 2400 红宝石戒指一枚 R
3 5 30 白银一块 S
1 001 630 贪心解
1 002 400 正确解
*/
/**
* 0 1 2 3 4 5 6 7 8 9 10
*
* 0 0 0 0 0 A A A A A A A 黄金
*
* 1 0 0 0 0 A A A A R R R 红宝石
*
* 2 0 0 0 0 A A A A R R R 白银
*
* 3 0 0 D D D DA DA DA DA DR DR 砖石
* if(装不下){
* 保持上一列价值不变
* dp[i][j] = dp[i-1][j]
* }else{装得下
* 当前选择价值加上 上一行剩余重量可加入价值比较
* dp[i][j]=max(dp[i-1][j],item.value+dp[i-1][j-item.weight])
* }
*/
static class Item {
int index;
int weight;
int value;
public Item(int index, int weight, int value) {
this.index = index;
this.weight = weight;
this.value = value;
}
public int unitValue() {
return value / weight;
}
@Override
public String toString() {
return "Item(" + index + ")";
}
}
public static void main(String[] args) {
Item[] items = new Item[]{
new Item(0, 1, 1_0_000),
new Item(1, 4, 1600),
new Item(2, 8, 2400),
new Item(3, 5, 30)
};
select1(items, 10);
}
//二维
static void select2(Item[] items, int total) {
int[][]dp = new int[items.length][total+1];
Item item0 = items[0];
for (int j = 0; j < total + 1; j++) {
if (j>=item0.weight){
dp[0][j] = item0.value;
}else {
dp[0][j] = 0;
}
}
for (int i = 1; i < dp.length; i++) {
Item item = items[i];
for (int j = 0; j < total + 1; j++) {
if (j>=item.weight){
dp[i][j]=Integer.max(dp[i-1][j],item.value+dp[i-1][j-item.weight]);
}else{
dp[i][j] = dp[i-1][j];
}
}
}
for (int[] ints : dp) {
System.out.println(Arrays.toString(ints));
}
System.out.println(dp[dp.length-1][total]);
}
//改动成1维
static void select1(Item[] items, int total) {
int[]dp = new int[total+1];
Item item0 = items[0];
for (int j = 0; j < total + 1; j++) {
if (j>=item0.weight){
dp[j] = item0.value;
}else {
dp[j] = 0;
}
}
//从左到右不行了,改为从右到左
for (int i = 1; i < items.length; i++) {
Item item = items[i];
for (int j = total; j > 0; j--) {
if (j>=item.weight){
dp[j]=Integer.max(dp[j],item.value+dp[j-item.weight]);
}
}
}
System.out.println(Arrays.toString(dp));
}
}
import java.util.Arrays;
/**
* 完全背包问题
*/
public class KnapsackProblemComplete {
static class Item {
int index;
String name;
int weight;
int value;
public Item(int index, int weight, int value) {
this.index = index;
this.weight = weight;
this.value = value;
}
public Item(int index, String name, int weight, int value) {
this.index = index;
this.name = name;
this.weight = weight;
this.value = value;
}
@Override
public String toString() {
return "Item(" + index + ")";
}
}
public static void main(String[] args) {
Item[] items = new Item[]{
new Item(1, "青铜",2, 3),//c
new Item(2, "白银",3, 4),//s
new Item(3, "黄金",4, 7),//a
};
System.out.println(select1(items,6));
}
/*
0 1 2 3 4 5 6
1 0 0 c c cc cc ccc
2 0 0 c s cc sc ccc>ss
3 0 0 c s a a ac>ccc
if(放的下){
dp[i][j] = max(dp[i-1][j-1],item.value+dp[i][j-item.weight])
}eles{
dp[i][j] = dp[i-1][j]
}
*/
/**
* 二维
* @param items 当前物品
* @param total 可以装入最大价值
* @return
*/
private static int select2(Item[] items,int total){
int[][]dp = new int[items.length][total+1];
Item item0 =items[0];
for (int j = 0; j < total + 1; j++) {
if (j>=item0.weight){
dp[0][j] = dp[0][j- item0.weight]+ item0.value;
}else {
dp[0][j] = 0;
}
}
for (int i = 1;i<items.length;i++){
Item item = items[i];
for (int j = 0; j <total+1; j++) {
if (j>=item.weight){
dp[i][j] = Integer.max(dp[i][j- item.weight]+ item.value,dp[i-1][j]);
}else {
dp[i][j] = dp[i-1][j];
}
}
}
System.out.println(Arrays.toString(dp[items.length-1]));
return dp[items.length-1][total];
}
/**
* 一维
* 第一行初始化可以省略了
* @param items
* @param total
* @return
*/
private static int select1(Item[] items,int total){
int[]dp = new int[total+1];
for (Item item : items) {
for (int j = 0; j < total + 1; j++) {
if (j >= item.weight) {
dp[j] = Integer.max(dp[j - item.weight] + item.value, dp[j]);
}
}
}
System.out.println(Arrays.toString(dp));
return dp[total];
}
}
完全背包是当前行的剩余价值,01背包是上一次 的剩余价值
与完全背包问题类似,不过会出现凑不齐金额的情况(采取初始化最大价值 amount+1),
不使用Interge.max_value由于做加法 会超出范围
package Algorithm.DynamicProgramming;
import java.util.Arrays;
/**
* 零钱兑换 - 动态规划
* 凑成总金额的凑法中,需要硬币最少个数是几个
*/
public class ChangeMakingProblem {
/**
* 面值 0 1 2 3 4 5
* 1 0 1 11 111 1111 11111
* 2 0 1 2 21 22 221
* 5 0 1 2 21 22 5
* if(装的下){
* dp[i][j] = min(dp[i-1][j],goValue(coins[i])+dp[i][j-coins[i]])
* }else{
* dp[i][j] = dp[i-1][j]
* }
* @param coins 每个金额类比物品重量
* @param amount 类比背包容量
* @return 每个物品的价值都为1
*/
public int coinChange(int[] coins, int amount) {
int[]dp = new int[amount+1];
for (int j = 1; j < amount + 1; j++) {
if (j>=coins[0]){
dp[j] = dp[j-coins[0]]+goValue(coins[0]);
}else {
dp[j] = amount+1;//最大值
}
}
for (int i = 1; i < coins.length; i++) {
for (int j = 1; j =coins[i]){
dp[j] = Integer.min(dp[j],goValue(coins[i])+dp[j-coins[i]]);
}
}
}
//System.out.println(Arrays.toString(dp));
if (dp[amount] <= amount){
return dp[amount];
}
return -1;
}
public int goValue(int coin){
return 1;
}
public static void main(String[] args) {
ChangeMakingProblem leetCode322 = new ChangeMakingProblem();
int i = leetCode322.coinChange(new int[]{2}, 11);
System.out.println(i);
}
}
import java.util.Arrays;
/**
* 零钱兑换II - 动态规划
* 凑成总金额有几种凑发
*/
public class ChangeMakingProblemTwo518 {
/**
* 面值 0 1 2 3 4 5
* 1 1 1 11 111 1111 11111
*
* 2 1 1 11 21 22 221
* 2 111 211 11111
* 1111 2111
* 5 1 1 2 21 22 5
* 1 11 21 22 221
* 2 111 211 11111
* 1111 2111
* if(装的下){
* dp[i][j] = dp[i-1][j]++dp[i][j-coins[i]]
* }else{
* dp[i][j] = dp[i-1][j]
* }
* @param coins 每个金额类比物品重量
* @param amount 类比背包容量
* @return 每个物品的凑法都在上一次的基础上进行增加
*/
public int coinChange(int[] coins, int amount) {
int[]dp = new int[amount+1];
dp[0] = 1;
for (int coin : coins) {
for (int j = coin; j < amount + 1; j++) {
/*if (j >= coin) {
dp[j] += dp[j - coin];
}*/
//优化 j大于coin 则从大于时开始
dp[j] += dp[j - coin];
}
}
System.out.println(Arrays.toString(dp));
return dp[amount];
}
public static void main(String[] args) {
ChangeMakingProblemTwo518 leetCode322 = new ChangeMakingProblemTwo518();
int i = leetCode322.coinChange(new int[]{1,2,5}, 5);
System.out.println(i);
}
}
类似的完全背包问题
import java.util.Arrays;
/**
* 钢条切割问题-动态规划
* 类似的完全背包问题
*/
public class CutRodProblem {
/*
0 1 2 3 4 5 6 7 8 9 10
0 1 5 8 9 10 17 17 20 24 30
4
0 1 2 3 4 钢条长度 = 背包容量
长度:1 1 11 111 1111
value (1) (2) (3) (4)
2 1 11 111 1111
2 21 22
211
(1) (5) (6) (10)
3 1 11 111 1111
2 21 22
3 211
31
(1) (5) (8) (10)
4 1 11 111 1111
2 21 22
3 211
31
4
(1) (5) (8) (10)
物品重量
if(放的下){
dp[i][j] = max(dp[i-1][j],当前物品价值+剩余物品价值=values[i]+dp[i][j-长度i])
}else{
dp[i][j] = dp[i-1][j]
}
*/
static int Cut(int[] values,int n){
int[] dp = new int[n+1];
for (int i = 1 ;i<values.length;i++ ) {
var value = values[i];
for (int j = i; j < n + 1; j++) {
dp[j] = Integer.max(dp[j],value+dp[j - i]);
}
}
System.out.println(Arrays.toString(dp));
return dp[n];
}
public static void main(String[] args) {
System.out.println(Cut(new int[]{0,1,5,8,9},4));
}
}
/**
* 最长公共子串
*/
public class LCSubstring {
/* 经过发现 当前和上一次都相同 来自于对角线的值
b i s h e n y a n g
s 0 0 1 0 0 0 0 0 0 0
h 0 0 0 2 0 0 0 0 0 0
e 0 0 0 0 3 0 0 0 0 0
a 0 0 0 0 0 0 0 1 0 0
n 0 0 0 0 0 0 0 0 2 0
if(相同的字符){
dp[i][j] = dp[i-1][j-1]+1;
}else{
dp[i][j] = 0;
}
*/
public static int longestCommonSubsequence(String a, String b) {
int[][] dp = new int[a.length()][b.length()];
int max = 0;
for (int i = 0; i <a.length(); i++) {
for (int j = 0; j < b.length(); j++) {
if (a.charAt(i) == b.charAt(j)){
if (i==0||j==0){
dp[i][j]=1;
}else{
dp[i][j] = dp[i-1][j-1]+1;
}
max = Integer.max(dp[i][j],max);
}else {
dp[i][j] = 0;
}
}
}
return max;
}
public static void main(String[] args) {
System.out.println(longestCommonSubsequence("bishenyang","shen"));
}
}
与最长公共子串的差别 **:**公共子序列不要求连续,只要求顺序保持正确。
/**
* 最长公共子序列
*/
public class LCSubqueue {
/* 多出来一列不用考虑边界条件
a b c x y z
0 0 0 0 0 0 0
a 0 1 1 1 1 1 1
b 0 1 2 2 2 2 2
x 0 1 2 2 3 3 3
y 0 1 2 2 3 4 4
z 0 1 2 2 3 4 5
if(相同字符){
dp[i][j] = dp[i-1][j-1]+1; 上一行上一列(左上角)+1
}else 不同字符{
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);max(上一行,上一列)
}
*/
public static int longestCommonSubsequence(String a, String b) {
int m = a.length();
int n = b.length();
int[][]dp = new int[m+1][n+1];
for (int i = 0; i < m + 1; i++) {
dp[i][0] = 0;
}
for (int j = 1; j < n + 1; j++) {
dp[0][j] = 0;
}
for (int i = 1; i < m+1; i++) {
for (int j = 1; j < n+1; j++) {
if (a.charAt(i-1)==b.charAt(j-1)){
dp[i][j] = dp[i-1][j-1]+1;
}else {
dp[i][j] = Integer.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[m][n];
}
public static void main(String[] args) {
System.out.println(longestCommonSubsequence("abcxyz","abxyz"));
}
}
class Solution {
public int minDistance(String word1, String word2) {
int common = longestCommonSubsequence(word1,word2);
return word1.length()+word2.length()-2*common;
}
public int longestCommonSubsequence(String a, String b) {
int m = a.length();
int n = b.length();
int[][]dp = new int[m+1][n+1];
for (int i = 0; i < m + 1; i++) {
dp[i][0] = 0;
}
for (int j = 1; j < n + 1; j++) {
dp[0][j] = 0;
}
for (int i = 1; i < m+1; i++) {
for (int j = 1; j < n+1; j++) {
if (a.charAt(i-1)==b.charAt(j-1)){
dp[i][j] = dp[i-1][j-1]+1;
}else {
dp[i][j] = Integer.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[m][n];
}
}
/**
* 最长递增子序列
*/
public class LeetCode300 {
/* 1 2 3 4
1 3 6 4 9
初始 1 3 6 4 9
1 13 16 14 19
136 134 139
169
1369
149
1349
(1 (2 (3 (3 (4
4
*/
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp,1);
for (int i = 1; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if (nums[i]>nums[j]){//满足了升序条件的数组
dp[i] = Integer.max(dp[i],dp[j]+1);
}
}
}
//结尾的数不一定是最大的
return Arrays.stream(dp).max().getAsInt();
}
}
这几种节点组成二叉搜索树有多少种可能
C(0) = 1种
C(1) =1种
C(2) = 2种
C(3)时以1为头节点,剩余节点都大于1,其余可以看成2个节点组合,即C(2),以2为根节点固定了,只有一种,以3为根节点剩余节点都比3小,看作**C(2)**即可。
所以:
C(3) = C(2)+C(1)+C(2) = 5
c(4)时以1组成的节点为头节点,其余节点都大于1,可以看作有C(3)种情况,以2为根节点,左侧节点已经固定为1,右侧两个节点组合可以看成C(2),以3为根节点可以同理以2为根节点
C(4)=2 C(3)+2*C(2)* = 14
同理可看出
C(5) = C(0)C(4)+C(1)(3)+C(2)C(2)+C(3)C(1)+C(4) C(0)=14+5+4+5+14=42种
/** 几种数能组成多少种二叉搜索数
* 卡特兰数:
* c(1) = 1
* c(2) = 2
* c(3) = c(2)+c(1)+c(2) = 5
* C(4) = 2*C(3)+2*C(2) = 14
*/
public class Catalan {
static int catalan(int n){
int[]dp = new int[n+1];
dp[0] = 1;
dp[1] = 1;
for (int j = 2; j < n + 1; j++) {
for (int i = 0; i < j; i++) {
System.out.printf("(%d,%d)\t",i,j-1-i);
dp[j] += dp[i]*dp[j-1-i];
}
System.out.println(dp[j]);
}
return 0;
}
public static void main(String[] args) {
System.out.println(catalan(5));
}
}
2.出栈总数:
3.括号生成
public List<String> generateParenthesis(int n) {
ArrayList<String>[]dp = new ArrayList[n + 1];
dp[0] = new ArrayList<>(List.of(""));//""
dp[1] = new ArrayList<>(List.of("()"));//()
//dp[2] ()(),(())
//dp[3] ()()(),()(()),(())(),((()))
for (int j = 2; j < n + 1; j++) {
dp[j] = new ArrayList<>();
for (int i = 0; i < j; i++) {//第j个卡特兰数的拆分
System.out.printf("(%d,%d)\t",i,j-1-i);//数字个数代表了平级,和包含关系的括号个数
//i对应了内层要嵌套的括号,j对应的集合是平级要拼接的括号
for (String k1 : dp[i]) {
for (String k2 : dp[j - 1 - i]) {
dp[j].add("("+k1+")"+k2) ;
}
}
}
System.out.println(dp[j]);
}
题目编号 | 题目标题 |
---|---|
96 | 不同的二叉搜索树 |
22 | 括号生成 |
/**
* 打家劫舍
*/
public class HouserRobber198 {
/*
价值 0 1 2 3 4
0 0 0 0 0
0(2) 2 0 0 0 0
1(7) 2 7 0 0 0
2(9) 2 7 11 0 0
3(3) 2 7 11 11(10) 0
4(1) 2 7 11 11 12
dp[j] = max(dp[j-1],dp[j-2]+item.value)
*/
public int rob(int[] nums) {
if (nums.length == 1) {
return nums[0];
}
int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = Integer.max(dp[0],nums[1]);
for (int i = 2; i < nums.length; i++) {
dp[i] = Integer.max(dp[i - 1], dp[i - 2] + nums[i]);
}
return dp[nums.length - 1];
}
public static void main(String[] args) {
System.out.println(new HouserRobber198().rob(new int[]{2, 7, 9, 3, 1}));
}
}
求经过所有点路径最短
import java.util.Arrays;
import java.util.stream.Collectors;
public class TravellingSalesmanProblem {
/*
北京->
上海->
武汉->
西安->
3*2
北京->上海->
武汉->
西安-> 北京->
西安->
武汉->北京->
武汉->
上海->
西安->北京->
西安->
上海->北京->
西安->
上海->
武汉->北京->
武汉->
上海->北京->
d(从出发城市,剩余城市集合)==>从出发城市开始,走完剩余城市,花费的最小代价
d(0,1|2|3)==>g[0][1]+d(1,2|3)
g[1][3]+d(3,2)
==>g[3][2]+d(2,空)
g[2][0]
g[1][2]+d(2,3)
g[0][3]+d(3,1|2)
g[0][2]+d(2,1|3)
从出发城市 =i
剩余城市集合 =j
遍历j时的变量叫k(剩余的一个城市)
d(i,j)==>min(
g[i][k],d(k,j去掉k)
g[i][k],d(k,j去掉k)
g[i][k],d(k,j去掉k)
)
d(k,空) ==》从k回到起点
0 1 2 3 4 5 6 7
没城市 1 2 1|2 3 1|3 2|3 1|2|3
0
1
2
3
000 没城市 0
001 1号 1
010 2号 2
100 3号 4
011 2号和1号 3
同理
*/
public static void main(String[] args) {
int[][] graph = {
{0,1,2,3},
{1,0,6,4},
{2,6,0,5},
{3,4,5,6}
};
System.out.println(tsp(graph));
}
private static int tsp(int[][] g) {
int m = g.length;//城市数
int n = 1<<(m-1);//剩余城市的组合数 //用二进制表达 2的n次方
int[][]dp = new int[m][n];
for (int k = 0; k < m; k++) {
dp[k][0] = g[k][0];
}
for (int j = 1; j < n; j++) {
for (int i = 0; i < m; i++) {
dp[i][j] = Integer.MAX_VALUE/2;
if (contains(j,i))continue;//从剩余城市集合已包含出发城市
//填充单元格
for (int k = 0; k < m; k++) {
if (contains(j,k)){
dp[i][j] =Integer.min(g[i][k] + dp[k][exclude(j,k)],dp[i][j]);
}
}
}
}
print(dp);
return dp[0][n-1];//最终结果在右上角
}
static void print(int[][] dist) {
System.out.println("-------------");
for (int[] row : dist) {
System.out.println(Arrays.stream(row)//使用Arrays.stream()方法将整型数组row转换为一个流(Stream)
.boxed()//使用boxed()方法将流中的元素装箱为对应的包装类型,这里是将整型转为Integer类型。
.map(x -> x == Integer.MAX_VALUE/2 ? "∞" : String.valueOf(x))//接下来,使用map()方法将流中的每个元素进行映射操作。如果元素值是Integer.MAX_VALUE,将其替换为"∞",否则转换为对应的字符串表示。
.map(s -> String.format("%2s", s))//继续使用map()方法,对每个字符串进行格式化操作,使用String.format()将每个字符串格式化为长度为2的字符串。
.collect(Collectors.joining(",", "[", "]")));//使用collect()方法将格式化后的字符串元素收集到一个列表中,并使用Collectors.joining()方法将列表中的元素用逗号连接起来,同时在最前面加上"[“,在最后面加上”]",形成最终的字符串表示。
}
}
static boolean contains(int set,int city){
return (set>>(city-1)&1) == 1;
}
//011 去掉1
//010
static int exclude(int set,int city){//把集合内部去掉一个城市
return set^(1<<(city-1));
}
}
把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。在计算机科学中,分治法就是运用分治思想的一种很重要的算法。分治法是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)等等
分治法与动态规划主要共同点:
二者都要求原问题具有最优子结构性质,都是将原问题分而治之,分解成若干个规模较小(小到很容易解决的程序)的子问题.然后将子问题的解合并,形成原问题的解.
分治法通常利用递归求解.
动态规划通常利用迭代法自底向上求解,但也能用具有记忆功能的递归法自顶向下求解.
分治法与动态规划主要区别:
分治法将分解后的子问题看成相互独立的.
动态规划将分解后的子问题理解为相互间有联系,有重叠部分.
import java.util.concurrent.ThreadLocalRandom;
/*
快速选择算法-分儿治之
*/
public class QuickSelect {
/*
求排在第 i名的元素 i从0开始,由小到达排序
6 5 1 2 4
*/
public static int quick(int[]array, int left,int right,int i){
/*
6 5 1 2 [4]
并不是直接排好序,而是找到正确基准点返
2
1 2 4 6 5
使用快排 直接把正确位置的元素取出
*/
int p = partition(array, left, right);//基准点元素的索引
if(p == i){
return array[p];
}
if (i<p){
return quick(array,left,p-1,i);
}else{
return quick(array, p+1, right, i);
}
}
private static int partition(int[] a, int left, int right) {
int idx = ThreadLocalRandom.current().nextInt(right - left + 1) + left;
swap(a, left, idx);
int pv = a[left];
int i = left + 1;
int j = right;
while (i <= j) {
// i 从左向右找大的或者相等的
while (i <= j && a[i] < pv) {
i++;
}
// j 从右向左找小的或者相等的
while (i <= j && a[j] > pv) {
j--;
}
if (i <= j) {
swap(a, i, j);
i++;
j--;
}
}
swap(a, j, left);
return j;
}
private static void swap(int[] a, int i, int j) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
public static void main(String[] args) {
int[] array = {6,5,1,2,4};
System.out.println(quick(array,0,array.length-1,0));
System.out.println(quick(array,0,array.length-1,1));
System.out.println(quick(array,0,array.length-1,2));
System.out.println(quick(array,0,array.length-1,3));
System.out.println(quick(array,0,array.length-1,4));
}
}
/**
* 快速幂 -- 分治
*/
public class QuickPow {
public double myPow(double x, int n) {
long p = n;
if (p < 0) {
p = -p;
}
double r = myPowPositive(x, p);
return n < 0 ? 1 / r : r;
}
public static double myPowPositive(double x, long n) {
if (n == 0) {
return 1;
}
if (n == 1) {
return x;
}
double y = myPowPositive(x, n / 2);
/*
可以用位运算判断寄偶
奇数&1为1偶数为0
*/
if (n % 2 == 0) {
return y * y;
} else {
return x * y * y;
}
}
public static void main(String[] args) {
System.out.println(myPowPositive(2, 16));
}
}
/**
* 求平方根的整数部分
*/
public class SqrtLQ69 {
int mySqrt(int x) {
int l = 0, r = x, ans = -1;
while (l <= r) {
int mid = (r + l) >>> 1;
if ((long) mid * mid <= x) { //可以乘法改除法防越界
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
return ans;
}
}
/**
* 数组中的中位数 - 快速选择排序
*
*/
public class FindMedian {
/*
寄数个
1 4 5 ==》 4
1 3 4 5 6 ==》 4
偶数个
1 3 4 5 ==》3.5
*/
public static double findMedian(int[] nums){
if (nums.length%2 == 1){//奇数
return QuickSelect.quick(nums,0,nums.length-1,nums.length/2);
}else {//偶数
return (double) (QuickSelect.quick(nums, 0, nums.length - 1, nums.length / 2-1) +
QuickSelect.quick(nums, 0, nums.length - 1, nums.length / 2)) /2;
}
}
public static void main(String[] args) {
System.out.println(" 奇数");
System.out.println(findMedian(new int[]{4,5,6}));
System.out.println(findMedian(new int[]{4,5,1,6,3}));
System.out.println("偶数");
System.out.println(findMedian(new int[]{3,1,5,4}));
}
}
public class LongestSubstring395 {
public static int longestSubstring(String s, int k) {
//终止条件 子串长度
if (s.length()<k){
return 0;//落选了 返回长度0
}
//1.统计出现次数
int[] counts = new int[26];//索引对应字符 值用来存储该字符出现了几次
char[] chars = s.toCharArray();
for (char c : chars) {//'a' -> 0 ‘b’->1
counts[c - 'a']++;
}
System.out.println(Arrays.toString(counts));
for (int i = 0; i < chars.length; i++) {
char c = chars[i];
int count = counts[c - 'a'];//字符出现次数
if (count > 0 && count < k) {
int j = i + 1;
while (j < s.length() && counts[chars[j] - 'a'] < k){
j++;
}
System.out.println(s.substring(0, i) + "\t" + s.substring(j));//切分字符
return Integer.max(
longestSubstring(s.substring(0, i),k)
,longestSubstring(s.substring(j),k));
}
}
//子串入选
return s.length();
//1.先将出现次数小于k次的剔除
}
public static void main(String[] args) {
System.out.println(longestSubstring("dddxaabaaabaacciiiiefbff", 3));
/*
dddxaabaaabaacciiiiefbff
第一次剔除 ddd aabaaabaa iiii fbff
aa aaa aa ff
统计字符串中每个字符的出现次数,移除那些出现次数
String str = "aHello";
byte[] byteArray = str.getBytes(); // 使用默认字符集进行转换
// 遍历字节数组并打印每个字节的值
for (byte b : byteArray) {
System.out.println(b);
}
}
}
/**
* 回溯
* -程序在运行过程中分成了多个阶段
* -通过某些手段,将数据恢复到之前某一阶段,这就称之为回溯
* -手段包括
* +方法栈
* +自定义栈
*/
public class Backtracking {
public static void main(String[] args) {
rec(1,new LinkedList<>());
}
static void rec(int n, LinkedList<String> list){
if (n == 3){
return;
}
System.out.println(" befor"+list);
list.push("a");//加入
rec(n+1,list);
list.pop();//恢复数据,在方法后,进行对称操作
System.out.println("befor "+list);
}
}
import java.util.LinkedList;
import java.util.List;
/**
* 全排列-回溯
*/
public class Permute {
public static List<List<Integer>> permute(int[] nums) {
boolean[] isvisited = new boolean[nums.length];
List<List<Integer>> list = new LinkedList<>();
dfs(nums,isvisited,new LinkedList<>(),0,list);
return list;
}
static void dfs(int[] nums, boolean[] isvisited, LinkedList<Integer> stack, int j, List<List<Integer>> list){
if (stack.size() == nums.length){
list.add(new LinkedList<>(stack));
System.out.println(stack);
return;
}
for (int i = j; i < nums.length; i++) {
if (!isvisited[i]){
stack.push(nums[i]);
isvisited[i] = true;
dfs(nums, isvisited, stack,j, list);
//回溯当前状态
isvisited[i] = false;
stack.pop();
}
}
}
public static void main(String[] args) {
List<List<Integer>> permute = permute(new int[]{1, 2, 3});
System.out.println(permute);
}
}
/**
* 不重复的全排列
*/
public class PermuteUnique {
public static List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
boolean[] isvisited = new boolean[nums.length];
List<List<Integer>> list = new LinkedList<>();
dfs(nums,isvisited,new LinkedList<>(),0,list);
return list;
}
static void dfs(int[] nums, boolean[] isvisited, LinkedList<Integer> stack, int j, List<List<Integer>> list){
if (stack.size() == nums.length){
list.add(new LinkedList<>(stack));
System.out.println(stack);
return;
}
for (int i = j; i < nums.length; i++) {
if (i >0&&nums[i] == nums[i-1] &&!isvisited[i-1]){//找出重复数字
continue;
}
if (!isvisited[i]){
stack.push(nums[i]);
isvisited[i] = true;
dfs(nums, isvisited, stack,j, list);
//回溯当前状态
isvisited[i] = false;
stack.pop();
}
}
}
public static void main(String[] args) {
List<List<Integer>> permute = permuteUnique(new int[]{1, 1, 3});
System.out.println(permute);
}
}
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* LeetCode 77 组合
*/
public class Combination {
public static List<List<Integer>> combine(int n, int k) {
List<List<Integer>> result = new ArrayList<>();
dfs(1,n,k,result,new LinkedList<Integer>());
return result;
}
private static void dfs(int start,int n, int k, List<List<Integer>> result, LinkedList<Integer> stack) {
if (stack.size() == k){
result.add(new ArrayList<>(stack));
return;
}
//i = 1 2 3 4
for (int i = start; i <= n; i++) {
// k k-stack.length //还差几个填满
//n-i+1 还剩几个备用数字
if (k-stack.size()>n-i+1){
continue;
}
stack.push(i);//i = 1
dfs(i+1,n,k,result,stack);
stack.pop();//回溯
}
}
public static void main(String[] args) {
List<List<Integer>> lists = combine(4,2);
lists.forEach(System.out::println);
}
}
/**
* 组合总和
*/
public class combinationSum {
public static List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
dfs(0,candidates,target,result,new LinkedList<Integer>());
return result;
}
private static void dfs(int start,int[] candidates, int target, List<List<Integer>> result, LinkedList<Integer> stack) {
if (target == 0){
result.add(new ArrayList<>(stack));
}
/* if (target<0){
return;
}*/
for (int i = start; i < candidates.length; i++) {
if (target < candidates[i]){
continue;//剪枝后去掉小于0
}
stack.push(candidates[i]);
dfs(i,candidates,target-candidates[i],result,stack);
stack.pop();
}
}
public static void main(String[] args) {
List<List<Integer>> lists = combinationSum(new int[]{2,3,6,7},7);
lists.forEach(System.out::println);
}
}
import java.util.*;
/**
* 组数总和2
*/
public class CombinationSumTwo {
public static List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
boolean[] nums = new boolean[candidates.length];
List<List<Integer>> result = new ArrayList<>();
dfs(nums,0,candidates,target,result,new LinkedList<Integer>());
return result;
}
private static void dfs(boolean[] isVisited, int start, int[] candidates, int target, List<List<Integer>> result, LinkedList<Integer> stack) {
if (target == 0){
result.add(new ArrayList<>(stack));
}
/* if (target<0){
return;
}*/
for (int i = start; i < candidates.length; i++) {
if (target < candidates[i]){
continue;//剪枝后去掉小于0
}
if (i >0&&candidates[i] == candidates[i-1] &&!isVisited[i-1]){//找出重复数字
continue;
}
stack.push(candidates[i]);
isVisited[i] = true;
dfs(isVisited, i+1,candidates,target-candidates[i],result,stack);
stack.pop();
isVisited[i] = false;
}
}
public static void main(String[] args) {
List<List<Integer>> lists = combinationSum(new int[]{10,1,2,7,6,1,5},8);
lists.forEach(System.out::println);
}
}
import java.util.*;
/**
* 组数总和2
*/
public class CombinationSumTwo {
public static List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
boolean[] nums = new boolean[candidates.length];
List<List<Integer>> result = new ArrayList<>();
dfs(nums,0,candidates,target,result,new LinkedList<Integer>());
return result;
}
private static void dfs(boolean[] isVisited, int start, int[] candidates, int target, List<List<Integer>> result, LinkedList<Integer> stack) {
if (target == 0){
result.add(new ArrayList<>(stack));
}
/* if (target<0){
return;
}*/
for (int i = start; i < candidates.length; i++) {
if (target < candidates[i]){
continue;//剪枝后去掉小于0
}
if (i >0&&candidates[i] == candidates[i-1] &&!isVisited[i-1]){//找出重复数字
continue;
}
stack.push(candidates[i]);
isVisited[i] = true;
dfs(isVisited, i+1,candidates,target-candidates[i],result,stack);
stack.pop();
isVisited[i] = false;
}
}
public static void main(String[] args) {
List<List<Integer>> lists = combinationSum(new int[]{10,1,2,7,6,1,5},8);
lists.forEach(System.out::println);
}
}
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/**
* N皇后问题
*/
public class NQueen51 {
public static List<List<String>> solveNQueens(int n) {
List<List<String>> map = new ArrayList<>();
boolean[] ca = new boolean[n];//记录列冲突
//i+j 行加列就是列数
boolean[] cb = new boolean[2*n-1];//左斜线冲突
//n-1-(i-j)
boolean[] cc = new boolean[2*n-1];//右斜线冲突
char[][] table = new char[n][n];//'.' 'Q'
for (char[] chars : table) {
Arrays.fill(chars,'.');
}
/*for (char[] chars : table) {
System.out.println(Arrays.toString(chars));
}*/
dfs(map,0,n,table,ca,cb,cc);
return map;
}
private static void dfs(List<List<String>> map, int i, int n, char[][] table, boolean[] ca, boolean[] cb, boolean[] cc) {
if (i == n){//找到解
List<String> kk = new ArrayList<>();
for (char[] chars : table) {
StringBuilder str = new StringBuilder();
for (char aChar : chars) {
str.append(aChar);
}
//System.out.println(str);
kk.add(String.valueOf(str));
}
map.add(kk);
return;
}
for (int j = 0; j <n; j++) {
if (ca[j] || cb[i+j] || cc[n-1-(i-j)]){
continue;
}
table[i][j] = 'Q';
ca[j] = cb[i+j] = cc[n-1-(i-j)] = true;
dfs(map, i+1,n,table,ca,cb,cc);
ca[j] = cb[i+j] = cc[n-1-(i-j)] = false;
table[i][j] = '.';
}
}
public static void main(String[] args) {
System.out.println(solveNQueens(4));
}
}
/**
* 解数独
*/
public class SudokuLeetcode37 {
/**
* 1. 不断遍历每个未填入的空格
* 逐一尝试1~9 若行。列,九宫格内有冲突,则填入
* 2.一旦1~9都尝试失败,回溯上一次状态,换数字重试
* 3.关键还是要记录冲突状态
*/
public static void solveSudoku(char[][] table) {
//用二维数组记录行冲突状态 ca[i]表示某一行 【】表示这一行的数字冲突状态
boolean[][] ca = new boolean[9][9];
//列冲突
boolean[][] cb = new boolean[9][9];
//九宫格冲突状态 单元格和九宫格对应关系 i/3*3+j/3
boolean[][] cc = new boolean[9][9];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
char c = table[i][j];
if (c != '.') {//初始冲突状态
ca[i][c - '1'] = true; //字符5 - 字符4 差值就是4
ca[j][c - '1'] = true;
cc[i / 3 * 3 + j / 3][c - '1'] = true;
}
}
}
dfs(0, 0, table, ca, cb, cc);
}
//针对某一个方法进行填空
static boolean dfs(int i, int j, char[][] table, boolean[][] ca, boolean[][] cb, boolean[][] cc) {
while (table[i][j] != '.') {//查找下一个空格
if (++j >= 9) {
j = 0;
i++;
}
if (i >= 9) {
return true;//找到解了
}
}
//填空
for (int x = 1; x <= 9; x++) {
//检查冲突
if (ca[i][x-1] || ca[i / 3 * 3 + j / 3][x - 1]){
continue;
}
table[i][j] = (char) (x + '0'); // 1 + '0' =>'1'
/**
* ca[0][0] = true 更新冲突 第0行不能存储1了
* cb[0][0] = true 更新冲突 第0行不能存储1了
* cc[0][0] = true 更新冲突 第0行不能存储1了
*/
ca[i][x - 1] = cb[j][x - 1] = ca[i / 3 * 3 + j / 3][x - 1] = true;
if (dfs(i,j,table,ca,cb,cc)){
return true;
};
table[i][j] = '.';
ca[i][x - 1] = cb[j][x - 1] = ca[i / 3 * 3 + j / 3][x - 1] = false;
}
return false;
}
}
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/**
* 四数之和
*/
public class FourSum {
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
List<List<Integer>> result = new LinkedList<>();
dfs(4,0,nums.length-1,target,nums,new LinkedList<>(),result);
return result;
}
static void dfs(int n, int i, int j, int target, int[] nums, LinkedList<Integer> stack, List<List<Integer>> result) {
if (n == 2) {
//用两数之和求解
twoSum(i,j,nums,target,stack,result);
return;
}
for (int k = i; k < j-(n-2); k++) {//四数之和 i
//检查重复
if (k > i && nums[k] == nums[k - 1]) {
continue;
}
//固定一个数字,再尝试 n-1个数字之和
stack.push(nums[k]);
dfs(n - 1, k + 1, j, target - nums[k], nums, stack, result);
stack.pop();
}
}
static public void twoSum(int i, int j, int[] nums, int target
, LinkedList<Integer> stack, List<List<Integer>> result) {
while (i < j) {
int sum = nums[i] + nums[j];
if (sum < target) {
i++;
} else if (sum > target) {
j--;
} else {
List<Integer> list = new ArrayList<>(stack);
list.add(nums[i]);
list.add(nums[j]);
result.add(list);
//继续查找其他的解
i++;
j--;
while(i<j&&nums[i] == nums[i-1]){
i++;
}
while(i<j&&nums[j] == nums[j+1]){
j--;
}
}
}
}
}
package Algorithm.advance;
import java.util.Random;
/**
* 设计跳表
*
*/
class Skiplist {
private static final int Max = 10;// redis 32 java 62
private final Node head = new Node(-1);
static class Node {
int val;//值
Node[] next = new Node[Max];//next数组
public Node(int val) {
this.val = val;
}
}
public Skiplist() {
}
//下楼梯寻找
public Node[] find(int val) {
Node[] path = new Node[Max];//记录经过路径
Node curr = head;
for (int level = Max - 1; level >= 0; level--) {//从上向下找
while (curr.next[level] != null && curr.next[level].val < val) {//向右
curr = curr.next[level];
}
path[level] = curr;
}
System.out.println(curr);
return path;
}
public boolean search(int target) {
Node[] path = find(target);
Node node = path[0].next[0];//路径最底层节点
return node != null && node.val == target;
}
public void add(int val) {
//1.确定添加位置 (把 val 当作目标查询,经历的路径就可以确定添加位置)
Node[] path = find(val);
//2.创建新节点随机高度
Node node = new Node(val);
int level = randomLevel(Max);
//3.修改路径节点 next 指针,以及新节点next 指针
for (int i = 0; i < level; i++) {
node.next[i] = path[i].next[i];//当前节点指向原来路径的下一节点
path[i].next[i] = node;
}
}
public boolean erase(int val) {
Node[] path = find(val);
Node node = path[0].next[0];
if (node == null || node.val != val){
return false;
}
for (int i = 0; i < Max; i++) {
if (path[i].next[i] != node){
break;
}
path[i].next[i] = node.next[i];
}
return true;
}
/*
设计一个方法,方法会随机返回1~max 的数字
从 1 开始,数字的几率逐级减半,例如max = 4 让打概
-50% 的几率返回 1
-25% 的几率返回 2
-12.5% 的几率返回 3
-12.5% 的几率返回 4
*/
static Random random = new Random();
static int randomLevel(int max) {
/*return random.nextBoolean() ? 1 : 50%
random.nextBoolean() ? 2 : 25%
random.nextBoolean() ? 3 : 4; 12.5% */
int x = 1;
while (x < max) {
if (random.nextBoolean()) {
return x;
}
x++;
}
return x;
}
}
package Algorithm.advance;
import java.util.HashMap;
/**
* LFU缓存
* 最不常使用缓存
*/
class LFUCache {
static class Node {
Node pre;
Node next;
int key;
int value;
int freq = 1;//频度
public Node() {
}
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
static class DoublyLinkedList {
Node head;
Node tail;
int size = 0;
public DoublyLinkedList() {
this.head = tail = new Node();//哨兵节点
head.next = tail;
tail.pre = head;
}
//头部添加
public void addFirst(Node newFirst) {//O(1)
Node oldFirst = head.next;
newFirst.pre = head;
newFirst.next = oldFirst;
head.next = newFirst;
oldFirst.pre = newFirst;
size++;
}
//已知节点删除
public void remove(Node node) {
Node prev = node.pre;
Node next = node.next;
prev.next = next;
next.pre = prev;
size--;
}
//尾部删除
public Node removeLast() {
Node last = tail.pre;
remove(last);
return last;
}
public boolean isEmpty() {
return size == 0;
}
}
private HashMap<Integer, Node> kvMap = new HashMap<>();
private HashMap<Integer, DoublyLinkedList> freqMap = new HashMap<>();
private final int capacity;//容量
private int minFreq = 1;//最小频度
public LFUCache(int capacity) {
this.capacity = capacity;
}
/*
key 不存在
返回-1
key 存在
返回value值
增加节点使用频度,将其转移到频度+1的链表当中
*/
public int get(int key) {
if (!kvMap.containsKey(key)) {
return -1;
}
Node node = kvMap.get(key);
DoublyLinkedList list = freqMap.get(node.freq);//旧链表
list.remove(node);
if (list.isEmpty() && node.freq == minFreq) {
minFreq++;
}
node.freq++;
/*DoublyLinkedList list = freqMap.get(node.freq);
if (list == null) {
list = new DoublyLinkedList();
freqMap.put(node.freq, list);
}
list.addFirst(node);*/
freqMap.computeIfAbsent(node.freq, k -> new DoublyLinkedList())
.addFirst(node);
return node.value;
}
/*
更新
将节点的 value 更新,增加节点的使用频度,将其转移到频度+1的链表中
新增
检查是否超过容量,若超过,淘汰minFreq 链表的最后节点
创建新节点,放入kvMap,并加入频度为1的双向链表
*/
public void put(int key, int value) {
if (kvMap.containsKey(key)) {//更新
Node node = kvMap.get(key);
DoublyLinkedList list = freqMap.get(node.freq);
list.remove(node);
if (list.isEmpty() && node.freq == minFreq) {
minFreq++;
}
node.freq++;
freqMap.computeIfAbsent(node.freq, k -> new DoublyLinkedList())
.addFirst(node);
node.value = value;
} else {//新增
if (kvMap.size() == capacity){
Node node = freqMap.get(minFreq).removeLast();
kvMap.remove(node.key);
}
Node node = new Node(key, value);
kvMap.put(key, node);
freqMap.computeIfAbsent(1,k->new DoublyLinkedList()).addFirst(node);
minFreq = 1;
}
}
}
package Algorithm.advance;
import java.util.HashMap;
/**
* LRU缓存
* (最近最少使用)最近不使用缓存
*/
public class LRUCache {
static class Node {
Node pre;
Node next;
int key;
int value;
public Node() {
}
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
private final HashMap<Integer,Node> map = new HashMap<>();
private final DoublyLinkedList list = new DoublyLinkedList();
private final int capaciity;
public LRUCache(int capacity) {
this.capaciity = capacity;
}
static class DoublyLinkedList {
Node head;
Node tail;
public DoublyLinkedList() {
this.head = tail = new Node();//哨兵节点
head.next = tail;
tail.pre = head;
}
//头部添加
public void addFirst(Node newFirst){//O(1)
Node oldFirst = head.next;
newFirst.pre = head;
newFirst.next = oldFirst;
head.next = newFirst;
oldFirst.pre = newFirst;
}
//已知节点删除
public void remove(Node node){
Node prev = node.pre;
Node next = node.next;
prev.next = next;
next.pre = prev;
}
//尾部删除
public Node removeLast(){
Node last = tail.pre;
remove(last);
return last;
}
}
public int get(int key) {
if (!map.containsKey(key)){
return -1;
}
Node node = map.get(key);
list.remove(node);
list.addFirst(node);
return node.value;
}
public void put(int key, int value) {
if (map.containsKey(key)){//更新
Node node = map.get(key);
node.value = value;
list.remove(node);
list.addFirst(node);
}else{//新增
Node node = new Node(key,value);
map.put(key,node);
list.addFirst(node);
if (map.size() > capaciity){
Node removed = list.removeLast();
map.remove(removed.key);
}
}
}
}
方法1:使用两个栈结构,一个记录元素,一个记录当前最小值
方法2:创建一个结构同时包含节点和当前最小值,入栈
package Algorithm.advance;
import java.util.LinkedList;
/**
* 最小栈
* 最快找到最小值
* 使用两个栈,其中一个栈记录当时元素时的最小值
*/
public class MinStack {
LinkedList<Integer> stack = new LinkedList<>();
LinkedList<Integer> min = new LinkedList<>();
public MinStack() {
min.push(Integer.MAX_VALUE);
}
public void push(int val) {
stack.push(val);
min.push(Math.min(val,min.peek()));
}
public void pop() {
if (stack.isEmpty()){
return;
}
stack.pop();
min.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return min.peek();
}
}
package Algorithm.advance;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
/**
* TinyURL 的加密与解密
* 设计短网址 短网址服务
* 使网址加密变短 请求后解密成为真正的网址
*/
public class TinyURL {
/**
* 要让长网址和短网址一一对应起来
* 1.随机数
* 2.Hash码
* 3.递增数
*/
static class CodeRandom {
private Map<String, String> longToShort = new HashMap<>();
private Map<String, String> shortToLong = new HashMap<>();
private static final String SHORT_PREFIX = "http://tinyurl.com/";
// Encodes a URL to a shortened URL.
public String encode(String longUrl) {
String shortUrl = longToShort.get(longUrl);
if (shortUrl != null) {
return shortUrl;
}
//生成短网址
while (true) {
int id = ThreadLocalRandom.current().nextInt();//int
shortUrl = SHORT_PREFIX + id;
if (!shortToLong.containsKey(shortUrl)) {
longToShort.put(longUrl, shortUrl);
shortToLong.put(shortUrl, longUrl);
break;
}
}
return shortUrl;
}
// Decodes a shortened URL to its original URL.
public String decode(String shortUrl) {
return shortToLong.get(shortUrl);
}
}
static class CodeHashCode {
private Map<String, String> longToShort = new HashMap<>();
private Map<String, String> shortToLong = new HashMap<>();
private static final String SHORT_PREFIX = "http://tinyurl.com/";
// Encodes a URL to a shortened URL.
public String encode(String longUrl) {
String shortUrl = longToShort.get(longUrl);
if (shortUrl != null) {
return shortUrl;
}
//生成短网址
int id = longUrl.hashCode();
while (true) {
;//int
shortUrl = SHORT_PREFIX + id;
if (!shortToLong.containsKey(shortUrl)) {
longToShort.put(longUrl, shortUrl);
shortToLong.put(shortUrl, longUrl);
break;
}
id++;
}
return shortUrl;
}
// Decodes a shortened URL to its original URL.
public String decode(String shortUrl) {
return shortToLong.get(shortUrl);
}
}
/*
不能多线程使用
不能分布式
4e9iAk 怎么生成的
a-z 0-9 A-Z 62进制数字
十进制 ==》 62 进制
31 %16 = 15
31 % 16 = 1
1 % 16 = 1
1 %16 = 0
*/
static class CodeSequence {
private Map<String, String> longToShort = new HashMap<>();
private Map<String, String> shortToLong = new HashMap<>();
private static final String SHORT_PREFIX = "http://tinyurl.com/";
private static final char[] toBase62 = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
};
public static String toBase62(int number) {
if (number == 0){
return String.valueOf(toBase62[0]);
}
StringBuilder sb = new StringBuilder();
while (number > 0) {
int r = number % 62;
sb.append(toBase62[r]);
number = number / 62;
}
return sb.toString();
}
private static int id = 1;
// Encodes a URL to a shortened URL.
public String encode(String longUrl) {
String shortUrl = longToShort.get(longUrl);
if (shortUrl != null) {
return shortUrl;
}
//生成短网址
shortUrl = SHORT_PREFIX + id;
longToShort.put(longUrl, shortUrl);
shortToLong.put(shortUrl, longUrl);
id++;
return shortUrl;
}
// Decodes a shortened URL to its original URL.
public String decode(String shortUrl) {
return shortToLong.get(shortUrl);
}
}
}
package Algorithm.advance;
import java.util.*;
/**
* 设计推特
*/
/*public class Twitter {
public Twitter() {
time =0;
}
static class Tweet{
int id;
int time;
Tweet next;
public Tweet(int id, int time, Tweet next) {
this.id = id;
this.time = time;
this.next = next;
}
public int getId() {
return id;
}
public int getTime() {
return time;
}
}
static class User{
int id;
public User(int id) {
this.id = id;
}
Set followees = new HashSet<>();
Tweet head = new Tweet(-1,-1,null);
}
private Map userMap = new HashMap<>();
private static int time;
//发布文章
public void postTweet(int userId, int tweetId) {
User user = userMap.computeIfAbsent(userId, User::new);
user.head.next = new Tweet(tweetId, time++, user.head.next);
}
//新增关注
public void follow(int userId, int followeeId) {
User user = userMap.computeIfAbsent(userId, User::new);
User followee = userMap.computeIfAbsent(followeeId, User::new);
user.followees.add(followee.id);
}
//取消关注
public void unfollow(int userId, int followeeId) {
User user = userMap.get(userId);
if (user != null){
user.followees.remove(followeeId);
}
}
//获取最新10篇文章 (包括用户自己和关注用户)
public List getNewsFeed(int userId) {
User user = userMap.get(userId);
if (user == null){
return List.of();
}
PriorityQueue queue = new PriorityQueue<>(Comparator.comparingInt(Tweet::getId).reversed());
if (user.head.next != null){
queue.offer(user.head.next);
}
for (Integer id : user.followees) {
User followee = userMap.get(id);
if (followee.head.next != null){
queue.offer(followee.head.next);
}
}
List res = new ArrayList<>();
int count = 0;
while (!queue.isEmpty()&&count<10){
Tweet max = queue.poll();
res.add(max.id);
if (max.next!= null){
queue.add(max.next);
}
count++;
}
return res;
}
}*/
class Twitter {
private class Node {
// 哈希表存储关注人的 Id
Set<Integer> followee;
// 用链表存储 tweetId
LinkedList<Integer> tweet;
Node() {
followee = new HashSet<Integer>();
tweet = new LinkedList<Integer>();
}
}
// getNewsFeed 检索的推文的上限以及 tweetId 的时间戳
private int recentMax, time;
// tweetId 对应发送的时间
private Map<Integer, Integer> tweetTime;
// 每个用户存储的信息
private Map<Integer, Node> user;
public Twitter() {
time = 0;
recentMax = 10;
tweetTime = new HashMap<Integer, Integer>();
user = new HashMap<Integer, Node>();
}
// 初始化
public void init(int userId) {
user.put(userId, new Node());
}
public void postTweet(int userId, int tweetId) {
if (!user.containsKey(userId)) {
init(userId);
}
// 达到限制,剔除链表末尾元素
if (user.get(userId).tweet.size() == recentMax) {
user.get(userId).tweet.remove(recentMax - 1);
}
user.get(userId).tweet.addFirst(tweetId);
tweetTime.put(tweetId, ++time);
}
public List<Integer> getNewsFeed(int userId) {
LinkedList<Integer> ans = new LinkedList<Integer>();
for (int it : user.getOrDefault(userId, new Node()).tweet) {
ans.addLast(it);
}
for (int followeeId : user.getOrDefault(userId, new Node()).followee) {
if (followeeId == userId) { // 可能出现自己关注自己的情况
continue;
}
LinkedList<Integer> res = new LinkedList<Integer>();
int tweetSize = user.get(followeeId).tweet.size();
Iterator<Integer> it = user.get(followeeId).tweet.iterator();
int i = 0;
int j = 0;
int curr = -1;
// 线性归并
if (j < tweetSize) {
curr = it.next();
while (i < ans.size() && j < tweetSize) {
if (tweetTime.get(curr) > tweetTime.get(ans.get(i))) {
res.addLast(curr);
++j;
if (it.hasNext()) {
curr = it.next();
}
} else {
res.addLast(ans.get(i));
++i;
}
// 已经找到这两个链表合起来后最近的 recentMax 条推文
if (res.size() == recentMax) {
break;
}
}
}
for (; i < ans.size() && res.size() < recentMax; ++i) {
res.addLast(ans.get(i));
}
if (j < tweetSize && res.size() < recentMax) {
res.addLast(curr);
for (; it.hasNext() && res.size() < recentMax;) {
res.addLast(it.next());
}
}
ans = new LinkedList<Integer>(res);
}
return ans;
}
public void follow(int followerId, int followeeId) {
if (!user.containsKey(followerId)) {
init(followerId);
}
if (!user.containsKey(followeeId)) {
init(followeeId);
}
user.get(followerId).followee.add(followeeId);
}
public void unfollow(int followerId, int followeeId) {
user.getOrDefault(followerId, new Node()).followee.remove(followeeId);
}
}