当前位置: 首页 > news >正文

Kimi LeetCode 3797. 统计在矩形格子里移动的路径数目 TypeScript实现

LeetCode 3797. 统计在矩形格子里移动的路径数目 — TypeScript 实现

思路

状态定义(从下往上递推):

- `f[i][j]`:到达 `(i, j)`,且最后一步是从下一行纵向移动上来的路径数
- `g[i][j]`:到达 `(i, j)`,且最后一步是同一行横向移动来的路径数

转移方程:

1. 纵向移动(从 `(i+1, j')` 到 `(i, j)`):
要求 `√(1 + (j-j')²) ≤ d`,即 `|j-j'| ≤ √(d²-1)`。记 `k = ⌊√(d²-1)⌋`。

```
f[i][j] = Σ(f[i+1][j'] + g[i+1][j']),j' ∈ [j-k, j+k]
```

2. 横向移动(从 `(i, j')` 到 `(i, j)`):
要求 `|j-j'| ≤ d` 且 `j' ≠ j`。关键限制:不能连续两次横向移动,所以横向移动的前一步必须是从下一行上来的。

```
g[i][j] = Σ(f[i][j']),j' ∈ [j-d, j+d] 且 j' ≠ j
```

3. 初始化:最后一行每个空地作为起点,`f[n-1][j] = 1`

两个转移都是区间求和,用前缀和优化到 `O(1)`,总复杂度 `O(n·m)`。

---

TypeScript 代码

```typescript
function numberOfRoutes(grid: string[], d: number): number {
const MOD = 1_000_000_007;
const n = grid.length;
const m = grid[0].length;
// 纵向移动时,横向最大偏移:floor(sqrt(d^2 - 1))
const k = Math.floor(Math.sqrt(d * d - 1));

// prefix[i][j][0]: 第 i 行前 j 个位置(0~j-1)的 f 之和
// prefix[i][j][1]: 第 i 行前 j 个位置(0~j-1)的 g 之和
const prefix: number[][][] = Array.from({ length: n }, () =>
Array.from({ length: m + 1 }, () => [0, 0])
);

const add = (a: number, b: number): number => (a + b) % MOD;
const sub = (a: number, b: number): number => (a - b + MOD) % MOD;

for (let i = n - 1; i >= 0; i--) {
// 1. 计算 f[i][j]:从下一行上来
for (let j = 0; j < m; j++) {
if (grid[i][j] === '.') {
if (i === n - 1) {
// 最后一行作为起点
prefix[i][j + 1][0] = add(prefix[i][j][0], 1);
} else {
const l = Math.max(j - k, 0);
const r = Math.min(j + k, m - 1);
const sumF = sub(prefix[i + 1][r + 1][0], prefix[i + 1][l][0]);
const sumG = sub(prefix[i + 1][r + 1][1], prefix[i + 1][l][1]);
const curr = add(sumF, sumG);
prefix[i][j + 1][0] = add(prefix[i][j][0], curr);
}
} else {
// 障碍物,前缀和不变
prefix[i][j + 1][0] = prefix[i][j][0];
}
}

// 2. 计算 g[i][j]:同一行横向移动
// 只能从上一步是"从下一行上来"的状态转移(不能连续横向)
for (let j = 0; j < m; j++) {
if (grid[i][j] === '.') {
const l = Math.max(j - d, 0);
const r = Math.min(j + d, m - 1);
// 排除 j 本身,拆成 [l, j-1] 和 [j+1, r] 两段
let left = 0, right = 0;
if (l <= j - 1) {
left = sub(prefix[i][j][0], prefix[i][l][0]);
}
if (j + 1 <= r) {
right = sub(prefix[i][r + 1][0], prefix[i][j + 1][0]);
}
const curr = add(left, right);
prefix[i][j + 1][1] = add(prefix[i][j][1], curr);
} else {
prefix[i][j + 1][1] = prefix[i][j][1];
}
}
}

// 第 0 行所有可用格子的 f + g 之和
return add(prefix[0][m][0], prefix[0][m][1]);
}
```

---

复杂度

项目 复杂度
时间 `O(n × m)`
空间 `O(n × m)`(可滚动优化至 `O(m)`)

http://www.jsqmd.com/news/1332529/

相关文章:

  • QMC解码器终极指南:3步快速将QQ音乐加密文件转为MP3/FLAC
  • 3步搞定网页翻译:DeepL Chrome翻译插件高效使用指南
  • 第一章:为什么 SA8775P 和 SA8797P 成为下一代智能汽车核心计算平台?
  • DSP+FPGA异构主板设计:从电源、时钟到核心互联的实战指南
  • C++中全局变量、局部变量、静态全局变量、静态局部变量的区别详解
  • Kubernetes架构解析与生产实践指南
  • Unity卡牌游戏开发实战:从架构设计到核心系统实现
  • 广东透气透湿面料供应商家哪家好 十大口碑榜,照着选不踩坑 - 工业推荐榜
  • 避免xiaomusic播放链接端口重复:XIAOMUSIC_HOSTNAME配置最佳实践
  • 申报材料反复被退?关于AI预审系统你需要了解的几点
  • STM32定时器详解:从延时到PWM输出
  • Unity Sprite Atlas深度解析:从Draw Call优化到实战配置指南
  • 【论文复现】ICLR 2026 北大NVIDIA 提出 MHLA:多头线性注意力,即插即用!附赠 YOLO26 改进
  • Unity游戏开发中的观察者模式:从C#事件到消息总线的实战指南
  • mysqlrouter高可用
  • Matlab实现动态再结晶的元胞自动机模拟
  • STM32-CAN
  • GTA5线上小助手:免费开源工具彻底改变你的洛圣都游戏体验
  • 靠挖漏洞和打比赛赚钱,黑客技术变现的真实路径
  • 金城银行基于 Apache Doris 构建实时数据平台:T+1 到分钟级的金融级实践
  • 别再死记硬背了!用“班级点名册“类比,3分钟搞懂区块链是什么
  • 深度学习在设备寿命预测中的应用:从CNN、LSTM到Transformer的实战解析
  • 高效网页保存解决方案:Chrome滚动截图完全指南
  • 免疫细胞培养基深度评测:从RPMI 1640到无血清配方的选择与优化指南
  • 涿州老王匠实木定制千套实景,还原业主心中理想原木家 - GrowthUME
  • DiskGenius专业版深度解析:分区管理、数据恢复与系统迁移实战指南
  • 抖音批量下载器终极指南:一键保存无水印视频和音乐
  • Windows 10系统迁移后启动失败?UEFI引导与驱动问题深度修复指南
  • 5步掌握BlenderKit:让3D创作效率提升300%的终极指南
  • 基于OpenClaw AI智能体框架构建专业领域论文降重助手