967. Numbers With Same Consecutive Differences
Problem Description
You need to find all integers of length n
where the absolute difference between every two consecutive digits is exactly k
.
For example, if n = 3
and k = 7
, then 181
is a valid number because:
- The difference between the first and second digit:
|1 - 8| = 7
- The difference between the second and third digit:
|8 - 1| = 7
The problem requires:
- All returned integers must have exactly
n
digits - No leading zeros are allowed (numbers like
02
or043
are invalid) - The absolute difference between any two consecutive digits must be exactly
k
- You can return the valid numbers in any order
The solution uses a depth-first search (DFS) approach. It starts by trying each digit from 1
to 9
as the first digit (to avoid leading zeros). For each starting digit, it recursively builds the number by:
- Taking the last digit of the current number
- Adding
k
to get the next digit (if it's ≤ 9) - Subtracting
k
to get the next digit (if it's ≥ 0)
The recursion continues until the number reaches the required length n
. The boundary value 10^(n-1)
is used to check when a number has reached n
digits - any number greater than or equal to this boundary has at least n
digits.
The special case where k = 0
is handled by ensuring we don't add the same digit twice (since adding and subtracting 0 would give the same result).
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: We can model this problem as a graph where each valid digit is a node, and edges connect digits that have a difference of exactly
k
. We're essentially traversing through possible digit combinations.
Is it a tree?
- Yes: The structure forms a tree where each path from root to leaf represents a valid number. The root would be the first digit (1-9), and each level represents the next digit position.
DFS
- While the flowchart suggests DFS for trees, we can also use BFS here. BFS would explore all numbers level by level (digit by digit).
However, let's trace an alternative path that leads more directly to BFS:
Is it a graph?
- Yes: As mentioned, this is a graph traversal problem.
Is it a tree?
- No: While it has tree-like properties, we can also view it as a general graph problem where we need to explore all paths of length
n
.
Is the problem related to directed acyclic graphs?
- No: This isn't specifically about DAGs or topological ordering.
Is the problem related to shortest paths?
- No: We're not finding shortest paths; we're finding all valid paths of a specific length.
Does the problem involve connectivity?
- No: We're not checking if nodes are connected or finding components.
Does the problem have small constraints?
- No: The constraint
n
can be up to 9, which means we could have many valid numbers to generate.
BFS
- Yes: BFS is suitable here as we can build numbers level by level, where each level represents adding one more digit to our numbers.
Conclusion: The flowchart suggests using BFS to systematically explore all valid numbers by building them one digit at a time, maintaining a queue of partial numbers and extending them with valid next digits until we reach the required length n
.
Intuition
Think of building these numbers digit by digit, like constructing a path where each step must maintain a specific distance k
from the previous step. When you're at any digit, you only have at most two choices for the next digit: either add k
or subtract k
from the current digit.
For example, if you're at digit 5
and k = 2
, your next digit can only be 3
(5-2) or 7
(5+2). This creates a branching structure where each valid number is a path from the starting digit to the final digit.
The key insight is that we can explore all possibilities systematically. Starting from each possible first digit (1 through 9, since we can't have leading zeros), we expand outward by considering all valid next digits. This is similar to exploring a tree where:
- The root level contains digits 1-9
- Each subsequent level adds one more digit to our numbers
- Branches represent valid transitions (difference of exactly
k
)
Using BFS, we would maintain a queue of partial numbers and repeatedly:
- Take a partial number from the queue
- Look at its last digit
- Try adding digits that are
k
away from it - If the new number has reached length
n
, add it to our result - Otherwise, add it back to the queue for further expansion
The DFS approach in the given solution follows the same logic but explores each path completely before backtracking. It starts with a single digit and recursively adds valid next digits until reaching the required length. The boundary check x >= 10^(n-1)
cleverly determines when we've built a number with exactly n
digits, since any n
-digit number must be at least 10^(n-1)
.
The special handling when k = 0
prevents adding the same digit twice (since both last + 0
and last - 0
would give the same result), avoiding duplicates in our answer.
Learn more about Breadth-First Search and Backtracking patterns.
Solution Approach
The solution uses a DFS (Depth-First Search) approach to systematically build all valid numbers digit by digit. Here's how the implementation works:
Key Components:
-
Boundary Calculation:
boundary = 10^(n-1)
represents the smallestn
-digit number. For example, ifn = 3
, thenboundary = 100
. Any number >=boundary
has at leastn
digits. -
DFS Function: The recursive function
dfs(x)
takes a partial numberx
and:- Checks if
x >= boundary
- if true, we've built a completen
-digit number - Extracts the last digit:
last = x % 10
- Tries to append valid next digits
- Checks if
-
Adding Valid Digits: From the current last digit, we can add:
last + k
(if it's <= 9, ensuring it's a valid single digit)last - k
(if it's >= 0, ensuring it's non-negative)
Implementation Walkthrough:
def dfs(x: int):
if x >= boundary: # We've built an n-digit number
ans.append(x)
return
last = x % 10 # Get the last digit
# Try adding (last + k) as the next digit
if last + k <= 9:
dfs(x * 10 + last + k)
# Try adding (last - k) as the next digit
# k != 0 check prevents duplicates when k = 0
if last - k >= 0 and k != 0:
dfs(x * 10 + last - k)
Main Logic:
- Initialize an empty result list
ans
- For each starting digit from 1 to 9 (no leading zeros), call
dfs(i)
- Each call explores all valid numbers starting with that digit
Example Trace for n = 3, k = 2
:
- Start with digit
1
- From
1
, we can go to3
(1+2), making13
- From
13
, we can go to1
(3-2) or5
(3+2), making131
or135
- Both
131
and135
are >= 100, so they're added to the result
The multiplication by 10 (x * 10 + digit
) effectively appends a digit to the right of the current number. This elegant approach builds the number as an integer rather than manipulating strings.
The time complexity is O(2^n) in the worst case since at each position we might have 2 choices, and space complexity is O(n) for the recursion stack depth.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the solution with n = 3
and k = 7
to find all 3-digit numbers where consecutive digits differ by exactly 7.
Step 1: Initialize
boundary = 10^(3-1) = 100
(smallest 3-digit number)ans = []
(empty result list)
Step 2: Try starting digit 1
- Call
dfs(1)
1 < 100
, so continue building- Last digit = 1
- Can add: 1 + 7 = 8 ✓ (valid digit)
- Can subtract: 1 - 7 = -6 ✗ (negative, invalid)
- Recurse with
dfs(18)
Step 3: From 18
18 < 100
, so continue building- Last digit = 8
- Can add: 8 + 7 = 15 ✗ (not a single digit)
- Can subtract: 8 - 7 = 1 ✓ (valid digit)
- Recurse with
dfs(181)
Step 4: From 181
181 >= 100
✓ (complete 3-digit number!)- Add 181 to
ans
- Return
Step 5: Try starting digit 2
- Call
dfs(2)
- Last digit = 2
- Can add: 2 + 7 = 9 ✓
- Can subtract: 2 - 7 = -5 ✗
- Recurse with
dfs(29)
- From 29: Last digit = 9
- Can add: 9 + 7 = 16 ✗
- Can subtract: 9 - 7 = 2 ✓
dfs(292)
→ 292 >= 100, add to result
Continue for digits 3-9...
- Starting with 7: leads to 707 (7→0→7)
- Starting with 8: leads to 818 (8→1→8)
- Starting with 9: leads to 929 (9→2→9)
Final Result: [181, 292, 707, 818, 929]
Each number maintains |consecutive digit difference| = 7:
- 181: |1-8|=7, |8-1|=7 ✓
- 292: |2-9|=7, |9-2|=7 ✓
- 707: |7-0|=7, |0-7|=7 ✓
Solution Implementation
1class Solution:
2 def numsSameConsecDiff(self, n: int, k: int) -> List[int]:
3 """
4 Find all n-digit integers where the absolute difference between
5 every two consecutive digits is k.
6
7 Args:
8 n: The number of digits in the target integers
9 k: The required absolute difference between consecutive digits
10
11 Returns:
12 List of all valid n-digit integers
13 """
14
15 def dfs(current_number: int) -> None:
16 """
17 Depth-first search to build valid numbers digit by digit.
18
19 Args:
20 current_number: The number being constructed
21 """
22 # If we've reached n digits, add to result
23 if current_number >= min_n_digit_value:
24 result.append(current_number)
25 return
26
27 # Get the last digit of current number
28 last_digit = current_number % 10
29
30 # Try adding a digit that is k more than the last digit
31 if last_digit + k <= 9:
32 dfs(current_number * 10 + last_digit + k)
33
34 # Try adding a digit that is k less than the last digit
35 # Avoid duplicates when k=0
36 if last_digit - k >= 0 and k != 0:
37 dfs(current_number * 10 + last_digit - k)
38
39 # Initialize result list
40 result = []
41
42 # Calculate the minimum n-digit number (e.g., 100 for n=3)
43 min_n_digit_value = 10 ** (n - 1)
44
45 # Start DFS from each digit 1-9 (no leading zeros)
46 for starting_digit in range(1, 10):
47 dfs(starting_digit)
48
49 return result
50
1class Solution {
2 // List to store all valid numbers that satisfy the consecutive difference constraint
3 private List<Integer> result = new ArrayList<>();
4 // Minimum value for n-digit numbers (e.g., 100 for n=3)
5 private int minBoundary;
6 // The required absolute difference between consecutive digits
7 private int difference;
8
9 /**
10 * Finds all n-digit numbers where the absolute difference between
11 * every two consecutive digits is exactly k.
12 *
13 * @param n The number of digits in the target numbers
14 * @param k The required absolute difference between consecutive digits
15 * @return Array of all valid n-digit numbers
16 */
17 public int[] numsSameConsecDiff(int n, int k) {
18 this.difference = k;
19 // Calculate the minimum n-digit number (10^(n-1))
20 this.minBoundary = (int) Math.pow(10, n - 1);
21
22 // Start DFS from each digit 1-9 (no leading zeros allowed)
23 for (int startDigit = 1; startDigit <= 9; startDigit++) {
24 dfs(startDigit);
25 }
26
27 // Convert List<Integer> to int array
28 return result.stream().mapToInt(i -> i).toArray();
29 }
30
31 /**
32 * Recursively builds numbers by appending digits that maintain
33 * the required difference with the last digit.
34 *
35 * @param currentNumber The number being built
36 */
37 private void dfs(int currentNumber) {
38 // Base case: if we've reached an n-digit number, add it to results
39 if (currentNumber >= minBoundary) {
40 result.add(currentNumber);
41 return;
42 }
43
44 // Get the last digit of the current number
45 int lastDigit = currentNumber % 10;
46
47 // Try adding a digit that is k more than the last digit
48 if (lastDigit + difference < 10) {
49 dfs(currentNumber * 10 + lastDigit + difference);
50 }
51
52 // Try adding a digit that is k less than the last digit
53 // Avoid duplicates when k=0 by checking k != 0
54 if (difference != 0 && lastDigit - difference >= 0) {
55 dfs(currentNumber * 10 + lastDigit - difference);
56 }
57 }
58}
59
1class Solution {
2public:
3 vector<int> numsSameConsecDiff(int n, int k) {
4 vector<int> result;
5
6 // Calculate the minimum value for n-digit numbers (e.g., 100 for n=3)
7 int minBoundary = pow(10, n - 1);
8
9 // Define recursive DFS function to build valid numbers
10 auto dfs = [&](this auto&& dfs, int currentNum) {
11 // Base case: if current number has n digits, add to result
12 if (currentNum >= minBoundary) {
13 result.push_back(currentNum);
14 return;
15 }
16
17 // Get the last digit of current number
18 int lastDigit = currentNum % 10;
19
20 // Try adding (lastDigit + k) if it's a valid digit (0-9)
21 if (lastDigit + k < 10) {
22 dfs(currentNum * 10 + lastDigit + k);
23 }
24
25 // Try adding (lastDigit - k) if it's valid and different from above
26 // Check k != 0 to avoid duplicates when k = 0
27 if (k != 0 && lastDigit - k >= 0) {
28 dfs(currentNum * 10 + lastDigit - k);
29 }
30 };
31
32 // Start DFS from each digit 1-9 (leading zeros not allowed)
33 for (int startDigit = 1; startDigit <= 9; ++startDigit) {
34 dfs(startDigit);
35 }
36
37 return result;
38 }
39};
40
1/**
2 * Finds all non-negative integers of length n where the absolute difference
3 * between every two consecutive digits is k.
4 * @param n - The length of the integers to generate
5 * @param k - The absolute difference between consecutive digits
6 * @returns An array of all valid integers
7 */
8function numsSameConsecDiff(n: number, k: number): number[] {
9 const result: number[] = [];
10 // Calculate the minimum value for an n-digit number (e.g., 100 for n=3)
11 const minBoundary: number = Math.pow(10, n - 1);
12
13 /**
14 * Performs depth-first search to build valid numbers digit by digit
15 * @param currentNumber - The number being constructed
16 */
17 const performDFS = (currentNumber: number): void => {
18 // If we've reached an n-digit number, add it to results
19 if (currentNumber >= minBoundary) {
20 result.push(currentNumber);
21 return;
22 }
23
24 // Get the last digit of the current number
25 const lastDigit: number = currentNumber % 10;
26
27 // Try adding k to the last digit if it results in a valid digit (0-9)
28 if (lastDigit + k < 10) {
29 performDFS(currentNumber * 10 + lastDigit + k);
30 }
31
32 // Try subtracting k from the last digit if it results in a valid digit
33 // Only do this when k > 0 to avoid duplicate results when k = 0
34 if (k > 0 && lastDigit - k >= 0) {
35 performDFS(currentNumber * 10 + lastDigit - k);
36 }
37 };
38
39 // Start DFS from each digit 1-9 (no leading zeros allowed)
40 for (let startDigit = 1; startDigit < 10; startDigit++) {
41 performDFS(startDigit);
42 }
43
44 return result;
45}
46
Time and Space Complexity
Time Complexity: O(n × 2^n × |Σ|)
where |Σ| = 9
The analysis breaks down as follows:
- We start DFS from each digit 1-9, giving us
|Σ| = 9
initial calls - At each position, we can potentially branch into 2 directions (adding
k
or subtractingk
to the last digit), though not all branches are valid - The depth of recursion is at most
n
(building an n-digit number) - In the worst case, we explore
O(2^n)
different paths for each starting digit - Each number construction involves
O(n)
operations (checking conditions and building the number digit by digit) - Therefore, the total time complexity is
O(n × 2^n × 9)
=O(n × 2^n × |Σ|)
Space Complexity: O(2^n)
The space complexity consists of:
- The recursion call stack depth is at most
O(n)
- The answer list
ans
stores all valid n-digit numbers, which in the worst case can beO(2^n)
numbers (when we have maximum branching) - Since
O(2^n) > O(n)
, the dominant factor is the storage for the result - Therefore, the total space complexity is
O(2^n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Handling the k=0 Edge Case Incorrectly
The Pitfall: When k=0, both last_digit + k
and last_digit - k
yield the same value. Without proper handling, this leads to duplicate numbers in the result.
Example: For n=2, k=0, starting with digit 1:
- Adding 1+0=1 gives us 11
- Subtracting 1-0=1 also gives us 11
- This creates duplicate 11 in the output
The Solution: Add a condition k != 0
before exploring the subtraction case:
if last_digit - k >= 0 and k != 0: dfs(current_number * 10 + last_digit - k)
2. Incorrect Boundary Check for n-digit Numbers
The Pitfall: Using len(str(current_number)) == n
to check if we've reached n digits is inefficient and can be error-prone. String conversion is slower and less elegant.
The Solution: Use mathematical comparison with 10^(n-1)
:
min_n_digit_value = 10 ** (n - 1) if current_number >= min_n_digit_value: result.append(current_number)
3. Starting DFS from 0
The Pitfall: Including 0 as a starting digit would generate numbers with leading zeros, which are invalid according to the problem constraints.
Wrong approach:
for starting_digit in range(0, 10): # WRONG - includes 0
dfs(starting_digit)
The Solution: Start the loop from 1:
for starting_digit in range(1, 10): # Correct - excludes 0
dfs(starting_digit)
4. Not Validating Next Digit Bounds
The Pitfall: Forgetting to check if the next digit is within the valid range [0, 9] before making the recursive call.
Example of incorrect code:
# WRONG - might try to add digit 10 or -1 dfs(current_number * 10 + last_digit + k) dfs(current_number * 10 + last_digit - k)
The Solution: Always validate bounds:
if last_digit + k <= 9: dfs(current_number * 10 + last_digit + k) if last_digit - k >= 0 and k != 0: dfs(current_number * 10 + last_digit - k)
5. Building Numbers as Strings
The Pitfall: Using string concatenation to build numbers adds unnecessary complexity and overhead:
# Less efficient approach
def dfs(current_str):
if len(current_str) == n:
result.append(int(current_str))
return
last = int(current_str[-1])
# ... continue with string concatenation
The Solution: Use integer arithmetic (multiplication by 10 and addition) which is more efficient and cleaner:
dfs(current_number * 10 + next_digit)
Which of the following is a min heap?
Recommended Readings
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!