Number of Ways to Reach a Position After Exactly k Steps
Number of Ways to Reach a Position After Exactly k Steps
You are given two positive integers startPos and endPos . Initially, you are standing at position startPos on an infinite number line. With one step, you can move either one position to the left, or one position to the right.
Given a positive integer $k$, return the number of different ways to reach the position endPos starting from startPos , such that you perform exactly $k$ steps. Since the answer may be very large, return it modulo ${10}^{9} + 7$.
Two ways are considered different if the order of the steps made is not exactly the same.
Note that the number line includes negative integers.
Example 1:
Input: startPos = 1, endPos = 2, k = 3 Output: 3 Explanation: We can reach position 2 from 1 in exactly 3 steps in three ways: - 1 -> 2 -> 3 -> 2. - 1 -> 2 -> 1 -> 2. - 1 -> 0 -> 1 -> 2. It can be proven that no other way is possible, so we return 3.
Example 2:
Input: startPos = 2, endPos = 5, k = 10 Output: 0 Explanation: It is impossible to reach position 5 from position 2 in exactly 10 steps.
Constraints:
$1 \leq startPos, endPos, k \leq 1000$
解题思路
比赛的时候是用动态规划写的,没想到还有数学的解法。
先算一下从起点到终点要走的步数,假设是$d$,即从起点到终点要净走$d$步。假设起点在终点的左边,往右走的步数记为$r$,往左走的步数记为$l$,那么要满足
\begin{cases}
r - l = d \\
r + l = k
\end{cases}
解得$r = \frac{d + k}{2}$,相当于要从$k$步中选出$r$步向右走,即$C_{k}^{r}$。算这个组合数可以直接用公式加快速幂求逆元。
AC代码如下,时间复杂度为$O(nlogn)$:
1 class Solution { 2 public: 3 int mod = 1e9 + 7; 4 5 int qmi(int a, int k, int p) { 6 int ret = 1; 7 while (k) { 8 if (k & 1) ret = 1ll * ret * a % p; 9 a = 1ll * a * a % p; 10 k >>= 1; 11 } 12 return ret; 13 } 14 15 int numberOfWays(int startPos, int endPos, int k) { 16 int d = abs(endPos - startPos); 17 if (d + k & 1 || d > k) return 0; // 特判掉d和k奇偶不同与无法到达终点的情况 18 int ret = 1, r = d + k >> 1; 19 for (int i = 1, j = k; i <= r; i++, j--) { 20 ret = (1ll * ret * j) % mod * qmi(i, mod - 2, mod) % mod; 21 } 22 return ret; 23 } 24 };
下面再讲讲用dp的做法。
定义状态$f(i,j)$表示所有从起点花$i$步走到位置$j$的方案的集合,属性就是计数。根据最后一步的不同来划分集合,因此状态转移方程为$$f(i,j) = f(i-1,j-1) + f(i-1,j+1)$$
还需要注意下标可能会出现负数的情况,因此需要加上一个偏移量。可以发现如果起点在$1$这个位置,那么最多向左$500$步,因此左边界最远为$-500$。同理如果起点在$1000$这个位置,那么最多向右走$500$步,因此右边界最远为$1500$。因此下标的最大长度为$2000$,一开始可以给起点和终点加上偏移量$500$。
AC代码如下,时间复杂度为$O(n^2)$:
1 class Solution { 2 public: 3 int mod = 1e9 + 7, N = 2010, B = 500; 4 5 int numberOfWays(int startPos, int endPos, int k) { 6 startPos += B, endPos += B; // 加上偏移量 7 vector<vector<int>> f(k + 1, vector<int>(N)); 8 f[0][startPos] = 1; 9 for (int i = 1; i <= k; i++) { 10 for (int j = 0; j < N; j++) { // 直接枚举加上偏移量后的位置 11 if (j) f[i][j] = f[i - 1][j - 1]; // 可以从左边走过来 12 if (j + 1 < N) f[i][j] = (f[i][j] + f[i - 1][j + 1]) % mod; // 可以从右边走过来 13 } 14 } 15 return f[k][endPos]; 16 } 17 };
参考资料
力扣第309场周赛:https://www.bilibili.com/video/BV1DB4y1G7gX
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16659441.html