方法一:正序遍历
$\textit{boxGrid}$ 的每一行互相独立,可以分别计算。
单独看每一行,我们需要知道每个障碍物($\texttt{*}$)的左边有多少个石头($\texttt{#}$)。
具体地,设当前障碍物到上一个障碍物之间有 $\textit{cnt}$ 个石头。那么旋转后,当前障碍物的左边有连续 $\textit{cnt}$ 个石头。据此:
- 在遍历过程中,统计石头的个数 $\textit{cnt}$。
- 如果下一个格子是障碍物,或者当前格子是最后一个格子,那么从当前格子往前填入连续 $\textit{cnt}$ 个石头,并重置计数器 $\textit{cnt}=0$。
细节:第 $i$ 行的格子旋转后在倒数第 $i$ 列,第 $j$ 列的格子旋转后在第 $j$ 行。所以 $(i,j)$ 旋转后位于 $(j,m-1-i)$。
class Solution:
def rotateTheBox(self, boxGrid: list[list[str]]) -> list[list[str]]:
m, n = len(boxGrid), len(boxGrid[0])
ans = [[''] * m for _ in range(n)]
for i, row in enumerate(boxGrid):
cnt = 0
for j, ch in enumerate(row):
if ch == '#': # 石头
cnt += 1
ch = '.' # 先把石头清空
ans[j][-1 - i] = ch
if j == n - 1 or row[j + 1] == '*': # 下一个格子是障碍物
# 石头垂直掉落后,从 j 往前 cnt 个格子都是石头
for k in range(j, j - cnt, -1):
ans[k][-1 - i] = '#'
cnt = 0 # 重置计数器
return ans
class Solution {
public char[][] rotateTheBox(char[][] boxGrid) {
int m = boxGrid.length;
int n = boxGrid[0].length;
char[][] ans = new char[n][m];
for (int i = 0; i < m; i++) {
char[] row = boxGrid[i];
int cnt = 0;
for (int j = 0; j < n; j++) {
char ch = row[j];
if (ch == '#') { // 石头
cnt++;
ch = '.'; // 先把石头清空
}
ans[j][m - 1 - i] = ch;
if (j == n - 1 || row[j + 1] == '*') { // 下一个格子是障碍物
// 石头垂直掉落后,从 j 往前 cnt 个格子都是石头
for (int k = j; k > j - cnt; k--) {
ans[k][m - 1 - i] = '#';
}
cnt = 0; // 重置计数器
}
}
}
return ans;
}
}
class Solution {
public:
vector<vector<char>> rotateTheBox(vector<vector<char>>& boxGrid) {
int m = boxGrid.size(), n = boxGrid[0].size();
vector ans(n, vector<char>(m));
for (int i = 0; i < m; i++) {
auto& row = boxGrid[i];
int cnt = 0;
for (int j = 0; j < n; j++) {
char ch = row[j];
if (ch == '#') { // 石头
cnt++;
ch = '.'; // 先把石头清空
}
ans[j][m - 1 - i] = ch;
if (j == n - 1 || row[j + 1] == '*') { // 下一个格子是障碍物
// 石头垂直掉落后,从 j 往前 cnt 个格子都是石头
for (int k = j; k > j - cnt; k--) {
ans[k][m - 1 - i] = '#';
}
cnt = 0; // 重置计数器
}
}
}
return ans;
}
};
char** rotateTheBox(char** boxGrid, int boxGridSize, int* boxGridColSize, int* returnSize, int** returnColumnSizes) {
int m = boxGridSize, n = boxGridColSize[0];
char** ans = malloc(n * sizeof(char*));
*returnColumnSizes = malloc(n * sizeof(int));
*returnSize = n;
for (int i = 0; i < n; i++) {
ans[i] = malloc(m * sizeof(char));
(*returnColumnSizes)[i] = m;
}
for (int i = 0; i < m; i++) {
char* row = boxGrid[i];
int cnt = 0;
for (int j = 0; j < n; j++) {
char ch = row[j];
if (ch == '#') { // 石头
cnt++;
ch = '.'; // 先把石头清空
}
ans[j][m - 1 - i] = ch;
if (j == n - 1 || row[j + 1] == '*') { // 下一个格子是障碍物
// 石头垂直掉落后,从 j 往前 cnt 个格子都是石头
for (int k = j; k > j - cnt; k--) {
ans[k][m - 1 - i] = '#';
}
cnt = 0; // 重置计数器
}
}
}
return ans;
}
func rotateTheBox(boxGrid [][]byte) [][]byte {
m, n := len(boxGrid), len(boxGrid[0])
ans := make([][]byte, n)
for i := range ans {
ans[i] = make([]byte, m)
}
for i, row := range boxGrid {
cnt := 0
for j, ch := range row {
if ch == '#' { // 石头
cnt++
ch = '.' // 先把石头清空
}
ans[j][m-1-i] = ch
if j == n-1 || row[j+1] == '*' { // 下一个格子是障碍物
// 石头垂直掉落后,从 j 往前 cnt 个格子都是石头
for k := j; k > j-cnt; k-- {
ans[k][m-1-i] = '#'
}
cnt = 0 // 重置计数器
}
}
}
return ans
}
var rotateTheBox = function(boxGrid) {
const m = boxGrid.length, n = boxGrid[0].length;
const ans = Array.from({ length: n }, () => Array(m));
for (let i = 0; i < m; i++) {
const row = boxGrid[i];
let cnt = 0;
for (let j = 0; j < n; j++) {
let ch = row[j];
if (ch === '#') { // 石头
cnt++;
ch = '.'; // 先把石头清空
}
ans[j][m - 1 - i] = ch;
if (j === n - 1 || row[j + 1] === '*') { // 下一个格子是障碍物
// 石头垂直掉落后,从 j 往前 cnt 个格子都是石头
for (let k = j; k > j - cnt; k--) {
ans[k][m - 1 - i] = '#';
}
cnt = 0; // 重置计数器
}
}
}
return ans;
};
impl Solution {
pub fn rotate_the_box(box_grid: Vec<Vec<char>>) -> Vec<Vec<char>> {
let m = box_grid.len();
let n = box_grid[0].len();
let mut ans = vec![vec!['\0'; m]; n];
for (i, row) in box_grid.iter().enumerate() {
let mut cnt = 0;
for (j, &ch) in row.into_iter().enumerate() {
let mut ch = ch;
if ch == '#' { // 石头
cnt += 1;
ch = '.'; // 先把石头清空
}
ans[j][m - 1 - i] = ch;
if j == n - 1 || row[j + 1] == '*' { // 下一个格子是障碍物
// 石头垂直掉落后,从 j 往前 cnt 个格子都是石头
for k in j - cnt + 1..=j {
ans[k][m - 1 - i] = '#';
}
cnt = 0; // 重置计数器
}
}
}
ans
}
}
复杂度分析
- 时间复杂度:$\mathcal{O}(mn)$,其中 $m$ 和 $n$ 分别是 $\textit{boxGrid}$ 的行数和列数。
- 空间复杂度:$\mathcal{O}(1)$。返回值不计入。
方法二:倒序遍历 + 双指针
对于每一行 $\textit{row}$,倒着遍历,我们可以直接确定每个石头落入的位置:
- 如果 $\textit{row}[j]$ 是障碍物,那么它左边最近的石头,在旋转后掉落到 $\textit{row}[j-1]$。我们用一个变量 $k$ 维护石头掉落后的位置,如果 $\textit{row}[j]$ 是障碍物,那么更新 $\textit{k} = j-1$。注:如果 $\textit{row}[j]$ 左边最近的不是石头而是障碍物,那么 $k$ 会继续更新,无需担心石头落到错误的位置。
- 如果 $\textit{row}[j]$ 是石头,那么它掉落到 $\textit{row}[\textit{k}]$。然后把 $k$ 减一,表示左边下一块石头掉落后的位置。
class Solution:
def rotateTheBox(self, boxGrid: list[list[str]]) -> list[list[str]]:
m, n = len(boxGrid), len(boxGrid[0])
ans = [['.'] * m for _ in range(n)]
for i, row in enumerate(boxGrid):
k = n - 1
for j in range(n - 1, -1, -1):
if row[j] == '*': # 障碍物
ans[j][-1 - i] = '*'
k = j - 1 # 障碍物左边最近的石头,在旋转后掉落到 j-1
elif row[j] == '#': # 石头
ans[k][-1 - i] = '#' # 旋转后,石头掉落到 k
k -= 1
return ans
class Solution {
public char[][] rotateTheBox(char[][] boxGrid) {
int m = boxGrid.length;
int n = boxGrid[0].length;
char[][] ans = new char[n][m];
for (char[] row : ans) {
Arrays.fill(row, '.');
}
for (int i = 0; i < m; i++) {
char[] row = boxGrid[i];
int k = n - 1;
for (int j = n - 1; j >= 0; j--) {
if (row[j] == '*') { // 障碍物
ans[j][m - 1 - i] = '*';
k = j - 1; // 障碍物左边最近的石头,在旋转后掉落到 j-1
} else if (row[j] == '#') { // 石头
ans[k][m - 1 - i] = '#'; // 旋转后,石头掉落到 k
k--;
}
}
}
return ans;
}
}
class Solution {
public:
vector<vector<char>> rotateTheBox(vector<vector<char>>& boxGrid) {
int m = boxGrid.size(), n = boxGrid[0].size();
vector ans(n, vector<char>(m, '.'));
for (int i = 0; i < m; i++) {
auto& row = boxGrid[i];
int k = n - 1;
for (int j = n - 1; j >= 0; j--) {
if (row[j] == '*') { // 障碍物
ans[j][m - 1 - i] = '*';
k = j - 1; // 障碍物左边最近的石头,在旋转后掉落到 j-1
} else if (row[j] == '#') { // 石头
ans[k][m - 1 - i] = '#'; // 旋转后,石头掉落到 k
k--;
}
}
}
return ans;
}
};
char** rotateTheBox(char** boxGrid, int boxGridSize, int* boxGridColSize, int* returnSize, int** returnColumnSizes) {
int m = boxGridSize, n = boxGridColSize[0];
char** ans = malloc(n * sizeof(char*));
*returnColumnSizes = malloc(n * sizeof(int));
*returnSize = n;
for (int i = 0; i < n; i++) {
ans[i] = malloc(m * sizeof(char));
memset(ans[i], '.', m * sizeof(char));
(*returnColumnSizes)[i] = m;
}
for (int i = 0; i < m; i++) {
char* row = boxGrid[i];
int k = n - 1;
for (int j = n - 1; j >= 0; j--) {
if (row[j] == '*') { // 障碍物
ans[j][m - 1 - i] = '*';
k = j - 1; // 障碍物左边最近的石头,在旋转后掉落到 j-1
} else if (row[j] == '#') { // 石头
ans[k][m - 1 - i] = '#'; // 旋转后,石头掉落到 k
k--;
}
}
}
return ans;
}
func rotateTheBox(boxGrid [][]byte) [][]byte {
m, n := len(boxGrid), len(boxGrid[0])
ans := make([][]byte, n)
for i := range ans {
ans[i] = bytes.Repeat([]byte{'.'}, m)
}
for i, row := range boxGrid {
k := n - 1
for j := n - 1; j >= 0; j-- {
if row[j] == '*' { // 障碍物
ans[j][m-1-i] = '*'
k = j - 1 // 障碍物左边最近的石头,在旋转后掉落到 j-1
} else if row[j] == '#' { // 石头
ans[k][m-1-i] = '#' // 旋转后,石头掉落到 k
k--
}
}
}
return ans
}
var rotateTheBox = function(boxGrid) {
const m = boxGrid.length, n = boxGrid[0].length;
const ans = Array.from({ length: n }, () => Array(m).fill('.'));
for (let i = 0; i < m; i++) {
const row = boxGrid[i];
let k = n - 1;
for (let j = n - 1; j >= 0; j--) {
if (row[j] === '*') { // 障碍物
ans[j][m - 1 - i] = '*';
k = j - 1; // 障碍物左边最近的石头,在旋转后掉落到 j-1
} else if (row[j] === '#') { // 石头
ans[k][m - 1 - i] = '#'; // 旋转后,石头掉落到 k
k--;
}
}
}
return ans;
};
impl Solution {
pub fn rotate_the_box(box_grid: Vec<Vec<char>>) -> Vec<Vec<char>> {
let m = box_grid.len();
let n = box_grid[0].len();
let mut ans = vec![vec!['.'; m]; n];
for (i, row) in box_grid.into_iter().enumerate() {
let mut k = n - 1;
for (j, ch) in row.into_iter().enumerate().rev() {
if ch == '*' { // 障碍物
ans[j][m - 1 - i] = '*';
k = j - 1; // 障碍物左边最近的石头,在旋转后掉落到 j-1
} else if ch == '#' { // 石头
ans[k][m - 1 - i] = '#'; // 旋转后,石头掉落到 k
k -= 1;
}
}
}
ans
}
}
复杂度分析
- 时间复杂度:$\mathcal{O}(mn)$,其中 $m$ 和 $n$ 分别是 $\textit{boxGrid}$ 的行数和列数。
- 空间复杂度:$\mathcal{O}(1)$。返回值不计入。
专题训练
见下面双指针题单的「六、分组循环」。
分类题单
如何科学刷题?
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
我的题解精选(已分类)
欢迎关注 B站@灵茶山艾府