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

【LeetCode 每日一题】3459. 最长 V 形对角线段的长度

Problem: 3459. 最长 V 形对角线段的长度

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(M * N)
    • 空间复杂度:O(M * N)

整体思路

这段代码旨在解决一个复杂的路径查找问题:在一个二维网格 grid 中,寻找由交替数值(比如1和2)构成的、最长的 "V字形"对角线路径 的长度。

这个 “V字形” 路径具有以下特征:

  1. 起点:必须从值为 1 的单元格开始。
  2. 交替数值:路径上的值必须严格交替。代码中,从一个值为 1 的点出发,下一步必须是值为 2 的点,再下一步是值为 0(通过2-target计算)的点,这似乎暗示路径上的值是 1 -> 2 -> 0 -> 2 -> 0 ...
  3. 对角线移动:路径只能沿着四个对角线方向移动(右下、左下、左上、右上)。
  4. V字形:整条路径最多只能包含一次90度的转向。例如,从右下方向转为左下方向。

为了高效地解决这个问题,算法采用了 深度优先搜索 (DFS) + 记忆化搜索 (Memoization) 的策略,这本质上是一种动态规划。

其核心逻辑步骤如下:

  1. 入口与初始化

    • lenOfVDiagonal 方法是主入口。它初始化了一个三维的 memo 数组,用于记忆化存储已计算过的子问题的结果,以避免重复计算。
    • 它遍历整个 grid,将每一个值为 1 的单元格 (i, j) 视为一个潜在的V形路径的起点。
    • 对于每个起点,它会尝试从所有四个对角线方向 (k=0..3) 开始进行深度优先搜索。
  2. 深度优先搜索 (DFS) 的状态定义

    • 核心的 dfs 函数被设计用来回答:“从点 (i, j) 出发,沿着方向 k,当前是否还允许拐弯 (turn),并且期望下一个点的值为 target,能够走出的最长路径是多少?”
    • 状态参数(i, j, k, turn, target) 完整地定义了一个子问题。
      • (i, j): 当前路径的末端位置。
      • k: 当前路径的前进方向。
      • turn: 一个标志位,1 表示还可以拐弯,0 表示已经拐过弯或者不允许拐弯。
      • target: 路径中下一个单元格期望的值。
  3. DFS 的递归与状态转移

    • 前进dfs 函数首先尝试沿着当前方向 k 前进一步。
    • 剪枝/终止条件:如果前进一步后越出边界,或者遇到的单元格值不等于 target,说明此路不通,返回路径长度0。
    • 记忆化:在进行计算前,先检查 memo 数组中是否已有当前状态的解。如果有,直接返回。
    • 递归探索:从新位置出发,探索两种可能的后续路径:
      a. 直行:继续沿当前方向 k 前进。递归调用 dfs,方向不变,turn 状态不变。
      b. 拐弯仅当 turn == 1,尝试进行一次90度拐弯。递归调用 dfs,方向变为 (k+1)%4,并将 turn 状态置为 0,表示拐弯机会已用尽。
    • dfs 函数返回的是这两种可能路径中的较长者,再加上当前点的长度 1
  4. 状态编码与记忆化

    • memo 数组的第三维大小为 8 (1 << 3)。这是因为需要为一个单元格 (i, j) 存储不同状态的结果。
    • 代码通过 temp = k << 1 | turn 这个位运算,将方向 k (0-3,需2位) 和 turn (0-1,需1位) 两个状态变量压缩成一个 0-7 之间的整数索引,高效地利用了 memo 数组的空间。
  5. 结果聚合

    • 主函数 lenOfVDiagonal 不断用每次 dfs 返回的路径长度(加上起点的1)来更新全局最大值 ans,最终得到整个网格中的最长V形路径。

完整代码

class Solution {// DIRT: 定义四个对角线方向的偏移量// {1, 1}: 右下, {1, -1}: 左下, {-1, -1}: 左上, {-1, 1}: 右上private static final int[][] DIRT = { { 1, 1 }, { 1, -1 }, { -1, -1 }, { -1, 1 } };/*** 计算网格中最长的V形交替数值对角线路径的长度。* @param grid 输入的二维网格* @return 最长路径的长度*/public int lenOfVDiagonal(int[][] grid) {int m = grid.length;int n = grid[0].length;int ans = 0;// memo: 记忆化数组。memo[i][j][state] 存储从(i,j)出发在特定状态下的最长路径。// state 通过方向k和拐弯标记turn编码而来,共 4*2=8 种状态。int[][][] memo = new int[m][n][1 << 3];// 遍历所有单元格,寻找值为 1 的起点for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if (grid[i][j] == 1) {// 对于每个起点,尝试从所有四个对角线方向开始搜索for (int k = 0; k < 4; k++) {// dfs返回的是后续路径长度,所以要 +1 包含起点本身ans = Math.max(ans, dfs(i, j, k, 1, 2, grid, memo) + 1);}}}}return ans;}/*** 深度优先搜索函数,计算从 (i, j) 出发的特定状态下的最长路径。* @param i      当前行* @param j      当前列* @param k      当前前进方向的索引 (0-3)* @param turn   是否还允许拐弯 (1:是, 0:否)* @param target 下一个单元格期望的值* @param grid   网格* @param memo   记忆化数组* @return 从 (i, j) 的下一个点开始的最长路径长度*/private int dfs(int i, int j, int k, int turn, int target, int[][] grid, int[][][] memo) {int m = grid.length;int n = grid[0].length;// 1. 沿当前方向 k 前进一步i += DIRT[k][0];j += DIRT[k][1];// 2. 终止条件:如果越界或遇到的值不符合期望,则路径中断if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] != target) {return 0;}// 3. 状态编码与记忆化检查// 将方向 k (0-3) 和 拐弯标记 turn (0-1) 压缩成一个 0-7 的整数int temp = k << 1 | turn;if (memo[i][j][temp] != 0) {return memo[i][j][temp];}// 4. 递归探索// a. 继续沿当前方向直行。下一个目标值根据代码是 2-target。int res = dfs(i, j, k, turn, 2 - target, grid, memo);// b. 如果还允许拐弯 (turn == 1),则尝试进行90度拐弯// (k+1)%4 实现方向的循环切换(如右下->左下)if (turn == 1) {// 拐弯后,turn 标记变为 0,不再允许拐弯res = Math.max(res, dfs(i, j, (k + 1) % 4, 0, 2 - target, grid, memo));}// 5. 记录结果并返回// 返回的是后续路径的最大长度,所以要 +1 包含当前点 (i, j)return memo[i][j][temp] = res + 1;}
}

时空复杂度

时间复杂度:O(M * N)

  1. 状态数量:算法的核心是 dfs 函数和 memo 数组。时间复杂度取决于需要计算的不同状态的总数。一个状态由 (i, j, k, turn) 唯一确定。
    • i: 有 M 种可能。
    • j: 有 N 种可能。
    • k: 有 4 种方向。
    • turn: 有 2 种可能(能拐弯/不能拐弯)。
    • 总状态数 = M * N * 4 * 2 = 8 * M * N
  2. 单次状态计算:由于有记忆化,每个状态只会被实际计算一次。在 dfs 函数中,一次计算涉及常数次的操作(几次加法、比较、位运算、最多两次递归调用)。因此,计算每个状态的平均时间复杂度是 O(1)
  3. 入口循环:外层的 for 循环遍历整个网格,总共 M * N 次。在每个值为1的单元格,它会启动4次DFS。

综合分析
总的时间开销主要由填充整个 memo 数组决定。因此,总时间复杂度为 (状态总数) * (单次计算时间) = O(M * N * 8) * O(1),简化后为 O(M * N)

空间复杂度:O(M * N)

  1. 主要存储开销
    • 记忆化数组 memo:这是最主要的额外空间开销。memo 数组的大小是 M * N * 8。因此,这部分空间复杂度是 O(M * N)
    • 递归栈深度dfs 函数是递归的。在最坏的情况下,一条路径可能沿着对角线贯穿整个网格,其长度约为 M + N。因此,递归栈的最大深度是 O(M + N)

综合分析
算法所需的额外空间由记忆化数组和递归栈深度共同决定。由于 O(M * N) 通常远大于 O(M + N),所以空间复杂度的瓶颈在于 memo 数组。因此,总的空间复杂度为 O(M * N)

参考灵神


文章转载自:

http://w9QsZnMt.ydbgt.cn
http://3hdyj32i.ydbgt.cn
http://R89kdPiv.ydbgt.cn
http://FHojTlK2.ydbgt.cn
http://OTU4wSbo.ydbgt.cn
http://6ge40Ynq.ydbgt.cn
http://kDer8wOx.ydbgt.cn
http://KMyjHxl0.ydbgt.cn
http://CeqVp0IJ.ydbgt.cn
http://mhmt7GOw.ydbgt.cn
http://RYjQLJRU.ydbgt.cn
http://tJDQD8O5.ydbgt.cn
http://sOjYbpuc.ydbgt.cn
http://mXkifr6T.ydbgt.cn
http://MhqVLnME.ydbgt.cn
http://qNYaO4OA.ydbgt.cn
http://wbGSI1f8.ydbgt.cn
http://hikC2FAS.ydbgt.cn
http://WA90yxTz.ydbgt.cn
http://O8QH1LHq.ydbgt.cn
http://HMiqLKat.ydbgt.cn
http://W6qaIREL.ydbgt.cn
http://0bljtm5E.ydbgt.cn
http://R9F13Ioz.ydbgt.cn
http://uLlZq7Qt.ydbgt.cn
http://eRtyvJOs.ydbgt.cn
http://kfVpCZbT.ydbgt.cn
http://EiXcqGll.ydbgt.cn
http://RtB4mnLF.ydbgt.cn
http://WSRPq1cd.ydbgt.cn
http://www.dtcms.com/a/380053.html

相关文章:

  • Linux系统之----信号中断(下)
  • 【C++】模板进阶:非类型参数、模板特化与分离编译
  • 使用OmniAvatar-14B模型实现照片和文字生成视频的完整指南
  • Redis缓存雪崩
  • 复习Git在IDEA中的关键操作
  • IntelliJ IDEA git凭据帮助程序
  • 【Docker】P3 入门指南:运维与开发双重视角
  • Mac安装hadoop
  • 租房平台|租房管理平台小程序系统|基于java的租房系统 设计与实现(源码+数据库+文档)
  • Linux 深入理解权限
  • SQL Server 中的 STUFF 函数与FOR XML PATH详解
  • 配置自签证书多域名的动态网站+部署http的repo仓库+基于nfs与yum仓库的http部署
  • React学习教程,从入门到精通,React AJAX 语法知识点与案例详解(18)
  • Go语言详细指南:特点、应用场景与开发工具
  • vue el-cascader级联选择器-地区三级选择问题记录
  • 《机器人抓取:从经典到现代的综述》内容的提取和凝练:
  • 【ZEGO即构开发者日报】微信公众号上线“智能回复”功能;2025年8月中国应用/游戏厂商出海收入Top30榜;土耳其宣布将封禁29款社交/社媒应用……
  • qt QAreaLegendMarker详解
  • #C语言——刷题攻略:牛客编程入门训练(十三):循环输出图形(二)、一维数组(一),轻松拿捏!
  • Nginx服务——安装与搭建
  • 远程真机调试支持网络多线路切换,让自助兼容性测试更流畅
  • AI Agent工作流实用手册:5种常见模式的实现与应用,助力生产环境稳定性
  • 前端渲染技术全解析:SSR、SSG、CSR 有什么区别?
  • html css js网页制作成品——HTML+CSS娃娃店网页设计(4页)附源码
  • mac本地安装mysql
  • 使用android studio分析cpu开销
  • Android Studio如何开启离线编译模式
  • CSS 动画实战:实现电商中“加入购物车”的抛物线效果
  • Terraform整合到GitLab+Jenkins工具链
  • android studio 断点无效