返回

从前缀和、二分到双指针,1004. 最大连续1的个数 III 有它独特解法!

后端

题目

给定一个由 0 和 1 组成的数组 nums 和一个整数 k,请返回其中包含 k 个 0 的最长连续子数组的长度。

示例 1:

输入:nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2
输出:6
解释:最长连续子数组为 [1,1,1,0,0,1,1,1,1,0],其中包含 20

示例 2:

输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], k = 3
输出:10
解释:最长连续子数组为 [0,0,1,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1],其中包含 30

提示:

  • 1 <= nums.length <= 105
  • nums[i] 为 0 或 1
  • 0 <= k <= nums.length

解法一:动态规划

动态规划是一种自底向上的解决问题的策略,它将问题分解成较小的子问题,然后依次解决这些子问题,最后得到问题的整体解。

对于本题,我们可以定义一个状态 dp[i],表示以 nums[i] 为结尾的最长连续子数组的长度。显然,dp[i] 的值可以由 dp[i-1] 的值推导而来。

如果 nums[i] 为 0,则 dp[i] 的值为 0。

如果 nums[i] 为 1,则 dp[i] 的值等于 dp[i-1] 加 1。

最终,我们要求得的最大值为 dp[n-1],其中 n 为 nums 的长度。

def longestOnes(nums, k):
  """
  :type nums: List[int]
  :type k: int
  :rtype: int
  """
  n = len(nums)
  dp = [0] * n

  if nums[0] == 1:
    dp[0] = 1

  for i in range(1, n):
    if nums[i] == 0:
      dp[i] = 0
    else:
      dp[i] = dp[i-1] + 1

  # 从左到右遍历数组,维护一个滑动窗口
  left, right = 0, 0
  max_len = 0
  zeros = 0

  while right < n:
    if nums[right] == 0:
      zeros += 1

    # 如果窗口中 0 的数量超过 k,则需要收缩窗口
    while zeros > k:
      if nums[left] == 0:
        zeros -= 1
      left += 1

    # 更新最大长度
    max_len = max(max_len, right - left + 1)

    right += 1

  return max_len

解法二:前缀和与二分

前缀和是一种将数组中的元素累加起来并存储在另一个数组中的技术。它可以帮助我们快速求出数组中某个子数组的和。

二分是一种在有序数组中快速查找元素的算法。它通过不断将数组一分为二来查找元素。

对于本题,我们可以先计算出 nums 数组的前缀和。然后,对于每个 i,我们可以使用二分法找到最大的 j,使得 nums[i] 到 nums[j] 之间的子数组中 0 的数量不超过 k。这样,我们就可以得到以 nums[i] 为结尾的最长连续子数组的长度。

def longestOnes(nums, k):
  """
  :type nums: List[int]
  :type k: int
  :rtype: int
  """
  n = len(nums)
  prefix = [0] * (n + 1)

  for i in range(n):
    prefix[i+1] = prefix[i] + nums[i]

  max_len = 0

  for i in range(n):
    # 使用二分法找到最大的 j,使得 nums[i] 到 nums[j] 之间的子数组中 0 的数量不超过 k
    left, right = i, n-1
    while left <= right:
      mid = (left + right) // 2
      if prefix[mid+1] - prefix[i] <= k:
        left = mid + 1
      else:
        right = mid - 1

    # 更新最大长度
    max_len = max(max_len, right - i + 1)

  return max_len

解法三:双指针

双指针是一种使用两个指针来遍历数组的算法。它可以帮助我们快速找到满足一定条件的子数组。

对于本题,我们可以使用两个指针 left 和 right 来遍历 nums 数组。当 right 指针向右移动时,我们维护一个窗口,该窗口包含从 left 指针到 right 指针之间的所有元素。当窗口中 0 的数量超过 k 时,我们将 left 指针向右移动。这样,我们就可以得到以 right 指针为结尾的最长连续子数组的长度。

def longestOnes(nums, k):
  """
  :type nums: List[int]
  :type k: int
  :rtype: int
  """
  n = len(nums)
  left, right = 0, 0
  max_len = 0
  zeros = 0

  while right < n:
    if nums[right] == 0:
      zeros += 1

    # 如果窗口中 0 的数量超过 k,则需要收缩窗口
    while zeros > k:
      if nums[left] == 0:
        zeros -= 1
      left += 1

    # 更新最大长度
    max_len = max(max_len, right - left + 1)

    right += 1

  return max_len

总结

  1. 最大连续1的个数 III 是一个中等难度的题目,涉及动态规划、前缀和、二分和双指针等多种算法。这篇文章详细讲解了这道题目的三种解法,并提供了示例代码。