:前缀/后缀预处理与单调性判定)
LeetCode 2100 适合打劫银行的日子Find Good Days to Rob the Bank前缀/后缀预处理与单调性判定【免费下载链接】leetcodeLeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解记录自己的leetcode解题之路。)项目地址: https://gitcode.com/gh_mirrors/le/leetcode本篇技术指南围绕 LeetCode 5935力扣题目编号 2100「适合打劫银行的日子」展开基于本仓库 problems/5935.find-good-days-to-rob-the-bank.md 题解文档从题目语义、双方向单调性预处理、线性扫描判定三个层面完整还原解题思路并结合仓库动态规划主题章节 thinkings/dynamic-programming.md 中的滚动数组与状态设计思想给出源码级扩展。读者学完后将掌握一类连续单调区间快速判定问题的标准解法用两个预处理数组分别记录左侧连续非递增长度与右侧连续非递减长度在 O(n) 时间内求解并能把该套路迁移到类似的数组区间条件判定题目中。题目概述题目描述你和一群强盗准备打劫银行。给你一个下标从 0 开始的整数数组security其中security[i]是第 i 天执勤警卫的数量日子从 0 开始编号。同时给你一个整数time。如果第 i 天满足以下所有条件我们称它为一个适合打劫银行的日子第 i 天前和后都分别至少有time天第 i 天前连续time天警卫数目都是非递增的第 i 天后连续time天警卫数目都是非递减的。更形式化地第 i 天是一个适合打劫银行的日子当且仅当security[i - time] security[i - time 1] ... security[i] ... security[i time - 1] security[i time]请你返回一个数组包含所有适合打劫银行的日子下标从 0 开始返回的日子可以任意顺序排列。该题在力扣中的题号为2100在仓库 README.md 与 SUMMARY.md 中均以「2100. 适合打劫银行的日子」收录文档文件命名为5935.find-good-days-to-rob-the-bank.md对应仓库内部约定文件名前缀使用力扣接口题号题目本身以find-good-days-to-rob-the-bank标识。条件语义拆解题目的核心条件是围绕某一个位置 i 的V 字形单调性左侧从i - time到i警卫数量必须非递增即向左看越靠近 i 数量越大或相等形成一段逐渐变大或持平的序列右侧从i到i time警卫数量必须非递减即向右看越远离 i 数量越大或相等形成一段逐渐变小或持平的序列。注意非递增和非递减都包含相等的情况与这与严格递增/严格递减不同是本题容易忽略的边界点。示例与边界情况原文档提供了四个覆盖典型场景的示例这里逐一展开说明并标注其中蕴含的边界条件示例 1输入security [5,3,3,3,5,6,2], time 2 输出[2,3]第 2 天值 3security[0] security[1] security[2] security[3] security[4]即5 3 3 3 5成立第 3 天值 3security[1] security[2] security[3] security[4] security[5]即3 3 3 5 6成立其余位置不满足因此输出[2,3]。该示例展示了连续相等值[3,3,3]可以同时充当多个合法日子的谷底因为非递增/非递减允许相等。示例 2输入security [1,1,1,1,1], time 0 输出[0,1,2,3,4]当time 0时前和后分别至少有 time 天这一条件自动满足左右两侧各需要考察 0 天即每一天都是合法日子。这是对至少 time 天边界的直接考验位置 i 必须满足i time且n - 1 - i time当time 0时任何位置都满足。示例 3输入security [1,2,3,4,5,6], time 2 输出[]数组整体严格递增任意位置左侧都不存在连续 2 天非递增的序列因此没有合法日子返回空数组。此例说明左侧单调性一旦不满足即使右侧再符合也无济于事。示例 4输入security [1], time 5 输出[]数组长度只有 1而time 5任何位置都无法满足前和后分别至少有 5 天的前置条件返回空数组。此例说明边界条件优先于单调性判断即使单调性满足只要窗口长度不足就应直接排除。数据范围与提示1 security.length 10^5 0 security[i], time 10^5security长度最大可达 10 万因此任何 O(n^2) 的朴素做法对每个位置向两侧扩展检查 time 天在最坏情况下都会超时必须设计 O(n) 级别的算法time可以为 0也可以大于数组长度如示例 4需要在前置条件中处理security[i]非负取值本身不参与复杂运算只需比较大小关系。前置知识原文档将本题的前置知识标注为动态规划。需要说明的是本题严格意义上属于动态规划思想的应用——它并不需要求解最优值而是利用与 DP 相同的状态递推与查表思路将位置 i 左侧连续非递增的长度视为一个可递推的状态该状态可由前一个位置i-1的状态在 O(1) 时间内转移得到预处理完成后通过查表l[i]、r[i]在 O(1) 时间内完成对每个位置的判定。这与仓库 thinkings/dynamic-programming.md 中对动态规划将一件事情分成若干阶段通过阶段之间的转移达到目标的概括一致本题的阶段就是数组下标转移就是相邻元素之间的大小关系判断最终目标是确定每个位置是否满足条件。核心思路双向预处理 线性扫描思路推导对于每一个位置 i我们如何判断其是否适合打劫显然需要知道两件事i前面有多少个连续位置满足小于等于当前位置即从 i 向左看序列非递增或者说security[j] security[j1]连续成立的长度i后面有多少个连续位置满足大于等于当前位置即从 i 向右看序列非递减或者说security[j] security[j1]连续成立的长度。因此我们可以先进行一次预处理将上面的两个信息求出来。不妨使用两个数组l和r分别存储l[i]表示 i 左侧有多少个连续位置是小于等于security[i]的即向左连续满足security[j-1] security[j]的步数r[i]表示 i 右侧有多少个连续位置是大于等于security[i]的即向右连续满足security[j] security[j1]的步数。接下来只需要遍历一次security判断每个位置是否满足l[i] time且r[i] time如果满足就将其下标加入结果数组ans。关键点预处理出数组l和r将每个位置两侧的连续单调长度在 O(n) 内求出两次独立的方向遍历l从左向右递推r从右向左递推最终判定是 O(1) 查表l[i] time and r[i] time不需要再向两侧扩展比较。正确性论证递推的单调性保持若security[i] security[i-1]则 i 左侧的连续非递增长度等于i-1左侧的连续非递增长度加 1把 i 自己接在 i-1 的序列后面若不等则说明以 i 为右端点的连续非递增段长度为 0。这正是当前状态只和前一个状态有关的 DP 式转移与 thinkings/dynamic-programming.md 中当前状态只和前两个状态有关因此只需要存储这两个的滚动数组思想同源——只不过本题需要同时保留所有位置的状态因此采用完整数组而非滚动变量。判定的充分必要性l[i] time意味着从 i 向左至少存在连续 time1 个位置含 i满足非递增等价于security[i-time] ... security[i]r[i] time同理等价于security[i] ... security[itime]。两者同时成立恰好对应题目形式化条件。同时前置条件第 i 天前和后分别至少有 time 天也由l[i] time要求 i 至少有 time 个左侧邻居隐含i time与r[i] time隐含n-1-i time自动保证——因为当左侧可连续非递增的步数达到 time 时i 前面必然至少有 time 天。代码实现原文档提供 Python3 实现这里完整保留并补充注释class Solution: def goodDaysToRobBank(self, security: List[int], time: int) - List[int]: n len(security) # l[i]i 左侧连续满足非递增security[j-1] security[j]的步数 # r[i]i 右侧连续满足非递减security[j] security[j1]的步数 l, r [0] * n, [0] * n ans [] # 从左向右递推 lsecurity[i] security[i-1] 时左侧非递增段延续 for i in range(1, n): if security[i] security[i-1]: l[i] l[i-1] 1 # 从右向左递推 rsecurity[i] security[i1] 时右侧非递减段延续 for i in range(n - 2, -1, -1): if security[i] security[i1]: r[i] r[i1] 1 # 查表判定两侧连续单调长度均达到 time 即为合法日子 for i in range(n): if l[i] time and r[i] time: ans.append(i) return ans代码逐段解读初始化l、r均初始化为全 0 的 n 长度数组。数组默认值为 0 是正确的基础态——当某个位置不满足延续条件时其对应侧连续长度为 0。正向递推 lfor i in range(1, n)中若security[i] security[i-1]则l[i] l[i-1] 1原代码以形式书写等价于直接赋值因为l[i]初始为 0。注意l[i]表示的是步数而非包含自身的个数l[0] 0若整个数组非递增则l[n-1] n-1。反向递推 rfor i in range(n-2, -1, -1)从右向左扫描若security[i] security[i1]则r[i] r[i1] 1。同理r[n-1] 0。查表判定单次循环中同时检查l[i] time与r[i] time满足则ans.append(i)。结果顺序自然为下标升序也满足题目可以任意顺序排列的要求。边界情况验证time 0示例 2任何位置的l[i] 0、r[i] 0恒成立因此全部下标都被加入答案输出[0,1,2,3,4]n 1, time 5示例 4两个递推循环体均不执行l[0] r[0] 0 5答案为空数组严格递增数组示例 3正向递推中security[i] security[i-1]恒不成立l全为 0任何time 0都无法满足l[i] time答案为空。复杂度分析令 n 为数组长度。时间复杂度O(n)。三次线性扫描两次预处理递推 一次查表判定总操作次数约为 3n与 n 呈线性关系空间复杂度O(n)。需要两个长度为 n 的辅助数组l和r用于存储每个位置两侧的单调长度。对于n 10^5的数据规模O(n) 的时间与空间均处于安全范围。深度扩展从本题看单调性预处理套路与其他题解方法的关联本仓库中还有多道题目采用了预处理 查表或方向递推的同类思想可以作为本题的延伸阅读42. 接雨水同样使用从左到右与从右到左两个方向的预处理数组leftMax/rightMax再对每个位置做 O(1) 判定与本题的双向数组结构高度一致84. 柱状图中最大的矩形利用单调栈快速求出每个柱子左右两侧第一个比它矮的位置同样是为每个位置求出两侧信息的思路变体1186. 删除一次得到子数组最大和也出现了l、r双数组 双向递推的代码结构用于分别记录前缀与后缀信息。这三道题与本题共同构成了一类可迁移的解题模式当判定条件涉及每个位置左右两侧的连续区间性质时先通过两次方向相反的线性递推把两侧信息存入数组再统一扫描判定。从动态规划视角看状态设计结合仓库 thinkings/dynamic-programming.md 的内容可以从更抽象的层面审视本题状态定义l[i]/r[i]是典型的以位置 i 为端点的状态状态总数恰为 n状态转移l[i] (security[i] security[i-1]) ? l[i-1] 1 : 0转移只依赖前一个状态是无后效性的体现——判定 i 时不需要知道 i 之后的信息l或 i 之前的信息r与滚动数组的对比动态规划中当前状态只和前一个状态有关时通常可以用滚动数组把空间压到 O(1)如 thinkings/dynamic-programming.md 中爬楼梯一例。本题之所以保留完整数组是因为每个位置的状态都要在最后阶段独立查表无法被覆盖丢弃这说明了状态是否需要被后续多次复用是决定能否滚动优化的关键判据记忆化递归 vs 迭代该主题文档指出记忆化递归无法使用滚动数组优化而迭代式 dp table 可以。本题的迭代双向递推正属于后者天然具备构造辅助数组的便利性。可能的变式与进一步思考严格单调版本若把非递增/非递减改为严格递减/严格递增只需把比较符改为其余结构不变左右窗口长度不同若左侧要求 time1、右侧要求 time2只需把判定条件改为l[i] time1 and r[i] time2空间优化r数组可以先求出来然后从右向左扫描时用滚动变量携带右侧连续长度在扫描过程中直接判定并生成答案从而只保留l一个数组空间可优化为只依赖单个 O(n) 数组时间仍为 O(n)。由于本题判定条件同时依赖两侧信息l数组仍需完整保留因此最坏空间仍为 O(n)二维/多维度推广该思路可推广到二维网格中每个格子向上/向下连续满足条件的长度等场景是很多矩阵类 DP 题目的前置步骤。小结本题表面是打劫银行的场景包装实质考查的是连续区间单调性的快速判定能力。标准解法分为三步从左向右递推l[i]记录位置 i 左侧连续非递增的长度从右向左递推r[i]记录位置 i 右侧连续非递减的长度单次扫描判定l[i] time and r[i] time收集答案。该解法的时间复杂度为 O(n)、空间复杂度为 O(n)完全适配n 10^5的数据范围同时天然覆盖time 0、数组过短、整体单调等多种边界情况。本仓库题解原文位于 problems/5935.find-good-days-to-rob-the-bank.md完整题目列表见 README.md对应条目「2100. 适合打劫银行的日子」。掌握双向预处理 查表判定这一套路后可以轻松迁移到接雨水、柱状图等同类区间单调性问题中。【免费下载链接】leetcodeLeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解记录自己的leetcode解题之路。)项目地址: https://gitcode.com/gh_mirrors/le/leetcode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考