Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FInd Next smallest number with same digits Python

I came across this challenge on codewars : https://www.codewars.com/kata/next-smaller-number-with-the-same-digits

It takes in an integer that can be very large. And return the next smallest integer with the same digits and returns -1 if it doesn't exist. I tried attempting this with brute-force, finding all permutations and finding the closest. This is obviously not efficient and results in a timeout.

What would be an efficient way of doing this?

like image 984
Shell1500 Avatar asked Nov 23 '19 06:11

Shell1500


2 Answers

  1. Starting from the right, find the first digit that has at least one smaller digit to its right. We'll call that digit X.
  2. Then find the largest digit that is to the right of X, and is smaller than X. We'll call that digit Y.
  3. Swap X and Y. This makes the number smaller.
  4. Then sort all of the digits to the right of Y in descending order. This makes the number as big as possible, without making it bigger than the original.

For example:

1262347  // original number
  ^  ^
  X  Y

1242367  // after the swap
  ^  ^
  Y  X

1247632  // after sorting digits to the right of Y
  ^
  Y
like image 90
user3386109 Avatar answered Nov 09 '22 00:11

user3386109


Here's a rookie attempt, and complexity is boundless, but still should be better than finding all possible combinations:

def swap(string, index1, index2):
    new_string = list(string)
    new_string[index1], new_string[index2] = string[index2], string[index1]
    new_string = sorted(new_string[:index1]) + new_string[index1:]
    return ''.join(new_string)

def smallest(number):
    if list(number) == sorted(number):
        return -1
    rev_num = number[::-1]
    for i, digit in enumerate(rev_num,0):
        if any(num for num in rev_num[:i] if num < digit):
            _, j = max(((num, j) for j, num in enumerate(rev_num[:i]) if int(num) < int(digit)),key = lambda x:(x[0], x[1]))
            swapped_num = swap(rev_num, i, j)
            if not swapped_num.endswith('0'):
                return int(swapped_num[::-1])
    return -1

x = ['29009','21','531','2071','9','111','135','1027','1231111111111111111111111111111111111111111111111111123456789']
for i in x:
    print(smallest(i))

Output:

20990
12
513
2017
-1
-1
-1
-1
1229876543311111111111111111111111111111111111111111111111111
like image 1
Sayandip Dutta Avatar answered Nov 08 '22 22:11

Sayandip Dutta