力扣2055这道题,我用前缀和做第一版的时候反而错了两次。原因很简单:题目里的“盘子”不是普通区间求和里那种每个元素都算数的元素,它要求盘子必须被两根蜡烛包住。换句话说,不能一上来就对查询区间做盘子数量的前缀和相减,得先把区间“裁剪”到有效蜡烛之间,再做统计。
这道题很适合当前缀和从“会写一维模板”进阶到“能处理边界条件”的过渡题,面试也比较常见。它能帮你把三件事一次练透:预处理数组怎么设计、边界值为什么不能漏判、区间查询怎么从O(n)降到O(1)。下文我会从题意拆解、前缀和与左右蜡烛数组的配合、完整代码实现、常见易错点排查这几个方面展开,最后把前缀和与差分这对概念的用途也捋一遍。
1. 题目核心拆解:为什么不能直接数区间里的盘子
1.1 先理解“被蜡烛夹住的盘子”到底是指什么
题目输入只有一个字符串s,里面的*代表盘子,|代表蜡烛,外加若干组查询。每个查询给一个下标区间[l, r],让我返回这个区间里满足条件的盘子数,条件是:盘子的左边和右边都至少有一根蜡烛,而且蜡烛也得在同一个查询区间内部。
我一开始理解错了,以为只要区间内出现两根蜡烛,中间不管有多少盘子都直接算进去,后来发现关键在“区间最外侧的蜡烛之外不能算”。举个例子:s = "*|*",查询[0,2]。肉眼扫过去,索引1是唯一的蜡烛,这个区间根本没构成“两根蜡烛中间夹着盘子”的结构,所以答案是0。但如果我用普通区间盘子数量前缀和去做,会得到区间内有1个盘子,直接返回1,这就错了。
再比如s = "|**|*",查询[0,4]。索引0和3各有一根蜡烛,中间索引1和2是两个盘子,索引4虽然也是盘子,但它在最右侧蜡烛3的右面,不能算作被夹住的盘子。所以正确答案是2,而不是区间总盘子数3。这道题真正要统计的,是区间内最左蜡烛和最右蜡烛之间的盘子。
1.2 暴力扫每个区间的做法为什么不够用
看到这种区间题,直觉是先对每个查询遍历子串,找到第一根蜡烛和最后一根蜡烛,再统计中间*的数量。思路没问题,问题是复杂度。如果字符串长度n和查询数量m都达到10^5,每个查询扫一次子串就是O(n*m),最坏情况到10^10次操作,基本不可能通过。
这时候自然会想到“预处理 + 查表”的思路。静态数组、字符串,配上一大堆区间查询,最典型的手段就是前缀和。可是前缀和能快速回答“某段区间里有多少个盘子”,不能直接回答“这段区间的有效蜡烛边界在哪里”,所以还需要辅助数组来解决边界定位问题。
1.3 题目真正想考察的预处理思路
把需求拆开看,会发现有两个独立问题:
- 如何快速知道某个位置右边最近的一根蜡烛在哪。
- 如何快速知道某个位置左边最近的一根蜡烛在哪。
只要预处理出这两个答案,再配合盘子数量的前缀和,每次查询就只需要做几次数组取值和一次减法,整体复杂度是线性的。这种“一次遍历生成辅助数组 + 前缀和做区间统计”的组合,刚好是前缀和类题目的进阶考法,力扣2055被归到前缀和标签下也就是这个原因。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法设计关键:左右蜡烛数组和盘子前缀和如何配合
2.1 一个数组不够,必须同时知道左右两个方向的最近蜡烛
有的解法会用“蜡烛下标数组+二分查找”,也就是把所有|的位置存到一个数组,查询时二分找到第一个不小于l的蜡烛下标,以及最后一个不大于r的蜡烛下标。这当然能过,而且代码也清晰。不过如果题目要求不依赖二分,或者想强化前缀和思想,用两个线性扫描数组更合适。
为什么一个数组不够?因为查询边界l和r的“身份”不一样。l是区间左端点,我需要从l向右找到第一根蜡烛,确保它不会被排除在区间外;r是区间右端点,我需要从r向左找到最后一根蜡烛。这是两个方向的问题,只存“左边最近的蜡烛”或只存“右边最近的蜡烛”都无法同时回答两端的需求。
2.2 预处理数组的构建过程
先定义两个数组:
leftCandle[i]:在下标[0, i]范围内,最靠右的蜡烛下标;如果不存在,则为-1。rightCandle[i]:在下标[i, n-1]范围内,最靠左的蜡烛下标;如果不存在,则为-1。
构建leftCandle时从左往右扫,用变量cur记录最后一次遇到的蜡烛下标。遇到|就更新cur,然后leftCandle[i] = cur。这样对于任意查询右端点r,leftCandle[r]直接告诉我区间内最右侧的蜡烛在哪。
构建rightCandle时从右往左扫,用变量cur记录从右往左最后一次遇到的蜡烛下标。遇到|就更新cur,然后rightCandle[i] = cur。这样对于任意查询左端点l,rightCandle[l]直接告诉我区间内最左侧的蜡烛在哪。
这两个数组的构建花费O(n),查询时是O(1),空间也是O(n)。整个过程
