算法分析课程博客分享 5

算法分析课程博客分享 5


542. 01 矩阵(01 Matrix)

给定一个由 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。
  • 矩阵中的元素只在四个方向上相邻: 上、下、左、右。

解题思路:
刚开始的时候,我考虑过能否使用 O(V) 的复杂度来解决问题,但实际上,我们必须考虑所有的路径才可以得出结果。首先,我们对矩阵进行一次扫描,获得所有的 0 的位置,并将这些位置的距离值设为 0,其余位置的距离值设为 INT_MAX,然后从 0 开始进行广搜。对于一个 0 点,我们将其周围的四个点的距离值设为 1,然后将这四个点放入队列中;对于一个 1 点,若周围的一个点的距离值大于当前点距离值加一,我们就更新这个周围点的距离值,并将更新过的点放入队列中,否则不进行操作。具体代码如下:

public:
    vector<vector<int>> updateMatrix(vector<vector<int>>& matrix) {
        if (matrix.empty() || matrix[0].empty())
            return vector<vector<int>>();
        
        vector<vector<int>> dis(matrix.size(), vector<int>(matrix[0].size(), INT_MAX));
        queue<pair<int, int>> q;
        for (int i = 0; i < matrix.size(); ++i) {
            for (int j = 0; j < matrix[i].size(); ++j) {
                if (matrix[i][j] == 0) {
                    q.push(make_pair(i, j));
                    dis[i][j] = 0;
                }
            }
        }
        while (!q.empty()) {
            pair<int, int> cur = q.front();
            q.pop();
            for (int i = 0; i < 4; ++i) {
                int x = cur.first + dir[i][0], y = cur.second + dir[i][1];
                if (x >= 0 && x < matrix.size() && y >= 0 && y < matrix[0].size()) {
                    if (dis[x][y] > dis[cur.first][cur.second] + 1) {
                        dis[x][y] = dis[cur.first][cur.second] + 1;
                        q.push(make_pair(x, y));
                    }
                }
            }
        }
        return dis;
    }
    
private:
    int dir[4][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };



200. 岛屿的个数(Number of Islands)

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

示例 1:
输入:
11110
11010
11000
00000
输出: 1

示例 2:
输入:
11000
11000
00100
00011
输出: 3

解题思路:
这道题和上一题的思路类似,但是,因为这道题与广搜的路径无关,所以我们可以使用 O(V) 复杂度解决这个问题。我们首先对矩阵进行扫描,当检测到值为 1 的点的时候,代表我们发现了一个岛,这时候我们要令 count 的值加一。然后,我们从当前检测到的点开始进行广搜,把与其属于同一个岛的点全部设为 0,以确保我们在扫描矩阵时不会扫描到属于同一个岛的多个值为 1 的点。最后,count 的值就是岛屿的个数,具体代码如下:

public:
    int numIslands(vector<vector<char>>& grid) {
        int count = 0;
        for (int i = 0; i < grid.size(); ++i) {
            for (int j = 0; j < grid[0].size(); ++j) {
                if (grid[i][j] == '1') {
                    ++count;
                    bfs(grid, i, j);
                }
            }
        }
        return count;
    }
    
private:
    int dir[4][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };
    
    void bfs(vector<vector<char>>& grid, int x, int y) {
        if (grid[x][y] == '0')
            return;
        grid[x][y] = '0';
        for (int i = 0; i < 4; ++i) {
            int nextX = x + dir[i][0], nextY = y + dir[i][1];
            if (nextX >= 0 && nextX < grid.size() && nextY >= 0 && nextY < grid[0].size()) {
                bfs(grid, nextX, nextY);
            }
        }
    }



773. 滑动谜题(Sliding Puzzle)

在一个 2 x 3 的板上(board)有 5 块砖瓦,用数字 1&126;5 来表示, 以及一块空缺用 0 来表示。

一次移动定义为选择 0 与一个相邻的数字(上下左右)进行交换。

最终当板 board 的结果是 [[1,2,3],[4,5,0]] 谜板被解开。

给出一个谜板的初始状态,返回最少可以通过多少次移动解开谜板,如果不能解开谜板,则返回 -1 。

示例:

输入:board = [[1,2,3],[4,0,5]]
输出:1
解释:交换 0 和 5 ,1 步完成

输入:board = [[1,2,3],[5,4,0]]
输出:-1
解释:没有办法完成谜板

输入:board = [[4,1,2],[5,0,3]]
输出:5
解释:
最少完成谜板的最少移动次数是 5 ,
一种移动路径:
尚未移动: [[4,1,2],[5,0,3]]
移动 1 次: [[4,1,2],[0,5,3]]
移动 2 次: [[0,1,2],[4,5,3]]
移动 3 次: [[1,0,2],[4,5,3]]
移动 4 次: [[1,2,0],[4,5,3]]
移动 5 次: [[1,2,3],[4,5,0]]

输入:board = [[3,2,4],[1,5,0]]
输出:14

提示:

  • board 是一个如上所述的 2 x 3 的数组.
  • board&91;i&93;&91;j&93; 是一个 [0, 1, 2, 3, 4, 5] 的排列.

解题思路:
在参考了网上的解法后,我发现在这道题上可以应用很多巧妙的小技巧,所以觉得有必要在这里记录一下。
首先,我以前处理 bfs 的时候,基本上都是用一个矩阵来表示节点是否被访问过。但是,在这道题中,我们可能需要重复访问同一个节点,而且我们需要关注的是所有元素的位置是否被正确摆放。由于我们关注的不再是单独某一个点的值,我们需要将访问过的状态以哈希的方式储存起来,以通过 bfs 穷举所有的可能状态。根据题目的描述,我们可以保证输入矩阵的维度是 2 x 3,因此,实际上,我们可以用一个字符串来表示当前矩阵的状态。而当我们使用字符串表示时,我们就不能像平时一样来获得相邻的点的坐标。由于我们已经知道了矩阵的维度,对于一个点,其相邻的点的坐标其实可以提前计算出来。因此,我们使用一个 dir 数组作为边缘链表,以在 bfs 时获得相邻的点。具体代码如下:

public:
    int slidingPuzzle(vector<vector<int>>& board) {
        string dest = "123450";
        string dept = "";
        for (int i = 0; i < 2; ++i) {
            for (int j = 0; j < 3; ++j) {
                dept += char(board[i][j]+'0');
            }
        }
        
        int step = 0;
        unordered_set<string> visited{dept};
        queue<string> q{ {dept} };
        while (!q.empty()) {
            for (int i = q.size(); i > 0; --i) {
                string cur = q.front();
                q.pop();
                if (cur == dest)
                    return step;
                int zero = cur.find('0');
                for (int adj : dir[zero]) {
                    string next = cur;
                    swap(next[zero], next[adj]);
                    if (visited.find(next) == visited.end()) {
                        visited.insert(next);
                        q.push(next);
                    }
                }
            }
            ++step;
        }
        return -1;
    }
    
private:
    vector<vector<int>> dir{ {1, 3}, {0, 2, 4}, {1, 5}, {0, 4}, {1, 3, 5}, {2, 4} };


考虑到上述的方法只能应用在维数已确定的矩阵上,我们在这里提出一种泛用性更强的解法。

public:
    int slidingPuzzle(vector<vector<int>>& board) {
        int r = board.size(), c = board[0].size();
        state dest(vector<vector<int>>{ {1, 2, 3}, {4, 5, 0} }, make_pair(r-1, c-1));
        state dept;
        for (int i = 0; i < r; ++i) {
            for (int j = 0; j < c; ++j) {
                if (board[i][j] == 0) {
                    dept = state(board, make_pair(i, j));
                }
            }
        }
        
        int step = 0;
        unordered_set<state, myHash> visited{dept};
        queue<state> q{ {dept} };
        while (!q.empty()) {
            for (int i = q.size(); i > 0; --i) {
                state cur = q.front();
                q.pop();
                if (cur == dest)
                    return step;
                for (int j = 0; j < 4; ++j) {
                    int x = cur.zero.first + dir[j][0], y = cur.zero.second + dir[j][1];
                    if (x >= 0 && x < r && y >= 0 && y < c) {
                        state next = state(cur.board, make_pair(x, y));
                        swap(next.board[cur.zero.first][cur.zero.second], next.board[x][y]);
                        if (visited.find(next) == visited.end()) {
                            visited.insert(next);
                            q.push(next);
                        }
                    }
                }
            }
            ++step;
        }
        return -1;
    }
    
private:
    struct state {
        state() {}
        state(vector<vector<int>> board, pair<int, int> zero) : board(board), zero(zero) {}
        state& operator=(const state& other) {
            this->board = other.board;
            this->zero = other.zero;
            return *this;
        }
        bool operator==(const state& other) const {
            return this->board == other.board;
        }
        
        vector<vector<int>> board;
        pair<int, int> zero;
    };
    
    struct myHash {
        size_t operator()(const state& s) const {
            return s.zero.first + s.zero.second;
        }
    };
    
    int dir[4][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };