Facebook Pixel

2967. Minimum Cost to Make Array Equalindromic

Problem Description

You have an array nums of integers (0-indexed) with length n. You can perform a special move on this array any number of times (including zero times).

A special move consists of:

  1. Choose an index i in the range [0, n - 1] and a positive integer x
  2. Add |nums[i] - x| to the total cost
  3. Change the value of nums[i] to x

A palindromic number is a positive integer that reads the same forwards and backwards. For example:

  • Palindromic: 121, 2552, 65756
  • Not palindromic: 24, 46, 235

An array is called equalindromic if all its elements are equal to the same integer y, where y is a palindromic number less than 10^9.

Your task is to find the minimum total cost to transform nums into an equalindromic array by performing any number of special moves.

In other words, you need to:

  1. Choose a palindromic number y (less than 10^9)
  2. Change all elements in nums to equal y
  3. Minimize the sum of |nums[i] - y| for all indices i

The problem essentially asks you to find the optimal palindromic number to which all array elements should be changed, such that the total cost (sum of absolute differences) is minimized.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that to minimize the total cost of changing all elements to a single value, we should choose a value close to the median of the array. This is a well-known property: the sum of absolute differences Σ|nums[i] - y| is minimized when y is the median of the array.

However, we have an additional constraint - the target value must be a palindromic number. So we can't just use the median directly; we need to find the palindromic number that's closest to the median.

Since there are infinitely many palindromic numbers up to 10^9, we need an efficient way to generate and search through them. The clever observation is that palindromic numbers have a special structure - they're symmetric. We can generate all palindromes by:

  1. Taking a number like 123
  2. Creating even-length palindrome: 123123321 (append reverse)
  3. Creating odd-length palindrome: 12312321 (append reverse without last digit)

Since palindromes up to 10^9 have at most 9 digits, we only need to enumerate numbers up to 10^5 (roughly half the digits) to generate all possible palindromes.

The solution strategy becomes:

  1. Pre-generate all palindromic numbers less than 10^9 and sort them
  2. Find the median of the input array nums
  3. Use binary search to find palindromic numbers near the median
  4. Check a small window of palindromes around the median (typically the closest 2-3 palindromes)
  5. Calculate the cost for each candidate and return the minimum

The reason we check multiple palindromes near the median (not just the closest one) is that the discrete nature of palindromes means the optimal choice might be slightly off from the theoretical continuous median.

Learn more about Greedy, Math, Binary Search and Sorting patterns.

Solution Approach

The solution consists of two main parts: preprocessing palindromes and finding the minimum cost.

Step 1: Generate All Palindromes

We generate all palindromic numbers less than 10^9 by iterating through numbers from 1 to 10^5:

ps = []
for i in range(1, 10**5 + 1):
    s = str(i)
    t1 = s[::-1]  # Full reverse for even-length palindrome
    t2 = s[:-1][::-1]  # Reverse without last digit for odd-length palindrome
    ps.append(int(s + t1))  # Even-length: 123 → 123321
    ps.append(int(s + t2))  # Odd-length: 123 → 12321
ps.sort()

This generates approximately 2 × 10^5 palindromes. We sort them to enable binary search later.

Step 2: Find the Optimal Palindrome

First, we define a cost function that calculates the total cost to change all elements to a given value x:

def f(x: int) -> int:
    return sum(abs(v - x) for v in nums)

Next, we find the median of the sorted array:

nums.sort()
median_index = len(nums) // 2
median_value = nums[median_index]

Using binary search (bisect_left), we find the position where the median would fit in the palindrome array:

i = bisect_left(ps, nums[len(nums) // 2])

Step 3: Check Candidates Around the Median

Since the optimal palindrome should be close to the median, we check palindromes in a small window around position i:

return min(f(ps[j]) for j in range(i - 1, i + 2) if 0 <= j < len(ps))

This checks three positions:

  • ps[i-1]: The palindrome just before the median
  • ps[i]: The palindrome at or just after the median
  • ps[i+1]: The next palindrome after that

The boundary check 0 <= j < len(ps) ensures we don't go out of bounds.

Time Complexity: O(n log n) for sorting nums, plus O(n) for each cost calculation (done a constant number of times).

Space Complexity: O(M) where M ≈ 2 × 10^5 is the number of palindromes stored.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with nums = [1, 25, 99, 4].

Step 1: Generate palindromes First, we generate palindromic numbers. For illustration, let's show a few relevant ones:

  • From 1: creates 11 (even) and 1 (odd)
  • From 2: creates 22 (even) and 2 (odd)
  • From 3: creates 33 (even) and 3 (odd)
  • ...continuing this pattern...
  • From 10: creates 1001 (even) and 101 (odd)
  • From 11: creates 1111 (even) and 111 (odd)

After generating all palindromes up to 10^9 and sorting them, we get: ps = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, ...]

Step 2: Find the median of nums Sort nums: [1, 4, 25, 99] The median index is len(nums) // 2 = 2, so the median value is nums[2] = 25.

Step 3: Binary search for palindromes near median Using binary search on our palindrome array ps, we find where 25 would fit:

  • 25 would be inserted between 22 and 33
  • bisect_left(ps, 25) returns index 11 (pointing to 33)

Step 4: Check candidate palindromes We check palindromes at indices [10, 11, 12] which correspond to values [22, 33, 44]:

For palindrome 22:

  • Cost = |1-22| + |4-22| + |25-22| + |99-22|
  • Cost = 21 + 18 + 3 + 77 = 119

For palindrome 33:

  • Cost = |1-33| + |4-33| + |25-33| + |99-33|
  • Cost = 32 + 29 + 8 + 66 = 135

For palindrome 44:

  • Cost = |1-44| + |4-44| + |25-44| + |99-44|
  • Cost = 43 + 40 + 19 + 55 = 157

Step 5: Return minimum cost The minimum cost is 119, achieved by changing all elements to palindrome 22.

This example demonstrates why we check multiple palindromes around the median - even though 33 is closer to the median value of 25, the palindrome 22 actually gives us a lower total cost due to the distribution of values in the array.

Solution Implementation

1from typing import List
2from bisect import bisect_left
3
4# Pre-generate all palindromes up to a certain range
5palindromes = []
6for i in range(1, 10**5 + 1):
7    # Convert number to string for manipulation
8    num_str = str(i)
9  
10    # Create odd-length palindrome by mirroring entire number
11    # Example: 123 -> 123321
12    odd_palindrome = num_str + num_str[::-1]
13  
14    # Create even-length palindrome by mirroring all but last digit
15    # Example: 123 -> 12321
16    even_palindrome = num_str + num_str[:-1][::-1]
17  
18    palindromes.append(int(odd_palindrome))
19    palindromes.append(int(even_palindrome))
20
21# Sort palindromes for binary search
22palindromes.sort()
23
24
25class Solution:
26    def minimumCost(self, nums: List[int]) -> int:
27        def calculate_total_difference(target: int) -> int:
28            """Calculate sum of absolute differences between all numbers and target"""
29            return sum(abs(num - target) for num in nums)
30      
31        # Sort input array to find median
32        nums.sort()
33      
34        # Find median value (middle element for optimal starting point)
35        median = nums[len(nums) // 2]
36      
37        # Binary search to find closest palindrome to median
38        closest_index = bisect_left(palindromes, median)
39      
40        # Check palindromes around the closest index to find minimum cost
41        # Check previous, current, and next palindrome for optimal solution
42        min_cost = float('inf')
43        for j in range(closest_index - 1, closest_index + 2):
44            if 0 <= j < len(palindromes):
45                min_cost = min(min_cost, calculate_total_difference(palindromes[j]))
46      
47        return min_cost
48
1public class Solution {
2    // Static array to store all palindromic numbers
3    private static long[] palindromes;
4    private int[] nums;
5
6    // Static initialization block to generate all palindromic numbers up to a certain range
7    static {
8        // Allocate space for approximately 200,000 palindromes
9        palindromes = new long[2 * (int) 1e5];
10      
11        // Generate palindromes by creating numbers and their reverses
12        for (int i = 1; i <= 1e5; i++) {
13            String numberStr = Integer.toString(i);
14          
15            // Create even-length palindrome (e.g., 123 -> 123321)
16            String reverseStr = new StringBuilder(numberStr).reverse().toString();
17            palindromes[2 * i - 2] = Long.parseLong(numberStr + reverseStr);
18          
19            // Create odd-length palindrome (e.g., 123 -> 12321)
20            String truncatedReverse = new StringBuilder(numberStr.substring(0, numberStr.length() - 1))
21                                        .reverse().toString();
22            palindromes[2 * i - 1] = Long.parseLong(numberStr + truncatedReverse);
23        }
24      
25        // Sort palindromes for binary search
26        Arrays.sort(palindromes);
27    }
28
29    public long minimumCost(int[] nums) {
30        this.nums = nums;
31      
32        // Sort the input array to find the median
33        Arrays.sort(nums);
34      
35        // Find the median value (middle element for optimization)
36        int median = nums[nums.length / 2];
37      
38        // Binary search to find the closest palindrome to the median
39        int searchIndex = Arrays.binarySearch(palindromes, median);
40      
41        // If exact match not found, get the insertion point
42        searchIndex = searchIndex < 0 ? -searchIndex - 1 : searchIndex;
43      
44        // Initialize minimum cost with a large value
45        long minCost = 1L << 60;
46      
47        // Check palindromes around the found index (one before, at, and one after)
48        for (int candidateIndex = searchIndex - 1; candidateIndex <= searchIndex + 1; candidateIndex++) {
49            // Ensure the index is within valid bounds
50            if (0 <= candidateIndex && candidateIndex < palindromes.length) {
51                // Calculate cost for this palindrome and update minimum
52                minCost = Math.min(minCost, calculateTotalCost(palindromes[candidateIndex]));
53            }
54        }
55      
56        return minCost;
57    }
58
59    // Calculate the total cost to change all numbers to the target palindrome
60    private long calculateTotalCost(long targetPalindrome) {
61        long totalCost = 0;
62      
63        // Sum up the absolute differences between each number and the target
64        for (int value : nums) {
65            totalCost += Math.abs(value - targetPalindrome);
66        }
67      
68        return totalCost;
69    }
70}
71
1using ll = long long;
2
3// Array to store all palindrome numbers generated from integers 1 to 100000
4ll palindromes[2 * 100000];
5
6// Initialize palindromes array at program startup
7int initialize = [] {
8    // Generate palindromes from each integer i
9    for (int i = 1; i <= 100000; i++) {
10        string numStr = to_string(i);
11      
12        // Generate even-length palindrome by mirroring the entire number
13        // Example: 123 -> 123321
14        string reversedStr = numStr;
15        reverse(reversedStr.begin(), reversedStr.end());
16        palindromes[2 * i - 2] = stoll(numStr + reversedStr);
17      
18        // Generate odd-length palindrome by mirroring all but the last digit
19        // Example: 123 -> 12321
20        string truncatedStr = numStr.substr(0, numStr.length() - 1);
21        reverse(truncatedStr.begin(), truncatedStr.end());
22        palindromes[2 * i - 1] = stoll(numStr + truncatedStr);
23    }
24  
25    // Sort palindromes for binary search
26    sort(palindromes, palindromes + 2 * 100000);
27    return 0;
28}();
29
30class Solution {
31public:
32    long long minimumCost(vector<int>& nums) {
33        // Sort input array to find median
34        sort(nums.begin(), nums.end());
35      
36        // Find the palindrome closest to the median (optimal point for minimizing sum of absolute differences)
37        int medianValue = nums[nums.size() / 2];
38        int palindromeIndex = lower_bound(palindromes, palindromes + 2 * 100000, medianValue) - palindromes;
39      
40        // Lambda function to calculate total cost for a given palindrome value
41        auto calculateCost = [&](ll palindromeValue) {
42            ll totalCost = 0;
43            for (int& num : nums) {
44                totalCost += abs(num - palindromeValue);
45            }
46            return totalCost;
47        };
48      
49        // Check palindromes around the found index (before, at, and after)
50        // This handles edge cases where the closest palindrome might be slightly off
51        ll minCost = LLONG_MAX;
52        for (int checkIndex = palindromeIndex - 1; checkIndex <= palindromeIndex + 1; checkIndex++) {
53            // Ensure index is within valid range
54            if (0 <= checkIndex && checkIndex < 2 * 100000) {
55                minCost = min(minCost, calculateCost(palindromes[checkIndex]));
56            }
57        }
58      
59        return minCost;
60    }
61};
62
1// Pre-computed array to store all palindromic numbers
2const palindromes = Array(2e5).fill(0);
3
4// Initialize palindromes array with all possible palindromic numbers
5const initializePalindromes = (() => {
6    // Generate palindromes by mirroring numbers
7    for (let i = 1; i <= 1e5; ++i) {
8        const numberStr: string = i.toString();
9      
10        // Create even-length palindrome by mirroring entire number
11        const evenPalindrome: string = numberStr + numberStr.split('').reverse().join('');
12      
13        // Create odd-length palindrome by mirroring all but last digit
14        const oddPalindrome: string = numberStr + numberStr.slice(0, -1).split('').reverse().join('');
15      
16        // Store both palindromes in the array
17        palindromes[2 * i - 2] = parseInt(evenPalindrome, 10);
18        palindromes[2 * i - 1] = parseInt(oddPalindrome, 10);
19    }
20  
21    // Sort palindromes in ascending order for binary search
22    palindromes.sort((a, b) => a - b);
23})();
24
25/**
26 * Finds the minimum cost to make all elements equal to a palindromic number
27 * @param nums - Array of numbers to transform
28 * @returns Minimum total cost
29 */
30function minimumCost(nums: number[]): number {
31    /**
32     * Binary search to find the index of the smallest palindrome >= target
33     * @param target - Target value to search for
34     * @returns Index of the smallest palindrome >= target
35     */
36    const binarySearch = (target: number): number => {
37        let left = 0;
38        let right = palindromes.length;
39      
40        while (left < right) {
41            const mid = (left + right) >> 1;
42          
43            if (palindromes[mid] >= target) {
44                right = mid;
45            } else {
46                left = mid + 1;
47            }
48        }
49      
50        return left;
51    };
52  
53    /**
54     * Calculates total cost to transform all numbers to a target value
55     * @param target - Target palindrome value
56     * @returns Total transformation cost
57     */
58    const calculateCost = (target: number): number => {
59        return nums.reduce((totalCost, currentNum) => {
60            return totalCost + Math.abs(currentNum - target);
61        }, 0);
62    };
63  
64    // Sort input array to find median
65    nums.sort((a, b) => a - b);
66  
67    // Find median element
68    const medianValue = nums[nums.length >> 1];
69  
70    // Find closest palindrome index to median
71    const closestPalindromeIndex = binarySearch(medianValue);
72  
73    // Check palindromes around the closest one to find minimum cost
74    let minCost = Number.MAX_SAFE_INTEGER;
75  
76    // Check three potential palindromes: one before, at, and after the found index
77    for (let j = closestPalindromeIndex - 1; j <= closestPalindromeIndex + 1; j++) {
78        if (j >= 0 && j < palindromes.length) {
79            const currentCost = calculateCost(palindromes[j]);
80            minCost = Math.min(minCost, currentCost);
81        }
82    }
83  
84    return minCost;
85}
86

Time and Space Complexity

Time Complexity:

The code consists of two main parts: preprocessing palindromes and solving the main problem.

  1. Preprocessing palindromes:

    • The loop runs from 1 to 10^5, creating O(10^5) iterations
    • For each number i, string operations like str(i), s[::-1], and s[:-1][::-1] take O(log i) time (proportional to number of digits)
    • Converting back to integer takes O(log i) time
    • Total preprocessing: O(10^5 * log(10^5)) = O(10^5 * 5) = O(10^5)
    • Sorting the palindromes list: O(|ps| * log|ps|) where |ps| = 2 * 10^5, so O(2 * 10^5 * log(2 * 10^5)) = O(10^5 * log(10^5))
  2. Main solution:

    • Sorting nums: O(n * log n) where n is the length of input array
    • Binary search using bisect_left: O(log|ps|) = O(log(2 * 10^5)) = O(log(10^5))
    • Computing minimum cost: evaluates function f at most 3 times, each taking O(n) time
    • Total for main solution: O(n * log n + n) = O(n * log n)

Overall Time Complexity: O(10^5 * log(10^5) + n * log n) where the preprocessing is done once and can be amortized if multiple test cases exist.

Space Complexity:

  1. Palindrome storage: The list ps stores 2 * 10^5 palindrome numbers, each requiring O(1) space, totaling O(10^5)

  2. Input storage: The sorted nums array takes O(n) space (in-place sorting or copy depending on implementation)

  3. Temporary variables: String operations create temporary strings of length O(log(10^5)) = O(5), which is O(1)

Overall Space Complexity: O(10^5 + n) where n is the length of the input array.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Palindrome Generation Logic

A common mistake is reversing the string incorrectly when creating palindromes. For example:

  • Wrong: Using num_str + num_str[:-1][::-1] for odd-length palindromes (this creates even-length)
  • Wrong: Using num_str + num_str[::-1] for even-length palindromes (this creates odd-length)

Solution: Remember that odd-length palindromes have a single center digit, while even-length palindromes mirror completely:

# Correct generation
even_length = num_str + num_str[::-1]      # 123 → 123321 (6 digits)
odd_length = num_str + num_str[:-1][::-1]  # 123 → 12321 (5 digits)

2. Missing Edge Cases in Palindrome Generation

The code generates palindromes starting from 1, but misses single-digit palindromes (1-9) when using the standard generation method since they would be duplicated.

Solution: The current approach actually handles this correctly by starting from 1, which generates all necessary palindromes including single digits through the odd-length formula.

3. Insufficient Window Size for Checking Candidates

Checking only [i-1, i+1] (3 positions) might miss the optimal palindrome, especially when there are large gaps between consecutive palindromes or when the array has outliers.

Solution: Expand the search window slightly to be more robust:

# Check a wider range of candidates
window_size = 5  # or more depending on requirements
min_cost = float('inf')
for j in range(max(0, closest_index - window_size), 
               min(len(palindromes), closest_index + window_size + 1)):
    min_cost = min(min_cost, calculate_total_difference(palindromes[j]))

4. Integer Overflow for Large Palindromes

When generating palindromes up to 10^9, concatenating strings like "99999" + "99999" would create 9999999999 which exceeds 10^9.

Solution: Filter out palindromes that exceed the limit:

palindromes = []
for i in range(1, 10**5 + 1):
    num_str = str(i)
    even_pal = int(num_str + num_str[::-1])
    odd_pal = int(num_str + num_str[:-1][::-1])
  
    if even_pal < 10**9:
        palindromes.append(even_pal)
    if odd_pal < 10**9:
        palindromes.append(odd_pal)

5. Using Mean Instead of Median

Using the mean (average) as the reference point instead of the median can lead to suboptimal results when the array has outliers.

Solution: Always use the median for minimizing sum of absolute differences:

nums.sort()
median = nums[len(nums) // 2]  # Correct: use median
# NOT: mean = sum(nums) // len(nums)  # Wrong for this problem

6. Not Handling Empty Array or Single Element

The code doesn't explicitly handle edge cases like empty arrays or single-element arrays.

Solution: Add validation at the beginning:

def minimumCost(self, nums: List[int]) -> int:
    if not nums:
        return 0
    if len(nums) == 1:
        # Find closest palindrome to the single element
        idx = bisect_left(palindromes, nums[0])
        candidates = []
        if idx > 0:
            candidates.append(palindromes[idx - 1])
        if idx < len(palindromes):
            candidates.append(palindromes[idx])
        return min(abs(nums[0] - p) for p in candidates)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

A person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".

What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More