leetcode
leetcode 1151 ~ 1200
统计「优美子数组」

统计「优美子数组」

难度:

标签:

题目描述

代码结果

运行时间: 574 ms, 内存: 22.7 MB


/*
 * 思路:
 * 我们需要找到包含恰好 k 个奇数的子数组的数量。
 * 通过使用Java Stream API,我们可以更优雅地实现这个问题。
 * 首先找到所有奇数的位置,然后计算有多少子数组可以包含这些奇数。
 * 对于每个窗口(包含 k 个奇数),我们可以通过计算其左边和右边的偶数数目来确定有多少个这样的子数组。
 */

import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

public class BeautifulSubarraysStream {
    public int numberOfSubarrays(int[] nums, int k) {
        List<Integer> oddIndices = IntStream.range(0, nums.length)
                .filter(i -> nums[i] % 2 != 0)
                .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);

        if (oddIndices.size() < k) {
            return 0;
        }

        return IntStream.range(0, oddIndices.size() - k + 1)
                .map(i -> {
                    int left = (i == 0) ? oddIndices.get(i) + 1 : oddIndices.get(i) - oddIndices.get(i - 1);
                    int right = (i + k == oddIndices.size()) ? nums.length - oddIndices.get(i + k - 1) : oddIndices.get(i + k) - oddIndices.get(i + k - 1);
                    return left * right;
                })
                .sum();
    }

    public static void main(String[] args) {
        BeautifulSubarraysStream bs = new BeautifulSubarraysStream();
        int[] nums = {1, 1, 2, 1, 1};
        int k = 3;
        System.out.println(bs.numberOfSubarrays(nums, k)); // 输出 2
    }
}

解释

方法:

此题解首先统计数组中所有奇数的位置,并将这些位置的索引存储在一个列表`odd`中。为了方便处理边界情况,题解在`odd`列表的开头添加了一个-1(表示数组起始之前的位置),在末尾添加了`len(nums)`(表示数组结尾之后的位置)。接下来,题解通过遍历`odd`中的每个有效奇数位置的索引来计算每种可能的子数组的数量。对于每个有效的索引`i`(从1到`len(odd)-k`),计算从`odd[i-1]+1`到`odd[i+k-1]`的子数组,其中包含恰好`k`个奇数。通过计算两端奇数位置之间的间隔,可以确定每个这样的子数组可以在原数组中存在的不同位置数,最后将所有这些可能的位置数累加起来,得到最终结果。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
在题解中添加-1和len(nums)作为数组的起始和结束辅助点的原因是什么?这样做有什么特别的好处吗?
在数组`odd`中添加-1和`len(nums)`作为起始和结束辅助点的主要原因是为了简化边界处理。添加-1意味着可以把数组前面没有元素的情况也统一处理,而添加`len(nums)`则表示数组之后没有更多元素的边界。这样的设置可以使得在计算子数组时,可以一致性地处理边界附近的情况,无需编写特殊的边界检查代码,从而提高代码的整洁性和可读性。
🦆
题解中提到的方法在计算可能的子数组位置时使用的是两侧奇数位置差的乘积,请问这种计算方式的原理是什么?
题解中使用两侧奇数位置差的乘积来计算可能的子数组位置的原理基于组合计数。给定一个包含恰好`k`个奇数的子数组,子数组的起始位置可以从前一个奇数后的任何一个位置开始(直到当前奇数的位置),结束位置则可以从最后一个奇数后的任何位置开始(直到下一个奇数之前)。因此,计算两侧奇数位置的差,实际上是计算起始位置和结束位置的选择范围。将这两个选择范围的长度相乘,就给出了所有可能的子数组的数量。
🦆
为什么题解在遍历数组时只记录奇数的索引,而不是直接在遍历时就计算子数组的数量?
题解中选择先记录奇数的索引,而不是在遍历时直接计算子数组的数量,是因为这种方法更加灵活和高效。首先,一次遍历记录奇数索引为后续计算提供了必要的信息,并且避免了多次重复检查每个元素是否为奇数。其次,有了奇数索引的列表后,在后续处理中可以更容易地通过索引间的距离计算出满足条件的子数组数量,这种方式比直接在遍历中计算要直观且易于理解处理。
🦆
题解算法是否需要对输入数组`nums`的长度或者数组中元素的值进行特殊处理或者验证?
题解算法在处理前不需要对数组`nums`的长度或元素的值进行特殊处理或验证。算法本身已经通过添加-1和`len(nums)`处理了空数组或较短数组的情况。然而,需要注意的是,如果`k`大于实际奇数数量,那么在实际应用中我们应返回0,因为不可能有子数组包含比实际更多的奇数。此外,对于数组中元素的值,由于只关心元素是否为奇数,因此不需要对具体的数值做额外处理。

相关问题