饭后小甜点leetcode——图的遍历

文章目录

      • DFS
        • 以图判树
        • 求无向图连通分量
        • 在遍历的时候染色
          • 判断二分图
          • 可能的二分法
          • 拓扑排序
      • BFS
        • 求最短路
          • 01 矩阵
          • 单词接龙
            • 求路径长度
            • 把最短路径打印出来
        • 岛屿数量

图的遍历一般有两种,DFS(深度优先搜索)和BFS(广度优先搜索),这两种基础的遍历上可以衍生出很多算法问题。

DFS

以图判树

【题意】给一个图,问这个图是不是树。
【思路】从0节点出发,遍历一次,遍历的时候更新visited数组,记录访问情况,如果遍历完以后,有顶点没有被遍历到或者有环,则说明不是树。
【易错点】注意如果graph用邻接表表示,则在dfs中,要把反向边对应点删掉。
【邻接矩阵实现代码】

public class Solution {
    public bool ValidTree(int n, int[][] edges) {
        var graph = new bool[n,n];
        var visit = new bool[n];
        bool ans = false;
        
        foreach(var e in edges){
            graph[e[0],e[1]] = true;
            graph[e[1],e[0]] = true;
        }
        
        ans = dfs(0,visit,graph);
        for(int i = 0;i < n; ++i){
            if(!visit[i]){
                return false;//森林
            }
        }
        
        return ans;
    }
    
    public bool dfs(int curr, bool[] visit, bool[,] graph){
        bool res = true;
        visit[curr] = true;

        for(int i = 0;i < graph.GetLength(0); ++i){
            if(graph[curr,i]){
                if(visit[i]){
                    return false;//有环
                }
                visit[i] = true;
                graph[curr,i] = false;//把两个节点之间的边抹掉
                graph[i,curr] = false;//把两个节点之间的边抹掉
                res &= dfs(i,visit,graph);
                if(!res) return false;//只要有一次false就直接返回false
            }
        }
        return res;
    }
}

【邻接表实现代码】

public class Solution {
    public bool ValidTree(int n, int[][] edges) {
        var graph = new List<int>[n];
        var visit = new bool[n];
        bool ans = false;
        for(var i=0;i<n;i++){
            graph[i] = new List<int>();
        }
        
        foreach(var e in edges){
            graph[e[0]].Add(e[1]);
            graph[e[1]].Add(e[0]);
        }
        
        ans = dfs(0,visit,graph);
        for(int i = 0;i < n; ++i){
            if(!visit[i]){
                return false;//森林
            }
        }
        
        return ans;
    }
    
    public bool dfs(int curr, bool[] visit, List<int>[] graph){
        bool res = true;
        visit[curr] = true;

        for(int i = 0;i < graph[curr].Count; ++i){
            if(visit[graph[curr][i]]){
                return false;//有环
            }
            visit[graph[curr][i]] = true;
            graph[graph[curr][i]].Remove(curr);//注意这儿,要从graph[curr][i]的list中删掉curr
            res &= dfs(graph[curr][i],visit,graph);
            if(!res) return false;//只要有一次false就直接返回false
        }
        return res;
    }
}

求无向图连通分量

323. 无向图中连通分量的数目
这道题就是DFS一个很基础的应用,求连通分量的个数,每轮dfs完了以后,该轮遍历到的节点都会被标为已访问,标不成的说明跟这个节点不连通,所以每轮dfs完成后,连通分量的count就可以加一了。
【易错点】

  • 一个是建立邻接表的时候,因为是无向图,注意把两个方向的边都添加一下。
  • 另一个就是在dfs之前一定要判断一下是否visited为false,如果当前节点已经访问过了就不要再访问了,因为每轮dfs后都会count++,否则count就不准了。
public class Solution {
    List<int>[] graph;
    bool[] visited;
    int count;
    public int CountComponents(int n, int[][] edges) {
    	//初始化各个变量
        graph = new List<int>[n];
        visited = new bool[n];
        for(var i=0;i<n;i++){
            graph[i] = new List<int>();
        }
        //建立邻接表式的图
        for(var i=0;i<edges.Length;i++){
            graph[edges[i][0]].Add(edges[i][1]);
            graph[edges[i][1]].Add(edges[i][0]);
        }
        //对每个节点遍历求连通分量个数
        for(var i=0;i<n;i++){
            if(!visited[i]){
                dfs(i);
                count++;
            }
        }
        return count;
    }
    
    public void dfs(int v){
    	//标记v节点为已访问
        visited[v] = true;
        //对于v节点的每一个相邻节点,递归dfs
        foreach(var w in graph[v]){
            if(!visited[w]){
                dfs(w);
            }
        }
    }
}

在遍历的时候染色

判断二分图
/// 
/// 785. 判断二分图
/// 
public class BipartitionGraph
{
    Dictionary<int, int> dict = new Dictionary<int, int>();
    public bool IsBipartite(int[][] graph)
    {
        for (var i = 0; i < graph.Length; i++)
        {
            if (!dict.ContainsKey(i) && !Visit(graph, 1, i))
            {
                return false;
            }
        }
        return true;
    }

    public bool Visit(int[][] graph, int color, int v)
    {
        if (dict.ContainsKey(v))
        {
            return dict[v] == color;
        }

        dict.Add(v, color);
        foreach (var w in graph[v])
        {
            if (!Visit(graph, -color, w))
                return false;
        }
        return true;
    }
}

可能的二分法
/// 
/// 886. 可能的二分法
/// 
public class PossibleBipartitionGraph
{
    List<int>[] graph;
    Dictionary<int, int> color = new Dictionary<int, int>();

    public bool PossibleBipartition(int N, int[][] dislikes)
    {
        graph = new List<int>[N + 1];
        for (int i = 1; i <= N; ++i)
            graph[i] = new List<int>();

        foreach (int[] edge in dislikes)
        {
            // 注意在构造邻接表的时候,两个方向的边都要添加一下
            graph[edge[0]].Add(edge[1]);
            graph[edge[1]].Add(edge[0]);
        }

        for (int node = 1; node <= N; ++node)
            if (!color.ContainsKey(node) && !dfs(node, 0))
                return false;
        return true;
    }

    public bool dfs(int node, int c)
    {
        if (color.ContainsKey(node))
            return color[node] == c;
        color.Add(node, c);

        foreach (int nei in graph[node])
            if (!dfs(nei, c ^ 1))
                return false;
        return true;
    }
}

拓扑排序

210. 课程表 II

public class CourceSchedule
{
    public int[] Color;
    public List<int> Routinue = new List<int>();
    public int[] FindOrder(int numCourses, int[][] prerequisites)
    {
        var graph = new List<int>[numCourses];
        Color = new int[numCourses];
        for (var i = 0; i < numCourses; i++)
            graph[i] = new List<int>();
        for (var i = 0; i < prerequisites.Length; i++)
            graph[prerequisites[i][1]].Add(prerequisites[i][0]);
        for (var i = 0; i < numCourses; i++)
            if (!DFS(graph, i))
                return new int[0];

        return Routinue.ToArray();
    }

    public bool DFS(List<int>[] graph, int i)
    {
        if (Color[i] != 0)
            return Color[i] == 2;
        Color[i] = 1;
        for (var j = 0; j < graph[i].Count; j++)
        {
            var u = graph[i][j];
            if (Color[u] == 2) continue;
            if (Color[u] == 1 || !DFS(graph, u))
                return false;
        }
        Color[i] = 2;
        Routinue.Insert(0, i);
        return true;
    }
}

329. 矩阵中的最长递增路径
给定一个整数矩阵,找出最长递增路径的长度。

对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。

示例 1:

输入: nums =
[
[9,9,4],
[6,6,8],
[2,1,1]
]
输出: 4
解释: 最长递增路径为 [1, 2, 6, 9]。
示例 2:

输入: nums =
[
[3,4,5],
[3,2,6],
[2,2,1]
]
输出: 4
解释: 最长递增路径是 [3, 4, 5, 6]。注意不允许在对角线方向上移动。

链接:https://leetcode-cn.com/problems/longest-increasing-path-in-a-matrix

public class LongestSeqInMatrix
{
    public int[][] dirs = { new int[] { 0, 1 }, new int[] { 1, 0 }, new int[] { 0, -1 }, new int[] { -1, 0 } };
    public int LongestIncreasingPath(int[,] matrix)
    {
        if (matrix.Length == 0) return 0;
        int m = matrix.GetLength(0), n = matrix.GetLength(1);
        var cache = new int[m, n];
        int max = 1;
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
            {
                int len = dfs(matrix, i, j, m, n, cache);
                max = Math.Max(max, len);
            }
        }
        return max;
    }

    public int dfs(int[,] matrix, int i, int j, int m, int n, int[,] cache)
    {
        if (cache[i, j] != 0) return cache[i, j];
        int max = 1;
        foreach (var dir in dirs)
        {
            int x = i + dir[0], y = j + dir[1];
            if (x < 0 || x >= m || y < 0 || y >= n || matrix[x, y] <= matrix[i, j]) continue;
            int len = 1 + dfs(matrix, x, y, m, n, cache);
            max = Math.Max(max, len);
        }
        cache[i, j] = max;
        return max;
    }
}

BFS

求最短路

01 矩阵

leetcode题目地址
给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。
两个相邻元素间的距离为 1 。

示例 1:
输入:
0 0 0
0 1 0
0 0 0

输出:
0 0 0
0 1 0
0 0 0

示例 2:
输入:
0 0 0
0 1 0
1 1 1

输出:
0 0 0
0 1 0
1 2 1

注意:
给定矩阵的元素个数不超过 10000。
给定矩阵中至少有一个元素是 0。
矩阵中的元素只在四个方向上相邻: 上、下、左、右。

public int[][] UpdateMatrix(int[][] matrix) {
   if (matrix.Length == 0 || matrix[0].Length == 0) {
        return matrix;
    }
    //初始化distance[][]
    int[][] dis = new int[matrix.Length][];
    for(var i=0;i<matrix.Length;i++){
        dis[i] = new int[matrix[i].Length];
    }
    int range = matrix.Length * matrix[0].Length;

    for (int i = 0; i < matrix.Length; i++) {
        for (int j = 0; j < matrix[0].Length; j++) {
            if (matrix[i][j] == 0) {
                dis[i][j] = 0;
            } else {
                int upCell = (i > 0) ? dis[i - 1][j] : range;
                int leftCell = (j > 0) ? dis[i][j - 1] : range;
                dis[i][j] = Math.Min(upCell, leftCell) + 1;
            }
        }
    }

    for (int i = matrix.Length - 1; i >= 0; i--) {
        for (int j = matrix[0].Length - 1; j >= 0; j--) {
            if (matrix[i][j] == 0) {
                dis[i][j] = 0;
            } else {
                int downCell = (i < matrix.Length - 1) ? dis[i + 1][j] : range;
                int rightCell = (j < matrix[0].Length - 1) ? dis[i][j + 1] : range;
                dis[i][j] = Math.Min(Math.Min(downCell, rightCell) + 1, dis[i][j]);
            }
        }
    }

    return dis;
}

单词接龙
求路径长度

127. 单词接龙
【题目】
给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:

每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。

说明:
如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。

示例 1:
输入:
beginWord = “hit”,
endWord = “cog”,
wordList = [“hot”,“dot”,“dog”,“lot”,“log”,“cog”]

输出: 5

解释: 一个最短转换序列是 “hit” -> “hot” -> “dot” -> “dog” -> “cog”,
返回它的长度 5。

示例 2:
输入:
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,“dot”,“dog”,“lot”,“log”]

输出: 0

解释: endWord “cog” 不在字典中,所以无法进行转换。

【易错点】注意一定要有一个used来保存已经产生过的变形词,对于即将生产出来的新的一层不能有以前层出现过的任何一个变形词。

public class Solution {
    public int LadderLength(string start, string end, IList<string> wordList) {
        var dict = new HashSet<string>(wordList);
        if(!dict.Contains(end)) return 0;
        
        var curr = new Queue<string>();
        var used = new HashSet<string>();
        used.Add(start);
        curr.Enqueue(start);
        int count = 1;
        if(start==end) return count;
        while(curr.Count!=0){
            var n = curr.Count;
            count++;
            while(n-->0){
                var word = curr.Dequeue();
                //获取单词的所有变体
                foreach(var transform in GetTransform(word, used, dict)){
                    if(transform == end){
                        return count;
                    }
                    curr.Enqueue(transform);//加入到下一轮的单词
                }
            }
        }
        return 0;
    }
    
    List<string> GetTransform(string word, HashSet<string> used, HashSet<string> dict){
        var letters = word.ToArray();
        var res = new List<string>();
        for(var i=0;i<letters.Length;i++){
            //每个字母做替换
            var orginal = letters[i];
            for(var c = 'a'; c<='z'; c++){
                if(orginal != c){
                    letters[i] = c;
                    var trans = new string(letters);
                    //如果变体直接等于end,或者它在字典里却没被用过,则加入
                    if(!used.Contains(trans) && dict.Contains(trans)){
                        used.Add(trans);
                        res.Add(trans);
                    }
                }
            }
            //恢复回原来的字符
            letters[i] = orginal;
        }
        return res;
    }
}
把最短路径打印出来

126. 单词接龙 II

public class Solution {
    public IList<IList<string>> FindLadders(string beginWord, string endWord, IList<string> wordList) {
        IList<IList<string>> ret = new List<IList<string>>();
        HashSet<string> dict = wordList.ToHashSet();
        if(!dict.Contains(endWord)) return ret;
        
        HashSet<string> forward = new HashSet<string>{ beginWord };
        HashSet<string> backward = new HashSet<string> { endWord };
        
        Dictionary<string, List<string>> map = new Dictionary<string, List<string>>();
        buildmap(map, dict, forward, backward, true);   
        
        findpaths(ret, map, new List<string>{ beginWord }, beginWord, endWord);        
        
        return ret;
    }
    
    public void findpaths(IList<IList<string>> ret, Dictionary<string, List<string>> map, List<string> pre,string start, string end)
    {
        if(start == end){
            ret.Add(pre);
            return;
        }
        if(!map.ContainsKey(start)) return;
        
        foreach(string item in map[start]){
            List<string> next = new List<string>(pre);
            next.Add(item);
            findpaths(ret, map, next, item, end);
        }        
    }
    
    public void buildmap(Dictionary<string, List<string>> map, HashSet<string> dict, HashSet<string> fo, HashSet<string> ba, bool asc)
    {
        if(fo.Count == 0) return;
        
        if(fo.Count > ba.Count){
            buildmap(map, dict, ba, fo, !asc);
            return;
        }
        
        foreach(string str in fo) dict.Remove(str);
        foreach(string str in ba) dict.Remove(str);
        
        bool got = false;
        HashSet<string> next = new HashSet<String>();
        foreach(string str in fo){
            char[] chars = str.ToCharArray();
            for(int i = 0; i < str.Length; i++){
                for(char c = 'a'; c <= 'z'; c++){
                    if(str[i] == c) continue;
                    chars[i] = c;
                    string word = new string(chars);                       
                    string key = asc ? str : word;
                    string val = asc ? word : str;
                    
                    if(ba.Contains(word)){
                        if(!map.ContainsKey(key)) map.Add(key, new List<string>());
                        map[key].Add(val);  
                        got = true;
                    }
                    else if(dict.Contains(word)){                          
                        if(!map.ContainsKey(key)) map.Add(key, new List<string>());
                        map[key].Add(val);                       
                        
                        if(!got && !next.Contains(word)) next.Add(word);
                    }                    
                }                
                chars[i] = str[i];
            }
        }
        
        if(!got) buildmap(map, dict, ba, next, !asc);        
    }
}

岛屿数量

给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。

示例 1:

输入:
11110
11010
11000
00000

输出: 1
示例 2:

输入:
11000
11000
00100
00011

输出: 3

链接:https://leetcode-cn.com/problems/number-of-islands
【代码–DFS】

public class Solution {
    public int NumIslands(char[][] grid) {
        if (grid == null || grid.Length == 0) {
          return 0;
        }

        int nr = grid.Length;
        int nc = grid[0].Length;
        int num_islands = 0;
        for (int r = 0; r < nr; ++r) {
          for (int c = 0; c < nc; ++c) {
            if (grid[r][c] == '1') {
              ++num_islands;
              dfs(grid, r, c);
            }
          }
        }

        return num_islands;
    }
    
    void dfs(char[][] grid, int r, int c) {
        if (r < 0 || c < 0 || r >= grid.Length || c >= grid[0].Length || grid[r][c] == '0') {
        	return;
        }

        grid[r][c] = '0';
        dfs(grid, r - 1, c);
        dfs(grid, r + 1, c);
        dfs(grid, r, c - 1);
        dfs(grid, r, c + 1);
    }
}

【代码–BFS】

public class Solution {
    public class Node{
        public int x;
        public int y;
        public Node(int x, int y){
            this.x = x;
            this.y = y;
        }
    }
    public int NumIslands(char[][] grid) {
        var m = grid.Length;
        if(m==0) return 0;
        var n = grid[0].Length;
        var res = 0;
        var dirt = new int[][]{new int[]{0,1},new int[]{1,0},new int[]{0,-1},new int[]{-1,0}};
        for(var i=0;i<m;i++){
            for(var j=0;j<n;j++){
                if(grid[i][j]=='1'){
                    res++;
                    bfs(dirt, grid, i, j, m, n);
                }
            }
        }
        return res;
    }
    public void bfs(int[][] dirt, char[][] grid, int i, int j, int m, int n){
        var queue = new Queue<Node>();
        queue.Enqueue(new Node(i,j));
        while(queue.Count!=0){
            var cnt = queue.Count;
            while(cnt-->0){
                var next = queue.Dequeue();
                if(grid[next.x][next.y]=='1'){
                    grid[next.x][next.y]='0';
                    foreach(var d in dirt){
                        var newx = next.x+d[0];
                        var newy = next.y+d[1];
                        if(newx<0||newy<0||newx>=m||newy>=n||grid[newx][newy]!='1'){
                            continue;
                        }
                        queue.Enqueue(new Node(newx, newy));
                    }
                }
            }
        }
    }
}

你可能感兴趣的:(基础算法)