Leetcode每日一题 —— 2615. 等值距离和

2026-04-29 11:052阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐
问题描述:
力扣 LeetCode

2615. 等值距离和 - 力扣(LeetCode)

2615. 等值距离和 - 给你一个下标从 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]...


思路

也就是对于每个下标 i 对应的数字 nums[i],要找到与其相等的所有其他数字 nums[j],累加 abs(i-j)

题目输入规模可能很大,时间复杂度要控制在 O(n\log n) 以下。

咱们在意的是某个数字 nums[i] 在数组内出现的所有下标,但如果直接按数字分组维护列表,扫描所耗费的时间从总体上看来是难以接受的。

既要涉及求和,而且还需要尽量是线性时间复杂度,能猜到可能能用到前缀和思想。

对于一个下标 i,我可以计算前缀中所有 i-j 之和,再计算其后缀中所有 j-i 之和,这个位置的前缀加后缀就是最终结果 arr[i] 了。

怎么生成这样的前缀和呢?从计算某个位置的前缀 prefix[i] 来思考:

比如 [1,3,1,1,1,2] 这个数组,若要计算 prefix[4]

  1. 首先 nums[4]=1,要从上一个前缀和处转移过来,我们可以用哈希表存上一个 1 出现的地方:3
  2. prefix[4] 可转移自 prefix[3],但是怎么从 prefix[3] 计算出 prefix[4]
  3. prefix[3] 等于 (3-0) + (3-2)。而 prefix[4] 则等于 (4-0) + (4-2) + (4-3),如果把 prefix[3] 的部分代入进去可以发现是 prefix[3] + (4-3) + (4-3) + (4-3),也就是在 prefix[3] 的基础上重复累加离上一个 1 出现的位置的距离多次。很显然这个次数就是 1 之前出现的次数。

因此在递推的时候可以用哈希表维护 nums[i] 上一次出现的下标以及 nums[i] 之前出现过的次数。递推得到前缀和和后缀和即可。


代码

写出来感觉还是挺直白的,应该还有很大可优化空间。

class Solution { public: vector<long long> distance(vector<int>& nums) { int n=nums.size(); vector<long long> res(n,0); // 也就是要找到所有相等元素的下标距离的和 // 输入规模比较大,可以分组做前缀和后缀和 vector<long long> prefix(n,0); // 某个数字 -> (其最后一次出现的下标, 这个数字出现的次数) unordered_map<int,pair<int,int>> iMap; for(int i=0;i<n;i++){ int appearCnt=0; if(iMap.count(nums[i])>0){ // 之前这个数字出现过 // 比如 1 ... 1 1 // 第三次出现的 1 的和前缀相当于 [前两个 1 的距离] + [第三次 1 和第二次 1 的距离] * [前面 1 出现的次数 = 2] int prevIdx=iMap[nums[i]].first; appearCnt=iMap[nums[i]].second; prefix[i]=prefix[prevIdx]+(i-prevIdx)*appearCnt; // cout<<"prefix["<<i<<"]="<<prefix[i]<<endl; } iMap[nums[i]]=make_pair(i,appearCnt+1); } // 接着边生成后缀边得到结果 vector<long long> postfix(n,0); iMap.clear(); for(int i=n-1;i>=0;i--){ int appearCnt=0; if(iMap.count(nums[i])>0){ int prevIdx=iMap[nums[i]].first; appearCnt=iMap[nums[i]].second; postfix[i]=postfix[prevIdx]+(prevIdx-i)*appearCnt; } res[i]=prefix[i]+postfix[i]; iMap[nums[i]]=make_pair(i,appearCnt+1); } return res; } };


继续复习一些题

139. 单词拆分 - 力扣(LeetCode)

  • 注意内层递推的写法,不能在没有判断的情况下直接继承前一个状态,因为在枚举单词,后面的单词如果不匹配后缀,可能会覆盖掉可能的结果。

func wordBreak(s string, wordDict []string) bool { // 一维动态规划题 // dp[i] 定义为 i 之前的 s[0...i-1] 子串是否能被 wordDict 拼出 // 这类动态规划题好像很适合让下标从 1 开始,把 0 位置作为哨兵,方便边界处理 n:=len(s) dp:=make([]bool,n+1) // 显然 dp[0]=true dp[0]=true for i:=1;i<=n;i++{ // 枚举每个单词的出现情况 for _,w:=range wordDict{ if i<len(w){ // 单词过长,跳过 continue; } // 否则看是否能当前单词拼接 // 👇 错误写法,前面单词能匹配后缀,但如果后面枚举的单词不匹配,会覆盖掉! // if s[i-len(w):i]==w { // dp[i]=dp[i-len(w)] // } if s[i-len(w):i]==w && dp[i-len(w)] { dp[i]=true } } } return dp[n] }

网友解答:
--【壹】--:

class Solution: def distance(self, nums: List[int]) -> List[int]: n = len(nums) ans = [0] * n dic = {} for i, x in enumerate(nums): if x not in dic: dic[x] = [] dic[x].append(i) for x in dic: idx = dic[x] m = len(idx) tot = 0 for i in range(m): tot += idx[i] - idx[0] ans[idx[0]] = tot for i in range(1, m): tot += (2 * i - m) * (idx[i] - idx[i - 1]) ans[idx[i]] = tot return ans


--【贰】--:

buildIM :: [Int] -> IM.IntMap ([Int], Int, Int) buildIM = let update !i (is, !s, !c) = let !s' = s + i; !c' = c + 1 in (i : is, s', c') updateMap m (i, x) = IM.alter (Just . maybe (one i, i, 1) (update i)) x m in foldl' updateMap mempty . zip [0 ..] calcDist :: ([Int], Int, Int) -> [(Int, Int)] calcDist ([], _, _) = [] calcDist (nums', s, n) = let nums = reverse nums' initVal = s - n * fromMaybe 0 (viaNonEmpty head nums) diffs = zipWith (-) (fromMaybe [] (viaNonEmpty tail nums)) nums multipliers = [i - n | i <- [2, 4 ..]] in zip nums (scanl' (+) initVal (zipWith (*) multipliers diffs)) distances :: [Int] -> [Int] distances nums = let im = buildIM nums n = length nums distList = concatMap calcDist (IM.elems im) arr = Arr.accumArray (const id) 0 (0, n - 1) distList in Arr.elems arr

手动做了一下融合捏。
推的是 v_0 = S - na_0,且 v_k = v_{k-1} + (2k-n)(a_k-a_{k-1}),全用差分也可以推但是写起来太恶心了就算了吧

leftDistances :: [Int] -> [Int] leftDistances = go 0 mempty where go _ _ [] = [] go i m (x : xs) = let (c, s) = IM.findWithDefault (0, 0) x m !c' = c + 1 !s' = s + i !m' = IM.insert x (c', s') m in (c * i - s) : go (i + 1) m' xs distances' :: [Int] -> [Int] distances' nums = zipWith (+) (leftDistances nums) (reverse (leftDistances (reverse nums)))

如果把分组和计算过程手动融合就可以只算左边的距离(什么手写状态机?),再倒过来算一次右边的加上就行


--【叁】--:

简单排序后是个简单数学题,但是我数学不好心态浮躁,一个公式捣鼓了半天也没像到一个巧妙的表达。公式没有化简,意义应该挺直白的. 好在跑的挺快的.小坑在一开始想回避但也没回避到的:存在等值且最大时最后一次循环不更新答案,在最后塞一个不存在的最大值就行了.其他语言有空补.

class Solution { public: vector<long long> distance(vector<int>& nums) { int n = nums.size(); std::vector<std::pair<int,int>> v(n,std::pair<int,int>()); std::vector<long long> res(n,0ll); for(int i = 0; i < n; i++) { v[i] = std::make_pair(nums[i],i); } std::sort(v.begin(),v.end(),[](const pair<int,int>& a, const pair<int,int>& b) -> bool { if(a.first != b.first) { return a.first < b.first; } return a.second < b.second; }); v.push_back(std::make_pair(1e6,n + 1)); long long sum = v[0].second; for(int i = 1,j = 0; i < n + 1; i++) { if(v[i].first != v[i - 1].first) { int l = j,r = i - 1; long long presum = 0ll; while(j < i) { long long base = v[j].second; res[v[j].second] = sum - base * (r - j + 1) + base * (j - l) - presum * 2; presum += base; j++; } sum = 0; } sum += v[i].second; } return res; } };


go 的类型很严格噢, 不统一就会错.
然后它的sort 参数好像是下标,不让动

type Num struct { val int id int } func distance(nums []int) []int64 { n := len(nums) res := make([]int64,n) v := make([]Num,n) for i := 0; i < n; i++ { v[i].val = nums[i] v[i].id = i } sort.Slice(v, func(i, j int) bool { if v[i].val != v[j].val { return v[i].val < v[j].val } return v[i].id < v[j].id }) v = append(v,Num{1e6,n + 1}) var sum int64 = int64(v[0].id) for i, j := 1, 0; i < n + 1; i++ { if v[i].val != v[i - 1].val { l, r := j, i - 1 var psum int64 = 0 for j < i { var base int64 = int64(v[j].id) res[v[j].id] = sum - base * int64(r - j + 1) + base * int64(j - l) - 2 * psum psum += base j++ } sum = 0 } sum += int64(v[i].id) } return res }


python 用下数组得了.但是跑的挺慢的, 语法不熟.

class Solution: def distance(self, nums: List[int]) -> List[int]: n = len(nums) v = [[0,0] for _ in range(n)] res = [0 for _ in range(n)] for i in range(n): v[i][0],v[i][1] = nums[i],i v.sort(key = lambda x: (x[0],x[1])) v.append([1e6,n + 1]) sum = 0 j = 0 for i in range(n + 1): if v[i][0] != v[i - 1][0]: l, r = j, i - 1 psum = 0 while j < i: base = v[j][1] res[v[j][1]] = sum - base * (r - j + 1) + base * (j - l) - 2 * psum j += 1 psum += base sum = 0 sum += v[i][1] return res