【记忆化搜索】条条大路通AC,走好适合你的那一条,走到后再考虑走得快

Problem: 2850. 将石头分散到网格图的最少移动次数

  1. 思路

    1. 碎碎念
    2. 改造题目
  2. 解题过程

    1. 树形结构
    2. 代码逻辑,先枚举终止点位
    3. 代码逻辑,后枚举起始点位
    4. 闭包实现记忆化搜索
  3. 复杂度

  4. Code

思路

一看标签,动态规划,觉得没啥思路,一看矩阵,固定大小3*3,好!咱们可以试试dfs,然后再用记忆化搜索优化一下时间复杂度。

碎碎念

力扣的新题很多,题干描述也是千奇百怪,我们怎么才能在面对一道新题时,能够利用自己所学的知识,解决它呢,我们怎么才能让自己不需要看题解,也能想到思路呢?

上述的内容是我自己在面对新题目的时候,常常会在脑海里闪过的念头。

我从5月份开始到现在,刷了290题了,这个题量虽然不算多,但是说实话还是积累了一些经验的。通过这个题量,我也能对自己擅长什么和不擅长什么有一个大致的概念了。

  • 我比较擅长dfs、二叉树、滑动窗口等类型的题目
  • 我不太擅长动态规划、位运算、堆、并查集等类型的题目

因此当我遇到一道新题目的时候,我会先尝试着把它转变成我熟悉的题目,拉到我熟悉的领域,再用我擅长的技能去解决它。

改造题目

理解题干信息,然后转换问题,改造题目。

题目要求我们计算移动矩阵中的石子使矩阵每个格子都有1颗石子的最小的步数

最小,最值问题,我们可以联想一下我们以前写过的类似的最值问题的解法,都有哪些可以解决的方式?

比如:

等等....

由我上方举的例子,可以看到,解决最值问题是有挺多种方式的。因此针对本题,我们看看是否也可以让它往上面的几个例子靠一靠?

移动,这个操作肯定需要一个起始点位和一个终止点位。

  • 石头数量大于1的点位,就是起始点位。
  • 石头数量等于0的点位,就是终止点位。

以示例2举例,我们可以得到2个数组,一个数组保存全部的起始点位,一个数组保存全部的终止点位。

  • 终止点位: [ [ 0, 2 ], [ 1, 1 ], [ 1, 2 ], [ 2, 1 ] ]
  • 起始点位: [ [ 0, 1 ], [ 2, 2 ] ]

那么石头移动的问题是不是就可以转变成:

计算起始点位 数组中元素 到达终止点位 数组中各元素的最短距离

举个例子,我们可以这样选择一条路:

  • 0, 1 \] =\> \[ 0, 2

  • 0, 1 \] =\> \[ 1, 1

  • 2, 2 \] =\> \[ 1, 2

  • 2, 2 \] =\> \[ 2, 1

    由此可得的路径长度为:1+1+1+1 = 4

还可以举个例子,我们选另外的一条路:

  • 0, 1 \] =\> \[ 1, 2

  • 0, 1 \] =\> \[ 2, 1

  • 2, 2 \] =\> \[ 0, 2

  • 2, 2 \] =\> \[ 1, 1

    由此可得的路径长度为:2+2+2+2 = 8

我们还可以继续举出很多不同的路径。

也就是说,问题进一步变成了我们枚举全部的路线 ,然后选中最短的那条路线

这是不是即摆脱了我们不是很想处理的矩阵问题,也抛开了什么所谓石头移动问题,而将本题变成了非常直白的一道枚举数组组合的问题?

那怎么去枚举呢?没错,就是我最喜欢的老朋友,dfs。

解题过程

针对每一个终止点位,都让它和起始点位中的元素进行一次组合,组合后,将此点位过滤,进入下一次操作,下一次操作同上次,挑选未被选择的终止点位,去让它起始点位中的元素进行一次组合,重复,直到终止点位中所有元素都被选择过了,则计算最终的路径长度,并将其和ans比较,选择较小的那一项。

树形结构

代码逻辑,先枚举终止点位

js 复制代码
  const dfs = (zeroIndex, step) => {
            // 边界条件,当所有点都被选过了,则结束。
            if (zeroIndex.length === 0) {
                ans = Math.min(step, ans)
                return;
            }
            for (const zPoint of zeroIndex) {
            // do something

                }


            }

        }

代码逻辑,后枚举起始点位

js 复制代码
    for (const zPoint of zeroIndex) {
                // 计算起始点位到当前终止点位的距离
                for (const largePoint of largeIndex) {
                    let [x, y] = largePoint
                    let [x2, y2] = zPoint
                // 如果起始点位上的石头数量大于1,说明还可以移动
                // 否则,不移动,这个点位上没有额外的石头给别人了。
                    if (hash.get(`${x}-${y}`) > 1) {
                        hash.set(`${x}-${y}`, hash.get(`${x}-${y}`) - 1);

                        // 进入下一次操作时,得把已选的终止点位过滤掉
                        memoDfs(zeroIndex.filter(item => JSON.stringify(item) !== JSON.stringify(zPoint)), step + Math.abs(x - x2) + Math.abs(y - y2));

                        // 选完起始点位后,在递归完成时,再把之前消耗的
                        // 石头加回去
                        hash.set(`${x}-${y}`, hash.get(`${x}-${y}`) + 1)
                    }


    }

闭包实现记忆化搜索

js 复制代码
        let memoDfs = cache(dfs)


// 使用闭包和高阶函数来实现缓存装饰器
function cache(func) {
    const memo = new Map();

    return function (...args) {
        const key = JSON.stringify(args);
        if (!memo.has(key)) {
            memo.set(key, func.apply(this, args));
        }
        return memo.get(key);
    };
}

复杂度

  • 时间复杂度
  • 空间复杂度: O(1)

Code

js 复制代码
/**
 * @param {number[][]} grid
 * @return {number}
 */
var minimumMoves = function (grid) {
    let zeroIndex = [];
    let largeIndex = [];
    let hash = new Map();
    for (let i = 0; i < 3; i++) {
        for (let j = 0; j < 3; j++) {
            if (grid[i][j] === 0) {
                zeroIndex.push([i, j])
            }
            if (grid[i][j] > 1) {
                largeIndex.push([i, j])
                hash.set(`${i}-${j}`, grid[i][j])
            }
        }
    }

    if (largeIndex.length === 1) {
        return zeroIndex.reduce((pre, cur) => {
            let [x, y] = largeIndex[0]
            let [x2, y2] = cur
            pre = pre + Math.abs(x - x2) + Math.abs(y - y2)
            return pre;
        }, 0)
    }
    else if (largeIndex.length === 0) {
        return 0;
    }
    else {
        let ans = Infinity;
        const dfs = (zeroIndex, step) => {
            if (zeroIndex.length === 0) {
                ans = Math.min(step, ans)
                return;
            }
            for (const zPoint of zeroIndex) {
                for (const largePoint of largeIndex) {
                    let [x, y] = largePoint
                    let [x2, y2] = zPoint

                    if (hash.get(`${x}-${y}`) > 1) {
                        hash.set(`${x}-${y}`, hash.get(`${x}-${y}`) - 1);
                        memoDfs(zeroIndex.filter(item => JSON.stringify(item) !== JSON.stringify(zPoint)), step + Math.abs(x - x2) + Math.abs(y - y2));
                        hash.set(`${x}-${y}`, hash.get(`${x}-${y}`) + 1)
                    }


                }


            }

        }
        let memoDfs = cache(dfs)
        memoDfs(zeroIndex, 0)
        return ans;

    }
};

// 使用闭包和高阶函数来实现缓存装饰器
function cache(func) {
    const memo = new Map();

    return function (...args) {
        const key = JSON.stringify(args);
        if (!memo.has(key)) {
            memo.set(key, func.apply(this, args));
        }
        return memo.get(key);
    };
}
相关推荐
gis开发之家1 小时前
《Vue3 从入门到大神40篇》Vue3 源码详解(十):diff 算法全解析 —— 为什么 Vue3 比 Vue2 更快?
javascript·算法·typescript·前端框架·vue3·vue3源码
进击的丸子1 小时前
虹软人脸服务器SDK-C++语言Demo实操指南
后端·算法
兰令水1 小时前
hot100【acm版】【2026.7.25/26打卡-java版本】
java·算法·排序算法
音视频工程实战2 小时前
PromptQL 新手入门与实战指南
数据库·sql·算法
wabs6662 小时前
关于图论【卡码网104.建造最大岛屿的思考】
数据结构·算法·图论
玖玥拾3 小时前
LeetCode 27 移除元素
算法·leetcode
冻柠檬飞冰走茶3 小时前
PTA基础编程题目集 7-15 计算圆周率(C语言实现)
c语言·开发语言·数据结构·算法
问商十三载3 小时前
AI 引擎生成式优化两种思路怎么选?2026 对比分析附选型方法
人工智能·算法
hanlin034 小时前
刷题笔记:力扣第704、977、209题(数组相关)
笔记·算法·leetcode