深度优先搜索之记忆化dfs

本文介绍了记忆化深度优先搜索(DFS)在解决递归问题中的关键作用,通过实例演示了如何从朴素DFS升级到记忆化,以及在整数排序、路径问题和学生出勤记录等场景的应用。记忆化策略显著减少了重复计算,使得复杂度从指数级降至幂次级。

前言

前不久,我写一篇bfs的进阶版双向bfs:广度优先搜索之双向bfs
这篇文章主要是讲述对于一些特定的bfs问题,可以使用两头齐搜的双向bfs方式来解决其搜索空间爆炸的问题。
同样,对于普通的dfs而言,它的时间复杂度是指数级别,这个时间复杂度是非常恐怖的,很容易就出现最经典的超出时间限制。正是因为如此,所以往往在使用dfs的时候回出现一些剪枝手段,来大幅减少搜索的时间复杂度,其中最有效的就莫过于记忆化dfs。
记忆化dfs,顾名思义就是使用缓存来记录搜索的数据,当再次搜索到相同的状态时直接使用缓存数据即可,从而能够将指数级别的时间复杂度降低到幂数级别的时间复杂度,这可是完全降低了一个档次啊。
同样,接下来我分别详细讲述朴素dfs的实现思路以及记忆化dfs的求解思路,然后结合真题进行实操。

朴素dfs的求解思路

朴素dfs实质上就是递归,在递归的过程中获取我们想要的数据。

记忆化dfs的求解思路

记忆化dfs的核心是在朴素dfs的基础上新增了一个记录递归中间状态和数据的容器,当再次递归到相同的状态时,直接使用容器的缓存结果即可,从而实现剪枝的效果,这也是一种空间换时间的策略。
而记忆化dfs的时间复杂度之所以降低到幂数级别,是因为由于缓存容器的存在,搜索的空间由原来的递归栈变成了容器的大小,简单来说,当容器填满时dfs将不再进行所有的数据都可以从容器中直接获取,所有其时间复杂度变相的变成填满容器的幂数级别。
而记忆化dfs的难点也正是在于其容器的定义,并且在很多情况下,记忆化dfs都能够转变为动态规划,而缓存容器也就是动态规划的dp数组,只不过记忆化dfs填充容器是通过“暴力”搜索来实现,而动态规划填充dp是通过状态转移方程来实现。
下面题目我都只使用dfs进行实现,若大家有兴趣,可以自行尝试将记忆化dfs改造成动态规划。

将整数按权重排序

题目描述

在这里插入图片描述
题目描述不是重点,所以我直接用截图,想看原题的可以直接点链接跳转过去。
原题地址: 将整数按权重排序

解题思路

一种很直接的想法:获取到lo到hi之间所有位置的权重,然后按照位置和权重进行排序,最后返回第k个位置是的数据即可。
思路不难,直接上代码:

function getKth(lo: number, hi: number, k: number): number {
    const dfs = (num: number) => {
        // ...
    }
    const res: number[][] = [];
    for (let i = lo; i <= hi; i++) {
        // 类似map的存储方式,便于自定义后续排序
        res.push([i, dfs(i)]);
    }
    res.sort((a, b) => {
        if (a[1] !== b[1]) {
            return a[1] - b[1];
        }
        return a[0] - b[0];
    });
    return res[k - 1][0];
};

接下来就是dfs的实现。

朴素dfs

 const dfs = (num: number) => {
        if (num === 1) {
            return 0;
        }
        return num % 2 === 0 ? dfs(num / 2) + 1 : dfs(3 * num + 1) + 1;
    }

根据条件很容易写出上述朴素版的dfs,但是显然,它一定会超出时间限制。因为我们外层有for循环在计算每一个数字的权重,那么这其中必然会重复计算很多相同的状态,所以此时记忆化dfs登场。

记忆化dfs

 const cache = new Array(1000010).fill(0);
    const dfs = (num: number) => {
        if (num === 1) {
            return 0;
        }
        // 直接获取缓存的数据
        if (cache[num] !== 0) {
            return cache[num];
        }
        // 默认当前查找需要一步
        let res = 1;
        res += num % 2 === 0 ? dfs(num / 2) : dfs(3 * num + 1);
        // 保存当前值的计算结果
        cache[num] = res;
        return res;
    }

这里,我们使用cache来记录递归中的重复状态,cache[num]代表数字为num时的权重大小。

小结

一维cache热身结束,接下来我们来试试二维cache。

矩阵中的最长递增路径

题目描述

在这里插入图片描述
原题地址:

评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值