Lazy loaded image
技术分享
Leetcode刷题——12月
00 分钟
2024-11-30
2024-12-30
type
status
date
slug
summary
tags
category
icon
password
comment
😀
12月,冲冲冲!!!

每日一题

2024.12.01 T51 . N 皇后

2024.12.02 T52 . N 皇后 II

两个题思路一样,一起写了就
  1. 问题分析
    1. 本题是经典的 n 皇后问题,目标是在一个 n × n 的棋盘上放置 n 个皇后,使得它们互不攻击。
      • 皇后可以攻击同一行、同一列或同一斜线上的其他棋子。
      • 对于给定的整数 n,要求返回所有可能的放置方式,其中每个解法表示为一个 n × n 的棋盘。
      • 在棋盘中,'Q' 代表皇后,'.' 代表空位。
  1. 解题思路
    1. 这道题可以通过回溯算法来求解,具体步骤如下:
    2. 回溯法
        • 我们将从第 0 行开始,尝试将皇后放在每一列上,递归地进行尝试。每当到达一行时,检查当前列和两个斜线是否能放置皇后,若可以则放置皇后并继续递归。
    3. 合法性检查
        • 对每一行的每一列,都需要检查皇后是否能安全放置。检查的条件是:
          • 该列上是否已有皇后。
          • 主对角线(左上到右下)是否已有皇后。
          • 副对角线(右上到左下)是否已有皇后。
    4. 回溯过程
        • 如果某一行的某个位置可以放置皇后,则递归进入下一行。如果能成功放置到最后一行,说明找到了一个解,将该解保存下来。
        • 若某一列不能放置皇后,则撤回上一步,继续尝试其他列。
    5. 返回所有解
        • 最终返回所有找到的解。
  1. 代码实现
  1. 代码解释
    1. 回溯函数
        • backtrack 函数用于递归搜索每一行的所有可能放置皇后的位置,并检查是否合法。如果某个位置合法,则放置皇后并递归尝试下一行,最后将有效解保存到 result 中。
    2. 合法性检查函数
        • isValid 函数用于检查在当前位置 (row, col) 放置皇后是否安全。检查条件包括列、主对角线和副对角线是否已经有皇后。
    3. 主函数
        • solveNQueens 函数是主要入口,初始化棋盘并调用回溯算法来搜索所有解。
    4. 结果存储
        • 每当找到一个解时,将棋盘状态(字符串形式)添加到 result 中。
  1. 复杂度分析
      • 时间复杂度:最坏情况下,算法需要尝试每个位置,因此时间复杂度为 ,因为每一行有 n 种选择,而递归深度为 n
      • 空间复杂度:空间复杂度主要来自于递归栈和保存结果的 result,因此空间复杂度为
 

2024.12.03 T3274. 检查棋盘方格颜色是否相同

notion image
  • 这题太简单了懒得写解释了,直接上代码
 

2024.12.04 T2056. 棋盘上有效移动组合的数目

2056. 棋盘上有效移动组合的数目 - 力扣(LeetCode)
2056. 棋盘上有效移动组合的数目 - 有一个 8 x 8 的棋盘,它包含 n 个棋子(棋子包括车,后和象三种)。给你一个长度为 n 的字符串数组 pieces ,其中 pieces[i] 表示第 i 个棋子的类型(车,后或象)。除此以外,还给你一个长度为 n 的二维整数数组 positions ,其中 positions[i] = [ri, ci] 表示第 i 个棋子现在在棋盘上的位置为 (ri, ci) ,棋盘下标从 1 开始。 棋盘上每个棋子都可以移动 至多一次 。每个棋子的移动中,首先选择移动的 方向 ,然后选择 移动的步数 ,同时你要确保移动过程中棋子不能移到棋盘以外的地方。棋子需按照以下规则移动: * 车可以 水平或者竖直 从 (r, c) 沿着方向 (r+1, c),(r-1, c),(r, c+1) 或者 (r, c-1) 移动。 * 后可以 水平竖直或者斜对角 从 (r, c) 沿着方向 (r+1, c),(r-1, c),(r, c+1),(r, c-1),(r+1, c+1),(r+1, c-1),(r-1, c+1),(r-1, c-1) 移动。 * 象可以 斜对角 从 (r, c) 沿着方向 (r+1, c+1),(r+1, c-1),(r-1, c+1),(r-1, c-1) 移动。 移动组合 包含所有棋子的 移动 。每一秒,每个棋子都沿着它们选择的方向往前移动 一步 ,直到它们到达目标位置。所有棋子从时刻 0 开始移动。如果在某个时刻,两个或者更多棋子占据了同一个格子,那么这个移动组合 不有效 。 请你返回 有效 移动组合的数目。 注意: * 初始时,不会有两个棋子 在 同一个位置 。 * 有可能在一个移动组合中,有棋子不移动。 * 如果两个棋子 直接相邻 且两个棋子下一秒要互相占据对方的位置,可以将它们在同一秒内 交换位置 。   示例 1: [https://assets.leetcode.com/uploads/2021/09/23/a1.png] 输入:pieces = ["rook"], positions = [[1,1]] 输出:15 解释:上图展示了棋子所有可能的移动。 示例 2: [https://assets.leetcode.com/uploads/2021/09/23/a2.png] 输入:pieces = ["queen"], positions = [[1,1]] 输出:22 解释:上图展示了棋子所有可能的移动。 示例 3: [https://assets.leetcode.com/uploads/2021/09/23/a3.png] 输入:pieces = ["bishop"], positions = [[4,3]] 输出:12 解释:上图展示了棋子所有可能的移动。 示例 4: [https://assets.leetcode.com/uploads/2021/09/23/a4.png] 输入:pieces = ["rook","rook"], positions = [[1,1],[8,8]] 输出:223 解释:每个车有 15 种移动,所以总共有 15 * 15 = 225 种移动组合。 但是,有两个是不有效的移动组合: - 将两个车都移动到 (8, 1) ,会导致它们在同一个格子相遇。 - 将两个车都移动到 (1, 8) ,会导致它们在同一个格子相遇。 所以,总共有 225 - 2 = 223 种有效移动组合。 注意,有两种有效的移动组合,分别是一个车在 (1, 8) ,另一个车在 (8, 1) 。 即使棋盘状态是相同的,这两个移动组合被视为不同的,因为每个棋子移动操作是不相同的。 示例 5: [https://assets.leetcode.com/uploads/2021/09/23/a5.png] 输入:pieces = ["queen","bishop"], positions = [[5,7],[3,4]] 输出:281 解释:总共有 12 * 24 = 288 种移动组合。 但是,有一些不有效的移动组合: - 如果后停在 (6, 7) ,它会阻挡象到达 (6, 7) 或者 (7, 8) 。 - 如果后停在 (5, 6) ,它会阻挡象到达 (5, 6) ,(6, 7) 或者 (7, 8) 。 - 如果象停在 (5, 2) ,它会阻挡后到达 (5, 2) 或者 (5, 1) 。 在 288 个移动组合当中,281 个是有效的。   提示: * n == pieces.length * n == positions.length * 1 <= n <= 4 * pieces 只包含字符串 "rook" ,"queen" 和 "bishop" 。 * 棋盘上最多只有一个后。 * 1 <= ri, ci <= 8 * 每一个 positions[i] 互不相同。
2056. 棋盘上有效移动组合的数目 - 力扣(LeetCode)
天天下棋,昨天下的还挺简单,今天就拉了坨大的,我服了
  1. 问题分析
    1. 给定一个 8x8 的棋盘,包含 n 个棋子(车、后、象)。每个棋子都可以至多移动一次。棋子的移动遵循不同的规则,具体为:
      • :可以水平或竖直移动。
      • :可以水平、竖直或斜对角移动。
      • :只能斜对角移动。
      给定每个棋子的位置和类型,要求计算出所有有效的棋子移动组合数。一个有效的组合是指每个棋子按照其规则进行合法移动,且在某个时刻不会有两个棋子占据同一位置。
  1. 解题思路
    1. 预处理所有合法移动
        • 对于每个棋子,列出其可能的所有合法移动路径。车的路径包括上下左右,象的路径包括四个斜对角,后则包含所有方向。
        • 每个棋子会有多个可能的移动,使用 DFS 遍历每个棋子的所有可能移动。
    2. 深度优先搜索 (DFS)
        • 从第一个棋子开始,尝试每个可能的移动,并递归地考虑后续棋子的移动组合。
        • 对于每个棋子,检查它的移动是否与之前棋子的移动冲突(即是否会碰撞)。如果没有冲突,则继续递归。
    3. 判断合法性
        • 在每一秒,检查所有棋子的状态,确保没有两个棋子重叠。
        • 如果有两个棋子会在某一时刻重叠,返回无效组合。
    4. 交换位置
        • 如果两个棋子直接相邻且会在同一时刻互相占据对方的位置,可以将它们交换位置。
  1. 代码实现
  1. 代码解释
    1. generate_moves 函数
        • 为每个棋子计算其所有可能的合法移动,包含原地不动的情况以及其他可能的步数和方向。
    2. is_valid 函数
        • 检查两个棋子是否会在某个时刻占据相同的位置,判断它们的移动是否会冲突。
    3. 深度优先搜索 (DFS)
        • 通过 DFS 枚举每个棋子的移动,确保没有棋子之间的冲突,并且计算所有合法的组合数。
    4. 主函数
        • 预处理每个棋子的合法移动,使用 DFS 遍历所有可能的合法组合,并返回有效的移动组合数。
  1. 复杂度分析
  • 时间复杂度:,其中:
    • npieces 数组的长度(即棋子的数量)。
    • L = 8 表示棋盘的大小。
    • 表示单个棋子的合法移动个数,最多为 8 种移动方向。
    • 由于 DFS 搜索树的深度为 n,每个节点可能有 M 个分支,树的总节点数为 。每个节点需要时间 O(n * L) 来判断第 i 个棋子的合法移动是否有效。故总时间复杂度为
  • 空间复杂度:,用于存储每个棋子的所有合法移动。每个棋子的合法移动数最多为 M,所以空间复杂度为
 

2024.12.05 T3001. 捕获黑皇后需要的最少移动次数

3001. 捕获黑皇后需要的最少移动次数 - 力扣(LeetCode)
3001. 捕获黑皇后需要的最少移动次数 - 现有一个下标从 1 开始的 8 x 8 棋盘,上面有 3 枚棋子。 给你 6 个整数 a 、b 、c 、d 、e 和 f ,其中: * (a, b) 表示白色车的位置。 * (c, d) 表示白色象的位置。 * (e, f) 表示黑皇后的位置。 假定你只能移动白色棋子,返回捕获黑皇后所需的最少移动次数。 请注意: * 车可以向垂直或水平方向移动任意数量的格子,但不能跳过其他棋子。 * 象可以沿对角线方向移动任意数量的格子,但不能跳过其他棋子。 * 如果车或象能移向皇后所在的格子,则认为它们可以捕获皇后。 * 皇后不能移动。   示例 1: [https://assets.leetcode.com/uploads/2023/12/21/ex1.png] 输入:a = 1, b = 1, c = 8, d = 8, e = 2, f = 3 输出:2 解释:将白色车先移动到 (1, 3) ,然后移动到 (2, 3) 来捕获黑皇后,共需移动 2 次。 由于起始时没有任何棋子正在攻击黑皇后,要想捕获黑皇后,移动次数不可能少于 2 次。 示例 2: [https://assets.leetcode.com/uploads/2023/12/21/ex2.png] 输入:a = 5, b = 3, c = 3, d = 4, e = 5, f = 2 输出:1 解释:可以通过以下任一方式移动 1 次捕获黑皇后: - 将白色车移动到 (5, 2) 。 - 将白色象移动到 (5, 2) 。   提示: * 1 <= a, b, c, d, e, f <= 8 * 两枚棋子不会同时出现在同一个格子上。
3001. 捕获黑皇后需要的最少移动次数 - 力扣(LeetCode)
  1. 问题分析
    1. 在给定的 8x8 棋盘上,存在三个棋子:
      • 白色车:可以向垂直或水平方向移动任意数量的格子,前提是没有其他棋子阻挡。
      • 白色象:可以沿对角线方向移动任意数量的格子,前提是没有其他棋子阻挡。
      • 黑色皇后:处于固定位置,不能移动。
      任务是找到最少的移动次数,使得白色的车或象捕获到黑色皇后。如果棋子在直线或对角线方向上能到达皇后所在的位置,即可视为捕获。
  1. 解题思路
    1. 移动规则
        • 车的移动范围是整个水平方向和竖直方向。其捕获皇后的条件是车所在行或列与皇后一致,同时中间没有其他棋子阻挡。
        • 象的移动范围是棋盘的对角线方向,其捕获皇后的条件是象和皇后在同一对角线上,且中间没有其他棋子阻挡。
    2. 捕获条件判断
        • 判断白色车是否能到达皇后位置,首先要满足车和皇后在同一行或列,然后需要检查中间是否存在其他棋子(即白色象)阻挡。
        • 判断白色象是否能到达皇后位置,需要它们在同一对角线上,并且中间没有其他棋子(白色车)阻挡。
    3. 结果判定
        • 若任一棋子可以在一次移动中捕获皇后,返回 1
        • 否则返回 2,表示需要两步才能捕获皇后。
  1. 代码实现
    1. 代码解释
        • 判断车能否捕获皇后
          • 如果车在与皇后相同的行,且白色象不在它们之间(即没有阻挡),则车可以一步捕获皇后。
          • 如果车在与皇后相同的列,同样判断白色象是否阻挡。
        • 判断象能否捕获皇后
          • 如果象和皇后在同一对角线上,且白色车不在它们之间(没有阻挡),象可以一步捕获皇后。
        • 返回结果
          • 如果车或象可以在一步内捕获皇后,返回 1;否则返回 2
    1. 复杂度分析
        • 时间复杂度。代码中仅进行了常量次的判断和比较操作,因此时间复杂度为
        • 空间复杂度。只使用了常量级别的额外空间。
     

    2024.12.06 T999 . 可以被一步捕获的棋子数

    999. 可以被一步捕获的棋子数 - 力扣(LeetCode)
    999. 可以被一步捕获的棋子数 - 给定一个 8 x 8 的棋盘,只有一个 白色的车,用字符 'R' 表示。棋盘上还可能存在白色的象 'B' 以及黑色的卒 'p'。空方块用字符 '.' 表示。 车可以按水平或竖直方向(上,下,左,右)移动任意个方格直到它遇到另一个棋子或棋盘的边界。如果它能够在一次移动中移动到棋子的方格,则能够 吃掉 棋子。 注意:车不能穿过其它棋子,比如象和卒。这意味着如果有其它棋子挡住了路径,车就不能够吃掉棋子。 返回白车将能 吃掉 的 卒的数量。   示例 1: [https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/02/23/1253_example_1_improved.PNG] 输入:[[".",".",".",".",".",".",".","."],[".",".",".","p",".",".",".","."],[".",".",".","R",".",".",".","p"],[".",".",".",".",".",".",".","."],[".",".",".",".",".",".",".","."],[".",".",".","p",".",".",".","."],[".",".",".",".",".",".",".","."],[".",".",".",".",".",".",".","."]] 输出:3 解释: 在本例中,车能够吃掉所有的卒。 示例 2: [https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/02/23/1253_example_2_improved.PNG] 输入:[[".",".",".",".",".",".",".","."],[".","p","p","p","p","p",".","."],[".","p","p","B","p","p",".","."],[".","p","B","R","B","p",".","."],[".","p","p","B","p","p",".","."],[".","p","p","p","p","p",".","."],[".",".",".",".",".",".",".","."],[".",".",".",".",".",".",".","."]] 输出:0 解释: 象阻止了车吃掉任何卒。 示例 3: [https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/02/23/1253_example_3_improved.PNG] 输入:[[".",".",".",".",".",".",".","."],[".",".",".","p",".",".",".","."],[".",".",".","p",".",".",".","."],["p","p",".","R",".","p","B","."],[".",".",".",".",".",".",".","."],[".",".",".","B",".",".",".","."],[".",".",".","p",".",".",".","."],[".",".",".",".",".",".",".","."]] 输出:3 解释: 车可以吃掉位置 b5,d6 和 f5 的卒。   提示: 1. board.length == 8 2. board[i].length == 8 3. board[i][j] 可以是 'R','.','B' 或 'p' 4. 只有一个格子上存在 board[i][j] == 'R'
    999. 可以被一步捕获的棋子数 - 力扣(LeetCode)
    1. 问题分析
      1. 给定一个 8 x 8 的棋盘,其中存在:
        • 白色的车 'R',可以在水平方向或竖直方向移动。
        • 白色的象 'B',会阻挡车的路径。
        • 黑色的卒 'p',是车的目标。
        车的移动规则:
      2. 水平或竖直方向移动,直到碰到边界或其他棋子。
      3. 遇到卒时,车可以吃掉卒,且停止移动。
      4. 遇到象时,车无法穿过象,停止移动。
      5. 任务是计算白车 'R' 能够吃掉的卒 'p' 的数量。
    1. 解题思路
      1. 遍历棋盘,找到白车 'R' 的位置 (x, y)
      2. 依次检查白车的四个方向(上、下、左、右):
          • 在每个方向中,移动并检查当前格子:
            • 如果遇到 'B',停止该方向的检查。
            • 如果遇到 'p',计数器加1,并停止该方向的检查。
            • 如果是 '.',继续移动。
      3. 将四个方向吃掉卒的数量累加并返回。
    1. 代码实现
      1. 代码解释
        1. 找到白车的位置:
            • 遍历整个棋盘,记录车的坐标 (x, y)
        2. 逐个方向移动:
            • 每次移动一格,并检查格子的状态:
              • 遇到象 'B',停止该方向检查。
              • 遇到卒 'p',计数加1并停止该方向检查。
              • 遇到空格 '.',继续移动。
        3. 返回计数器:
            • 将四个方向能吃到的卒数量累加返回。
      1. 复杂度分析
        1. 时间复杂度:
            • 找到车的位置:,即遍历整个棋盘。
            • 四个方向的检查:每个方向最多检查 步,总共
            • 总复杂度:
        2. 空间复杂度:
            • 使用了常数级别的额外变量,空间复杂度为
       

      2024.12.07 T688 . 骑士在棋盘上的概率

      1. 问题分析
        1. 在一个 n x n 的国际象棋棋盘上,有一个骑士(马)从位置 (row, column) 开始尝试进行 k 次移动。每次移动时,骑士从 8 种可能的方向中随机选择一种。如果骑士在任意时刻离开了棋盘,则停止移动。
      目标是计算骑士在进行 k 次移动之后,仍然留在棋盘上的概率。
      1. 解题思路
        1. 骑士的移动方式
            • 骑士有 8 种可能的移动,每次走两个单元格,再走一个正交方向。
            • 骑士可能会走出棋盘,因此需要判断每一步的合法性。
        2. 动态规划(DP)
            • 使用动态规划来解决这个问题。
            • 定义 dp[step][row][col] 表示从位置 (row, col) 出发,经过 step 步后,骑士留在棋盘上的概率。
            • 状态转移:从当前位置 (row, col),骑士可以移动到新的位置 (newRow, newCol),新位置的概率由之前的状态累加而来。
        3. 滚动数组优化
            • k 次移动中,实际上我们只需要维护当前步和上一步的状态。
            • 因此,使用一个三维数组 dp[2][n][n] 来存储两轮的状态,来减少空间复杂度。
      1. 代码实现
        1. 代码解释
          1. 初始化
              • 使用一个三维数组 dp[2][boardSize][boardSize] 来保存两轮状态的概率。
              • 初始化状态为 1,表示骑士在初始位置的概率为 1。
          2. 动态规划过程
              • 使用变量 currentState 来指示当前的状态。
              • 对于每一个 step,更新当前所有棋盘位置的概率。
              • 骑士有 8 个可能的方向,从 (row, col) 移动到 (newRow, newCol),每种可能的概率为 1/8
          3. 返回结果
              • 返回 dp[moves & 1][startRow][startColumn],表示经过 k 次移动后,骑士留在初始位置的概率。
        1. 复杂度分析
            • 时间复杂度,其中 k 为移动次数,n 为棋盘的边长。我们需要遍历每一步的每个格子,并且对于每个格子,计算 8 个可能的移动方向。
            • 空间复杂度,我们只保存两轮状态,因此只需要 的空间。
         

        2024.12.08 T782 . 变为棋盘

        1. 问题分析
        给定一个 n x n 的矩阵 board,其中仅包含 01。通过交换任意两列或两行的位置,目标是将矩阵变成 “棋盘”
        “棋盘”的定义是:任意格子的上下左右四个方向的值都与其本身不同。
        1. 解题思路
          1. 判断是否能变为棋盘
              • 任意行(或列)的二进制表示只能有两种互为相反的模式。例如,rowMaskreverseRowMask
              • 如果某一行或列不符合上述规则,则无法将矩阵变为棋盘。
          2. 确定交换次数
              • 统计当前矩阵中行和列的分布,与理想的棋盘状态的匹配程度,计算最小交换次数。
          3. 处理特殊情况
              • 当矩阵的大小 n 为奇数时,棋盘中的 10 的数量会相差 1
              • n 为偶数时,棋盘中的 10 的数量应完全相等。
          4. 使用位运算简化
              • 通过 bitmask 记录行或列的二进制状态。
              • 通过按位异或计算互为反转的行或列状态。
        1. 代码实现
          1. 代码解释
            1. 核心函数 getMoves
                • 使用 mask 表示行或列的二进制状态。
                • 计算当前行或列需要多少次交换才能符合棋盘模式。
                • n 为奇数或偶数时,处理逻辑不同:
                  • 奇数:10 的数量相差 1
                  • 偶数:10 的数量相等。
            2. 主函数 movesToChessboard
                • 遍历矩阵的每一行和每一列,检查其是否符合棋盘模式。
                • 如果任意一行或列无法与棋盘模式匹配,返回 1
                • 如果满足条件,则计算最小的交换次数。
          1. 复杂度分析
            1. 时间复杂度
                • :需要遍历整个矩阵来检查行和列的状态,并计算最小交换次数。
            2. 空间复杂度
                • :只使用了少量额外变量和位运算处理。
           

          2024.12.09 T1812. 判断国际象棋棋盘中一个格子的颜色

          1. 问题分析
            1. 给定一个国际象棋棋盘上的坐标 coordinates(如 "a1""h8"),判断这个格子的颜色是否为白色:
              • 国际象棋棋盘是 8x8 的矩阵,其中:
                • 左上角格子 (a1) 是黑色。
                • 格子的颜色交替变化(黑白相间)。
          1. 解题思路
            1. 坐标的表示
                • 字母部分(ah)表示列,对应的 ASCII 值可通过减去 'a' 转换为数字(07)。
                • 数字部分(18)表示行,可通过减去 '1' 转换为数字(07)。
            2. 颜色判断
                • 行列之和为奇数的格子是白色。
                • 行列之和为偶数的格子是黑色。
            3. 位运算优化
                • (row + col) & 1 用于快速判断颜色:
                  • 如果结果为 1,说明是白色。
                  • 如果结果为 0,说明是黑色。
          1. 代码实现
            1. 代码解释
              1. 转换行列索引
                  • coordinates[0] - 'a':将字母部分转为 0 到 7 的列索引。
                  • coordinates[1] - '1':将数字部分转为 0 到 7 的行索引。
              2. 颜色判断
                  • (coordinates[0] - 'a' + coordinates[1] - '1') & 1
                    • 计算行列之和并取模 2(使用位运算),判断是奇数还是偶数。
              3. 返回值
                  • 如果结果为 1,返回 true(白色)。
                  • 如果结果为 0,返回 false(黑色)。
            1. 复杂度分析
              1. 时间复杂度
                  • 仅涉及常量次的字符操作和简单计算。
              2. 空间复杂度
                  • 仅使用了少量的局部变量,无额外空间开销。
             

            2024.12.10 T935 . 骑士拨号器

            1. 问题分析
              1. 象棋骑士在电话拨号盘上的独特跳跃规则,要求计算长度为 n 的电话号码的数量。骑士可以从任何数字单元格开始,通过有效的骑士跳跃生成一系列号码。
            1. 解题思路
              1. 骑士的移动规则
                  • 骑士可以从一个数字单元格跳到其他数字单元格。以下是电话拨号盘的跳跃规则:
                    • 0: 可以跳到 4, 6
                    • 1: 可以跳到 6, 8
                    • 2: 可以跳到 7, 9
                    • 3: 可以跳到 4, 8
                    • 4: 可以跳到 3, 9, 0
                    • 5: 无法跳跃。
                    • 6: 可以跳到 1, 7, 0
                    • 7: 可以跳到 2, 6
                    • 8: 可以跳到 1, 3
                    • 9: 可以跳到 2, 4
              2. 动态规划 (DP)
                  • 使用 dp[step][i] 表示从数字 i 开始,经过 step 步后可以到达的路径数量。
                  • 状态转移公式: 其中,moves[i] 表示从数字 i 可以跳到的所有目标数字。
              3. 状态压缩优化
                  • 由于计算时只需要当前步和上一步的状态,使用滚动数组优化空间复杂度。
              4. 初始状态
                  • 0 步时,每个数字都可以作为起始点,因此路径数量为 1。
              5. 结果计算
                  • 从所有起始数字的路径数量累加得到最终结果。
            1. 代码实现
              1. 代码解释
                1. 定义跳跃规则
                    • 使用二维数组 moves 定义每个数字的跳跃目标。
                2. 动态规划
                    • dpPrev 保存上一步的状态。
                    • dpCurr 保存当前步的状态。
                    • 对于每个数字,计算其跳跃目标数字的路径数量并累加。
                3. 结果计算
                    • 最后一步的所有数字路径数量的总和就是最终结果。
                4. 模运算
                    • 每次累加时都对结果取模,防止溢出。
              1. 复杂度分析
                1. 时间复杂度
                    • 总共 n 步,每步需要遍历 10 个数字,每个数字的跳跃目标最多为 3 个。
                2. 空间复杂度
                    • 只需存储当前步和上一步的状态,空间复杂度为常数级别。
               

              2024.12.11 T2717. 半有序排列

              2717. 半有序排列 - 力扣(LeetCode)
              2717. 半有序排列 - 给你一个下标从 0 开始、长度为 n 的整数排列 nums 。 如果排列的第一个数字等于 1 且最后一个数字等于 n ,则称其为 半有序排列 。你可以执行多次下述操作,直到将 nums 变成一个 半有序排列 : * 选择 nums 中相邻的两个元素,然后交换它们。 返回使 nums 变成 半有序排列 所需的最小操作次数。 排列 是一个长度为 n 的整数序列,其中包含从 1 到 n 的每个数字恰好一次。   示例 1: 输入:nums = [2,1,4,3] 输出:2 解释:可以依次执行下述操作得到半有序排列: 1 - 交换下标 0 和下标 1 对应元素。排列变为 [1,2,4,3] 。 2 - 交换下标 2 和下标 3 对应元素。排列变为 [1,2,3,4] 。 可以证明,要让 nums 成为半有序排列,不存在执行操作少于 2 次的方案。 示例 2: 输入:nums = [2,4,1,3] 输出:3 解释: 可以依次执行下述操作得到半有序排列: 1 - 交换下标 1 和下标 2 对应元素。排列变为 [2,1,4,3] 。 2 - 交换下标 0 和下标 1 对应元素。排列变为 [1,2,4,3] 。 3 - 交换下标 2 和下标 3 对应元素。排列变为 [1,2,3,4] 。 可以证明,要让 nums 成为半有序排列,不存在执行操作少于 3 次的方案。 示例 3: 输入:nums = [1,3,4,2,5] 输出:0 解释:这个排列已经是一个半有序排列,无需执行任何操作。   提示: * 2 <= nums.length == n <= 50 * 1 <= nums[i] <= 50 * nums 是一个 排列
              2717. 半有序排列 - 力扣(LeetCode)
              1. 问题分析
                1. 给定一个排列 nums(包含从 1n 的每个数字且每个数字恰好出现一次),要求通过交换相邻的两个元素,使得:
                  • nums[0] == 1(第一个元素为 1)
                  • nums[n-1] == n(最后一个元素为 n
                  我们需要计算将 nums 转变为 半有序排列 所需的最小交换次数。
              1. 解题思路
                1. 找到 1 的位置
                    • 遍历数组找到 1 的索引 pos1
                    • 计算将 1 移动到 nums[0] 所需的交换次数 pos1
                2. 1 移到首位
                    • pos1 开始,将 1 向左逐步交换,直到移动到第一个位置。
                3. 找到 n 的位置
                    • 再次遍历数组,找到 n 的索引 posN
                    • 计算将 n 移动到 nums[n-1] 所需的交换次数 n - posN - 1
                4. 返回结果
                    • 两次移动的总和就是最终的最小交换次数。
              1. 代码实现
                1. 代码解释
                  1. 找到 1 的位置
                      • 遍历数组,找到 1 的索引 pos1
                      • 索引 pos1 表示 1 到达 nums[0] 所需的交换次数。
                  2. 1 移动到首位
                      • pos1 开始,将 1 向左逐步移动,同时调整其右侧的元素位置。
                  3. 找到 n 的位置
                      • 遍历数组,找到 n 的索引 posN
                      • 索引 posN 表示 n 到达 nums[n-1] 所需的交换次数。
                  4. 返回最小交换次数
                      • 将两次操作的交换次数累加得到结果。
                1. 复杂度分析
                  1. 时间复杂度
                      • 找到 1n 的位置需要两次线性扫描,总时间复杂度为
                  2. 空间复杂度
                      • 只使用了常量空间,空间复杂度为
                 

                2024.12.12 T2931. 购买物品的最大开销

                2931. 购买物品的最大开销 - 力扣(LeetCode)
                2931. 购买物品的最大开销 - 给你一个下标从 0 开始大小为 m * n 的整数矩阵 values ,表示 m 个不同商店里 m * n 件不同的物品。每个商店有 n 件物品,第 i 个商店的第 j 件物品的价值为 values[i][j] 。除此以外,第 i 个商店的物品已经按照价值非递增排好序了,也就是说对于所有 0 <= j < n - 1 都有 values[i][j] >= values[i][j + 1] 。 每一天,你可以在一个商店里购买一件物品。具体来说,在第 d 天,你可以: * 选择商店 i 。 * 购买数组中最右边的物品 j ,开销为 values[i][j] * d 。换句话说,选择该商店中还没购买过的物品中最大的下标 j ,并且花费 values[i][j] * d 去购买。 注意,所有物品都视为不同的物品。比方说如果你已经从商店 1 购买了物品 0 ,你还可以在别的商店里购买其他商店的物品 0 。 请你返回购买所有 m * n 件物品需要的 最大开销 。   示例 1: 输入:values = [[8,5,2],[6,4,1],[9,7,3]] 输出:285 解释:第一天,从商店 1 购买物品 2 ,开销为 values[1][2] * 1 = 1 。 第二天,从商店 0 购买物品 2 ,开销为 values[0][2] * 2 = 4 。 第三天,从商店 2 购买物品 2 ,开销为 values[2][2] * 3 = 9 。 第四天,从商店 1 购买物品 1 ,开销为 values[1][1] * 4 = 16 。 第五天,从商店 0 购买物品 1 ,开销为 values[0][1] * 5 = 25 。 第六天,从商店 1 购买物品 0 ,开销为 values[1][0] * 6 = 36 。 第七天,从商店 2 购买物品 1 ,开销为 values[2][1] * 7 = 49 。 第八天,从商店 0 购买物品 0 ,开销为 values[0][0] * 8 = 64 。 第九天,从商店 2 购买物品 0 ,开销为 values[2][0] * 9 = 81 。 所以总开销为 285 。 285 是购买所有 m * n 件物品的最大总开销。 示例 2: 输入:values = [[10,8,6,4,2],[9,7,5,3,2]] 输出:386 解释:第一天,从商店 0 购买物品 4 ,开销为 values[0][4] * 1 = 2 。 第二天,从商店 1 购买物品 4 ,开销为 values[1][4] * 2 = 4 。 第三天,从商店 1 购买物品 3 ,开销为 values[1][3] * 3 = 9 。 第四天,从商店 0 购买物品 3 ,开销为 values[0][3] * 4 = 16 。 第五天,从商店 1 购买物品 2 ,开销为 values[1][2] * 5 = 25 。 第六天,从商店 0 购买物品 2 ,开销为 values[0][2] * 6 = 36 。 第七天,从商店 1 购买物品 1 ,开销为 values[1][1] * 7 = 49 。 第八天,从商店 0 购买物品 1 ,开销为 values[0][1] * 8 = 64 。 第九天,从商店 1 购买物品 0 ,开销为 values[1][0] * 9 = 81 。 第十天,从商店 0 购买物品 0 ,开销为 values[0][0] * 10 = 100 。 所以总开销为 386 。 386 是购买所有 m * n 件物品的最大总开销。   提示: * 1 <= m == values.length <= 10 * 1 <= n == values[i].length <= 104 * 1 <= values[i][j] <= 106 * values[i] 按照非递增顺序排序。
                2931. 购买物品的最大开销 - 力扣(LeetCode)
                1. 问题分析
                  1. 给定一个 m x n 的矩阵 values,每一行代表一个商店的物品价值,所有物品的价值已经按非递增顺序排列。每天可以选择一个商店购买其当前价值最高的未购买物品。目标是通过尽可能高的开销完成所有物品的购买。
                    开销计算规则
                    • d 天购买的物品的开销为 values[i][j] * d
                    • 最大化总开销需要优先购买价值最高的物品,并且越晚购买价值越高的物品越好。
                1. 解题思路
                  1. 方法一:直接排序法(略讲)
                  2. 收集所有物品
                      • 将矩阵 values 展平为一维数组,并按降序排序。
                  3. 逐天购买
                      • i 天购买第 i-1 个最大价值的物品,计算开销。
                  4. 计算总开销
                      • 根据排序后的物品价值和购买天数,累积总开销。
                    代码实现
                    时间复杂度
                    • 排序
                    • 计算开销
                    空间复杂度
                方法二:优先队列法(推荐)
                核心思路
                • 使用优先队列(最小堆)动态维护当前所有商店中价值最高的物品。
                • 每天从堆中取出当前价值最高的物品进行购买,同时将该商店的下一个最高价值物品加入堆中。
                • 按购买天数和物品价值累积开销。
                具体步骤
                1. 初始化堆
                    • 每个商店的价值最高的物品(最右边物品)加入优先队列。
                1. 逐天购买
                    • 每天从堆中取出当前价值最高的物品,计算开销。
                    • 将对应商店的下一个最高价值物品加入堆中。
                1. 终止条件
                    • 当所有物品都购买完(堆为空)。
                代码实现

                复杂度分析
                时间复杂度:
                • 堆操作:每次取堆顶和插入的时间复杂度为 ,共进行 m * n 次操作,因此时间复杂度为
                空间复杂度:
                • 优先队列:需要存储最多 m 个商店的当前物品,空间复杂度为
                 

                2024.12.13 T3264. K 次乘运算后的最终数组 I

                2024.12.14 T3266. K 次乘运算后的最终数组 II

                方法一:
                1. 问题分析
                  1. 给定一个整数数组 nums,一个整数 k,以及一个整数 multiplier。需要对数组执行 k 次操作:
                    • 每次找到数组中最小的值(如果存在多个最小值,选择最前面的一个)。
                    • 将找到的最小值替换为其值乘以 multiplier
                    最终返回经过 k 次操作后的数组。
                1. 解题思路
                  1. 找到最小值
                      • 使用 std::min_element 函数,找到数组中当前的最小值。如果有多个最小值,std::min_element 会返回最前面的位置。
                  2. 更新最小值
                      • 将找到的最小值乘以 multiplier,更新数组。
                  3. 重复操作
                      • 执行上述步骤 k 次。
                  4. 返回结果
                      • 最终的 nums 数组即为结果。
                1. 代码实现
                  1. 代码解释
                    1. min_element 函数
                        • std::min_element(nums.begin(), nums.end()) 用于找到数组中最小值的迭代器。
                        • 如果有多个相等的最小值,它会返回最前面的一个。
                    2. 更新最小值
                        • 使用 iter 直接访问最小值,将其更新为 iter * multiplier
                    3. 循环操作
                        • 重复执行 k 次,每次都找到当前最小值并更新。
                    4. 返回结果
                        • 返回更新后的数组。
                  1. 复杂度分析
                    1. 时间复杂度
                        • 找最小值的时间复杂度是 n 是数组长度)。
                        • 重复操作 k 次,总时间复杂度为
                    2. 空间复杂度
                        • 只使用了常量级别的额外空间,空间复杂度为 O(1)
                  方法二:
                  1. 问题分析
                    1. 给定整数数组 nums,一个整数 k 和一个整数 multiplier。需要对数组进行以下操作 k 次:
                    2. 找到数组中最小值 x(若有多个最小值,选择最前面的一个)。
                    3. 将该值替换为 x * multiplier
                    4. 最终对数组中每个值取模 10^9 + 7
                    5. 要求返回经过所有操作后,数组的最终状态。
                  1. 解题思路
                    1. 优化操作次数
                    2. 快速幂计算
                        • 利用快速幂算法计算 multiplier^e % MOD,可以高效处理多次乘法操作。
                        • 这避免了直接对每个元素逐次相乘,从而提升效率。
                    3. 提前计算所需倍数
                        • 如果 multiplier 足够大,每次乘以 multiplier 后的结果可能很快超出数组中最大值。
                        • 使用预处理方法,提前计算使每个元素增长到超过数组最大值所需的最小倍数及相应的幂次。
                      两种处理方式
                    4. 直接暴力模拟
                        • 若剩余操作数 k 较小,直接使用最小堆找到当前最小值并更新其值。
                        • 每次操作后,重新维护最小堆。
                    5. 批量处理剩余操作
                        • 若剩余操作数较多,利用数学公式计算结果:
                          • 将剩余的 k 次操作按均匀分布应用到所有元素,减少逐次计算的复杂度。
                  1. 代码实现
                    1. 代码解释
                      1. 快速幂
                          • 使用快速幂计算乘法结果,避免逐次乘法导致的高时间复杂度。
                      2. 预处理
                          • 计算将每个元素提升到超过当前最大值所需的最小幂次和倍数。
                      3. 暴力模拟
                          • 若剩余操作较少,直接使用最小堆依次更新最小值。
                      4. 批量处理
                          • 若剩余操作较多,利用数学公式直接计算每个元素最终值,避免逐次更新。
                    1. 复杂度分析
                      1. 时间复杂度
                          • 暴力模拟:(每次维护最小堆的时间复杂度)。
                          • 数学公式处理:
                          • 预处理:
                          • 总体时间复杂度为
                      2. 空间复杂度
                          • 使用最小堆和预处理表,空间复杂度为
                     

                    2024.12.15 T1338. 数组大小减半

                    1. 问题分析
                      1. 给定一个整数数组 arr,要求从数组中选出一个整数集合,并删除这些整数在数组中的所有出现。目标是通过删除这些数字,使得剩余数组中的元素个数至少为原数组的一半。
                    我们需要计算最小的整数集合大小,使得删除这些整数后,数组中剩下的元素个数不超过原数组的一半。
                    1. 解题思路
                      1. 统计元素出现频率
                          • 使用哈希表 unordered_map 来统计每个整数在数组中出现的次数。
                      2. 将出现频率排序
                          • 将所有数字的频率按降序排列,这样可以优先选择删除频率高的元素,从而更高效地减少数组的元素个数。
                      3. 累积删除
                          • 从频率数组中开始,逐步删除出现次数最多的数字,直到删除的元素总数超过数组的一半。记录删除数字的集合大小。
                      4. 返回结果
                          • 最终的结果即为删除数字的集合大小。
                    1. 代码实现
                      1. 代码解释
                        1. 统计元素频率
                            • 遍历数组 arr,用哈希表 mp 记录每个数字出现的次数。每个数字作为 mp 的键,出现次数作为值。
                        2. 构建频率数组
                            • 将所有数字的出现次数提取到 freq 数组中。
                        3. 降序排序
                            • 使用 std::sortfreq 数组按降序进行排序,这样频率高的数字排在前面。
                        4. 删除数字并累加
                            • freq 数组中逐个累加,删除频率最多的数字,直到删除的元素个数达到或超过数组长度的一半。
                            • 每次删除一个数字时,累加 sum(即已删除元素的个数),并通过 i 记录删除的数字集合的大小。
                        5. 返回结果
                            • 最终返回集合大小,即需要删除的数字的数量。
                      1. 复杂度分析
                        1. 时间复杂度
                            • 统计频率的时间复杂度是 ,其中 n 是数组的长度。
                            • 将频率数组进行排序的时间复杂度是 ,其中 k 是不同元素的个数,最多为 n。
                            • 因此,总的时间复杂度是 ,在最坏情况下 k 接近 n,所以整体时间复杂度是
                        2. 空间复杂度
                            • 使用了一个哈希表 mp 来存储元素及其频率,空间复杂度为 ,其中 k 是不同元素的个数,最多为 n。
                       

                      2024.12.16 T1847. 最近的房间

                      1847. 最近的房间 - 力扣(LeetCode)
                      1847. 最近的房间 - 一个酒店里有 n 个房间,这些房间用二维整数数组 rooms 表示,其中 rooms[i] = [roomIdi, sizei] 表示有一个房间号为 roomIdi 的房间且它的面积为 sizei 。每一个房间号 roomIdi 保证是 独一无二 的。 同时给你 k 个查询,用二维数组 queries 表示,其中 queries[j] = [preferredj, minSizej] 。第 j 个查询的答案是满足如下条件的房间 id : * 房间的面积 至少 为 minSizej ,且 * abs(id - preferredj) 的值 最小 ,其中 abs(x) 是 x 的绝对值。 如果差的绝对值有 相等 的,选择 最小 的 id 。如果 没有满足条件的房间 ,答案为 -1 。 请你返回长度为 k 的数组 answer ,其中 answer[j] 为第 j 个查询的结果。   示例 1: 输入:rooms = [[2,2],[1,2],[3,2]], queries = [[3,1],[3,3],[5,2]] 输出:[3,-1,3] 解释:查询的答案如下: 查询 [3,1] :房间 3 的面积为 2 ,大于等于 1 ,且号码是最接近 3 的,为 abs(3 - 3) = 0 ,所以答案为 3 。 查询 [3,3] :没有房间的面积至少为 3 ,所以答案为 -1 。 查询 [5,2] :房间 3 的面积为 2 ,大于等于 2 ,且号码是最接近 5 的,为 abs(3 - 5) = 2 ,所以答案为 3 。 示例 2: 输入:rooms = [[1,4],[2,3],[3,5],[4,1],[5,2]], queries = [[2,3],[2,4],[2,5]] 输出:[2,1,3] 解释:查询的答案如下: 查询 [2,3] :房间 2 的面积为 3 ,大于等于 3 ,且号码是最接近的,为 abs(2 - 2) = 0 ,所以答案为 2 。 查询 [2,4] :房间 1 和 3 的面积都至少为 4 ,答案为 1 因为它房间编号更小。 查询 [2,5] :房间 3 是唯一面积大于等于 5 的,所以答案为 3 。   提示: * n == rooms.length * 1 <= n <= 105 * k == queries.length * 1 <= k <= 104 * 1 <= roomIdi, preferredj <= 107 * 1 <= sizei, minSizej <= 107
                      1847. 最近的房间 - 力扣(LeetCode)
                      1. 问题分析
                        1. 给定一个二维数组 rooms 和二维数组 queries,每个房间都有唯一的房间号和面积。我们需要针对每一个查询,在符合条件的房间中找到最合适的房间。每个查询包含两个信息:
                          • preferred: 目标房间号
                          • minSize: 最小面积要求
                          对于每个查询,我们需要找到一个满足以下条件的房间:
                        2. 房间面积大于等于 minSize
                        3. 房间号与 preferred 的差的绝对值最小。
                        4. 如果差值相等,选择房间号较小的。
                        5. 如果没有满足条件的房间,返回 -1。
                      1. 解题思路
                        1. 排序
                          1. 首先,我们需要根据房间的面积和查询的最小面积进行排序:
                            • 对房间 rooms 按照面积从大到小排序,这样可以在处理查询时,逐步加入符合面积要求的房间。
                            • 对查询 queries 按照最小面积 minSize 从大到小排序,这样可以在处理查询时,优先处理面积大的房间。
                        2. 使用双指针和集合
                            • 使用一个指针 j 遍历 rooms 数组,将符合最小面积要求的房间加入集合 room_ids
                            • 对于每个查询,通过集合 room_ids 找到一个房间,其房间号与 preferred 的差的绝对值最小。
                          c. 计算最小差值
                          对于每个查询,使用集合中的房间号,找出与 preferred 的差值最小的房间号。
                          • 可以通过 lower_bound 查找集合中第一个大于等于 preferred 的房间号。
                          • 检查集合中该位置及其前一个位置的差值,选择最合适的房间。
                          d. 返回结果
                          将结果按照查询的顺序保存,最后返回答案。
                      1. 代码实现
                        1. 代码解释
                          1. 排序房间
                              • 使用 ranges::sort(rooms, {}, [](auto& a) { return -a[1]; }); 对房间按面积降序排序,确保面积大或相等的房间会在前面。
                          2. 排序查询
                              • 使用 ranges::sort(query_ids, {}, [&](int i) { return -queries[i][1]; }); 对查询按照 minSize 降序排序。query_ids 数组保存了查询的索引,排序后方便逐个查询。
                          3. 房间的加入
                              • 使用 set<int> room_ids; 来存储符合查询条件的房间号。set 保证了房间号的有序性。
                              • 在处理每个查询时,首先将所有符合 minSize 的房间号加入 room_ids
                          4. 查找最合适的房间
                              • 使用 lower_bound 查找最接近 preferred_id 的房间号。
                              • 如果 it 指向的房间号比 preferred_id 小,还需要检查前一个房间号。
                              • 比较差值,选择最小的差值对应的房间号。
                          5. 答案保存
                              • 将每个查询的结果保存到 ans 数组,最终返回。
                        1. 复杂度分析
                            • 时间复杂度
                                1. 排序:房间排序的时间复杂度是 ,查询排序的时间复杂度是 ,其中 n 是房间数量,k 是查询数量。
                                1. 查询处理:对于每个查询,我们最多遍历一次房间数组和集合,查询部分的复杂度是
                                因此,总的时间复杂度是
                            • 空间复杂度
                              • 使用了一个 set 来存储房间号,其空间复杂度是 O(n),此外还使用了 O(k) 的空间存储查询结果。因此,空间复杂度是

                        2024.12.17 T3291. 形成目标字符串需要的最少字符串数 I

                        2024.12.18 T3292. 形成目标字符串需要的最少字符串数 II

                        1. 问题分析
                          1. 给定字符串数组 words 和目标字符串 target,每个 words[i] 都可以作为目标字符串的前缀。需要计算出通过连接这些前缀字符串形成目标字符串 target 所需的最少字符串数量。如果无法实现目标字符串,返回 -1。
                            • 需要判断 target 的子串是否是 words 中任意字符串的前缀。
                            • 使用高效的数据结构处理频繁的前缀查找。
                            • 通过有限的字符串拼接 target,确保字符串数量最少。
                        1. 解题思路
                        • 使用 Aho-Corasick 自动机构建前缀树
                          • Aho-Corasick 自动机是一种高效的多模式字符串匹配算法,支持:
                            1. 构建前缀树(Trie),存储所有模式字符串(words)。
                            1. 为每个节点添加失败指针(fail),使得匹配失败时能够高效跳转到下一个可能的匹配节点。
                        • 动态规划
                          • 利用自动机处理 target
                          • 定义 f[i] 为构造 target[0..i-1] 所需的最少有效字符串数量。
                          • 遍历 target,在每个位置动态更新 f,以记录通过前缀树的匹配状态。
                        • 算法步骤
                            1. 构建 words 的前缀树。
                            1. 为前缀树构造失配指针(fail)。
                            1. 使用动态规划,通过前缀树匹配 target,最小化字符串连接次数。
                        1. 代码实现
                          1. 复杂度分析
                          • 时间复杂度:,其中 n 是 target 的长度,L 是 words 中所有字符串的长度之和。
                          • 空间复杂度:
                           

                          2024.12.19 T3285. 找到稳定山的下标

                          这个题太easy了,懒得写了,期末了尽量保证做题不保证题解了~
                           

                          2024.12.20 T3138. 同位字符串连接的最小长度

                          1. 问题分析
                            1. 给定一个字符串 s,它由若干个相同的子字符串 t 和这些子字符串的同位字符串连接而成。我们需要找出字符串 t 的最小可能长度。所谓同位字符串,是指两个字符串由相同的字符组成,但字符的排列顺序可能不同。例如,“abc”和“bca”是同位字符串。
                          1. 解题思路
                            1. 我们可以从小到大尝试每个可能的长度 k,看是否可以将字符串 s 划分为若干个长度为 k 的子串,这些子串要么相同,要么是同位字符串。
                            2. 子串长度的选择
                                • 如果 n(字符串 s 的长度)能被 k整除,那么就可以尝试长度为 k 的子串。
                                • 否则,跳过当前长度。
                            3. 验证同位字符串
                                • 对于每个可能的长度 k,我们检查从第一个子串开始,后续的每一个子串是否都是同位字符串。
                                • 如果某个子串不是同位字符串,就跳过该长度,尝试更大的长度。
                            4. 返回最小长度
                                • 一旦找到一个合法的 k,就返回这个长度。
                          1. 代码实现
                            1. 代码解释
                              1. 遍历每个可能的子串长度
                                  • 代码从 k = 1 开始遍历每个可能的子串长度,直到 k = n / 2。如果 n % k != 0,就跳过这个长度,因为无法将 s 平均分割为 k 长度的子串。
                              2. 验证每个长度的子串是否是同位字符串
                                  • 首先,统计字符串 s 中第一个长度为 k 的子串的字符频次(cnt0)。
                                  • 然后,依次检查后续的每个长度为 k 的子串的字符频次。如果某个子串的字符频次与 cnt0 不同,则说明这不是一个合法的同位字符串,跳出循环,尝试更大的 k
                              3. 返回最小的有效长度
                                  • 如果找到了一个长度 k,使得所有子串都是同位字符串,就返回该长度。
                                  • 如果没有找到合法的 k,则返回 n,表示最小的子串长度为整个字符串 s 的长度。
                            1. 复杂度分析
                              1. 时间复杂度
                                  • 外层循环遍历 k 从 1 到 n / 2,因此有最多 n / 2 次迭代。
                                  • 对于每个 k,我们检查每个长度为 k 的子串,共需要遍历 n / k 个子串。每个子串的检查需要 O(k) 的时间(因为要统计 k 个字符的频次)。
                                  • 总的时间复杂度为:
                                  • 这里的复杂度是平方级别的,因为我们在最坏情况下需要遍历 n 次,每次遍历都进行最多 O(n) 的操作。
                              2. 空间复杂度
                                  • 使用了固定大小的数组 cnt 来存储每个子串的字符频次,数组大小为 26(即字母表大小),因此空间复杂度是 (常数空间)。
                             

                            2024.12.21 T2545. 根据第 K 场考试的分数排序

                            2545. 根据第 K 场考试的分数排序 - 力扣(LeetCode)
                            2545. 根据第 K 场考试的分数排序 - 班里有 m 位学生,共计划组织 n 场考试。给你一个下标从 0 开始、大小为 m x n 的整数矩阵 score ,其中每一行对应一位学生,而 score[i][j] 表示第 i 位学生在第 j 场考试取得的分数。矩阵 score 包含的整数 互不相同 。 另给你一个整数 k 。请你按第 k 场考试分数从高到低完成对这些学生(矩阵中的行)的排序。 返回排序后的矩阵。   示例 1: [https://assets.leetcode.com/uploads/2022/11/30/example1.png] 输入:score = [[10,6,9,1],[7,5,11,2],[4,8,3,15]], k = 2 输出:[[7,5,11,2],[10,6,9,1],[4,8,3,15]] 解释:在上图中,S 表示学生,E 表示考试。 - 下标为 1 的学生在第 2 场考试取得的分数为 11 ,这是考试的最高分,所以 TA 需要排在第一。 - 下标为 0 的学生在第 2 场考试取得的分数为 9 ,这是考试的第二高分,所以 TA 需要排在第二。 - 下标为 2 的学生在第 2 场考试取得的分数为 3 ,这是考试的最低分,所以 TA 需要排在第三。 示例 2: [https://assets.leetcode.com/uploads/2022/11/30/example2.png] 输入:score = [[3,4],[5,6]], k = 0 输出:[[5,6],[3,4]] 解释:在上图中,S 表示学生,E 表示考试。 - 下标为 1 的学生在第 0 场考试取得的分数为 5 ,这是考试的最高分,所以 TA 需要排在第一。 - 下标为 0 的学生在第 0 场考试取得的分数为 3 ,这是考试的最低分,所以 TA 需要排在第二。   提示: * m == score.length * n == score[i].length * 1 <= m, n <= 250 * 1 <= score[i][j] <= 105 * score 由 不同 的整数组成 * 0 <= k < n
                            2545. 根据第 K 场考试的分数排序 - 力扣(LeetCode)
                            1. 问题分析
                              1. 我们需要对一个矩阵进行排序,其中每行表示一个学生的成绩,每列表示一场考试的分数。要求按照某一场考试(第 kk 场考试)的分数从高到低对矩阵的行进行排序,返回排序后的矩阵。
                                输入:
                                • 的整数矩阵 score,每行表示一个学生的成绩。
                                • 整数 ,表示要按第 场考试的分数排序。
                                输出:
                                • 排序后的矩阵,行按照第 列分数从高到低排序。

                            1. 解题思路
                              1. 问题分解
                                  • 我们需要根据第 列的值排序矩阵的行。
                                  • 排序规则为降序排序。
                              2. 实现方法
                                  • 使用 C++ 的 std::sort,通过传入自定义排序函数对 score 按行排序。
                                  • 自定义排序函数比较每一行的第 列值,确保按降序排列。
                              3. 返回结果
                                  • 排序后的矩阵直接返回。

                            1. 代码实现

                              1. 代码解释
                                1. 函数定义
                                    • vector<vector<int>> sortTheStudents(vector<vector<int>>& score, int k)
                                      • 输入参数 score 是成绩矩阵。
                                      • 输入参数 表示按第 列排序。
                                      • 返回排序后的矩阵。
                                2. 排序逻辑
                                    • sort(score.begin(), score.end(), ...)
                                      • sortscore 的行(向量)排序。
                                      • 自定义排序函数 [](const vector<int>& u, const vector<int>& v) 比较两行:
                                        • u[k] > v[k] 实现降序排序。
                                3. 返回结果
                                    • 排序后的矩阵 score

                              1. 复杂度分析
                                1. 时间复杂度
                                    • 排序的时间复杂度为 ,其中 是矩阵的行数。
                                    • 每次比较的时间复杂度为 (比较第 列的值)。
                                    综合时间复杂度为:
                                2. 空间复杂度
                                    • 使用原地排序,不需要额外空间。空间复杂度为:
                               

                              2024.12.22 T1387. 将整数按权重排序

                              1387. 将整数按权重排序 - 力扣(LeetCode)
                              1387. 将整数按权重排序 - 我们将整数 x 的 权重 定义为按照下述规则将 x 变成 1 所需要的步数: * 如果 x 是偶数,那么 x = x / 2 * 如果 x 是奇数,那么 x = 3 * x + 1 比方说,x=3 的权重为 7 。因为 3 需要 7 步变成 1 (3 --> 10 --> 5 --> 16 --> 8 --> 4 --> 2 --> 1)。 给你三个整数 lo, hi 和 k 。你的任务是将区间 [lo, hi] 之间的整数按照它们的权重 升序排序 ,如果大于等于 2 个整数有 相同 的权重,那么按照数字自身的数值 升序排序 。 请你返回区间 [lo, hi] 之间的整数按权重排序后的第 k 个数。 注意,题目保证对于任意整数 x (lo <= x <= hi) ,它变成 1 所需要的步数是一个 32 位有符号整数。   示例 1: 输入:lo = 12, hi = 15, k = 2 输出:13 解释:12 的权重为 9(12 --> 6 --> 3 --> 10 --> 5 --> 16 --> 8 --> 4 --> 2 --> 1) 13 的权重为 9 14 的权重为 17 15 的权重为 17 区间内的数按权重排序以后的结果为 [12,13,14,15] 。对于 k = 2 ,答案是第二个整数也就是 13 。 注意,12 和 13 有相同的权重,所以我们按照它们本身升序排序。14 和 15 同理。 示例 2: 输入:lo = 7, hi = 11, k = 4 输出:7 解释:区间内整数 [7, 8, 9, 10, 11] 对应的权重为 [16, 3, 19, 6, 14] 。 按权重排序后得到的结果为 [8, 10, 11, 7, 9] 。 排序后数组中第 4 个数字为 7 。   提示: * 1 <= lo <= hi <= 1000 * 1 <= k <= hi - lo + 1
                              1387. 将整数按权重排序 - 力扣(LeetCode)
                              1. 问题分析
                                1. 题目要求我们根据整数的“权重”对区间 [lo, hi] 之间的整数进行排序,然后返回排序后的第 k 个数。整数的“权重”是指根据以下规则将该整数变成 1 所需要的步数:
                                2. 如果是偶数,则 x = x / 2
                                3. 如果是奇数,则 x = 3 * x + 1
                                4. 我们需要计算每个整数的权重并根据权重排序,若权重相同,则按数值升序排序。
                              1. 解题思路
                                1. 权重计算:首先,我们需要为每个整数计算其权重。计算权重的过程遵循题目给出的规则,这可以通过递归实现。为了提高效率,我们使用一个 unordered_map 来记忆化已计算的结果,避免重复计算。
                                2. 排序:对于区间 [lo, hi] 内的所有整数,我们计算每个整数的权重,并将其存入一个数组。然后根据权重进行升序排序,若权重相同,则按数值升序排序。
                                3. 返回第 k 个数:排序完成后,直接返回排序后的第 k 个数。
                              1. 代码实现
                                1. 代码解释
                                  1. unordered_map<int, int> mp:这个哈希表用于缓存已计算的整数权重。每当计算一个整数的权重时,我们首先检查该整数的权重是否已经计算过,避免重复计算。
                                  2. dfs(int i):这是一个递归函数,计算整数 i 的权重。如果 i 等于 1,则返回 0,表示权重为 0;否则,根据 i 是奇数还是偶数,递归计算其权重并返回。计算过程中使用了记忆化优化,避免重复计算。
                                  3. getKth(int lo, int hi, int k):此函数的目标是返回区间 [lo, hi] 中的第 k 个数。首先,通过 iota 函数生成区间 [lo, hi] 内的数字列表。然后,使用 ranges::sort 对这些数字按照它们的权重升序排序,若权重相同则按数值升序排序。最后,返回排序后的第 k 个数字。
                                1. 复杂度分析
                                  1. 计算权重的时间复杂度:每个整数的权重计算最多进行 32 步,因为题目保证了整数变成 1 所需的步数是一个 32 位有符号整数,因此单次权重计算的复杂度是 ,其中 x 是整数的大小。由于使用了记忆化优化,每个整数的权重最多计算一次。
                                  2. 排序的时间复杂度:我们需要对 [lo, hi] 区间内的所有整数进行排序。排序的时间复杂度是 ,其中 n 是区间 [lo, hi] 中整数的个数。
                                  3. 总体时间复杂度:整体时间复杂度为 ,其中 n 是区间 [lo, hi] 中的整数个数。每次计算整数的权重的复杂度是 ,但在排序过程中整体的时间复杂度会被排序的 主导。
                                  4. 空间复杂度:空间复杂度为 ,用于存储区间中的整数列表。由于我们使用了记忆化,存储已计算权重的 unordered_map 的空间复杂度为 ,其中 n 是区间 [lo, hi] 内整数的数量。
                                 

                                2024.12.23 T855 . 考场就座

                                1. 问题分析
                                  1. 需要设计一个模拟考场座位分配的类 ExamRoom,该类的功能主要有两个:
                                  2. 入座:学生进入考场时,他们必须选择距离其他学生最远的座位。如果有多个座位符合条件,他们会选择编号最小的座位。
                                  3. 离开:某个学生离开座位后,该座位变为空,系统需要更新座位信息。
                                1. 解题思路
                                  1. 数据结构设计
                                      • 使用 set<int> 来维护已经占用的座位,保证所有座位按升序排列,并且可以快速地检查某个座位是否已被占用。
                                      • 使用 priority_queue<pair<int, int>>(最大堆)来存储空座位区间,便于快速选择距离其他学生最远的座位。队列中的每个元素表示一对座位区间 (start, end),即从 startend 的连续空座位区间。
                                  2. 入座逻辑
                                      • 如果考场为空(即没有学生),则学生直接坐在第一个座位(座位编号为 0)。
                                      • 否则,我们需要选择一个距离其他学生最远的座位。这可以通过查看最大堆中的区间来实现。区间的计算方式是:对于每个空座位区间,计算其最远座位距离,选择距离最大的位置,若多个座位距离相同,选择编号最小的座位。
                                  3. 离开逻辑
                                      • 学生离开后,原本的座位成为空座位,更新空座位区间,并将新的空座位区间加入最大堆。
                                1. 代码实现
                                  1. 代码解释
                                    1. Comp 结构体
                                        • 该结构体定义了 priority_queue 的比较规则,用来根据区间的大小和起始位置排序。优先级队列会根据区间长度降序排序,如果长度相同,则优先选择起始位置较小的区间。
                                    2. ExamRoom 类的成员函数
                                        • ExamRoom(int n):初始化函数,n 为座位总数。初始化时,我们将整个座位区间 [0, n-1] 放入最大堆。
                                        • seat():当有学生进来时,我们从最大堆中取出最远的座位区间,计算出最优座位(最远座位),然后更新 seats 集合,最后将新产生的空座位区间推入最大堆。
                                        • leave(int p):当学生离开时,我们首先从 seats 集合中删除该座位,然后计算出新的空座位区间,并将该区间重新放入最大堆。
                                  1. 复杂度分析
                                      • seat() 函数
                                        • 最坏情况下,我们需要从 priority_queue 中提取最大元素并插入新的元素。priority_queue 的操作复杂度是 ,插入和提取操作都需要 时间,因此 seat() 的时间复杂度为
                                      • leave() 函数
                                        • 删除一个元素的复杂度为 ,然后插入新生成的空区间同样是 。因此 leave() 的时间复杂度也是
                                      • 总体时间复杂度:
                                      • 总体空间复杂度
                                        • 主要是由 set<int>priority_queue<pair<int, int>> 组成。set 存储已经占用的座位,priority_queue 存储空座位区间,因此空间复杂度为
                                   

                                  2024.12.24 T1705. 吃苹果的最大数目

                                  1705. 吃苹果的最大数目 - 力扣(LeetCode)
                                  1705. 吃苹果的最大数目 - 有一棵特殊的苹果树,一连 n 天,每天都可以长出若干个苹果。在第 i 天,树上会长出 apples[i] 个苹果,这些苹果将会在 days[i] 天后(也就是说,第 i + days[i] 天时)腐烂,变得无法食用。也可能有那么几天,树上不会长出新的苹果,此时用 apples[i] == 0 且 days[i] == 0 表示。 你打算每天 最多 吃一个苹果来保证营养均衡。注意,你可以在这 n 天之后继续吃苹果。 给你两个长度为 n 的整数数组 days 和 apples ,返回你可以吃掉的苹果的最大数目。   示例 1: 输入:apples = [1,2,3,5,2], days = [3,2,1,4,2] 输出:7 解释:你可以吃掉 7 个苹果: - 第一天,你吃掉第一天长出来的苹果。 - 第二天,你吃掉一个第二天长出来的苹果。 - 第三天,你吃掉一个第二天长出来的苹果。过了这一天,第三天长出来的苹果就已经腐烂了。 - 第四天到第七天,你吃的都是第四天长出来的苹果。 示例 2: 输入:apples = [3,0,0,0,0,2], days = [3,0,0,0,0,2] 输出:5 解释:你可以吃掉 5 个苹果: - 第一天到第三天,你吃的都是第一天长出来的苹果。 - 第四天和第五天不吃苹果。 - 第六天和第七天,你吃的都是第六天长出来的苹果。   提示: * apples.length == n * days.length == n * 1 <= n <= 2 * 104 * 0 <= apples[i], days[i] <= 2 * 104 * 只有在 apples[i] = 0 时,days[i] = 0 才成立
                                  1705. 吃苹果的最大数目 - 力扣(LeetCode)
                                  1. 问题分析
                                    1. 在这道题目中,我们有一棵特殊的苹果树,在每个给定的 n 天中,树上会长出不同数量的苹果,并且每种苹果都有它的腐烂时间。每天你最多能吃掉一个苹果,目的是计算出你能吃掉的苹果的最大数目。
                                      对于第 i 天,apples[i] 表示树上长出的苹果的数量,而 days[i] 表示这些苹果在多少天后会腐烂。我们的目标是设计一个高效的算法来模拟这个过程,尽可能多地吃掉苹果。
                                  1. 解题思路
                                    1. 我们可以通过一个优先队列(最小堆)来模拟这个过程。最小堆将帮助我们在每一天尽量选择腐烂时间最早的苹果来食用。具体步骤如下:
                                    2. 优先队列:我们使用一个优先队列来存储腐烂日期和苹果数量的对 (腐烂时间, 苹果数量)。优先队列会根据腐烂时间(最小的腐烂时间优先)排序,这样我们就可以在每一天挑选出最早腐烂的苹果。
                                    3. 遍历天数:从第 0 天开始,我们逐天检查苹果树上的苹果。每一天,我们将腐烂时间到当前日期的苹果移除(因为它们已经腐烂)。然后,如果当天有新苹果长出来(即 apples[i] > 0),我们将这些苹果加入优先队列。
                                    4. 吃苹果:如果优先队列不为空,我们从中取出最早腐烂的苹果,并将其数量减 1,表示我们吃掉了一个苹果。如果该类型的苹果还有剩余,则将其重新放入优先队列中,继续等待下一次食用。
                                    5. 终止条件:当我们遍历完所有天数并且优先队列为空时,停止。
                                  1. 代码实现
                                    1. 代码解释
                                      1. 变量初始化
                                          • ans:用来记录我们吃掉的苹果数。
                                          • n:苹果树的天数。
                                          • pq:一个最小堆,用来存储腐烂时间和苹果数量。
                                      2. 循环逻辑
                                          • 遍历每一天,检查是否有苹果腐烂(通过 while (!pq.empty() && pq.top().first == i) 来移除腐烂的苹果)。
                                          • 如果当天有新的苹果(apples[i] > 0),就将它们加入到优先队列,腐烂时间为 i + days[i]
                                          • 如果优先队列不为空,意味着我们可以吃掉一个苹果,就从堆中取出最早腐烂的苹果,并更新苹果数量。如果该种苹果数量不为 1,继续将它放回优先队列。
                                      3. 返回结果
                                          • 最后返回 ans,即我们能吃掉的苹果数量。
                                    1. 复杂度分析
                                        • 时间复杂度
                                          • 我们遍历了每一天,对于每一天,可能有一个苹果被加入到优先队列中,也可能有一个苹果从优先队列中被移除。每个操作的时间复杂度是 ,因此,总的时间复杂度是 ,其中 是苹果树的天数。
                                        • 空间复杂度
                                          • 使用了一个优先队列来存储苹果的腐烂时间和数量。最坏情况下,所有的苹果都不会腐烂(即每一天都有新的苹果),此时空间复杂度为
                                     
                                     
                                    3218. 切蛋糕的最小总开销 I - 力扣(LeetCode)
                                    3218. 切蛋糕的最小总开销 I - 有一个 m x n 大小的矩形蛋糕,需要切成 1 x 1 的小块。 给你整数 m ,n 和两个数组: * horizontalCut 的大小为 m - 1 ,其中 horizontalCut[i] 表示沿着水平线 i 切蛋糕的开销。 * verticalCut 的大小为 n - 1 ,其中 verticalCut[j] 表示沿着垂直线 j 切蛋糕的开销。 一次操作中,你可以选择任意不是 1 x 1 大小的矩形蛋糕并执行以下操作之一: 1. 沿着水平线 i 切开蛋糕,开销为 horizontalCut[i] 。 2. 沿着垂直线 j 切开蛋糕,开销为 verticalCut[j] 。 每次操作后,这块蛋糕都被切成两个独立的小蛋糕。 每次操作的开销都为最开始对应切割线的开销,并且不会改变。 请你返回将蛋糕全部切成 1 x 1 的蛋糕块的 最小 总开销。   示例 1: 输入:m = 3, n = 2, horizontalCut = [1,3], verticalCut = [5] 输出:13 解释: [https://assets.leetcode.com/uploads/2024/06/04/ezgifcom-animated-gif-maker-1.gif] * 沿着垂直线 0 切开蛋糕,开销为 5 。 * 沿着水平线 0 切开 3 x 1 的蛋糕块,开销为 1 。 * 沿着水平线 0 切开 3 x 1 的蛋糕块,开销为 1 。 * 沿着水平线 1 切开 2 x 1 的蛋糕块,开销为 3 。 * 沿着水平线 1 切开 2 x 1 的蛋糕块,开销为 3 。 总开销为 5 + 1 + 1 + 3 + 3 = 13 。 示例 2: 输入:m = 2, n = 2, horizontalCut = [7], verticalCut = [4] 输出:15 解释: * 沿着水平线 0 切开蛋糕,开销为 7 。 * 沿着垂直线 0 切开 1 x 2 的蛋糕块,开销为 4 。 * 沿着垂直线 0 切开 1 x 2 的蛋糕块,开销为 4 。 总开销为 7 + 4 + 4 = 15 。   提示: * 1 <= m, n <= 20 * horizontalCut.length == m - 1 * verticalCut.length == n - 1 * 1 <= horizontalCut[i], verticalCut[i] <= 103
                                    3218. 切蛋糕的最小总开销 I - 力扣(LeetCode)
                                     
                                    3219. 切蛋糕的最小总开销 II - 力扣(LeetCode)
                                    3219. 切蛋糕的最小总开销 II - 有一个 m x n 大小的矩形蛋糕,需要切成 1 x 1 的小块。 给你整数 m ,n 和两个数组: * horizontalCut 的大小为 m - 1 ,其中 horizontalCut[i] 表示沿着水平线 i 切蛋糕的开销。 * verticalCut 的大小为 n - 1 ,其中 verticalCut[j] 表示沿着垂直线 j 切蛋糕的开销。 一次操作中,你可以选择任意不是 1 x 1 大小的矩形蛋糕并执行以下操作之一: 1. 沿着水平线 i 切开蛋糕,开销为 horizontalCut[i] 。 2. 沿着垂直线 j 切开蛋糕,开销为 verticalCut[j] 。 每次操作后,这块蛋糕都被切成两个独立的小蛋糕。 每次操作的开销都为最开始对应切割线的开销,并且不会改变。 请你返回将蛋糕全部切成 1 x 1 的蛋糕块的 最小 总开销。   示例 1: 输入:m = 3, n = 2, horizontalCut = [1,3], verticalCut = [5] 输出:13 解释: [https://assets.leetcode.com/uploads/2024/06/04/ezgifcom-animated-gif-maker-1.gif] * 沿着垂直线 0 切开蛋糕,开销为 5 。 * 沿着水平线 0 切开 3 x 1 的蛋糕块,开销为 1 。 * 沿着水平线 0 切开 3 x 1 的蛋糕块,开销为 1 。 * 沿着水平线 1 切开 2 x 1 的蛋糕块,开销为 3 。 * 沿着水平线 1 切开 2 x 1 的蛋糕块,开销为 3 。 总开销为 5 + 1 + 1 + 3 + 3 = 13 。 示例 2: 输入:m = 2, n = 2, horizontalCut = [7], verticalCut = [4] 输出:15 解释: * 沿着水平线 0 切开蛋糕,开销为 7 。 * 沿着垂直线 0 切开 1 x 2 的蛋糕块,开销为 4 。 * 沿着垂直线 0 切开 1 x 2 的蛋糕块,开销为 4 。 总开销为 7 + 4 + 4 = 15 。   提示: * 1 <= m, n <= 105 * horizontalCut.length == m - 1 * verticalCut.length == n - 1 * 1 <= horizontalCut[i], verticalCut[i] <= 103
                                    3219. 切蛋糕的最小总开销 II - 力扣(LeetCode)
                                    1. 问题分析
                                      1. 我们需要将一个大小为 的矩形蛋糕切成 的小块。切割的操作开销不相同,对于每一条水平切割线和垂直切割线,有给定的切割开销数组 horizontalCutverticalCut。每次切割会增加一块小蛋糕,我们的目标是以最小的总开销将蛋糕切成 的小块。
                                    1. 解题思路
                                      1. 要解决这个问题,我们可以使用贪心算法。由于每次切割都会将蛋糕切成两个部分,并且切割操作的开销是根据每个部分的数量来计算的,因此我们应该优先选择开销较大的切割线,以便在切割的过程中减少对后续操作的影响。
                                        具体思路如下:
                                      2. 排序切割线的开销:首先,按切割开销从大到小对水平切割线 horizontalCut 和垂直切割线 verticalCut 进行排序。这样可以保证我们每次选择开销最大的切割线。
                                      3. 模拟切割过程
                                          • 初始化水平和垂直的分块数(初始时整个蛋糕就是一个大块,即水平和垂直方向上分别只有1块)。
                                          • 每次选择切割开销较大的切割线进行切割,并根据当前分块数计算切割的成本。
                                          • 对于每次切割,无论是水平切割还是垂直切割,切割的成本都等于当前切割线的开销乘以当前水平和垂直块数。
                                          • 更新切割方向的分块数。
                                      4. 边界条件
                                          • 如果水平切割线用完了,则只能进行垂直切割;反之亦然。
                                          • 每次选择较大的切割线,直到所有切割都完成。
                                    1. 代码实现
                                      1. 代码解释
                                        1. 排序
                                            • 我们首先对 horizontalCutverticalCut 数组进行排序,使用 greater<long long>() 保证它们按降序排列。这样可以确保我们每次选择开销最大的切割操作。
                                        2. 贪心选择切割操作
                                            • 使用两个指针 ij 分别指向 horizontalCutverticalCut 数组的当前位置。初始化时,水平和垂直方向上的切割块数分别为 1。
                                            • 在每次循环中,我们比较当前 ij 指向的切割开销,选择开销较大的切割线。
                                            • 每次切割操作都会增加分块数并更新总开销。
                                        3. 处理边界条件
                                            • 如果某一方向的切割已经完成(即指针 ij 超过了数组的大小),我们只能选择另一方向的切割。
                                        4. 返回结果
                                            • 最后,返回 totalCost,即最小的总切割开销。
                                      1. 复杂度分析
                                          • 时间复杂度
                                            • 排序 horizontalCutverticalCut 的时间复杂度是 ,其中 mn 分别是水平和垂直切割线的数量。
                                            • 计算总开销的过程中,每次操作的时间复杂度为 ,总共进行 m + n - 2 次操作,因此总时间复杂度为
                                          • 空间复杂度
                                            • 由于我们只使用了常量额外空间来存储变量,空间复杂度是
                                       

                                      2024.12.26 T3083. 字符串及其反转中是否存在同一子字符串

                                      1. 问题分析
                                        1. 题目要求我们检查字符串中是否存在一个长度为 2 的子字符串,其反转后的字符串也在原字符串中出现。具体来说,如果字符串 s 中存在子字符串 ab,并且反转后的 ba 也存在,则返回 true;否则返回 false
                                      1. 解题思路
                                        1. 位运算压缩状态
                                            • 我们可以利用一个二维布尔矩阵来表示每对字符是否曾作为长度为 2 的子字符串出现过。由于字符是小写英文字母,范围是 az,共有 26 个字符,因此矩阵的大小为
                                            • 为了节省空间,可以将二维矩阵用一个大小为 26 的整数数组代替,每个整数用 26 位的二进制表示每个字符的状态。
                                        2. 子字符串的记录
                                            • 遍历字符串 s,对于每个长度为 2 的子字符串 s[i]s[i+1]
                                              • 记录子字符串的状态:将 s[i+1] 对应的位设置为 1,表示 s[i] 后可以接 s[i+1]
                                              • 检查反转后的子字符串是否已出现:如果 s[i+1] 的记录中包含 s[i],即 h[s[i+1]]s[i] 位为 1,则说明反转后的子字符串存在,返回 true
                                        3. 快速返回
                                            • 一旦发现某个反转的子字符串存在,可以立即返回 true,无需继续遍历。
                                      1. 代码实现
                                        1. 代码解释
                                          1. 状态记录
                                              • h|= (1 << y) 表示字符 x 后可以接字符 y。通过位操作,利用 1 << yy 的位置标记为 1。
                                          2. 检查反转状态
                                              • h[y] & (1 << x) 检查字符 y 后是否接过字符 x。如果结果不为 0,说明反转的子字符串 yx 存在。
                                          3. 效率
                                              • 由于字符范围是固定的(26 个小写字母),使用位运算的方式能够高效地检查和记录子字符串状态,且内存占用低。
                                        1. 复杂度分析
                                          1. 时间复杂度
                                              • 遍历字符串一次,复杂度为 ,其中 是字符串的长度。
                                          2. 空间复杂度
                                              • 使用一个大小为 26 的整数数组,每个整数用 26 位记录状态,空间复杂度为
                                         

                                        2024.12.27 T3159. 查询数组中元素的出现位置

                                        1. 问题分析
                                          1. 题目要求我们对于每个查询,找到整数数组 nums 中指定次数 queries[i] 的整数 x 出现的位置,如果 x 出现的次数少于查询次数,则返回 -1
                                        1. 解题思路
                                          1. 查找所有 x 的位置
                                              • 遍历 nums 数组,记录所有 x 出现的位置。
                                              • 使用一个数组 idx 来存储这些位置。具体来说,当 nums[i] == x 时,将 i 添加到 idx 中。
                                          2. 处理查询
                                              • 对于每个查询 queries[i],我们检查 idx 数组的长度。如果 queries[i] 小于等于 idx 的大小,则返回 idx[queries[i] - 1],即查询指定的第 queries[i] 个位置(注意查询是 1-indexed,因此需要减 1)。
                                              • 如果 queries[i] 超出了 idx 数组的范围,则返回 1
                                        1. 代码实现
                                          1. 代码解释
                                            1. 找到所有 x 的位置
                                                • for (int i = 0; i < nums.size(); i++) 遍历数组 nums,如果 nums[i] == x,就将索引 i 添加到 idx 中,表示 xnums 中的出现位置。
                                            2. 处理查询
                                                • 对于每个查询 queries[i],首先检查 queries[i] 是否小于等于 idx 的大小。
                                                • 如果小于等于,则返回 idx[queries[i] - 1](因为查询是 1-indexed,需要减 1)。
                                                • 如果大于,则返回 1,表示该查询的 x 出现次数不足。
                                            3. 返回结果
                                                • 将每个查询的结果保存在 ans 数组中,最后返回该数组。
                                          1. 复杂度分析
                                            1. 时间复杂度
                                                • 遍历 nums 数组的时间复杂度为 ,其中 是数组 nums 的长度。
                                                • 遍历 queries 数组的时间复杂度为 ,其中 是数组 queries 的长度。
                                                • 总的时间复杂度为
                                            2. 空间复杂度
                                                • 存储 x 所有出现位置的数组 idx 的大小为 ,其中 x 出现的次数(最多为 )。
                                                • 存储查询结果的数组 ans 的大小为
                                                • 总的空间复杂度为

                                          2024.12.28 T3046. 分割数组

                                          1. 问题分析
                                            1. 题目要求我们将一个偶数长度的整数数组 nums 分割成两个长度相等的子数组 nums1nums2,并且满足以下条件:
                                            2. nums1nums2 的长度相等,且均为数组的一半长度。
                                            3. nums1 中的元素必须互不相同。
                                            4. nums2 中的元素必须互不相同。
                                            5. 如果能够找到符合条件的分割方式,返回 true,否则返回 false
                                          1. 解题思路
                                            1. 频次统计
                                                • 对于一个合法的分割,数组中的每个元素最多只能出现 2 次。因为如果某个元素出现超过 2 次,那么它就无法被分配到两个互不相同的子数组中。
                                                • 我们可以利用一个哈希表 mp 来记录每个数字的出现次数。如果某个数字的出现次数超过了 2 次,直接返回 false,因为无法满足要求。
                                            2. 分割检查
                                                • 如果所有元素的出现次数不超过 2 次,数组就可能分割成符合条件的两个子数组。具体来说:
                                                  • 每个元素出现 2 次时,必须将它均匀分配到两个子数组中。
                                                  • 每个元素出现 1 次时,可以任意分配到 nums1nums2
                                            3. 返回结果
                                                • 如果没有任何元素的出现次数超过 2 次,就返回 true,否则返回 false
                                          1. 代码实现
                                            1. 代码解释
                                              1. 频次统计
                                                  • unordered_map<int, int> mp 用来存储每个元素的出现次数。
                                                  • if (++mp[num] > 2) 这一行在遇到某个元素时,首先将该元素的出现次数加 1,如果该元素的次数超过了 2 次,直接返回 false,因为这不满足条件。
                                              2. 返回结果
                                                  • 如果所有元素的出现次数都不超过 2 次,则说明可以找到一种合法的分割方式,返回 true
                                            1. 复杂度分析
                                              1. 时间复杂度
                                                  • 遍历数组 nums 一次,时间复杂度为 ,其中 是数组的长度。
                                              2. 空间复杂度
                                                  • 使用哈希表存储每个元素的频次,空间复杂度为 ,其中 是数组的长度。
                                             

                                            2024.12.29 T1366. 通过投票对团队排名

                                            1366. 通过投票对团队排名 - 力扣(LeetCode)
                                            1366. 通过投票对团队排名 - 现在有一个特殊的排名系统,依据参赛团队在投票人心中的次序进行排名,每个投票者都需要按从高到低的顺序对参与排名的所有团队进行排位。 排名规则如下: * 参赛团队的排名次序依照其所获「排位第一」的票的多少决定。如果存在多个团队并列的情况,将继续考虑其「排位第二」的票的数量。以此类推,直到不再存在并列的情况。 * 如果在考虑完所有投票情况后仍然出现并列现象,则根据团队字母的字母顺序进行排名。 给你一个字符串数组 votes 代表全体投票者给出的排位情况,请你根据上述排名规则对所有参赛团队进行排名。 请你返回能表示按排名系统 排序后 的所有团队排名的字符串。   示例 1: 输入:votes = ["ABC","ACB","ABC","ACB","ACB"] 输出:"ACB" 解释: A 队获得五票「排位第一」,没有其他队获得「排位第一」,所以 A 队排名第一。 B 队获得两票「排位第二」,三票「排位第三」。 C 队获得三票「排位第二」,两票「排位第三」。 由于 C 队「排位第二」的票数较多,所以 C 队排第二,B 队排第三。 示例 2: 输入:votes = ["WXYZ","XYZW"] 输出:"XWYZ" 解释: X 队在并列僵局打破后成为排名第一的团队。X 队和 W 队的「排位第一」票数一样,但是 X 队有一票「排位第二」,而 W 没有获得「排位第二」。 示例 3: 输入:votes = ["ZMNAGUEDSJYLBOPHRQICWFXTVK"] 输出:"ZMNAGUEDSJYLBOPHRQICWFXTVK" 解释:只有一个投票者,所以排名完全按照他的意愿。   提示: * 1 <= votes.length <= 1000 * 1 <= votes[i].length <= 26 * votes[i].length == votes[j].length for 0 <= i, j < votes.length * votes[i][j] 是英文 大写 字母 * votes[i] 中的所有字母都是唯一的 * votes[0] 中出现的所有字母 同样也 出现在 votes[j] 中,其中 1 <= j < votes.length
                                            1366. 通过投票对团队排名 - 力扣(LeetCode)
                                            1. 问题分析
                                              1. 本题的目标是通过投票排序一个团队列表,依据团队的排名票数以及字母顺序来决定最终顺序。具体规则如下:
                                              2. 根据每个团队获得的 排位第一 的票数排序;如果并列,则比较 排位第二 的票数,依次类推。
                                              3. 如果团队的所有排名票数都相等,则按字母顺序排序。
                                            1. 解题思路
                                              1. 统计排名票数
                                                  • 使用一个 map,其中键是团队名称(字符),值是一个长度为 n 的数组,记录每个团队在每个排位上获得的票数。例如,mp['A'][0] 表示团队 A第一名 的票数,mp['A'][1] 表示团队 A第二名 的票数。
                                              2. 自定义排序
                                                  • 对团队按上述规则进行排序。
                                                  • 首先比较每个团队的票数数组,从第一名到最后一名依次比较票数,票数多的排前面。
                                                  • 如果所有排名票数都相等,则按字母顺序排序。
                                              3. 生成结果
                                                  • 将排序后的团队字符依次拼接成字符串,作为最终结果返回。
                                            1. 代码实现
                                              1. 代码解释
                                                1. 统计排名票数
                                                    • 遍历每个投票字符串 v,对于每个字符 v[i],将其在 i 名次上的票数加 1。
                                                    • 确保 mp[v[i]] 的大小为团队数量(v.size()),避免数组越界。
                                                2. 排序规则
                                                    • 使用 std::sort 按照自定义规则对团队排序。
                                                    • 比较 mp[a]mp[b],即从第一名到最后一名的票数逐一比较。如果票数不同,则票数多的优先;如果票数相同,则按字母顺序排序。
                                                3. 生成结果
                                                    • 排序后,将结果存储到字符串 res 中并返回。
                                              1. 复杂度分析
                                                1. 时间复杂度
                                                    • 统计票数:遍历所有投票字符串,每个字符串长度为 n,投票人数为 m,时间复杂度为
                                                    • 排序:对 n 个团队按票数排序,排序复杂度为
                                                    • 总时间复杂度为
                                                2. 空间复杂度
                                                    • mp 的大小为 ,因为每个团队对应一个长度为 n 的数组。
                                                    • 总空间复杂度为
                                               

                                              2024.12.30 T1367. 二叉树中的链表

                                              1. 问题分析
                                                1. 本题要求判断在给定的二叉树中是否存在一条从某个节点开始的路径,且路径的节点值依次与给定的链表的节点值一一对应。路径必须是从树的根节点开始并向下延伸的连续路径。
                                                  • 关键点:
                                                    • 二叉树:从某个节点向下,可能是左子树或右子树。
                                                    • 链表:每个节点值要与树中的一个节点值一一匹配。
                                              1. 解决思路:
                                                1. 深度优先搜索(DFS)
                                                    • 对每一个树的节点,我们尝试匹配链表中的第一个节点。
                                                    • 如果当前树的节点值与链表的第一个节点匹配,则递归地检查左子树和右子树,继续匹配链表的下一个节点。
                                                    • 如果树的某个路径能够完全匹配链表,则返回 True;否则返回 False
                                                2. 递归调用
                                                    • 在每个节点上,我们都检查是否从当前节点开始存在符合条件的路径。
                                                    • 如果当前节点值和链表当前节点值匹配,则递归去检查左子树或右子树。
                                                    • 如果路径不匹配,则直接返回 False
                                                3. 返回条件
                                                    • 如果链表的所有节点都匹配完毕,则返回 True
                                                    • 如果树的节点遍历完了还没有找到匹配路径,则返回 False
                                              1. 代码实现
                                                1. 代码解释
                                                  1. DFS函数
                                                      • 如果链表已经为空 (head == nullptr),说明所有节点都匹配成功,返回 true
                                                      • 如果树为空 (root == nullptr),说明当前路径无法匹配链表,返回 false
                                                      • 如果当前树节点的值与链表的当前节点值相等,则继续向下匹配,分别递归检查左子树和右子树。
                                                      • 如果当前树节点的值与链表的当前节点值不相等,则返回 false
                                                  2. isSubPath函数
                                                      • 如果链表为空,直接返回 true
                                                      • 如果树为空,且链表不为空,返回 false
                                                      • 从根节点开始,递归地检查树的每个节点,看是否存在一条路径与链表匹配。如果存在匹配的路径,则返回 true
                                                  3. 递归调用
                                                      • 首先在当前树的节点上执行 DFS,检查从当前节点开始是否存在匹配的路径。
                                                      • 然后递归检查左子树和右子树,看是否有其他节点能作为链表匹配的起点。
                                                1. 复杂度分析
                                                    • 时间复杂度
                                                      • 每个节点和每个链表节点只会被访问一次,因此最坏的情况下时间复杂度为 ,其中 是二叉树中的节点数, 是链表的长度。
                                                      • 对于每个树节点,我们最多需要执行一次链表的递归匹配。
                                                    • 空间复杂度
                                                      • 递归的深度是二叉树的最大深度 ,其中 是树的高度.
                                                      • 总空间复杂度为 ,其中 是树的高度, 是链表的长度。
                                                 

                                                2024.12.31 T3219. 切蛋糕的最小总开销 II

                                                见2024.12.25 T3219.切蛋糕的最小总开销 I
                                                 
                                                上一篇
                                                Leetcode刷题——11月
                                                下一篇
                                                Leetcode热门100题

                                                评论
                                                Loading...