[20210513]LeetCode每日一题 - 1269. 停在原地的方案数

题目描述【困难】

有一个长度为 arrLen 的数组,开始有一个指针在索引 0 处。

每一步操作中,你可以将指针向左或向右移动 1 步,或者停在原地(指针不能被移动到数组范围外)。

给你两个整数 stepsarrLen ,请你计算并返回:在恰好执行 steps 次操作以后,指针仍然指向索引 0 处的方案数。

由于答案可能会很大,请返回方案数 模 10^9 + 7 后的结果。

示例 1:

输入:steps = 3, arrLen = 2
输出:4
解释:3 步后,总共有 4 种不同的方法可以停在索引 0 处。
向右,向左,不动
不动,向右,向左
向右,不动,向左
不动,不动,不动

示例 2:

输入:steps = 2, arrLen = 4
输出:2
解释:2 步后,总共有 2 种不同的方法可以停在索引 0 处。
向右,向左
不动,不动

示例 3:

输入:steps = 4, arrLen = 2
输出:8

提示:

1 <= steps <= 500
1 <= arrLen <= 10^6

题目地址:地址


初步分析

遇到需要多少步后得到多少种方案的,可以一律用动态规划解题


正式分析

既然基于动态规划解题,则我们分析其动态规划的状态转移方程:
我们以 dp[i][j] 为在第 i 步时,所处下标为 j 的所用 方案数

其中:

  • i 的范围值是 0 ~ steps
  • j 的范围值,也就是 0 到最远可到达的位置,即 arrLen - 1(数组边界) 与 steps(步数上限) 的最小值,即 0 ~ Math.min(arrLen - 1, steps)

dp[i][j] 的状态来源为:

  • 来自 j - 1 的位置:由左移动过来
  • 来自 j + 1 的位置:由右移动过来
  • 来自 j 的位置:不动

因此,状态转移方程即为:

dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1] + dp[i - 1][j]

接下来,我们分析边界值情况:

  1. i = 0 时,下标 j 一定在 0 ,因此此时的 dp[0][0] = 1
    因此当 i = 0 时,下标 j 肯定不能处于任何非 0 的位置,所以 dp[0][j] = 0
  2. j >= 1 时,状态值来源肯定是可以来自于 dp[i - 1][j - 1] ,也就是可以来自于 由左边(j - 1)移动而来
  3. j < Math.min(arrLen - 1, steps) 时,状态值来源可以来自于 dp[i - 1][j + 1],即 由右边(j + 1)移动而来

根据以上梳理,即可求得代码:


具体代码

初始方案
function numWays(steps: number, arrLen: number): number {
    const MODULO = 1000000007;
    const indexMax = Math.min(steps, arrLen - 1)
    const dp: number[][] = new Array(steps + 1).fill(0).map(item => new Array(indexMax + 1).fill(0))

    dp[0][0] = 1

    for (let i = 1; i <= steps; i++) {
        for (let j = 0; j <= indexMax; j++) {
            dp[i][j] = dp[i - 1][j]

            if (j >= 1) {
                dp[i][j] = (dp[i][j] + dp[i - 1][j - 1]) % MODULO
            }
            
            if (j < indexMax) {
                dp[i][j] = (dp[i][j] + dp[i - 1][j + 1]) % MODULO
            }
        }
    }

    return dp[steps][0]
};

TS AC,执行用时:144 ms, 在所有 TypeScript 提交中击败了 100% 的用户


对上面的代码,我们分析后发现,其实 最远走到的下标 应该是 Math.floor(steps / 2)arrLen - 1 中最小值,因为如果超出了 Math.floor(steps / 2),则 j 永远都不可能回到 0 上,至于为什么用 Math.floor,可以思考一下为什么

另外,我们可以发现,dp 一直依赖于上一行的状态值,即 i - 1,所以我们可以把 dp 简化成一维数组:

优化后
function numWays(steps: number, arrLen: number): number {
    const MODULO = 1000000007;
    const indexMax = Math.min(Math.floor(steps / 2), arrLen - 1)
    let dp: number[] = new Array(indexMax + 1).fill(0)

    dp[0] = 1

    for (let i = 1; i <= steps; i++) {
        const dpNext = new Array(indexMax + 1).fill(0)

        for (let j = 0; j <= indexMax; j++) {
            dpNext[j] = dp[j]

            if (j >= 1) {
                dpNext[j] = (dpNext[j] + dp[j - 1]) % MODULO
            }
            
            if (j < indexMax) {
                dpNext[j] = (dpNext[j] + dp[j + 1]) % MODULO
            }
        }

        dp = dpNext
    }

    return dp[0]
};

TS AC,执行用时:104 ms, 在所有 TypeScript 提交中击败了 100% 的用户


最后

如果我在哪里写的有问题,欢迎指出,共同进步,谢谢阅读~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值