算法08-深度优先搜索和广度优先搜索

《算法练习-文章汇总》

深度优先搜索

递归写法
Python
 #Python
 visited = set() 
 def dfs(node, visited):    
 if node in visited: # terminator    
 # already visited      
 return     visited.add(node)   
# process current node here.
    ... 
for next_node in node.children(): 
if next_node not in visited: 
dfs(next_node, visited)

C++
//C/C++
//递归写法:
map visited;
void dfs(Node* root) {
 // terminator  if (!root) return ; 
  if (visited.count(root->val)) {    
  // already visited    
  return ;  
  }  
  visited[root->val] = 1; 
   // process current node here.  
    // ...  
    for (int i = 0; i < root->children.size(); ++i) {            dfs(root->children[i]);  
    }  
    return ;
    }
    
Java
//Java    
public List> levelOrder(TreeNode root) {        List> allResults = new ArrayList<>();        if(root==null{ 
 return allResults;       
}        
travel(root,0,allResults); 
return allResults;    
}    
private void travel(TreeNode root,int level,List> results){ 
if(results.size()==level{  
results.add(new ArrayList<>());        
}        
results.get(level).add(root.val); 
if(root.left!=null){    
travel(root.left,level+1,results);    
}        
if(root.right!=null){            
travel(root.right,level+1,results); 
}    
}

JavaScript
//JavaScript
const visited = new Set()const dfs = node => {  
if (visited.has(node)) 
return 
visited.add(node)  
dfs(node.left)  
dfs(node.right)
}

非递归写法
Python
#Python
def DFS(self, tree):    
if tree.root is None:       
return []   
visited, stack = [], [tree.root]
    while stack:        
    node = stack.pop()      
    visited.add(node)       
    process (node)      
    nodes = generate_related_nodes(node) 
    stack.push(nodes)   
    # other processing work     
    ...
    
C++
//C/C++//非递归写法:
void dfs(Node* root) {
map visited; 
 if(!root) return ; 
  stack stackNode;
    stackNode.push(root);  
    while (!stackNode.empty()) {   
     Node* node = stackNode.top(); 
        stackNode.pop();  
          if (visited.count(node->val)) 
          continue;    
          visited[node->val] = 1;   
for (int i = node->children.size() - 1; i >= 0; --i) {      
    stackNode.push(node->children[i]);   
   } 
} 
  return ;
}

广度优先遍历

# Pythondef BFS(graph, start, end):   
  visited = set()   
  queue = []    
  queue.append([start]) 
  while queue:      
  node = queue.pop()
  visited.add(node)
process(node)       
     nodes = generate_related_nodes(node)       queue.push(nodes)   
     # other processing work    
     ...
     
//Java
public class TreeNode {   
 int val;   
 TreeNode left;    
 TreeNode right;    
 TreeNode(int x) { 
        val = x;    
  }
 }
public List> levelOrder(TreeNode root) {    
List> allResults = new ArrayList<>(); 
   if (root == null) {        
   return allResults;   
 }    
Queue nodes = new LinkedList<>();  
nodes.add(root);    
while (!nodes.isEmpty()) {  
 int size = nodes.size();   
 List results = new ArrayList<>(); 
 for (int i = 0; i < size; i++) {      
 TreeNode node = nodes.poll(); 
 results.add(node.val);   
 if (node.left != null){                
 nodes.add(node.left);     
}           
 if (node.right != null) {               
  nodes.add(node.right);   
  }        
 } 
 allResults.add(results);   
}    
return allResults;
}

// C/C++
void bfs(Node* root) { 
map visited; 
if(!root) return ;  
  queue queueNode;  
  queueNode.push(root); 
   while (!queueNode.empty()) {  
     Node* node = queueNode.top(); 
        queueNode.pop(); 
           if (visited.count(node->val)) 
           continue;    
           visited[node->val] = 1;
for (int i = 0; i < node->children.size(); ++i) {    
   queueNode.push(node->children[i]);    } 
} 
return ;
}

//JavaScript
const bfs = (root) => { 
 let result = [],
 queue = [root]
 while (queue.length > 0) { 
 let level = [], 
 n = queue.length
for (let i = 0; i < n; i++) { 
let node = queue.pop()
level.push(node.val) 
if (node.left) 
queue.unshift(node.left)  
if (node.right) 
queue.unshift(node.right)  
}    
result.push(level) 
} 
return result
};

二叉树的层序遍历(字节、亚马逊、微软)

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例:
二叉树:[3,9,20,null,null,15,7]
3
/
9 20
/
15 7
返回其层序遍历结果:
[
[3],
[9,20],
[15,7]
]

    //二叉树的层序遍历
    public List> levelOrder(TreeNode root) {
           List> results = new ArrayList<>();
           if (root == null)return results;
           Queue queue = new LinkedList<>();
           queue.offer(root);
           while (!queue.isEmpty()){
               List list = new ArrayList<>();
               int currentQueueSize = queue.size();
               for (int i = 0;i

括号生成(字节、亚马逊、微软)

   public List results;
    public List generateParenthesis(int n) {
        results = new ArrayList<>();
        generator(0,0,n,"");
       return results;
    }
    public void generator(int left,int right,int n,String str){
        //terminator
        if (left == n && right == n){
            results.add(str);
            return;
        }
        //drill down
        if (left < n)
        generator(left+1,right,n,str+"(");
        if (left > right)
        generator(left,right+1,n,str+")");
    }

最小基因变化

一条基因序列由一个带有8个字符的字符串表示,其中每个字符都属于 "A", "C", "G", "T"中的任意一个。
假设我们要调查一个基因序列的变化。一次基因变化意味着这个基因序列中的一个字符发生了变化。
例如,基因序列由"AACCGGTT" 变化至 "AACCGGTA" 即发生了一次基因变化。
与此同时,每一次基因变化的结果,都需要是一个合法的基因串,即该结果属于一个基因库。
现在给定3个参数 — start, end, bank,分别代表起始基因序列,目标基因序列及基因库,请找出能够使起始基因序列变化为目标基因序列所需的最少变化次数。如果无法实现目标变化,请返回 -1。
注意:
起始基因序列默认是合法的,但是它并不一定会出现在基因库中。
如果一个起始基因序列需要多次变化,那么它每一次变化之后的基因序列都必须是合法的。
假定起始基因序列与目标基因序列是不一样的。
示例 1:
start: "AACCGGTT"
end: "AACCGGTA"
bank: ["AACCGGTA"]
返回值: 1
示例 2:
start: "AACCGGTT"
end: "AAACGGTA"
bank: ["AACCGGTA", "AACCGCTA", "AAACGGTA"]
返回值: 2
示例 3:
start: "AAAAACCC"
end: "AACCCCCC"
bank: ["AAAACCCC", "AAACCCCC", "AACCCCCC"]
返回值: 3

    //最小基因变化
    public int minMutation(String start, String end, String[] bank) {
        //bank数组变为哈希set集合
       HashSet set = new HashSet<>(Arrays.asList(bank));
       //bank数组中是否包含目标基因,若不包含直接返回
       if (!set.contains(end)) return -1;
       //变化的基因数组
       char[] fours = {'A','C','G','T'};
       //广度优先搜索队列
       Queue queue = new LinkedList<>();
       //start放入队列中
       queue.offer(start);
       //变化的次数
       int count = 0;
       while (!queue.isEmpty()){
           count++;
           for (int k = queue.size();k>0;k--){
               //取出队列中的字符串变为字符数组
               char[] tempChars = queue.poll().toCharArray();
               //数组长度遍历
               for (int i = 0,size = tempChars.length;i

在每个树行中找最大值(微软、亚马逊、Facebook)

您需要在二叉树的每一行中找到最大的值。
示例:
输入:

      1
     / \
    3   2
   / \   \  
  5   3   9 

输出: [1, 3, 9]

    public List largestValues(TreeNode root) {
        List ans = new ArrayList();
        if (root == null) return new ArrayList<>();
        Queue queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            List list = new ArrayList<>();
            int currentQueueSize = queue.size();
            int max = Integer.MIN_VALUE;
            for (int i=0;i max ? nodes.val:max;
                if (nodes.left!=null){
                    queue.offer(nodes.left);
                }
                if (nodes.right!=null){
                    queue.offer(nodes.right);
                }
            }
            ans.add(max);
        }
        return ans;
    }

岛屿数量(亚马逊)

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
输出:1
示例 2:
输入:grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出:3

   int m = 0;//行数
    int n = 0;//列数
    public int numIslands(char[][] grid) {
        m = grid.length;
        if (m == 0) return 0;
        n = grid[0].length;
        int count = 0;//岛屿数量
        for (int i = 0;i=m || j>=n || grid[i][j] != '1')return;//深度优先遍历,i>=m、j>=n
        grid[i][j] = '0';
        DFS(grid,i-1,j);
        DFS(grid,i+1,j);
        DFS(grid,i,j-1);
        DFS(grid,i,j+1);
    }

单词接龙(亚马逊)

字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:
序列中第一个单词是 beginWord 。
序列中最后一个单词是 endWord 。
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典 wordList 中的单词。
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。
示例 1:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
示例 2:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
输出:0
解释:endWord "cog" 不在字典中,所以无法进行转换。

注意点:此题要在基因序列的基础上再加1,求的是长度"hit" -> "hot" -> "dot" -> "dog" -> "cog"长度为5,变化次数为4

image.png
public int ladderLength(String beginWord, String endWord, List wordList) {
        //字符串数组转哈希表
        HashSet set = new HashSet<>(wordList);
        //若目标字符串数组中不包含endWord
        if (!set.contains(endWord)) return 0;
        //最短路径次数为0
        int count = 0;
        //队列
        Queue queue  = new LinkedList<>();
        queue.offer(beginWord);
        while (!queue.isEmpty()){
            count++;
            for (int k = queue.size();k>0;k--){
                //取出队列中的字符数组
                char[] tempString = queue.poll().toCharArray();
                for (int i=0;i

单词接龙II(微软、亚马逊、Facebook)

给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
每次转换只能改变一个字母。
转换后得到的单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回一个空列表。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出:
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
示例 2:
输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
输出: []
解释: endWord "cog" 不在字典中,所以不存在符合要求的转换序列。

        List> results = new ArrayList<>();
        //字符串数组转哈希表
        HashSet set = new HashSet<>(wordList);
        //若目标字符串数组中不包含endWord
        if (!set.contains(endWord)) return results;
        //队列
        Queue> queue  = new LinkedList<>();
        queue.offer(new ArrayList<>(Arrays.asList(beginWord)));
        //已经访问过的单词
        Set visited = new HashSet<>();
        visited.add(beginWord);
        // 是否到达符合条件的层:如果该层添加的某一单词符合目标单词,则说明截止该层的所有解为最短路径,停止循环
        Boolean flag = false;
        while (!queue.isEmpty() && !flag){
            // 该层添加的所有元素:每层必须在所有结果都添加完新的单词之后,再将这些单词统一添加到已使用单词集合
            // 如果直接添加到 visited 中,会导致该层本次结果添加之后的相同添加行为失败
            // 如:该层遇到目标单词,有两条路径都可以遇到,但是先到达的将该单词添加进 visited 中,会导致第二条路径无法添加
            Set subVisited = new HashSet<>();
            for (int k = queue.size();k>0;k--){
                //取出队列中的字符数组
                List path = queue.poll();
                // 获取该路径上一层的单词
                char[] tempString = path.get(path.size()-1).toCharArray();
                // 寻找该单词的下一个符合条件的单词
                for (int i=0;i pathList = new ArrayList<>(path);
                            pathList.add(String.valueOf(tempString));
                             //相等即返回 如果该单词是目标单词:将该路径添加到结果集中,查询截止到该层
                             if (String.valueOf(tempString).equals(endWord)){
                                 flag = true;
                                 results.add(pathList);
                             }
                             //将该路径添加到该层队列中
                             queue.add(pathList);
                             // 将该单词添加到该层已访问的单词集合中
                             subVisited.add(String.valueOf(tempString));
                        }
                    }
                    //还原字符
                    tempString[i] = tempChar;
                }
            }
            // 将该层所有访问的单词添加到总的已访问集合中
            visited.addAll(subVisited);
        }
        return results;

扫雷游戏(亚马逊、Facebook)

让我们一起来玩扫雷游戏!

给定一个代表游戏板的二维字符矩阵。 'M' 代表一个未挖出的地雷,'E' 代表一个未挖出的空方块,'B' 代表没有相邻(上,下,左,右,和所有4个对角线)地雷的已挖出的空白方块,数字('1' 到 '8')表示有多少地雷与这块已挖出的方块相邻,'X' 则表示一个已挖出的地雷。

现在给出在所有未挖出的方块中('M'或者'E')的下一个点击位置(行和列索引),根据以下规则,返回相应位置被点击后对应的面板:

如果一个地雷('M')被挖出,游戏就结束了- 把它改为 'X'。
如果一个没有相邻地雷的空方块('E')被挖出,修改它为('B'),并且所有和其相邻的未挖出方块都应该被递归地揭露。
如果一个至少与一个地雷相邻的空方块('E')被挖出,修改它为数字('1'到'8'),表示相邻地雷的数量。
如果在此次点击中,若无更多方块可被揭露,则返回面板。
示例 1:
输入:
[['E', 'E', 'E', 'E', 'E'],
['E', 'E', 'M', 'E', 'E'],
['E', 'E', 'E', 'E', 'E'],
['E', 'E', 'E', 'E', 'E']]
Click : [3,0]
输出:
[['B', '1', 'E', '1', 'B'],
['B', '1', 'M', '1', 'B'],
['B', '1', '1', '1', 'B'],
['B', 'B', 'B', 'B', 'B']]
解释:


image.png

示例 2:
输入:
[['B', '1', 'E', '1', 'B'],
['B', '1', 'M', '1', 'B'],
['B', '1', '1', '1', 'B'],
['B', 'B', 'B', 'B', 'B']]
Click : [1,2]
输出:
[['B', '1', 'E', '1', 'B'],
['B', '1', 'X', '1', 'B'],
['B', '1', '1', '1', 'B'],
['B', 'B', 'B', 'B', 'B']]
解释:


image.png

注意:
输入矩阵的宽和高的范围为 [1,50]。
点击的位置只能是未被挖出的方块 ('M' 或者 'E'),这也意味着面板至少包含一个可点击的方块。
输入面板不会是游戏结束的状态(即有地雷已被挖出)。
简单起见,未提及的规则在这个问题中可被忽略。例如,当游戏结束时你不需要挖出所有地雷,考虑所有你可能赢得游戏或标记方块的情况。
int[] dirX = {0, 1, 0, -1, 1, 1, -1, -1};
    int[] dirY = {1, 0, -1, 0, 1, -1, 1, -1};

    public char[][] updateBoard(char[][] board, int[] click) {
        int x = click[0], y = click[1];
        if (board[x][y] == 'M') {
            // 规则 1
            board[x][y] = 'X';
        } else{
            dfs(board, x, y);
        }
        return board;
    }

    public void dfs(char[][] board, int x, int y) {
        int cnt = 0;
        for (int i = 0; i < 8; ++i) {
            int tx = x + dirX[i];
            int ty = y + dirY[i];
            if (tx < 0 || tx >= board.length || ty < 0 || ty >= board[0].length) {
                continue;
            }
            // 不用判断 M,因为如果有 M 的话游戏已经结束了
            if (board[tx][ty] == 'M') {
                ++cnt;
            }
        }
        if (cnt > 0) {
            // 规则 3
            board[x][y] = (char) (cnt + '0');
        } else {
            // 规则 2
            board[x][y] = 'B';
            for (int i = 0; i < 8; ++i) {
                int tx = x + dirX[i];
                int ty = y + dirY[i];
                // 这里不需要在存在 B 的时候继续扩展,因为 B 之前被点击的时候已经被扩展过了
                if (tx < 0 || tx >= board.length || ty < 0 || ty >= board[0].length || board[tx][ty] != 'E') {
                    continue;
                }
                dfs(board, tx, ty);
            }
        }
    }

你可能感兴趣的:(算法08-深度优先搜索和广度优先搜索)