BFS,DFS,动态规划的总结

 提到BFS,DFS大家第一个想到的就是图论的基本方法,然而对于一些非图论的题目,也可以通过构造成图的形式使用其解题。更为重要的是,我认为BFS和DFS是一种类似于暴力的方法,遇到递推下一步问题的题目,几乎就可以用他们两者中的一种解题。多说无益直接上题目。

1.DFS

Given a 2D board and a word, find if the word exists in the grid.
The word can be constructed from letters of sequentially adjacent cell, where "adjacent" cells are those
horizontally or vertically neighbouring. The same letter cell may not be used more than once.
For example, Given board =
[
["ABCE"],
["SFCS"],
["ADEE"]
]
word = "ABCCED", -> returns true,
word = "SEE", -> returns true,
word = "ABCB", -> returns false.
链接为:https://leetcode.com/problems/word-search/description/

典型DFS题,选择board中其中一个字母为首字母,匹配word,匹配成功选择下一个在匹配,否则返回。匹配下一个有四种情况:左右上下,每种情况成功时,再按上述步骤递归处理。需要注意的是,去重问题,就是不要访问以前访问过的点。代码:

 bool exist(vector<vector<char>>& board, string word) {
    int row = board.size(), col = board[0].size();
	vector<vector<bool>>traversed(row, vector<bool>(col, false));
	bool res;
	for (int i = 0; i<row; i++)
		for (int j = 0; j<col; j++)
		{
			if(dfs(board, word, i, j, 0, traversed))
            return true;
		}
	return false;
}
bool dfs(vector<vector<char>>&board, string &word, int row, int col, int pos, vector<vector<bool>>&traversed)
{
	if (pos == word.length())
		return true;
	if (col < 0 || col >= board[0].size()||row < 0 || row >= board.size())
		return false;                              //超出边界返回错误
    if(traversed[row][col])
        return false;                             //访问过返回错误
    if(board[row][col]!=word[pos])
        return false;                            //匹配不上返回错误。

		traversed[row][col] = true;             //匹配成功,标记已访问过
   
	bool res=dfs(board, word, row, col + 1, pos + 1, traversed) || dfs(board, word, row, 
    col - 1, pos + 1, traversed) || dfs(board, word, row + 1, col, pos + 1, traversed) || 
    dfs(board, word, row - 1, col, pos + 1, traversed); //结果为四种情况DFS后的结果
	traversed[row][col] = false;                        //回溯到本节点,还原是否访问过
    return res;
    }

2.BFS 

先构造相邻接点,将所有相邻接点构造完之后,在本层节点进行操作,再继续搜索下一层。

题目:

Given two words (beginWord and endWord), and a dictionary's word list, find the length of shortest transformation sequence from beginWord to endWord, such that:

  1. Only one letter can be changed at a time.
  2. Each transformed word must exist in the word list. Note that beginWord is not a transformed word.

Note:

  • Return 0 if there is no such transformation sequence.
  • All words have the same length.
  • All words contain only lowercase alphabetic characters.
  • You may assume no duplicates in the word list.
  • You may assume beginWord and endWord are non-empty and are not the same.

Example 1:

Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

Output: 5

Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
return its length 5.

Example 2:

Input:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]

Output: 0

Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.

链接:https://leetcode.com/problems/word-ladder/description/

每层搜索字母相差一个的单词,代码如下:

int ladderLength(string beginWord, string endWord, unordered_set<string>& wordDict) {
        wordDict.insert(endWord);                       //将结束单词添加其中,有助查找。
        queue<string> toVisit;                          //相邻接点
        addNextWords(beginWord, wordDict, toVisit);
        int dist = 2;                                   //已经构造两层
        while (!toVisit.empty()) {                      
            int num = toVisit.size();
            for (int i = 0; i < num; i++) {
                string word = toVisit.front();
                toVisit.pop();
                if (word == endWord) return dist;
                addNextWords(word, wordDict, toVisit);
            }
            dist++;
        }
    }
private:
    void addNextWords(string word, unordered_set<string>& wordDict, queue<string>& toVisit)                                               //将符合条件的单词构造成相邻接点
{
        wordDict.erase(word);                          //去重
        for (int p = 0; p < (int)word.length(); p++) {
            char letter = word[p];
            for (int k = 0; k < 26; k++) { 
                word[p] = 'a' + k;
                if (wordDict.find(word) != wordDict.end()) {
                    toVisit.push(word);
                    wordDict.erase(word);
                }
            }
            word[p] = letter;
        } 
    } 

3.动态规划(Dynamic Programming,简称DP),

     如果要求一个问题的最优解(通常是最大值或者最小值),而且该问题能够分解成若干个子问题,并且小问题之间也存在重叠的子问题,则考虑采用动态规划。

使用动态规划特征: 

1. 求一个问题的最优解 
2. 大问题可以分解为子问题,子问题还有重叠的更小的子问题 
3. 整体问题最优解取决于子问题的最优解(状态转移方程) 
4. 从上往下分析问题,从下往上解决问题 
5. 讨论底层的边界问题

上题目:Given a string s, partition s such that every substring of the partition is a palindrome.
Return the minimum cuts needed for a palindrome partitioning of s.
For example, given s = ”aab”,
Return 1 since the palindrome partitioning [”aa”,”b”] could be produced using 1 cut

链接:

分析:

     定义状态 f(i,j) 表示区间 [i,j] 之间最小的 cut 数,则状态转移方程为f(i; j) = min ff(i; k) + f(k + 1; j)g ; i ≤ k ≤ j; 0 ≤ i ≤ j < n
这是一个二维函数,实际写代码比较麻烦。所以要转换成一维 DP。如果每次,从 i 往右扫描,每找到一个回文就算一次 DP 的话,就可以转换为 f(i)= 区间 [i, n-1] 之间最小的 cut 数, n 为字符串长度,则状态转移方程为f(i) = min ff(j + 1) + 1g ; i ≤ j < n
一个问题出现了,就是如何判断 [i,j] 是否是回文?每次都从 i 到 j 比较一遍?太浪费了,这里也是一个 DP 问题。
定义状态 P[i][j] = true if [i,j] 为回文,那么P[i][j] = str[i] == str[j] && P[i+1][j-1]
  代码:

int minCut(string s) {
    const int n = s.size();
    int f[n+1];                            //最小分割数数组
    bool p[n][n];                         //s{i,j}是否为回文串
    fill_n(&p[0][0], n * n, false);
   
    for (int i = 0; i <= n; i++)
    f[i] = n - 1 - i; 
    for (int i = n - 1; i >= 0; i--) {
        for (int j = i; j < n; j++) {
            if (s[i] == s[j] && (j - i < 2 || p[i + 1][j - 1])) {
                p[i][j] = true;
                f[i] = min(f[i], f[j + 1] + 1);
                }
            }
        }
        return f[0];
    }
};

猜你喜欢

转载自blog.csdn.net/weixin_37519761/article/details/81155046