[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行的算法的
所以我认为这个也是可以求解的,希望蹲一个数学巨魔帮我算算!

Rust 是一种现代的编程语言,特别适合处理内存安全和线程安全的代码。在 LeetCode 中,链表是经常出现的题目练习类型,Rust 语言也是一种非常适合处理链表的语言。接下来,本文将从 Rust 语言的特点、链表的定义和操作,以及 RustLeetCode 中链表题目的练习等几个方面进行介绍和讲解。 Rust 语言的特点: Rust 是一种现代化的高性能、系统级、功能强大的编程语言,旨在提高软件的可靠性和安全性。Rust 语言具有如下几个特点: 1. 内存安全性:Rust 语言支持内存安全性和原语级的并发,可以有效地预防内存泄漏,空悬指针以及据竞争等问题,保证程序的稳定性和可靠性。 2. 高性能:Rust 语言采用了“零成本抽象化”的设计思想,具有 C/C++ 等传统高性能语言的速度和效率。 3. 静态类型检查:Rust 语言支持静态类型检查,可以在编译时检查类型错误,避免一些运行时错误。 链表的定义和操作: 链表是一种据结构,由一个个节点组成,每个节点保存着据,并指向下一个节点。链表的定义和操作如下: 1. 定义:链表是由节点组成的据结构,每个节点包含一个据元素和一个指向下一个节点的指针。 2. 操作:链表的常用操作包括插入、删除、查找等,其中,插入操作主要包括在链表首尾插入节点和在指定位置插入节点等,删除操作主要包括删除链表首尾节点和删除指定位置节点等,查找操作主要包括根据据元素查找节点和根据指针查找节点等。 RustLeetCode 中链表题目的练习: 在 LeetCode 中,链表是常见的题目类型,而 Rust 语言也是一个非常适合练习链表题目的语言。在 Rust 中,我们可以定义结构体表示链表的节点,使用指针表示节点的指向关系,然后实现各种操作函来处理链表操作。 例如,针对 LeetCode 中的链表题目,我们可以用 Rust 语言来编写解法,例如,反转链表,合并两个有序链表,删除链表中的重复元素等等,这样可以更好地熟悉 Rust 语言的使用和链表的操作,提高算法和编程能力。 总之,在 Rust 中处理链表是非常方便和高效的,而 LeetCode 中的练习也是一个非常好的机会,让我们更好地掌握 Rust 语言和链表据结构的知识。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值