Find the Minimum Cost Array Permutation
HardPrompt
You are given an array nums which is a permutation of [0, 1, 2, ..., n - 1]. The score of any permutation of [0, 1, 2, ..., n - 1] named perm is defined as:
score(perm) = |perm[0] - nums[perm[1]]| + |perm[1] - nums[perm[2]]| + ... + |perm[n - 1] - nums[perm[0]]|
Return the permutation perm which has the minimum possible score. If multiple permutations exist with this score, return the one that is lexicographically smallest among them.
Example 1:
Input: nums = [1,0,2]
Output: [0,1,2]
Explanation:

The lexicographically smallest permutation with minimum cost is [0,1,2]. The cost of this permutation is |0 - 0| + |1 - 2| + |2 - 1| = 2.
Example 2:
Input: nums = [0,2,1]
Output: [0,2,1]
Explanation:

The lexicographically smallest permutation with minimum cost is [0,2,1]. The cost of this permutation is |0 - 1| + |2 - 2| + |1 - 0| = 2.
Constraints:
2 <= n == nums.length <= 14numsis a permutation of[0, 1, 2, ..., n - 1].
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves generating every single possible permutation of the numbers [0, 1, ..., n-1]. For each permutation, we calculate its score based on the provided formula. We maintain a variable to keep track of the minimum score found so far and the permutation that produced it. If we find a permutation with a score equal to the current minimum, we choose the one that is lexicographically smaller. This exhaustive search guarantees finding the correct answer but is computationally very expensive.
Algorithm
- Initialize
min_scoreto a very large value andbest_permto an empty array. - Create a recursive function, say
generatePermutations, that takes the current partial permutation and a set of used numbers as input. - The base case for the recursion is when the partial permutation's length equals
n. a. When a full permutation is formed, calculate its score using the formula:score = |perm[0] - nums[perm[1]]| + ... + |perm[n-1] - nums[perm[0]]|. b. If the calculatedscoreis less thanmin_score, updatemin_scoreto this new score andbest_permto the current permutation. c. If thescoreis equal tomin_score, compare the current permutation withbest_permlexicographically. If the current one is smaller, updatebest_perm. - The recursive step involves iterating through all numbers from
0ton-1. If a number has not been used, add it to the partial permutation and recurse. Backtrack by removing the number and marking it as unused after the recursive call returns. - Start the process by calling the recursive function with an empty initial permutation.
- After all permutations have been explored,
best_permwill hold the desired result.
Walkthrough
The brute-force method systematically explores the entire search space of n! permutations. We can implement this using a recursive backtracking algorithm.
We define a helper function that builds a permutation step by step. This function would keep track of the numbers already included in the current partial permutation to ensure no duplicates are used.
Once a full permutation of length n is constructed, we compute its score. The score is the sum of absolute differences as defined in the problem: Σ|perm[i] - nums[perm[i+1]]| (with the index wrapping around at the end).
This score is then compared against the minimum score found so far. If it's better, we update our best-found permutation. If it's the same, we perform a lexicographical comparison to decide if we should update. Due to the n! growth, this method is only feasible for very small values of n.
class Solution { private int minScore; private int[] bestPerm; private int[] nums; private int n; public int[] findPermutation(int[] nums) { this.n = nums.length; this.nums = nums; this.minScore = Integer.MAX_VALUE; this.bestPerm = new int[n]; java.util.List<Integer> p = new java.util.ArrayList<>(); boolean[] used = new boolean[n]; generatePermutations(p, used); return bestPerm; } private void generatePermutations(java.util.List<Integer> p, boolean[] used) { if (p.size() == n) { int[] currentPerm = p.stream().mapToInt(i -> i).toArray(); int currentScore = calculateScore(currentPerm); if (currentScore < minScore) { minScore = currentScore; System.arraycopy(currentPerm, 0, bestPerm, 0, n); } else if (currentScore == minScore) { if (isLexicographicallySmaller(currentPerm, bestPerm)) { System.arraycopy(currentPerm, 0, bestPerm, 0, n); } } return; } for (int i = 0; i < n; i++) { if (!used[i]) { used[i] = true; p.add(i); generatePermutations(p, used); p.remove(p.size() - 1); used[i] = false; } } } private int calculateScore(int[] perm) { int score = 0; for (int i = 0; i < n - 1; i++) { score += Math.abs(perm[i] - nums[perm[i + 1]]); } score += Math.abs(perm[n - 1] - nums[perm[0]]); return score; } private boolean isLexicographicallySmaller(int[] p1, int[] p2) { for (int i = 0; i < n; i++) { if (p1[i] < p2[i]) { return true; } if (p1[i] > p2[i]) { return false; } } return false; }}Complexity
Time
O(n! * n) - There are `n!` permutations to generate. For each permutation, calculating the score takes O(n) time.
Space
O(n) - The space is dominated by the recursion depth and the storage for the current and best permutations.
Trade-offs
Pros
Conceptually simple and easy to understand.
Guaranteed to find the correct answer as it checks every possibility.
Cons
Extremely high time complexity, making it impractical for
ngreater than 10 or 11.Will result in a 'Time Limit Exceeded' error on most platforms for the given constraints.
This problem can be framed as the classic Traveling Salesperson Problem (TSP). The numbers 0, 1, ..., n-1 act as cities (nodes). The cost to travel from a city u to a city v is given by |u - nums[v]|. We are looking for a tour (a permutation) that visits every city exactly once and returns to the start, minimizing the total travel cost. Given the constraint n <= 14, this can be efficiently solved using dynamic programming with bitmasking.
Algorithm
- Model the problem as a Traveling Salesperson Problem (TSP) where nodes are
0..n-1and edge cost fromutovis|u - nums[v]|. - Use dynamic programming with bitmasking. Define
dp[mask][i]as the minimum cost of a path visiting nodes inmaskand ending ati. - Initialize a
dptable of size(1<<n) x nwith infinity and aparenttable to reconstruct paths. - Set base cases:
dp[1<<i][i] = 0for allifrom 0 ton-1(paths of length 1). - Iterate through
maskfrom 1 to(1<<n)-1. For eachmask, iterate through nodesiin the mask. Then iterate through nodesjinmaskexcludingi(prev_mask). - Update
dp[mask][i]using the transition:dp[mask][i] = min(dp[mask][i], dp[prev_mask][j] + |j - nums[i]|). Storejinparent[mask][i]. - After filling the DP table, find the overall minimum cycle cost. For each possible path end-node
i, reconstruct the path using theparenttable to find its start-nodes. The cycle cost isdp[(1<<n)-1][i] + |i - nums[s]|. - Identify all permutations that achieve this minimum cost.
- For each such optimal permutation (which forms a cycle), find its lexicographically smallest cyclic shift.
- Return the lexicographically smallest among all these optimal candidates.
Walkthrough
The DP state dp[mask][i] will store the minimum cost of a path that visits the set of nodes represented by the bitmask mask and ends at node i. A parent[mask][i] table is also used to backtrack and reconstruct the path itself.
Algorithm Steps:
-
Initialization: Create
dp[1<<n][n]andparent[1<<n][n]tables. Initializedpwith a large value. For each nodei, the cost of a path just visitingiis 0, sodp[1 << i][i] = 0. -
DP Calculation: We iterate through all masks, from those with 2 set bits up to
n. For eachmask, we iterate through each nodeiit contains. We then look at all possible predecessorsj(frommaskexcludingi) and updatedp[mask][i]if we find a cheaper path throughj. The transition is:dp[mask][i] = min(dp[mask][i], dp[mask ^ (1<<i)][j] + |j - nums[i]|). -
Finding Minimum Cost and Permutation: Once the
dptable is populated,dp[(1<<n)-1][i]gives the cost of the cheapest Hamiltonian path ending ati. To complete the cycle, we add the cost of the edge from the end nodeiback to the start node of that path. The start node is found by backtracking using theparenttable. We do this for all possible end nodesito find the global minimum cycle cost. -
Lexicographical Requirement: After finding the minimum possible score, we must find the lexicographically smallest permutation that achieves it. We iterate through all cycles that yield this minimum score. For each such cycle (represented as a permutation), we find its lexicographically smallest cyclic shift. The smallest among these shifts is our final answer.
class Solution { public int[] findPermutation(int[] nums) { int n = nums.length; int fullMask = (1 << n) - 1; int[][] dp = new int[1 << n][n]; int[][] parent = new int[1 << n][n]; for (int[] row : dp) { java.util.Arrays.fill(row, Integer.MAX_VALUE / 2); } for (int i = 0; i < n; i++) { dp[1 << i][i] = 0; } for (int mask = 1; mask < (1 << n); mask++) { for (int i = 0; i < n; i++) { if ((mask & (1 << i)) != 0) { // if i is in mask int prevMask = mask ^ (1 << i); if (prevMask == 0) continue; for (int j = 0; j < n; j++) { if ((prevMask & (1 << j)) != 0) { // if j is in prevMask int cost = dp[prevMask][j] + Math.abs(j - nums[i]); if (cost < dp[mask][i]) { dp[mask][i] = cost; parent[mask][i] = j; } } } } } } int minScore = Integer.MAX_VALUE; int[] bestPerm = null; for (int i = 0; i < n; i++) { // i is the last element of the path int[] path = new int[n]; int curr = i; int mask = fullMask; for (int k = n - 1; k >= 0; k--) { path[k] = curr; int prev = parent[mask][curr]; mask ^= (1 << curr); curr = prev; } int startNode = path[0]; int currentScore = dp[fullMask][i] + Math.abs(i - nums[startNode]); if (currentScore < minScore) { minScore = currentScore; bestPerm = getLexicographicallySmallest(path); } else if (currentScore == minScore) { int[] candidatePerm = getLexicographicallySmallest(path); if (isLexicographicallySmaller(candidatePerm, bestPerm)) { bestPerm = candidatePerm; } } } return bestPerm; } private int[] getLexicographicallySmallest(int[] path) { int n = path.length; int[] bestShift = path.clone(); int[] currentShift = path.clone(); for (int i = 1; i < n; i++) { int[] nextShift = new int[n]; for(int j=0; j<n; j++) { nextShift[j] = currentShift[(j + 1) % n]; } currentShift = nextShift; if (isLexicographicallySmaller(currentShift, bestShift)) { bestShift = currentShift.clone(); } } return bestShift; } private boolean isLexicographicallySmaller(int[] p1, int[] p2) { for (int i = 0; i < p1.length; i++) { if (p1[i] < p2[i]) return true; if (p1[i] > p2[i]) return false; } return false; }}Complexity
Time
O(n^2 * 2^n) - The main loop iterates through `2^n` masks, `n` end-nodes, and `n` predecessor nodes.
Space
O(n * 2^n) - To store the `dp` and `parent` tables, each of size `(1 << n) x n`.
Trade-offs
Pros
Provides an optimal and efficient solution for the given constraints.
It's a standard and powerful technique for problems like TSP on a small number of nodes.
Cons
Considerably more complex to implement compared to brute force.
Requires a good understanding of dynamic programming, bitmasking, and the TSP.
Uses significant memory,
O(n * 2^n), which can be large.
Solutions
Solution
class Solution {private Integer[][] f;private int[] nums;private int[] ans;private int n;public int[] findPermutation(int[] nums) { n = nums.length; ans = new int[n]; this.nums = nums; f = new Integer[1 << n][n]; g(1, 0, 0); return ans; }private int dfs(int mask, int pre) { if (mask == (1 << n) - 1) { return Math.abs(pre - nums[0]); } if (f[mask][pre] != null) { return f[mask][pre]; } int res = Integer.MAX_VALUE; for (int cur = 1; cur < n; ++cur) { if ((mask >> cur & 1) == 0) { res = Math.min(res, Math.abs(pre - nums[cur]) + dfs(mask | 1 << cur, cur)); } } return f[mask][pre] = res; }private void g(int mask, int pre, int k) { ans[k] = pre; if (mask == (1 << n) - 1) { return; } int res = dfs(mask, pre); for (int cur = 1; cur < n; ++cur) { if ((mask >> cur & 1) == 0) { if (Math.abs(pre - nums[cur]) + dfs(mask | 1 << cur, cur) == res) { g(mask | 1 << cur, cur, k + 1); break; } } } }}class Solution {public: vector<int> findPermutation(vector<int> &nums) { int n = nums.size(); vector<int> ans; int f[1 << n][n]; memset(f, -1, sizeof(f)); function<int(int, int)> dfs = [&](int mask, int pre) { if (mask == (1 << n) - 1) { return abs(pre - nums[0]); } int *res = &f[mask][pre]; if (*res != -1) { return *res; } *res = INT_MAX; for (int cur = 1; cur < n; ++cur) { if (mask >> cur & 1 ^ 1) { *res = min(*res, abs(pre - nums[cur]) + dfs(mask | 1 << cur, cur)); } } return *res; }; function<void(int, int)> g = [&](int mask, int pre) { ans.push_back(pre); if (mask == (1 << n) - 1) { return; } int res = dfs(mask, pre); for (int cur = 1; cur < n; ++cur) { if (mask >> cur & 1 ^ 1) { if (abs(pre - nums[cur]) + dfs(mask | 1 << cur, cur) == res) { g(mask | 1 << cur, cur); break; } } } }; g(1, 0); return ans; }};class Solution: def findPermutation(self, nums: List[int]) -> List[int]: @ cache def dfs(mask: int, pre: int) -> int: if mask == (1 << n) - 1: return abs(pre - nums[0]) res = inf for cur in range(1, n): if mask >> cur & 1 ^ 1: res = min(res, abs(pre - nums[cur]) + dfs(mask | 1 << cur, cur)) return res def g(mask: int, pre: int): ans . append(pre) if mask == (1 << n) - 1: return res = dfs(mask, pre) for cur in range(1, n): if mask >> cur & 1 ^ 1: if abs(pre - nums[cur]) + dfs(mask | 1 << cur, cur) == res: g(mask | 1 << cur, cur) break n = len(nums) ans = [] g(1, 0) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.