每日算法系列【LeetCode 329】矩阵中的最长递增路径

简介: 每日算法系列【LeetCode 329】矩阵中的最长递增路径

题目描述

给定一个整数矩阵,找出最长递增路径的长度。

对于每个单元格,你可以往上,下,左,右四个方向移动。你不能在对角线方向上移动或移动到边界外(即不允许环绕)。

示例1

输入:
nums = 
[
  [9,9,4],
  [6,6,8],
  [2,1,1]
]
输出:
4
解释:
最长递增路径为 [1, 2, 6, 9]。

示例2

输入:
nums = 
[
  [3,4,5],
  [3,2,6],
  [2,2,1]
]
输出:
4
解释:
最长递增路径是 [3, 4, 5, 6]。注意不允许在对角线方向上移动。

题解

DFS+记忆化搜索

对于点  来说,以它为终点的最长递增路径一定会经过上下左右四个点其一。所以如果它四周的点小于  ,就递归遍历四周的点,然后以  为终点的最长递增路径长度就是以四周小于它的点为终点的最长递增路径长度加  :

注意这里四周的点首先不能超过边界,然后数值上必须小于  。

但是直接这样会有很多重复计算,所以我们必须用记忆化搜索,用  保存搜索结果。如果发现已经计算过了,就不再递归,直接返回结果。

最终每个格子最多遍历一遍,时间复杂度是  。

拓扑排序

把每个格子当作一个点,然后从数值小的点向四周比它大的点连一条有向边,最终一定会形成一个有向无环图,问题就转变成了求有向无环图中的最长路径。

方法是先找到所有入度为  的结点,然后放入一个队列,依次从队列里取出结点,从图中删除这些结点。然后图中就出现了新的入度为  的结点了,它们路径长度加  。接着重复上面的操作,直到最后没有结点。

代码

DFS+记忆化搜索(c++)

class Solution {
public:
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    int longestIncreasingPath(vector<vector<int>>& matrix) {
        int n = matrix.size();
        if (!n) return 0;
        int m = matrix[0].size();
        vector<vector<int> > dp(n, vector<int>(m, 0));
        int res = 1;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                dp[i][j] = dfs(matrix, i, j, dp);
                res = max(res, dp[i][j]);
            }
        }
        return res;
    }
    int dfs(vector<vector<int>>& matrix, int x, int y, vector<vector<int>>& dp) {
        if (dp[x][y] > 0) return dp[x][y];
        int n = matrix.size(), m = matrix[0].size();
        dp[x][y] = 1;
        for (int i = 0; i < 4; ++i) {
            int nx = x + dx[i], ny = y + dy[i];
            if (inside(nx, ny, n, m) && matrix[nx][ny] < matrix[x][y]) {
                dp[x][y] = max(dp[x][y], dfs(matrix, nx, ny, dp)+1);
            }
        }
        return dp[x][y];
    }
    bool inside(int x, int y, int n, int m) {
        return 0 <= x && x < n && 0 <= y && y < m;
    }
};

拓扑排序(c++)

class Solution {
public:
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    int longestIncreasingPath(vector<vector<int>>& matrix) {
        int n = matrix.size();
        if (!n) return 0;
        int m = matrix[0].size();
        vector<vector<int> > dp(n, vector<int>(m, 0));
        vector<vector<int> > degree(n, vector<int>(m, 0));
        queue<pair<int, int> > Q;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                for (int k = 0; k < 4; ++k) {
                    int nx = i + dx[k], ny = j + dy[k];
                    if (inside(nx, ny, n, m) && matrix[nx][ny] < matrix[i][j]) {
                        degree[i][j]++;
                    }
                }
                if (!degree[i][j]) {
                    Q.push({i, j});
                    dp[i][j] = 1;
                }
            }
        }
        int res = 1;
        while (!Q.empty()) {
            int x = Q.front().first, y = Q.front().second;
            Q.pop();
            res = max(res, dp[x][y]);
            for (int i = 0; i < 4; ++i) {
                int nx = x + dx[i], ny = y + dy[i];
                if (inside(nx, ny, n, m) && matrix[nx][ny] > matrix[x][y]) {
                    if (!(--degree[nx][ny])) {
                        Q.push({nx, ny});
                    }
                    dp[nx][ny] = max(dp[nx][ny], dp[x][y]+1);
                }
            }
        }
        return res;
    }
    bool inside(int x, int y, int n, int m) {
        return 0 <= x && x < n && 0 <= y && y < m;
    }
};
相关文章
|
7月前
|
存储 人工智能 算法
从零掌握贪心算法Java版:LeetCode 10题实战解析(上)
在算法世界里,有一种思想如同生活中的"见好就收"——每次做出当前看来最优的选择,寄希望于通过局部最优达成全局最优。这种思想就是贪心算法,它以其简洁高效的特点,成为解决最优问题的利器。今天我们就来系统学习贪心算法的核心思想,并通过10道LeetCode经典题目实战演练,带你掌握这种"步步为营"的解题思维。
|
12月前
|
Go 开发者 索引
【LeetCode 热题100】路径与祖先:二叉树中的深度追踪技巧(力扣33 / 81/ 153/154)(Go语言版)
本文深入探讨了LeetCode中四道关于「搜索旋转排序数组」的经典题目,涵盖了无重复和有重复元素的情况。通过二分查找的变形应用,文章详细解析了每道题的解题思路和Go语言实现代码。关键点包括判断有序区间、处理重复元素以及如何缩小搜索范围。文章还总结了各题的异同,并推荐了类似题目,帮助读者全面掌握二分查找在旋转数组中的应用。无论是初学者还是有经验的开发者,都能从中获得实用的解题技巧和代码实现方法。
490 14
|
算法 Go
【LeetCode 热题100】深入理解二叉树结构变化与路径特性(力扣104 / 226 / 114 / 543)(Go语言版)
本博客深入探讨二叉树的深度计算、结构变换与路径分析,涵盖四道经典题目:104(最大深度)、226(翻转二叉树)、114(展开为链表)和543(二叉树直径)。通过递归与遍历策略(前序、后序等),解析每题的核心思路与实现方法。结合代码示例(Go语言),帮助读者掌握二叉树相关算法的精髓。下一讲将聚焦二叉树构造问题,欢迎持续关注!
368 10
|
Go
【LeetCode 热题100】路径与祖先:二叉树中的深度追踪技巧(力扣437 / 236 )(Go语言版)
本文深入探讨二叉树中路径与祖先问题,涵盖两道经典题目:LeetCode 437(路径总和 III)和236(最近公共祖先)。对于路径总和 III,文章分析了双递归暴力解法与前缀和优化方法,后者通过哈希表记录路径和,将时间复杂度从O(n²)降至O(n)。在最近公共祖先问题中,采用后序遍历递归查找,利用“自底向上”的思路确定最近公共祖先节点。文中详细解析代码实现与核心要点,帮助读者掌握深度追踪技巧,理解树结构中路径与节点关系的本质。这类问题在面试中高频出现,掌握其解法意义重大。
303 4
|
算法 Go
【LeetCode 热题100】73:矩阵置零(详细解析)(Go语言版)
这篇文章详细解析了力扣热题 73——矩阵置零问题,提供两种解法:一是使用额外标记数组,时间复杂度为 O(m * n),空间复杂度为 O(m + n);二是优化后的原地标记方法,利用矩阵的第一行和第一列记录需要置零的信息,将空间复杂度降低到 O(1)。文章通过清晰的代码示例与复杂度分析,帮助理解“原地操作”及空间优化技巧,并推荐相关练习题以巩固矩阵操作能力。适合刷题提升算法思维!
389 9
|
存储 算法 Java
leetcode算法题-有效的括号(简单)
【11月更文挑战第5天】本文介绍了 LeetCode 上“有效的括号”这道题的解法。题目要求判断一个只包含括号字符的字符串是否有效。有效字符串需满足左括号必须用相同类型的右括号闭合,并且左括号必须以正确的顺序闭合。解题思路是使用栈数据结构,遍历字符串时将左括号压入栈中,遇到右括号时检查栈顶元素是否匹配。最后根据栈是否为空来判断字符串中的括号是否有效。示例代码包括 Python 和 Java 版本。
421 4
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
413 6
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
503 2

热门文章

最新文章