BFS最主要的数据结构是Queue,由LinkedList实现。
二叉树上的BFS:
1.binary-tree-level-order-traversal(二叉树的层次遍历)
给出一棵二叉树,返回其节点值的层次遍历(逐层从左往右访问)
BFS解法一【基本模板】:
public class Solution { /** * @param root: The root of binary tree. * @return: Level order a list of lists of integer */ public ArrayList<ArrayList<Integer>> levelOrder(TreeNode root) { // write your code here ArrayList<ArrayList<Integer>> results = new ArrayList<ArrayList<Integer>>(); if (root == null) { return results; } Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.offer(root); while (!queue.isEmpty()) { ArrayList<Integer> level = new ArrayList<Integer>(); int size = queue.size(); for (int i = 0; i < size; i++) { TreeNode node = queue.poll(); level.add(node.val); if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } } results.add(level); } return results; } }
BFS解法二(使用Q1和Q2两个ArrayList):
/** * Definition of TreeNode: * public class TreeNode { * public int val; * public TreeNode left, right; * public TreeNode(int val) { * this.val = val; * this.left = this.right = null; * } * } */ public class Solution { /** * @param root: The root of binary tree. * @return: Level order a list of lists of integer */ public ArrayList<ArrayList<Integer>> levelOrder(TreeNode root) { // write your code here ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>(); if (root == null) { return result; } ArrayList<TreeNode> Q1 = new ArrayList<TreeNode>(); ArrayList<TreeNode> Q2 = new ArrayList<TreeNode>(); Q1.add(root); while (Q1.size() != 0) { ArrayList<Integer> level = new ArrayList<Integer>(); Q2.clear(); for (int i = 0; i < Q1.size(); i++) { TreeNode node = Q1.get(i); level.add(node.val); if (node.left != null) { Q2.add(node.left); } if (node.right != null) { Q2.add(node.right); } } // swap q1 and q2 ArrayList<TreeNode> temp = Q1; Q1 = Q2; Q2 = temp; // add to result result.add(level); } return result; } }
2.binary-tree-level-order-traversal-ii(二叉树的层次遍历II)
给出一棵二叉树,返回其节点值从底向上的层次遍历(按从叶节点所在层到根节点所在的层遍历,然后逐层从左往右遍历)
public class Solution { /** * @param root: The root of binary tree. * @return: buttom-up level order a list of lists of integer */ public ArrayList<ArrayList<Integer>> levelOrderBottom(TreeNode root) { // write your code here ArrayList<ArrayList<Integer>> results = new ArrayList<ArrayList<Integer>>(); if (root == null) { return results; } Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.offer(root); while (!queue.isEmpty()) { ArrayList<Integer> level = new ArrayList<Integer>(); int size = queue.size(); for (int i = 0; i < size; i++) { TreeNode node = queue.poll(); level.add(node.val); if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } } results.add(level); } Collections.reverse(results); return results; } }
注意:在第1题的基础上在返回results之前利用Colllections.reverse(results)翻转一下即可。
3.binary-tree-zigzag-level-order-traversal(二叉树的锯齿形层次遍历)
给出一棵二叉树,返回其节点值的锯齿形层次遍历(先从左往右,下一层再从右往左,层与层之间交替进行)
public class Solution { /** * @param root: The root of binary tree. * @return: A list of lists of integer include * the zigzag level order traversal of its nodes' values */ public ArrayList<ArrayList<Integer>> zigzagLevelOrder(TreeNode root) { // write your code here ArrayList<ArrayList<Integer>> results = new ArrayList<ArrayList<Integer>>(); if (root == null) { return results; } Stack<TreeNode> curtLevel = new Stack<TreeNode>(); Stack<TreeNode> nextLevel = new Stack<TreeNode>(); Stack<TreeNode> temp; curtLevel.push(root); boolean normalOrder = true; while (!curtLevel.empty()) { ArrayList<Integer> curtLevelResult = new ArrayList<Integer>(); while (!curtLevel.empty()) { TreeNode node = curtLevel.pop(); curtLevelResult.add(node.val); if (normalOrder) { if (node.left != null) { nextLevel.push(node.left); } if (node.right != null) { nextLevel.push(node.right); } } else { if (node.right != null) { nextLevel.push(node.right); } if (node.left != null) { nextLevel.push(node.left); } } } results.add(curtLevelResult); temp = curtLevel; curtLevel = nextLevel; nextLevel = temp; normalOrder = !normalOrder; } return results; } }
注意:使用Stack(栈)先进后出的特征,curtLevel和nextLevel交替使用,normalOrder变量用于标记是否是正常顺序(从左到右)。初始时为从左至右的顺序,下一层就应该先放入左儿子,再放入右儿子。由于栈的先进后出特性,右儿子先出来。
4.convert-binary-tree-to-linked-lists-by-depth(将二叉树按照层级转化为链表)
给一棵二叉树,设计一个算法为每一层的节点建立一个链表。也就是说,如果一棵二叉树有D
层,那么你需要创建D条链表。
DFS解法:
/** * Definition of TreeNode: * public class TreeNode { * public int val; * public TreeNode left, right; * public TreeNode(int val) { * this.val = val; * this.left = this.right = null; * } * } * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ public class Solution { /** * @param root the root of binary tree * @return a lists of linked list */ public List<ListNode> binaryTreeToLists(TreeNode root) { // Write your code here List<ListNode> result = new ArrayList<ListNode>(); if (root == null) { return result; } dfs(root, 1, result); return result; } private void dfs(TreeNode root, int depth, List<ListNode> result) { if (root == null) { return; } ListNode node = new ListNode(root.val); if (result.size() < depth) { result.add(node); } else { node.next = result.get(depth - 1); result.set(depth - 1, node); } dfs(root.right, depth + 1, result); dfs(root.left, depth + 1, result); } }
注意:例如下图的二叉树,运行步骤为:执行dfs(1,1,result),结果{1};执行dfs(3,2,{1}),结果{1,3};执行(2,2,{1,3}),结果{1,2->3};执行(4,3,{1,2->3})结果{1,2->3, 4}。注意到要先dfs根节点的右儿子,再dfs左儿子,这样才能实现左儿子->右儿子。
5.binary-tree-serialization(二叉树的序列化和反序列化)【理解有难度】
设计一个算法,并编写代码来序列化和反序列化二叉树。将树写入一个文件被称为“序列化”,读取文件后重建同样的二叉树被称为“反序列化”。如何反序列化或序列化二叉树是没有限制的,你只需要确保可以将二叉树序列化为一个字符串,并且可以将字符串反序列化为原来的树结构。
/** * Definition of TreeNode: * public class TreeNode { * public int val; * public TreeNode left, right; * public TreeNode(int val) { * this.val = val; * this.left = this.right = null; * } * } */ class Solution { /** * This method will be invoked first, you should design your own algorithm * to serialize a binary tree which denote by a root node to a string which * can be easily deserialized by your own "deserialize" method later. */ public String serialize(TreeNode root) { // write your code here if (root == null) { return "{}"; } ArrayList<TreeNode> list = new ArrayList<TreeNode>(); list.add(root); //将每个节点及其左右儿子放入list中 for (int i = 0; i < list.size(); i++) { TreeNode node = list.get(i); if (node == null) { continue; } list.add(node.left); list.add(node.right); } //删除list最后的空节点 while (list.get(list.size() - 1) == null) { list.remove(list.size() - 1); } //将list转换为string字符串 StringBuilder sb = new StringBuilder(); sb.append("{"); sb.append(list.get(0).val); for (int i = 1; i < list.size(); i++) { if (list.get(i) == null) { sb.append(",#"); } else { sb.append(","); sb.append(list.get(i).val); } } sb.append("}"); return sb.toString(); } /** * This method will be invoked second, the argument data is what exactly * you serialized at method "serialize", that means the data is not given by * system, it's given by your own serialize method. So the format of data is * designed by yourself, and deserialize it here as you serialize it in * "serialize" method. */ public TreeNode deserialize(String data) { // write your code here if (data.equals("{}")) { return null; } //将字符串进行分割 String[] vals = data.substring(1, data.length() - 1).split(","); ArrayList<TreeNode> list = new ArrayList<TreeNode>(); TreeNode root = new TreeNode(Integer.parseInt(vals[0])); list.add(root); int index = 0; boolean isLeftChild = true;//标记添加到左儿子还是右儿子 for (int i = 1; i < vals.length; i++) { if (!vals[i].equals("#")) { TreeNode node = new TreeNode(Integer.parseInt(vals[i])); if (isLeftChild) { list.get(index).left = node; } else { list.get(index).right = node; } list.add(node); } //index标记当前根节点,当右儿子已添加时,就换下一个根节点。 if (!isLeftChild) { index++; } isLeftChild = !isLeftChild; } return root; } }
图上的BFS:(与树上的区别在于:图中有环,意味着同一个节点可能重复进入队列)
6.graph-valid-tree(图是否是树)
给出 n
个节点,标号分别从 0
到 n - 1
并且给出一个 无向
边的列表 (给出每条边的两个顶点), 写一个函数去判断这张'无向'图是否是一棵树。你可以假设我们不会给出重复的边在边的列表当中。 无向
边 [0, 1]
和 [1, 0]
是同一条边, 因此他们不会同时出现在我们给你的边的列表当中。
图是树的标准:1.图有n-1条边(n个点);2.n-1条边把图连通。
public class Solution { /** * @param n an integer * @param edges a list of undirected edges * @return true if it's a valid tree, or false */ public boolean validTree(int n, int[][] edges) { // Write your code here if (n == 0) { return false; } if (edges.length() != n - 1) { return false; } Map<Integer, Set<Integer>> graph = initializeGraph(n, edges); //bfs Queue<Integer> queue = new LinkedeList<Integer>(); Set<Integer> hash = new HashSet<Integer>(); queue.offer(0); hash.add(0); while (!queue.isEmpty()) { int node = queue.poll(); for (Integer neighbor : graph.get(node)) { if (hash.contains(neighbor)) { continue; } queue.offer(neighbor); hash.add(neighbor); } } return (hash.size() == n); } private Map<Integer, Set<Integer>> initializeGraph(int n, int[][] edges) { Map<Integer, Set<Integer>> graph = new HashMap<>(); for (int i = 0; i < n; i++) { } for (int i = 0; i < edges.length; i++) { int u = edges[i][0]; int v = edges[i][1]; graph.get(u).add(v); graph.get(v).add(u); } return graph; } }
注意:使用Map<Integer, Set<Integer>>来将图进行初始化,Integer存储节点,Set<Integer>存储节点所连接的边。hash表用来判断一个点是否可以进入队列,不要进入两次。最后判断hash表中的节点数等于n就返回true,否则返回false。hash跟queue同时出现。
7.clone-graph(克隆图)
克隆一张无向图,图中的每个节点包含一个 label
和一个列表 neighbors
。你的程序需要返回一个经过深度拷贝的新图。这个新图和原图具有同样的结构,并且对新图的任何改动不会对原图造成任何影响。数据中如何表示一个无向图?比如,序列化图 {0,1,2#1,2#2,2}
共有三个节点, 因此包含两个个分隔符#。1.第一个节点label为0,存在边从节点0链接到节点1和节点2。2.第二个节点label为1,存在边从节点1连接到节点2。3.第三个节点label为2,存在边从节点2连接到节点2(本身),从而形成自环。
三步解法:
/** * Definition for undirected graph. * class UndirectedGraphNode { * int label; * ArrayList<UndirectedGraphNode> neighbors; * UndirectedGraphNode(int x) { label = x; neighbors = new ArrayList<UndirectedGraphNode>(); } * }; */ public class Solution { /** * @param node: A undirected graph node * @return: A undirected graph node */ public UndirectedGraphNode cloneGraph(UndirectedGraphNode node) { // write your code here if (node == null) { return node; } //step one:use bfs algorithm to traverse the graph and get all nodes. ArrayList<UndirectedGraphNode> nodes = getNodes(node); //step two:copy nodes, store the old->new mapping information in a hash map HashMap<UndirectedGraphNode, UndirectedGraphNode> mapping = new HashMap<>(); for (UndirectedGraphNode n : nodes) { mapping.put(n, new UndirectedGraphNode(n.label)); } //step three:copy neighbors(edges) for (UndirectedGraphNode n : nodes) { UndirectedGraphNode newNode = mapping.get(n); for (UndirectedGraphNode neighbor : n.neighbors) { UndirectedGraphNode newNeighbor = mapping.get(neighbor); newNode.neighbors.add(newNeighbor); } } return mapping.get(node); } private ArrayList<UndirectedGraphNode> getNodes(UndirectedGraphNode node) { Queue<UndirectedGraphNode> queue = new LinkedList<UndirectedGraphNode>(); HashSet<UndirectedGraphNode> hash = new HashSet<>(); queue.offer(node); hash.add(node); while (!queue.isEmpty()) { UndirectedGraphNode head = queue.poll(); for (UndirectedGraphNode neighbor : head.neighbors) { if (!hash.contains(neighbor)){ hash.add(neighbor); queue.offer(neighbor); } } } return new ArrayList<UndirectedGraphNode>(hash); } }
注意:1.使用bfs算法遍历图并获取所有节点;2.复制节点,将old-new映射信息存储在hash映射中;3.复制邻居(边)。
8.search-graph-nodes(搜索图中的节点)
给定一个无向图,一个节点和一个目标值,返回距离给定的节点最近值是target的节点,如果找不到,返回NULL。有一个mapping存储给定参数中的节点值。
例如:{1,2,3,4#2,1,3#3,1#4,1,5#5,4}, [3,4,5,50,50], 1, 50(无向图,mapping,节点,目标值)
/** * Definition for graph node. * class UndirectedGraphNode { * int label; * ArrayList<UndirectedGraphNode> neighbors; * UndirectedGraphNode(int x) { * label = x; neighbors = new ArrayList<UndirectedGraphNode>(); * } * }; */ public class Solution { /** * @param graph a list of Undirected graph node * @param values a hash mapping, <UndirectedGraphNode, (int)value> * @param node an Undirected graph node * @param target an integer * @return the a node */ public UndirectedGraphNode searchNode(ArrayList<UndirectedGraphNode> graph, Map<UndirectedGraphNode, Integer> values, UndirectedGraphNode node, int target) { // Write your code here Queue<UndirectedGraphNode> queue = new LinkedList<UndirectedGraphNode>(); Set<UndirectedGraphNode> hash = new HashSet<UndirectedGraphNode>(); queue.offer(node); hash.add(node); while (!queue.isEmpty()) { UndirectedGraphNode head = queue.poll(); if (values.get(head) == target) { return head; } for (UndirectedGraphNode neighbor : head.neighbors) { if (!hash.contains(neighbor)) { queue.offer(neighbor); hash.add(neighbor); } } } return null; } }
注意:在BFS模板的基础上增加判断是否等于目标节点的步骤。如果要查找所有最近的value=target的点?使用分层遍历。
拓扑排序:(有向图)
9.topological-sorting(拓扑排序)
给定一个有向图,图节点的拓扑排序被定义为:对于每条有向边A--> B,则A必须排在B之前;拓扑排序的第一个节点可以是任何在图中没有其他节点指向它的节点。找到给定图的任一拓扑排序。
三步解法:
/** * Definition for Directed graph. * class DirectedGraphNode { * int label; * ArrayList<DirectedGraphNode> neighbors; * DirectedGraphNode(int x) { label = x; neighbors = new ArrayList<DirectedGraphNode>(); } * }; */ public class Solution { /** * @param graph: A list of Directed graph node * @return: Any topological order for the given graph. */ public ArrayList<DirectedGraphNode> topSort(ArrayList<DirectedGraphNode> graph) { // write your code here ArrayList<DirectedGraphNode> result = new ArrayList<DirectedGraphNode>(); HashMap<DirectedGraphNode, Integer> map = new HashMap<DirectedGraphNode, Integer>(); //step one: collect in-degree for (DirectedGraphNode node : graph) { for (DirectedGraphNode neighbor : node.neighbors) { if (map.containsKey(neighbor)) { map.put(neighbor, map.get(neighbor) + 1); } else { map.put(neighbor, 1); } } } //step two: put all nodes that indegree = 0 into queue Queue<DirectedGraphNode> queue = new LinkedList<DirectedGraphNode>(); for (DirectedGraphNode node : graph) { if (!map.containsKey(node)) { queue.offer(node); result.add(node); } } //step three: bfs while (!queue.isEmpty()) { DirectedGraphNode node = queue.poll(); for (DirectedGraphNode neighbor : node.neighbors) { map.put(neighbor, map.get(neighbor) - 1); if (map.get(neighbor) == 0) { result.add(neighbor); queue.offer(neighbor); } } } return result; } }
注意:1.收集所有节点的入度;2.将入度为0的节点放入队列;3.bfs。入度(indegree)是终点次数之和,出度(outdegree)是起点次数之和。入度为0才进入队列,不会出现两次入度等于0,所以不需要hash表。使用Map<DirectedGraphNode,Integer>来存储(节点,入度值)。
10.course-schedule(课程安排)
总共有n门课程,从0到n - 1。一些课程可能有先决条件,例如,要学习课程0,你必须先学习1,这表示为一对:[0,1]。鉴于课程总数和先决条件列表,您可以完成所有课程吗?返回结果为true或false。
public class Solution { /** * @param numCourses a total of n courses * @param prerequisites a list of prerequisite pairs * @return true if can finish all courses or false */ public boolean canFinish(int numCourses, int[][] prerequisites) { // Write your code here //initialize int[] degree = new int[numCourses]; List[] edges = new ArrayList[numCourses]; for (int i = 0; i < numCourses; i++) { edges[i] = new ArrayList<Integer>(); } //step one: collect indegree for (int i = 0; i < prerequisites.length; i++) { degree[prerequisites[i][0]]++; edges[prerequisites[i][1]].add(prerequisites[i][0]); } //step two: put all nodes that indegrees = 0 into queue Queue<Integer> queue = new LinkedList<Integer>(); for (int i = 0; i < numCourses; i++) { if (degree[i] == 0) { queue.offer(i); } } //step three: bfs int count = 0; while (!queue.isEmpty()) { int course = queue.poll(); count++; int n = edges[course].size(); for (int i = 0; i < n; i++) { int num = (int) edges[course].get(i); degree[num]--; if (degree[num] == 0) { queue.offer(num); } } } return count == numCourses; } }
注意:依然是三步法的主体思路,具体实现细节有变化。使用int类型数组degree记录每个课程的入度,使用List类型数组edges来记录每个先决条件,在bfs阶段使用count记录已学习的课程数,最后判断是否跟课程总数相同。
11.course-schedule-ii(课程安排II)
你需要去上n门九章的课才能获得offer,这些课被标号为 0
到 n-1
。有一些课程需要“前置课程”,比如如果你要上课程0,你需要先学课程1,我们用一个匹配来表示他们: [0,1]
给你课程的总数量和一些前置课程的需求,返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。
public class Solution { /** * @param numCourses a total of n courses * @param prerequisites a list of prerequisite pairs * @return the course order */ public int[] findOrder(int numCourses, int[][] prerequisites) { // Write your code here int[] degree = new int[numCourses]; List[] edges = new ArrayList[numCourses]; for (int i = 0; i < numCourses; i++) { edges[i] = new ArrayList<Integer>(); } for (int i = 0; i < prerequisites.length; i++) { degree[prerequisites[i][0]]++; edges[prerequisites[i][1]].add(prerequisites[i][0]); } Queue<Integer> queue = new LinkedList<Integer>(); for (int i = 0; i < degree.length; i++){ if (degree[i] == 0) { queue.add(i); } } int count = 0; int[] order = new int[numCourses]; while (!queue.isEmpty()) { int course = queue.poll(); order[count] = course; count++; int size = edges[course].size(); for (int i = 0; i < size; i++) { int num = (int) edges[course].get(i); degree[num]--; if (degree[num] == 0) { queue.add(num); } } } if (count == numCourses) { return order; } return new int[0]; } }
注意:在课程安排题目的基础上增加一个order数组,记录顺序即可。如果不可能完成所有课程,返回new int[0]。
12.sequence-reconstruction(序列重构)
判断是否序列 org
能唯一地由 seqs
重构得出. org
是一个由从1到n的正整数排列而成的序列,1 ≤ n ≤ 10^4。 重构表示组合成seqs
的一个最短的父序列 (意思是,一个最短的序列使得所有 seqs
里的序列都是它的子序列)。判断是否有且仅有一个能从 seqs
重构出来的序列,并且这个序列是org
。
public class Solution { /** * @param org a permutation of the integers from 1 to n * @param seqs a list of sequences * @return true if it can be reconstructed only one or false */ public boolean sequenceReconstruction(int[] org, int[][] seqs) { // Write your code here Map<Integer, Set<Integer>> map = new HashMap<Integer, Set<Integer>>(); Map<Integer, Integer> indegree = new HashMap<Integer, Integer>(); for (int num : org) { map.put(num, new HashSet<Integer>()); indegree.put(num, 0); } int n = org.length; int count = 0; for (int[] seq : seqs) { count += seq.length; if (seq.length >= 1 && (seq[0] <= 0 || seq[0] > n)) { return false; } for (int i = 1; i < seq.length; i++) { if (seq[i] <= 0 || seq[i] > n) { return false; } if (map.get(seq[i - 1]).add(seq[i])) { indegree.put(seq[i], indegree.get(seq[i]) + 1); } } } // case: [1], [] if (count < n) { return false; } Queue<Integer> q = new ArrayDeque<Integer>(); for (int key : indegree.keySet()) { if (indegree.get(key) == 0) { q.add(key); } } int cnt = 0; while (q.size() == 1) { for (int next : map.get(q.poll())) { indegree.put(next, indegree.get(next) - 1); if (indegree.get(next) == 0) { q.add(next); } } cnt++; } return cnt == org.length; } }
矩阵中的BFS:
注意如下划线所示内容,访问过的位置要变为不能再次访问!!!
13.number-of-islands(岛屿的个数)
给一个01矩阵,求不同的岛屿的个数。0代表海,1代表岛,如果两个1相邻,那么这两个1属于同一个岛。我们只考虑上下左右为相邻。
class Coordinate { int x; int y; public Coordinate(int x, int y) { this.x = x; this.y = y; } } public class Solution { /** * @param grid a boolean 2D matrix * @return an integer */ public int numIslands(boolean[][] grid) { // Write your code here if (grid == null || grid.length == 0 || grid[0].length == 0) { return 0; } int n = grid.length; int m = grid[0].length; int islands = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (grid[i][j]) { markByBFS(grid, i, j); islands++; } } } return islands; } private void markByBFS(boolean[][] grid, int x, int y) { int[] deltaX = {1, 0, 0, -1}; int[] deltaY = {0, 1, -1, 0}; Queue<Coordinate> queue = new LinkedList<Coordinate>(); queue.offer(new Coordinate(x, y)); grid[x][y] = false; while (!queue.isEmpty()) { Coordinate node = queue.poll(); for (int i = 0; i < 4; i++) { Coordinate adj = new Coordinate( node.x + deltaX[i], node.y + deltaY[i] ); if (!inBound(adj, grid)) { continue; } if (grid[adj.x][adj.y]) { grid[adj.x][adj.y] = false; queue.offer(adj); } } } } private boolean inBound(Coordinate coor, boolean[][] grid) { int n = grid.length; int m = grid[0].length; return coor.x >= 0 && coor.x < n && coor.y >= 0 && coor.y < m; } }
注意:四个方向坐标变换数组:int[] deltaX={1, 0, 0, -1};int[] deltaY={0, 1, -1, 0};的使用。
双重for循环对矩阵中的每一个元素进行遍历,遇到元素1就进行BFS搜索(在搜索过程中遇到在边界范围内相邻的1就变为0),搜索一次岛屿数目加一。inBound()函数判断某点是否在边界范围内。
14.number-of-islands-ii(岛屿的个数II)【hard】
给定 n,m,分别代表一个2D矩阵的行数和列数,同时,给定一个大小为 k 的二元数组A。起初,2D矩阵的行数和列数均为 0,即该矩阵中只有海洋。二元数组有 k 个运算符,每个运算符有 2 个整数 A[i].x, A[i].y,你可通过改变矩阵网格中的A[i].x],[A[i].y] 来将其由海洋改为岛屿。请在每次运算后,返回矩阵中岛屿的数量。0 代表海,1 代表岛。如果两个1相邻,那么这两个1属于同一个岛。我们只考虑上下左右为相邻。
/** * Definition for a point. * class Point { * int x; * int y; * Point() { x = 0; y = 0; } * Point(int a, int b) { x = a; y = b; } * } */ public class Solution { /** * @param n an integer * @param m an integer * @param operators an array of point * @return an integer array */ int converttoId(int x, int y, int m){ return x * m + y; } class UnionFind{ HashMap<Integer, Integer> father = new HashMap<Integer, Integer>(); UnionFind(int n, int m){ for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { int id = converttoId(i, j, m); father.put(id, id); } } } int compressed_find(int x){ int parent = father.get(x); while (parent != father.get(parent)) { parent = father.get(parent); } int temp = -1; int fa = x; while (fa != father.get(fa)) { temp = father.get(fa); father.put(fa, parent); fa = temp; } return parent; } void union(int x, int y){ int fa_x = compressed_find(x); int fa_y = compressed_find(y); if (fa_x != fa_y) { father.put(fa_x, fa_y); } } } public List<Integer> numIslands2(int n, int m, Point[] operators) { // Write your code here List<Integer> ans = new ArrayList<Integer>(); if (operators == null) { return ans; } int []dx = {0, -1, 0, 1}; int []dy = {1, 0, -1, 0}; int [][]island = new int[n][m]; UnionFind uf = new UnionFind(n, m); int count = 0; for (int i = 0; i < operators.length; i++) { int x = operators[i].x; int y = operators[i].y; if (island[x][y] != 1) { count++; island[x][y] = 1; int id = converttoId(x, y, m); for (int j = 0; j < 4; j++) { int nx = x + dx[j]; int ny = y + dy[j]; if (0 <= nx && nx < n && 0 <= ny && ny < m && island[nx][ny] == 1) { int nid = converttoId(nx, ny, m); int fa = uf.compressed_find(id); int nfa = uf.compressed_find(nid); if (fa != nfa) { count--; uf.union(id, nid); } } } } ans.add(count); } return ans; } }
15.zombie-in-matrix(矩阵中的僵尸)
给定一个二维矩阵,每个单元格是墙,僵尸或人(数字0,1,2)。僵尸每天可以将最近的人(上/下/左/右)变成僵尸,但是不能通过墙。将所有人变成僵尸需要多长时间? 如果不能把所有人都变成僵尸返回-1。
class Coordinate { int x; int y; public Coordinate(int x, int y) { this.x = x; this.y = y; } } public class Solution { /** * @param grid a 2D integer grid * @return an integer */ public int PEOPLE = 0; public int ZOMBIE = 1; public int WALL = 2; public int[] deltaX = {1, 0, 0, -1}; public int[] deltaY = {0, 1, -1, 0}; public int zombie(int[][] grid) { // Write your code here if (grid == null || grid.length == 0 || grid[0].length == 0) { return 0; } int n = grid.length; int m = grid[0].length; //step one int people = 0; Queue<Coordinate> queue = new LinkedList<>(); for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (grid[i][j] == PEOPLE) { people++; } else if (grid[i][j] == ZOMBIE) { queue.offer(new Coordinate(i, j)); } } } //step two if (people == 0) { return 0; } //step three int days = 0; while (!queue.isEmpty()) { days++; int size = queue.size(); for (int i = 0; i < size; i++) { Coordinate node = queue.poll(); for (int j = 0; j < 4; j++) { Coordinate adj = new Coordinate( node.x + deltaX[j], node.y + deltaY[j]); if (!isPeople(grid, adj)) { continue; } grid[adj.x][adj.y] = ZOMBIE; people--; if (people == 0) { return days; } queue.offer(adj); } } } return -1; } private boolean isPeople(int[][] grid, Coordinate adj) { int n = grid.length; int m = grid[0].length; if (adj.x < 0 || adj.x >= n) { return false; } if (adj.y < 0 || adj.y >= m) { return false; } return (grid[adj.x][adj.y] == PEOPLE); } }
注意:此题是BFS完整的三层循环。四个方向坐标变换数组:int[] deltaX={1, 0, 0, -1};int[] deltaY={0, 1, -1, 0};的使用。需要变量people(记录人数)和days(记录需要的天数,先记!)。分为三个步骤:1.初始化queue同时计算PEOPLE的数目(遇到ZOMBIE就放入队列,遇到PEOPLE,people加一);2.如果PEOPLE的数量为0,直接返回0;3.BFS(队列不为空,days就加一。isPeople()函数判断某点是否在边界内且为PEOPLE,如果满足该条件,将PEOPLE变为ZOMBIE,people减一。当people等于0时,返回days)。
16.knight-shortest-path(骑士的最短路径)
给定一个棋盘上的骑士(一个二进制矩阵,0为空,1为障碍),具有一个源位置,找到目的地位置的最短路径,返回路线的长度。如果骑士无法到达,返回-1。如果骑士在位置(x, y),他一步可以走到下列位置:(x + 1, y + 2)(x + 1, y - 2)(x - 1, y + 2)(x - 1, y - 2)
(x + 2, y + 1)(x + 2, y - 1)(x - 2, y + 1)(x - 2, y - 1)
/** * Definition for a point. * public class Point { * public int x, y; * public Point() { x = 0; y = 0; } * public Point(int a, int b) { x = a; y = b; } * } */ public class Solution { /** * @param grid a chessboard included 0 (false) and 1 (true) * @param source, destination a point * @return the shortest path */ int[] deltaX = {1, 1, 2, 2, -1, -1, -2, -2}; int[] deltaY = {2, -2, 1, -1, 2, -2, 1, -1}; public int shortestPath(boolean[][] grid, Point source, Point destination) { // Write your code here if (grid == null || grid.length == 0 || grid[0].length == 0) { return -1; } int n = grid.length; int m = grid[0].length; Queue<Point> queue = new LinkedList<>(); queue.offer(source); int steps = 0; while (!queue.isEmpty()) { int size = queue.size(); for (int i = 0; i < size; i++) { Point point = queue.poll(); if (point.x == destination.x && point.y == destination.y) { return steps; } for (int direction = 0; direction < 8; direction++) { Point nextPoint = new Point( point.x + deltaX[direction], point.y + deltaY[direction] ); if (!inBound(nextPoint, grid)) { continue; } queue.offer(nextPoint); // mark the point not accessible grid[nextPoint.x][nextPoint.y] = true; } } steps++; } return -1; } private boolean inBound(Point point, boolean[][] grid) { int n = grid.length; int m = grid[0].length; if (point.x < 0 || point.x >= n) { return false; } if (point.y < 0 || point.y >= m) { return false; } return grid[point.x][point.y] == false; } }
注意:八个方向坐标变换数组:int[] deltaX={1, 1, 2, 2, -1, -1, -2, -2};int[] deltaY={2, -2, 1, -1, 2, -2, 1, -2};的使用。
此题是BFS完整的三层循环。需要变量steps(记录步数,后记!)。在BFS阶段如果骑士的坐标等于目标坐标,就返回steps。inBound()函数判断某点是否在边界内且不是障碍。骑士路过的位置要变为ture(障碍)。steps在while循环最后加一。
Build Post Office 解法有二:
方法1:从空格出发。循环枚举所有的office修建位置的可能性(空格),计算从这个位置出发到达所有房子的距离之和,在所有方案中找到最小的距离和。
方法2:从房子出发。循环枚举所有的房子的位置,从房子出发,计算每个空格到达房子的距离,累加某个空格到达其他所有房子的距离之和,在所有空格中找到最小距离和。
17.build-post-office(建邮局)【hard】
给定一个二维网格,每个单元格是一个房子或空(数字1,0),找到建立邮局的地方,邮局到所有房屋总和的距离最小。返回最小距离。 如果不可能返回-1。
public class Solution { /** * @param grid a 2D grid * @return an integer */ public int shortestDistance(int[][] grid) { // Write your code here if (grid == null || grid.length == 0 || grid[0].length == 0) { return -1; } int n = grid.length; int m = grid[0].length; //存储x和y的坐标和 List<Integer> sumx = new ArrayList<Integer>(); List<Integer> sumy = new ArrayList<Integer>(); //存储x和y的坐标 List<Integer> x = new ArrayList<Integer>(); List<Integer> y = new ArrayList<Integer>(); int result = Integer.MAX_VALUE; //循环枚举所有房子的位置 for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (grid[i][j] == 1) { x.add(i); y.add(j); } } } //排序,方便后续二分法找位置 Collections.sort(x); Collections.sort(y); int total = x.size(); //x中的元素位置index与sumx中的index+1相对应 sumx.add(0); sumy.add(0); for (int i = 1; i <= total; i++) { sumx.add(sumx.get(i - 1) + x.get(i - 1)); sumy.add(sumy.get(i - 1) + y.get(i - 1)); } //计算每个空格到达所有房子的距离 for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (grid[i][j] == 0) { int cost_x = get_cost(x, sumx, i, total); int cost_y = get_cost(y, sumy, j, total); //找到最小的距离和 if (cost_x + cost_y < result) { result = cost_x + cost_y; } } } } return result; } private int get_cost(List<Integer> x, List<Integer> sum, int pos, int n) { if (n == 0) { return 0; } //所有坐标都比空格的坐标大 if (x.get(0) > pos) { return sum.get(n) - pos * n; } //二分法找到<=pos的最大位置index int l = 0; int r = n - 1; while (l + 1 < r) { int mid = l + (r - l) / 2; if (x.get(mid) <= pos) { l = mid; } else { r = mid; } } int index = 0; if (x.get(r) <= pos) { index = r; } else { index = l; } //分>pos(第一行)和<=pos(第二行)两部分计算距离代价 return sum.get(n) - sum.get(index + 1) - pos * (n - index - 1) + pos * (index + 1) - sum.get(index + 1); } }
注意:使用方法2。
18.build-post-office-ii(建邮局II)【hard】
给定一个二维网格,每个单元格是墙,房屋或空(数字2,1,0),找到建立邮局的地方,以便从邮局到所有房屋的距离总和最小。返回距离的最小总和。 如果不可能返回-1。
class Coordinate { int x; int y; public Coordinate(int x, int y) { this.x = x; this.y = y; } } public class Solution { /** * @param grid a 2D grid * @return an integer */ public int EMPTY = 0; public int HOUSE = 1; public int WALL = 2; public int[][] grid; public int n; public int m; public int[] deltaX = {1, 0, 0, -1}; public int[] deltaY = {0, 1, -1, 0}; //复制原始grid,便于全局使用 private void setGrid(int[][] grid) { n = grid.length; m = grid[0].length; this.grid = grid; } //获得相应类型元素的坐标列表 private List<Coordinate> getCoordinates(int type) { List<Coordinate> coordinates = new ArrayList<Coordinate>(); for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (grid[i][j] == type) { coordinates.add(new Coordinate(i, j)); } } } return coordinates; } //判断元素在边界内且为空格 private boolean inBound(Coordinate coor) { if (coor.x < 0 || coor.x >= n) { return false; } if (coor.y < 0 || coor.y >= m) { return false; } return grid[coor.x][coor.y] == EMPTY; } public int shortestDistance(int[][] grid) { // Write your code here if (grid == null || grid.length == 0 || grid[0].length == 0) { return -1; } setGrid(grid); List<Coordinate> houses = getCoordinates(HOUSE); int[][] distanceSum = new int[n][m];//记录一个空格到所有房子的距离和 //记录哪个空格被访问以及访问次数(最大次数为房子的个数) int[][] visitedTimes = new int[n][m]; for (Coordinate house : houses) { bfs(house, distanceSum, visitedTimes); } //在所有空格中找到最小距离和 int shortest = Integer.MAX_VALUE; List<Coordinate> empties = getCoordinates(EMPTY); for (Coordinate empty : empties) { if (visitedTimes[empty.x][empty.y] != houses.size()) { continue; } shortest = Math.min(shortest, distanceSum[empty.x][empty.y]); } if (shortest == Integer.MAX_VALUE) { return -1; } return shortest; } private void bfs(Coordinate start, int[][] distanceSum, int[][] visitedTimes) { Queue<Coordinate> queue = new LinkedList<>(); boolean[][] hash = new boolean[n][m]; queue.offer(start); hash[start.x][start.y] = true; int steps = 0; while (!queue.isEmpty()) { steps++; int size = queue.size(); for (int i = 0; i < size; i++) { Coordinate node = queue.poll(); for (int j = 0; j < 4; j++) { Coordinate adj = new Coordinate( node.x + deltaX[j], node.y + deltaY[j]); if (!inBound(adj)) { continue; } if (hash[adj.x][adj.y]) { continue; } queue.offer(adj); hash[adj.x][adj.y] = true; distanceSum[adj.x][adj.y] += steps; visitedTimes[adj.x][adj.y]++; } } } } }
注意:使用方法2。
19.connected-component-in-undirected-graph(找无向图的连通块)
找出无向图中所有的连通块。图中的每个节点包含一个label属性和一个邻接点的列表。(一个无向图的连通块是一个子图,其中任意两个顶点通过路径相连,且不与整个图中的其它顶点相连。)每个连通块内部应该按照label属性排序。
例:{1,2,4#2,1,4#3,5#4,1,2#5,3}
/** * Definition for Undirected graph. * class UndirectedGraphNode { * int label; * ArrayList<UndirectedGraphNode> neighbors; * UndirectedGraphNode(int x) { label = x; neighbors = new ArrayList<UndirectedGraphNode>(); } * }; */ public class Solution { /** * @param nodes a array of Undirected graph node * @return a connected set of a Undirected graph */ public List<List<Integer>> connectedSet(ArrayList<UndirectedGraphNode> nodes) { // Write your code here List<List<Integer>> result = new ArrayList<List<Integer>>(); Set<Integer> visited = new HashSet<Integer>(); //若该节点未被访问过则加入队列,开始访问其邻居节点 for (int i = 0; i < nodes.size(); i++) { if (!visited.contains(nodes.get(i).label)) { List<Integer> list = new ArrayList<Integer>(); Queue<UndirectedGraphNode> queue = new LinkedList<>(); queue.offer(nodes.get(i)); list.add(nodes.get(i).label); visited.add(nodes.get(i).label); while (!queue.isEmpty()) {//将该节点未访问过的邻居节点加入队列 UndirectedGraphNode node = queue.poll(); for (int j = 0; j < node.neighbors.size(); j++) { UndirectedGraphNode newNode = node.neighbors.get(j); if (!visited.contains(newNode.label)) { queue.offer(newNode); list.add(newNode.label); visited.add(newNode.label); } } } Collections.sort(list);//排序 result.add(list); } } return result; } }
注意:每次都从没有被访问过的节点开始,BFS遍历其邻居。
20.word-ladder(单词接龙)
给出两个单词(start和end)和一个字典,找到从start到end的最短转换序列。每次只能改变一个字母。变换过程中的中间单词必须在字典中出现。如果没有转换序列则返回0。
所有单词具有相同的长度,都只包含小写字母。
public class Solution { /** * @param start, a string * @param end, a string * @param dict, a set of string * @return an integer */ public int ladderLength(String start, String end, Set<String> dict) { // write your code here if (dict == null) { return 0; } if (start.equals(end)) { return 1; } dict.add(start); dict.add(end); Queue<String> queue = new LinkedList<String>(); Set<String> hash = new HashSet<String>(); queue.offer(start); hash.add(start); int length = 1; while (!queue.isEmpty()) { length++; int size = queue.size(); for (int i = 0; i < size; i++) { String word = queue.poll(); for (String nextWord : getNextWords(word, dict)) { if (hash.contains(nextWord)) { continue; } if (nextWord.equals(end)) { return length; } queue.offer(nextWord); hash.add(nextWord); } } } return 0; } private String replace(String word, int index, char c) { char[] chars = word.toCharArray(); chars[index] = c; return new String(chars); } private ArrayList<String> getNextWords(String word, Set<String> dict) { ArrayList<String> nextWords = new ArrayList<String>(); for (char c = 'a'; c <= 'z'; c++) { for (int i = 0; i < word.length(); i++) { if (c == word.charAt(i)) { continue; } String nextWord = replace(word, i, c); if (dict.contains(nextWord)) { nextWords.add(nextWord); } } } return nextWords; } }
注意:replace()函数:替换单词中的一个字母(涉及string和char的转换);getNextWords()函数:得到可以替换的下一个单词的集合。变量length记录转换长度(先记!)
如果字典为空,返回0。如果start和end相同,返回1。首先将start和end加入dict中,然后执行BFS。
21.six-degrees(六度问题)
六度分离是一个哲学问题,说的是每个人每个东西可以通过六步或者更少的步数建立联系。现在给你一个友谊关系,查询两个人可以通过几步相连,如果不相连返回 -1。
例:{1,2,3#2,1,4#3,1,4#4,2,3} s = 1, t = 4 返回 2
/** * Definition for Undirected graph. * class UndirectedGraphNode { * int label; * List<UndirectedGraphNode> neighbors; * UndirectedGraphNode(int x) { * label = x; * neighbors = new ArrayList<UndirectedGraphNode>(); * } * }; */ public class Solution { /** * @param graph a list of Undirected graph node * @param s, t two Undirected graph nodes * @return an integer */ public int sixDegrees(List<UndirectedGraphNode> graph, UndirectedGraphNode s, UndirectedGraphNode t) { // Write your code here if (s == t) { return 0; } Map<UndirectedGraphNode, Integer> visited = new HashMap<UndirectedGraphNode, Integer>(); Queue<UndirectedGraphNode> queue = new LinkedList<UndirectedGraphNode>(); queue.offer(s); visited.put(s, 0); while (!queue.isEmpty()) { UndirectedGraphNode node = queue.poll(); int step = visited.get(node); for (int i = 0; i < node.neighbors.size(); i++) { if (visited.containsKey(node.neighbors.get(i))) { continue; } visited.put(node.neighbors.get(i), step + 1); queue.offer(node.neighbors.get(i)); if (node.neighbors.get(i) == t) { return step + 1; } } } return -1; } }
注意:使用Map<UndirectedGraphNode, Integer>来存储(经过的节点,需要的步数)。
22.remove-substrings(删除子字符串)
给定一个字符串s和一组n个子字符串。 你应该从s中删除那些n个子字符串的每个实例,以便s是最小长度,并输出这个最小长度。
{ /** * @param s a string * @param dict a set of n substrings * @return the minimum length */ public int minLength(String s, Set<String> dict) { // Write your code here Queue<String> queue = new LinkedList<String>(); Set<String> hash = new HashSet<String>(); queue.offer(s); hash.add(s); int min = s.length(); while (!queue.isEmpty()) { s = queue.poll(); for (String sub : dict) { int found = s.indexOf(sub); while (found != -1) { String new_s = s.substring(0, found) + s.substring(found + sub.length(), s.length()); if (!hash.contains(new_s)) { if (new_s.length() < min) { min = new_s.length(); } queue.offer(new_s); hash.add(new_s); } found = s.indexOf(sub, found + 1); } } } return min; } }
注意:indexOf(str)函数返回某个指定的字符串str在字符串中首次出现的位置;indexOf(str,index)函数返回某个指定的字符串str从index处开始搜索在字符串中首次出现的位置。substring()取子字符串。对字典中的每个字符串要一次性删除干净。
int found = s.indexOf(sub);
while (found != -1) {
......
found = s.indexOf(sub, found + 1);
}
23.smallest-rectangle-enclosing-black-pixels(覆盖黑点的最小矩阵)【hard】
图像由具有0作为白色像素和1作为黑色像素的二进制矩阵表示。连接黑色像素,即只有一个黑色区域。像素水平和垂直连接。给定一个黑色像素的位置(x,y),返回包围所有黑色像素的最小(轴对齐)矩形的区域。
public class Solution { /** * @param image a binary matrix with '0' and '1' * @param x, y the location of one of the black pixels * @return an integer */ public int minArea(char[][] image, int x, int y) { // Write your code here if (image == null || image.length == 0 || image[0].length == 0) { return 0; } int n = image.length; int m = image[0].length; int left = findLeft(image, 0, y); int right = findRight(image, y, m - 1); int top = findTop(image, 0, x); int bottom = findBottom(image, x, n - 1); return (right - left + 1) * (bottom - top + 1); } private int findLeft(char[][] image, int start, int end) { while (start + 1 < end) { int mid = start + (end - start) / 2; if (isEmptyColumn(image, mid)) { start = mid; } else { end = mid; } } if (isEmptyColumn(image, start)) { return end; } return start; } private int findRight(char[][] image, int start, int end) { while (start + 1 < end) { int mid = start + (end - start) / 2; if (isEmptyColumn(image, mid)) { end = mid; } else { start = mid; } } if (isEmptyColumn(image, end)) { return start; } return end; } private int findTop(char[][] image, int start, int end) { while (start + 1 < end) { int mid = start + (end - start) / 2; if (isEmptyRow(image, mid)) { start = mid; } else { end = mid; } } if (isEmptyRow(image, start)) { return end; } return start; } private int findBottom(char[][] image, int start, int end) { while (start + 1 < end) { int mid = start + (end - start) / 2; if (isEmptyRow(image, mid)) { end = mid; } else { start = mid; } } if (isEmptyRow(image, end)) { return start; } return end; } private boolean isEmptyColumn(char[][] image, int col) { for (int i = 0; i < image.length; i++) { if (image[i][col] == '1') { return false; } } return true; } private boolean isEmptyRow(char[][] image, int row) { for (int j = 0; j < image[0].length; j++) { if (image[row][j] == '1') { return false; } } return true; } }
注意:利用二分法找边界:left = findLeft(image, 0, y); right = findRight(image, y, m - 1);top = findTop(image, 0, x); bottom = findBottom(image, x, n - 1)。
isEmptyColumn()和isEmptyRow()函数判断该点是否是白色像素点。最后返回(right - left + 1) * (bottom - top + 1)。