1. 图论建模

大多数的时候我们遇到的问题都不会直接说明这是一个图论的问题,然后你需要使用什么方法来解决。大多数的时候是需要我们自己对问题进行抽象,然后将问题建模成一个图论的问题,然后求解。图论的问题求解过程绝大多数还是使用的dfs或者是bfs。不同的是对于不同的问题,我们需要在遍历的过程中记录我们需要的信息。这一篇中通过解决一些LeetCode上的问题来体会图论建模。

首先来看一个简单的题目,这个问题中我们不需要对问题进行建模。LeetCode785, 给一个无向图,判断是否是二分图。我们使用bfs来进行求解,当然对于这个问题,我们也可以使用dfs进行求解。这个问题在前面两篇中都讲解过。

给定一个无向图graph,当这个图为二分图时返回true。如果我们能将一个图的节点集合分割成两个独立的子集A和B,并使图中的每一条边的两个节点一个来自A集合,一个来自B集合,我们就将这个图称为二分图。graph将会以邻接表方式给出,graph[i]表示图中与节点i相连的所有节点。每个节点都是一个在0到graph.length-1之间的整数。这图中没有自环和平行边: graph[i] 中不存在i,并且graph[i]中没有重复的值。

class Solution {
public:
    vector<bool> visited;
    vector<int> color;
    bool bfs(vector<vector<int>>& graph, int s, int c){
        queue<int> q;
        q.push(s);
        visited[s] = true;
        color[s] = c;
        while(!q.empty()){
            int v = q.front();
            q.pop();

            for(auto w : graph[v])
            {
                if(!visited[w]){
                    q.push(w);
                    visited[w] = true;
                    color[w] = 1 - color[v];
                }
                else if(color[w] == color[v])
                    return false;
            } 
        }
        return true;
    }

    bool isBipartite(vector<vector<int>>& graph) {
        visited = vector<bool>(graph.size(), false);
        color = vector<int>(graph.size(), -1);
        for(size_t i=0;i<graph.size();++i)
        {
            if(!visited[i])
                if(bfs(graph, i, 0) == false)
                return false;
        }
        return true;
    }
};

2. Floodfill

leetcode 695 岛屿的最大面积

func maxAreaOfIsland(grid [][]int) (area int) {
	var dfs func(i, j int) int
	if len(grid) <= 0{
		return 0
	}
	MaxI, MaxJ := len(grid), len(grid[0])

	dfs = func(i, j int) int {
		if i < 0 || i > MaxI-1 || j < 0 || j > MaxJ-1 {
			return 0
		}
		v := grid[i][j]
		if v != 1 {
			return 0
		}
		grid[i][j] = -1
		return 1 + dfs(i-1, j) + dfs(i+1, j) + dfs(i, j-1) + dfs(i, j+1)
	}

	for i := 0; i < MaxI; i++ {
		for j := 0; j < MaxJ; j++ {
			if grid[i][j] == 1 {
				tmp := dfs(i, j)
				if tmp > area {
					area = tmp
				}
			}
		}
	}
	return area
}

3. Floodfill的应用

leetcode #200 岛屿数量

func numIslands(grid [][] byte)int{
	if len(grid) == 0 || len(grid[0]) == 0 {
		return 0
	}
	dir := [][]int{{-1,0},{1, 0}, {0, -1}, {0, 1}}
	MaxI, MaxJ := len(grid), len(grid[0])
	res := 0
	var dfs func(i, j int)
	dfs = func(i, j int){
		if i < 0 || i > MaxI - 1 || j < 0 || j > MaxJ-1 {
			return
		}
		if grid[i][j] != 1{
			return
		}
		grid[i][j] = 0
		for k:=0;k<len(dir);k++ {
			dfs(i+dir[k][0], j+dir[k][1])
		}
	}
	for  i:=0;i<MaxI;i++{
		for j:=0;j<MaxJ;j++ {
			if grid[i][j] == 1 {
				dfs(i, j)
				res ++
			}
		}
	}
	return res
}

leetcode #1020 飞地的数量

func numEnclaves(A [][]int) int {
	if len(A) == 0 || len(A[0]) == 0 {
		return 0
	}
	MaxI, MaxJ := len(A), len(A[0])
	var dfs func(i, j int)

	dfs = func(i, j int) {
		dir := [][]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}
		if i < 0 || j < 0 || i > MaxI-1 || j > MaxJ-1 {
			return
		}
		if A[i][j] == 0 {
			return
		}
		A[i][j] = 0
		for k := 0; k < len(dir); k++ {
			dfs(i+dir[k][0], j+dir[k][1])
		}
	}

	// 对边界上的点进行深度优先遍历,将其都写成0,剩下的1就是不能到达边界的了
	for i := 0; i < MaxI; i++ {
		for j := 0; j < MaxJ; j++ {
			isEdge := (i == 0) || (j == 0) || (i == MaxI-1) || (j == MaxJ-1)
			if isEdge && A[i][j] == 1 {
				dfs(i, j)
			}
		}
	}

	//统计剩下1的数量
	res := 0
	for i := 0; i < MaxI; i++ {
		for j := 0; j < MaxJ; j++ {
			if A[i][j] == 1 {
				res++
			}
		}
	}
	return res
}

leetcode #130 被围绕的区域

func solve(board [][]byte){
	if len(board) < 3 || len(board[0]) < 3{
		return
	}
	m, n := len(board), len(board[0])

	var dfs func(i, j int)

	dfs = func(i, j int) {
		dir := [][]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}
		if i < 0 || j < 0 || i > m-1 || j > n-1 {
			return
		}
		if board[i][j] == 'X' || board[i][j] == '$' {
			return
		}
		board[i][j] = '$'
		for k := 0; k < len(dir); k++ {
			dfs(i+dir[k][0], j+dir[k][1])
		}
	}

	for i:=0;i<m;i++ {
		for j:=0;j<n;j++ {
			isEdge := (i == 0) || (j == 0) || (i == m-1) || (j == n-1)
			if isEdge && board[i][j] == 'O' {
				dfs(i, j)
			}
		}
	}

	for i:=0;i<m;i++ {
		for j:=0;j<n;j++ {
			if board[i][j] == '$'{
				board[i][j] = 'O'
			} else if board[i][j] == 'O'{
				board[i][j] = 'X'
			}
		}
	}
}

leetcode #733 图像渲染

func floodFill(image [][]int, sr int, sc int, newColor int) [][]int {
    m, n := len(image), len(image[0])
	srcColor := image[sr][sc]
	var dfs func(i,j int)

	dfs = func(i, j int) {
		if i<0||i>m-1||j<0||j>n-1{
			return
		}
		if image[i][j] != srcColor || image[i][j] == newColor{
			return
		}
		image[i][j] = newColor
		dfs(i-1,j)
		dfs(i+1,j)
		dfs(i,j-1)
		dfs(i,j+1)
	}
	dfs(sr,sc)
	return image
}

leetcode #1034 边框着色

func colorBorder(grid [][]int, r0 int, c0 int, color int) [][]int {
	m, n := len(grid), len(grid[0])
	visited := make([]bool, m*n)
	srcColor := grid[r0][c0]
	var dfs func(i, j int) int
	dfs = func(i, j int) int {
		// 返回值: 0表示这个点不属于这个联通分量
		if i < 0 || i > m-1 || j < 0 || j > n-1 {
			return 0
		}
		// 因为后面修改了点的颜色属性,所以先要进行visited判断,如果遍历过,那么肯定属于这个连通分量的
		if visited[i*n+j] == true {
			return 1
		}
		//没有被遍历过,而且颜色和点击点不同,那么不属于这个连通分量
		if grid[i][j] != srcColor {
			return 0
		}

		visited[i*n+j] = true
		res := dfs(i-1, j) + dfs(i+1, j) + dfs(i, j-1) + dfs(i, j+1)
		if res < 4 {
			grid[i][j] = color
		}
		return 1
	}
	dfs(r0, c0)
	return grid
}

leetcode #529

leetcode #827

func largestIsland(grid [][]int) int {
	m, n := len(grid), len(grid[0])
	var dfs func(x, y, cc int) int
	area := make(map[int]int, m*n)
	visited := make([]int, m*n)
	dir := [][]int{{-1,0}, {1, 0}, {0, -1}, {0, 1}}

	dfs = func(x, y, cc int) int {
		if x < 0 || x > m-1 || y < 0 || y > n-1 || grid[x][y] != 1 {
			return 0
		}
		if visited[x*n+y] != 0 {
			return 0
		}
		visited[x*n+y] = cc
		return 1 + dfs(x-1, y, cc) + dfs(x+1, y, cc) + dfs(x, y-1, cc) + dfs(x, y+1, cc)
	}

	cc := 1
	for i := 0; i < m; i++ {
		for j := 0; j < n; j++ {
			if grid[i][j] == 1 && visited[i*n+j] == 0{
				area[cc] = dfs(i, j, cc)
				cc++
			}
		}
	}
	rt := 0
	for i := 0; i < m; i++ {
		for j := 0; j < n; j++ {
			resVal := 1
			if grid[i][j] == 0 {
				tmp := make(map[int]int, 4)
				for k := 0; k < len(dir);k++ {
					// 检查附近的4个点
					if i + dir[k][0] >= 0 && i+dir[k][0] < m && j+dir[k][1] >= 0 && j+dir[k][1] < n{
						x, y := i + dir[k][0], j + dir[k][1]
						v := visited[x*n + y]
						tmp[v] = 1
					}
				}
				for k, _ :=range tmp {
					resVal += area[k]
				}
				if resVal > rt{
					rt = resVal
				}
			}
		}
	}
	return rt
}