【特殊子序列 DP】力扣935. 骑士拨号器

象棋骑士有一个独特的移动方式,它可以垂直移动两个方格,水平移动一个方格,或者水平移动两个方格,垂直移动一个方格(两者都形成一个 L 的形状)。

象棋骑士可能的移动方式如下图所示:

在这里插入图片描述
我们有一个象棋骑士和一个电话垫,如下所示,骑士只能站在一个数字单元格上(即蓝色单元格)。

在这里插入图片描述
给定一个整数 n,返回我们可以拨多少个长度为 n 的不同电话号码。

你可以将骑士放置在任何数字单元格上,然后你应该执行 n - 1 次移动来获得长度为 n 的号码。所有的跳跃应该是有效的骑士跳跃。

因为答案可能很大,所以输出答案模 109 + 7.

示例 1:
输入:n = 1
输出:10
解释:我们需要拨一个长度为1的数字,所以把骑士放在10个单元格中的任何一个数字单元格上都能满足条件。

示例 2:
输入:n = 2
输出:20
解释:我们可以拨打的所有有效号码为[04, 06, 16, 18, 27, 29, 34, 38, 40, 43, 49, 60, 61, 67, 72, 76, 81, 83, 92, 94]

示例 3:
输入:n = 3131
输出:136006598
解释:注意取模

提示:
1 <= n <= 5000

动态规划

class Solution {
public:
    int knightDialer(int n) {
        int MOD = 1e9 + 7;
        vector<vector<int>> moves = {
            {4, 6},
            {6, 8},
            {7, 9},
            {4, 8},
            {3, 9, 0},
            {},
            {1, 7, 0},
            {2, 6},
            {1, 3},
            {2, 4}
        };

        vector<vector<int>> d(2, vector<int>(10, 0));
        fill(d[1].begin(), d[1].end(), 1);
        for(int i = 2; i <= n; i++){
            int x = i & 1;
            for(int j = 0; j <= 9; j++){
                d[x][j] = 0;
                for(int k : moves[j]){
                    d[x][j] = (d[x][j] + d[x ^ 1][k]) % MOD;
                }
            }
        }

        int res = 0;
        for(int j = 0; j <= 9; j++){
            res = (res + d[n % 2][j]) % MOD;
        }
        return res;
    }
};

时间复杂度:O(n)。此处我们忽略了状态转移时的复杂度,因为这部分的系数是一个常数,大约为 10,但在做题过程中,你需要考虑这部分影响来评估是否可通过所有测试。

空间复杂度:O(1)。在使用滚动数组实现时,空间复杂度为 O(1),否则为 O(n)。

这道题运用了两个重要的技巧:
一个是当我们发现了棋子的终点位置,我们可以知道他在一次移动中能够到达的起始位置是哪里,我们定义一个数组moves来储存这些信息,moves的索引则是棋子的终点的位置。如moves[0]就是终点在0,可能起始的点有{4,6}两个。

还有一个技巧是滚动数组,由于考虑到每次移动都是由上一次移动转移而来,所以我们只需要储存当前状态和上一个状态就行。我们可以使用滚动数组的方法 ,使用i & 1,当i为奇数的时候,记作1,当i为偶数的时候,记作0。

d[i][j] 的值:表示经过 i 次跳跃后,骑士最终停留在数字键 j 的路径数量。而n可以通过上述方式压缩成2的大小。

最后我们在i的循环中,对j进行循环,遍历每个起始点的情况,然后进行状态转移,在转移中,上一个状态是d[x ^ 1][k],也就是在上一次移动并且最终停留在第k键的号码数。

最后我们用一个变量res来计算n次跳跃后的号码总数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值