LintCode 615:课程表

https://www.lintcode.com/problem/course-schedule

描述

现在你总共有 n 门课需要选,记为 0n - 1.
一些课程在修之前需要先修另外的一些课程,比如要学习课程 0 你需要先学习课程 1 ,表示为[0,1]
给定n门课以及他们的先决条件,判断是否可能完成所有课程?

样例

  1. 输入: n = 2, prerequisites = [[1,0]]
  2. 输出: true
  3. 输入: n = 2, prerequisites = [[1,0],[0,1]]
  4. 输出: false

解题思路

拓扑排序,不能有环,和https://www.lintcode.com/problem/topological-sorting 题一样的解法,最后如果有课程入度不为0则表示不能完成所有课程。

AC代码

  1. public boolean canFinish(int numCourses, int[][] prerequisites) {
  2. // 存储每个课程的入度(直接用数组更好)
  3. Map<Integer, Integer> map = new HashMap<>();
  4. // 存储每个课程的后置课程
  5. Map<Integer, Set<Integer>> dict = new HashMap<>();
  6. // 获取每个课程的入度和其后置课程
  7. for (int[] arr : prerequisites) {
  8. int x = arr[0];
  9. int y = arr[1];
  10. if (!dict.containsKey(y)) {
  11. Set<Integer> set = new HashSet<>();
  12. dict.put(y, set);
  13. }
  14. // 数据中有重复的边。。需要去重
  15. if (dict.get(y).contains(x)) {
  16. continue;
  17. }
  18. if (!map.containsKey(x)) {
  19. map.put(x, 0);
  20. }
  21. map.put(x, map.get(x) + 1);
  22. if (!map.containsKey(y)) { map.put(y, 0); }
  23. dict.get(y).add(x);
  24. }
  25. // 宽搜队列,入度为0的课程首先加入队列
  26. Deque<Integer> deque = new ArrayDeque<>();
  27. for (Integer course : map.keySet()) {
  28. if (map.get(course) == 0) {
  29. deque.add(course);
  30. }
  31. }
  32. // 简单宽搜过程,当一个课程入度为0了加入队列
  33. while (!deque.isEmpty()) {
  34. int course = deque.removeFirst();
  35. // 取出的课程从map中删除,当map中没有课程了时表明所有课程都可以被选上
  36. map.remove(course);
  37. if (!dict.containsKey(course)) {
  38. continue;
  39. }
  40. for (Integer x : dict.get(course)) {
  41. map.put(x, map.get(x) - 1);
  42. if (map.get(x) == 0) {
  43. deque.add(x);
  44. }
  45. }
  46. }
  47. // 返回答案
  48. return map.size() == 0;
  49. }

LintCode 618:搜索图中节点

https://www.lintcode.com/problem/search-graph-nodes

描述

给定一张 无向图,一个 节点 以及一个 目标值,返回距离这个节点最近 且 值为目标值的节点,如果不能找到则返回 NULL
在给出的参数中, 我们用 map 来存节点的值
保证答案唯一

样例

  1. 输入:
  2. {1,2,3,4#2,1,3#3,1,2#4,1,5#5,4}
  3. [3,4,5,50,50]
  4. 1
  5. 50
  6. 输出:
  7. 4
  8. 解释:
  9. 2------3 5
  10. \ | |
  11. \ | |
  12. \ | |
  13. \ | |
  14. 1 --4
  15. Give a node 1, target is 50
  16. there a hash named values which is [3,4,10,50,50], represent:
  17. Value of node 1 is 3
  18. Value of node 2 is 4
  19. Value of node 3 is 10
  20. Value of node 4 is 50
  21. Value of node 5 is 50
  22. Return node 4

解题思路

标准的宽搜题,每次从节点扩展它能到达的节点,为target直接返回节点,不是加入宽搜队列。

AC代码

  1. public UndirectedGraphNode searchNode(ArrayList<UndirectedGraphNode> graph,
  2. Map<UndirectedGraphNode, Integer> values,
  3. UndirectedGraphNode node,
  4. int target) {
  5. // 出发点的value便是target直接返回
  6. if (values.get(node) == target) {
  7. return node;
  8. }
  9. // 宽搜队列
  10. Deque<UndirectedGraphNode> deque = new ArrayDeque<>();
  11. deque.add(node);
  12. // 记录已经走过的节点
  13. Set<Integer> set = new HashSet<>();
  14. while (!deque.isEmpty()) {
  15. // 取出节点
  16. UndirectedGraphNode unode = deque.removeFirst();
  17. set.add(unode.label);
  18. // 拓展
  19. for (UndirectedGraphNode node1 : unode.neighbors) {
  20. if (values.get(node1) == target) {
  21. return node1;
  22. }
  23. if (!set.contains(node1.label)) {
  24. deque.add(node1);
  25. }
  26. }
  27. }
  28. return null;
  29. }

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

样例

  1. 输入:org = [1,2,3], seqs = [[1,2],[1,3]]
  2. 输出: false
  3. 解释:
  4. [1,2,3] 并不是唯一可以被重构出的序列,还可以重构出 [1,3,2]
  5. 输入: org = [1,2,3], seqs = [[1,2],[1,3],[2,3]]
  6. 输出: true
  7. 解释:
  8. 序列 [1,2], [1,3], [2,3] 可以唯一重构出 [1,2,3].
  9. 输入:org = [4,1,5,2,6,3], seqs = [[5,2,6,3],[4,1,5,2]]
  10. 输出:true

解题思路

拓扑排序
观看题目可以想象 seqs 的一个序列可以构成一个顺序,所有序列加起来就能构成一个拓扑排序。
org 要是唯一的序列,那么seqs只能 构成一个拓扑排序,如果有多的拓扑排序那便不能唯一。

AC代码

写的比较垃圾。。。。

  1. public boolean sequenceReconstruction(int[] org, int[][] seqs) {
  2. // 边界条件
  3. if (org.length < 1) { return true;}
  4. // 存储拓扑顺序
  5. Map<Integer, List<Integer>> map = new HashMap<>();
  6. // 存储每个点的入度
  7. int[] in = new int[org.length + 1];
  8. // 预处理,有不在org序列中的数或者seqs长度不够org可以直接返回false。
  9. int length = 0;
  10. for (int[] arr : seqs) {
  11. for (int i = 1; i < arr.length; i++) {
  12. int x = arr[i - 1], y = arr[i];
  13. if (y > org.length) {
  14. return false;
  15. }
  16. if (!map.containsKey(x)) {
  17. map.put(x, new ArrayList<>());
  18. }
  19. // 存储顺序和入度
  20. map.get(x).add(y);
  21. in[y]++;
  22. }
  23. // 记录seqs长度
  24. length += arr.length;
  25. if (arr.length > 0 && arr[0] > org.length) {
  26. return false;
  27. }
  28. }
  29. //seqs长度不够org可以直接返回false
  30. if (length < org.length) {
  31. return false;
  32. }
  33. // 宽搜队列,按找org序列顺序进出队列。
  34. Deque<Integer> deque = new ArrayDeque<>();
  35. int index = 0;
  36. deque.add(org[index]);
  37. while (!deque.isEmpty()) {
  38. int x = deque.removeFirst();
  39. index++;
  40. // index 等于 org.length表明org序列已经走完且唯一返回true
  41. if (index == org.length) {
  42. return true;
  43. }
  44. // x 没有扩展了,而org还没结束,返回false
  45. if (!map.containsKey(x)) {
  46. return false;
  47. }
  48. int next = org[index];
  49. for (Integer y : map.get(x)) {
  50. in[y]--;
  51. // 不是 org序列的下一个元素入度为 0 代表拓扑顺序不唯一,返回false
  52. if (in[y] == 0 && y != next) {
  53. return false;
  54. }
  55. }
  56. // 序列的下一个不为0代表序列不成功,返回false
  57. if (in[next] != 0) {
  58. return false;
  59. }
  60. deque.add(next);
  61. }
  62. return false;
  63. }

LintCode 598:僵尸矩阵

https://www.lintcode.com/problem/zombie-in-matrix

描述

给一个二维网格,每一个格子都有一个值,2 代表墙,1 代表僵尸,0 代表人类(数字 0, 1, 2)。僵尸每天可以将上下左右最接近的人类感染成僵尸,但不能穿墙。将所有人类感染为僵尸需要多久,如果不能感染所有人则返回 -1

样例

  1. 输入:
  2. [[0,1,2,0,0],
  3. [1,0,0,2,1],
  4. [0,1,0,0,0]]
  5. 输出:
  6. 2
  7. 输入:
  8. [[0,0,0],
  9. [0,0,0],
  10. [0,0,1]]
  11. 输出:
  12. 4

解题思路

简单宽搜题,首先将僵尸加入宽搜队列,然后每步将队列的僵尸都去感染附件的人类,已经传播过的僵尸设为2。避免重复感染。
最后还有0表示不能感染所有人,否则返回宽搜了多少次。

AC代码

  1. public class Solution {
  2. public int zombie(int[][] grid) {
  3. // 宽搜队列
  4. Deque<Point> deque = new ArrayDeque<>();
  5. // 预处理,将初始僵尸加入宽搜队列
  6. int n = grid.length, m = grid[0].length;
  7. int count = 0;
  8. for (int i = 0; i < n; i++) {
  9. for (int j = 0; j < m; j++) {
  10. if (grid[i][j] == 0) {
  11. count++;
  12. } else if (grid[i][j] == 1) {
  13. Point point = new Point(i, j);
  14. deque.add(point);
  15. // 已经传播过的僵尸设为2
  16. grid[i][j] = 2;
  17. }
  18. }
  19. }
  20. // 方便计算上下左右
  21. int[] a = {0, 0, 1, -1};
  22. int[] b = {1, -1, 0, 0};
  23. int day = 0;
  24. while (!deque.isEmpty()) {
  25. day++;
  26. int size = deque.size();
  27. for (int i = 0; i < size; i++) {
  28. Point point = deque.removeFirst();
  29. for (int j = 0; j < 4; j++) {
  30. int x = point.x + a[j];
  31. int y = point.y + b[j];
  32. if (x >= n || x < 0 || y >= m || y < 0 || grid[x][y] == 2) {
  33. continue;
  34. }
  35. count--;
  36. grid[x][y] = 2;
  37. deque.add(new Point(x, y));
  38. }
  39. }
  40. if (count == 0) {
  41. return day;
  42. }
  43. }
  44. return count > 0 ? -1 : day;
  45. }
  46. }
  47. class Point {
  48. int x;
  49. int y;
  50. Point(int x, int y) {
  51. this.x = x;
  52. this.y = y;
  53. }
  54. }

LintCode 178:图是否是树

https://www.lintcode.com/problem/graph-valid-tree

描述

给出 n 个节点,标号分别从 0n - 1 并且给出一个 无向 边的列表 (给出每条边的两个顶点), 写一个函数去判断这张`无向`图是否是一棵树
你可以假设我们不会给出重复的边在边的列表当中. 无向边 [0, 1][1, 0] 是同一条边, 因此他们不会同时出现在我们给你的边的列表当中。

样例

  1. 输入: n = 5 edges = [[0, 1], [0, 2], [0, 3], [1, 4]]
  2. 输出: true.
  3. 输入: n = 5 edges = [[0, 1], [1, 2], [2, 3], [1, 3], [1, 4]]
  4. 输出: false.

解题思路

BFS
一棵n个节点的树只有n-1条边,当一个图有n-1条边且没有环时便是一棵树。
对输入预处理存储图的关系后,随便从一个节点宽搜,最后队列能走过n个点便是没有环的图,也就是树。
并查集
用并查集合并相连的点,遍历每条边,合并前判断两个节点是否属于同一个集合,属于同一个集合表明有环,不是树;不是同一个集合则合并。
最后如果所有点都在一个集合中,则说明这个图联通且无环,是一棵树。

AC代码

  1. public boolean validTree(int n, int[][] edges) {
  2. // 不是n-1条边便不是一棵树。
  3. if (n == 0 || edges.length != n - 1) {
  4. return false;
  5. }
  6. Map<Integer, Set<Integer>> map = new HashMap<>();
  7. Set<Integer> set = new HashSet<>();
  8. Deque<Integer> deque = new ArrayDeque<>();
  9. for (int[] arr : edges) {
  10. int x = arr[0], y = arr[1];
  11. if (!map.containsKey(x)) {map.put(x, new HashSet<>());}
  12. if (!map.containsKey(y)) {map.put(y, new HashSet<>());}
  13. map.get(x).add(y);
  14. map.get(y).add(x);
  15. }
  16. // 标准宽搜过程,走过的节点都加入set中
  17. deque.add(0);
  18. while (!deque.isEmpty()) {
  19. int x = deque.removeFirst();
  20. set.add(x);
  21. if (!map.containsKey(x)) {
  22. continue;
  23. }
  24. for (Integer next : map.get(x)) {
  25. if (!set.contains(next)) {
  26. set.add(next);
  27. deque.add(next);
  28. }
  29. }
  30. }
  31. // 宽搜没有走完n个节点表明有环,不是树
  32. return set.size() == n;
  33. }

LintCode 892:外星人字典

https://www.lintcode.com/problem/alien-dictionary

描述

有一种新的使用拉丁字母的外来语言。但是,你不知道字母之间的顺序。你会从词典中收到一个非空的单词列表,其中的单词在这种新语言的规则下按字典顺序排序。请推导出这种语言的字母顺序。

  1. 你可以假设所有的字母都是小写。
  2. 如果a是b的前缀且b出现在a之前,那么这个顺序是无效的。
  3. 如果顺序是无效的,则返回空字符串。
  4. 这里可能有多个有效的字母顺序,返回以正常字典顺序看来最小的。

    样例

    ```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”

  1. <a name="TdKQJ"></a>
  2. #### 解题思路
  3. 拓扑排序。每两个单词之间可以确定一个顺序,这样就可以得到拓扑排序的一个指向。例如 "wrt"和"wrf",就可以得到 t -> f。<br />两个单词中的第一个相同位置不同字母可以确定出一个指向。<br />得到所有指向后便可整理出一个拓扑顺序,判断是否可以排出一个字典顺序(有环便没有)。
  4. <a name="Oe5BG"></a>
  5. #### AC代码
  6. ```java
  7. // 九章
  8. public String alienOrder(String[] words) {
  9. // 先构造拓扑排序的每一条边
  10. Map<Character, Set<Character>> graph = constructGraph(words);
  11. if (graph == null) {
  12. return "";
  13. }
  14. // 宽搜找寻拓扑顺序
  15. return topologicalSorting(graph);
  16. }
  17. // 建图,找出拓扑排序中的每一条边,也就是字母的顺序
  18. private Map<Character, Set<Character>> constructGraph(String[] words) {
  19. Map<Character, Set<Character>> graph = new HashMap<>();
  20. // 先给每个字母初始化
  21. for (int i = 0; i < words.length; i++) {
  22. for (int j = 0; j < words[i].length(); j++) {
  23. char c = words[i].charAt(j);
  24. if (!graph.containsKey(c)) {
  25. graph.put(c, new HashSet<>());
  26. }
  27. }
  28. }
  29. for (int i = 0; i < words.length - 1; i++) {
  30. int index = 0;
  31. while (index < words[i].length() && index < words[i + 1].length()) {
  32. // 找到第一个字母不相同的位置,建立边
  33. if (words[i].charAt(index) != words[i + 1].charAt(index)) {
  34. graph.get(words[i].charAt(index)).add(words[i + 1].charAt(index));
  35. break;
  36. }
  37. index++;
  38. }
  39. // 如果长单词前缀是短单词,并且短单词还排在长单词后面,表明无解。
  40. if (index == Math.min(words[i].length(), words[i + 1].length())) {
  41. if (words[i].length() > words[i + 1].length()) {
  42. return null;
  43. }
  44. }
  45. }
  46. return graph;
  47. }
  48. // 获取每个字母的入度,同之前的拓扑排序一样
  49. private Map<Character, Integer> getIndegree(Map<Character, Set<Character>> graph) {
  50. Map<Character, Integer> indegree = new HashMap<>();
  51. for (Character u : graph.keySet()) {
  52. indegree.put(u, 0);
  53. }
  54. for (Character u : graph.keySet()) {
  55. for (Character v : graph.get(u)) {
  56. indegree.put(v, indegree.get(v) + 1);
  57. }
  58. }
  59. return indegree;
  60. }
  61. private String topologicalSorting(Map<Character, Set<Character>> graph) {
  62. Map<Character, Integer> indgree = getIndegree(graph);
  63. // 优先队列,因为要返回正常字典顺序看起来最小的
  64. Queue<Character> queue = new PriorityQueue<>();
  65. // 将入度为0的单词都加入队列中
  66. for (Character u : indgree.keySet()) {
  67. if (indgree.get(u) == 0) {
  68. queue.offer(u);
  69. }
  70. }
  71. // 标准宽搜过程
  72. StringBuilder sb = new StringBuilder();
  73. while (!queue.isEmpty()) {
  74. Character head = queue.poll();
  75. sb.append(head);
  76. for (Character neighbor : graph.get(head)) {
  77. indgree.put(neighbor, indgree.get(neighbor) - 1);
  78. if (indgree.get(neighbor) == 0) {
  79. queue.offer(neighbor);
  80. }
  81. }
  82. }
  83. // 表明有环
  84. if (sb.length() != indgree.size()) {
  85. return "";
  86. }
  87. return sb.toString();
  88. }