leetcode 2333. 最小差值平方和 中等

给你两个下标从 0 开始的整数数组 nums1 和 nums2 ,长度为 n 。

数组 nums1 和 nums2 的 差值平方和 定义为所有满足 0 <= i < n 的 (nums1[i] - nums2[i])2 之和。

同时给你两个正整数 k1 和 k2 。你可以将 nums1 中的任意元素 +1 或者 -1 至多 k1 次。类似的,你可以将 nums2 中的任意元素 +1 或者 -1 至多 k2 次。

请你返回修改数组nums1至多k1 次且修改数组nums2 至多 k2次后的最小 差值平方和 。

注意: 你可以将数组中的元素变成 负 整数。

示例 1:

复制代码
输入:nums1 = [1,2,3,4], nums2 = [2,10,20,19], k1 = 0, k2 = 0
输出:579
解释:nums1 和 nums2 中的元素不能修改,因为 k1 = 0 和 k2 = 0 。
差值平方和为:(1 - 2)2 + (2 - 10)2 + (3 - 20)2 + (4 - 19)2 = 579 。

示例 2:

复制代码
输入:nums1 = [1,4,10,12], nums2 = [5,8,6,9], k1 = 1, k2 = 1
输出:43
解释:一种得到最小差值平方和的方式为:
- 将 nums1[0] 增加一次。
- 将 nums2[2] 增加一次。
最小差值平方和为:
(2 - 5)2 + (4 - 8)2 + (10 - 7)2 + (12 - 9)2 = 43 。
注意,也有其他方式可以得到最小差值平方和,但没有得到比 43 更小答案的方案。

提示:

  • n == nums1.length == nums2.length
  • 1 <= n <= 10^5
  • 0 <= nums1[i], nums2[i] <= 10^5
  • 0 <= k1, k2 <= 10^9

分析:题目要求通过最多 k1 + k2 次操作,使两个数组对应元素差值的平方和最小。由于每次操作都可以使 nums1 或 nums2 中的某个元素增加或减少 1,因此无论操作哪个数组,都相当于将对应位置的绝对差值减少 1。可以将两种操作合并考虑,令 k = k1 + k2,并构造差值数组 diff,其中 diffi = abs(nums1i - nums2i)。

接下来需要考虑如何分配这 k 次操作。

对于一个差值 x,将其减少 1,平方和就会减少 x² - (x - 1)² = 2x - 1。可以发现,差值越大,减少 1 所带来的收益越大。因此,应该优先减小较大的差值。

基于这一贪心策略,首先将 diff 按照从大到小的顺序排序,并在数组末尾添加一个 0,方便处理所有差值都被减小到 0 的情况。随后遍历排序后的数组,使用变量 val 表示当前需要处理的差值大小,cnt 表示已经处理的差值数量,初始时 val = diff0,cnt = 1。

对于当前遍历到的 diffi,希望将前 cnt 个差值统一减小到 diffi,所需要的操作次数为 temp = (val - diffi) × cnt。根据剩余操作次数 k,可以分为以下两种情况:

情况一:k >= temp。说明剩余操作次数足够将前 cnt 个差值全部减小到 diffi。此时直接消耗 temp 次操作,更新 k = k - temp,并令 val = diffi,cnt 加 1,继续处理下一个差值。

情况二:k < temp。说明剩余操作次数不足以将前 cnt 个差值全部减小到 diffi,此时需要将剩余操作尽可能均匀地分配给这 cnt 个差值。令 q = k / cnt,r = k % cnt。其中,q 表示每个差值至少可以减少的次数,r 表示剩余的操作次数。

经过操作后:

  • 有 cnt - r 个差值变为 hi = val - q。

  • 有 r 个差值变为 hi - 1。

  • 从下标 i 开始的其余差值保持不变。

此时所有操作均已分配完毕,只需要计算上述差值的平方和,再加上尚未处理的其余差值的平方和,即可得到最终答案,并结束遍历。

如果遍历结束后仍有剩余操作次数,说明所有差值都已经减小到 0,此时最小平方和为 0。

cpp 复制代码
class Solution {
public:
    static bool cmp(const int a,const int b)
    {
        return a>b?true:false;
    }
    long long minSumSquareDiff(vector<int>& nums1, vector<int>& nums2, int k1, int k2) {
        int n=nums1.size();
        vector<int>diff;
        for(int i=0;i<n;++i)
            diff.push_back(abs(nums1[i]-nums2[i]));
        sort(diff.begin(),diff.end(),cmp);
        diff.push_back(0);
        long long k=1LL*k1+1LL*k2,ans=0;
        int cnt=1,val=diff[0];
        for(int i=1;i<=n;++i)
        {
            long long temp=val-diff[i];
            if(k>=temp*cnt)k-=temp*cnt,val=diff[i],cnt++;
            else
            {
                long long q=k/cnt,r=k%cnt,hi=diff[i-1]-q;
                ans+=1LL*hi*hi*(i-r);
                ans+=1LL*(hi-1)*(hi-1)*r;
                for(int j=i;j<n;++j)
                    ans+=1LL*diff[j]*diff[j];
                break;
            }
        }
        return ans;
    }
};
相关推荐
Nil2085 小时前
leetcode 300最长递增子序列
算法·leetcode·职场和发展
yi0117 小时前
LeetCode 424. 替换后的最长重复字符|不定长滑动窗口:如何判断窗口是否合法?
笔记·python·算法·leetcode·双指针·滑动窗口
All for pursuit.8 小时前
【动态规划-11】279.完全平方数
数据结构·c++·算法·leetcode·动态规划
yi0119 小时前
LeetCode 3. 无重复字符的最长子串|不定长滑动窗口:什么时候更新最大值?
笔记·python·算法·leetcode·双指针·滑动窗口
y1su9 小时前
Leetcode 单调栈模板
java·开发语言·数据结构·算法·leetcode
longlongzihan20 小时前
链表找环:Floyd 判圈算法(LeetCode 141 & 142 )
c++·算法·leetcode·链表
yi01121 小时前
LeetCode 643. 子数组最大平均数 I|从暴力枚举到滑动窗口
人工智能·笔记·python·算法·leetcode·滑动窗口
java_nnnn1 天前
LeetCode刷题双指针(有效三角形的个数+三数之和)Java
算法·leetcode·职场和发展
All for pursuit.1 天前
【动态规划-6】96.不同的二叉搜索树
数据结构·c++·算法·leetcode·动态规划