需要解释一种在Leetcode 1493中找到的很棒的解决方案。

huangapple go评论73阅读模式
英文:

Need explanation on one awesome solution found for Leetcode 1493

问题

我正在研究解决LeetCode问题1493. 删除一个元素后的最长子数组的解决方案:

给定一个二进制数组nums,你应该从中删除一个元素。

返回结果数组中仅包含1的最长非空子数组的大小。如果没有这样的子数组,则返回0。

示例 1

输入: nums = [1,1,0,1]
输出: 3
解释: 删除位置2上的数字后,[1,1,1]包含值为1的3个数字。

示例 2

输入: nums = [0,1,1,1,0,1,1,0,1]
输出: 5
解释: 删除位置4上的数字后,[0,1,1,1,1,1,0,1]中值为1的最长子数组是[1,1,1,1,1]

约束条件:

  • 1 <= nums.length <= 105
  • nums[i] 要么是0,要么是1。

我在提交池中找到了一个非常简洁的解决方案,我相信如果我能理解它是如何工作的,它将会很有益,但我就是无法理解。

class Solution(object):
    def longestSubarray(self, nums):
        ans = 0
        zero = 0
        left = 0
        for i, v in enumerate(nums):
            if v == 0:
                zero += 1
            if zero > 1:
                if nums[left] == 0:
                    zero -= 1
                left += 1
        return i - left

我认为,从返回语句可以清楚地看出,left 用于计算从左边未使用的零和未计数的1。
我理解它如何跟踪左边未使用的零的数量。
但它如何跟踪未包括的1呢?

而且为什么这个算法仍然适用于当最长子数组出现在中间时,它继续向前移动,超出了最长子数组?
我猜我只是在这里感到困惑...

英文:

I am looking at solutions for LeetCode problem 1493. Longest Subarray of 1's After Deleting One Element:

> Given a binary array nums, you should delete one element from it.
>
> Return the size of the longest non-empty subarray containing only 1's in the resulting array. Return 0 if there is no such subarray.
>
> ### Example 1
>
> Input: nums = [1,1,0,1]<br>
> Output: 3<br>
> Explanation: After deleting the number in position 2, [1,1,1] contains 3 numbers with value of 1's.
>
> ### Example 2
>
> Input: nums = [0,1,1,1,0,1,1,0,1]<br>
> Output: 5<br>
> Explanation: After deleting the number in position 4, [0,1,1,1,1,1,0,1] longest subarray with value of 1's is [1,1,1,1,1].
>
> ### Constraints:
>
> * 1 &lt;= nums.length &lt;= 105
> * nums[i] is either 0 or 1.

I found a super clean solution in the submission pool, which I believe to be beneficial if I can understand how it worked, but I just can't.

class Solution(object):
    def longestSubarray(self, nums):
        ans = 0
        zero = 0
        left = 0
        for i, v in enumerate(nums):
            if v == 0:
                zero += 1
            if zero &gt; 1:
                if nums[left] == 0:
                    zero -= 1
                left += 1
        return i - left

I think, from the return statement, it's clear that left is used to count the zeros from left, and the un-counted 1.
I see how it tracks how many unused zeros on the left.
But how does it track the 1s that are not included?

And why this algorithm still works for when the longest subarray appears in the middle, as it continues further, passing beyond the longest subarray?

I guess I'm just confused here...

答案1

得分: 1

这是一个使用滑动窗口技巧的方法,其中当前窗口在每次迭代开始时用 nums[left:i] 表示。

我们可以在循环体的 开始 处确定以下循环不变性:

  • 大小 i - leftnums[:i] 中包含至多一个零的最大窗口的大小。它表示我们之前(某处)遇到了一个具有相同大小且最多包含一个零的窗口,但我们不追踪找到它的索引。
  • zero 代表当前窗口 nums[left:i] 中零的数量。

当前迭代将 nums[i] 添加到窗口中,如果这保持了该窗口中零的数量最多为一个,那么我们有一个当前的“赢家”,并且 left 不会增加,以便窗口大小增加。否则,它不是赢家,left 会增加,以保持窗口大小不变。在这些操作期间,zero 会更新,以确保第二个不变性得以维持。

循环结束后,我们需要考虑以下事项:

在那里我们没有不变性,因为 i 没有增加到 len(nums),而是保持等于 nums 的最后索引。这是与编程语言有关的特殊情况。在其他循环构造或其他编程语言中,i 可能最终等于 len(nums)。无论如何,这里的值 i - left 比我们找到的“赢家”少一个。但这正是我们需要的,因为我们需要“...从中删除一个元素”。

在所有这些内容中可能令人困惑的一点是,当前窗口不代表感兴趣的窗口,只代表感兴趣窗口的 大小。该算法不关心最优窗口的确切位置。它只会进一步查找可能的比赢家多一个单位的潜在赢家。

为了了解代码的运行方式,我将以下 print 语句放入了代码中:

if zero > 1:
    if nums[left] == 0:
        zero -= 1
    left += 1
else:  # We have an improvement!
    print(f"the current winner is window {left}:{i+1}: {nums[left:i+1]}")

希望这能澄清一些问题。

英文:

This uses a sliding window technique, where the current window is identified with nums[left:i] at the start of every iteration.

We can identify the following loop invariants, when at the very start of the loop body:

  • The size i - left is the size of the largest window in nums[:i] that includes at most one zero. It indicates that we previously encountered (somewhere) a window of the same size that had at most one zero, but we don't track at which index we found it.
  • zero represents the number of zeroes in the current window nums[left:i].

The current iteration will add nums[i] to the window, and if that keeps the number of zeroes in that window to at most one, we have a current "winner", and left is not incremented so that the window size increases. Otherwise it is not a winner, and left is incremented, aiming to keep the window size unchanged. During these actions zero is updated to make sure the second invariant is maintained.

After the loop has finished, we need to take the following into account:

We don't have the invariant there, because i did not increase to len(nums), but remains equal to the last index of nums. This is something that is language specific. It happens to work like that in Python, but in other loop constructs or other languages, i could end up as equal to len(nums). Anyway, here value i - left is one less than the "winner" we found. But this is exactly what we needed, because we "...should delete one element from it".

One thing in all this that can be confusing is that the current window does not represent the window of interest, only the size of the window of interest. This algorithm doesn't care about where exactly that optimal window is located. It only looks further to find potential winners that are one unit larger.

To get some insight in how the code runs, it helped me to put this print in the code:

            if zero &gt; 1:
                if nums[left] == 0:
                    zero -= 1
                left += 1
            else:  # We have an improvement!
                print(f&quot;the current winner is window {left}:{i+1}: {nums[left:i+1]}&quot;)

I hope this clarifies it.

huangapple
  • 本文由 发表于 2023年7月6日 15:11:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76626315.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定