leetcode每日一题系列-学生出勤记录II-「dfs + 记忆化」-「动态规划」-「状态压缩」-「矩阵快速幂」

712 阅读1分钟

这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战

leetcode-552-学生出勤记录II

[博客链接]

菜🐔的学习之路

掘金首页

[题目描述]

给你一个字符串 s 表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤、迟到、到场)。记录中只含下面三种字符:

  • 'A':Absent,缺勤
  • 'L':Late,迟到
  • 'P':Present,到场

如果学生能够 同时 满足下面两个条件,则可以获得出勤奖励:

按 总出勤 计,学生缺勤('A')严格 少于两天。 学生 不会 存在 连续 3 天或 3 天以上的迟到('L')记录。

如果学生可以获得出勤奖励,返回获得奖励的方案数量 。

示例 1:

输入:n = 2
输出:8
解释:
有 8 种长度为 2 的记录将被视为可奖励:
"PP" , "AP", "PA", "LP", "PL", "AL", "LA", "LL"
只有"AA"不会被视为可奖励,因为缺勤次数为 2 次(需要少于 2 次)。

示例 2:

输入:n = 1
输出:3

示例 3:

输入:n = 10101
输出:183236316

提示:

  • 1 <= n <= 10000

[题目链接]

leetcode题目链接

[github地址]

代码链接

[思路介绍]

思路一:动态规划

  • 常规dp的动态规划分析已经在上一篇文章给了出来,大家可以去看一下,这里就不赘述了
  • 时间复杂度O(n)
  • 空间复杂度O(n)

思路二:dfs+记忆化

  • 这道题最开始想到的肯定是dfs,每一次选择三选一
  • 然后判断符合条件的话加入结果集
  • 去重返回方案数量
  • 但是这样面临的也必然是时间复杂度爆炸
  • 时间复杂度O(3n)(3^n)
int res = 0, len = 0;
        Set<String> set = new HashSet<>();

        public int checkRecord(int n) {
            len = n;
            dfs(0, "", "A");
            dfs(0, "", "P");
            dfs(0, "", "L");
            return res;
        }

        public void dfs(int idx, String now, String add) {
            if (idx == len && check(now) ) {
                if (!set.contains(now)) {
                    res += 1;
                    set.add(now);
                }
                return;
            }
            now = now + add;
            if (!check(now)) {
                return;
            }
            dfs(idx + 1, now, "A");
            dfs(idx + 1, now, "P");
            dfs(idx + 1, now, "L");
        }

        public boolean check(String s) {
            return !(s.indexOf("A") != s.lastIndexOf("A") || s.contains("LLL"));
        }
  • 时间复杂度O(3n3^n)
  • 空间复杂度O(n)

思路三:dfs+记忆化

  • 根据思路二可以发现某些状态可以不进行重复计算
  • 对上述dfs做记忆化改造
  • 其实刷题刷到这里,大家可以发现记忆化dfs动态规划就非常相似了
int MOD = (int) (1e9 + 7);
int len = 0;
int[][][] cache;

public int checkRecord(int n) {
    cache = new int[n][2][3];
    len = n;
    return dfs(0, 0, 0);
}

public int dfs(int idx, int cntA, int cntL) {
    if (idx >= len) {
        return 1;
    }

    // 相同的状态计算过了直接返回
    if (cache[idx][cntA][cntL] != 0) {
        return cache[idx][cntA][cntL];
    }

    // 回溯开始
    int ans = 0;
    ans = (ans + dfs(idx + 1, cntA, 0)) % MOD;
    if (cntA < 1) {
        ans = (ans + dfs(idx + 1, 1, 0)) % MOD;
    }
    if (cntL < 2) {
        ans = (ans + dfs(idx + 1, cntA, cntL + 1)) % MOD;
    }
    cache[idx][cntA][cntL] = ans;
    return ans;
}
  • 时间复杂度O(n)
  • 空间复杂度O(n)

思路四:状态压缩

  • 前一天的文章已经写了动态规划了
  • 这次也可以通过状态压缩的方式对dp方程进行降维处理
  • 因为发现dp[i]只和dp[i-1]有关
  • 当然也可以通过滚动数组的方案去写
  • 我这里就不再写了
public int checkRecord(int n) {
    long[][] dp = new long[2][3];
    dp[0][0] = 1;
    dp[1][0] = 1;
    dp[0][1] = 1;
    int mod = (int) 1e9 + 7;
    for (int i = 1; i < n; i++) {
        long[][] temp = new long[2][3];
        temp[0][0] = (dp[0][0] + dp[0][1] + dp[0][2]) % mod;
        temp[1][0] = (dp[1][0] + dp[1][1] + dp[1][2]) % mod;
        temp[0][1] = dp[0][0];
        temp[0][2] = dp[0][1];
        temp[1][1] = dp[1][0];
        temp[1][2] = dp[1][1];
        temp[1][0] += (dp[0][0] + dp[0][1] + dp[0][2]) % mod;
        dp = temp;
    }
    return (int) ((dp[0][0] + dp[0][1] + dp[0][2] + dp[1][0] + dp[1][1] + dp[1][2]) % mod);
}
  • 时间复杂度O(n)
  • 空间复杂度O(1)

思路五:矩阵快速幂

  • 不得不说数学是真的有意思
  • 三叶大佬yyds
  • 矩阵快速幂其实就将上述运算转换成矩阵运算
  • idx = 0 cntA = 0 cntL = 0
  • idx = 1 cntA = 0 cntL = 2
  • idx = 2 cntA = 0 cntL = 1
  • idx = 3 cntA = 1 cntL = 0
  • idx = 4 cntA = 1 cntL = 1
  • idx = 5 cntA = 1 cntL = 2
  • 这种也是一种常见的降维思路,将二维坐标转换成一维坐标
  • idx = cntA * 3 + cntL
int N = 6;
 int mod = (int)1e9+7;
 //矩阵乘法
 long[][] mul(long[][] a, long[][] b) {
     int r = a.length, c = b[0].length, z = b.length;
     long[][] ans = new long[r][c];
     for (int i = 0; i < r; i++) {
         for (int j = 0; j < c; j++) {
             for (int k = 0; k < z; k++) {
                 ans[i][j] += a[i][k] * b[k][j];
                 ans[i][j] %= mod;
             }
         }
     }
     return ans;
 }
 public int checkRecord(int n) {
     long[][] ans = new long[][]{
             {1}, {0}, {0}, {0}, {0}, {0}
     };
     long[][] mat = new long[][]{
             {1, 1, 1, 0, 0, 0},
             {1, 0, 0, 0, 0, 0},
             {0, 1, 0, 0, 0, 0},
             {1, 1, 1, 1, 1, 1},
             {0, 0, 0, 1, 0, 0},
             {0, 0, 0, 0, 1, 0}
     };
     while (n != 0) {
         if ((n & 1) != 0) ans = mul(mat, ans);
         mat = mul(mat, mat);
         n >>= 1;
     }
     int res = 0;
     for (int i = 0; i < N; i++) {
         res += ans[i][0];
         res %= mod;
     }
     return res;
 }
  • 时间复杂度O(logn)
  • 空间复杂度O(1)