Hard

题目描述

给你一个整数数组 nums 和两个整数 cost1cost2。你可以执行以下操作任意次数:

  • 选择数组 nums 中的一个下标 i,将 nums[i] 增加 1,开销为 cost1
  • 选择数组 nums 中两个不同的下标 ij,将 nums[i]nums[j] 都增加 1,开销为 cost2

返回使数组中所有元素相等所需的最小开销。

由于答案可能很大,请返回答案对 10^9 + 7 取模的结果。

示例 1:

输入:nums = [4,1], cost1 = 5, cost2 = 2
输出:15
解释:
可以执行以下操作使值相等:
- 将 nums[1] 增加 1,开销为 5。nums 变为 [4,2]。
- 将 nums[1] 增加 1,开销为 5。nums 变为 [4,3]。
- 将 nums[1] 增加 1,开销为 5。nums 变为 [4,4]。
总开销为 15。

示例 2:

输入:nums = [2,3,3,3,5], cost1 = 2, cost2 = 1
输出:6

示例 3:

输入:nums = [3,5,3], cost1 = 1, cost2 = 3
输出:4

约束条件:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^6
  • 1 <= cost1 <= 10^6
  • 1 <= cost2 <= 10^6

解题思路

这道题的关键在于确定最终的目标值和选择最优的操作策略。

核心思路分析:

  1. 目标值选择:所有元素最终都要变成某个值 target,显然 target 不会小于原数组的最大值。我们需要枚举可能的目标值。

  2. 操作策略

    • 如果 cost2 >= 2 * cost1,那么使用两次操作1总是比使用一次操作2更便宜,所以只用操作1
    • 否则,我们应该尽可能多地使用操作2(同时增加两个元素)
  3. 贪心策略:当 cost2 < 2 * cost1 时,我们优先配对较小的元素使用操作2。但需要特别处理最小元素,因为它可能需要增加很多,导致无法完全配对。

  4. 目标值枚举:我们需要枚举从 max(nums) 开始的若干个可能的目标值。关键观察是,当目标值足够大时,所有增量都可以通过操作2完成(除了可能剩余的一个),此时成本变化趋于线性。

算法流程:

  • 如果 cost2 >= 2 * cost1,直接用操作1,成本为 sum(target - nums[i]) * cost1
  • 否则,对于每个可能的目标值,计算使用操作2的最优分配,找出最小成本

代码实现

class Solution {
public:
    int minCostToEqualizeArray(vector<int>& nums, int cost1, int cost2) {
        const int MOD = 1e9 + 7;
        int n = nums.size();
        if (n == 1) return 0;
        
        int maxVal = *max_element(nums.begin(), nums.end());
        long long totalDiff = 0;
        for (int num : nums) {
            totalDiff += maxVal - num;
        }
        
        // 如果cost2太大,直接用cost1
        if (cost2 >= 2LL * cost1) {
            return (totalDiff * cost1) % MOD;
        }
        
        long long minCost = LLONG_MAX;
        
        // 枚举目标值
        for (int target = maxVal; target <= maxVal + n; target++) {
            long long total = 0, maxDiff = 0;
            for (int num : nums) {
                long long diff = target - num;
                total += diff;
                maxDiff = max(maxDiff, diff);
            }
            
            long long cost;
            if (maxDiff * 2 <= total) {
                // 可以完全配对
                cost = (total / 2) * cost2 + (total % 2) * cost1;
            } else {
                // 最大差值太大,需要单独处理
                long long pairs = total - maxDiff;
                long long singles = maxDiff - pairs;
                cost = pairs * cost2 + singles * cost1;
            }
            
            minCost = min(minCost, cost);
        }
        
        return minCost % MOD;
    }
};
class Solution:
    def minCostToEqualizeArray(self, nums: List[int], cost1: int, cost2: int) -> int:
        MOD = 10**9 + 7
        n = len(nums)
        if n == 1:
            return 0
        
        max_val = max(nums)
        total_diff = sum(max_val - num for num in nums)
        
        # 如果cost2太大,直接用cost1
        if cost2 >= 2 * cost1:
            return (total_diff * cost1) % MOD
        
        min_cost = float('inf')
        
        # 枚举目标值
        for target in range(max_val, max_val + n + 1):
            total = 0
            max_diff = 0
            for num in nums:
                diff = target - num
                total += diff
                max_diff = max(max_diff, diff)
            
            if max_diff * 2 <= total:
                # 可以完全配对
                cost = (total // 2) * cost2 + (total % 2) * cost1
            else:
                # 最大差值太大,需要单独处理
                pairs = total - max_diff
                singles = max_diff - pairs
                cost = pairs * cost2 + singles * cost1
            
            min_cost = min(min_cost, cost)
        
        return min_cost % MOD
public class Solution {
    public int MinCostToEqualizeArray(int[] nums, int cost1, int cost2) {
        const int MOD = 1000000007;
        int n = nums.Length;
        if (n == 1) return 0;
        
        int maxVal = nums.Max();
        long totalDiff = nums.Sum(num => (long)(maxVal - num));
        
        // 如果cost2太大,直接用cost1
        if (cost2 >= 2L * cost1) {
            return (int)((totalDiff * cost1) % MOD);
        }
        
        long minCost = long.MaxValue;
        
        // 枚举目标值
        for (int target = maxVal; target <= maxVal + n; target++) {
            long total = 0, maxDiff = 0;
            foreach (int num in nums) {
                long diff = target - num;
                total += diff;
                maxDiff = Math.Max(maxDiff, diff);
            }
            
            long cost;
            if (maxDiff * 2 <= total) {
                // 可以完全配对
                cost = (total / 2) * cost2 + (total % 2) * cost1;
            } else {
                // 最大差值太大,需要单独处理
                long pairs = total - maxDiff;
                long singles = maxDiff - pairs;
                cost = pairs * cost2 + singles * cost1;
            }
            
            minCost = Math.Min(minCost, cost);
        }
        
        return (int)(minCost % MOD);
    }
}
var minCostToEqualizeArray = function(nums, cost1, cost2) {
    const MOD = 1000000007;
    const n = nums.length;
    const max = Math.max(...nums);
    const min = Math.min(...nums);
    
    if (n === 1) return 0;
    
    let minCost = Infinity;
    
    // Try different target values
    for (let target = max; target <= max + n; target++) {
        let totalNeeded = 0;
        let maxNeeded = 0;
        
        for (let num of nums) {
            const needed = Math.max(0, target - num);
            totalNeeded += needed;
            maxNeeded = Math.max(maxNeeded, needed);
        }
        
        if (totalNeeded === 0) {
            minCost = 0;
            break;
        }
        
        let cost = Infinity;
        
        // Case 1: Use only operation 1
        cost = Math.min(cost, (totalNeeded % MOD) * cost1);
        
        // Case 2: Use operation 2 as much as possible
        if (cost2 < 2 * cost1) {
            const otherNeeded = totalNeeded - maxNeeded;
            
            if (maxNeeded <= otherNeeded) {
                // Can pair everything
                const pairs = Math.floor(totalNeeded / 2);
                const remaining = totalNeeded % 2;
                cost = Math.min(cost, pairs * cost2 + remaining * cost1);
            } else {
                // maxNeeded > otherNeeded
                const excess = maxNeeded - otherNeeded;
                
                // Use operation 2 for otherNeeded pairs, operation 1 for excess
                cost = Math.min(cost, otherNeeded * cost2 + excess * cost1);
                
                // Alternative: balance by using more operation 1 on others
                if (excess % 2 === 0) {
                    const extraOp1 = excess;
                    const newMaxNeeded = maxNeeded;
                    const newOtherNeeded = otherNeeded + extraOp1;
                    const newTotal = newMaxNeeded + newOtherNeeded;
                    const pairs = Math.floor(newTotal / 2);
                    const remaining = newTotal % 2;
                    cost = Math.min(cost, pairs * cost2 + remaining * cost1 + extraOp1 * cost1);
                } else {
                    const extraOp1 = excess + 1;
                    const newMaxNeeded = maxNeeded;
                    const newOtherNeeded = otherNeeded + extraOp1;
                    const newTotal = newMaxNeeded + newOtherNeeded;
                    const pairs = Math.floor(newTotal / 2);
                    const remaining = newTotal % 2;
                    cost = Math.min(cost, pairs * cost2 + remaining * cost1 + extraOp1 * cost1);
                }
            }
        }
        
        minCost = Math.min(minCost, cost);
    }
    
    return minCost % MOD;
};

复杂度分析

复杂度类型分析
时间复杂度O(n²) - 枚举O(n)个目标值,每次计算需要O(n)时间
空间复杂度O(1) - 只使用常数额外空间