题目链接
题意简述
给你一个 m * n 的字符矩阵 boxGrid,包含三种字符:
#:石头,受重力影响向下(本题先向右)掉落*:障碍物,石头不能穿过.:空位
规则:
- 重力方向:盒子向右倾倒,石头会落到每一段的最右侧,障碍物隔开独立区间;
- 处理完石头掉落之后,将整个盒子顺时针旋转 90°,返回最终矩阵。
注意:旋转之后重力方向才变成向下,我们先在原矩阵模拟石头往右侧下沉,再做坐标旋转。
思路分析
核心:双指针(读指针 j + 写指针 right)
不需要提前计算石头移动多少步!
j:遍历指针,从右向左扫描一行 ,负责寻找石头#right:写指针,记录当前分段内最靠右可以放置石头的空位
每行处理逻辑:
- 初始化
right = n - 1(本行最右侧位置) - j 从右往左遍历:
- 遇到
*障碍物:石头无法穿过,right = j - 1,障碍物左边作为新的落位起点 - 遇到
#石头:把石头放到right空位,原位置置空,right-- - 遇到
.空位:什么都不做,right保持不动,留给后续石头
- 遇到
一句话记忆:j 找石头,right 放石头;遇墙重置,放完右位左移。
第二步:顺时针 90° 坐标转换
原矩阵:m行 n列 旋转后矩阵:n行 m列 映射公式:
res[j][m-1-i] = boxGrid[i][j]
- 原坐标
(i,j):i = 原行,j = 原列 - 新行 = j(原来的列号直接变成新行号)
- 新列 = m-1-i(原行号上下反转,上面的行转到右边,下面的行转到左边)
原理:顺时针 90° = 转置 + 原行上下翻转
完整 Go 代码
Go
func rotateTheBox(boxGrid [][]byte) [][]byte {
// 用一个指针right记录本分段里当前最靠右的空位,从右往左扫,自动放石头
m := len(boxGrid)
if m == 0 {
return nil
}
n := len(boxGrid[0])
// 第一步:双指针,模拟石头向右侧掉落
for i := 0; i < m; i++ {
right := n - 1
for j := n - 1; j >= 0; j-- { // 注意边界 j >= 0,不要写成 j>0
if boxGrid[i][j] == '*' {
// 障碍物,重置可放石头的位置到障碍物左边
right = j - 1
} else if boxGrid[i][j] == '#' {
// 抓到石头,放到right空位
boxGrid[i][j] = '.'
boxGrid[i][right] = '#'
right--
}
// 遇到 . 空位:不操作,right不动
}
}
// 第二步:顺时针旋转90度
res := make([][]byte, n)
for idx := range res {
res[idx] = make([]byte, m)
}
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
res[j][m-1-i] = boxGrid[i][j]
}
}
return res
}
易错点总结(刷题踩坑清单)
- 循环边界:
j >= 0,如果写成j>0,会漏掉第 0 列,最左侧石头处理失败 - 旋转矩阵行列互换:原 m 行 n 列 → 结果 n 行 m 列,make 数组长度不要写反
- 旋转公式
m-1-i:是原总行数 m ,不是 n!很容易写成n-1-i - 区分:本题和轮转数组(189)分组循环不一样!
- 189:所有元素固定移动 k 步,需要 gcd 分组
- 1861:障碍物分割成多个独立段,石头向段末尾挤,用双指针更合适
复杂度分析
- 时间复杂度:\(O(m*n)\),每个格子最多遍历两次(一次掉落、一次旋转)
- 空间复杂度:\(O(m*n)\),存储答案矩阵(不计输出矩阵则为 O (1) 原地修改原数组)