首页 > 其他分享 >【LeetCode 552】学生出勤记录II

【LeetCode 552】学生出勤记录II

时间:2024-03-23 19:11:54浏览次数:42  
标签:出勤 记录 int 552 II 缺勤 pmatrix LeetCode dp

题目描述

原题链接: LeetCode.552 学生出勤记录II

解题思路

  • 根据题意, 缺勤天数最多只有一天, 迟到最多只能连续两天, 可以按末尾两个字符状态作为DP数组含义的不同维度往后递推长度增长时的数量值。 dp[i][j]中的i表示长度为i的出勤记录, j表示末尾字符状态:

    j的值 含义
    0 无缺勤且最后不以'L'结尾
    1 无缺勤且最后一位是'L', 倒数第二位不是'L'
    2 无缺勤且最后两位是'L'
    3 有一天缺勤且最后不以'L'结尾
    4 有一天缺勤且最后一位是'L', 倒数第二位不是'L‘
    5 有一天缺勤且最后两位是'L'
  • 长度为1的对应初始值为{1, 1, 0, 1, 0, 0};

  • 可以由长度i递推出长度i+1各状态出勤记录的数量:

    j的值 i相关递推公式 递推理由
    0 \(dp_i0+dp_i1+dp_i2\) 前i位没有缺勤记录的三种情况再追加1个'P'都能得到\(dp_{i+1}0\)对应状态
    1 \(dp_i0\) 第i+1位确定是'L'且第i位不能是'L', 同时无缺勤记录, 只能是由\(dp_i0\)追加1个'L'得到
    2 \(dp_i1\) 第i+1位确定是'L'且第i位也是'L', 同时无缺勤记录, 只能是由\(dp_i1\)追加1个'L'得到
    3 \(dp_i0+dp_i1+dp_i2+dp_i3+dp_i4+dp_i5\) 前i位没有缺勤记录的三种情况再追加1个'A', 或者前i位有1天缺勤再追加1个'P'
    4 \(dp_i3\) 前i位有1天缺勤记录但是第i位不能是'L', 只有这种情况追加1个'L'能得到\(dp_{i+1}4\)的状态
    5 \(dp_i4\) 前i位有1天缺勤记录且第i位是'L', 只有这种情况追加1个'L'能得到\(dp_{i+1}5\)的状态
  • 确定递推公式后可以进一步确定递推矩阵, 借助矩阵快速幂技巧求得时间复杂度最优解:

    • 设长度为i的出勤记录6种状态的数量值矩阵为\(A_i=\begin{pmatrix}dp_i0&dp_i1&dp_i2&dp_i3&dp_i4&dp_i5\end{pmatrix}\), 存在矩阵B使得 \(A_i * B = A_{i+1}\)成立;

    • 长度为i的出勤记录6种状态的数量值就可以通过\(A_1*B^{i-1}\)计算得到, 也就是通过求递推矩阵B的幂来得到;

    • 由递推公式中各项前置变量的常数是1或0来快速确认递推矩阵中的每列, 具体见下表:

      递推公式 递推矩阵的列
      \(dp_{i+1}0=dp_i0+dp_i1+dp_i2\) \(\begin{pmatrix}1\\1\\1\\0\\0\\0\end{pmatrix}\)
      \(dp_{i+1}1=dp_i0\) \(\begin{pmatrix}1\\0\\0\\0\\0\\0\end{pmatrix}\)
      \(dp_{i+1}2=dp_i1\) \(\begin{pmatrix}0\\1\\0\\0\\0\\0\end{pmatrix}\)
      \(dp_{i+1}3=dp_i0+dp_i1+dp_i2+dp_i3+dp_i4+dp_i5\) \(\begin{pmatrix}1\\1\\1\\1\\1\\1\end{pmatrix}\)
      \(dp_{i+1}4=dp_i3\) \(\begin{pmatrix}0\\0\\0\\1\\0\\0\end{pmatrix}\)
      \(dp_{i+1}5=dp_i4\) \(\begin{pmatrix}0\\0\\0\\0\\1\\0\end{pmatrix}\)
  • 本题难点就在于明确DP数组含义以及递推公式的确定, 后续无论是用正常动态规划或者矩阵快速幂都能快速写出对应代码;

  • 普通动态规划的时间复杂度是\(O(n)\), 矩阵快速幂能优化到\(6^3 * log_2n\)。

解题代码

  • 6维动态规划版本

      final int MOD = 1_000_000_007;
     
      /**
       * 第一版代码基础上优化用一维数组滚动DP
       * 执行用时: 24 ms , 在所有 Java 提交中击败了 79.83% 的用户
       * 内存消耗: 43.20 MB , 在所有 Java 提交中击败了 75.63% 的用户
       */
      public int checkRecord(int n) {
          /*
          * 按照题意, 出勤记录中'A'的数量最多就1个, 迟到的'L'不会连续出现超过3个
          * dp[i][0]: 长度为i的出勤记录中没有缺勤且结尾不为'L', dp[i-1][0] + dp[i-1][1] + dp[i-1][2]
          * dp[i][1]: 长度为i的出勤记录中没有缺勤且结尾为1个'L', dp[i-1][0]
          * dp[i][2]: 长度为i的出勤记录中没有缺勤且结尾为2个'L', dp[i-1][1]
          * dp[i][3]: 长度为i的出勤记录中有1个缺勤且结尾不为'L', dp[i-1][0~5]
          * dp[i][4]: 长度为i的出勤记录中有1个缺勤且结尾为1个'L', dp[i-1][3]
          * dp[i][5]: 长度为i的出勤记录中有1个缺勤且结尾为2个'L', dp[i-1][4]
          */
          int[] dp = new int[6];
          dp[0] = 1;
          dp[1] = 1;
          dp[2] = 0;
          dp[3] = 1;
          dp[4] = 0;
          dp[5] = 0;
          for (int i = 2; i <= n; i++) {
              int[] next = new int[6];
              next[0] = (int) (((long) dp[0] + dp[1] + dp[2]) % MOD);
              next[1] = dp[0];
              next[2] = dp[1];
              next[3] = (int) (((long) next[0] + dp[3] + dp[4] + dp[5]) % MOD);
              next[4] = dp[3];
              next[5] = dp[4];
              dp = next;
          }
          int res = 0;
          for (int i = 0; i < 6; i++) {
              res = (res + dp[i]) % MOD;
          }
          return res;
      }
    
  • 矩阵快速幂版本

      final int MOD = 1_000_000_007;
    
      /**
       * 矩阵快速幂解法
       * 执行用时: 2 ms , 在所有 Java 提交中击败了 100.00% 的用户
       * 内存消耗: 39.86 MB , 在所有 Java 提交中击败了 79.51% 的用户
       */
      public int checkRecord(int n) {
          int[][] start = {{1, 1, 0, 1, 0, 0}};
          int[][] transfer = {
                  {1, 1, 0, 1, 0, 0},
                  {1, 0, 1, 1, 0, 0},
                  {1, 0, 0, 1, 0, 0},
                  {0, 0, 0, 1, 1, 0},
                  {0, 0, 0, 1, 0, 1},
                  {0, 0, 0, 1, 0, 0}};
          int[][] res = multiply(start, power(transfer, n - 1, MOD), MOD);
          int ans = 0;
          for (int a : res[0]) {
              ans = (ans + a) % MOD;
          }
          return ans;
      }
    
      /**
       * 矩阵求幂并对mod取余
       */
      public int[][] power(int[][] base, int n, int mod) {
          int row = base.length;
          int[][] res = new int[row][row];
          for (int i = 0; i < row; i++) {
              res[i][i] = 1;
          }
          while (n > 0) {
              if ((n & 1) == 1) {
                  res = multiply(res, base, mod);
              }
              base = multiply(base, base, mod);
              n >>= 1;
          }
          return res;
      }
    
      /**
       * 矩阵相乘并对mod取余
       */
      public int[][] multiply(int[][] a, int[][] b, int mod) {
          int m = a.length, n = b[0].length;
          int[][] res = new int[m][n];
          int k = b.length;
          for (int aRow = 0; aRow < m; aRow++) {
              for (int bCol = 0; bCol < n; bCol++) {
                  long sum = 0;
                  for (int i = 0; i < k; i++) {
                      sum = (sum + (long) a[aRow][i] * b[i][bCol]) % mod;
                  }
                  res[aRow][bCol] = (int) sum;
              }
          }
          return res;
      }
    

参考链接:
左程云大佬的B站算法讲解视频01:53:30开始

标签:出勤,记录,int,552,II,缺勤,pmatrix,LeetCode,dp
From: https://www.cnblogs.com/coding-memory/p/18091556

相关文章

  • ☆【前后缀】【双指针】Leetcode 42. 接雨水
    【前后缀】【双指针】Leetcode42.接雨水解法1前后缀分解解法2双指针---------------......
  • Leetcode 有效的括号
    Day8第1题力扣官方解题思路:利用栈的特性和哈希表快速配对及时把括号数组pop出去,关键在于左右括号需要连续封闭:stack.peek()!=pairs.get(ch)。classSolution{publicbooleanisValid(Strings){intn=s.length();if(n%2==1){......
  • 【STC8H】全网最详细的IIC协议
    七、IIC协议(一)原理1.IIC总线 IIC(Inter-IntegratedCircuit)是IICBus简称,中文叫集成电路总线。它是一种串行通信总线,使用多主从(多个主机可以连接多个从机)架构。 IIC使用两根双向信号线进行通信:一根时钟线SCL,用于通信双方时钟的同步;一根数据线SDA,用于收发数据。IIC总......
  • 【LeetCode 1220】统计元音字母序列的数目
    题目描述原题链接:LeetCode.1220统计元音字母序列的数目解题思路定义DP数组dp[i][j]含义为长度为i+1且以j字符结尾的字符串有多少个,j从0到4依次代表('a','e','i','o','u')这5个元音字符,dp[0][0~4]长度为1时的初始个数都为1;dp[i][j]对应字符串末尾字符已经由j确定,对应......
  • 代码随想录算法训练营day31 | leetcode 455. 分发饼干、376. 摆动序列、53. 最大子数
    目录贪心理论基础核心:题目链接:455.分发饼干-简单题目链接:376.摆动序列-中等题目链接:53.最大子数组和-中等贪心理论基础核心:由局部推全局最优题目链接:455.分发饼干-简单题目描述:假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每......
  • 【LeetCode 509 】斐波那契数
    题目描述原题链接:LeetCode.0509斐波那契数解题思路题目直接给出了公式,朴素解法可以直接用\(O(n)\)复杂度求出答案,可以看做是递归或动态规划的入门题;这里重点作为模板题来介绍矩阵快速幂技巧,讲一下\(O(log_2n)\)复杂度的解法:递推公式\(F(n)=F(n-1)+F(n-2)\),转换为矩......
  • 代码随想录算法训练营第十八天| 513. 找树左下角的值 112. 路径总和 113. 路径总和
    513.找树左下角的值https://leetcode.cn/problems/find-bottom-left-tree-value/description/publicintfindBottomLeftValue(TreeNoderoot){intval=0;Deque<TreeNode>deque=newArrayDeque<>();deque.offer(root);whi......
  • (Java)猛刷LeetCode——数组知识点篇
    数组Array在连续的内存空间中,存储一组相同类型的元素元素:值索引:数组的下标数组访问(Access)和数组搜索(Search)●数组访问:索引●数组搜索:找2这个元素数组中有没有以下是数组的常规操作:数组创建、添加元素、访问元素、修改元素、删除元素、遍历数组、查找元素、数组......
  • LeetCode题练习与总结:接雨水
    一、题目给定 n个非负整数表示每个宽度为1的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。示例1:输入:height=[0,1,0,2,1,0,1,3,2,1,2,1]输出:6解释:上面是由数组[0,1,0,2,1,0,1,3,2,1,2,1]表示的高度图,在这种情况下,可以接6个单位的雨水(蓝色部分表示雨......
  • LeetCode题练习与总结:缺失的第一个正数
    一、题目给你一个未排序的整数数组nums,请你找出其中没有出现的最小的正整数。请你实现时间复杂度为O(n)并且只使用常数级别额外空间的解决方案。二、解题思路遍历数组:首先,我们需要遍历数组,找到所有负数和零,并将它们替换为一个特定的值(比如数组的最大值加一),这样我们就......