思路:

题目的主要信息:

  • 跳上n级台阶,每次只能跳奇数级
  • a数组中存放着不能跳上的台阶序号
  • 达不到n级就返回0
  • 求跳上n级的方案数,结果需要取模1e9+7

方法一:动态规划
具体做法:
我们可以用动态规划,辅助数组dp,其中表示i级台阶方法数,有如下情况:

  • 如果i级台阶有积水,
  • 如果i为奇数,那它肯定由偶数级台阶跳来,于是,对于每个偶数级台阶跳一个相差的奇数即可
  • 如果i为偶数,那它肯定由奇数级台阶挑来,于是,理由同上

按照上述说法,我们可以计算前缀奇数和与前缀偶数和来辅助dp数组。
图片说明

class Solution {
public:
    int solve(int n, int m, vector<int>& a) {
        int mod = 1e9 + 7;
        vector<int> dp(n + 1, 0); //dp[i]表示i级台阶方法数
        dp[0] = 1;
        int odd = 0; //奇数台阶方法种类前缀和
        int even = 1; //偶数台阶方法种类前缀和
        int index = 0; //记录数组a的下标
        for(int i = 1; i <= n; i++){
            if(i == a[index]){ //积水台阶不算
                index++;
                continue;
            }
            if(i % 2 == 1){ //奇数
                dp[i] = (dp[i] + even) % mod;
                odd = (odd + dp[i]) % mod;
            }
            else{ //偶数
                dp[i] = (dp[i] + odd) % mod;
                even = (even + dp[i]) % mod;         
            }
        }
        return dp[n];
    }
};

复杂度分析:

  • 时间复杂度:,遍历一次即可
  • 空间复杂度:,辅助数组dp的chang

方法二:递归
具体做法:
按照上述动态规划的思路,依靠前缀奇数和前缀偶数,对于某一个问题,我们可以自顶向下递归地求它的前缀奇数和或者偶数和,以2为遍历基数,将其累加。
图片说明

class Solution {
public:
    int mod = 1e9 + 7;
    int recursion(int n, vector<bool>& water){
        if(water[n]) //有积水
            return 0;
        if(n <= 1)  //不大于1级的方案都是1
            return 1;
        int res = 0;
        for(int i = n - 1; i >= 0; i = i - 2) //求其奇数前缀和或者偶数前缀和
            res = (res + recursion(i, water)) % mod;
        return res;
    }
    int solve(int n, int m, vector<int>& a) {
        vector<bool> water(n + 1, false);
        for(int i = 0; i < m; i++) //标记有水的台阶
            water[a[i]] = true;
        return recursion(n, water);
    }
};

复杂度分析:

  • 时间复杂度:,递归为,每次累加前缀和也是
  • 空间复杂度:,递归栈深度及辅助数组water的长度