lcBFS1 题集

28 篇文章 0 订阅
21 篇文章 0 订阅

1 BFS

最关键的是:
- bfs本身的特性,一个是按照层数往下遍历,一个是可以决定是否遍历完当前层再往下遍历
- bfs的队列初始化即为bfs的搜索起点,然后注意状态问题,当每个节点带有状态以后,bfs的visited变量就不仅仅是node的编号,应该是node的编号 + “当前状态”,具体参考(0864,0847)
- 注意:遍历当前层,需要把size记录成临时变量,因为curLevel会加入新元素
- 注意:入队后就需要置为visited,否则可能重复入队

bfs的遍历本质上可以看成当前点,到所有后继节点的可能跳转的状态。(0733例题)

bfs的核心代码:

int bfsCheck(vector<vector<int>>& oriBoard) {
        queue<vector<vector<int>>> curLevel;
        curLevel.push(oriBoard);
        vis.insert(toInt(oriBoard));

        int depth = 1;
        while(!curLevel.empty()) {
            int curSize = curLevel.size();
            // pop all curLevel and do next level
            while(curSize-- > 0) { // 最关键的,一定不要直接写成curLevel.size,因为curLevel会放后面的节点的呜呜呜
                auto board = curLevel.front();
                curLevel.pop();

                auto coord = getPos(board);
                int x = coord.first;
                int y = coord.second;
            
                for(int mv = 0; mv < 4; ++mv) {
                    int nextX = x + dx[mv];
                    int nextY = y + dy[mv];
                    if(0 <= nextX && nextX < 2 && 0 <= nextY && nextY < 3) {
                        swap(board[nextX][nextY], board[x][y]);
                        // cout << "trying : " << x << ", " << y << " to " << nextX << ", " << nextY << " withd d = " << depth <<endl;
                        // print(board);
                        
                        int intBoard = toInt(board);

                        if(0 == vis.count(intBoard)) {
                            curLevel.push(board);
                            if(isTar(board)) {
                                // cout << "final tar: " << endl;
                                // print(board);
                                return depth;
                            }
                            vis.insert(intBoard);
                        }
                        swap(board[x][y], board[nextX][nextY]);
                    }
                }
            }
            ++depth;
        }

2 例题

0733slidingPuzzle 滑动到123450谜题

1 题目

https://leetcode.cn/problems/sliding-puzzle/

2 解题思路

  • 1 解题思路:
    • 1.1 为何选用bfs,因为要获得最终的移动步数,那么bfs的层数就是最终的答案
    • 1.2 节点就是当前的棋盘,后继节点为移动后改变的棋盘
    • 1.3 使用位运算加速
class Solution {
public:
    constexpr static int tar = ((1<<3) | (2<<6) | (3<<9) | (4<<12) | (5<<15));
    unordered_set<int> vis;

    int dx[4] = {1, 0, -1, 0};
    int dy[4] = {0, -1, 0, 1};
    

    bool isTar(vector<vector<int>>& board) {
        return tar == toInt(board);
    }
    int toInt(vector<vector<int>>& board) {
        return board[0][0]<<3 | board[0][1]<<6 | board[0][2]<<9 | board[1][0]<<12 | board[1][1]<<15;
    }

    pair<int, int> getPos(vector<vector<int>>& board) {
        for(int i = 0; i < 2; ++i) {
            for(int j = 0; j < 3; ++j) {
                if(board[i][j] == 0) {
                    return {i, j};
                }
            }
        }
        return {-1, -1};
    }

    int slidingPuzzle(vector<vector<int>>& board) {
        int res = 0;
        if(toInt(board) == tar) {
            return 0;
        }
    
        res = bfsCheck(board);
        return res;
    }

    void swap(int& a, int& b) {
        int tmp = a;
        a = b;
        b = tmp;
    }

    int bfsCheck(vector<vector<int>>& oriBoard) {
        queue<vector<vector<int>>> curLevel;
        curLevel.push(oriBoard);
        vis.insert(toInt(oriBoard));

        int depth = 1;
        while(!curLevel.empty()) {
            int curSize = curLevel.size();
            // pop all curLevel and do next level
            while(curSize-- > 0) {
                auto board = curLevel.front();
                curLevel.pop();

                auto coord = getPos(board);
                int x = coord.first;
                int y = coord.second;
            
                for(int mv = 0; mv < 4; ++mv) {
                    int nextX = x + dx[mv];
                    int nextY = y + dy[mv];
                    if(0 <= nextX && nextX < 2 && 0 <= nextY && nextY < 3) {
                        swap(board[nextX][nextY], board[x][y]);
                        // cout << "trying : " << x << ", " << y << " to " << nextX << ", " << nextY << " withd d = " << depth <<endl;
                        // print(board);
                        
                        int intBoard = toInt(board);

                        if(0 == vis.count(intBoard)) {
                            curLevel.push(board);
                            if(isTar(board)) {
                                // cout << "final tar: " << endl;
                                // print(board);
                                return depth;
                            }
                            vis.insert(intBoard);
                        }
                        swap(board[x][y], board[nextX][nextY]);
                    }
                }
            }
            ++depth;
        }
        return -1;

    }

    void print(vector<vector<int>>& board) {
        for(int i = 0; i < 2; ++i) {
            for(int j = 0; j < 3; ++j) {
                cout << board[i][j] << " ";
            }cout << " | ";
        }
        cout << "----------\n";
    }

};

0675golfCutTree 高尔夫砍树

1 题目

https://leetcode.cn/problems/cut-off-trees-for-golf-event/

2 解题思路

  • 1 解题思路:
    • 1.1 使用优先队列将所有树从小到高排序,存为trees
    • 1.2 每次从trees中取出一个树,对其进行bfs直到找到trees的下一个目标
  • 2 bfs思路:
    • 2.1 queue初始化为起点
    • 2.2 对于queue中所有元素,加入当前queue的所有后继,加入以后就把元素置为visited
    • 2.3 进入下一个level
  • 3 为什么加入的过程中就要吧元素置为vis?因为:假设1层有a,b, 2层有c,c为ab的邻居,那么遍历第1层的时候,对于ab的后继,到a,加入c,若不置为vis,则b会再加入一遍,导致bfs很慢!
class Solution {
public:
    int m;
    int n;
    
    int dx[4] = {1, 0, -1, 0};
    int dy[4] = {0, -1, 0, 1};
    int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    
    int cutOffTree(vector<vector<int>>& forest) {
        // get all trees
        m = forest.size();
        n = forest[0].size();

        auto cmp = [](const pair<pair<int, int>, int>& a, const pair<pair<int, int>, int>& b) {
            return a.second > b.second;
        };
        priority_queue<pair<pair<int, int>, int>, vector<pair<pair<int, int>, int>>, decltype(cmp)>trees (cmp);

        for(int i = 0; i < m; ++i) {
            for(int j = 0; j < n; ++j) {
                if(forest[i][j] > 1) {
                    trees.push({{i, j}, forest[i][j]});
                }
            }
        }


        int curX = 0, curY = 0;
        int tarX = -1, tarY = -1;
        int res = 0;
        while(!trees.empty()) {
            auto tree = trees.top();
            trees.pop();

            tarX = tree.first.first;
            tarY = tree.first.second;
            
            // judge if we can walk to x, y
            // cout << "from/to: " << curX << "," << curY << " -> " << tarX << "," << tarY << endl; 
            if(!tryWalk(forest, curX, curY, tarX, tarY, res)) {
                return -1;
            } else {
                // cut tarX, tarY
                forest[tarX][tarY] = 1;
                curX = tarX;
                curY = tarY;
            }
            // auto step = bfs(forest, curX, curY, tarX, tarY);
            // if(-1 == step) {
            //     return -1;
            // } else {
            //     // cut tarX, tarY
            //     forest[tarX][tarY] = 1;
            //     res+= step;
            //     curX = tarX;
            //     curY = tarY;
            // }
        }

        return res;
    }

    int bfs(vector<vector<int>>& forest, int sx, int sy, int tx, int ty) {
    if (sx == tx && sy == ty) {
        return 0;
    }

    int row = forest.size();
    int col = forest[0].size();
    int step = 0;
    queue<pair<int, int>> qu;
    vector<vector<bool>> visited(row, vector<bool>(col, false));         
    qu.emplace(sx, sy);
    visited[sx][sy] = true;
    while (!qu.empty()) {
        step++;
        int sz = qu.size();
        for (int i = 0; i < sz; ++i) {
            auto [cx, cy] = qu.front();
            qu.pop();
            for (int j = 0; j < 4; ++j) {
                int nx = cx + dirs[j][0];
                int ny = cy + dirs[j][1];
                if (nx >= 0 && nx < row && ny >= 0 && ny < col) {
                    if (!visited[nx][ny] && forest[nx][ny] > 0) {
                        if (nx == tx && ny == ty) {
                            return step;
                        }
                        qu.emplace(nx, ny);
                        visited[nx][ny] = true;
                    }
                }
            }
        }
    }
    return -1;
}


    // using bfs from <curX, curY> to <tarX, tarY>, the depth of bfs should be the distance
    bool tryWalk(vector<vector<int>>& forest, int curX, int curY, int tarX, int tarY, int& res) {
        // bfs
        queue<pair<int, int>> curLevel;
        vector<vector<int>> vis(m, vector<int>(n, false));
        
        curLevel.push({curX, curY});
        vis[curX][curY] = true;

        int depth = 0;
        while(!curLevel.empty()) {
            // queue<pair<int, int>> nextLevel;

            // while(!curLevel.empty()) {
            int curLevelSize = curLevel.size();
            while(curLevelSize-- > 0) {
                auto curNode = curLevel.front();

                curLevel.pop();
                if(curNode == pair<int, int>{tarX, tarY}) {
                    // update res
                    res += depth;
                    return true;
                }
                for(int mv = 0; mv < 4; ++mv) {
                    int nextX = curNode.first + dx[mv];
                    int nextY = curNode.second + dy[mv];

                    if(0 <= nextX && nextX < m && 0 <= nextY && nextY < n && 0 != forest[nextX][nextY] && !vis[nextX][nextY]) {
                        curLevel.push({nextX, nextY});
                        vis[nextX][nextY] = true;
                        // cout << "nextNode: " << curNode.first << "," << curNode.second << endl; 
                    }
                }
            }
            // curLevel = std::move(nextLevel);
            ++depth;
        }
        return false;
    }
};

0847allTravelShortestPath 访问所有节点最短路径

1 题目

https://leetcode.cn/problems/shortest-path-visiting-all-nodes/

2 解题思路

  • 1 解题思路:
    • 1.1 使用三元组 <u, mask, dist>表示,当前节点u的mask的搜索情况对应的搜索距离dist,调用bfs即可,但是对于下一个v,我们需要检测: v节点的1 << v | mask这种访问情况是否被访问过
    • 1.2 如何考虑这个方法?
      • 1.2.1 初始化的时候所有节点都加入队里(认为可以从每个节点出发)
      • 1.2.2 bfs的具体过程中,退出条件就是 队首的mask是否标记了所有节点都被访问
    • 1.3 这个方法为什么可以?
      • 一句话:这个方法:利用bfs,按照路径长度从小到大遍历了所有的可能路径(队列初始化有所有的顶点就是这个意思),比如初始化,就是说将长度为0的所有可能路径遍历完成,然后下一层bfs会将所有长度为1的可能路径遍历完成,这个路径的记录方式为mask以及mask对应的终点u,那么由于路径长度是从小到大去遍历的,那么必然保证最终答案是最短路径,
      • 假设用dfs做,那么dfs要考虑所有路径可能,然后去比较,那么会出现枚举的情况,然鹅bfs不会,因为省去所有长度比最短路径大的路径
  • 2 总结一下:最关键的有两点
    • 2.1 利用bfs能够将遍历路径的长度从小到大遍历的特性
    • 2.2 使用<到达点,已经遍历的点,目前长度>来记录所有的遍历情况这个trick很聪明
// 这里看一个具体 
[[1,2,3],[0],[0],[0]]

// 具体的遍历顺序为:
// 遍历到达的节点 目前遍历的节点 遍历路径的长度
0 00000001 0
1 00000010 0
2 00000100 0
3 00001000 0
1 00000011 1
2 00000101 1
3 00001001 1
0 00000011 1
0 00000101 1
0 00001001 1
2 00000111 2
3 00001011 2
1 00000111 2
3 00001101 2
1 00001011 2
2 00001101 2
0 00000111 3
0 00001011 3
0 00001101 3
3 00001111 4
3 00001111 4


class Solution {
public:
    int shortestPathLength(vector<vector<int>>& graph) {
        queue<tuple<int, int, int>> curLevel;
        int n = graph.size();
        vector<vector<bool>> seen(n, vector<bool>(1 << n));

        for(int i = 0; i < n; ++i) {
            curLevel.emplace(i, 1 << i, 0);
            seen[i][1 << i] = true;
        }

        while(!curLevel.empty()) {
            auto [node, mask, dist] = curLevel.front();
            // printf("%d %x", node, mask);
            // cout << dist << endl;
            curLevel.pop();
            
            // << not priority to -
            if(mask == (1 << n) - 1) {
                return dist;
            }
            
            for(auto neighbor : graph[node]) {
                int newMask = mask | 1 << neighbor;
                if(!seen[neighbor][newMask]) {
                    curLevel.emplace(neighbor, newMask, dist + 1);
                    seen[neighbor][newMask] = true;
                }
            }
        }

        return -1;
    }
};

0864getAllKeys 获取所有钥匙的最短路径

1 题目

https://leetcode.cn/problems/shortest-path-to-get-all-keys/submissions/

2 解题思路

  • 1 解题思路:
    • 1.1 首先考虑使用bfs,因为从起点到终点,bfs的遍历深度就等于最短路径,那么有一个问题对于:[“@a.”,“bAB”]这样的地图如何知道经过a和b的最短路呢?
    • 1.2 也就是这个bfs会走"回头路",但是又不是完全的回头路,因为走路的人的状态发生了变化,也就是手里多了钥匙,也就是bfs的变种:带状态(压缩)的bfs
    • 1.3 那么就很容易想到,原来最普通的bfs判断是否走过就是只用了位置xy,那么现在我们多增加一个信息,也就是拥有的钥匙,那么该点没有走过变成了什么呢?那就是:该点位置没有走过,或者当前的拥有钥匙的状态在该点没有出现过
    • 1.4 有了1.3我们就很容易知道,用什么数据结构去存顶点是否被访问啦: map<pair<int, int>, set> seenKey; 左边是该点的位置,右边是该点所经历过的所有钥匙的集合
    • 1.5 还需要考虑如何记录路径长度:map<pair<pair<int, int>, int>, int> dis; 很显然,左侧是<xy,key>,右侧代表了xy在key情况下的路径长度
    • 1.6 考虑一个具体[“@a.”,“bAB”]的例子即可:
cur: 0,0 -> 000000
next: 0,1 charis a-> 000001
next: 1,0 charis b-> 000010
cur: 0,1 -> 000001
next: 0,2 charis .-> 000001
next: 1,1 charis A-> 000001
next: 0,0 charis @-> 000001
cur: 1,0 -> 000010
next: 0,0 charis @-> 000010
cur: 0,2 -> 000001
cur: 1,1 -> 000001
next: 1,0 charis b-> 000011
class Solution {
public:

    int m, n;
    int dx[4] = {-1, 0, 1, 0};
    int dy[4] = {0, 1, 0, -1};

    int shortestPathAllKeys(vector<string>& grid) {
        m = grid.size();
        n = grid[0].size();
        int i = 0;
        int j = 0;
        int keyNum = 0;
        int stX = -1, stY = -1;
        for(auto& s : grid) {
            j = 0;
            for(auto& c : s) {
                if('a' <= c && c <= 'f') {
                    ++keyNum;
                }
                if('@' == c) {
                    stX = i;
                    stY = j;
                }
                ++j;
            }
            ++i;
        }
        
        int tarKey = 0;
        for(int i = 0; i < keyNum; ++i) {
            tarKey = tarKey | (1 << i);
        };

        // cout << "tarKey is : " << bitset<8>(tarKey) << " | st: " << stX << " " << stY << endl;
        return bfs(grid, stX, stY, tarKey);
    }

    bool canWalk(vector<string>& grid, int x, int y, int keyInfo) {
        if('A' <= grid[x][y] && grid[x][y] <= 'F') {
            int keyNum = grid[x][y] - 'A';
            return ((keyInfo >> keyNum) & 1) != 0;
        } 
        if('#' == grid[x][y]) {
            return false;
        }

        return true;
    }

    int bfs(vector<string>& grid, int stX, int stY, int tarKey) {
        queue<pair<pair<int, int>, int>> curLevel; // xy, key
        curLevel.push({{stX, stY}, 0});
        map<pair<pair<int, int>, int>, int> dis; // xy, key -> pathLen
        map<pair<int, int>, set<int>> seenKey; // xy -> key
        dis[{{stX, stY}, 0}] = 0;
        seenKey[{stX, stY}] = {0};

        int res = 0;

        while(!curLevel.empty()) {
            int curSize = curLevel.size();
            while(curSize-- > 0) {
                auto curNode = curLevel.front();
                int curDis = dis[curNode];
                int x = curNode.first.first;
                int y = curNode.first.second;
                int curKey = curNode.second;

                curLevel.pop();
                // cout << "cur: " << x << "," << y << " -> " << bitset<6>(curKey) << endl;
                for(int mv = 0; mv < 4; ++mv) {
                    int nextX = x + dx[mv];
                    int nextY = y + dy[mv];
                    pair<int, int> nextNode = {nextX, nextY};
                    // nextNode not explored or has new keys
                    if(0 <= nextX && nextX < m && 0 <= nextY && nextY < n && \
                         canWalk(grid, nextX, nextY, curKey) && \
                        (0 == seenKey.count(nextNode) || 0 == seenKey[nextNode].count(curKey))) {
                        int nextKey = curKey;
                        if('a' <= grid[nextX][nextY] && grid[nextX][nextY] <= 'f') {
                            nextKey = curKey | (0x1 << (grid[nextX][nextY] - 'a'));
                        }
                        
                        curLevel.push({{nextX, nextY}, nextKey});
                        seenKey[{nextX, nextY}].insert(nextKey);
                        dis[{nextNode, nextKey}] = dis[curNode] + 1;
                        
                        // cout << "next: " << nextX << "," << nextY << " charis "<< grid[nextX][nextY] << "-> " << bitset<6>(nextKey) << endl;
                        if(nextKey == tarKey) {
                            return dis[curNode] + 1;
                        }
                    }
                }
            }
        }
        return -1;
    }
};

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值