算法设计与分析(四)

200.Number of Islands

Given a 2d grid map of '1’s (land) and '0’s (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.

Example1

Input:
11110
11010
11000
00000
Output: 1

Example2

Input:
11000
11000
00100
00011
Output: 3

思路

看到题目的第一反应是,这种找连续“1”块的搜索当然是用DFS来做啦,但这周算法课讲了BFS(Breath-First-Search),本来我就对BFS不太熟练,于是就尝试着用BFS去做这道题了,过程真的是一波三折,且听我慢慢叙说。

基本的思路就是从左上角开始,从左往右,从上往下遍历整个01矩阵,当我找到某个值为“1”的点时(01竟然是以char类型存储的,也不知道是出于什么考虑,节省空间?),从这个点开始往上下左右四个方向延伸,找到整个连续的“1”块,把这些点的值变为“0”,这样就能把矩阵中的所有连续“1”块数出来了。

矩阵是以vector<vector<char>>的形式作为参数传递过来的,一开始我把行和列的长度弄反了,提交上去时会数组越界。一开始我还以为是BFS那部分的问题,白白浪费了一小时才发现是行列两个循环的长度弄反了。然后我就写出了这么一段代码:

class Solution {
public:
    int numIslands(vector<vector<char>>& grid) 
    {
        int count = 0;
        int X, Y, newX, newY;
        int dX[4] = {0, 0, -1, 1};
        int dY[4] = {-1, 1, 0, 0};
        vector<pair<int, int>> coordinate;	//BFS时记录坐标的队列
        for (int i = 0; i < grid.size(); i++)
          for (int j = 0; j < grid[0].size(); j++)
            if (grid[i][j] == '1')
            {
              count++;
              coordinate.clear();
              coordinate.push_back(make_pair(i, j));		//把初始的坐标放进队列

              while (coordinate.size() > 0)
              {
                X = coordinate[0].first;
                Y = coordinate[0].second;
                coordinate.erase(coordinate.begin());		//队列头出队
                grid[X][Y] = '0';				//把当前坐标的值变为1,避免重复遍历
                for (int k = 0; k < 4; k++)		//求上下左右四个方向的新坐标
                {
                  newX = X + dX[k];
                  newY = Y + dY[k];
                  //如果新坐标没有越界内并且值为1,就把它放在队尾
                  if (newX >= 0 && newX < grid.size() &&
                      newY >= 0 && newY < grid[0].size() &&
                      grid[newX][newY] == '1')	
                  {
                    coordinate.push_back(make_pair(newX, newY));
                  }
                }
              }

            }
        return count;
    }
};

当我自信满满地把代码提交上去时,结果给我弹出了这个提示:
超时
嗯?
DFS算法超时我能够理解,为什么连BFS算法都会超时?而且这道题这么直接,主体除了算法部分的while循环就只有外层的矩阵循环了,没有多余的内容,超时实在是有点难以理解。后来问了一下同学,得知问题可能出现在下面这一句:

coordinate.erase( coordinate.begin( ) );

vector类的earse( )函数的实现是新建一个新数组,把除了旧数组要删除部分以外的值赋值给新数组,所以时间消耗可能会有点多。但这也只是个时间复杂度为O(n)的操作而已,这都能导致超时,LeetCode真的很严格。

知道原因了还不简单,我换一种数据结构存放这个队列不就好了,于是我改成用queue类实现BFS的版本:

class Solution {
public:
    int numIslands(vector<vector<char>>& grid) 
    {
        int count = 0;
        int X, Y, newX, newY;
        int dX[4] = {0, 0, -1, 1};
        int dY[4] = {-1, 1, 0, 0};
        queue<pair<int, int>> coordinate;	//BFS时记录坐标的队列
        for (int i = 0; i < grid.size(); i++)
          for (int j = 0; j < grid[0].size(); j++)	
            if (grid[i][j] == '1')
            {
              count++;
              coordinate.push(make_pair(i, j));	//把初始的坐标放进队列

              while (coordinate.size() > 0)
              {
                X = coordinate.front().first;
                Y = coordinate.front().second;
                coordinate.pop();
                grid[X][Y] = '0';	//把当前坐标的值变为1,避免重复遍历
                for (int k = 0; k < 4; k++)	//求上下左右四个方向的新坐标
                {
                  newX = X + dX[k];
                  newY = Y + dY[k];
                   //如果新坐标没有越界内并且值为1,就把它放在队尾
                  if (newX >= 0 && newX < grid.size() &&
                      newY >= 0 && newY < grid[0].size() &&
                      grid[newX][newY] == '1')
                  {
                    coordinate.push(make_pair(newX, newY));
                  }
                }
              }

            }
        return count;
    }
};

提交!
溢出
嗯?
为什么连内存溢出都给我弄出来了?事已至此,我只能怀疑我被LeetCode搞了(开玩笑的)。超时跟内存溢出都出现了,那么原因大概不是用什么数据结构实现BFS的问题了,应该是我写的算法里有一些瑕疵。在用笔按问题代码的步骤一步一步去试,尝试了不同的矩阵输入后,终于被我发现了真正的问题!

问题出在把矩阵“1”点置为“0”的那一句放的位置不对。

grid[i][j] = '0';

我最初的想法是,每当一个坐标被访问到时才会把值改为“0”,这样可以防止重复遍历。但这个想法是错的,才造成了超时个内存溢出的问题。正确的做法应该是把坐标放进队列时就把值改为“0”,这样才能避免同一个坐标被多次放进队列里。下面讲一下为什么我最初的想法是错误的。

以下面矩阵为例:
11
11
此时队列为{ [0,0] }

开始第一次循环,矩阵跟队列会变成:[0,0]的右跟下方向的点值为“1”
01
11
队列:{ [0,1], [1,0] }

继续循环:[0,1]下方向的点值为“1”
00
11
队列:{ [1,0], [1,1] }

再做一步就能发现问题了:[1,0]右方向的点值为“1”
00
01
队列:{ [1,1], [1,1]}
因为访问到[1,0]时,[1,1]的值还没有被改成0,所以会被再次放进队列里,这就导致了同一个点被重复访问的情况,最坏的情况下会被访问4次,那么不管是超时还是内存溢出都变得可以理解了。错的不是这个世界,是我。

谁能想到我竟然在这么简单的问题上耗费了这么久的时间,说到底还是我的修行还不够,在写这道题的代码时我绝对没有粗心大意,因为题目简单就掉以轻心,只是因为对BFS算法的实现和应用还不够熟练。有时候就算对理论很清楚了,在具体问题具体分析时还有许多细节是需要落实的,对于BFS算法,我还欠缺火候,以后还需要多多练习。

最后贴一下正确的代码以及我惨烈的提交记录:

class Solution {
public:
    int numIslands(vector<vector<char>>& grid) 
    {
        int count = 0;
        int X, Y, newX, newY;
        int dX[4] = {0, 0, -1, 1};
        int dY[4] = {-1, 1, 0, 0};
        vector<pair<int, int>> coordinate;	//BFS时记录坐标的队列
        for (int i = 0; i < grid.size(); i++)
          for (int j = 0; j < grid[0].size(); j++)	
            if (grid[i][j] == '1')
            {
              count++;
              coordinate.clear();
              coordinate.push_back(make_pair(i, j));	//把初始的坐标放进队列
              grid[i][j] = '0';

              while (coordinate.size() > 0)
              {
                X = coordinate[0].first;
                Y = coordinate[0].second;
                coordinate.erase(coordinate.begin());
                for (int k = 0; k < 4; k++)	//求上下左右四个方向的新坐标
                {
                  newX = X + dX[k];
                  newY = Y + dY[k];
                  //如果新坐标没有越界内并且值为1,就把它放在队尾
                  if (newX >= 0 && newX < grid.size() &&
                      newY >= 0 && newY < grid[0].size() &&
                      grid[newX][newY] == '1')
                  {
                    grid[newX][newY] = '0';	//划重点,就是这里!把坐标的值变为1,避免重复遍历
                    coordinate.push_back(make_pair(newX, newY));
                  }
                }
              }

            }
        return count;
    }
};

提交记录

猜你喜欢

转载自blog.csdn.net/Maple_Lai/article/details/82908435
今日推荐