Leetcode #785 判断二分图

题名:判断二分图
描述:
给定一个无向图graph,当这个图为二分图时返回true。

如果我们能将一个图的节点集合分割成两个独立的子集A和B,并使图中的每一条边的两个节点一个来自A集合,一个来自B集合,我们就将这个图称为二分图。

graph将会以邻接表方式给出,graph[i]表示图中与节点i相连的所有节点。每个节点都是一个在0到graph.length-1之间的整数。这图中没有自环和平行边: graph[i] 中不存在i,并且graph[i]中没有重复的值。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/is-graph-bipartite/

方法:DFS + 二分图概念

什么是二分图:如果对图中每一个顶点上色(只有2种颜色),如果任一顶点的相邻顶点颜色与其不同,则称改图为二分图。

DFS用于遍历图的各个顶点,用0表示黑色,1表示蓝色,顶点初始值为-1表示无色,然后模拟上色过程即可。
有一点需要注意,如果用vector来存储colors很容易超时,用int数组来存就不会。


    bool dfs(vector<vector<int>>& graph, int *colors, int curNode, int curColor)
    {
        colors[curNode] = curColor;//上色
        for(auto w : graph[curNode])
        {
            if(colors[w] == -1){//如果没上过色
                if(!dfs(graph, colors, w, 1 - curColor))
                    return false;
            }  
            else if(colors[w] == curColor)  return false;//如果颜色与相邻顶点相同,返回false       
        }
        return true;
    }
    bool isBipartite(vector<vector<int>>& graph) {
        int colors[110];//表示是顶点i的颜色
        fill(colors, colors + 110, -1);//初始化为未上色
        for(int i = 0;i < graph.size();i++)//防止出现非连通图
        {
            if(colors[i] == -1 && !dfs(graph, colors, i, 0))
                return false;
        }
        return true;
    }

Leetcode #684 冗余连接

题名:冗余连接
描述:
在本问题中, 树指的是一个连通且无环的无向图。

输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, ..., N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。

结果图是一个以边组成的二维数组。每一个边的元素是一对[u, v] ,满足 u < v,表示连接顶点u 和v的无向图的边。

返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v] 应满足相同的格式 u < v。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/redundant-connection/

这道题有两个思路,一是用DFS来遍历整个图并记录改图的联通分量,当连通分量仅为1时即找到了答案;第二种思路是用并查集;

方法一:

利用DFS遍历图并记录联通分量,常规操作,上代码:

    int maxn = 1000;
    int G[maxn][maxn];
    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        int vn = edges.size(), num;
        fill(G[0], G[0] + vn, 0);
        bool vis[vn]{false};//初始化vis为false
        int index = vn;
        for(int i = 0;i < vn;i++){//将图存储在邻接矩阵中
            G[edges[i][0] - 1][edges[i][1] - 1] = 1;
            G[edges[i][1] - 1][edges[i][0] - 1] = 1;
        }
        while(num != 1 && index-- >= 0){//从后往前遍历vector来找到正确的边,当删去该边时使得图的连通分量为1;(以防万一让index >= 0)
            int u = edges[index][0] - 1, v = edges[index][1] - 1;
            fill(vis, vis + vn, false);
            G[u][v] = 0;//删去对应的边
            G[v][u] = 0;//删去对应的边
            num = dfs(vn, vis);
            G[u][v] = 1;//添上对应的边
            G[v][u] = 1;//添上对应的边
        }
        return edges[index];
    }
    int dfs(int vn, bool *vis){
        int res = 0;
        for(int i = 0;i < vn;i++){
            if(!vis[i]){
                res++;//用于记录连通分量
                dfs(vn, i, vis);
            }
        }
        return res;
    }
    void dfs(int vn, int v, bool *vis){
        vis[v] = true;
        for(int i = 0;i < vn;i++){
            if(!vis[i] && G[v][i] == 1) dfs(vn, i, vis);
        }
    }

方法二:

用并查集的思路,首先来回顾一下并查集:
1.并查集有什么用呢?
它最大的作用就是用来检查一个图是否存在环。
2.并查集的操作分两步:一、并(Union) 二、查(Find)
常规写法:

    int maxn = 1001;
    int fa[maxn];
    int Find(int x){
        int a = x;
        while(x != fa[x]){
            x = fa[x];
        }//如果x的父亲不是它自己本身
        //对集合进行优化:
        while(a != fa[a]){
            int z = a;
            a = fa[a];
            fa[z] = x;
        }
        return x;
    }

    void Union(int a, int b){
        int af = Find(a);
        int bf = Find(b);
        if(af != bf)    fa[a] = bf;
    }

所以这题的完整代码如下:


vector<int> findRedundantConnection(vector<vector<int>>& edges){
        int vn = edges.size();
        int fa[vn + 1];
        vector<int> res;
        for(int i = 1;i <= vn;i++){
            fa[i] = i;
        }//初始化fa数组,使每个顶点的父亲都是其本身
        for(int i = 0;i < vn;i++){
            int u = edges[i][0], v = edges[i][1];
            if(!Union(fa, u, v)){//如果合并失败说明图中出现了环,所以这条边就是产生环的边,就是答案
                res.push_back(u);
                res.push_back(v);
                break;
            }
        }
        return res;
    }
    int Find(int *fa, int x){
        int a = x;
        while(x != fa[x]){
            x = fa[x];
        }
        while(a != fa[a]){
            int z = a;
            a = fa[a];
            fa[z] = x;
        }
        return x;
    }
    bool Union(int *fa, int a, int b){
        int af = Find(fa, a);
        int bf = Find(fa, b);
        if(af != bf){
            fa[af] = bf;
            return true;
        }
        return false;
    }

Leetcode #133 克隆图

题名:克隆图
描述:
给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。

图中的每个节点都包含它的值 val(int) 和其邻居的列表(list[Node])。

测试用例格式:

简单起见,每个节点的值都和它的索引相同。例如,第一个节点值为 1,第二个节点值为 2,以此类推。该图在测试用例中使用邻接列表表示。

邻接列表是用于表示有限图的无序列表的集合。每个列表都描述了图中节点的邻居集。

给定节点将始终是图中的第一个节点(值为 1)。你必须将 给定节点的拷贝 作为对克隆图的引用返回。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/clone-graph/

EXAMPLE:

输入:adjList = [[2,4],[1,3],[2,4],[1,3]]
输出:[[2,4],[1,3],[2,4],[1,3]]
解释:
图中有 4 个节点。
节点 1 的值是 1,它有两个邻居:节点 2 和 4 。
节点 2 的值是 2,它有两个邻居:节点 1 和 3 。
节点 3 的值是 3,它有两个邻居:节点 2 和 4 。
节点 4 的值是 4,它有两个邻居:节点 1 和 3 。

方法: DFS

用DFS遍历每个图节点,new一个新对象并通过构造函数进行val上的复制,然后继续DFS并将新生成的结点存储到邻接表中。代码如下:


    bool vis[105] = {false};
    Node* cloneGraph(Node* node) {
        if(!node)   return NULL;
        map<int, Node *> mp;//因为val唯一标识一个结点,所以用map存储val值到结点指针的映射;
        Node *ans = NULL;
        ans = dfs(node, mp);
        return ans;
    }

    Node *dfs(Node* node, map<int, Node *> &mp){
        vis[node->val] = true;
        Node *nn = new Node(node->val);//注意是深拷贝,不能用 Node(node->val, node->neighbor) 这个构造函数
        mp[node->val] = nn;//存储映射
        for(int i = 0;i < node->neighbors.size();i++){
            Node *tmp = node->neighbors[i];//初始化tmp为邻接表中的图结点指针
            if(!vis[tmp->val]){//如果没访问过,也就是该节点还没new过,那么去new这个节点
                tmp = dfs(tmp, mp);
                nn->neighbors.push_back(tmp);
            }
            else//如果已经new过该节点,将该节点存储到邻接表中
                nn->neighbors.push_back(mp[tmp->val]);
        }
        return nn;
    }

Leetcode #130 被围绕的区域

题名:被围绕的区域
描述:
给定一个二维的矩阵,包含 'X' 和 'O'(字母 O)。

找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。

示例:

X X X X
X O O X
X X O X
X O X X

运行你的函数后,矩阵变为:

X X X X
X X X X
X X X X
X O X X

解释:
被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/surrounded-regions/

方法:DFS

想法:只需要找出图的边界上O,以及与这个O相连接的区域,将该区域保留,其他区域都置为X即可。
1.对边界上字母为O的点开始DFS。
2.将DFS遍历到的字母置为N
3.结束DFS后,对矩阵进行从上到下遍历,将字母为N的点置为O,其他点置为X

代码如下:

    bool vis[1000][1000];
    void solve(vector<vector<char>>& board) {
        if(!board.size())   return;
        int x = board.size(), y = board[0].size();
        for(int i = 0;i < x;i++){
            if(i == 0 || i == x - 1){
                for(int j = 0;j < y;j++){
                    if(!vis[i][j] && board[i][j] == 'O')
                        dfs(board, i, j, x, y);
                }
            }
            else{
                if(!vis[i][0] && board[i][0] == 'O')
                    dfs(board, i, 0, x, y);
                if(!vis[i][y - 1] && board[i][y - 1] == 'O')
                    dfs(board, i, y - 1, x, y);
            }
        }
        for(int i = 0;i < x;i++){
            for(int j = 0;j < y;j++){
                if(board[i][j] == 'N')
                    board[i][j] = 'O';
                else if(board[i][j] == 'O')
                    board[i][j] = 'X';
            }
        }
    }
    void dfs(vector<vector<char>>& board, int u, int v, int x, int y){
        vis[u][v] = true;
        board[u][v] = 'N';
        //将与顶点[u,v]相邻的(上下左右)且字母为 O 的点加入容器中,以备之后DFS使用。
        vector<pair<int, int> > vi;
        if(u - 1 >= 0 && board[u - 1][v] == 'O')
            vi.push_back(make_pair(u - 1, v));
        if(u + 1 < x && board[u + 1][v] == 'O')
            vi.push_back(make_pair(u + 1, v));
        if(v - 1 >= 0 && board[u][v - 1] == 'O')
            vi.push_back(make_pair(u, v - 1));
        if(v + 1 < y && board[u][v + 1] == 'O')
            vi.push_back(make_pair(u, v + 1));
        for(int i = 0;i < vi.size();i++){
            int a = vi[i].first, b = vi[i].second;
            if(!vis[a][b])
                dfs(board, a, b, x, y);
        }
    }

Leetcode #13 机器人的运动范围

题名:机器人的运动范围
描述:
地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

示例1:

输入:m = 2, n = 3, k = 1
输出:3

示例2:

输入:m = 3, n = 1, k = 0
输出:1

提示:

1 <= n,m <= 100
0 <= k <= 20

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/

方法:直接DFS

代码如下:


ool vis[101][101] = {false};
    int movingCount(int m, int n, int k) {
        //vector<vector<bool> > vis(m, vector<bool> (n, false));
        return dfs(0, 0, m, n, k);
    }
    int dfs(int u, int v, int m, int n, int k){
        if(u < 0 || v < 0 || u >= m || v >= n || vis[u][v] ||
            u % 10 + u / 10 + v % 10 + v / 10 > k)
            return 0;
        vis[u][v] = true;
        return dfs(u - 1, v, m, n, k) +
               dfs(u + 1, v, m, n, k) +
               dfs(u, v - 1, m, n, k) +
               dfs(u, v + 1, m, n, k) + 1;
    }
posted @ 2020-02-24 12:15  Codroc  阅读(137)  评论(0编辑  收藏  举报