题目链接
题目描述
给你一个二进制数组 nums
,你需要从中删掉一个元素。请你在删掉元素的结果数组中,返回最长的且只包含 1 的非空子数组的长度。如果不存在这样的子数组,请返回 0 。
示例
- 输入:
nums = [1,1,0,1]
-
- 输出:3
- 解释:删掉位置 2 的0后,得到
[1,1,1]
,包含 3 个 1。
- 输入:
nums = [0,1,1,1,0,1,1,0,1]
-
- 输出:5
- 解释:删掉位置 4 的0后,最长全 1 子数组长度为 5。
- 输入:
nums = [1,1,1]
-
- 输出:2
- 解释:必须删除一个元素,最长全 1 子数组长度为 2。
解法分析:滑动窗口法
核心思路
本题采用滑动窗口(双指针)技术解决,核心思想是维护一个窗口,使得窗口内最多包含1个0。通过移动窗口,找到满足条件的最大窗口长度,该长度即为删除一个0后所能得到的最长全1子数组的长度。
原始代码实现
class Solution:
def longestSubarray(self, nums: List[int]) -> int:
l = zeros = ans = 0
for k, v in enumerate(nums):
zeros += v ^ 1 # 计算当前元素是否为0,1表示是0,0表示是1
while zeros > 1:
zeros -= nums[l] ^ 1 # 减少左指针位置元素的0计数
l += 1 # 左指针右移
ans = max(ans, k - l) # 更新最长窗口长度
return ans
代码解析
- 初始化变量:
-
l
:滑动窗口的左指针,初始化为0zeros
:记录窗口内0的数量,初始化为0ans
:记录最长全1子数组的长度,初始化为0
- 遍历数组:
-
- 使用
enumerate(nums)
同时获取元素索引k
和值v
zeros += v ^ 1
:利用位运算计算当前元素是否为0。因为1 ^ 1 = 0
(非0),0 ^ 1 = 1
(是0),所以这行代码等价于统计窗口内0的数量
- 使用
- 调整窗口:
-
- 当
zeros > 1
时,说明窗口内0的数量超过1个,需要移动左指针l
zeros -= nums[l] ^ 1
:减少左指针位置元素的0计数,同样使用位运算判断元素是否为0l += 1
:左指针右移,缩小窗口范围,直到窗口内0的数量≤1
- 当
- 更新最长长度:
-
ans = max(ans, k - l)
:计算当前窗口长度(窗口内最多包含1个0),删除该0后,窗口内所有元素都是1,因此窗口长度即为所求的最长全1子数组长度- 最终返回
ans
关键技巧说明
- 位运算统计0的数量:使用
v ^ 1
代替条件判断,简洁高效。当v
为0时,v ^ 1 = 1
,表示增加一个0;当v
为1时,v ^ 1 = 0
,不影响0的计数。 - 滑动窗口维护:
-
- 右指针不断向右扩展,扩大窗口范围
- 当窗口内0的数量超过1时,左指针向右移动,缩小窗口范围
- 确保窗口内最多包含1个0,这样删除该0后,窗口内所有元素都是1
- 窗口长度计算:窗口长度为
k - l
,其中k
是右指针索引,l
是左指针索引。这个长度即为删除一个0后全为1的子数组长度。
复杂度分析
- 时间复杂度:O(n),其中n是数组的长度。左右指针各遍历数组一次,每个元素最多被访问两次。
- 空间复杂度:O(1),只使用了常数级别的额外空间。
示例详解
以输入nums = [1,1,0,1]
为例:
- 初始状态:
l=0
,zeros=0
,ans=0
k=0, v=1
:
-
zeros += 1 ^ 1 = 0
→zeros=0
zeros <= 1
,不进入循环ans = max(0, 0-0)=0
k=1, v=1
:
-
zeros += 1 ^ 1 = 0
→zeros=0
zeros <= 1
,不进入循环ans = max(0, 1-0)=1
k=2, v=0
:
-
zeros += 0 ^ 1 = 1
→zeros=1
zeros <= 1
,不进入循环ans = max(1, 2-0)=2
k=3, v=1
:
-
zeros += 1 ^ 1 = 0
→zeros=1
zeros <= 1
,不进入循环ans = max(2, 3-0)=3
- 最终返回
ans=3
,符合示例1的输出。
总结
该解法利用滑动窗口技术高效地解决了问题,通过维护窗口内0的数量不超过1个,确保删除一个0后窗口内全为1。算法使用位运算优化了0的计数过程,时间复杂度为O(n),空间复杂度为O(1),是解决该问题的最优解法之一。这种滑动窗口控制特定元素数量的思路,可广泛应用于类似的子数组问题。