Shortest String with All Substrings

Last Updated : 29 Jun, 2026

Given two integers n and k, find the lexicographically smallest string s of minimum possible length such that every possible string of length n formed using characters from 0 to k - 1 appears exactly once as a substring of s.

Examples:

Input: n = 2, k = 2
Output: "00110"
Explanation: The allowed characters are 0 and 1. All possible strings of length 2 are: "00", "01", "10", "11". The string "00110" contains all of them as substrings and has the minimum possible length.

Input: n = 2, k = 3
Output: "0010211220"
Explanation: The allowed characters are 0, 1, and 2. All possible strings of length 2 are: "00", "01", "02", "10", "11", "12", "20", "21", "22". The string "0010211220" contains every possible length-2 string exactly once as a substring and has the minimum possible length.

Try It Yourself
redirect icon

[Expected Approach 1] Backtracking with DFS and Set - O(k^n * k * n) Time and O(k^n * n) Space

The idea is to start with n zeroes and keep adding one digit at a time. At every step, we check the last n - 1 characters and try to append a digit from 0 to k - 1. If the newly formed substring of length n has not been used before, we add it and continue DFS. Once all kn substrings are generated, the current string is a valid minimum length string.

C++
#include <iostream>
#include <string>
#include <unordered_set>
using namespace std;

bool dfs(int n, int k, int total, string& ans, unordered_set<string>& visited) {
    if ((int)visited.size() == total) return true;

    string prefix = n > 1 ? ans.substr(ans.size() - n + 1) : "";

    for (int digit = 0; digit < k; digit++) {
        string curr = prefix + char(digit + '0');

        if (!visited.count(curr)) {
            visited.insert(curr);
            ans.push_back(char(digit + '0'));

            // Continue after adding a new substring.
            if (dfs(n, k, total, ans, visited)) return true;

            visited.erase(curr);
            ans.pop_back();
        }
    }

    return false;
}

string findString(int n, int k) {
    int total = 1;
    for (int i = 0; i < n; i++) total *= k;

    string ans(n, '0');
    unordered_set<string> visited;
    visited.insert(ans);

    dfs(n, k, total, ans, visited);
    return ans;
}

int main() {
    cout << findString(2, 2) << endl;
    cout << findString(2, 3) << endl;
    return 0;
}
Java
import java.util.HashSet;

class GFG {
    static boolean dfs(int n, int k, int total, StringBuilder ans, HashSet<String> visited) {
        if (visited.size() == total) return true;

        String prefix = n > 1 ? ans.substring(ans.length() - n + 1) : "";

        for (int digit = 0; digit < k; digit++) {
            String curr = prefix + (char)(digit + '0');

            if (!visited.contains(curr)) {
                visited.add(curr);
                ans.append((char)(digit + '0'));

                // Continue after adding a new substring.
                if (dfs(n, k, total, ans, visited)) return true;

                visited.remove(curr);
                ans.deleteCharAt(ans.length() - 1);
            }
        }

        return false;
    }

    static String findString(int n, int k) {
        int total = 1;
        for (int i = 0; i < n; i++) total *= k;

        StringBuilder ans = new StringBuilder();
        for (int i = 0; i < n; i++) ans.append('0');

        HashSet<String> visited = new HashSet<>();
        visited.add(ans.toString());

        dfs(n, k, total, ans, visited);
        return ans.toString();
    }

    public static void main(String[] args) {
        System.out.println(findString(2, 2));
        System.out.println(findString(2, 3));
    }
}
Python
def findString(n, k):
    total = k ** n
    ans = ["0"] * n
    visited = {"".join(ans)}

    def dfs():
        if len(visited) == total:
            return True

        prefix = "".join(ans[-(n - 1):]) if n > 1 else ""

        for digit in range(k):
            curr = prefix + str(digit)

            if curr not in visited:
                visited.add(curr)
                ans.append(str(digit))

                # Continue after adding a new substring.
                if dfs():
                    return True

                visited.remove(curr)
                ans.pop()

        return False

    dfs()
    return "".join(ans)


if __name__ == "__main__":
    print(findString(2, 2))
    print(findString(2, 3))
C#
using System;
using System.Text;
using System.Collections.Generic;

class GFG {
    static bool dfs(int n, int k, int total, StringBuilder ans, HashSet<string> visited) {
        if (visited.Count == total) return true;

        string current = ans.ToString();
        string prefix = n > 1 ? current.Substring(current.Length - n + 1) : "";

        for (int digit = 0; digit < k; digit++) {
            string curr = prefix + (char)(digit + '0');

            if (!visited.Contains(curr)) {
                visited.Add(curr);
                ans.Append((char)(digit + '0'));

                // Continue after adding a new substring.
                if (dfs(n, k, total, ans, visited)) return true;

                visited.Remove(curr);
                ans.Length--;
            }
        }

        return false;
    }

    static string findString(int n, int k) {
        int total = 1;
        for (int i = 0; i < n; i++) total *= k;

        StringBuilder ans = new StringBuilder(new string('0', n));
        HashSet<string> visited = new HashSet<string>();
        visited.Add(ans.ToString());

        dfs(n, k, total, ans, visited);
        return ans.ToString();
    }

    static void Main() {
        Console.WriteLine(findString(2, 2));
        Console.WriteLine(findString(2, 3));
    }
}
JavaScript
function dfs(n, k, total, ans, visited) {
    if (visited.size === total) return true;

    const prefix = n > 1 ? ans.slice(ans.length - n + 1).join("") : "";

    for (let digit = 0; digit < k; digit++) {
        const curr = prefix + digit;

        if (!visited.has(curr)) {
            visited.add(curr);
            ans.push(String(digit));

            // Continue after adding a new substring.
            if (dfs(n, k, total, ans, visited)) return true;

            visited.delete(curr);
            ans.pop();
        }
    }

    return false;
}

function findString(n, k) {
    let total = 1;
    for (let i = 0; i < n; i++) total *= k;

    const ans = Array(n).fill("0");
    const visited = new Set();
    visited.add(ans.join(""));

    dfs(n, k, total, ans, visited);
    return ans.join("");
}

// Driver Code
console.log(findString(2, 2));
console.log(findString(2, 3));

Output
00110
0010211220

[Expected Approach 2] Greedy Construction using De Bruijn Sequence - O(k^n * k * n) Time and O(k^n * n) Space

We start with n zeroes and repeatedly try to append the largest possible digit that creates a new substring of length n. This ensures that we keep adding unused substrings until all k^n strings are covered.

Let us understand with an example:

For n = 2, k = 2

  • Start with ans = "00"
  • Visited substrings: {00}
  • Append 1: 001, new substring 01
  • Append 1: 0011, new substring 11
  • Append 0: 00110, new substring 10
  • All 4 substrings are covered: 00, 01, 11, 10.
  • Final string: 00110

Why does this work?

  • We start with a string of size n, and every appended character creates a new substring. We use a hashset to ensure that we append only when a new substring is being generated.
  • By always choosing the extension that hasn’t been seen before, the algorithm guarantees that every n-digit string over {0,…,K−1} will appear exactly once in the shortest possible superstring.
  • The total string length will be: k^n + (n-1) which is the minimum possible length.
C++
#include <iostream>
#include <string>
#include <unordered_set>
#include <algorithm>
using namespace std;

void dfs(const string& node, int k, unordered_set<string>& visited, string& ans) {
    for (int digit = 0; digit < k; digit++) {
        string edge = node + char(digit + '0');

        if (!visited.count(edge)) {
            visited.insert(edge);
            dfs(edge.substr(1), k, visited, ans);

            // Add digit after completing this edge.
            ans.push_back(char(digit + '0'));
        }
    }
}

string findString(int n, int k) {
    string start(n - 1, '0');
    unordered_set<string> visited;
    string ans;

    dfs(start, k, visited, ans);
    ans += start;

    reverse(ans.begin(), ans.end());
    return ans;
}

int main() {
    cout << findString(2, 2) << endl;
    cout << findString(2, 3) << endl;
    return 0;
}
Java
import java.util.HashSet;

class GFG {
    static void dfs(String node, int k, HashSet<String> visited, StringBuilder ans) {
        for (int digit = 0; digit < k; digit++) {
            String edge = node + (char)(digit + '0');

            if (!visited.contains(edge)) {
                visited.add(edge);
                dfs(edge.substring(1), k, visited, ans);

                // Add digit after completing this edge.
                ans.append((char)(digit + '0'));
            }
        }
    }

    static String findString(int n, int k) {
        String start = "0".repeat(n - 1);
        HashSet<String> visited = new HashSet<>();
        StringBuilder ans = new StringBuilder();

        dfs(start, k, visited, ans);
        ans.append(start);

        return ans.reverse().toString();
    }

    public static void main(String[] args) {
        System.out.println(findString(2, 2));
        System.out.println(findString(2, 3));
    }
}
Python
def findString(n, k):
    start = "0" * (n - 1)
    visited = set()
    ans = []

    def dfs(node):
        for digit in range(k):
            edge = node + str(digit)

            if edge not in visited:
                visited.add(edge)
                dfs(edge[1:])

                # Add digit after completing this edge.
                ans.append(str(digit))

    dfs(start)
    ans.append(start)

    return "".join(ans)[::-1]


if __name__ == "__main__":
    print(findString(2, 2))
    print(findString(2, 3))
C#
using System;
using System.Text;
using System.Collections.Generic;

class GFG {
    static void dfs(string node, int k, HashSet<string> visited, StringBuilder ans) {
        for (int digit = 0; digit < k; digit++) {
            string edge = node + (char)(digit + '0');

            if (!visited.Contains(edge)) {
                visited.Add(edge);
                dfs(edge.Substring(1), k, visited, ans);

                // Add digit after completing this edge.
                ans.Append((char)(digit + '0'));
            }
        }
    }

    static string findString(int n, int k) {
        string start = new string('0', n - 1);
        HashSet<string> visited = new HashSet<string>();
        StringBuilder ans = new StringBuilder();

        dfs(start, k, visited, ans);
        ans.Append(start);

        char[] chars = ans.ToString().ToCharArray();
        Array.Reverse(chars);

        return new string(chars);
    }

    static void Main() {
        Console.WriteLine(findString(2, 2));
        Console.WriteLine(findString(2, 3));
    }
}
JavaScript
function dfs(node, k, visited, ans) {
    for (let digit = 0; digit < k; digit++) {
        const edge = node + digit;

        if (!visited.has(edge)) {
            visited.add(edge);
            dfs(edge.slice(1), k, visited, ans);

            // Add digit after completing this edge.
            ans.push(String(digit));
        }
    }
}

function findString(n, k) {
    const start = "0".repeat(n - 1);
    const visited = new Set();
    const ans = [];

    dfs(start, k, visited, ans);
    ans.push(start);

    return ans.join("").split("").reverse().join("");
}

// Driver Code
console.log(findString(2, 2));
console.log(findString(2, 3));

Output
00110
0010211220
Comment