【算法文章10| 前缀和:力扣2055题:蜡烛之间的盘子】

【算法文章10| 前缀和:力扣2055题:蜡烛之间的盘子】

题目链接:2055:蜡烛之间的盘子

这道题是力扣的一道1818分的题,大概题意是这样的:

  • 给你一个字符串,字符串里面只有两种符号:字符 '*''|' ,其中 '*' 表示 盘子'|' 表示 蜡烛
  • 然后给你一个二维的查询数组queries,对于每一个查询 queries[i] = [lefti, righti],找到 子字符串中两支蜡烛之间 的盘子的 数目
  • 返回一个整数数组 answer ,其中 answer[i] 是第 i 个查询的答案。

总结一句话:找规定区间内, 在两支蜡烛之间的盘子的数目

这道题的难点在于,对于每一个查询区间[lefti, righti],怎么找区间内的离边界最近的蜡烛的位置,也就是怎么寻找“有效边界”

思路:寻找“有效边界”

  1. 定义前缀和sum[],记录盘子的数量
  2. 定义两个数组left[] 跟 right[],他们的含义如下:
    1. left[]表示:索引i左侧(含 i)最近的 ‘|’ (蜡烛) 的索引
    2. right[]表示:索引 i 右侧(含 i)最近的 ‘|’ (蜡烛) 的索引
  3. 枚举所有的查询
    • 缩小区间范围
      • 找左端点右边的第一根蜡烛,作为新的左端点
      • 找右端点左边的第一根蜡烛,作为新的右端点

代码

classSolution{publicint[]platesBetweenCandles(String s,int[][] queries){int n = s.length();//1.前缀和sum[]记录盘子的数量int[] sum =newint[n+1];//2.left[]统计索引i(含 i)左侧最近的'|'的索引int[]left =newint[n];int lastCandle =-1;for(int i =0; i < n; i++){if(s.charAt(i)=='|'){ sum[i+1]= sum[i]; lastCandle = i;}else{ sum[i+1]= sum[i]+1;} left[i]= lastCandle;}//3.right[]统计索引i(含 i)右侧最近的'|'的索引int[]right =newint[n]; lastCandle =-1;for(int i = n-1; i >=0; i--){if(s.charAt(i)=='|'){ lastCandle = i;} right[i]= lastCandle;}int[]ans =newint[queries.length];for(int i =0; i < queries.length; i++){//找左端点右边的第一根蜡烛,作为新的左端点int l = right[queries[i][0]];//找右端点左边的第一根蜡烛,作为新的右端点int r = left[queries[i][1]];//更新答案if(l !=-1&& r !=-1&& l < r){ ans[i]= sum[r]- sum[l];}else{ ans[i]=0;}}return ans;}}


一定要理解这两句代码,这两句代码非常关键,我们的目的是:left 向右移,找到区间内的第一根蜡烛。将 right 向左移,找到区间内的最后一根蜡烛。
  • 时间复杂度: O ( n + q ) O(n + q) O(n+q)
    • 遍历字符串计算前缀和 sum 和 left 数组:O(n)
    • 遍历字符串计算 right 数组:O(n)
    • 遍历查询数组 queries:O(q)(q = queries.length)

利用具体案例帮助理解代码

场景一:区间内完全没有蜡烛

假设字符串 s = "***|***",长度为 7。查询区间为 [0, 2](即前三个星号 ***)。

  1. 预处理的结果
    • right 数组:每一个位置向右看的第一根蜡烛都在索引 3。所以 right[0] = 3
    • left 数组:前三个位置向左看都没有蜡烛。所以 left[0, 1, 2] = -1
  2. 判断
    • if (l != -1 && r != -1 && l < r)
    • 这里 r 是 -1,条件直接不成立。
    • 结果ans[i] = 0
场景二:只有一根蜡烛,或蜡烛在边缘无法包裹盘子

假设字符串 s = "***|**",查询区间为 [0, 5](整个字符串)。

  1. 预处理结果
    • right[0]:从索引 0 开始向右找,第一根蜡烛在索引 3。所以 l = 3
    • left[5]:从索引 5 开始向左找,第一根蜡烛也在索引 3。所以 r = 3
  2. 判断逻辑
    • if (l < r) → \rightarrow → 即 if (3 < 3)
    • 条件不成立(因为只有一根蜡烛,无法形成“中间”区域)。
    • 结论ans[i] = 0
场景三:区间内有蜡烛,但它们中间没盘子

假设字符串 s = "**||**",查询区间为 [0, 5]

  1. 预处理结果
    • l = right[0] → \rightarrow → 索引 2(第一根 |)。
    • r = left[5] → \rightarrow → 索引 3(第二根 |)。
  2. 前缀和计算
    • sum[2](前 2 个字符里的盘子数)是 2。
    • sum[3](前 3 个字符里的盘子数)也是 2(因为索引 2 是蜡烛,盘子数没增加)。
  3. 判断
    • l < r (2 < 3) 成立。
    • ans[i] = sum[3] - sum[2] → \rightarrow →0

如果对你有帮助,欢迎点赞、关注、收藏!

Read more

华为OD机试双机位C卷:日志解析(C/C++/Java/Python/Go/JS)

华为OD机试双机位C卷:日志解析(C/C++/Java/Python/Go/JS)

日志解析 2026华为OD机试双机位C卷 - 华为OD上机考试双机位C卷 200分题型 华为OD机试双机位C卷真题目录点击查看: 华为OD机试双机位C卷真题题库目录|机考题库 + 算法考点详解 题目描述 你是一个运维工程师,你同时负责n个系统的运维工作,已知每个系统每天会都从现场采集大量的现网运行日志(错误日志、接口日志等)下来生成一个日志文件,每个系统采集下来的日志文件大小均不相同。为了解析这些日志,你给每个系统配备了一台默认服务器进行日志解析,且此台服务器只能给本系统使用,由于所配置的服务器规则均相同,因为解析日志的速度也是相同的,即每秒钟可以解析defaultCnt条日志。 现在你发现解析的速度达不到预期,但你手头上还有一部分额外的资源可以使用,这些资源可以在任意时刻配置给任意一台服务器。但有个限制,那就是同一时刻只能配给其中一台服务器器,且服务器器是能整合全部额外资源,当然在下一秒钟即可配备给另外一台服务器。某一台服务器配备了额外资源以后,则每秒钟会增加解析extraCnt条日志,即每秒可解析(defaultCnt+extraCnt)条日志。 输入描述 输入一

By Ne0inhk
C++ 函数重载:规则、实现与实战案例

C++ 函数重载:规则、实现与实战案例

C++ 函数重载:规则、实现与实战案例 💡 学习目标:掌握函数重载的核心规则,能够熟练实现重载函数,并解决实际开发中重载相关的常见问题。 💡 学习重点:函数重载的匹配原则、与默认参数的冲突处理、实战场景中的重载应用。 一、函数重载的定义与核心价值 ✅ 结论:函数重载是 C++ 多态性的基础体现,允许同一作用域内定义多个同名函数,通过参数列表的差异区分调用。 函数重载的核心价值在于: 1. 简化函数命名,避免为功能相似的函数创建不同名称,提升代码可读性 2. 适配不同类型或数量的参数输入,让函数调用更灵活 ⚠️ 注意事项:函数返回值不能作为区分重载函数的依据。 例如以下代码是非法的: #include<iostream>usingnamespace std;// 非法重载:仅返回值不同intadd(int a,int b){return a + b;}doubleadd(int a,int

By Ne0inhk
改造红黑树实现封装 map/set:感受C++ 标准容器的精妙设计与底层实现

改造红黑树实现封装 map/set:感受C++ 标准容器的精妙设计与底层实现

容器map/set的底层是红黑树,这一篇详解红黑树如何封装实现map/set。 1.map/set设计的巧妙之处 map是key/value类型,set是key类型,两个冲突的参数类型,是如何由红黑树封装而成? 暴力思路:两个红黑树,一个kv,一个k。可是这样代码复用率极低,维护成本高。 源码思路:利用 键提取器——仿函数 提取kv、k的key,用一颗红黑树实现map,set C语言一般用函数指针,但是它十分麻烦,C++有了仿函数就很方便 接下来在红黑树基础上封装map和set 2.map和set的实现 2.1map和set的基本框架 + 原红黑树结构变化 map是key、value结构,set是key结构:  既然我们要用一个红黑树封装实现map和set,那传的参数就得通用: 原本是K,V结构,现在,要改成通用的,就用T吧 T根据需要,可选择传pair<K,

By Ne0inhk
C++ 继承:面向对象的代码复用核心机制

C++ 继承:面向对象的代码复用核心机制

C++ 继承:面向对象的代码复用核心机制 💡 学习目标:掌握继承的基本语法与核心特性,理解不同继承方式的访问权限控制,能够通过继承实现代码复用与扩展。 💡 学习重点:继承的语法格式、三种继承方式的区别、基类与派生类的关系、继承中的构造与析构顺序。 一、继承的概念与核心价值 ✅ 结论:继承是 C++ 面向对象三大特性之一,允许一个类派生类继承另一个类基类的属性和行为,实现代码复用,同时支持派生类在基类基础上扩展新功能。 继承的核心价值体现在两个方面: 1. 代码复用:避免重复编写相同的成员变量和成员函数,降低代码冗余度 2. 功能扩展:派生类可以在基类的基础上新增属性和方法,满足更复杂的业务需求 生活中的继承示例:学生和老师都属于“人”,都有姓名、年龄等属性和吃饭、睡觉等行为。可以先定义 Person 基类,再让 Student 和 Teacher 继承 Person,并各自扩展专属功能。 二、继承的基本语法与实现 2.1

By Ne0inhk