2055. 蜡烛之间的盘子(前缀和)

129 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

不是因为看到了希望才坚持,而是因为坚持了才能看到希望。共勉

每日刷题第58天 2021.03.08

2055. 蜡烛之间的盘子

题目描述

  • 给你一个长桌子,桌子上盘子和蜡烛排成一列。给你一个下标从 0 开始的字符串 s ,它只包含字符 '' 和 '|' ,其中 '' 表示一个 盘子 ,'|' 表示一支 蜡烛 。
  • 同时给你一个下标从 0 开始的二维整数数组 queries ,其中 queries[i] = [lefti, righti] 表示 子字符串 s[lefti...righti] (包含左右端点的字符)。对于每个查询,你需要找到 子字符串中 在 两支蜡烛之间 的盘子的 数目 。如果一个盘子在 子字符串中 左边和右边 都 至少有一支蜡烛,那么这个盘子满足在 两支蜡烛之间。
  • 比方说,s = "|||||" ,查询 [3, 8] ,表示的是子字符串 "||**|" 。子字符串中在两支蜡烛之间的盘子数目为 2 ,子字符串中右边两个盘子在它们左边和右边 都 至少有一支蜡烛。
  • 请你返回一个整数数组 answer ,其中 answer[i] 是第 i 个查询的答案。

示例

  • 示例1 image.png
输入:s = "**|**|***|", queries = [[2,5],[5,9]]
输出:[2,3]
解释:
- queries[0] 有两个盘子在蜡烛之间。
- queries[1] 有三个盘子在蜡烛之间。
  • 示例2 image.png
输入:s = "***|**|*****|**||**|*", queries = [[1,17],[4,5],[14,17],[5,11],[15,16]]
输出:[9,0,0,0,0]
解释:
- queries[0] 有 9 个盘子在蜡烛之间。
- 另一个查询没有盘子在蜡烛之间。

提示

  • 3 <= s.length <= 105
  • s 只包含字符 '*' 和 '|' 。
  • 1 <= queries.length <= 105
  • queries[i].length == 2
  • 0 <= lefti <= righti < s.length

解题思路

  • 根据题意,查询区间最左侧和最右侧的两个蜡烛间的所有盘子都符合条件。因此,对于每个查询区间,只需要关注:
    • 左边端点右边第一个蜡烛的位置 (从右往左遍历数组)
    • 右边端点左边第一个蜡烛的位置 (从左往右遍历数组)
    • 这两个蜡烛之间的盘子数量(前缀和)
  • 首先统计每个位置之前有多少个盘子,这样我们就知道:两个位置之间的盘子的数目
  • 然后,再计算出给定区间能够包含的距离最远的两个蜡烛的位置,那么这两个蜡烛之间的盘子数目就是题目要求的答案。
  • 那么,如何计算给定区间能够包含的最大的两个蜡烛的位置呢?
    • 可以先计算出每个位置左侧第一个蜡烛的位置left。正序对s遍历,使用last=-1记录上个盘子的位置(-1表示当前位置之前没有盘子),在遍历的过程中,如果当前位置i是盘子,就更新last为i
    • 每次迭代最后,将last记录为i位置的目标值,即i位置左侧第一个盘子的位置为last
  • 同理,可以得到另外一个数组right表示每个位置右侧第一个盘子的位置,这个时候我们突然发现了,找到一个区间[i,j]内的间距最大的两个蜡烛的位置其实就是找左端点右侧第一个盘子的位置和右端点左侧第一个盘子的位置,也就是[right[i],left[j]]。那么,寻找这个区间内蜡烛之间盘子的数量,即计算这个区间内间距最远的两个蜡烛之间的盘子的数量,即计算[right[i],left[j]]这两个位置之间的盘子的数量。这个结果根据1可以得出。

AC代码

  • 思路
var platesBetweenCandles = function(s, queries) {
  // 需要维护三个数组 相邻最近的左边界、右边界和当前符合的数
  const lenS = s.length, lenQ = queries.length;
  // 维护左边界
  let left = new Array(lenS),right = new Array(lenS),pre = new Array(lenS);
  let flag = true,num = 0;
  // 左边节点正着找
  let leftNode = 0;
  for(let i = 0; i < lenS; i++) {
    if(flag && s[i] == '|'){
      leftNode = i;
      flag = false;
    }
    if(s[i] == '|'){
      leftNode = i;
    }
    if(!flag && s[i] == '*'){
      num++;
    }
    pre[i] = num;
    left[i] = leftNode;
  }
  // console.log(left)
  // 左边节点倒着找
  let rightNode = lenS - 1;
  for(let i = lenS - 1; i >= 0; i--) {
    if(s[i] == '|'){
      rightNode = i;
    }
    right[i] = rightNode;
  }
  // console.log(right)
  // 维护好三个数组后,做运算。
  // 只有两个*||*中间存在的时候,才会为正数,否则是负数
  // 左区间点的最近的右端点,右区间点最近的左端点相减。
  // 如果不在中间,那么就为负数
  let ans = new Array(lenQ);
  for(let i = 0; i < lenQ; i++) {
    ans[i] = pre[left[queries[i][1]]] - pre[right[queries[i][0]]];
    ans[i] = ans[i] > 0 ? ans[i] : 0;
  }
  return ans;
};

总结

  • 虽然是中等题,但是在周赛的时候,没有想出来,说明前缀和这种思想还需要多加练习。