找出不同元素数目差数组

标签: 数组 哈希表

难度: Easy

给你一个下标从 0 开始的数组 nums ,数组长度为 n

nums不同元素数目差 数组可以用一个长度为 n 的数组 diff 表示,其中 diff[i] 等于前缀 nums[0, ..., i] 中不同元素的数目 减去 后缀 nums[i + 1, ..., n - 1] 中不同元素的数目。

返回 nums不同元素数目差 数组。

注意 nums[i, ..., j] 表示 nums 的一个从下标 i 开始到下标 j 结束的子数组(包含下标 ij 对应元素)。特别需要说明的是,如果 i > j ,则 nums[i, ..., j] 表示一个空子数组。

示例 1:

输入:nums = [1,2,3,4,5]
输出:[-3,-1,1,3,5]
解释:
对于 i = 0,前缀中有 1 个不同的元素,而在后缀中有 4 个不同的元素。因此,diff[0] = 1 - 4 = -3 。
对于 i = 1,前缀中有 2 个不同的元素,而在后缀中有 3 个不同的元素。因此,diff[1] = 2 - 3 = -1 。
对于 i = 2,前缀中有 3 个不同的元素,而在后缀中有 2 个不同的元素。因此,diff[2] = 3 - 2 = 1 。
对于 i = 3,前缀中有 4 个不同的元素,而在后缀中有 1 个不同的元素。因此,diff[3] = 4 - 1 = 3 。
对于 i = 4,前缀中有 5 个不同的元素,而在后缀中有 0 个不同的元素。因此,diff[4] = 5 - 0 = 5 。

示例 2:

输入:nums = [3,2,3,4,2]
输出:[-2,-1,0,2,3]
解释:
对于 i = 0,前缀中有 1 个不同的元素,而在后缀中有 3 个不同的元素。因此,diff[0] = 1 - 3 = -2 。
对于 i = 1,前缀中有 2 个不同的元素,而在后缀中有 3 个不同的元素。因此,diff[1] = 2 - 3 = -1 。
对于 i = 2,前缀中有 2 个不同的元素,而在后缀中有 2 个不同的元素。因此,diff[2] = 2 - 2 = 0 。
对于 i = 3,前缀中有 3 个不同的元素,而在后缀中有 1 个不同的元素。因此,diff[3] = 3 - 1 = 2 。
对于 i = 4,前缀中有 3 个不同的元素,而在后缀中有 0 个不同的元素。因此,diff[4] = 3 - 0 = 3 。 

提示:

  • 1 <= n == nums.length <= 50
  • 1 <= nums[i] <= 50

Submission

运行时间: 29 ms

内存: 16.0 MB

class Solution:
    def distinctDifferenceArray(self, nums: List[int]) -> List[int]:
        st = set()
        sufCnt = [0] * (len(nums) + 1)
        for i in range(len(nums) - 1, 0, -1):
            st.add(nums[i])
            sufCnt[i] = len(st)
        res = []
        st.clear()
        for i in range(len(nums)):
            st.add(nums[i])
            res.append(len(st) - sufCnt[i + 1])
        return res

Explain

该题解的核心思想是利用两次遍历的方法来分别计算前缀和后缀中不同元素的数量。首先,从数组的末尾开始向前遍历,使用一个集合(st)来存储遇到的元素,同时利用一个数组(sufCnt)记录从当前位置到数组末尾的不同元素的数量。在第二次遍历中,从数组的开始向后遍历,再次使用集合(st)来存储遇到的元素,并计算当前位置的前缀中不同元素的数量。然后利用前缀中的不同元素数量减去后缀中的不同元素数量,得到结果数组(res)的当前位置的值。

时间复杂度: O(n)

空间复杂度: O(n)

class Solution:
    def distinctDifferenceArray(self, nums: List[int]) -> List[int]:
        st = set()  # 用于存储遍历过程中的不同元素
        sufCnt = [0] * (len(nums) + 1)  # 存储从当前位置到末尾的不同元素数量
        # 从后向前遍历以填充sufCnt
        for i in range(len(nums) - 1, 0, -1):
            st.add(nums[i])
            sufCnt[i] = len(st)  # 更新后缀不同元素数量
        res = []
        st.clear()  # 清空集合,用于前缀不同元素的计算
        # 从前向后遍历,计算每个位置的不同元素数目差
        for i in range(len(nums)):
            st.add(nums[i])
            res.append(len(st) - sufCnt[i + 1])  # 计算前缀和后缀的不同元素数目差
        return res

Explore

是的,这种方法仍然可以正确地计算出每个位置的后缀不同元素数量。集合(st)的特性是自动排除重复元素,因此即使数组nums中存在重复元素,每当添加一个新元素到集合中,集合的大小(即不同元素的数量)只有在该元素是首次出现时才会增加。这保证了在每个步骤中sufCnt数组正确记录了从当前位置到数组末尾的不同元素数量。

清空集合st后重新用于前缀的计算有几个好处。首先,这确保了集合中没有来自后缀计算的残留数据,从而保证前缀计算的准确性。此外,重用同一个集合也节省了额外的内存开销,因为不需要创建另一个集合来执行前缀计算。这样的设计使得算法更加高效和简洁。

sufCnt数组的最后一个元素sufCnt[len(nums)]实际上在计算中没有直接使用,主要用作边界条件处理。设置数组长度为len(nums) + 1是为了简化代码逻辑,避免在计算过程中出现越界错误。当遍历到nums数组的最后一个元素时,sufCnt[len(nums)]提供了一个初始值(通常是0),这样可以避免额外的条件检查来确定数组的边界。

选择len(st) - sufCnt[i + 1]这种形式来计算不同元素数目差是因为这种方式直观且高效地体现了前缀和后缀不同元素数量的差异。这种计算方式简化了思考过程,直接使用集合的大小来表示不同元素的数量,并且利用已经计算好的后缀不同元素数量数组,可以快速得到结果。尽管可以考虑其他计算方式,例如单独计算每个位置的前缀和后缀然后相减,但那将增加算法的复杂度和运行时间。因此,当前的计算方式是在效率和简洁性之间的一个很好的平衡。