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. 优化代码时优先减少空间开销(如用变量代替列表),其次简化逻辑(如去掉冗余的统计/判断)。
相关推荐
yyds_yyd_100861 小时前
1464. 数组中两元素的最大乘积(2026.07.27)
数据结构·c++·算法·leetcode
听风3472 小时前
Arch Linux 软件安装完全指南
linux·运维·archlinux·pacman
网络安全零基础教程3 小时前
网络安全春招面试避坑与复盘指南
linux·网络·安全·web安全·面试·职场和发展
摇滚侠3 小时前
《RocketMQ 官网》阅读笔记 RocketMQ 消息队列 MessageQueue 消息 Messagege
笔记·rocketmq
扛枪的书生4 小时前
TigerVNC 安装流程
linux
Mortalbreeze4 小时前
深入 Linux Socket 编程:端口号、网络字节序与 struct sockaddr 详解
linux·服务器·网络·c++
躺柒4 小时前
读半导体简史08大型机
linux·服务器·c语言·数据库·unix·ibm
Ztt6666666664 小时前
【无标题】
经验分享·笔记·其他·百度·微信公众平台
井川廊咏4 小时前
【总集篇】Linux | 如何创建一个 home 目录在 /data 磁盘的 sudo 用户(补档)
linux·运维·服务器
玖玥拾4 小时前
LeetCode 26 删除有序数组中的重复项
算法·leetcode