# LeetCode 463. 岛屿的周长
LeetCode 463. Island Perimeter
给定一个 row x col
的二维网格地图 grid
,其中: grid[i][j] = 1
表示陆地, grid[i][j] = 0
表示水域
网格中的格子 水平 和 垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)
岛屿中没有 “湖”(“湖” 是指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长
示例 1:
输入:grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]
输出:16
解释:它的周长是上面图片中的 16 个黄色的边
示例 2:
输入:grid = [[1]]
输出:4
示例 3:
输入:grid = [[1,0]]
输出:4
提示:
row == grid.length
col == grid[i].length
1 <= row, col <= 100
grid[i][j]
为0
或1
# Method 1: 模拟
# 算法思路
对于一个岛屿格子的每条边:如果这条边为网格的边界,或者这条边是岛屿与水域的分界线,则需将这条边计入岛屿的周长
因此,可以遍历每个岛屿格子,看其四个方向的边是否为网格边界或水域分界线,如果是,则计入岛屿周长
# 代码实现
int islandPerimeter(vector<vector<int>>& grid) { | |
int m = grid.size(); | |
int n = grid[0].size(); | |
vector<vector<int>> dir = <!--swig0-->; | |
int res = 0; | |
for (int i = 0; i < m; ++i) { | |
for (int j = 0; j < n; ++j) { | |
if (grid[i][j] == 0) continue; | |
for (int k = 0; k < 4; ++k) { | |
int x = i + dir[k][0]; | |
int y = j + dir[k][1]; | |
if (x == -1 || x == m || y == -1 || y == n) ++res; // 网格边界 | |
else if (grid[x][y] == 0) ++res; // 水域分界线 | |
} | |
} | |
} | |
return res; | |
} |
# 复杂度分析
时间复杂度:,其中, 和 分别为网格的行数和列数
空间复杂度:
# Method 2: 深度优先搜索
# 算法思路
可以将方法一改成深度优先搜索遍历的方式(该方式可以拓展至多个岛屿情形)
其中,为避免岛屿格子被重复遍历,需要将已经遍历过的岛屿格子标记。特别地,可以将已经遍历过的岛屿格子的值置为 -1 (不能置为 0,因为置 0 会形成新的 “水域边界线”,进而导致结果出错)
# 代码实现
vector<vector<int>> dir = <!--swig1-->; | |
int dfs(vector<vector<int>>& grid, int x, int y) { | |
int m = grid.size(); | |
int n = grid[0].size(); | |
if (x == -1 || x == m || y == -1 || y == n) return 1; | |
if (grid[x][y] == 0) return 1; | |
if (grid[x][y] == -1) return 0; | |
grid[x][y] = -1; | |
int count = 0; | |
for (int k = 0; k < 4; ++k) { | |
int newx = x + dir[k][0]; | |
int newy = y + dir[k][1]; | |
count += dfs(grid, newx, newy); | |
} | |
return count; | |
} | |
int islandPerimeter(vector<vector<int>>& grid) { | |
int m = grid.size(); | |
int n = grid[0].size(); | |
int ans = 0; | |
for (int i = 0; i < m; ++i) { | |
for (int j = 0; j < n; ++j) { | |
if (grid[i][j] == 1) | |
ans += dfs(grid, i, j); | |
} | |
} | |
return ans; | |
} |
# 复杂度分析
时间复杂度:,其中, 和 分别为网格的行数和列数
空间复杂度:
参考:力扣官方题解
# LeetCode 48. 旋转图像
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
示例 2:
输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]
提示:
n == matrix.length == matrix[i].length
-
n
-
matrix[i][j]
# Method 1: 原地旋转
经过观察,可以发现:
-
原矩阵中的位置 ,旋转后的目标位置为
-
原矩阵中的位置 ,旋转后的目标位置为
-
原矩阵中的位置 ,旋转后的目标位置为
-
原矩阵中的位置 ,旋转后的目标位置为
即,对于 、 、 、 而言,每一项旋转后的位置就是下一项所在的位置
因此,可以引入一个临时变量 tmp
,完成这四项的原地交换
int tmp = matrix[n - j - 1][i]; | |
matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1]; | |
matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1]; | |
matrix[j][n - i - 1] = matrix[i][j]; | |
matrix[i][j] = tmp; |
那么,我们应该枚举哪些 和 呢?
每一次可以交换四个位置的元素:
-
当 为偶数时,此时需要枚举 个位置。因此, 和 分别遍历 个位置,以 矩阵为例
-
当 为奇数时,由于中心位置始终不变,需要枚举 个位置。因此,i 遍历 个位置、j 遍历 个位置,以 矩阵为例
因此,无论 是奇数还是偶数,均可按照 “ 遍历 个位置、 遍历 个位置 ” 来处理,即, i
遍历 [0, n / 2)
, j
遍历 [0, (n + 1) / 2)
代码实现:
void rotate(vector<vector<int>>& matrix) { | |
int n = matrix.size(); | |
for (int i = 0; i < n / 2; i++) { | |
for (int j = 0; j < (n + 1) / 2; j++) { | |
int tmp = matrix[n - j - 1][i]; | |
matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1]; | |
matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1]; | |
matrix[j][n - i - 1] = matrix[i][j]; | |
matrix[i][j] = tmp; | |
} | |
} | |
} |
时间复杂度:
空间复杂度:
# Method 2: 翻转
算法思路:
首先进行上下翻转,然后进行对角线翻转(即,转置)
理论推导:
对于上下翻转:只需要枚举矩阵上半部分的元素,并将其与下半部分对应位置的元素进行交换,即
对于对角线翻转:只需要枚举对角线左侧的元素,并将其与右侧对应位置的元素进行交换,即
联立以上两式可得
即,实现了将 旋转 90 度、放到 位置
代码实现:
void rotate(vector<vector<int>>& matrix) { | |
int n = matrix.size(); | |
for (int i = 0; i < n / 2; i++) { // 上下翻转 | |
for (int j = 0; j < n; j++) { | |
swap(matrix[i][j], matrix[n - i - 1][j]); | |
} | |
} | |
for (int i = 0; i < n; i++) { // 对角线翻转 | |
for (int j = 0; j < i; j++) { | |
swap(matrix[i][j], matrix[j][i]); | |
} | |
} | |
} |
时间复杂度:
空间复杂度:
# LeetCode 59. 螺旋矩阵 II
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
示例 1:
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
示例 2:
输入:n = 1
输出:[[1]]
提示:
-
n
# Method: 模拟
思路:模拟 顺时针画矩阵的过程,即
-
填充上行从左到右
-
填充右列从上到下
-
填充下行从右到左
-
填充左列从下到上
由外向内一圈一圈这么画下去
注意:每画一条边都要坚持一致的 左闭右开 ,或者 左开又闭 的原则,这样这一圈才能按照统一的规则画下来
坚持 循环不变量 原则
下图以左闭右开原则为例:每一种颜色,代表一条边,注意每一个拐角处的处理规则,拐角处让给新的一条边来继续画(即,每条边都遵循左闭右开原则)
代码实现:
vector<vector<int>> generateMatrix(int n) { | |
vector<vector<int>> matrix(n, vector<int>(n, 0)); | |
int num = 1; // 填充的数字 | |
for (int i = 0; i < n / 2; ++i) { // 每次循环填充一圈 | |
int start = i; // 填充的起点 | |
int len = n - i * 2 - 1; // 每个方向的填充长度 | |
for (int j = 0; j < len; ++j) { // 上面一行,从左往右填充 | |
matrix[start][start + j] = num++; | |
} | |
for (int j = 0; j < len; ++j) { // 右边一列,从上往下填充 | |
matrix[start + j][start + len] = num++; | |
} | |
for (int j = len; j >= 1; --j) { // 下面一行,从右往左填充 | |
matrix[start + len][start + j] = num++; | |
} | |
for (int j = len; j >= 1; --j) { // 左边一列,从下往上填充 | |
matrix[start + j][start] = num++; | |
} | |
} | |
if (n % 2) matrix[n / 2][n / 2] = n * n; //n 为奇数,数组最中间的位置需要单独处理 | |
return matrix; | |
} |
时间复杂度:,需要填充 个元素
空间复杂度:
# LeetCode 621. 任务调度器
给你一个用字符数组 tasks
表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。
然而,两个 相同种类 的任务之间必须有长度为整数 n
的冷却时间,因此至少有连续 n
个单位时间内 CPU 在执行不同的任务,或者在待命状态。
你需要计算 完成所有任务所需要的最短时间 。
示例 1:
输入:tasks = ["A","A","A","B","B","B"], n = 2
输出:8
解释:
A -> B -> idle -> A -> B -> idle -> A -> B
示例 2:
输入:tasks = ["A","A","A","B","B","B"], n = 0
输出:6
解释:在这种情况下,任何大小为 6 的排列都可以满足要求,因为 n = 0
["A","A","A","B","B","B"]
["A","B","A","B","A","B"]
["B","B","B","A","A","A"]
...
诸如此类
示例 3:
输入:tasks = ["A","A","A","A","A","A","B","C","D","E","F","G"], n = 2
输出:16
解释:一种可能的解决方案:
A -> B -> C -> A -> D -> E -> A -> F -> G -> A -> (待命) -> (待命) -> A -> (待命) -> (待命) -> A
提示:
-
task.length
tasks[i]
是大写英文字母n
的取值范围为
# Method: 桶思想
# 算法思路
使用一个宽为 的矩阵来展示任务执行方案,其中,任务按照逐行遍历的顺序执行,空白格子对应 CPU 的待命状态
于是,可以将具有最大数量的任务排布在矩阵的第一列,将其余任务依次排布成列。由于冷却时间为 ,上述排列方案可以保证满足题目要求,并且总时间最小
-
情况一:冷却时间长、任务种类和数量很少,假设所有任务种类中的最大数量为 (即,矩阵的行数),具有最大数量的任务的种类数为 (即,矩阵最后一行的任务数量),则执行任务所需的时间为
- 示例 1:
- 示例 3:
-
情况二:冷却时间短、任务种类或数量很多,可以拓展矩阵的列数,并将任务排布在拓展的列中。此时,每个任务之间都不存在待命时间,因此,执行任务所需的时间就是任务的数量,即
因此,需要的最少时间为
具体可参考 popopop
# 代码实现
int leastInterval(vector<char>& tasks, int n) { | |
vector<int> rec(26, 0); | |
for (auto c : tasks) // 记录每种任务的数量 | |
++rec[c - 'A']; | |
int maxn = 0; // 任务的最大数量 | |
int cnt = 0; // 具有最大数量的任务种类数 | |
for (int num : rec) { | |
if (num > maxn) { | |
maxn = num; | |
cnt = 1; | |
} else if (num == maxn) { | |
++cnt; | |
} | |
} | |
return max((int)tasks.size(), (maxn - 1) * (n + 1) + cnt); | |
} |
# 复杂度分析
时间复杂度:,其中, 为数组 tasks 的长度, 为数组 tasks 中可能出现的任务种类数
空间复杂度:
参考: