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