算法训练营(十)深度优先搜索、广度优先搜索的实现和特性(单词接龙II为高频面试题)

 

 

 

一、基本概念

1.1 遍历搜索

在树(图/状态集)中寻找特定结点 

 

 

1.1.1 DFS 代码模板——使用栈

递归写法

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 not next_node in visited: 
			dfs(next_node, visited)

 

非递归写法

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 
	...

 

 

1.1.2 广度优先——使用队列(和层序遍历好像)

void bfs(Node* root) {
  map<int, int> visited;

  if(!root) return ;

  queue<Node*> 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 ;
}

 

 

 

二、实战题目

 

 

2.1 二叉树的层序遍历

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。

示例:
二叉树:[3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

返回其层序遍历结果:

[
  [3],
  [9,20],
  [15,7]
]

思路

只要你相信自己,平心静气的慢慢写。总能写出来的

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>>res;

        if(!root) return res;

        queue<TreeNode*>q;
        q.push(root);
        vector<int>temp;
        while(!q.empty()){
            int size = q.size();
            for(int i=0; i<size; i++){
                TreeNode* current = q.front();
                if(current != nullptr && current->left) q.push(current->left);
                if(current != nullptr && current->right) q.push(current->right);
                temp.push_back(current->val);
                q.pop();
            }
            res.push_back(temp);
            temp.clear();
        }
        return res;

    }
};

 

 

 

2.2 最小基因变化

一条基因序列由一个带有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

 

思路:

宽搜。依次start中的每个字母为“ATCG”,检测bank中是否存在该字符,如果存在,则记录下转变了多少个字母才转为该字符串的。

以此类推,当匹配到与end相同的字符串时,返回匹配的步数。

class Solution {
public:
    int minMutation(string start, string end, vector<string>& bank) {
        unordered_set<string>candidate(bank.begin(), bank.end());
        queue<pair<string, int>>q;
        q.push({start,0});

        string gene;
        int step;

        while(!q.empty()){
            if(q.front().first == end){
                return q.front().second;
            }

            gene=q.front().first;
            step = q.front().second;
            q.pop();

             for (int i = 0; i < gene.length(); i++){
                 char cur_char = gene[i];
                 for(char base:"ATCG"){
                     if(gene[i]==base) continue; //相同则无需变换
                     gene[i] = base; //修改碱基
                     if(candidate.find(gene)!=candidate.end()){
                         q.push({gene,step+1});
                         candidate.erase(gene);
                     }
                 }
                 gene[i]=cur_char;
             }
        }
        return -1;

    }
};

 

 

 

 

2.3 在每个树行中找最大值

您需要在二叉树的每一行中找到最大的值。

示例:

输入: 

          1
         / \
        3   2
       / \   \  
      5   3   9 

输出: [1, 3, 9]

思路:

层序遍历(也可以视为宽度优先搜索)。使用变量存储该层中最大的值。

class Solution {
public:
    vector<int> largestValues(TreeNode* root) {
        vector<int>res;
        if(!root)return res;

        queue<TreeNode*>q;
        q.push(root);

        while(!q.empty()){
            int size = q.size();
            int max = q.front()->val;
            for(int i=0; i<size;i++){
                TreeNode*current = q.front();
                max = q.front()->val>max?q.front()->val:max;
                if(current!=nullptr && current->left) q.push(current->left);
                if(current!=nullptr && current->right) q.push(current->right);
                q.pop();
            }
            res.push_back(max);
        }
        return res;
    }
};

 

 

 

 

 

三、课后作业

 

 

彩蛋:http://music.163.com/song?id=1317308643&userid=287398539 这首歌超好听

 

3.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 <= beginWord.length <= 10
  • endWord.length == beginWord.length
  • 1 <= wordList.length <= 5000
  • wordList[i].length == beginWord.length
  • beginWord、endWord 和 wordList[i] 由小写英文字母组成
  • beginWord != endWord
  • wordList 中的所有字符串 互不相同

思路

宽度优先搜索。

  • 新建队列,将当前队列中所有节点遍历并取出,将这些节点能走到的所有节点均推入队列,当遍历到end节点时退出bfs,否则将路径长度+1,然后继续遍历直到队列为空
  • 当判断当前节点的可到达节点时,可以循环本节点单词的所有字符,用'a' - 'z'中与原来不相等的字符替换后判断dict内是否存在该字符,若存在,则可到达该节点
for i 0:len_word //遍历整个单词
    oldchar = word[i]
    for j 'a':'z' //遍历26个字母
        if (oldchar == j)
            continue
        word[i] = j //得到新单词
        if (dict.find(word) or word == end) //若该单词在dict中或等于end
            queue.push(word)
            dict.erase(word)
     word[i] = oldchar //回溯,恢复当前单词

 使用宽搜实现了一版,最后报错超时

class Solution {
public:
    int ladderLength(string start, string end, vector<string>& wordList ) {
        unordered_set<string> dict;
        for(auto i:wordList ){
            dict.insert(i);
        }

        if(dict.find(start)==dict.end() 
        || dict.find(end)==dict.end())
            return 0;
            
        int n=start.size();
        queue<string> queue;
        int length = 2;

        if (start == end) {  //若起始单词等于终点单词,直接返回1
            return 1;
        }

        queue.push(start);  
         dict.erase(start);   //删除dict中的起点单词(若存在的话)
          while(!queue.empty()){
              int size = queue.size();
              for(int i=0; i<size;i++){
                  string word = queue.front();
                  queue.pop();
                  for(int j=0; j<n;j++){
                      char old_char = word[j];
                      for(char c = 'a'; 'c'<='z';c++){
                          if(c==old_char) continue;
                          word[j]=c;
                          if(word==end) return length;
                          if(dict.find(word)!= dict.end()){
                              queue.push(word);
                              dict.erase(word);
                          }
                      }
                      word[j]=old_char;
                  }
              }
              length++;
          }   
          return 0;

    }
};

 

 

 

 

 

3.2 单词接龙 II——高频

给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:

  1. 每次转换只能改变一个字母。
  2. 转换后得到的单词必须是字典中的单词。

说明:

  • 如果不存在这样的转换序列,返回一个空列表。
  • 所有单词具有相同的长度。
  • 所有单词只由小写字母组成。
  • 字典中不存在重复的单词。
  • 你可以假设 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" 不在字典中,所以不存在符合要求的转换序列。

难题先跳过了

 

 

 

 

3.3 岛屿数量

给你一个由 '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

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 300
  • grid[i][j] 的值为 '0' 或 '1'

 

思路:

遍历二维数组,遇到岛就将其夷为平地。 

 

未完待续   2021 03 23  最近有点堕落了,罪过罪过

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值