第19周 算法思想 搜索(BFS&DFS)

本文介绍了如何使用BFS算法解决Word Ladder II中的最短单词路径问题,并将BFS转换为DFS以寻找所有路径。此外,展示了DFS在找到矩阵中最近0的距离、最大连通区域和封闭区域填充中的应用,以及括号生成的多种解决方案,如动态规划、暴力法和深度优先搜索。

BFS

最短单词路径(输出所有路径)

把对节点的BFS转换为对路径的DFS,依然可以用层次遍历的思路解决,有两点需要注意

  • 从队列的pop出一个路径时需要把它的长度和最短路径长度做比较
  • 对visited节点的更新需要在整个层次遍历完之后进行
class Solution {
public:    
    vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
        queue<vector<string>> q;
        vector<vector<string>> ans;
        unordered_set<string> wordSet;
        unordered_set<string> visitedSet;
        
        for(string word: wordList)
            wordSet.insert(word);
        
        q.push({beginWord});
        
        int shortest  = 1005;
        
        while(!q.empty()){
            vector<string> visited;
            
            int size = q.size();
                        
            while(size--){
                vector<string> curr = q.front();
                q.pop();
            
                if(curr.back() == endWord){
                    if(curr.size() < shortest){  // 清除非最短路径
                        for(int i = 0;i < ans.size(); i++)
                            ans[i].clear();
                        ans.clear();                    
                        ans.push_back(curr);
                        shortest = curr.size();
                    }
                    else if(curr.size() == shortest){
                        ans.push_back(curr);
                    }
                }

                if(curr.size() > shortest)
                    continue;

                string last = curr.back();

                for(int i = 0;i < last.size(); i++){
                    string tmp = last;
                    for(char c: "abcdefghijklmnopqrstuvwxyz"){
                        tmp[i] = c;
                        if(c != last[i] && wordSet.find(tmp) != wordSet.end() && (visitedSet.find(tmp) == visitedSet.end() || tmp == endWord)){
                            vector<string> curr_tmp = curr;
                            curr_tmp.push_back(tmp);
                            q.push(curr_tmp);
                            visited.push_back(tmp);                            
                        }
                    }    
                }
            }
            
            for(auto s: visited)
               visitedSet.insert(s);
            
        }
        
        return ans;
        
        
    }
};

到离得最近的0的距离

typedef pair<int,int> P;
class Solution {
public:
    vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
        int n = mat.size();
        int m = mat[0].size();
        
        queue<P> q;
        
        vector<vector<int>> ans;
        for(int i = 0;i < n; i++){
            vector<int> tmp(m, 99999);
            ans.push_back(tmp);
        }
        
        for(int i = 0;i < n; i++){
            for(int j = 0;j < m; j++){
                if(mat[i][j] == 0){
                    ans[i][j] = 0;
                    q.push({i,j});
                }
            }
        }
        
        int dx[] = {0,0,1,-1};
        int dy[] = {1,-1,0,0};
        
        while(!q.empty()){
            P u = q.front();
            q.pop();
            
            int x = u.first, y = u.second;
            for(int i = 0;i < 4; i++){
                int nx = x + dx[i];
                int ny = y + dy[i];
                if(nx >= 0 && nx < n && ny >= 0 && ny < m && ans[nx][ny] > ans[x][y] + 1){
                    ans[nx][ny] = ans[x][y] + 1;
                    q.push({nx, ny});
                }
            }
        }
        return ans;     
    }
};

DFS

查找最大的连通面积

class Solution {
public:
    int m, n, curr;
    int dx[4] = {0,0,1,-1};
    int dy[4] = {1,-1,0,0};
    
    void dfs(vector<vector<int>>& grid, int x, int y){
        grid[x][y] = 0;
        curr += 1;
        for(int i = 0;i < 4; i++){
            int nx = x + dx[i];
            int ny = y + dy[i];
            if(nx >= 0 && nx < n && ny >= 0 && ny < m && grid[nx][ny] == 1){
                dfs(grid, nx, ny);
            }
        }
    }
    
    int maxAreaOfIsland(vector<vector<int>>& grid) {
        n = grid.size();
        m = grid[0].size();
        
        int ans = 0;
        
        for(int i = 0;i < n; i++){
            for(int j = 0;j < m; j++){
                if(grid[i][j] == 1){
                    curr = 0;
                    dfs(grid, i, j);
                    if(curr > ans)
                        ans = curr;
                }
            }
        }
        
        return ans;
        
    }
};

填充封闭区域

从边界上的“缺口”开始DFS,剩下没有遍历到的”缺口“就是被包围的。

class Solution {
public:
    int dx[4] = {0,0,1,-1};
    int dy[4] = {1,-1,0,0};
    int n, m;
    
    void dfs(vector<vector<char>>& board, int x, int y){
        board[x][y] = 'Z';
        for(int i = 0;i < 4; i++){
            int nx = x + dx[i];
            int ny = y + dy[i];
            if(nx >= 0 && nx < n && ny >= 0 && ny < m && board[nx][ny] == 'O')
                dfs(board, nx, ny);
        }
    }
    
    void solve(vector<vector<char>>& board) {
        n = board.size();
        m = board[0].size();
        
        for(int i = 0;i < n; i++){
            if(board[i][0] == 'O')
                dfs(board, i, 0);
            if(board[i][m-1] == 'O')
                dfs(board, i, m-1);
        }
        for(int i = 0;i < m; i++){
            if(board[0][i] == 'O')
                dfs(board, 0, i);
            if(board[n-1][i] == 'O')
                dfs(board, n-1, i);
        }
        
        for(int i = 0;i < n; i++){
            for(int j = 0;j < m; j++){
                if(board[i][j] == 'Z')
                    board[i][j] = 'O';
                else if(board[i][j] == 'O')
                    board[i][j] = 'X';
            }
        }
        
    }
};

括号生成

三种方法

  • 动态规划法:求解包含n对括号的字符串时,如果包好0到n-1对括号的解都知道,那就可以直接得到n对括号的解。设想n对括号的解集包括两部分,一部分是可以拆为两部分的,就是左边是完整的k对括号,右边是完整的n-k对括号,另一部分是不可以拆分的,也就是最外层有一对括号,括号里面是n-1对括号。为了避免生成过程中产生重复,所以用set记录生成的所有括号。
  • 暴力法:这道题一开始想到的就是动态规划法,后来才知道可以DFS,直接写DFS感觉优点理解不了,所以就实现了下暴力法方便自己理解。暴力法就是生成有左右括号组成的长度为2n的字符串,然后再挑选其中的合法字符串。合法的定义就是:(的数量要一直大于等于)的数量,并且遍历完成后左右括号的数量相等且都为n。
  • DFS:在搜索的过程中就判断是否合法,left,right变量记录已经用掉的左右括号数量,left==0 && right == 0表明用完了则curr就是答案,否则只有在left<=right的时候也就是用掉的左括号多于右括号的时候才继续后续的添加。
class Solution {
public:
    // 动态规划的思想,求解子问题的解,用子问题解决原问题
    vector<string> generateParenthesis_dp(int n) 
    {
        unordered_set<string> ans[n+1];
        for(int i = 1;i <= n; i++)
        {
            if(i == 1)
                ans[i].insert("()");
            else{
                for(auto it : ans[i-1])
                   ans[i].insert("("+it+")");
                
                for(int j = 1;j <= i-1; j++){
                    for(auto& it1 : ans[j]){
                        for(auto& it2 : ans[i-j])
                            ans[i].insert(it1+it2);
                    }
                }
            }
        }
        
        vector<string> ans_v;
        for(auto it : ans[n])
            ans_v.push_back(it);

        return ans_v;
    }
    
    // 暴力解法,生成所有字符串然后挑合法的
    vector<string> generateParenthesis_BruteForce(int n){
        vector<string> all, ans;
        dfs_BruteForce("", n * 2, all);
        cout << all.size() << endl;
        for(int i = 0;i < all.size(); i++){
            // cout << all[i] << endl;
            if(valid(all[i]))
                ans.push_back(all[i]);
        }
        return ans;
    }
    
    void dfs_BruteForce(string curr, int k, vector<string>& all){
        if(k == 1){
            all.push_back(curr+"(");
            all.push_back(curr+")");
        }
        else{
            dfs_BruteForce(curr+"(", k-1, all);
            dfs_BruteForce(curr+")", k-1, all);
        }
    }
    
    bool valid(string& s){
        int left = 0, right = 0;
        for(int i = 0;i < s.size(); i++){
            if(s[i] == '(')
                left += 1;
            else if(s[i] == ')')
                right += 1;
            if(left < right)
                return false;
        }
        return left == right && left == s.size() / 2;
    }
    
    // DFS,在暴力求解的基础上对搜索的目标进行剪枝
    vector<string> generateParenthesis(int n){
        vector<string> ans;
        
        dfs("", n, n, ans);
        
        return ans;
    }
    
    void dfs(string curr, int left, int right, vector<string>& ans){
        if(left == 0 && right == 0)
            ans.push_back(curr);
        
        if(left <= right && right > 0)
            dfs(curr + ")", left, right-1, ans);
        
        if(left <= right && left > 0)
            dfs(curr + "(", left-1, right, ans);
    }
    
};
<think>嗯,用户想了解在C++比赛中常用的算法和数据结构,比如枚举、排序、搜索、贪心等等。我需要详细解释每个部分,还要用中文,结构清晰。首先,我得确保每个算法都分点介绍,包括基本概念、应用场景、C++实现要点和例题。用户可能正在准备竞赛,需要这些知识点来提升解题能力。 首先,枚举,也就是暴力法,要讲清楚它的适用情况,比如数据量小的时候。然后是排序算法,用户提到了冒泡、选择、插入,可能还需要补充归并和快排,虽然用户后面也提到了归并和快排,但可能在排序部分需要分开说明。接下来是搜索,BFS和DFS的区别和应用,比如最短路径用BFS,回溯用DFS。然后是贪心算法,强调局部最优但可能不是全局最优,需要例子比如活动选择。 模拟题需要仔细处理步骤,可能容易出错,比如日期计算。二分法适用于有序数据,比如找最小值。高精度处理大数,用数组存储。数据结构部分,栈和队列的应用,比如括号匹配和BFS。归并和快排的分治思想,时间复杂度的差异。记忆化搜索用于优化递归,比如斐波那契数列。图论的LCA问题,最近公共祖先,用倍增法。数学中的容斥原理,计算集合的并集。ST表用于区间最值查询,比如RMQ问题。 需要检查每个算法的实现要点,比如循环变量、边界条件。例如冒泡排序的双层循环,快速排序的递归实现。还要注意C++的代码片段是否正确,比如使用vector还是数组,是否需要头文件。可能用户需要代码示例,但用户没有明确要求,所以可能简要给出关键部分。 还要考虑用户可能遇到的常见错误,比如二分法的终止条件,高精度加法时的进位处理。数据结构部分,栈和队列的STL用法,或者自己实现。ST表的预处理步骤和查询函数。另外,例题推荐可以帮助用户练习,但可能用户需要具体的题目名称,比如LeetCode或洛谷的题号。 需要确保每个部分结构一致,先介绍概念,再讲应用场景,然后实现要点,最后例题。语言要简洁,避免冗长。可能用户时间有限,需要快速掌握重点。还要注意术语的准确性,比如BFS和DFS的应用场景区别,贪心算法的适用条件。 最后,检查是否有遗漏的部分,用户提到的所有关键词是否都覆盖到了,比如模拟、高精度、ST表、容斥原理等。确保每个子项都有对应的解释,并且逻辑连贯,帮助用户逐步构建知识体系。可能还需要提醒一些优化技巧,比如在枚举中剪枝,或者高精度运算时的效率问题。</think>我将分模块为您介绍C++竞赛中常用算法与数据结构,按实现难度和竞赛出现频率分层讲解: 一、基础算法(必须掌握) 1. 枚举(暴力法) - 概念:遍历所有可能解 - 应用:小数据量(n≤1e4)、状态有限问题 - 实现要点:循环嵌套层数控制,剪枝优化 - 例题:百钱买百鸡问题 2. 基础排序(时间复杂度O()) ```cpp // 冒泡排序核心代码 for(int i=0; i<n-1; ++i) for(int j=0; j<n-i-1; ++j) if(a[j] > a[j+1]) swap(a[j], a[j+1]); // 插入排序核心代码 for(int i=1; i<n; ++i){ int key = a[i], j = i-1; while(j>=0 && a[j]>key){ a[j+1] = a[j]; j--; } a[j+1] = key; } ``` 二、中级算法(区域赛常用) 3. 二分查找(时间复杂度O(logn)) - 核心思想:有序序列的快速定位 - 实现要点:左右边界处理,循环终止条件 ```cpp int l=0, r=n-1; while(l <= r){ int mid = (l+r)/2; if(a[mid] >= target) r = mid-1; else l = mid+1; } return l; // 第一个>=target的位置 ``` 4. 简单数据结构 - 栈:括号匹配、表达式求值 ```cpp stack<int> s; s.push(1); s.pop(); s.top(); ``` - 队列:BFS基础结构 ```cpp queue<int> q; q.push(1); q.pop(); q.front(); ``` 三、高阶算法(省赛/区域赛关键题) 5. 分治排序(时间复杂度O(nlogn)) - 归并排序:稳定排序,逆序对计算 ```cpp void merge_sort(int l, int r){ if(l >= r) return; int mid = (l+r)/2; merge_sort(l, mid); merge_sort(mid+1, r); // 合并两个有序数组 } ``` - 快速排序:实际应用更广泛 ```cpp void quick_sort(int l, int r){ if(l >= r) return; int i=l-1, j=r+1, x=a[(l+r)>>1]; while(i < j){ do i++; while(a[i] < x); do j--; while(a[j] > x); if(i < j) swap(a[i], a[j]); } quick_sort(l, j); quick_sort(j+1, r); } ``` 6. 记忆化搜索 - 核心思想:递归+结果缓存 - 应用:斐波那契数列、网格路径计数 ```cpp int memo[MAXN]; int dfs(int state){ if(memo[state] != -1) return memo[state]; // 计算逻辑 return memo[state] = res; } ``` 四、进阶专题(国赛/ICPC级) 7. 图论-LCA(最近公共祖先) - 倍增法实现(时间复杂度O(nlogn)预处理,O(logn)查询) ```cpp int up[MAXN][20]; // 预处理祖先表 int lca(int u, int v){ if(depth[u] < depth[v]) swap(u,v); // 先跳到同一深度 for(int i=19; i>=0; --i) if(depth[up[u][i]] >= depth[v]) u = up[u][i]; if(u == v) return u; // 共同上跳 for(int i=19; i>=0; --i) if(up[u][i] != up[v][i]) u=up[u][i], v=up[v][i]; return up[u][0]; } ``` 8. ST表(区间最值查询) ```cpp int st[MAXN][20]; void init(){ for(int j=1; (1<<j)<=n; ++j) for(int i=1; i+(1<<j)-1<=n; ++i) st[i][j] = max(st[i][j-1], st[i+(1<<(j-1))][j-1]); } int query(int l, int r){ int k = log2(r-l+1); return max(st[l][k], st[r-(1<<k)+1][k]); } ``` 五、特殊技巧 9. 容斥原理 - 公式表达:$$|A_1 \cup ... \cup A_n| = \sum|A_i| - \sum|A_i \cap A_j| + ... + (-1)^{n+1}|A_1 \cap ... \cap A_n|$$ - 应用:求多个集合的并集大小 10. 高精度计算 - 实现要点:字符串处理、进位处理 ```cpp vector<int> add(vector<int>& a, vector<int>& b){ vector<int> c; int t = 0; for(int i=0; i<a.size()||i<b.size(); ++i){ if(i < a.size()) t += a[i]; if(i < b.size()) t += b[i]; c.push_back(t%10); t /= 10; } if(t) c.push_back(1); return c; } ``` 建议练习顺序:基础排序 → 二分/模拟 → 数据结构 → 分治算法 → 图论/数学。实际比赛中要注重代码模板的整理和边界条件处理,推荐配合《算法竞赛进阶指南》系统学习。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值