LeetCode 刷题之旅(2020.04.21)

LeetCode 刷题之旅(2020.04.21)——1248. 优美子数组(中)

题目:

给你一个整数数组 nums 和一个整数 k。
如果某个 连续 子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。
请返回这个数组中「优美子数组」的数目。
示例 1:

输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1][1,2,1,1] 

示例 2:

输入:nums = [2,4,6], k = 1
输出:0
解释:数列中不包含任何奇数,所以不存在优美子数组。

示例 3:

输入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
输出:16

提示:
1 <= nums.length <= 50000
1 <= nums[i] <= 10^5
1 <= k <= nums.length

解题模板:

class Solution:
    def numberOfSubarrays(self, nums: List[int], k: int) -> int:

解题思路:

  • 滑动窗口
  • 排列组合

问题简化:

由于题目说明子数组必须连续。
因此,可以看做由 k 个奇数形成一组最小组合,然后将最小组合看做一个整体,组合两边剩余的偶数,产生多种子数组的组合,以此类推。
这样做可以将问题简化为两步:

  1. 找到最小组合
  2. 计算最小组合可以衍生的更多组合

找这样的最小组合很简单,在此不再赘述。
根据最小组合计算衍生的组合有一定数学规律:
举个例子:
2 2 2 1 2 2
将最小组合看做一个整体(例子中的 “1”),“1” 左边有三个可以组合的元素,右边有两个可以组合的元素。由于题目要求连续,因此左边可以有四种包含 “1” 的选取方式,右边可以有两种包含 “1” 的选取方式:

1 2 1 2 2 1 2 2 2 1
1 1 2

这样我们就得到了所有组合数量:4 * 2 = 8
接下来就是实现它了

解法:

  1. 版本1:
class Solution:
    def numberOfSubarrays(self, nums: List[int], k: int) -> int:
        find_ = [] # 用来记录位序的列表
        for i in range(len(nums)):
            if nums[i] % 2 == 1:
                find_.append(i) # 记录奇数所在位序
        if k > len(find_):
            return 0 # 奇数不足,异常判断
        count = 0
        for i in range(len(find_)):
            j = i + k - 1 # 最小组合的右边界
            left = 0 # 左边连续偶数的数量
            right = 0 # 右边连续偶数的数量
            if j >= len(find_):
                break
            if i - 1 in range(len(find_)):
                if find_[i] - find_[i-1] > 1:
                    left = find_[i] - find_[i-1] - 1
            else:
                left = find_[i]
            if j + 1 in range(len(find_)):
                if find_[j+1] - find_[j] > 1:
                    right = find_[j+1] - find_[j] - 1
            else:
                right = len(nums) - find_[j] - 1
            count += (left + 1) * (right + 1)
        return count

时间复杂度 O(N),不过因为条件判断太多,会影响程序运行速度,接下来的优化将尽可能减少判断
(不是不判断,而是将程序结构化,提高语句的重用性)
2. 版本2:

class Solution:
    def numberOfSubarrays(self, nums: List[int], k: int) -> int:
        find_ = [i for i in range(len(nums)) if nums[i] % 2] # 迭代器这样用看上去是不是很厉害?(不过其实没啥用...)
        if k > len(find_):
            return 0
        count = 0
        for i in range(len(find_) - k + 1): # 这里限制 i 的取值范围,可以替代过程中的不必要判断
            if i - 1 >= 0:
                left = find_[i] - find_[i-1] - 1
            else:
                left = find_[i]
            if i + k < len(find_):
                right = find_[i+k] - find_[i+k-1] - 1
            else:
                right = len(nums) - find_[i+k-1] - 1
            count += (left + 1) * (right+1)
        return count

时间复杂度O(N),虽然时间复杂度没什么变化,可是不必要的判断语句减少了,可以稍微提高一下运行速度,不过貌似这个版本的边界还是需要控制的,处于边界的情况还是和一般情况不太一样,find_数组还不能完全代表nums数组(find_两端的值依然不能和中间的值采用同样的操作处理)
3. 版本3:

class Solution:
    def numberOfSubarrays(self, nums: List[int], k: int) -> int:
        find_ = [-1] + [i for i in range(len(nums)) if nums[i] % 2] + [len(nums)]
        # 这样在头尾加上标记值就完全没问题了
        if k > len(find_) - 2:
            return 0
        count = 0
        for i in range(1, len(find_) - k):
            left = find_[i] - find_[i-1] - 1
            right = find_[i+k] - find_[i+k-1] - 1
            count += (left + 1) * (right+1)
        return count

这种处理方法,令我想到了学习 C 语言和数据结构课时候的带头结点的链表,同样达到了统一化操作的效果

你可能感兴趣的:(LeetCode每日一题)