算法专练:图

1.1791. 找出星型图的中心节点

原题链接


        有一个无向的 星型 图,由 n 个编号从 1 到 n 的节点组成。星型图有一个 中心 节点,并且恰有 n - 1 条边将中心节点与其他每个节点连接起来。

        给你一个二维整数数组 edges ,其中 edges[i] = [ui, vi] 表示在节点 ui 和 vi 之间存在一条边。请你找出并返回 edges 所表示星型图的中心节点。

        示例 1:

        输入:edges = [[1,2],[2,3],[4,2]]

        输出:2

        示例 2:

        输入:edges = [[1,2],[5,1],[1,3],[1,4]]

        输出:1

        提示:

        3 <= n <= 105

        edges.length == n - 1

        edges[i].length == 2

        1 <= ui, vi <= n

        ui != vi

        题目数据给出的 edges 表示一个有效的星型图


        这道题并没有用到图的算法。首先看题目描述,既然题目说明了给出的图一定是一个星型图,那么就一定有一个中心点,其余每个点的出边都指向他,中心点的出边也指向他。那么对于edges中任意两个集合他重复出现的点就一定是中心节点。

class Solution {
public:
    int findCenter(vector<vector<int>>& edges) {
        int first=edges[0][0],second=edges[0][1];
        if(edges[1][0]==first||edges[1][1]==first)
            return first;
        else 
            return second;//第一个点没有重复出现就是第二个点
    }
};

2.797. 所有可能的路径

原题链接


        给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序)
graph[i] 是一个从节点 i 可以访问的所有节点的列表(即从节点 i 到节点 graph[i][j]存在一条有向边)。

        示例 1:

        输入:graph = [[1,2],[3],[3],[]]

        输出:[[0,1,3],[0,2,3]]

        解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3

        示例 2:

        输入:graph = [[4,3,1],[3,2,4],[3],[4],[]]

        输出:[[0,4],[0,3,4],[0,1,3,4],[0,1,2,3,4],[0,1,4]]

        提示:

        n == graph.length

        2 <= n <= 15

        0 <= graph[i][j] < n

        graph[i][j] != i(即不存在自环)

        graph[i] 中的所有元素 互不相同

        保证输入为 有向无环图(DAG)


        这道题就是找到所有从0号结点到第n-1号结点的所有路径,建立好边之后搜素即可,这里搜索的过程类似于全排列。

class Solution {
    vector<vector<int>> ans;
    vector<int> tmp;
    void dfs(int n,vector<vector<int>> & graph,int cur){
        if(cur==n-1){
            ans.push_back(tmp);
            return;
        }
        for(int i=0;i<graph[cur].size();++i){
            tmp.push_back(graph[cur][i]);
            dfs(n,graph,graph[cur][i]);
            tmp.pop_back();
        }
    }
public:
    vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
            ans.clear();
            tmp.clear();
            tmp.push_back(0);//起点是0直接加入tmp即可
            int n=graph.size();
            dfs(n,graph,0);
            return ans;
    }
};

3.959. 由斜杠划分区域

原题链接


        在由 1 x 1 方格组成的 n x n 网格 grid 中,每个 1 x 1 方块由 ‘/’、‘’ 或空格构成。这些字符会将方块划分为一些共边的区域。

        给定网格 grid 表示为一个字符串数组,返回 区域的数量 。

        请注意,反斜杠字符是转义的,因此 ‘’ 用 ‘\\’ 表示。

        示例 1:

        输入:grid = [" /“,”/ “]

        输出:2

        示例 2:

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

        输出:1

        示例 3:

        输入:grid = [”/\\“,”\\/"]

        输出:5

        解释:回想一下,因为 \ 字符是转义的,所以 “/\\” 表示 /\,而 “\\/” 表示\/。

        提示:

        n == grid.length == grid[i].length

        1 <= n <= 30

        grid[i][j] 是 ‘/’、‘’、或 ’ ’


        这道题刚好可以利用昨天学过的并查集来做,因为可以吧题目理解为统计图中连通分量的数量。

        首先我们考虑如何连通的问题,这里我们把一个格子分成四个小格并给他们编号,如下图所示:(这里提一句,题目中的空格太小了导致我开始看题目的时候还以为斜杠和反斜杠直接吧整个矩阵给分割了,但实际上题目中给出的grid是一个格子一个格子的给出的,也是因为这里我们可以先对小格进行分割)
在这里插入图片描述


        从这里可以看出来当遇到“/"的时候我们吧2,3合并,0,1合并,遇到”\“的时候0,2合并,1,3合并,而遇到空格我们就把0,1,2,3合并。(具体怎么合并后面会讲到,这里先不考虑,并且合并的时候我们可以保证每个格子的四个小格都不会与其他的小格重复)

        现在一个格子的状态我们处理完了,他又该怎么跟其他格子合并呢?同样很简单,在对当前格子合并完毕之后,我们吧当前格子跟他上下左右四个格子合并即可(0和上格子的3,1和左格子2,2和右格子的1,3和下格子的0合并。这个合并过程不需要考虑斜杠,反斜杠或者空格就是简单的合并。

        那么具体怎么合并呢?我们把当前格子的坐标映射到一维得到一个标记mask(一个简单的映射就是i*n+j,也就是把二维坐标映射成一维,可以保证每个格子坐标都不重复)。在得到了不同的mask后我们选取其二进制中的连续两位作为小格的标记位。

        为什么是两位呢?我们看这些小格的编号:0,1,2,3他们的二进制为00,01,10,11两个二进制位完全可以存储。此外,理论上这两位除了所有格子映射之后的mask的原本数据位都可以,不过为了统一和操作方便这里就把mask左移两位,将最后两位当作标记位了,同时我们利用按位或把编号直接加到最后两位上。(按位或的这个性质多次提到了,对应着矩阵中的逻辑加法,经常要用到)

		int dir[4][2]={{-1,0},{0,-1},{0,1},{1,0}};//分别是上,左,右,下也就是0,1,2,3方便对应
		for(i=0;i<n;++i){
            for(j=0;j<n;++j){
                int mask=i*n+j;
                if(grid[i][j]=='\\'){
                    union_union(mask<<2|0,mask<<2|2);
                    union_union(mask<<2|1,mask<<2|3);
                }else if(grid[i][j]=='/'){
                    union_union(mask<<2|0,mask<<2|1);
                    union_union(mask<<2|2,mask<<2|3);
                }else {
                    union_union(mask<<2|0,mask<<2|2);
                    union_union(mask<<2|1,mask<<2|3);
                    union_union(mask<<2|0,mask<<2|1);
                    union_union(mask<<2|2,mask<<2|3);
                }//分别对应右斜杠,左斜杠,空格的情况
                for(int k=0;k<4;++k){
                    int ti=i+dir[k][0];
                    int tj=j+dir[k][1];
                    if(ti==-1||tj==-1||ti==n||tj==n)
                        continue;
                    }
                    int tmask=ti*n+tj;
                    union_union(mask<<2|k,tmask<<2|(3-k));
                }
                //这里的k是方向,不过取了巧,因为编号和方向范围都是[0,3]
                //并且3-k这里刚好与他要合并周围格子的小格编号一样
            }
        }


        最后我们去遍历所有的小格统计连通分量即可。
完整代码:

class Solution {
    #define maxn 3605
     int fa[maxn];
    void union_init(){
        for(int i=0;i<maxn;++i){
            fa[i]=i;
        }
    }
    int  union_find(int x){
        return fa[x]==x?x:(fa[x]=union_find(fa[x]));
    }
    bool union_union(int x,int y){
        int fx=union_find(x);
        int fy=union_find(y);
        if(fx==fy){
            return false;
        }
        fa[fx]=fa[fy];
        return true;
    }//并查集模板
    int dir[4][2]={{-1,0},{0,-1},{0,1},{1,0}};
public:
    int regionsBySlashes(vector<string>& grid) {
        int n=grid.size();
        int i,j;
        int hash[maxn];
        memset(hash,0,sizeof(hash));
        union_init();
        for(i=0;i<n;++i){
            for(j=0;j<n;++j){
                int mask=i*n+j;
                if(grid[i][j]=='\\'){
                    union_union(mask<<2|0,mask<<2|2);
                    union_union(mask<<2|1,mask<<2|3);
                }else if(grid[i][j]=='/'){
                    union_union(mask<<2|0,mask<<2|1);
                    union_union(mask<<2|2,mask<<2|3);
                }else {
                    union_union(mask<<2|0,mask<<2|2);
                    union_union(mask<<2|1,mask<<2|3);
                    union_union(mask<<2|0,mask<<2|1);
                    union_union(mask<<2|2,mask<<2|3);
                }
                for(int k=0;k<4;++k){
                    int ti=i+dir[k][0];
                    int tj=j+dir[k][1];
                    if(ti==-1||tj==-1||ti==n||tj==n){
                        continue;
                    }
                    int tmask=ti*n+tj;
                    union_union(mask<<2|k,tmask<<2|(3-k));
                }
            }
        }
        int ans=0;
        for(i=0;i<n*n*4;++i){
            int fi=union_find(i);
            if(!hash[fi]){
                hash[fi]=1;;
                ans++;
            }
        }
        return ans;
    }
};

4.851. 喧闹和富有

原题链接


        有一组 n 个人作为实验对象,从 0 到 n - 1 编号,其中每个人都有不同数目的钱,以及不同程度的安静值(quietness)。为了方便起见,我们将编号为 x 的人简称为 "person x "。

        给你一个数组 richer ,其中 richer[i] = [ai, bi] 表示 person ai 比 person bi 更有钱。另给你一个整数数组 quiet ,其中 quiet[i] 是 person i 的安静值。richer 中所给出的数据 逻辑自洽(也就是说,在 person x 比 person y 更有钱的同时,不会出现 person y 比 person x 更有钱的情况 )。

        现在,返回一个整数数组 answer 作为答案,其中 answer[x] = y 的前提是,在所有拥有的钱肯定不少于 person x 的人中,person y 是最安静的人(也就是安静值 quiet[y] 最小的人)。

        示例 1:

        输入:richer = [[1,0],[2,1],[3,1],[3,7],[4,3],[5,3],[6,3]], quiet = [3,2,5,4,6,1,7,0]

        输出:[5,5,2,5,4,5,6,7]

        示例 2:

        输入:richer = [], quiet = [0]

        输出:[0]

        提示:

        n == quiet.length

        1 <= n <= 500

        0 <= quiet[i] < n

        quiet 的所有值 互不相同

        0 <= richer.length <= n * (n - 1) / 2

        0 <= ai, bi < n

        ai != bi

        richer 中的所有数对 互不相同

        对 richer 的观察在逻辑上是一致的


        这道题没什么好说的,先根据richer建立边集,其中每个点的边集元素都是比他富有的人,然后我们去搜素所有点的边集,选出来最冷静的人即可。这里搜素我们要明白比我富有的人还富有的人一定比我还富有,但是题目不一定把这些人都给出来,所以要先建出来所有的边。

class Solution {
    vector<int> edges[510];
    int hash[510];
    void dfs(int u,int &ans,vector<int>&quiet){
        if(hash[u]){
            return ;
        }
        hash[u]=1;
        if(quiet[u]<quiet[ans]){
            ans=u;
        }
        for(int i=0;i<edges[u].size();++i){
            int v=edges[u][i];
            dfs(v,ans,quiet);
        }
    }
public:
    vector<int> loudAndRich(vector<vector<int>>& richer, vector<int>& quiet) {
        int i,n=quiet.size();
        for(i=0;i<quiet.size();++i){
            edges[i].clear();
        }
        for(i=0;i<richer.size();++i){
            int u=richer[i][0];
            int v=richer[i][1];
            edges[v].push_back(u);
        }
        vector<int> ans;
        for(i=0;i<n;++i){
            memset(hash,0,sizeof(hash));
            int num=i;
            dfs(i,num,quiet);
            ans.push_back(num);
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值