LintCode 615:课程表
https://www.lintcode.com/problem/course-schedule
描述
现在你总共有 n 门课需要选,记为 0 到 n - 1.
一些课程在修之前需要先修另外的一些课程,比如要学习课程 0 你需要先学习课程 1 ,表示为[0,1]
给定n门课以及他们的先决条件,判断是否可能完成所有课程?
样例
输入: n = 2, prerequisites = [[1,0]]输出: true输入: n = 2, prerequisites = [[1,0],[0,1]]输出: false
解题思路
拓扑排序,不能有环,和https://www.lintcode.com/problem/topological-sorting 题一样的解法,最后如果有课程入度不为0则表示不能完成所有课程。
AC代码
public boolean canFinish(int numCourses, int[][] prerequisites) {// 存储每个课程的入度(直接用数组更好)Map<Integer, Integer> map = new HashMap<>();// 存储每个课程的后置课程Map<Integer, Set<Integer>> dict = new HashMap<>();// 获取每个课程的入度和其后置课程for (int[] arr : prerequisites) {int x = arr[0];int y = arr[1];if (!dict.containsKey(y)) {Set<Integer> set = new HashSet<>();dict.put(y, set);}// 数据中有重复的边。。需要去重if (dict.get(y).contains(x)) {continue;}if (!map.containsKey(x)) {map.put(x, 0);}map.put(x, map.get(x) + 1);if (!map.containsKey(y)) { map.put(y, 0); }dict.get(y).add(x);}// 宽搜队列,入度为0的课程首先加入队列Deque<Integer> deque = new ArrayDeque<>();for (Integer course : map.keySet()) {if (map.get(course) == 0) {deque.add(course);}}// 简单宽搜过程,当一个课程入度为0了加入队列while (!deque.isEmpty()) {int course = deque.removeFirst();// 取出的课程从map中删除,当map中没有课程了时表明所有课程都可以被选上map.remove(course);if (!dict.containsKey(course)) {continue;}for (Integer x : dict.get(course)) {map.put(x, map.get(x) - 1);if (map.get(x) == 0) {deque.add(x);}}}// 返回答案return map.size() == 0;}
LintCode 618:搜索图中节点
https://www.lintcode.com/problem/search-graph-nodes
描述
给定一张 无向图,一个 节点 以及一个 目标值,返回距离这个节点最近 且 值为目标值的节点,如果不能找到则返回 NULL。
在给出的参数中, 我们用 map 来存节点的值
保证答案唯一
样例
输入:{1,2,3,4#2,1,3#3,1,2#4,1,5#5,4}[3,4,5,50,50]150输出:4解释:2------3 5\ | |\ | |\ | |\ | |1 --4Give a node 1, target is 50there a hash named values which is [3,4,10,50,50], represent:Value of node 1 is 3Value of node 2 is 4Value of node 3 is 10Value of node 4 is 50Value of node 5 is 50Return node 4
解题思路
标准的宽搜题,每次从节点扩展它能到达的节点,为target直接返回节点,不是加入宽搜队列。
AC代码
public UndirectedGraphNode searchNode(ArrayList<UndirectedGraphNode> graph,Map<UndirectedGraphNode, Integer> values,UndirectedGraphNode node,int target) {// 出发点的value便是target直接返回if (values.get(node) == target) {return node;}// 宽搜队列Deque<UndirectedGraphNode> deque = new ArrayDeque<>();deque.add(node);// 记录已经走过的节点Set<Integer> set = new HashSet<>();while (!deque.isEmpty()) {// 取出节点UndirectedGraphNode unode = deque.removeFirst();set.add(unode.label);// 拓展for (UndirectedGraphNode node1 : unode.neighbors) {if (values.get(node1) == target) {return node1;}if (!set.contains(node1.label)) {deque.add(node1);}}}return null;}
LintCode 605:序列重构
https://www.lintcode.com/problem/sequence-reconstruction
描述
判断是否序列 org 能唯一地由 seqs重构得出. org是一个由从1到n的正整数排列而成的序列,1 \leq n \leq 10^41≤n≤104。 重构表示组合成seqs的一个最短的父序列 (意思是,一个最短的序列使得所有 seqs里的序列都是它的子序列).
判断是否有且仅有一个能从 seqs重构出来的序列,并且这个序列是org。
样例
输入:org = [1,2,3], seqs = [[1,2],[1,3]]输出: false解释:[1,2,3] 并不是唯一可以被重构出的序列,还可以重构出 [1,3,2]输入: org = [1,2,3], seqs = [[1,2],[1,3],[2,3]]输出: true解释:序列 [1,2], [1,3], 和 [2,3] 可以唯一重构出 [1,2,3].输入:org = [4,1,5,2,6,3], seqs = [[5,2,6,3],[4,1,5,2]]输出:true
解题思路
拓扑排序
观看题目可以想象 seqs 的一个序列可以构成一个顺序,所有序列加起来就能构成一个拓扑排序。
而 org 要是唯一的序列,那么seqs只能 构成一个拓扑排序,如果有多的拓扑排序那便不能唯一。
AC代码
写的比较垃圾。。。。
public boolean sequenceReconstruction(int[] org, int[][] seqs) {// 边界条件if (org.length < 1) { return true;}// 存储拓扑顺序Map<Integer, List<Integer>> map = new HashMap<>();// 存储每个点的入度int[] in = new int[org.length + 1];// 预处理,有不在org序列中的数或者seqs长度不够org可以直接返回false。int length = 0;for (int[] arr : seqs) {for (int i = 1; i < arr.length; i++) {int x = arr[i - 1], y = arr[i];if (y > org.length) {return false;}if (!map.containsKey(x)) {map.put(x, new ArrayList<>());}// 存储顺序和入度map.get(x).add(y);in[y]++;}// 记录seqs长度length += arr.length;if (arr.length > 0 && arr[0] > org.length) {return false;}}//seqs长度不够org可以直接返回falseif (length < org.length) {return false;}// 宽搜队列,按找org序列顺序进出队列。Deque<Integer> deque = new ArrayDeque<>();int index = 0;deque.add(org[index]);while (!deque.isEmpty()) {int x = deque.removeFirst();index++;// index 等于 org.length表明org序列已经走完且唯一返回trueif (index == org.length) {return true;}// x 没有扩展了,而org还没结束,返回falseif (!map.containsKey(x)) {return false;}int next = org[index];for (Integer y : map.get(x)) {in[y]--;// 不是 org序列的下一个元素入度为 0 代表拓扑顺序不唯一,返回falseif (in[y] == 0 && y != next) {return false;}}// 序列的下一个不为0代表序列不成功,返回falseif (in[next] != 0) {return false;}deque.add(next);}return false;}
LintCode 598:僵尸矩阵
https://www.lintcode.com/problem/zombie-in-matrix
描述
给一个二维网格,每一个格子都有一个值,2 代表墙,1 代表僵尸,0 代表人类(数字 0, 1, 2)。僵尸每天可以将上下左右最接近的人类感染成僵尸,但不能穿墙。将所有人类感染为僵尸需要多久,如果不能感染所有人则返回 -1。
样例
输入:[[0,1,2,0,0],[1,0,0,2,1],[0,1,0,0,0]]输出:2输入:[[0,0,0],[0,0,0],[0,0,1]]输出:4
解题思路
简单宽搜题,首先将僵尸加入宽搜队列,然后每步将队列的僵尸都去感染附件的人类,已经传播过的僵尸设为2。避免重复感染。
最后还有0表示不能感染所有人,否则返回宽搜了多少次。
AC代码
public class Solution {public int zombie(int[][] grid) {// 宽搜队列Deque<Point> deque = new ArrayDeque<>();// 预处理,将初始僵尸加入宽搜队列int n = grid.length, m = grid[0].length;int count = 0;for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {if (grid[i][j] == 0) {count++;} else if (grid[i][j] == 1) {Point point = new Point(i, j);deque.add(point);// 已经传播过的僵尸设为2grid[i][j] = 2;}}}// 方便计算上下左右int[] a = {0, 0, 1, -1};int[] b = {1, -1, 0, 0};int day = 0;while (!deque.isEmpty()) {day++;int size = deque.size();for (int i = 0; i < size; i++) {Point point = deque.removeFirst();for (int j = 0; j < 4; j++) {int x = point.x + a[j];int y = point.y + b[j];if (x >= n || x < 0 || y >= m || y < 0 || grid[x][y] == 2) {continue;}count--;grid[x][y] = 2;deque.add(new Point(x, y));}}if (count == 0) {return day;}}return count > 0 ? -1 : day;}}class Point {int x;int y;Point(int x, int y) {this.x = x;this.y = y;}}
LintCode 178:图是否是树
https://www.lintcode.com/problem/graph-valid-tree
描述
给出 n 个节点,标号分别从 0 到 n - 1 并且给出一个 无向 边的列表 (给出每条边的两个顶点), 写一个函数去判断这张`无向`图是否是一棵树
你可以假设我们不会给出重复的边在边的列表当中. 无向边 [0, 1] 和 [1, 0] 是同一条边, 因此他们不会同时出现在我们给你的边的列表当中。
样例
输入: n = 5 edges = [[0, 1], [0, 2], [0, 3], [1, 4]]输出: true.输入: n = 5 edges = [[0, 1], [1, 2], [2, 3], [1, 3], [1, 4]]输出: false.
解题思路
BFS
一棵n个节点的树只有n-1条边,当一个图有n-1条边且没有环时便是一棵树。
对输入预处理存储图的关系后,随便从一个节点宽搜,最后队列能走过n个点便是没有环的图,也就是树。
并查集
用并查集合并相连的点,遍历每条边,合并前判断两个节点是否属于同一个集合,属于同一个集合表明有环,不是树;不是同一个集合则合并。
最后如果所有点都在一个集合中,则说明这个图联通且无环,是一棵树。
AC代码
public boolean validTree(int n, int[][] edges) {// 不是n-1条边便不是一棵树。if (n == 0 || edges.length != n - 1) {return false;}Map<Integer, Set<Integer>> map = new HashMap<>();Set<Integer> set = new HashSet<>();Deque<Integer> deque = new ArrayDeque<>();for (int[] arr : edges) {int x = arr[0], y = arr[1];if (!map.containsKey(x)) {map.put(x, new HashSet<>());}if (!map.containsKey(y)) {map.put(y, new HashSet<>());}map.get(x).add(y);map.get(y).add(x);}// 标准宽搜过程,走过的节点都加入set中deque.add(0);while (!deque.isEmpty()) {int x = deque.removeFirst();set.add(x);if (!map.containsKey(x)) {continue;}for (Integer next : map.get(x)) {if (!set.contains(next)) {set.add(next);deque.add(next);}}}// 宽搜没有走完n个节点表明有环,不是树return set.size() == n;}
LintCode 892:外星人字典
https://www.lintcode.com/problem/alien-dictionary
描述
有一种新的使用拉丁字母的外来语言。但是,你不知道字母之间的顺序。你会从词典中收到一个非空的单词列表,其中的单词在这种新语言的规则下按字典顺序排序。请推导出这种语言的字母顺序。
- 你可以假设所有的字母都是小写。
- 如果a是b的前缀且b出现在a之前,那么这个顺序是无效的。
- 如果顺序是无效的,则返回空字符串。
- 这里可能有多个有效的字母顺序,返回以正常字典顺序看来最小的。
样例
```java 输入:[“wrt”,”wrf”,”er”,”ett”,”rftt”] 输出:”wertf” 解释: 从 “wrt”和”wrf” ,我们可以得到 ‘t’<’f’ 从 “wrt”和”er” ,我们可以得到’w’<’e’ 从 “er”和”ett” ,我们可以得到 get ‘r’<’t’ 从 “ett”和”rftt” ,我们可以得到 ‘e’<’r’ 所以返回 “wertf”
输入:[“z”,”x”] 输出:”zx” 解释: 从 “z” 和 “x”,我们可以得到 ‘z’ < ‘x’ 所以返回”zx”
<a name="TdKQJ"></a>#### 解题思路拓扑排序。每两个单词之间可以确定一个顺序,这样就可以得到拓扑排序的一个指向。例如 "wrt"和"wrf",就可以得到 t -> f。<br />两个单词中的第一个相同位置不同字母可以确定出一个指向。<br />得到所有指向后便可整理出一个拓扑顺序,判断是否可以排出一个字典顺序(有环便没有)。<a name="Oe5BG"></a>#### AC代码```java// 九章public String alienOrder(String[] words) {// 先构造拓扑排序的每一条边Map<Character, Set<Character>> graph = constructGraph(words);if (graph == null) {return "";}// 宽搜找寻拓扑顺序return topologicalSorting(graph);}// 建图,找出拓扑排序中的每一条边,也就是字母的顺序private Map<Character, Set<Character>> constructGraph(String[] words) {Map<Character, Set<Character>> graph = new HashMap<>();// 先给每个字母初始化for (int i = 0; i < words.length; i++) {for (int j = 0; j < words[i].length(); j++) {char c = words[i].charAt(j);if (!graph.containsKey(c)) {graph.put(c, new HashSet<>());}}}for (int i = 0; i < words.length - 1; i++) {int index = 0;while (index < words[i].length() && index < words[i + 1].length()) {// 找到第一个字母不相同的位置,建立边if (words[i].charAt(index) != words[i + 1].charAt(index)) {graph.get(words[i].charAt(index)).add(words[i + 1].charAt(index));break;}index++;}// 如果长单词前缀是短单词,并且短单词还排在长单词后面,表明无解。if (index == Math.min(words[i].length(), words[i + 1].length())) {if (words[i].length() > words[i + 1].length()) {return null;}}}return graph;}// 获取每个字母的入度,同之前的拓扑排序一样private Map<Character, Integer> getIndegree(Map<Character, Set<Character>> graph) {Map<Character, Integer> indegree = new HashMap<>();for (Character u : graph.keySet()) {indegree.put(u, 0);}for (Character u : graph.keySet()) {for (Character v : graph.get(u)) {indegree.put(v, indegree.get(v) + 1);}}return indegree;}private String topologicalSorting(Map<Character, Set<Character>> graph) {Map<Character, Integer> indgree = getIndegree(graph);// 优先队列,因为要返回正常字典顺序看起来最小的Queue<Character> queue = new PriorityQueue<>();// 将入度为0的单词都加入队列中for (Character u : indgree.keySet()) {if (indgree.get(u) == 0) {queue.offer(u);}}// 标准宽搜过程StringBuilder sb = new StringBuilder();while (!queue.isEmpty()) {Character head = queue.poll();sb.append(head);for (Character neighbor : graph.get(head)) {indgree.put(neighbor, indgree.get(neighbor) - 1);if (indgree.get(neighbor) == 0) {queue.offer(neighbor);}}}// 表明有环if (sb.length() != indgree.size()) {return "";}return sb.toString();}
