恭喜发现宝藏!微信搜索公众号【TechGuide】关注更多新鲜好文和互联网大厂的笔经面经。
作者@TechGuide【全网同名】
点赞再看,养成习惯,您动动手指对原创作者意义非凡🤝
文章目录
前言
在笔者大大小小笔试了五六场之后,似乎得到了一点规律,互联网大厂的笔试题集中在两大块,一是dp动态规划,另外一个就是dfs深度优先搜索(也包含回溯问题)了(此处再补充一个——BFS 广度优先搜索)。之前笔试的时候,总是觉得面对这种类型的题目,即使勉强写出来了,也远远没有达到融会贯通的程度,很是苦恼。看了几十篇高赞高评论的总结对比文章后,并没有觉得有什么帮助,我总结了两个原因,第一个原因就是作者讲的太浅了,最然容易理解,却无法实际运用到更加复杂的场景中,另一个原因就是,很多别人得出的宝贵结论,你因为缺少自己的思考和大量题目的训练而无法感同身受,因而无法体会到其中的精妙之处。这里我专门讲一下leetcode中的BFS专题下的题目,试一试能不能总结出一些规律,也给自己练习用。
本文末尾附上了图论的几个经典算法,供感兴趣的读者拓展延伸。
正文
Leetcode127. word Ladder
思路:
利用两个set分别从beginword和endword层序替换成新的一批wordset,直到两个set互相有联通了,返回链长度;或者某个set为空时,返回0.这里用了双向BFS,会调换beginset和endset
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
Set<String> wordSet = new HashSet<String>(wordList);
if(!wordSet.contains(endWord)) return 0;
Set<String> beginSet = new HashSet<String>(), endSet = new HashSet<String>();
beginSet.add(beginWord);
endSet.add(endWord);
int len = 1;
Set<String> vis = new HashSet<String>();
while(!beginSet.isEmpty()&&!endSet.isEmpty()){
if(beginSet.size()>endSet.size()){
Set<String> temp = beginSet;
beginSet = endSet;
endSet = temp;
}
Set<String> temp = new HashSet<String>();
for(String word:beginSet){
char[] chars = word.toCharArray();
for (int i = 0; i < chars.length; i++) {
//每个位置依次替换a-z字母,构造新的word
for(char c='a';c<='z';c++){
char origin = chars[i];
chars[i] = c;
String target = String.valueOf(chars);
if(endSet.contains(target)){
return len+1;
}
if(!vis.contains(target) && wordSet.contains(target)){
temp.add(target);
vis.add(target);
}
chars[i] = origin;
}
}
}
beginSet = temp;
len++;
}
return 0;
}
Leetcode199. Binary Tree right view
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if(root==null) return res;
Queue<TreeNode> q = new LinkedList<TreeNode>();
q.offer(root);
while(!q.isEmpty()){
int lastIndex = q.size()-1;
for (int i = 0; i <= lastIndex; i++) {
TreeNode node = q.poll();
if(node.left!=null) q.offer(node.left);
if(node.right!=null) q.offer(node.right);
if(i==lastIndex) res.add(node.val);
}
}
return res;
}
做完以上两题还未进入正餐环节,因为笔试题目考察BFS喜欢结合图或者二维数组来考,如果只停留在以上水平是远远不够的,接下来几题我会挑一些关于图(或二维数组)的类笔试来做。
Leetcode 542. 01 Matrix
这是一道非常典型的bfs题目,极适合于新手练手,快速熟悉操作。
public class Solution {
public int[][] updateMatrix(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
Queue<int[]> queue = new LinkedList<>();
boolean[][] vis = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if(matrix[i][j]==0){
queue.offer(new int[]{i,j});
vis[i][j] = true;
}
}
}
int[][] dir = new int[][]{{-1,0},{1,0},{0,-1},{0,1}};
while (!queue.isEmpty()){
int[] cur = queue.poll();
for (int i = 0; i < 4; i++) {
int x = cur[0]+dir[i][0];
int y = cur[1]+dir[i][1];
if(x<0 || y<0 || x>=m || y>=n || vis[x][y]) continue;
matrix[x][y] = matrix[cur[0]][cur[1]]+1;
vis[x][y] = true;
queue.offer(new int[]{x,y});
}
}
return matrix;
}
}
Leetcode 743. Network Delay Time
非常典型且巧妙的邻接表存图方式,大力推荐!
//There are N network nodes, labelled 1 to N.
//
// Given times, a list of travel times as directed edges times[i] = (u, v, w), w
//here u is the source node, v is the target node, and w is the time it takes for
//a signal to travel from source to target.
//
// Now, we send a signal from a certain node K. How long will it take for all no
//des to receive the signal? If it is impossible, return -1.
//
//
//
// Example 1:
//
//
//
//
//Input: times = [[2,1,1],[2,3,1],[3,4,1]], N = 4, K = 2
//Output: 2
//
//
//
//
// Note:
//
//
// N will be in the range [1, 100].
// K will be in the range [1, N].
// The length of times will be in the range [1, 6000].
// All edges times[i] = (u, v, w) will have 1 <= u, v <= N and 0 <= w <= 100.
class Solution {
public int networkDelayTime(int[][] times, int N, int K) {
//作为模板使用
HashMap<Integer,HashMap<Integer,Integer>> map = new HashMap<>();
boolean[] vis = new boolean[N+1];
for (int[] time:times) {
map.putIfAbsent(time[0],new HashMap<>());
map.get(time[0]).put(time[1],time[2]);
}
//distance and node, ordered by distance to K
PriorityQueue<int[]> pq = new PriorityQueue<>((o1,o2)->(o1[0]-o2[0]));
pq.offer(new int[]{0,K});
int res=0;
while (!pq.isEmpty()){
int[] cur = pq.poll();
int curNode = cur[1];
int curDist = cur[0];
if(vis[curNode]) continue;
vis[curNode] = true;
//dist always from low to high
res = curDist;
N--;
//if all the nodes are visited, complete
if(N==0) return res;
if(map.containsKey(curNode)){
for (int next : map.get(curNode).keySet()) {
pq.offer(new int[]{curDist+map.get(curNode).get(next),next});
}
}
}
return -1;
}
}
Leetcode 752. open the lock
一个标记是否访问过的新思路,用set而不是数组,很巧妙
//You have a lock in front of you with 4 circular wheels. Each wheel has 10 slot
//s: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'. The wheels can rotate freel
//y and wrap around: for example we can turn '9' to be '0', or '0' to be '9'. Each
// move consists of turning one wheel one slot.
//
// The lock initially starts at '0000', a string representing the state of the 4
// wheels.
//
// You are given a list of deadends dead ends, meaning if the lock displays any
//of these codes, the wheels of the lock will stop turning and you will be unable
//to open it.
//
// Given a target representing the value of the wheels that will unlock the lock
//, return the minimum total number of turns required to open the lock, or -1 if i
//t is impossible.
//
//
// Example 1:
//
//
//Input: deadends = ["0201","0101","0102","1212","2002"], target = "0202"
//Output: 6
//Explanation:
//A sequence of valid moves would be "0000" -> "1000" -> "1100" -> "1200" -> "12
//01" -> "1202" -> "0202".
//Note that a sequence like "0000" -> "0001" -> "0002" -> "0102" -> "0202" would
// be invalid,
//because the wheels of the lock become stuck after the display becomes the dead
// end "0102".
//
//
// Example 2:
//
//
//Input: deadends = ["8888"], target = "0009"
//Output: 1
//Explanation:
//We can turn the last wheel in reverse to move from "0000" -> "0009".
//
//
// Example 3:
//
//
//Input: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], t
//arget = "8888"
//Output: -1
//Explanation:
//We can't reach the target without getting stuck.
//
//
// Example 4:
//
//
//Input: deadends = ["0000"], target = "8888"
//Output: -1
//
//
//
// Constraints:
//
//
// 1 <= deadends.length <= 500
// deadends[i].length == 4
// target.length == 4
// target will not be in the list deadends.
// target and deadends[i] consist of digits only.
class Solution {
public int openLock(String[] deadends, String target) {
Queue<String> q = new LinkedList<>();
Set<String> deads = new HashSet<String>(Arrays.asList(deadends));
Set<String> vis = new HashSet<String>();
q.offer("0000");
vis.add("0000");
int cnt = 0;
while(!q.isEmpty()){
int size = q.size();
while(size>0){
String cur = q.poll();
if(deads.contains(cur)){
size--;
continue;
}
if(cur.equals(target)) return cnt;
StringBuilder sb = new StringBuilder(cur);
for (int i = 0; i < 4; i++) {
char c = cur.charAt(i);
String s1 = sb.substring(0,i)+(c=='9'?0:c-'0'+1)+sb.substring(i+1);
String s2 = sb.substring(0,i)+(c=='0'?9:c-'0'-1)+sb.substring(i+1);
if(!vis.contains(s1)&&!deads.contains(s1)){
q.offer(s1);
vis.add(s1);
}
if(!vis.contains(s2)&&!deads.contains(s2)){
q.offer(s2);
vis.add(s2);
}
}
size--;
}
cnt++;
}
return -1;
}
}
Leetcode 787. Cheapest Flights Within K Stops
//There are n cities connected by m flights. Each flight starts from city u and
//arrives at v with a price w.
//
// Now given all the cities and flights, together with starting city src and the
// destination dst, your task is to find the cheapest price from src to dst with u
//p to k stops. If there is no such route, output -1.
//
//
//Example 1:
//Input:
//n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
//src = 0, dst = 2, k = 1
//Output: 200
//Explanation:
//The graph looks like this:
//
//
//The cheapest price from city 0 to city 2 with at most 1 stop costs 200, as mar
//ked red in the picture.
//
//
//Example 2:
//Input:
//n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
//src = 0, dst = 2, k = 0
//Output: 500
//Explanation:
//The graph looks like this:
//
//
//The cheapest price from city 0 to city 2 with at most 0 stop costs 500, as mar
//ked blue in the picture.
//
//
//
// Constraints:
//
//
// The number of nodes n will be in range [1, 100], with nodes labeled from 0 to
// n - 1.
// The size of flights will be in range [0, n * (n - 1) / 2].
// The format of each flight will be (src, dst, price).
// The price of each flight will be in the range [1, 10000].
// k is in the range of [0, n - 1].
// There will not be any duplicated flights or self cycles.
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public static int findCheapestPrice(int n, int[][] flights, int src, int dst, int K) {
HashMap<Integer, HashMap<Integer, Integer>> cities = new HashMap<>();
for (int i = 0; i < flights.length; i++) {
cities.putIfAbsent(flights[i][0],new HashMap<>());
cities.get(flights[i][0]).put(flights[i][1],flights[i][2]);
}
Queue<int[]> queue = new LinkedList<>();
queue.offer(new int[]{src,0});
int stop = 0, ans = Integer.MAX_VALUE;
while (!queue.isEmpty()){
int size = queue.size();
// System.out.println("queue:"+size);
while (size-->0){
int[] cur = queue.poll();
// System.out.println("cur:"+cur[0]);
if(cur[0]==dst) ans = Integer.min(ans,cur[1]);
// System.out.println("ans:"+ans);
if(!cities.keySet().contains(cur[0])){
continue;
}
for(int next:cities.get(cur[0]).keySet()){
if(cur[1]+cities.get(cur[0]).get(next)<ans){
System.out.println("next:"+next);
queue.offer(new int[]{next,cur[1]+cities.get(cur[0]).get(next)});
}
}
}
// System.out.println("=============");
if(stop++>K) break;
// System.out.println("stop:"+stop);
}
return (ans==Integer.MAX_VALUE?-1:ans);
}
}
Leetcode 934. Shortest Bridge(BFS&DFS)
BFS&DFS混合题型
//In a given 2D binary array A, there are two islands. (An island is a 4-directi
//onally connected group of 1s not connected to any other 1s.)
//
// Now, we may change 0s to 1s so as to connect the two islands together to form
// 1 island.
//
// Return the smallest number of 0s that must be flipped. (It is guaranteed that
// the answer is at least 1.)
//
//
// Example 1:
// Input: A = [[0,1],[1,0]]
//Output: 1
// Example 2:
// Input: A = [[0,1,0],[0,0,0],[0,0,1]]
//Output: 2
// Example 3:
// Input: A = [[1,1,1,1,1],[1,0,0,0,1],[1,0,1,0,1],[1,0,0,0,1],[1,1,1,1,1]]
//Output: 1
//
//
// Constraints:
//
//
// 2 <= A.length == A[0].length <= 100
// A[i][j] == 0 or A[i][j] == 1
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
final int[][] dirs = new int[][]{{-1,0},{1,0},{0,-1},{0,1}};
public int shortestBridge(int[][] A) {
int m = A.length, n = A[0].length;
boolean[][] vis = new boolean[m][n];
Queue<int[]> queue = new LinkedList<>();
boolean flag = false;
for (int i = 0; i < m; i++) {
if(flag) break;
for (int j = 0; j < n; j++) {
if(A[i][j]==1){
dfs(i,j,A,queue,vis,m,n);
flag = true;
break;
}
}
}
int level = 0;
while (!queue.isEmpty()){
int size = queue.size();
while(size-->0){
int[] cur = queue.poll();
for(int[] dir:dirs){
int nx = cur[0]+dir[0];
int ny = cur[1]+dir[1];
if(nx>=0 && ny>=0 && nx<m && ny<n && !vis[nx][ny]){
if(A[nx][ny] == 1) return level;
queue.offer(new int[]{nx,ny});
vis[nx][ny] = true;
}
}
}
level++;
}
return -1;
}
public void dfs(int x,int y, int[][] A,Queue<int[]> queue, boolean[][] vis, int m, int n){
if(x<0||y<0||x>=m||y>=n||vis[x][y]||A[x][y]==0) return;
vis[x][y] = true;
queue.offer(new int[]{x,y});
for (int i = 0; i < 4; i++) {
int nx = x + dirs[i][0];
int ny = y + dirs[i][1];
dfs(nx,ny,A,queue,vis,m,n);
}
}
}
拼多多0926笔试题第二题 bfs(最短路径)
import java.util.*;
class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int M = sc.nextInt();
char[][] map = new char[N][M];
sc.nextLine();
int begX = 0, begY = 0;
for (int i = 0; i < N; i++) {
char[] chars = sc.nextLine().toCharArray();
for (int j = 0; j < N; j++) {
map[i][j] = chars[j];
if(chars[j]=='T'){
begX = i;
begY = j;
}
}
}
boolean[][] vis = new boolean[N][M];
int[][] dir = new int[][]{{-1,0},{1,0},{0,-1},{0,1}};
Queue<int[]> q = new LinkedList<>();
q.offer(new int[]{begX,begY});
vis[begX][begY] = true;
int[][] dist = new int[N][M];
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
dist[i][j] = 30;
}
}
int minDist = Integer.MAX_VALUE;
dist[begX][begY] = 0;
boolean noWay = true;
while (!q.isEmpty()){
int[] cur = q.poll();
if(map[cur[0]][cur[1]]=='X' && dist[cur[0]][cur[1]]<minDist){
minDist = dist[cur[0]][cur[1]];
System.out.println(minDist);
noWay = false;
}
for (int i = 0; i < 4; i++) {
int[] next = new int[2];
next[0] = cur[0]+dir[i][0];
next[1] = cur[1]+dir[i][1];
if(next[0]<0||next[1]<0||next[0]>=N||next[1]>=M||map[next[0]][next[1]]=='1') continue;
if(!vis[next[0]][next[1]]){
q.add(next);
vis[next[0]][next[1]] = true;
dist[next[0]][next[1]]=Integer.min(dist[next[0]][next[1]],dist[cur[0]][cur[1]]+1);
}
}
}
if(noWay){
System.out.println(0);
}else {
System.out.println(minDist);
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if(dist[i][j]==minDist && map[i][j]=='X'){
System.out.print(i);
System.out.print(" ");
System.out.print(j);
System.out.print(" ");
}
}
}
}
}
}
以上练习就来到一个节点,难度基本达到了笔试级别,就看看能不能领会奥妙,运用自如了。
以下内容关于图论的BFS、SPFA、Dijkstra、FLOYD算法,各自有不同的使用场景和局限。
BFS只适用于无权图,如何改进?
SPFA算法(可用于负权图)
堆优化Dijkstra算法(复杂度最低)
Floyd算法
图论小结
以上截图来自北理工ACM课程Slides