Facebook Pixel

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 or 043 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:

  1. Taking the last digit of the current number
  2. Adding k to get the next digit (if it's ≤ 9)
  3. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Take a partial number from the queue
  2. Look at its last digit
  3. Try adding digits that are k away from it
  4. If the new number has reached length n, add it to our result
  5. 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:

  1. Boundary Calculation: boundary = 10^(n-1) represents the smallest n-digit number. For example, if n = 3, then boundary = 100. Any number >= boundary has at least n digits.

  2. DFS Function: The recursive function dfs(x) takes a partial number x and:

    • Checks if x >= boundary - if true, we've built a complete n-digit number
    • Extracts the last digit: last = x % 10
    • Tries to append valid next digits
  3. 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 to 3 (1+2), making 13
  • From 13, we can go to 1 (3-2) or 5 (3+2), making 131 or 135
  • Both 131 and 135 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 Evaluator

Example 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 subtracting k 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 be O(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)
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of the following is a min heap?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More