Minimum Possible Integer After at Most K Adjacent Swaps On Digits solution and explanation
6 more videos on this problem
1505. Minimum Possible Integer After at Most K Adjacent Swaps On Digits (Leetcode Hard)Programming Live with Larry · 28:19 · 1K views
花花酱 LeetCode 1505. Minimum Possible Integer After at Most K Adjacent Swaps On Digits - 刷题找工作 EP341Hua Hua · 16:51 · 1K views
Minimum Possible Integer After at Most K Adjacent Swaps On Digits | Leetcode | HardKamal Singh · 9:16 · 569 views
Leetcode 1505 - Minimum Possible Integer After at Most K Adjacent Swaps On Digits济公学院 · 13:59 · 441 views
【每日一题】1505. Minimum Possible Integer After at Most K Adjacent Swaps On Digits, 7/9/2020Huifeng Guan · 37:35 · 431 views
小Z解题 | LeetCode 1505 Minimum Possible Integer After at Most K Adjacent Swaps On DigitscoderZ · 33:33 · 230 views
LeetCode 1505: Minimum Possible Integer After at Most K Adjacent Swaps On DigitsAlitaCode · 28:01 · 12 views
The problem
You are given a string num representing the digits of a very large integer and an integer k. You are allowed to swap any two adjacent digits of the integer at most k times.
Return the minimum integer you can obtain also as a string.
Example 1:
Input: num = "4321", k = 4 Output: "1342" Explanation: The steps to obtain the minimum integer from 4321 with 4 adjacent swaps are shown.
Example 2:
Input: num = "100", k = 1 Output: "010" Explanation: It's ok for the output to have leading zeros, but the input is guaranteed not to have any leading zeros.
Example 3:
Input: num = "36789", k = 1000 Output: "36789" Explanation: We can keep the number without any swaps.
Constraints:
1 <= num.length <= 3 * 104numconsists of only digits and does not contain leading zeros.1 <= k <= 109
Approach overview
Problem Overview: You are given a numeric string num and an integer k. Each operation swaps two adjacent digits, and you can perform at most k swaps. The goal is to produce the lexicographically smallest possible integer after these swaps.
Approach 1: Greedy with Permutation Indices + Binary Indexed Tree (O(n log n) time, O(n) space)
The key observation is that the leftmost digits have the greatest impact on the final number. At each position, try to bring the smallest possible digit forward if it can reach the current position within the remaining swap budget. Maintain queues of indices for digits 0–9. For each candidate digit, compute how many swaps are needed to move its next occurrence to the current position.
A Binary Indexed Tree (Fenwick Tree) tracks how many digits have already been moved. This allows you to adjust the effective index after previous shifts. If the required swaps are ≤ k, move that digit to the result, update the tree, reduce k, and continue. The strategy works because a greedy choice of the smallest reachable digit always leads to the optimal prefix.
Approach 2: Segment Tree for Efficient Minimum Finding (O(n log n) time, O(n) space)
Another way to implement the same greedy idea is with a segment tree. Instead of scanning digits 0–9 each step, build a segment tree that stores the minimum digit available in a range along with its index. The tree supports queries to find the smallest digit that can be moved to the current position without exceeding the remaining swaps.
When a digit is selected, update the tree to mark its index as used and adjust swap counts accordingly. The segment tree efficiently handles repeated minimum queries and updates in O(log n). This approach is especially useful in languages where priority structures or index bookkeeping becomes complex.
Recommended for interviews: The greedy strategy combined with a Binary Indexed Tree is the expected optimal solution. It demonstrates understanding of lexicographic minimization, index shifting caused by swaps, and efficient prefix counting. Segment trees solve the same problem but are usually considered an alternative implementation. Showing the greedy reasoning first, then implementing it with a Fenwick Tree, signals strong algorithmic maturity.
Approach 1: Greedy Approach with Permutation Indices
This approach makes use of a greedy algorithm. The idea is to always try to bring the smallest possible digit to the front using the allowed adjacent swaps.
By iterating through the digits of the number, for each position, determine the smallest digit within a distance of k. Then perform the necessary swaps to bring that smallest digit to the current position.
This approach minimizes the resulting integer by always prioritizing the smallest digit to be swapped forward.
def minInteger(num: str, k: int) -> str:
num = list(num)
n = len(num)
for i in range(n):
min_index = i
for j in range(i + 1, min(i + k + 1, n)):
if num[j] < num[min_index]:
min_index = j
k -= min_index - i
num[i:min_index+1] = [num[min_index]] + num[i:min_index]
if k <= 0:
break
return ''.join(num)public class Solution {
public String minInteger(String num, int k) {
char[] chars = num.toCharArray();
int n = chars.length;
for (int i = 0; i < n && k > 0; i++) {
int minIndex = i;
for (int j = i + 1; j < n && j <= i + k; j++) {
if (chars[j] < chars[minIndex]) {
minIndex = j;
}
}
k -= (minIndex - i);
char minChar = chars[minIndex];
for (int j = minIndex; j > i; j--) {
chars[j] = chars[j - 1];
}
chars[i] = minChar;
}
return new String(chars);
}
}We maintain the string as a list for in-place modifications. For each index, find the minimum digit within the next k positions and swap it forward. Update k for the swaps made and break if k runs out.
Time Complexity: O(n*k) in the worst case where k is large.
Space Complexity: O(n) due to the storage of digits.
Approach 2: Using Segment Tree for Efficient Minimum Finding
This approach uses a Segment Tree to efficiently find the minimum digit within a segment of the number in logarithmic time. It allows us to store and update the position of digits dynamically.
The Segment Tree helps in reducing the computational bottleneck of finding the minimum digit that can be swapped to the front within 'k' swaps, from linear to log time complexity per query.
#include <vector>
#include <string>
using namespace std;
class Solution {
vector<int> segTree;
int n;
void buildTree(vector<int>& num, int left, int right, int pos) {
if (left == right) {
segTree[pos] = left;
return;
}
int mid = (left + right) / 2;
buildTree(num, left, mid, 2 * pos + 1);
buildTree(num, mid + 1, right, 2 * pos + 2);
int leftIndex = segTree[2 * pos + 1];
int rightIndex = segTree[2 * pos + 2];
segTree[pos] = num[leftIndex] <= num[rightIndex] ? leftIndex : rightIndex;
}
int query(int qlow, int qhigh, int low, int high, int pos, vector<int>& num) {
if (qlow <= low && qhigh >= high) return segTree[pos];
if (qlow > high || qhigh < low) return -1;
int mid = (low + high) / 2;
int leftIndex = query(qlow, qhigh, low, mid, 2 * pos + 1, num);
int rightIndex = query(qlow, qhigh, mid + 1, high, 2 * pos + 2, num);
if (leftIndex == -1) return rightIndex;
if (rightIndex == -1) return leftIndex;
return num[leftIndex] <= num[rightIndex] ? leftIndex : rightIndex;
}
public:
string minInteger(string num, int k) {
n = num.size();
segTree.resize(4 * n);
vector<int> nums(n);
for (int i = 0; i < n; ++i) nums[i] = num[i] - '0';
buildTree(nums, 0, n - 1, 0);
string result;
for (int i = 0; i < n && k > 0; ++i) {
int minIndex = query(i, min(i + k, n - 1), 0, n - 1, 0, nums);
if (minIndex > i) {
k -= (minIndex - i);
result.push_back(num[minIndex]);
nums.erase(nums.begin() + minIndex);
segTree.clear();
buildTree(nums, 0, n - 1, 0);
--n;
} else {
result.push_back(num[i]);
}
}
return result;
}
};class SegmentTree {
constructor(size) {
this.size = size;
this.tree = Array(size * 4).fill(0);
}
build(nums, node, start, end) {
if (start === end) {
this.tree[node] = start;
} else {
const mid = Math.floor((start + end) / 2);
this.build(nums, 2 * node + 1, start, mid);
this.build(nums, 2 * node + 2, mid + 1, end);
this.tree[node] = nums[this.tree[2 * node + 1]] <= nums[this.tree[2 * node + 2]] ? this.tree[2 * node + 1] : this.tree[2 * node + 2];
}
}
query(nums, node, start, end, L, R) {
if (R < start || L > end) {
return -1;
}
if (L <= start && end <= R) {
return this.tree[node];
}
const mid = Math.floor((start + end) / 2);
const leftQuery = this.query(nums, 2 * node + 1, start, mid, L, R);
const rightQuery = this.query(nums, 2 * node + 2, mid + 1, end, L, R);
if (leftQuery === -1) return rightQuery;
if (rightQuery === -1) return leftQuery;
return nums[leftQuery] <= nums[rightQuery] ? leftQuery : rightQuery;
}
}
function minInteger(num, k) {
const n = num.length;
const nums = Array.from(num).map(x => parseInt(x, 10));
const segTree = new SegmentTree(n);
segTree.build(nums, 0, 0, n - 1);
let result = '';
for (let i = 0; i < n && k > 0; i++) {
const minIndex = segTree.query(nums, 0, 0, n - 1, i, Math.min(i + k, n - 1));
if (minIndex > i) {
k -= (minIndex - i);
result += num.charAt(minIndex);
nums.splice(minIndex, 1);
segTree.build(nums, 0, 0, nums.length - 1);
n--;
} else {
result += num.charAt(i);
}
}
return result;
}This solution is implemented using a Segment Tree wrapped into a class. It builds the tree and uses it to find the minimum index efficiently, re-building as adjustments are made.
Time Complexity: O(n log n) due to segment tree operations.
Space Complexity: O(n) due to the segment tree structure.
Approach 3: Default Approach
type BinaryIndexedTree struct {
n int
c []int
}
func newBinaryIndexedTree(n int) *BinaryIndexedTree {
c := make([]int, n+1)
return &BinaryIndexedTree{n, c}
}
func (this *BinaryIndexedTree) lowbit(x int) int {
return x & -x
}
func (this *BinaryIndexedTree) update(x, delta int) {
for x <= this.n {
this.c[x] += delta
x += this.lowbit(x)
}
}
func (this *BinaryIndexedTree) query(x int) int {
s := 0
for x > 0 {
s += this.c[x]
x -= this.lowbit(x)
}
return s
}
func minInteger(num string, k int) string {
pos := make([][]int, 10)
for i, c := range num {
pos[c-'0'] = append(pos[c-'0'], i+1)
}
n := len(num)
tree := newBinaryIndexedTree(n)
var ans strings.Builder
for i := 1; i <= n; i++ {
for v := 0; v < 10; v++ {
if len(pos[v]) > 0 {
j := pos[v][0]
dist := tree.query(n) - tree.query(j) + j - i
if dist <= k {
k -= dist
pos[v] = pos[v][1:]
ans.WriteByte(byte(v + '0'))
tree.update(j, 1)
break
}
}
}
}
return ans.String()
}Complexity comparison
| Approach | Complexity |
|---|---|
| Greedy Approach with Permutation Indices | Time Complexity: O(n*k) in the worst case where k is large. |
| Using Segment Tree for Efficient Minimum Finding | Time Complexity: O(n log n) due to segment tree operations. |
| Default Approach | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Greedy with Binary Indexed Tree | O(n log n) | O(n) | Best general solution. Efficiently tracks index shifts caused by swaps. |
| Greedy with Segment Tree | O(n log n) | O(n) | Useful when implementing range minimum queries and updates together. |
Frequently asked questions
Is Minimum Possible Integer After at Most K Adjacent Swaps On Digits easy or hard?
Minimum Possible Integer After at Most K Adjacent Swaps On Digits is classified as Hard on LeetCode. The difficulty comes from combining greedy digit selection with advanced data structures like Binary Indexed Trees or Segment Trees to handle dynamic index shifts efficiently.
Minimum Possible Integer After at Most K Adjacent Swaps On Digits Python/Java solution
Python and Java implementations usually follow the greedy + Fenwick Tree strategy. Maintain queues for each digit (0–9), select the smallest reachable digit, compute its swap cost using prefix sums from the tree, and update the structure after placing the digit. This ensures O(n log n) performance.
How to solve Minimum Possible Integer After at Most K Adjacent Swaps On Digits in O(n)?
A strict O(n) solution is generally not feasible because the algorithm must repeatedly update and query positions after swaps. Efficient implementations rely on a Binary Indexed Tree or Segment Tree, which reduces operations to O(log n). The resulting complexity becomes O(n log n), which is considered optimal for this problem.
What is the best approach for Minimum Possible Integer After at Most K Adjacent Swaps On Digits?
The best approach uses a greedy strategy combined with a Binary Indexed Tree (Fenwick Tree). At each position, choose the smallest digit that can be moved to the front using at most k swaps. The Fenwick Tree tracks how many digits have already shifted so the effective swap distance can be computed quickly. This solution runs in O(n log n) time with O(n) space.
Is Minimum Possible Integer After at Most K Adjacent Swaps On Digits asked at Google/Amazon/Meta?
Problems involving greedy digit selection with Fenwick Trees or segment trees frequently appear in interviews at companies like Google, Amazon, and Meta. This specific LeetCode problem is considered a high-level interview question because it combines greedy reasoning with advanced data structures.
What data structure is used in Minimum Possible Integer After at Most K Adjacent Swaps On Digits?
The most common data structures used are a Binary Indexed Tree (Fenwick Tree) and sometimes a Segment Tree. They help track how many digits have been removed or shifted so the effective swap distance can be calculated efficiently during the greedy selection process.
What is the time complexity of Minimum Possible Integer After at Most K Adjacent Swaps On Digits?
The optimal solution runs in O(n log n) time. Each digit selection requires logarithmic updates and prefix queries in a Binary Indexed Tree or Segment Tree. Space complexity is O(n) for storing digit positions and maintaining the tree structure.
