搜索算法——广度搜索总结

本文主要探讨了搜索算法中的深度优先搜索(DFS)和广度优先搜索(BFS)。作者通过分析不同方法,包括DFS为主BFS为辅、双向搜索以及结合其他策略的解题思路,详细阐述了这些算法在解决复杂问题中的应用。此外,文章还涵盖了理论知识、经典难题及简单问题的解决方案,并提供了一些典型题目的代码示例。
摘要由CSDN通过智能技术生成

学习资料:力扣、《LeetCode 101 - A LeetCode Grinding Guide (C   Version)》、力扣liweiwei1419用户等等。

 

目录

1.下马威,最难最综合的广搜深搜题。

 法一:DFS为主BFS为辅助

第一阶段分析

第二阶段:加速

代码:(还是有点慢)

法二:DFS + BFS 双向搜索(two-end BFS)估计这个是正解

法三:另外一个不错的解析(看注释足够了)

插一道深搜水题,不知道为什么会有bfs的标签

2.理论

3.经典题也是难题

 思路

自己的代码

4.简单一点的题

(1)111二叉树最小深度力扣

(2)112.路径总和力扣

5.何以包邮型问题

(1)322零钱兑换322. 零钱兑换

 法一:BFS

代码:好好看注释

法二:动态规划与记忆化递归

 代码:(好好看注释)

法三:套「完全背包」问题的公式(背包问题以后会做专题的)

代码


1.下马威,最难最综合的广搜深搜题。

这题实在太强了,估计在我未来一年做的题目里面都能排前十,也非常妙,必须放第一个说。题目链接: 力扣

而且力扣官方后来提高样例的难度,导致法一法二只能对34/36,只有法三C

 法一:DFS为主BFS为辅助

第一阶段分析

从题目其实可以任容易看出,用深度搜索似乎是一个比较明显的解法,我利用回溯来控制path和result,加上只变化一个字母的条件,用回溯是一定可解出来的。

但是毫无疑问超时太多,只能对两三个样例。

那我们可以加两个简单的,也是容易想到的剪枝,比如:

        1.因为我们要找的是最短的路径。但是事先我们并不知道最短的路径是多少,我们需要一个全局变量来保存当前找到的路径的长度。如果找到的新的路径的长度比之前的路径短,就把之前的结果清空,重新找,如果是最小的长度,就加入到结果中。

        2.当前的长度到达了 min,还是没有到达结束单词就提前结束

同时,我们判断只变化一个字母的函数也可以加快:

第一种,遍历 wordList 来判断每个单词和当前单词是否只有一个字母不同。类似于下面的代码,O(m*n),虽然比较自然但是慢。

private boolean oneChanged(String beginWord, String curWord) {
    int count = 0;
    for (int i = 0; i < beginWord.length(); i++) {
        if (beginWord.charAt(i) != curWord.charAt(i)) {
            count++;
        }
        if (count == 2) {
            return false;
        }
    }
    return count == 1;
}

优化:将要找的节点单词的每个位置换一个字符,然后看更改后的单词在不在 wordList 中,利用count函数,O(26n),不过count最坏也是O(n),只能说是略微优化

    //更快的找邻居,因为如果要遍历去找是mn,这样改变字母去对应
    //利用count是26n,会快很多
    vector<string> getNeighbours(string s, vector<string>& wordList)
    {
        vector<string> rs;
        for (char ch = 'a'; ch <= 'z'; ++ch) {
            for (int i = 0; i < s.size(); ++i) {
                if (s[i] == ch)continue;
                char od = s[i];
                s[i] = ch;
                if (count(wordList.begin(), wordList.end(), s)) {
                    rs.push_back(s);
                }
                s[i] = od;
            }
        }
        return rs;
    }

回溯的函数比较简单,对回溯已经很熟练了,加上剪枝的代码如下:(注意注释)

    vector<vector<string>> rs;
    vector<string> path;
    int min = 1 << 20;//足够的大数
    void backtracking(string temp, string endWord, vector<string>& wordList) {
        //剪枝加收获结果
        if (temp == endWord) {
            path.push_back(temp);
            if (path.size() < min) {
                rs.clear();//这步很关键,说明之前更长的结果已经没用了
                min = path.size();
                rs.push_back(path);
            }
            else if (path.size() == min) {
                rs.push_back(path);//相等要加入
            }
            path.pop_back();
            return;
        }

        if (path.size() >= min) {
            return;//没有找到最后一个,长度也已经大于等于,自然剪枝
        }
        //获取该节点的邻居,准备for横向,递归纵向遍历
        vector<string> neighbours = getNeighbours(temp, wordList);
        
        for (const string& neb : neighbours) {
            if (count(path.begin(), path.end(), neb))continue;//如果path里面已经有这个元素自然跳过
            path.push_back(temp);
            backtracking(neb, endWord, wordList);
            path.pop_back();//回溯
        }



    }

所以我们可以写出19/36的一份慢但是对的代码:

class Solution {
public:
    vector<vector<string>> rs;
    vector<string> path;
    int min = 1 << 20;//足够的大数
    void backtracking(string temp, string endWord, vector<string>& wordList) {
        //剪枝加收获结果
        if (temp == endWord) {
            path.push_back(temp);
            if (path.size() < min) {
                rs.clear();//这步很关键,说明之前更长的结果已经没用了
                min = path.size();
                rs.push_back(path);
            }
            else if (path.size() == min) {
                rs.push_back(path);//相等要加入
            }
            path.pop_back();
            return;
        }

        if (path.size() >= min) {
            return;//没有找到最后一个,长度也已经大于等于,自然剪枝
        }
        //获取该节点的邻居,准备for横向,递归纵向遍历
        vector<string> neighbours = getNeighbours(temp, wordList);
        
        for (const string& neb : neighbours) {
            if (count(path.begin(), path.end(), neb))continue;//如果path里面已经有这个元素自然跳过
            path.push_back(temp);
            backtracking(neb, endWord, wordList);
            path.pop_back();//回溯
        }



    }

    //更快的找邻居,因为如果要遍历去找是mn,这样改变字母去对应
    //利用count是26n,会快很多
    vector<string> getNeighbours(string s, vector<string>& wordList)
    {
        vector<string> rs;
        for (char ch = 'a'; ch <= 'z'; ++ch) {
            for (int i = 0; i < s.size(); ++i) {
                if (s[i] == ch)continue;
                char od = s[i];
                s[i] = ch;
                if (count(wordList.begin(), wordList.end(), s)) {
                    rs.push_back(s);
                }
                s[i] = od;
            }
        }
        return rs;
    }

    vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
       
        backtracking(beginWord, endWord, wordList);
        return rs;
    
    }
};

第二阶段:加速

参考的题解点这里。

第一处优化:

 这个还是很巧妙的,往往需要认真思考才能明白

第二处:

这个问题我们第一阶段的部分考虑了一丢丢:

但我们只考虑了当前路径是否含有该单词,而就像上上图表示的,其他路径之前已经考虑过了当前单词,我们也是可以跳过的 

方法:再利用一个 HashMap,记为 distance 变量。在 BFS 的过程中,把第一次遇到的单词当前的层数存起来。之后遇到也不进行更新

代码:(还是有点慢)

class Solution {
public:


    vector<vector<string>> rs;
    vector<string> path;

    void backtracking(string temp, string
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值