算法专练:广度优先搜索

1.LCP 44. 开幕式焰火

原题链接


        「力扣挑战赛」开幕式开始了,空中绽放了一颗二叉树形的巨型焰火。

        给定一棵二叉树 root 代表焰火,节点值表示巨型焰火这一位置的颜色种类。请帮小扣计算巨型焰火有多少种不同的颜色。

        示例 1:

        输入:root = [1,3,2,1,null,2]

        输出:3

        解释:焰火中有 3 个不同的颜色,值分别为 1、2、3

        示例 2:

        输入:root = [3,3,3]
输出:1

        解释:焰火中仅出现 1 个颜色,值为 3

        提示:

        1 <= 节点个数 <= 1000

        1 <= Node.val <= 1000


        这道题通过任何一个二叉树遍历来搜索二叉树每个结点的val之前是否出现过即可,没有就让答案++,如果出现过就不管了。

class Solution {
    queue<TreeNode*> q;
    bool vis[1001];
public:
    int numColor(TreeNode* root) {
        q.push(root);
        memset(vis,0,sizeof(vis));
        int ans=0;
        while(!q.empty()){
            TreeNode* cur=q.front();
            q.pop();
            if(!vis[cur->val]){
                ans++;
                vis[cur->val]=true;
            }
            if(cur->left){
                q.push(cur->left);
            }
            if(cur->right){
                q.push(cur->right);
            }
        }
        return ans;
    }
};

2.102. 二叉树的层序遍历

原题链接


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

        示例 1:

        输入:root = [3,9,20,null,null,15,7]

        输出:[[3],[9,20],[15,7]]

        示例 2:

        输入:root = [1]

        输出:[[1]]

        示例 3:

        输入:root = []

        输出:[]

        提示:

        树中节点数目在范围 [0, 2000] 内

        -1000 <= Node.val <= 1000


        层序遍历也就是获取二叉树每个深度从左往右的序列,层序遍历的方法有很多这里介绍两种


        1.这是最简单的方法,不过如果要处理关于深度的问题可能就要记录上一层的深度,当前的深度,上一个元素的值来作比较。下一题就会遇到这种情况.

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if(!root){
            return {};
        }
        queue<TreeNode*> q;
        q.push(root);
        vector<vector<int>> ans;
        int size=0;
        while(!q.empty()){
            size=q.size();
            vector<int> tmp;
            while(size--){
                TreeNode* cur=q.front();
                q.pop();
                tmp.push_back(cur->val);
                if(cur->left){
                    q.push(cur->left);
                }
                if(cur->right){
                    q.push(cur->right);
                }
            }
            ans.push_back(tmp);
        }
        return ans;
    }
};


        2.这里就选择利用了无序表来记录每个结点的深度,在处理关于深度的问题的时候比较方便.

class Solution {
    unordered_map<TreeNode*,int> dep;
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if(!root){
            return {};
        }
        dep[root]=0;
        vector<vector<int>> ans;
        int prev=-1;
        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty()){
            TreeNode* cur=q.front();
            q.pop();
            if(dep[cur]!=prev){
                ans.push_back({cur->val});
                prev=dep[cur];
            }else {
                ans.back().push_back(cur->val);
            }
            if(cur->left){
                q.push(cur->left);
                dep[cur->left]=dep[cur]+1;
            }
            if(cur->right){
                q.push(cur->right);
                dep[cur->right]=dep[cur]+1;
            }
        }
        return ans;
    }
};

3.1609. 奇偶树

原题链接


        如果一棵二叉树满足下述几个条件,则可以称为 奇偶树 :

        二叉树根节点所在层下标为 0 ,根的子节点所在层下标为 1 ,根的孙节点所在层下标为 2 ,依此类推。

        偶数下标 层上的所有节点的值都是 奇 整数,从左到右按顺序 严格递增

        奇数下标 层上的所有节点的值都是 偶 整数,从左到右按顺序 严格递减

        给你二叉树的根节点,如果二叉树为 奇偶树 ,则返回 true ,否则返回 false 。

        示例 1:

        输入:root = [1,10,4,3,null,7,9,12,8,6,null,null,2]

        输出:true
示例 2:

        输入:root = [5,4,2,3,3,7]

        输出:false

        示例 3:

        输入:root = [5,9,1,3,5,7]

        输出:false

        解释:1 层上的节点值应为偶数。

        示例 4:

        输入:root = [1]

        输出:true

        示例 5:

        输入:root = [11,8,6,1,3,9,11,30,20,18,16,12,10,4,2,17]

        输出:true

        提示:

        树中节点数在范围 [1, 10^5] 内

        1 <= Node.val <= 10^6


        一种十分简单的做法就是利用我们的第二题得到的二维数组来直接对每层进行判断,利于理解也好操作:

class Solution {
    unordered_map<TreeNode*,int> dep;
    vector<vector<int>> levelOrder(TreeNode* root) {
        if(!root){
            return {};
        }
        dep[root]=0;
        vector<vector<int>> ans;
        int prev=-1;
        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty()){
            TreeNode* cur=q.front();
            q.pop();
            if(dep[cur]!=prev){
                ans.push_back({cur->val});
                prev=dep[cur];
            }else {
                ans.back().push_back(cur->val);
            }
            if(cur->left){
                q.push(cur->left);
                dep[cur->left]=dep[cur]+1;
            }
            if(cur->right){
                q.push(cur->right);
                dep[cur->right]=dep[cur]+1;
            }
        }
        return ans;
    }
public:
    bool isEvenOddTree(TreeNode* root) {
        vector<vector<int>> lo=levelOrder(root);
        for(int i=0;i<lo.size();++i){
            if(i&1){
                for(int j=0;j<lo[i].size();++j){
                    if(lo[i][j]&1){
                        return false;
                    }
                    if(j&&lo[i][j]>=lo[i][j-1]){
                        return false;
                    }
                }
            }else {
                for(int j=0;j<lo[i].size();++j){
                    if((lo[i][j]&1)==0){
                        return false;
                    }
                    if(j&&lo[i][j]<=lo[i][j-1]){
                        return false;
                    }
                }
            }
        }
        return true;
    }
};


        不过我们可以直接在层序遍历的途中来进行判断,首先看一下不在遍历过程中记录层数的情况

class Solution {
public:
    bool isEvenOddTree(TreeNode* root) {
        queue<TreeNode*> q;
        int dep=0;//当前深度
        q.push(root);
        while(!q.empty()){
            int sz=q.size();//当前层的元素数量
            int prev=0;//之前元素的值
            while(sz--){
                TreeNode* cur=q.front();
                q.pop();
                int value=cur->val;
                if(cur->left) q.push(cur->left);
                if(cur->right) q.push(cur->right);
                if(dep&1){//当前是奇数层
                    if((value&1)||prev&&value>=prev)
                    //奇数层要求严格递减并且元素均为偶数
                    	return false;
                    prev=value;
                }
                else{
                    if((!(value&1))||prev&&value<=prev)
                    //偶数层要求严格递增且元素均为奇数
                    	return false;
                    prev=value;
                }
            }
            dep++;//进入到下一深度
        }
        return true;
    }
};


        记录深度的做法和这种类似,不过我们需要对哈希表改变一下,需要将深度设置为key,当前元素的值设置为value,由于这种做法无法得到当前元素上一个元素的值,所以value主要是为了记录上一个元素的值。当map[level]==0的时候就代表还没有搜索过该层,更改键值对,然后去判断。判断结束我们并不是判断当前层的下一个元素,而是去搜搜当前结点的左右结点。不过不用担心这样做的元素是否符合层序遍历的顺序,其实这样和先序遍历一样,判断完该结点去判断左右节点一定是按照每层的先后顺序来的,不同的是我们的层数可能不是按照递增顺序来的。

class Solution {
    unordered_map<int,int> map;
public:
    bool isEvenOddTree(TreeNode* root) {
        return dfs(root,0);
        //上面提到无法直接使该层元素进行比较所以利用递归来处理
    }
    bool dfs(TreeNode* root,int level){
        if(!root){
        	return true;//为空直接返回true
        }
        if((level+1)%2==root->val%2){//首先是当前层和元素不同奇偶
            if (map[level]==0){
            	map[level]=root->val;//还没有搜索过该层
            }
            else if(level%2==0){
                if(root->val%2==0||root->val<=map[level]){
                	return false;//偶数层要求元素为奇数且严格递增
                }
            }else if(level%2==1){
                if(root->val%2==1||root->val>=map[level]){
                	return false;//奇数层要求元素为偶数且严格递减
                }
            }
        }else{
            return false;
        }
        map[level]=root->val;//搜索完毕之后将当前层的当前元素作为键值对进行修改
        return dfs(root->left,level+1)&&dfs(root->right,level+1);
    }
};

这里就推荐使用前两种利于理解和操作的解法了,最后一种也是看过了别人的代码之后写出来的不得不说真的很妙:力扣题解

4.1263. 推箱子

原题链接


        「推箱子」是一款风靡全球的益智小游戏,玩家需要将箱子推到仓库中的目标位置。

        游戏地图用大小为 m x n 的网格 grid 表示,其中每个元素可以是墙、地板或者是箱子。

        现在你将作为玩家参与游戏,按规则将箱子 ‘B’ 移动到目标位置 ‘T’ :

        玩家用字符 ‘S’ 表示,只要他在地板上,就可以在网格中向上、下、左、右四个方向移动。

        地板用字符 ‘.’ 表示,意味着可以自由行走。

        墙用字符 ‘#’ 表示,意味着障碍物,不能通行。

        箱子仅有一个,用字符 ‘B’ 表示。相应地,网格上有一个目标位置 ‘T’。

        玩家需要站在箱子旁边,然后沿着箱子的方向进行移动,此时箱子会被移动到相邻的地板单元格。记作一次「推动」。

        玩家无法越过箱子。

        返回将箱子推到目标位置的最小 推动 次数,如果无法做到,请返回 -1。

        示例 1:

        输入:grid =

         [

        [“#”,“#”,“#”,“#”,“#”,“#”],

         [“#”,“T”,“#”,“#”,“#”,“#”],

         [“#”,“.”,“.”,“B”,“.”,“#”],

         [“#”,“.”,“#”,“#”,“.”,“#”],

         [“#”,“.”,“.”,“.”,“S”,“#”],

         [“#”,“#”,“#”,“#”,“#”,“#”]

         ]

        输出:3

        示例 2:

        输入:grid =
        [
        [“#”,“#”,“#”,“#”,“#”,“#”],

         [“#”,“T”,“#”,“#”,“#”,“#”],

         [“#”,“.”,“.”,“B”,“.”,“#”],

         [“#”,“#”,“#”,“#”,“.”,“#”],

         [“#”,“.”,“.”,“.”,“S”,“#”],

         [“#”,“#”,“#”,“#”,“#”,“#”]
        ]

        输出:-1

        示例 3:

        输入:grid = [
        [“#”,“#”,“#”,“#”,“#”,“#”],

         [“#”,“T”,“.”,“.”,“#”,“#”],

         [“#”,“.”,“#”,“B”,“.”,“#”],

         [“#”,“.”,“.”,“.”,“.”,“#”],

         [“#”,“.”,“.”,“.”,“S”,“#”],

         [“#”,“#”,“#”,“#”,“#”,“#”]
        ]

        输出:5

        提示:

        m == grid.length

        n == grid[i].length

        1 <= m, n <= 20

        grid 仅包含字符 ‘.’, ‘#’, ‘S’ , ‘T’, 以及 ‘B’。

        grid 中 ‘S’, ‘B’ 和 ‘T’ 各只能出现一个。


         玩过推箱子的话我们很容易理解这道题不能仅仅只是从一个方向来推,而是对某个箱子的位置四个方向都有可能,这样也说明了这道题的状态十分之多,我们考虑状态压缩的话,对于从人和箱子的每个位置状态是可以记录下来的,所以这道题使用状态压缩非常合适。

        先看代码:

class Solution {
    int m,n;
    int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};//方向数组
    int step[1<<20];//每个状态下的移动步数
    bool isvalid(int x,int y,vector<vector<char>> &grid){
        if(x<0||x>=m||y<0||y>=n){
           return false;
        }
        if(grid[x][y]=='#'){
          return false;
        }
        return true;
    }
    int pack(int sx,int sy,int bx,int by){
       return  (sx<<15)|(sy<<10)|(bx<<5)|by;
    }
public:
    int minPushBox(vector<vector<char>>& grid) {
        m=grid.size();
        n=grid[0].size();
        int sx,sy,bx,by;
        for(int i=0;i<m;++i){
            for(int j=0;j<n;++j){
                if(grid[i][j]=='S'){
                    sx=i;
                    sy=j;
                    grid[sx][sy]='.';
                }
                if(grid[i][j]=='B'){
                    bx=i;
                    by=j;
                    grid[bx][by]='.';
                }
            }
        }
        int s=pack(sx,sy,bx,by);
        memset(step,-1,sizeof(step));
        queue<int> q;
        q.push(s);
        step[s]=0;
        int ans=-1;
        while(!q.empty()){
            s=q.front();
            q.pop();

            by=s&31;
            bx=(s>>5)&31;
            sy=(s>>10)&31;
            sx=(s>>15)&31;
            if(grid[bx][by]=='T'){
                if(ans==-1||step[s]<ans){
                    ans=step[s];
                }
            }
            int sxn,syn,bxn,byn;
            for(int i=0;i<4;++i){
                sxn=sx+dir[i][0];
                syn=sy+dir[i][1];
                if(!isvalid(sxn,syn,grid)){
                    continue;
                }
                if(sxn==bx&&syn==by){
                    bxn=bx+dir[i][0];
                    byn=by+dir[i][1];
                    if(isvalid(bxn,byn,grid)){
                        int ts=pack(sxn,syn,bxn,byn);
                        if(step[s]+1<step[ts]||step[ts]==-1){
                            step[ts]=step[s]+1;
                            q.push(ts);
                        }
                    }
                }else {
                    int ts=pack(sxn,syn,bx,by);
                    if(step[s]+0<step[ts]||step[ts]==-1){
                        step[ts]=step[s];
                        q.push(ts);
                    }
                }
            }
        }
        return ans;
    }
};


        先对状态进行解释,m,n最大20,那么格子数目最多就是400,400*400一个int完全可以存下.对于一个状态我们考虑二进制,后五位定义为箱子的列坐标,其前五位为箱子的行坐标,再前五位为人的列坐标,再前五位为人的行坐标。11111这个二进制数字为31,对于某个状态我们要得到这四个坐标只需要对对应的五个位置按位与上31即可,要得到某个状态我们就将当前人和箱子的坐标分别与其对应的五个二进制位按位或即可。(按位与和按位或都与离散数学种矩阵的某种运算对应,其中按位或与矩阵的逻辑加法对应,按位与可以看作将该矩阵与其单位矩阵做逻辑运算。这里我们把该状态当作一个一行的矩阵即可)

        接下来把问题简化为寻找一个最短路,对于某个状态来说,如果从人的角度出发遍历四个方向,如果下个方向的位置恰好是格子的位置就说明了可以从这个方向来推箱子,我们更新箱子的位置、状态以及步数。但是如果从这个方向出发的下一个格子并不是箱子的位置,我们只需要更新状态和新状态的步数,箱子此时不动。

        当这样一直从人的位置出发,每次搜索四个方向,我们注意到对于状态的更新人的位置是一直在变化的,而箱子的位置则会根据他是否能被人推到来选择更新与否。这样某个状态就可以看作人从原始的某个状态经过任意方向若干次移动且在能推动箱子的时候就推动而得到的状态。这样对于某个状态就可以由不同的原始状态来得到,我们步数的更新就是在第一次和重复到达该状态的时候取较小的上一状态步数来转移。

        而对于答案的更新就是当每次出队的时候,判断当前状态的箱子位置是否是指定的位置,如果是就选择答案和当前状态步数之间的较小者来更新答案。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值