给你一个下标从 0 开始的整数数组 nums
。现有一个长度等于 nums.length
的数组 arr
。对于满足 nums[j] == nums[i]
且 j != i
的所有 j
,arr[i]
等于所有 |i - j|
之和。如果不存在这样的 j
,则令 arr[i]
等于 0
。
返回数组 arr
。
示例 1:
输入:nums = [1,3,1,1,2] 输出:[5,0,3,4,0] 解释: i = 0 ,nums[0] == nums[2] 且 nums[0] == nums[3] 。因此,arr[0] = |0 - 2| + |0 - 3| = 5 。 i = 1 ,arr[1] = 0 因为不存在值等于 3 的其他下标。 i = 2 ,nums[2] == nums[0] 且 nums[2] == nums[3] 。因此,arr[2] = |2 - 0| + |2 - 3| = 3 。 i = 3 ,nums[3] == nums[0] 且 nums[3] == nums[2] 。因此,arr[3] = |3 - 0| + |3 - 2| = 4 。 i = 4 ,arr[4] = 0 因为不存在值等于 2 的其他下标。
示例 2:
输入:nums = [0,5,3] 输出:[0,0,0] 解释:因为 nums 中的元素互不相同,对于所有 i ,都有 arr[i] = 0 。
提示:
1 <= nums.length <= 10^5
0 <= nums[i] <= 10^9
提示 1
Can we use the prefix sum here?
提示 2
For each number x, collect all the indices where x occurs, and calculate the prefix sum of the array.
提示 3
For each occurrence of x, the indices to the right will be regular subtraction while the indices to the left will be reversed subtraction.
解法1:前缀和 + 距离和 + 哈希表
若此题难以理解,可以先做类似题型:LeetCode 1685. 有序数组中差绝对值之和-CSDN博客
题目描述
想象一下,你手中有一串编号的珠子,每个珠子上都刻有一个数字,这些数字可能相同也可能不同。现在,我们要为每个珠子计算一个特殊的分数,这个分数是它与所有刻有相同数字的其他珠子之间的距离之和。如果某个珠子是独一无二的,即没有其他珠子与它刻有相同的数字,那么它的分数就是0。
算法思路
这个问题的关键在于如何高效地计算每个珠子与其他珠子的距离之和。我们可以采用以下步骤:
-
分组:首先,我们将所有刻有相同数字的珠子分为一组。这可以通过使用一个哈希表来实现,其中键是珠子上的数字,值是包含所有对应珠子编号的列表。
-
前缀和:对于每组珠子,我们计算一个前缀和数组,这个数组的第i个元素表示从组的第一个珠子到第i个珠子的编号之和。
-
计算距离和:对于组内的每个珠子,我们使用前缀和来快速计算它与其他珠子的距离和。有一个的公式,可以避免我们对每个珠子都进行遍历:
- 对于组内的第i个珠子,它左边有i-1个珠子,右边有m-1- i个珠子(其中m是组内珠子的总数)。
- 它与左边每个珠子的距离和可以通过 index * i - presum[i] 来计算。
- 它与右边每个珠子的距离和可以通过 index * (m-1-i) + presum[m] - presum[i +1] 来计算。
-
填充结果数组:最后,我们将计算得到的距离和填充到结果数组中,对应每个珠子的分数。
Java版:
class Solution {
public long[] distance(int[] nums) {
int n = nums.length;
Map<Integer, List<Integer>> groups = new HashMap<>();
for (int i = 0; i < n; i++) {
if (!groups.containsKey(nums[i])) {
groups.put(nums[i], new ArrayList<>());
}
groups.get(nums[i]).add(i);
}
long[] arr = new long[n];
for (List<Integer> indices : groups.values()) {
int m = indices.size();
long[] presum = new long[m + 1];
for (int i = 0; i < m; i++) {
presum[i + 1] = presum[i] + indices.get(i);
}
for (int i = 0; i < m; i++) {
int index = indices.get(i);
// long left = index * i - presum[i];
// long right = presum[m] - presum[i + 1] - index * (m - 1 - i)
arr[index] = (long) index * (2 * i + 1 - m) - presum[i] + presum[m] - presum[i + 1];
}
}
return arr;
}
}
Python3版:
class Solution:
def distance(self, nums: List[int]) -> List[int]:
n = len(nums)
groups = dict()
for i in range(n):
if nums[i] not in groups:
groups[nums[i]] = []
groups[nums[i]].append(i)
arr = [0] * n
for indices in groups.values():
m = len(indices)
presum = [0] * (m + 1)
for i in range(m):
presum[i + 1] = presum[i] + indices[i]
for i in range(m):
index = indices[i]
arr[index] = index * (2 * i + 1 - m) - presum[i] + presum[m] - presum[i + 1]
return arr
复杂度分析
- 时间复杂度: O(n),其中 n 为数组 nums 的长度。
-
分组:遍历整个数组
nums
,将相同数字的索引存储在哈希表中。这一步的时间复杂度是O(n),其中n是数组nums
的长度。 -
计算前缀和:对于哈希表中的每个键(即数组中的不同数字),我们计算对应索引列表的前缀和。最坏情况下,如果数组中所有元素都相同,这一步的时间复杂度是O(k),其中k是数组中不同数字的数量。但在平均情况下,k远小于n,因此这一步的时间复杂度可以认为是O(n)。
-
计算距离和:对于每个索引,我们计算它与其他索引的距离和。这一步涉及到访问前缀和数组和一些基本的算术运算。对于每个组,这一步的时间复杂度是O(m),其中m是该组中元素的数量。由于我们对所有组都执行了这个操作,总的时间复杂度是O(n)。
综上所述,整个算法的时间复杂度主要由三个O(n)的操作组成,因此最终的时间复杂度是O(n)。
- 空间复杂度: O(n)。
-
哈希表:我们使用了一个哈希表来存储每个数字及其索引的映射。在最坏情况下,如果数组中的所有元素都不相同,这个哈希表将包含n个键值对,因此空间复杂度是O(n)。
-
前缀和数组:对于每个组,我们创建了一个前缀和数组。最坏情况下,如果一个组包含n个元素,前缀和数组的长度将是n+1。但由于我们为每个不同的数字都创建了一个前缀和数组,总的空间复杂度仍然是O(n)。
-
结果数组:我们创建了一个长度为n的结果数组来存储最终的距离和。这个数组的空间复杂度是O(n)。
因此,整个算法的空间复杂度主要由哈希表和结果数组决定,总的空间复杂度是O(n)。