一、引言
这道题的题目信息请点击这里查看Island Perimeter。
其实我一看到这道题,我就想到了我之前做过的一个五子棋的项目。关于这个项目我还写了一篇博客,可以点击这里查看一个使用纯Win32 SDK和C语言实现的五子棋游戏。
这里让我们想想,五子棋的判胜逻辑。
五子棋五子棋,顾名思义,就是五个同色的棋子连接成了一条线,则判定胜利。那么五子棋的判胜逻辑应该怎么完成呢?
这里进行简要的总结:其实主要的核心思想就是以一个棋子为观察原点,我们以这个棋子出发,观察八个方向(包括四个斜方向)是否存在同色棋子,然后递归计数即可。
回到这道题,其实我们以一个 island 为中心,观察它附近是否存在 island,如果存在,则周长 -1 即可。
二、我的代码
这里我再来总结下思路:
1. 遍历整个 grid,找到其中所有的 island,也就是值为 1 的点
2. 以这个点为中心,检测它附近是否有 island,也就是检测它附近的四个方向的点(上下左右)是否值为 1。这里我们假想一下,其实很简单,只要有一个相邻的 island,周长就会减少 1,最后我们将每个 island 的周长计数加起来即可。
3. 将每个 island 的周长计数加起来即可。
代码如下:
class Solution {
public:
struct point {
unsigned x;
unsigned y;
};
int validSideCount(const point &pt, vector<vector <int>> &grid) {
int validSideCount = 0;
if (pt.x == 0 || grid[pt.x - 1][pt.y] == 0) {
++validSideCount;
}
if (pt.x == grid.size() - 1 || grid[pt.x + 1][pt.y] == 0) {
++validSideCount;
}
if (pt.y == 0 || grid[pt.x][pt.y - 1] == 0) {
++validSideCount;
}
if (pt.y == grid[0].size() - 1 || grid[pt.x][pt.y + 1] == 0) {
++validSideCount;
}
return validSideCount;
}
int islandPerimeter(vector<vector<int>> &grid) {
int islandPerimeter = 0;
for (int row = 0; row < grid.size(); ++row) {
for (int col = 0; col < grid[row].size(); ++col) {
if (grid[row][col] == 1) {
point pt = { row, col };
islandPerimeter += validSideCount(pt, grid);
}
}
}
return islandPerimeter;
}
};
这里,我声明了一个 point 类,方便记录当前 island 的坐标。validSideCount 函数的作用就是用来检测 island 附近是否存在值为 1 的点,计数一个 island 的周长,要获得所有 island 的周长,只需要将所有的 island 的周长计数加起来即可。
这里需要注意的是,validSideCount 函数中这四个判定,需要检测是否碰到了边(坐标不能超出 vector 的最大范围值),如果碰到了边,则不能进行递减或者递增操作了,这样会出现数组访问溢出错误。
三、同样,赏析一段别人的代码
直接上代码吧,写完自己的,总是要看看别人的代码为什么比自己的代码优美不是?
int islandPerimeter(vector<vector<int>>& grid) {
int count=0, repeat=0;
for(int i=0;i<grid.size();i++)
{
for(int j=0; j<grid[i].size();j++)
{
if(grid[i][j]==1)
{
count ++;
if(i!=0 && grid[i-1][j] == 1) repeat++;
if(j!=0 && grid[i][j-1] == 1) repeat++;
}
}
}
return 4*count-repeat*2;
}
这段代码要点如下:
1. 利用了两个变量进行周长计数:
其中,count 用来计数 island 的个数, repeat 用来计数两个 island 相邻(两个 island 相邻必然导致两条周长的减少)的个数。
那么 count * 4 - repeat * 2
必然就是最终的周长,其中 repeat 的计算只计算了当前 island 的上方和左方的记录,因为下方和右方的损失也已经包括在相对方里面去了,这里就不用再多判断了,只需要乘以2即可。
- 我发现外国人写代码,很喜欢将很短小的代码块放到一行书写,比如这里的两个判断语句:
if(i!=0 && grid[i-1][j] == 1) repeat++;
if(j!=0 && grid[i][j-1] == 1) repeat++;
要是我来写,准得写成这样:
if (i != 0 && grid[i - 1][j == 1) {
repeat++;
}
if (j != 0 && grid[i)[j - 1] == 1) {
repeat++;
}
其实孰优孰劣一目了然,前者真的是一目了然,而我看似在认真的遵循着编码习惯,实则扰乱了眼球,让代码阅读者一眼看不到重点。
三、总结
总结就这么多,从这道题,又看到了一点点当初写五子棋游戏的影子;同时也看到了不一样的代码风格。
让我们的代码更加 elegant 吧!