LeetCode 每日一题笔记 日期:2025.12.14 题目:2147.分隔长廊的方案数

LeetCode 每日一题笔记

0. 前言

  • 日期:2025.12.14
  • 题目:2147.分隔长廊的方案数
  • 难度:困难
  • 标签:数字 字符串

1. 题目理解

问题描述

在一个图书馆的长廊里,有一些座位和装饰植物排成一列。给你一个下标从 0 开始,长度为 n 的字符串 corridor ,它包含字母 'S' 和 'P' ,其中每个 'S' 表示一个座位,每个 'P' 表示一株植物。

在下标 0 的左边和下标 n - 1 的右边 已经 分别各放了一个屏风。你还需要额外放置一些屏风。每一个位置 i - 1 和 i 之间(1 <= i <= n - 1),至多能放一个屏风。

请你将走廊用屏风划分为若干段,且每一段内都 恰好有两个座位 ,而每一段内植物的数目没有要求。可能有多种划分方案,如果两个方案中有任何一个屏风的位置不同,那么它们被视为 不同 方案。

请你返回划分走廊的方案数。由于答案可能很大,请你返回它对 10⁹ + 7 取余 的结果。如果没有任何方案,请返回 0 。

示例

示例1

输入:corridor = "SSPPSPS"

输出:3

解释:总共有 3 种不同分隔走廊的方案。

上图中黑色的竖线表示已经放置好的屏风。

上图每种方案中,每一段都恰好有 两个 座位。
示例2

输入:corridor = "PPSPSP"

输出:1

解释:只有 1 种分隔走廊的方案,就是不放置任何屏风。

放置任何的屏风都会导致有一段无法恰好有 2 个座位。
示例3

输入:corridor = "SSPP"

输出:1

解释:仅有一种方案,无需额外放置屏风,整体为一段且恰好含2个座位。
示例4

输入:corridor = "SPSPSP"

输出:0

解释:座位总数为3,是奇数,无法分割为若干段且每段恰好2个座位,返回0。

2. 解题思路

核心观察

  1. 无解条件
    • 若字符串中 'S' 的总数为 0 或奇数,直接返回 0(无法分割为若干段且每段恰好 2 个座位);
    • 若 'S' 的总数为 2,仅有 1 种方案(无需额外放置屏风)。
  2. 方案数的核心逻辑
    • 把走廊按「每两个 S 为一组」划分,组与组之间的「可选屏风位置数」的乘积即为总方案数;
    • 例如 "SSPPSPS" 中,第一组 SS 结束于索引 1,第二组 SS 开始于索引 4,两组之间的位置有 3 个(索引 2、3、4 前),第三组 SS 开始于索引 6,与第二组之间有 1 个位置,总方案数为 3×1=3,与示例 1 一致。
  3. 关键推导
    • 记录每一组最后一个 S 的位置,相邻两组的「后一组第一个 S 的位置 - 前一组最后一个 S 的位置」即为两组间的可选屏风数;
    • 总方案数是所有相邻组间可选屏风数的乘积,最终对 10⁹+7 取余。

算法步骤

  1. 预处理检查:统计字符串中 'S' 的总数,若为 0 或奇数则返回 0;若为 2 则返回 1。
  2. 收集关键位置:遍历字符串,记录每两个 'S' 为一组的结束位置,以及下一组第一个 'S' 的位置,计算组间可选屏风数。
  3. 计算方案数:将所有组间可选屏风数相乘,过程中对 10⁹+7 取余,避免数值溢出。
  4. 结果处理:最终结果再次取余,返回整数形式。

3. 代码实现

java 复制代码
class Solution {
    private static final int MOD = 1000000007; 

       public static int numberOfWays(String corridor) {
        int s = 0;
        for (int i = 0; i < corridor.length(); i++) {
            if (corridor.charAt(i) == 'S') {
                s++;
            }
        }
        if (s == 0 || s % 2 != 0) {
            return 0;
        }
        if (s == 2 ) {
            return 1;
        }
        //正式逻辑
        long result=1;
        ArrayList<Integer> arr = new ArrayList<>();
        int seatCount = 0;
        int plantCount=0;
        for (int i = 0; i < corridor.length(); i++) {
            if (s==0){
                break;
            }
            else if (corridor.charAt(i) == 'S') {
               s-=2;
               i++;
               while (i<corridor.length()&&corridor.charAt(i)!='S'){
                   i++;
               }
               if (plantCount!=0)arr.add(plantCount+1);
               plantCount=0;
            }

            else plantCount++;
        }
       
         //对第一个的特殊处理
        
        if(corridor.charAt(0)!='S'){
            for(int i=1;i<arr.size();i++){
            int  a=arr.get(i)%MOD;
            System.out.println("第"+i+"个是"+a);
            result*=a;
            result=result%MOD;
        }
        }
        else {
            for(int i=0;i<arr.size();i++){
            int  a=arr.get(i)%MOD;
            System.out.println("第"+i+"个是"+a);
            result*=a;
            result=result%MOD;
        }
        }
       
        //算答案
        result%=MOD;
        return (int)result;
    }
}

4. 代码优化说明

原有代码的核心逻辑

原有代码通过统计总座位数先做无解判定,再遍历字符串收集组间可选屏风数,最后通过遍历列表相乘得到结果,核心思路与官方题解一致,但实现上存在冗余(如 plantCount 统计、arr 列表存储、首尾特殊处理)。

官方题解优化点

  1. 空间优化:无需额外列表存储组间可选数,遍历过程中直接计算并累乘,空间复杂度从 O(n) 降至 O(1);
  2. 逻辑简化 :通过 cnt 统计座位数,当 cnt >=3 且为奇数时(即遇到下一组第一个 S),直接计算当前位置与上一组最后一个 S 位置的差值,作为可选屏风数并累乘;
  3. 减少冗余操作 :去掉无意义的 plantCount 统计和首尾特殊处理,仅通过座位位置的差值计算方案数。

官方题解代码解析

java 复制代码
class Solution {
    private static final int mod = 1000000007;
    
    public int numberOfWays(String corridor) {
        int n = corridor.length();
        int prev = -1, cnt = 0, ans = 1;
        for (int i = 0; i < n; ++i) {
            if (corridor.charAt(i) == 'S') {
                ++cnt; // 统计当前遍历到的座位数
                // 当座位数>=3且为奇数时,说明遇到下一组第一个S,计算组间可选屏风数
                if (cnt >= 3 && cnt % 2 == 1) {
                    ans = (int)((long)ans * (i - prev) % mod);
                }
                prev = i; // 更新上一个S的位置
            }
        }
        // 最终检查:座位数不足2或为奇数,返回0
        if (cnt < 2 || cnt % 2 != 0) {
            ans = 0;
        }
        return ans;
    }
}
  • prev:记录上一组最后一个 S 的位置;
  • cnt:遍历过程中统计总 S 数,当 cnt 为奇数且 >=3 时,说明进入下一组,此时 i - prev 即为两组间的可选屏风数;
  • 累乘时通过 (long)ans * (i - prev) 避免 int 溢出,再对 mod 取余。

5. 复杂度分析

原有代码复杂度

  • 时间复杂度:O(n)。两次遍历字符串(第一次统计 S 数,第二次收集组间数),遍历列表的时间也为 O(n),整体为线性时间;
  • 空间复杂度 :O(n)。最坏情况下 arr 列表存储 O(n) 个组间数,其余变量为常数级。

官方题解复杂度

  • 时间复杂度:O(n)。仅一次遍历字符串,所有操作均为线性时间;
  • 空间复杂度 :O(1)。仅使用常数个变量(prevcntans 等),无额外空间开销。

6. 总结

题目核心

本题的核心是将「分段恰好含 2 个座位」的问题转化为「统计组间可选屏风数的乘积」,关键在于发现「方案数 = 各组间可选位置数的乘积」这一数学规律,同时需注意数值溢出问题(用 long 存储中间结果、过程中取余)。

关键知识点

  1. 无解条件快速判定:通过统计座位总数的奇偶性/是否为 0,提前返回 0,减少无效计算;
  2. 溢出处理 :由于答案可能极大,需用 long 存储中间结果,且每一步乘法后对 10⁹+7 取余;
  3. 空间优化思路:避免额外数据结构存储中间值,遍历过程中直接计算并累乘,降低空间复杂度。

刷题启示

  1. 面对字符串类计数问题,先通过数学规律简化问题(如本题的组间数乘积),再考虑代码实现;
  2. 处理大数取余问题时,需注意:
    • final 常量定义模数(如 1000000007),避免魔法数字;
    • 乘法前将变量转为 long 类型,防止 int 溢出;
    • 每一步运算后取余,而非最后统一取余,避免中间结果超出数据类型范围;
  3. 优化代码时优先减少空间开销(如用变量代替列表),其次简化逻辑(如去掉冗余的统计/判断)。
相关推荐
徐子元竟然被占了!!2 小时前
Linux-tar
linux
艾莉丝努力练剑2 小时前
【Linux进程(一)】深入理解计算机系统核心:从冯·诺依曼体系结构到操作系统(OS)
java·linux·运维·服务器·git·编辑器·操作系统核心
阿蒙Amon2 小时前
JavaScript学习笔记:8.日期和时间
javascript·笔记·学习
被制作时长两年半的个人练习生2 小时前
使用rvv优化rms_norm
linux·llama·risc-v
暗然而日章2 小时前
C++基础:Stanford CS106L学习笔记 10 函数模板(Function Templates)
c++·笔记·学习
swan4162 小时前
SCAU期末笔记 - 实时计算框架章末实验
笔记
zyq~2 小时前
【课堂笔记】统计
笔记·概率论
蒙奇D索大2 小时前
【数据结构】考研408|从B树到B+树:多路平衡的优化形态与数据库索引基石
数据结构·笔记·b树·学习·考研
不会代码的小猴2 小时前
C++的第十五天笔记
数据结构·c++·笔记