[C++&Rust]LeetCode No.1269 停在原地的方案数(每日一题)

原贴地址:http://blog.leanote.com/post/dawnmagnet/No.1269-停在原地的方案数

题目

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

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

给你两个整数 steps 和 arrLen ,请你计算并返回:在恰好执行 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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-ways-to-stay-in-the-same-place-after-some-steps
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路分析

这个题目其实需要一点点观察能力,我们先看到arr和steps的两种关系。
其实示例2和示例3就很好地展示了这一点。
对于示例2来说,4长度的arr根本就没有必要。因为一共就两步,最多右一步左一步,根本不可能走到后面的格子,而我们假设有一个无限长度的arr和step等于5的条件,可以看出,最多只能走到arr[1]的位置,因为一旦走到arr[2]就回不来了(已经向右走了三步,回头也需要三步,但总共只有5步)。
列个表看一下规律
steps|2|3|4|5
–|
最大回头的位置|1(0->1->0)|1|2(0->1->2->1->0)|2
最大位置对应的arr_len|2|2|3|3
所以我们归纳出一个结论,当arr大于(step整除2+1)的时候有多余的地方,可以使用(step整除2+1)来代替多余的arr,达到减少计算的目的。

这个题应该使用dp来做,因为许多因素,比如这个题是一个模拟题,他引导我们去模拟,但必定超时,可以用dfs做,也必定超时,这就引导我们向dp去思考,而且这个题目还有一个非常关键的提示点,就是我们只需要获得方案数,我们无需获得每个方案是什么
这也是出题人考虑的一点,就是我引导你去这么思考,但这只是一个思考的途径,不是让你做题按照这个思路做。
这又是一个建议使用dp的铁证,通常题目出现“答案数”,“路径数”,“方案数”等字眼的时候,就说明题目关心的并非每个具体方案,而是整体的方案数,这种情况下99%的情况都使用dp来完成。
dp递推的量一般都是与答案息息相关的,甚至能直接推导出答案本身的情况也不少见。这道题就是可以直接推出答案的。
鉴于要计算的是方案数,我们就设一个dp数组,arr[i]表示指向arr[i]的方案数,这样我们递推结束后,可以直接读取arr[0]来获得答案。
递推也非常容易想到,因为在step每变化1的时候,假设一个方案上一步指向i号指针,下一步可能指向的是i-1或者i或者i+1(暂未考虑边界条件),那么我们对于方案数来说,我们下一步的arr[i+1]和arr[i]和arr[i-1]都需要加上上一步的arr[i],这样才能模拟出走了一步的情况。

接下来我们就可以直接看代码了。

C++代码

const int mod = 1e9 + 7;
class Solution {
public:
    int numWays(int steps, int arr_len) {
        int n = min(arr_len, steps / 2 + 1);
        vector<int> arr(n, 0);
        arr[0] = 1;
        for (int i = 0; i < steps; ++i) {
            vector<int> arr_tmp(n, 0);
            for (int i = 0; i < n && arr[i] > 0; ++i) {
                for (int j = max(0, i - 1); j <= min(n - 1, i + 1); ++j) {
                    arr_tmp[j] += arr[i];
                    arr_tmp[j] %= mod;
                }
            }
            arr = move(arr_tmp);
        }
        return arr[0];
    }
};

Rust代码

impl Solution {
    pub fn num_ways(steps: i32, arr_len: i32) -> i32 {
        let n = arr_len.min((steps + 2) / 2) as usize;
        let mut arr = vec![0; n];
        arr[0] = 1;
        for i in 0..steps {
            let mut arr_tmp = vec![0; n];
            for i in 0..n {
                if arr[i] > 0 {
                    for j in (1.max(i) - 1)..=((n-1).min(i+1)) {
                        arr_tmp[j] += arr[i];
                        arr_tmp[j] %= (1e9 as i32 + 7);
                    }
                } else {
                    break;
                }
            }
            arr = arr_tmp;
        }
        arr[0]
    }
}

看到这里应该结束了吧!不是!
今天还有特别节目
对于该题我还有一种全新的思路!

在线蹲一个数学巨魔,我已经证明出来时间复杂度可以到达O(steps)了,但就是算不出来递推公式
可以观察一下steps=10,arr_len=10的情况

[0, 1, 1, 0, 0, 0, 0, 0]
[0, 2, 2, 1, 0, 0, 0, 0]
[0, 4, 5, 3, 1, 0, 0, 0]
[0, 9, 12, 9, 4, 1, 0, 0]
[0, 21, 30, 25, 14, 5, 1, 0]
[0, 51, 76, 69, 44, 20, 6, 0]
[0, 127, 196, 189, 133, 70, 26, 0]
[0, 323, 512, 518, 392, 229, 96, 0]
[0, 835, 1353, 1422, 1139, 717, 325, 0]
[0, 2188, 3610, 3914, 3278, 2181, 1042, 0]

最终的答案是2188
但其实我们根本没必要算这么多行
可以观察得出,

(观察系数,就是上表开头的几行)

2188  
=1*835+1*1353  
=2*323+2*512+1*518  
=...  
=21**2+30**2+25**2+14**2+5**2+1**2  

所以只需要算出这一行的,求平方和即可得出答案。
(如果steps是奇数可能需要算两行,但仍是O(steps))
而我参考了杨辉三角的求解
https://leetcode-cn.com/problems/pascals-triangle-ii/solution/yang-hui-san-jiao-ii-by-leetcode-solutio-shuk/
是有在O(n)时间求出第n行的算法的
所以我认为这个也是可以求解的,希望蹲一个数学巨魔帮我算算!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值