LeetCode 2802 - Find The K-th Lucky Number
This problem asks us to find the k-th lucky number, where a lucky number is defined as an integer consisting only of the digits 4 and 7. For example, the sequence of lucky numbers in increasing order starts as 4, 7, 44, 47, 74, 77, 444, and so on.
Difficulty: 🟡 Medium
Topics: Math, String, Bit Manipulation
Solution
Problem Understanding
This problem asks us to find the k-th lucky number, where a lucky number is defined as an integer consisting only of the digits 4 and 7. For example, the sequence of lucky numbers in increasing order starts as 4, 7, 44, 47, 74, 77, 444, and so on.
The input is a single integer k (1 ≤ k ≤ 10^9), which specifies the position in the sorted list of lucky numbers. The output should be the lucky number at that position, represented as a string. Using a string is important because the numbers can become very large and exceed typical integer ranges.
Key points in understanding the problem include:
- Lucky numbers are strictly composed of the digits 4 and 7.
- Numbers are considered in increasing order as if sorted lexicographically by numeric value.
- The input
kcan be as large as 10^9, meaning generating all numbers sequentially would be infeasible. - Edge cases include
k = 1(smallest lucky number) and very largek(requiring many digits).
The problem guarantees k is always valid, so we do not need to handle negative numbers or zero. However, we need a method that scales efficiently to large values of k.
Approaches
Brute Force
A naive approach would generate lucky numbers in order until we reach the k-th one. This could be done using recursion or a queue. For example, starting with "4" and "7", we could append "4" and "7" repeatedly to generate longer lucky numbers and track the sequence.
While this method is correct, it is far too slow for k up to 10^9. Generating numbers sequentially would require keeping track of all previous numbers, leading to exponential time and memory usage.
Optimal Insight
The key insight is that lucky numbers can be represented as binary numbers, with '4' corresponding to 0 and '7' corresponding to 1. For instance, the sequence of lucky numbers is analogous to counting in binary:
1-digit: 4 (0), 7 (1)
2-digit: 44 (00), 47 (01), 74 (10), 77 (11)
3-digit: 444 (000), 447 (001), 474 (010), ...
Using this observation, the problem reduces to:
- Determine how many digits the
k-thnumber has. - Represent
kas a binary number within that digit length. - Map 0 → 4 and 1 → 7 to form the lucky number.
This method is efficient because it avoids generating all previous lucky numbers and directly constructs the answer.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^d) where d = number of digits needed | O(2^d) | Generates all lucky numbers until k-th; infeasible for k ~ 10^9 |
| Optimal | O(log k) | O(log k) | Converts k to binary-like representation to construct the lucky number directly |
Algorithm Walkthrough
- Initialize a variable
length = 1. This will represent the number of digits in the lucky number we are trying to construct. - Compute the total number of lucky numbers with
lengthdigits using the formula2^length. - While
kis greater than the count of numbers with the current length, subtract that count fromkand incrementlength. This identifies the correct number of digits for the k-th lucky number. - Once the correct
lengthis determined, treatk-1as a zero-based index. - Convert
k-1into a binary string of lengthlength. Each bit represents whether to choose 4 (bit = 0) or 7 (bit = 1). - Map each bit to the corresponding lucky digit and concatenate them to form the resulting string.
- Return the resulting string as the k-th lucky number.
Why it works: Every lucky number corresponds uniquely to a binary number where 0 → 4 and 1 → 7. By adjusting k to the right digit length, we ensure that the binary mapping correctly indexes into the sorted sequence of lucky numbers.
Python Solution
class Solution:
def kthLuckyNumber(self, k: int) -> str:
length = 1
count = 2 ** length
while k > count:
k -= count
length += 1
count = 2 ** length
k -= 1 # zero-based index
binary_repr = bin(k)[2:].zfill(length)
result = ''.join('4' if c == '0' else '7' for c in binary_repr)
return result
This Python implementation first determines the number of digits required by subtracting the total number of lucky numbers with fewer digits. Then, it converts the zero-based index k-1 to binary, pads it to the correct length, and maps 0 → 4, 1 → 7 to produce the lucky number string.
Go Solution
import (
"strconv"
"strings"
)
func kthLuckyNumber(k int) string {
length := 1
count := 1 << length // 2^length
for k > count {
k -= count
length++
count = 1 << length
}
k -= 1 // zero-based index
binaryStr := strconv.FormatInt(int64(k), 2)
for len(binaryStr) < length {
binaryStr = "0" + binaryStr
}
var sb strings.Builder
for _, ch := range binaryStr {
if ch == '0' {
sb.WriteByte('4')
} else {
sb.WriteByte('7')
}
}
return sb.String()
}
In Go, we handle integer arithmetic explicitly with bit shifts and use strings.Builder for efficient string concatenation. Padding the binary string ensures it has the correct number of digits before mapping to lucky digits.
Worked Examples
Example 1: k = 4
- length = 1, count = 2 → k > 2, so k = 4 - 2 = 2, length = 2
- count = 4 → k <= 4, stop
- k-1 = 1 → binary "01"
- Map "0" → 4, "1" → 7 → result "47"
Example 2: k = 10
- length = 1, count = 2 → k > 2, k = 8, length = 2
- count = 4 → k > 4, k = 4, length = 3
- count = 8 → k <= 8, stop
- k-1 = 3 → binary "011"
- Map to lucky digits → 4 7 7 → result "477"
Example 3: k = 1000
- Iterate lengths and subtract counts until length = 10, k = some value
- Convert k-1 to binary string of length 10
- Map 0 → 4, 1 → 7 → result "777747447"
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(log k) | Each subtraction reduces k exponentially; converting k to binary and mapping digits is O(log k) |
| Space | O(log k) | Space for binary string and result string of length proportional to log k |
The complexity is efficient because the number of digits in the k-th lucky number grows logarithmically with k. Memory usage is minimal, proportional to the number of digits.
Test Cases
# Provided examples
assert Solution().kthLuckyNumber(4) == "47" # basic 2-digit number
assert Solution().kthLuckyNumber(10) == "477" # 3-digit number
assert Solution().kthLuckyNumber(1000) == "777747447" # large k
# Edge and boundary cases
assert Solution().kthLuckyNumber(1) == "4" # smallest lucky number
assert Solution().kthLuckyNumber(2) == "7" # second smallest
assert Solution().kthLuckyNumber(3) == "44" # first 2-digit lucky number
assert Solution().kthLuckyNumber(15) == "777" # last 3-digit lucky number
| Test | Why |
|---|---|
| k=4 | Checks small 2-digit lucky number |
| k=10 | Verifies correct 3-digit construction |
| k=1000 | Stress test for large k |
| k=1,2,3 | Checks smallest numbers and first 2-digit number |
| k=15 | Ensures last number of a digit length is correct |
Edge Cases
Edge Case 1: k = 1
This tests the very first lucky number. A naive implementation might subtract counts incorrectly or fail to handle the zero-based index adjustment. The algorithm correctly returns "4".
Edge Case 2: k at boundary of digit length
For example, k = 2^d + 1, which is the first number of the next digit length. Subtracting previous counts ensures that we correctly identify the new length and do not misalign the binary mapping.