- 岛屿的周长
给定一个包含 0 和 1 的二维网格地图,其中 1 表示陆地 0 表示水域。
网格中的格子水平和垂直方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。
岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/island-perimeter
很简单的一道题,贪心或者DFS,(我都想到了,但是都忘了),所以这次就来复习一下。
首先上官方题解吧,也懒得码字了。
方法一:迭代
思路与算法
对于一个陆地格子的每条边,它被算作岛屿的周长当且仅当这条边为网格的边界或者相邻的另一个格子为水域。 因此,我们可以遍历每个陆地格子,看其四个方向是否为边界或者水域,如果是,将这条边的贡献 1 加入答案 中即可。
class Solution {
static int[] dx = {0, 1, 0, -1};
static int[] dy = {1, 0, -1, 0};
public int islandPerimeter(int[][] grid) {
int n = grid.length, m = grid[0].length;
int ans = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (grid[i][j] == 1) {
int cnt = 0;
for (int k = 0; k < 4; ++k) {
int tx = i + dx[k];
int ty = j + dy[k];
if (tx < 0 || tx >= n || ty < 0 || ty >= m || grid[tx][ty] == 0) {
cnt += 1;
}
}
ans += cnt;
}
}
}
return ans;
}
}
复杂度分析
时间复杂度:O(nm),其中 n 为网格的高度,m 为网格的宽度。我们需要遍历每个格子,每个格子要看其周围 4 个格子是否为岛屿,因此总时间复杂度为 O(4nm)=O(nm)。
空间复杂度:O(1)。只需要常数空间存放若干变量。
方法二:深度优先搜索
思路与算法
我们也可以将方法一改成深度优先搜索遍历的方式,此时遍历的方式可扩展至统计多个岛屿各自的周长。需要注意的是为了防止陆地格子在深度优先搜索中被重复遍历导致死循环,我们需要将遍历过的陆地格子标记为已经遍历过,下面的代码中我们设定值为 2 的格子为已经遍历过的陆地格子。
class Solution {
static int[] dx = {0, 1, 0, -1};
static int[] dy = {1, 0, -1, 0};
public int islandPerimeter(int[][] grid) {
int n = grid.length, m = grid[0].length;
int ans = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (grid[i][j] == 1) {
ans += dfs(i, j, grid, n, m);
}
}
}
return ans;
}
public int dfs(int x, int y, int[][] grid, int n, int m) {
if (x < 0 || x >= n || y < 0 || y >= m || grid[x][y] == 0) {
return 1;
}
if (grid[x][y] == 2) {
return 0;
}
grid[x][y] = 2;
int res = 0;
for (int i = 0; i < 4; ++i) {
int tx = x + dx[i];
int ty = y + dy[i];
res += dfs(tx, ty, grid, n, m);
}
return res;
}
}
复杂度分析
时间复杂度:O(nm),其中 n 为网格的高度,m 为网格的宽度。每个格子至多会被遍历一次,因此总时间复杂度为 O(nm)。
空间复杂度:O(nm)。深度优先搜索复杂度取决于递归的栈空间,而栈空间最坏情况下会达到 O(nm)。
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/island-perimeter/solution/dao-yu-de-zhou-chang-by-leetcode-solution/
来源:力扣(LeetCode)
看我的,害,很正常的思路,全部遍历计算,和迭代差不多。
class Solution {
public int islandPerimeter(int[][] grid) {
int rows = grid.length;
int columns = grid[0].length;
int gridnum = 0;
int sidenum = 0;
int result = 0;
for( int i = 0; i < rows; i ++)
for( int j = 0; j < columns; j ++)
{
if(grid[i][j] == 1)
{
gridnum ++;
if(j >= 1){
if(grid[i][j-1] == 1)
sidenum ++;
}
if(i >= 1){
if(grid[i-1][j] == 1)
sidenum ++;
}
}
}
result = gridnum*4 - sidenum*2;
return result;
}
}
复杂度分析
时间复杂度:O(nm),其中 n 为网格的高度,m 为网格的宽度。每个格子至多会被遍历一次,因此总时间复杂度为 O(nm)。
空间复杂度:O(1)。只需要常数空间存放若干变量。
明天再来补知识点。
10.31 14:41
好了 我来补知识点了。
贪心法
贪心法是一种经典的解题方法,就是遵循某种规则,不断贪心地选取当前最优方案来设计的算法。
这里举一个简单的入门问题:
- 判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
示例 1:
s = “abc”, t = “ahbgdc”
返回 true.
示例 2:
s = “axc”, t = “ahbgdc”
返回 false.
后续挑战 :
如果有大量输入的 S,称作S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/is-subsequence
我的答案:
class Solution {
public boolean isSubsequence(String s, String t) {
int index = 0;
int comp = 0;
for(int i = 0; i < s.length();i ++ )
{
char sc = s.charAt(i);
for(; index < t.length(); index ++)
{
if(sc == t.charAt(index))
{
comp ++;
index ++;
break;
}
}
}
if(comp != s.length())
return false;
else return true;
}
}
也就是每次贪心地匹配靠前的字符。这就是最优决策。(然后发现自己做两个for,但其实复杂度也是 O(n+m),和官方解一样的)
通俗一点解释贪心就是,每次思考如何解决当前的问题,也就是找到当前的局部最优解,然后每次都是局部最优解来得到最后的结果,其实在某些问题就不一定是全局最优。
这道题,我要匹配子串,所以先匹配子串第一个字母,所以我用贪心法,母串从前往后匹配,然后以此,就一直匹配,匹配成功则匹配下一个子串字母,直到得到最终结果。
然后这道题还有动态规划解法,后面复习的时候做。
搜索
搜索有两种,DFS (depth-first-search)与 BFS(breadth-first-search),即深度优先算法和广度优先算法。
DFS
深度优先搜索
是一种用于遍历或搜索树或图的算法。
第一种解释:纵向搜索,尽可能深入搜索图或树的分支,当节点所在边均被搜索过后(搜索点没有未搜索分支),开始回溯到上一节点,重复搜索。直到回溯到源节点且无未搜索分支时结束。(若有多个源节点,则会在下一个未搜索的源节点继续此过程)
第二种解释:从某个状态开始,不断地转移状态,直到状态无法转移,就回退到前一状态,继续转移,以此重复,直到得到最终解。(比如求解数独,每个格子都依次填入数字,当发现无解后,向前回退,重新填入数字,当所有数字尝试后仍然无解,向前格子回退,重新填入数字,以此重复,直到填入所有格子,即得到最终解)
由于DFS的特性,所以一般都用递归函数来实现。
所以我们来看看之前岛屿的DFS:
class Solution {
static int[] dx = {0, 1, 0, -1};
static int[] dy = {1, 0, -1, 0};
public int islandPerimeter(int[][] grid) {
// n 行数, m 列数
int n = grid.length, m = grid[0].length;
int ans = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
// 遇到陆地开始dfs
if (grid[i][j] == 1) {
ans += dfs(i, j, grid, n, m);
}
}
}
return ans;
}
// DFS 实现
public int dfs(int x, int y, int[][] grid, int n, int m) {
// 当遇到海洋 开始回退,并返回 1 点长度周长
// 陆地面临一格海洋等于一个长度的周长
if (x < 0 || x >= n || y < 0 || y >= m || grid[x][y] == 0) {
return 1;
}
// 2 表示被搜索过 则不再搜索 回退
if (grid[x][y] == 2) {
return 0;
}
// 已搜索的标记
grid[x][y] = 2;
int res = 0;
// 计算四条边邻近格子的 dfs 值,即向四个方向继续DFS搜索
for (int i = 0; i < 4; ++i) {
int tx = x + dx[i];
int ty = y + dy[i];
res += dfs(tx, ty, grid, n, m);
}
return res;
}
}
BFS
待续。
弄完这个就刷几个这两天遇到的类型的题。慢慢来啦。
11.1 我来了。。
广度优先搜索(宽度优先搜索)
与DFS相似,从某个状态出发探索所有可以到达的状态。
区别:DFS先转移到最远的状态,再回退,BFS是先搜索距离初始状态最近的状态,再向下搜索。
DFS隐式的利用了栈,BFS隐式的利用了队列。