Medium

题目描述

给你一个长度为 n0 索引 整数数组 nums

你可以对 nums 执行任意次数(包括零次)的 特殊操作。在一次特殊操作中,你需要按顺序执行以下步骤:

  • 选择范围 [0, n - 1] 内的一个索引 i 和一个正整数 x
  • |nums[i] - x| 加到总代价中。
  • nums[i] 的值更改为 x

回文数 是一个正整数,其数字反转后保持不变。例如,121255265756 是回文数,而 2446235 不是回文数。

如果数组中的所有元素都等于一个整数 y,且 y 是一个小于 10^9 的回文数,则该数组被认为是 等回文数组

返回通过执行任意次数的特殊操作使 nums 成为等回文数组的 最小可能总代价

示例 1:

输入:nums = [1,2,3,4,5]
输出:6
解释:我们可以通过将所有元素更改为 3(回文数)来使数组成为等回文数组。使用 4 次特殊操作将数组更改为 [3,3,3,3,3] 的代价为 |1 - 3| + |2 - 3| + |4 - 3| + |5 - 3| = 6。
可以证明,将所有元素更改为除 3 以外的任何其他回文数都无法以更低的代价实现。

示例 2:

输入:nums = [10,12,13,14,15]
输出:11
解释:我们可以通过将所有元素更改为 11(回文数)来使数组成为等回文数组。使用 5 次特殊操作将数组更改为 [11,11,11,11,11] 的代价为 |10 - 11| + |12 - 11| + |13 - 11| + |14 - 11| + |15 - 11| = 11。
可以证明,将所有元素更改为除 11 以外的任何其他回文数都无法以更低的代价实现。

示例 3:

输入:nums = [22,33,22,33,22]
输出:22
解释:我们可以通过将所有元素更改为 22(回文数)来使数组成为等回文数组。使用 2 次特殊操作将数组更改为 [22,22,22,22,22] 的代价为 |33 - 22| + |33 - 22| = 22。
可以证明,将所有元素更改为除 22 以外的任何其他回文数都无法以更低的代价实现。

约束:

  • 1 <= n <= 10^5
  • 1 <= nums[i] <= 10^9

解题思路

解题思路

这道题的关键在于找到最优的回文数作为目标值。我们可以通过以下步骤来解决:

  1. 中位数特性:为了最小化所有元素到目标值的距离之和,最优的目标值应该接近数组的中位数。这是因为中位数能最小化到所有点的距离之和。

  2. 候选回文数:由于目标值必须是回文数,我们需要找到距离中位数最近的回文数。具体来说,我们需要考虑:

    • 大于等于中位数的最小回文数
    • 小于等于中位数的最大回文数
  3. 生成回文数:我们可以通过数学构造来生成这两个候选回文数:

    • 对于一个数字,我们可以通过镜像其前半部分来构造回文数
    • 如果构造的回文数小于原数,我们需要增加中间部分并重新构造
  4. 计算代价:对于每个候选回文数,计算将所有数组元素变为该值的总代价,取最小值。

算法流程:

  1. 排序数组并找到中位数
  2. 生成两个候选回文数(大于等于和小于等于中位数的)
  3. 计算每个候选的总代价
  4. 返回最小代价

推荐解法:数学构造法,时间复杂度更优。

代码实现

class Solution {
public:
    long long minimumCost(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int n = nums.size();
        int median = nums[n / 2];
        
        // 获取小于等于median的最大回文数
        long long lower = getLowerPalindrome(median);
        // 获取大于等于median的最小回文数
        long long upper = getUpperPalindrome(median);
        
        return min(calculateCost(nums, lower), calculateCost(nums, upper));
    }
    
private:
    long long getLowerPalindrome(long long num) {
        string s = to_string(num);
        int n = s.length();
        
        // 先构造镜像回文数
        for (int i = 0; i < n / 2; i++) {
            s[n - 1 - i] = s[i];
        }
        
        long long palindrome = stoll(s);
        if (palindrome <= num) {
            return palindrome;
        }
        
        // 如果镜像回文数大于原数,需要减小中间部分
        if (n == 1) return num - 1;
        
        // 减小前半部分
        string half = s.substr(0, (n + 1) / 2);
        long long halfNum = stoll(half) - 1;
        if (halfNum == 0) return n == 1 ? 0 : 9;
        
        string newHalf = to_string(halfNum);
        if (newHalf.length() < half.length()) {
            return stoll(string(n - 1, '9'));
        }
        
        string result = newHalf;
        for (int i = n / 2 - 1; i >= 0; i--) {
            result += newHalf[i];
        }
        
        return stoll(result);
    }
    
    long long getUpperPalindrome(long long num) {
        string s = to_string(num);
        int n = s.length();
        
        // 先构造镜像回文数
        for (int i = 0; i < n / 2; i++) {
            s[n - 1 - i] = s[i];
        }
        
        long long palindrome = stoll(s);
        if (palindrome >= num) {
            return palindrome;
        }
        
        // 如果镜像回文数小于原数,需要增大中间部分
        string half = s.substr(0, (n + 1) / 2);
        long long halfNum = stoll(half) + 1;
        string newHalf = to_string(halfNum);
        
        if (newHalf.length() > half.length()) {
            return stoll("1" + string(n - 1, '0') + "1");
        }
        
        string result = newHalf;
        for (int i = n / 2 - 1; i >= 0; i--) {
            result += newHalf[i];
        }
        
        return stoll(result);
    }
    
    long long calculateCost(const vector<int>& nums, long long target) {
        long long cost = 0;
        for (int num : nums) {
            cost += abs(num - target);
        }
        return cost;
    }
};
class Solution:
    def minimumCost(self, nums: List[int]) -> int:
        nums.sort()
        n = len(nums)
        median = nums[n // 2]
        
        # 获取小于等于median的最大回文数
        lower = self.get_lower_palindrome(median)
        # 获取大于等于median的最小回文数
        upper = self.get_upper_palindrome(median)
        
        return min(self.calculate_cost(nums, lower), self.calculate_cost(nums, upper))
    
    def get_lower_palindrome(self, num):
        s = str(num)
        n = len(s)
        
        # 先构造镜像回文数
        s_list = list(s)
        for i in range(n // 2):
            s_list[n - 1 - i] = s_list[i]
        
        palindrome = int(''.join(s_list))
        if palindrome <= num:
            return palindrome
        
        # 如果镜像回文数大于原数,需要减小中间部分
        if n == 1:
            return num - 1
        
        # 减小前半部分
        half = s[:(n + 1) // 2]
        half_num = int(half) - 1
        if half_num == 0:
            return 9 if n == 1 else int('9' * (n - 1))
        
        new_half = str(half_num)
        if len(new_half) < len(half):
            return int('9' * (n - 1))
        
        result = new_half + new_half[n // 2 - 1::-1]
        return int(result)
    
    def get_upper_palindrome(self, num):
        s = str(num)
        n = len(s)
        
        # 先构造镜像回文数
        s_list = list(s)
        for i in range(n // 2):
            s_list[n - 1 - i] = s_list[i]
        
        palindrome = int(''.join(s_list))
        if palindrome >= num:
            return palindrome
        
        # 如果镜像回文数小于原数,需要增大中间部分
        half = s[:(n + 1) // 2]
        half_num = int(half) + 1
        new_half = str(half_num)
        
        if len(new_half) > len(half):
            return int('1' + '0' * (n - 1) + '1')
        
        result = new_half + new_half[n // 2 - 1::-1]
        return int(result)
    
    def calculate_cost(self, nums, target):
        return sum(abs(num - target) for num in nums)
public class Solution {
    public long MinimumCost(int[] nums) {
        Array.Sort(nums);
        int n = nums.Length;
        long median = nums[n / 2];
        
        // 获取小于等于median的最大回文数
        long lower = GetLowerPalindrome(median);
        // 获取大于等于median的最小回文数
        long upper = GetUpperPalindrome(median);
        
        return Math.Min(CalculateCost(nums, lower), CalculateCost(nums, upper));
    }
    
    private long GetLowerPalindrome(long num) {
        string s = num.ToString();
        int n = s.Length;
        
        // 先构造镜像回文数
        char[] chars = s.ToCharArray();
        for (int i = 0; i < n / 2; i++) {
            chars[n - 1 - i] = chars[i];
        }
        
        long palindrome = long.Parse(new string(chars));
        if (palindrome <= num) {
            return palindrome;
        }
        
        // 如果镜像回文数大于原数,需要减小中间部分
        if (n == 1) return num - 1;
        
        // 减小前半部分
        string half = s.Substring(0, (n + 1) / 2);
        long halfNum = long.Parse(half) - 1;
        if (halfNum == 0) return n == 1 ? 0 : long.Parse(new string('9', n - 1));
        
        string newHalf = halfNum.ToString();
        if (newHalf.Length < half.Length) {
            return long.Parse(new string('9', n - 1));
        }
        
        string result = newHalf;
        for (int i = n / 2 - 1; i >= 0; i--) {
            result += newHalf[i];
        }
        
        return long.Parse(result);
    }
    
    private long GetUpperPalindrome(long num) {
        string s = num.ToString();
        int n = s.Length;
        
        // 先构造镜像回文数
        char[] chars = s.ToCharArray();
        for (int i = 0; i < n / 2; i++) {
            chars[n - 1 - i] = chars[i];
        }
        
        long palindrome = long.Parse(new string(chars));
        if (palindrome >= num) {
            return palindrome;
        }
        
        // 如果镜像回文数小于原数,需要增大中间部分
        string half = s.Substring(0, (n + 1) / 2);
        long halfNum = long.Parse(half) + 1;
        string newHalf = halfNum.ToString();
        
        if (newHalf.Length > half.Length) {
            return long.Parse("1" + new string('0', n - 1) + "1");
        }
        
        string result = newHalf;
        for (int i = n / 2 - 1; i >= 0; i--) {
            result += newHalf[i];
        }
        
        return long.Parse(result);
    }
    
    private long CalculateCost(int[] nums, long target) {
        long cost = 0;
        foreach (int num in nums) {
            cost += Math.Abs(num - target);
        }
        return cost;
    }
}
var minimumCost = function(nums) {
    nums.sort((a, b) => a - b);
    const n = nums.length;
    const median = nums[Math.floor(n / 2)];
    
    // 获取小于等于median的最大回文数
    const lower = getLowerPalindrome(median);
    // 获取大于等于median的最小回文数
    const upper = getUpperPalindrome(median);
    
    return Math.min(calculateCost(nums, lower), calculateCost(nums, upper));

复杂度分析

指标复杂度
时间-
空间-

相关题目