题目链接
https://leetcode-cn.com/problems/longest-subarray-of-1s-after-deleting-one-element/
题目描述
给定二进制数组,需要从中删除一个元素。请你再删掉某个元素后得到的结果数组中,返回最长的且只包含1的非空子数组的长度。如果不存在这样的子数组,请返回0。
示例
输入:nums = [1,1,0,1]
输出:3
删掉索引为2的数字0后,[1,1,1]包含3个1。
解题思路
此题和【leetcode-Python】-滑动窗口-424. Longest Repeating Character Replacement类似,可以等价于从nums中找到0的个数不超过1、包含1的数组最多的子串。我们仍可以按照传统滑动窗口模板来思考,维持窗口内子串的合法状态。右边界一直右移,直到窗口内子串不合法(窗口内0的个数大于1),右指针不懂,左指针开始右移。直到子串内0的个数小于等于1,子串恢复到合法状态,左指针不动,右指针继续右移......直到右指针到达nums末尾。
由于必须删除一个元素,因此删除元素后的子数组长度应该为最长合法窗口的长度减去1。
Python实现
class Solution:
def longestSubarray(self, nums: List[int]) -> int:
left,right = 0,0
count = 0 #0的个数
res = 0
while(right < len(nums)):
c = nums[right]
right += 1
if(c == 0):
count += 1
while(count > 1): #窗口内0的个数大于1
d = nums[left]
left += 1
if(d == 0): #如果移出窗口的元素为0,给count减去1
count -= 1
res = max(res,right-left-1) #在count<=1的条件下更新结果
return res
进一步优化
由于找的是符合条件的最长子串,我们可以不维护窗口内子串的合法性,让滑动窗口要么扩张,要么平移(上面的常规滑动窗口算法更容易被记住并迁移,这种优化方法其实可以不记)。在这种方法下,窗口内的子串未必合法,但算法执行结束后子串的长度为至今满足题意的最长窗口长度。以nums = [0,1,1,1,0,1,1,0,1]为例说明这种更新方式:
右指针右移,直到窗口内0的个数超过1个。此时我们维护的删除一个元素后最长连续1的子串长度为4,子串为[0,1,1,1]。
此时窗口停止扩张,开始平移。左指针先移一位,右指针再移一位。经过滑动后窗口内0的个数为1,为合法子串,窗口长度为5。
右指针继续右移,新加入窗口的元素为1,符合条件,滑动窗口扩张。此时窗口内0的个数为1,为合法子串,窗口长度为6。
右指针继续右移,新加入窗口的元素为0,窗口内子串不再符合条件。
由于窗口内元素不再符合条件,滑动窗口开始平移(左边界移动一位后,右边界移动一位)。
窗口内子串中0个个数仍大于1,因此左指针右移一位。此时右指针已经到达数组终点,算法执行结束。此时[left,right)子串不合法,但是长度等于删除一个元素后最长连续1的子串长度,即为6。(因为滑动窗口扩张的条件是窗口内的子串合法,平移的条件是窗口内的子串不合法。滑动窗口平移到right到达字符串末尾时,如果窗口内的元素合法,那么当前窗口就是合法的最长子串。如果窗口内元素不合法,那么合法最长子串的长度就是当前窗口长度减去1(合法最长子串为扩张前的窗口)。left右移一位后当前窗口长度减去1,就和合法最长子串长度相同。因此我们最后可以直接返回right-left-1。减去的1表示子串中“删除的一个元素”)。
窗口内子串不满足条件时只平移不收缩是因为,收缩后得到的合法子串长度要么比收缩前得到的最长合法子串相等,要么比收缩前得到的最长合法子串短。因此收缩窗口不会给寻找“最长子串”带来任何帮助。
代码上的不同为:左边界一次只移动一位,将内层循环的while语句换为if语句。另外直接最后返回子串长度或对子串长度进行处理后的结果即可。
class Solution:
def longestSubarray(self, nums: List[int]) -> int:
left,right = 0,0
count = 0 #0的个数
res = 0
while(right < len(nums)):
c = nums[right]
right += 1
if(c == 0):
count += 1
if(count > 1): #窗口内0的个数大于1,此时我们平移滑动窗口
d = nums[left]
left += 1
if(d == 0):
count -= 1
return right-left -1