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

从经典力扣题发掘DFS与记忆化搜索的本质 -从矩阵最长递增路径入手 一步步探究dfs思维优化与编程深度思考

1引子:

DFS和递归法的一道经典例题矩阵最长递增子序列这个题写完之后脑袋产生了许多突发奇想:


1 第一个堆栈代码段这些底层C语言内部管理的工具它是怎么进行内存分配的?能不能深究?
2 第二个这个DFS和计划数组存储的思路到底抽象了哪种思维?能不能发散到其他算法数据结构的面试题中?或者说能不能在其他的编程中应用到这种思维抽象出一个公有的编程范式思维范式应用到其他的任何领域包括生活中?
3 是就有了这篇博文希望大家一起和我探讨一下

2 题描述

给定一个 n 行 m 列的矩阵,矩阵内所有数均为非负整数。需要找到一条最长路径,使得这条路径上的元素是严格递增的。路径可以向上、下、左、右四个方向移动,但不能重复访问同一个单元格。

示例:
输入:[[1,2,3],[4,5,6],[7,8,9]]
输出:5
解释:最长递增路径为 1->2->3->6->9

1暴力 DFS 解法

首先想到的是暴力 DFS,从每个单元格出发,尝试所有可能的路径:
 

#include <stdio.h>
#include <stdlib.h>// 方向数组:上、下、左、右
const int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};// 深度优先搜索函数
int dfs(int** matrix, int rows, int cols, int r, int c, int prev) {// 检查边界条件和递增条件if (r < 0 || r >= rows || c < 0 || c >= cols || matrix[r][c] <= prev) {return 0;}int maxPath = 0;// 遍历四个方向for (int i = 0; i < 4; i++) {int nr = r + dirs[i][0];int nc = c + dirs[i][1];int path = 1 + dfs(matrix, rows, cols, nr, nc, matrix[r][c]);if (path > maxPath) {maxPath = path;}}return maxPath;
}// 主函数:计算最长递增路径
int longestIncreasingPath(int** matrix, int matrixSize, int* matrixColSize) {if (matrixSize == 0 || *matrixColSize == 0) {return 0;}int rows = matrixSize;int cols = *matrixColSize;int maxLength = 0;// 遍历每个单元格作为起点for (int r = 0; r < rows; r++) {for (int c = 0; c < cols; c++) {int length = dfs(matrix, rows, cols, r, c, -1);if (length > maxLength) {maxLength = length;}}}return maxLength;
}

复杂度分析:

  • 时间复杂度:O (4^(n*m)),每个单元格有 4 个方向可以选择
  • 空间复杂度:O (n*m),递归栈的深度最大为矩阵的大小

问题: 这种解法会导致大量重复计算,比如从不同路径到达同一个单元格时,会重复计算该单元格的最长路径。

2 记忆化搜索优化

为了避免重复计算,可以使用记忆化搜索:用一个二维数组memo记录每个单元格的最长递增路径长度。当访问一个单元格时,如果该单元格的结果已经计算过,直接返回结果,否则进行计算并保存结果。

#include <stdio.h>
#include <stdlib.h>// 方向数组:上、下、左、右
const int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};// 深度优先搜索函数
int dfs(int** matrix, int rows, int cols, int r, int c, int** memo) {// 如果已经计算过,直接返回结果if (memo[r][c] != 0) {return memo[r][c];}int maxPath = 1;  // 至少包含当前单元格// 遍历四个方向for (int i = 0; i < 4; i++) {int nr = r + dirs[i][0];int nc = c + dirs[i][1];// 检查边界条件和递增条件if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && matrix[nr][nc] > matrix[r][c]) {int path = 1 + dfs(matrix, rows, cols, nr, nc, memo);if (path > maxPath) {maxPath = path;}}}// 记录当前单元格的最长路径memo[r][c] = maxPath;return maxPath;
}// 主函数:计算最长递增路径
int longestIncreasingPath(int** matrix, int matrixSize, int* matrixColSize) {if (matrixSize == 0 || *matrixColSize == 0) {return 0;}int rows = matrixSize;int cols = *matrixColSize;// 创建记忆化数组并初始化为0int** memo = (int**)malloc(rows * sizeof(int*));for (int i = 0; i < rows; i++) {memo[i] = (int*)calloc(cols, sizeof(int));}int maxLength = 0;// 遍历每个单元格作为起点for (int r = 0; r < rows; r++) {for (int c = 0; c < cols; c++) {int length = dfs(matrix, rows, cols, r, c, memo);if (length > maxLength) {maxLength = length;}}}// 释放内存for (int i = 0; i < rows; i++) {free(memo[i]);}free(memo);return maxLength;
}

复杂度分析:

  • 时间复杂度:O (n*m),每个单元格只需要计算一次
  • 空间复杂度:O (n*m),主要用于存储记忆化数组

3 记忆化搜索的本质

记忆化搜索实际上是动态规划的递归实现,它的核心思想是

  1. 重叠子问题:原问题可以分解为大量重复的子问题
  2. 最优子结构:问题的最优解包含子问题的最优解
  3. 状态保存:用一个数组保存已经解决的子问题的解

在这个问题中,每个单元格的最长递增路径就是一个子问题,我们通过记忆化数组避免了重复计算,将指数级时间复杂度优化到了线性级别。

4 vscode实际我的测试代码:

1 我的测试

输入矩阵:

[[9, 9, 4],[6, 6, 8],[2, 1, 1]]

步骤解析:

  1. 从单元格 (0,0) 开始,值为 9,四个方向都无法移动,路径长度为 1
  2. 从单元格 (0,1) 开始,值为 9,同样无法移动,路径长度为 1
  3. 从单元格 (1,0) 开始,值为 6,可以移动到 (2,0),继续递归计算...
  4. 当计算到单元格 (2,1) 时,值为 1,可以移动到 (2,0),但 (2,0) 的结果已经计算过,直接使用保存的结果

最终,整个矩阵的最长递增路径为 1->2->6->8->9,长度 5

通过这个问题,我们深入理解了 DFS 和记忆化搜索的结合使用。当遇到需要大量重复计算的问题时,记忆化搜索是一种非常有效的优化方法。关键在于识别问题中的重叠子问题,并设计合适的状态保存方式。

希望这篇文章能帮助你更好地理解深度优先搜索和动态规划的思想,如果你有任何疑问或更好的解法,欢迎在评论区留言讨论!

4.2 举一反三 我的习惯...

如果你对这类问题感兴趣,可以尝试以下我做的其他教程题目

  1. 力扣 329:矩阵中的最长递增路径本题进阶版
  2. 力扣 62:不同路径
  3. 力扣 64:最小路径和
  4. 力扣 79:单词搜索

如果你觉得我自己写的这个代码还不错,请给我点赞、收藏、关注,获取更多算法干货,这也是我之后继续发布高质量算法教程和编程技术帖的最大的动力   感谢大家了!!!!!

相关文章:

  • 互联网大厂Java求职面试实战:Spring Boot与微服务场景深度解析
  • 鸿蒙HarmonyOS list优化一: list 结合 lazyforeach用法
  • yarn workspace使用指南
  • 精品可编辑PPT | 全面风险管理信息系统项目建设风控一体化标准方案
  • 数据库实验报告 SQL SERVER 2008的基本操作 1
  • 企业对数据集成工具的需求及 ETL 工具工作原理详解
  • 一键生成达梦、Oracle、MySQL 数据库 ER 图!解锁高效数据库设计!
  • 《基于 Kubernetes 的 WordPress 高可用部署实践:从 MariaDB 到 Nginx 反向代理》
  • PostgreSQL 序列(Sequence) 与 Oracle 序列对比
  • springboot集成langchain4j实现票务助手实战
  • 视觉-语言-动作模型:概念、进展、应用与挑战(上)
  • 基于Django和机器学习实现的中风预测系统
  • web 自动化之 selenium 下拉鼠标键盘文件上传
  • 【Linux网络编程】HTTPS协议原理
  • MySQL全量,增量备份与恢复
  • PTA:jmu-ds-最短路径
  • vue3+dhtmlx-gantt实现甘特图展示
  • 前端项目2-01:个人简介页面
  • 使用 DMM 测试 TDR
  • openpi π₀ 项目部署运行逻辑(一)——综述
  • 英国收紧移民政策,技术工作签证、大学招生面临更严要求
  • 夜读丨取稿费的乐趣
  • 扶桑谈|从石破茂“越菲行”看日本周边外交布局战略新动向
  • 上海浦东机场1号、2号航站楼均推出国内出发安检24小时服务
  • 5年建成强化城市核心功能新引擎,上海北外滩“风景文化都是顶流”
  • 中方发布会:中美经贸高层会谈取得了实质性进展,达成了重要共识