Cedric Buest issued a interesting
little challenge
this morning:
Here is an
interesting coding challenge: write a counter function that counts
from 1 to max but only returns numbers whose digits don't repeat.
For example, part of the output would be:
- 8, 9, 10, 12 (11 is not valid)
- 98, 102, 103 (99, 100 and 101 are not valid)
- 5432, 5436, 5437 (5433, 5434 and 5435 are not valid)
Also:
- Display the biggest jump (in the sequences above, it's 4: 98 -> 102)
- Display the total count of numbers
- Give these two values for max=10000
He welcomed brute-force solutions, but
really the challenge here is in coming up with something more
efficient and elegant. There are basically three general approaches:
Run through all the numbers from 0
to n, test each for no
repeating digits, and track the above statistics while you do it.
This is the brute force method.
Permute
the digits in a fashion that generates numbers sequentially and
track the statistics. Alternatively you could generate them in any
order, sort them, and then calculate the statistics.
Derive
a heuristic that proves a given sequence of numbers will all contain
repeated digits and can therefore be skipped.
I think #2 is probably the ideal fashion, but I didn't think of it
until I was mostly done coding #3.
Finding Repeated Digits
The first step in solving this problem, no matter what the approach, is
to come up with an algorithm to detect repeated digits. Commenters
on Cedric's blog came up with a number of ways to do this, most of
which centered around converting the integer into a string and then
finding repeated characters. This is a rather frighteningly
inefficient approach. There is certainly no need to convert the
number into a string in order to know its digits. A much simpler
approach is allocate a ten element array of booleans initialized to
false, and start generate the digits from lowest to highest by moding
the number by ten. The first time you encounter a digit, you flip
it's associated array element to true. The second time, you exit
because you have detected a repeat. The array is essentially serving
as a thrifty man's map. Here it is in Scala:
def containsRepeatedDigit(i: Long): Boolean = {
val digits = new Array[Boolean](10) // elements default to false
def f(i: Long): Boolean = {
if (i == 0L) false // all digits have been processed
else {
val d = (i % 10L).asInstanceOf[Int]
if (digits(d)) true
else {
digits(d) = true
f(i / 10L)
}
}
}
if (i < 11L) false else f(i)
}
The Heuristic
Consider
the number 2201. It has repeating digits. The question is: What's
the next number without repeating digits? It is 2301. You could
calculate it using brute-force, but you'd end up scanning an extra 99
numbers. Notice that the repetition is in the upper digits. This
means that you will cannot get a number with non-repeating digits
until the second digit (counting from the left) changes. Now
consider the number 2200. In this case changes need to occur in both
the lower digits and the upper digits, however addressing the upper
digits allows us to skip a much larger section of the search space.
Finally, consider 22200. In this case, you still want the second
digit. However, you are searching from the right, so algorithms what
detect the first repeat won't work. Here's Scala code to find the
appropriate digit. Notice that it looks very similar to the repeated
digit test above.
def max(array: Array[Int]): Int = {
def f(idx: Int, m: Int): Int = {
if (idx == array.length) m
else if (array(idx) > m) f(idx + 1, array(idx))
else f(idx + 1, m)
}
f(1, array(0))
}
def repeatedDigit(i: Long): Int = {
val prevIdx = new Array[Int](10)
val recentIdx = new Array[Int](10)
def f(i: Long, idx: Int) {
if (i > 0) {
val d = (i % 10L).asInstanceOf[Int]
if (recentIdx(d) > 0) prevIdx(d) = recentIdx(d)
recentIdx(d) = idx
f(i / 10L, idx + 1)
}
}
f(i, 1)
Math.max(max(prevIdx), 0)
}
Now
that we have an algorithm for finding the highest digit that needs to
be changed, we need one that will take that information and turn it
into the next possible number containing no repeating digits. This
simply requires basic arithmetic.
def nextPossibleNonRepeating(i: Long): Long =
nextPossibleNonRepeating(i, repeatedDigit(i))
def nextPossibleNonRepeating(i: Long, idx: Int): Long = {
if (idx == 0) i + 1L
else {
val p = Math.pow(10.0, (idx - 1).asInstanceOf[Double]).asInstanceOf[Long]
val r = i % p
val d = p - r
i + d
}
}
Given this, it is easy to generate a sequence:
def nextNonRepeating(i: Long): Long = nextNonRepeating(i, repeatedDigit(i))
def nextNonRepeating(i: Long, idx: Int): Long = {
val p = nextPossibleNonRepeating(i, idx)
val d = repeatedDigit(p)
if (d == 0) p else nextNonRepeating(p, d)
}
Solution
Once
this is all done, the solution is pretty straight-forward. It takes
the general form of the function use to generate the next number with
non-repeating digits, only it has to keep track of a bunch of extra
information.
def printNonRepeatingReport(start: Long, stop: Long, last: Long, gapLow: Long,
gapHigh: Long, cnt: Long): Unit = {
if (start > stop) {
println("max: " + last)
println("max gap: " + (gapHigh - gapLow) + " between " +
gapLow + " and " + gapHigh)
println("count: " + cnt)
} else {
val d = repeatedDigit(start)
if (d == 0L) {
//println(start)
val gap = start - last
val (gl, gh) = if (gap > (gapHigh - gapLow)) (last, start)
else (gapLow, gapHigh)
printNonRepeatingReport(start + 1L, stop, start, gl, gh, cnt + 1L)
} else {
printNonRepeatingReport(nextPossibleNonRepeating(start, d), stop, last,
gapLow, gapHigh, cnt)
}
}
}
Results
I'm not going to list all the numbers here, just the statistics for
1-10,000:
- max: 9,876
- max gap: 105 between 1,098 and 1,203
- count: 5,274
Of course I haven't checked it against
a brute-force solution or other posted solution, so I owe a
beer/coffee/tea, depending on your persuasion, to anyone who can
point out a bug and provide the solution.
Just for kicks, here's to 10,000,000,000:
- max: 9,876,543,210
- max gap: 104,691,357 between 1,098,765,432 and 1,203,456,789
- count: 8,877,690
Which took 1 minute 23 seconds on my MacBook. Try that with a
brute-force approach.
Sphere: Related Content