Talentd
FleetCode
Make and follow sheets.Sign in

Extra Characters in a String solution and explanation

MediumArrayDynamic ProgrammingHash TableString8 min read57.4% acceptanceUpdated 9 Apr 2026
Extra Characters in a String - Leetcode 2707 - PythonNeetCodeIO · 21:11 · 25K viewsWatch on YouTube
9 more videos on this problem
  1. Extra Characters in a String - Leetcode 2707 - PythonNeetCodeIO · 21:11 · 25K views
  2. Extra Characters in a String | Recursion | Bottom Up | Easy | Leetcode 2707 | codestorywithMIKcodestorywithMIK · 25:33 · 12K views
  3. Extra Characters in a String | Tree Diagram | Recursion | Memoization | MADE EASY | Leetcode - 2707codestorywithMIK · 22:30 · 9K views
  4. 2707. Extra Characters in a String II DP II Leetcode 2707 II Complete IntuitionAryan Mittal · 15:51 · 7K views
  5. Extra Characters in a String | Leetcode 2707Techdose · 16:43 · 2K views
  6. Unlock the Power of Dynamic Programming! LeetCode 2707. Extra Characters in a String - Live CodingvanAmsen · 8:43 · 1K views
  7. 2707. Extra Characters in a String (Leetcode Medium)Programming Live with Larry · 7:57 · 870 views
  8. Daily Leetcode #1637: Sep 23, 2024 - 2707. Extra Characters in a StringProgramming Live with Larry · 21:25 · 496 views
  9. Extra Characters in a String | Google |Daily Leetcode Challenge | Leetcode 2707 | MediumLet's Code Together · 10:45 · 315 views
  10. Daily Leetcode #1250: Sep 02, 2023 - 2707. Extra Characters in a StringProgramming Live with Larry · 26:15 · 302 views

The problem

You are given a 0-indexed string s and a dictionary of words dictionary. You have to break s into one or more non-overlapping substrings such that each substring is present in dictionary. There may be some extra characters in s which are not present in any of the substrings.

Return the minimum number of extra characters left over if you break up s optimally.

Example 1:

Input: s = "leetscode", dictionary = ["leet","code","leetcode"]
Output: 1
Explanation: We can break s in two substrings: "leet" from index 0 to 3 and "code" from index 5 to 8. There is only 1 unused character (at index 4), so we return 1.

Example 2:

Input: s = "sayhelloworld", dictionary = ["hello","world"]
Output: 3
Explanation: We can break s in two substrings: "hello" from index 3 to 7 and "world" from index 8 to 12. The characters at indices 0, 1, 2 are not used in any substring and thus are considered as extra characters. Hence, we return 3.

Constraints:

  • 1 <= s.length <= 50
  • 1 <= dictionary.length <= 50
  • 1 <= dictionary[i].length <= 50
  • dictionary[i] and s consists of only lowercase English letters
  • dictionary contains distinct words

Approach overview

Problem Overview: You are given a string s and a dictionary of valid words. The goal is to split the string into dictionary words so that the number of leftover characters (characters not part of any word) is minimized.

Approach 1: Dynamic Programming (O(n²) time, O(n) space)

This approach treats the problem as a prefix optimization task using dynamic programming. Create a DP array where dp[i] represents the minimum number of extra characters in the substring starting at index i. Iterate from the end of the string toward the beginning. At each position, assume the current character is extra (1 + dp[i+1]), then check every substring s[i:j]. If the substring exists in the dictionary (stored in a hash table for O(1) lookups), update dp[i] with dp[j]. This effectively tries all valid word breaks while minimizing leftover characters. Time complexity is O(n²) due to checking substrings, and space complexity is O(n) for the DP array.

Approach 2: Trie and Memoization (O(n * L) average time, O(n + totalDictChars) space)

This version improves substring checking using a Trie. Insert all dictionary words into the Trie, then traverse the string while simultaneously walking the Trie. From index i, explore characters forward as long as they match a Trie path. Every time you hit a word-ending node, recursively compute the result for the remaining suffix. Memoization caches results for each index so the same suffix is never recomputed. The key advantage is that substring checks become incremental character traversals instead of repeated string slicing. The time complexity is roughly O(n * L), where L is the maximum dictionary word length, while space complexity includes the memo array plus Trie storage.

Recommended for interviews: The dynamic programming solution is typically expected because it clearly demonstrates optimal substructure and efficient state transitions. It is straightforward to implement and easy to reason about during an interview. The Trie + memoization approach shows deeper knowledge of string optimization and can reduce redundant substring checks when the dictionary contains many overlapping prefixes.

Approach 1: Dynamic Programming Approach

In this approach, we use dynamic programming to solve the problem. We define a DP array where dp[i] represents the minimum number of extra characters from index 0 to i of the string s. Initially, we set dp[0] to 0 because there are no extra characters in an empty prefix. For each index i while iterating through the string s, we check every j from 0 to i-1 and see if substring s[j:i] exists in the dictionary. If yes, we update dp[i] to min(dp[i], dp[j]), otherwise, we set dp[i] to dp[i-1] + 1.

def min_extra_chars(s, dictionary):
    dict_set = set(dictionary)
    n = len(s)
    dp = [n] * (n + 1)
    dp[0] = 0

    for i in range(1, n + 1):
        dp[i] = dp[i - 1] + 1
        for j in range(i):
            if s[j:i] in dict_set:
                dp[i] = min(dp[i], dp[j])
    return dp[n]

s = "leetscode"
dictionary = ["leet", "code", "leetcode"]
print(min_extra_chars(s, dictionary)) # Output: 1
import java.util.*;

public class Main {
    public static int minExtraChars(String s, List<String> dictionary) {
        Set<String> dictSet = new HashSet<>(dictionary);
        int n = s.length();
        int[] dp = new int[n + 1];
        Arrays.fill(dp, n);
        dp[0] = 0;

        for (int i = 1; i <= n; i++) {
            dp[i] = dp[i - 1] + 1;
            for (int j = 0; j < i; j++) {
                if (dictSet.contains(s.substring(j, i))) {
                    dp[i] = Math.min(dp[i], dp[j]);
                }
            }
        }
        return dp[n];
    }

    public static void main(String[] args) {
        String s = "leetscode";
        List<String> dictionary = Arrays.asList("leet", "code", "leetcode");
        int result = minExtraChars(s, dictionary);
        System.out.println(result); // Output: 1
    }
}
#include <iostream>
#include <vector>
#include <unordered_set>

using namespace std;

int minExtraChars(string s, vector<string>& dictionary) {
    unordered_set<string> dictSet(dictionary.begin(), dictionary.end());
    int n = s.length();
    vector<int> dp(n + 1, n);
    dp[0] = 0;

    for (int i = 1; i <= n; ++i) {
        dp[i] = dp[i - 1] + 1;
        for (int j = 0; j < i; ++j) {
            if (dictSet.find(s.substr(j, i - j)) != dictSet.end()) {
                dp[i] = min(dp[i], dp[j]);
            }
        }
    }
    return dp[n];
}

int main() {
    string s = "leetscode";
    vector<string> dictionary = {"leet", "code", "leetcode"};
    int result = minExtraChars(s, dictionary);
    cout << result << endl; // Output: 1
    return 0;
}
function minExtraChars(s, dictionary) {
    const dictSet = new Set(dictionary);
    const n = s.length;
    const dp = Array(n + 1).fill(n);
    dp[0] = 0;

    for (let i = 1; i <= n; i++) {
        dp[i] = dp[i - 1] + 1;
        for (let j = 0; j < i; j++) {
            if (dictSet.has(s.substring(j, i))) {
                dp[i] = Math.min(dp[i], dp[j]);
            }
        }
    }
    return dp[n];
}

const s = "leetscode";
const dictionary = ["leet", "code", "leetcode"];
console.log(minExtraChars(s, dictionary)); // Output: 1
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

#define MAX_LENGTH 51

bool isInDictionary(char *word, char dictionary[][MAX_LENGTH], int dictionarySize) {
    for (int i = 0; i < dictionarySize; i++) {
        if (strcmp(word, dictionary[i]) == 0) {
            return true;
        }
    }
    return false;
}

int minExtraChars(char *s, char dictionary[][MAX_LENGTH], int dictionarySize) {
    int n = strlen(s);
    int dp[n + 1];
    for (int i = 0; i <= n; i++) {
        dp[i] = i;
    }
    dp[0] = 0;
    for (int i = 1; i <= n; i++) {
        dp[i] = dp[i - 1] + 1;
        for (int j = 0; j < i; j++) {
            char sub[i - j + 1];
            strncpy(sub, s + j, i - j);
            sub[i - j] = '\0';
            if (isInDictionary(sub, dictionary, dictionarySize)) {
                if (dp[j] < dp[i]) {
                    dp[i] = dp[j];
                }
            }
        }
    }
    return dp[n];
}

int main() {
    char s[] = "leetscode";
    char dictionary[][MAX_LENGTH] = {"leet", "code", "leetcode"};
    int dictionarySize = 3;
    int result = minExtraChars(s, dictionary, dictionarySize);
    printf("%d\n", result); // Output: 1
    return 0;
}
using System;
using System.Collections.Generic;

public class Solution {
    public int MinExtraChars(string s, IList<string> dictionary) {
        var dictSet = new HashSet<string>(dictionary);
        int n = s.Length;
        var dp = new int[n + 1];
        Array.Fill(dp, n);
        dp[0] = 0;

        for (int i = 1; i <= n; i++) {
            dp[i] = dp[i - 1] + 1;
            for (int j = 0; j < i; j++) {
                if (dictSet.Contains(s.Substring(j, i - j))) {
                    dp[i] = Math.Min(dp[i], dp[j]);
                }
            }
        }
        return dp[n];
    }

    public static void Main(string[] args) {
        var solution = new Solution();
        string s = "leetscode";
        IList<string> dictionary = new List<string>{"leet", "code", "leetcode"};
        Console.WriteLine(solution.MinExtraChars(s, dictionary)); // Output: 1
    }
}

The Python solution employs a set for the dictionary and a dynamic programming array to calculate the minimum extra characters for each prefix length, checking substrings against the dictionary.

Complexity

Time Complexity: O(N^2) since it considers every possible substring.

Space Complexity: O(N) due to the usage of the DP array.

Try this approach in the editor

Approach 2: Trie and Memoization Approach

To solve the problem using a Trie and memoization, we first build a Trie from the dictionary. We then use a recursive function with memoization to attempt to decompose the string s into valid segments. For each position in the string, we check possible substrings against the Trie, saving calculated results to avoid redundant computations.

class TrieNode:
    def __init__(self):
        self.children = {}
        self.isEnd = False

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word):
        node = self.root
        for char in word:
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
        node.isEnd = True

def minExtraHelper(s, idx, root, memo):
    n = len(s)
    if idx == n:
        return 0
    if memo[idx] != -1:
        return memo[idx]
    minExtra = n - idx
    node = root
    for i in range(idx, n):
        if s[i] not in node.children:
            break
        node = node.children[s[i]]
        if node.isEnd:
            minExtra = min(minExtra, minExtraHelper(s, i + 1, root, memo))
    minExtra = min(minExtra, 1 + minExtraHelper(s, idx + 1, root, memo))
    memo[idx] = minExtra
    return minExtra


def minExtraChars(s, dictionary):
    trie = Trie()
    for word in dictionary:
        trie.insert(word)
    n = len(s)
    memo = [-1] * n
    return minExtraHelper(s, 0, trie.root, memo)

s = "leetscode"
dictionary = ["leet", "code", "leetcode"]
print(minExtraChars(s, dictionary)) # Output: 1
import java.util.*;

class TrieNode {
    Map<Character, TrieNode> children;
    boolean isEnd;

    public TrieNode() {
        children = new HashMap<>();
        isEnd = false;
    }
}

class Trie {
    TrieNode root;

    public Trie() {
        root = new TrieNode();
    }

    public void insert(String word) {
        TrieNode node = root;
        for (char c : word.toCharArray()) {
            node = node.children.computeIfAbsent(c, k -> new TrieNode());
        }
        node.isEnd = true;
    }
}

public class Solution {
    private int minExtraHelper(String s, int idx, TrieNode root, int[] memo) {
        int n = s.length();
        if (idx == n) return 0;
        if (memo[idx] != -1) return memo[idx];
        int minExtra = n - idx;
        TrieNode node = root;
        for (int i = idx; i < n; i++) {
            node = node.children.get(s.charAt(i));
            if (node == null) break;
            if (node.isEnd)
                minExtra = Math.min(minExtra, minExtraHelper(s, i + 1, root, memo));
        }
        minExtra = Math.min(minExtra, 1 + minExtraHelper(s, idx + 1, root, memo));
        return memo[idx] = minExtra;
    }

    public int minExtraChars(String s, List<String> dictionary) {
        Trie trie = new Trie();
        for (String word : dictionary)
            trie.insert(word);
        int n = s.length();
        int[] memo = new int[n];
        Arrays.fill(memo, -1);
        return minExtraHelper(s, 0, trie.root, memo);
    }

    public static void main(String[] args) {
        String s = "leetscode";
        List<String> dictionary = Arrays.asList("leet", "code", "leetcode");
        Solution solution = new Solution();
        System.out.println(solution.minExtraChars(s, dictionary)); // Output: 1
    }
}
#include <iostream>
#include <unordered_map>
#include <vector>
#include <string>

using namespace std;

struct TrieNode {
    unordered_map<char, TrieNode*> children;
    bool isEnd = false;
};

class Trie {
public:
    TrieNode* root;
    Trie() { root = new TrieNode(); }
    void insert(string& word) {
        TrieNode* node = root;
        for (char c : word) {
            if (!node->children[c])
                node->children[c] = new TrieNode();
            node = node->children[c];
        }
        node->isEnd = true;
    }
};

int minExtraHelper(string& s, int idx, TrieNode* root, vector<int>& memo) {
    int n = s.size();
    if (idx == n) return 0;
    if (memo[idx] != -1) return memo[idx];
    int minExtra = n - idx;
    TrieNode* node = root;
    for (int i = idx; i < n; ++i) {
        if (node->children.find(s[i]) == node->children.end()) break;
        node = node->children[s[i]];
        if (node->isEnd)
            minExtra = min(minExtra, minExtraHelper(s, i + 1, root, memo));
    }
    minExtra = min(minExtra, 1 + minExtraHelper(s, idx + 1, root, memo));
    return memo[idx] = minExtra;
}

int minExtraChars(string s, vector<string>& dictionary) {
    Trie trie;
    for (string word : dictionary)
        trie.insert(word);
    int n = s.size();
    vector<int> memo(n, -1);
    return minExtraHelper(s, 0, trie.root, memo);
}

int main() {
    string s = "leetscode";
    vector<string> dictionary = {"leet", "code", "leetcode"};
    cout << minExtraChars(s, dictionary) << endl; // Output: 1
    return 0;
}
class TrieNode {
    constructor() {
        this.children = {};
        this.isEnd = false;
    }
}

class Trie {
    constructor() {
        this.root = new TrieNode();
    }

    insert(word) {
        let node = this.root;
        for (const char of word) {
            if (!node.children[char]) {
                node.children[char] = new TrieNode();
            }
            node = node.children[char];
        }
        node.isEnd = true;
    }
}

function minExtraHelper(s, idx, root, memo) {
    const n = s.length;
    if (idx === n) return 0;
    if (memo[idx] !== undefined) return memo[idx];
    let minExtra = n - idx;
    let node = root;
    for (let i = idx; i < n; i++) {
        if (!node.children[s[i]]) break;
        node = node.children[s[i]];
        if (node.isEnd) {
            minExtra = Math.min(minExtra, minExtraHelper(s, i + 1, root, memo));
        }
    }
    minExtra = Math.min(minExtra, 1 + minExtraHelper(s, idx + 1, root, memo));
    return memo[idx] = minExtra;
}

function minExtraChars(s, dictionary) {
    const trie = new Trie();
    for (const word of dictionary) {
        trie.insert(word);
    }
    const n = s.length;
    const memo = {};
    return minExtraHelper(s, 0, trie.root, memo);
}

const s = "leetscode";
const dictionary = ["leet", "code", "leetcode"];
console.log(minExtraChars(s, dictionary)); // Output: 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ALPHABET_SIZE 26

typedef struct TrieNode {
    struct TrieNode *children[ALPHABET_SIZE];
    int isEndOfWord;
} TrieNode;

TrieNode *getNode(void) {
    TrieNode *newNode = (TrieNode *)malloc(sizeof(TrieNode));
    newNode->isEndOfWord = 0;
    for (int i = 0; i < ALPHABET_SIZE; i++)
        newNode->children[i] = NULL;
    return newNode;
}

void insertTrie(TrieNode *root, const char *key) {
    TrieNode *levelNode = root;
    for (int i = 0; key[i]; i++) {
        int index = key[i] - 'a';
        if (!levelNode->children[index])
            levelNode->children[index] = getNode();
        levelNode = levelNode->children[index];
    }
    levelNode->isEndOfWord = 1;
}

int searchTrie(TrieNode *root, const char *str, int start, int end) {
    TrieNode *levelNode = root;
    for (int i = start; i < end; i++) {
        int index = str[i] - 'a';
        if (!levelNode->children[index])
            return 0;
        levelNode = levelNode->children[index];
    }
    return levelNode->isEndOfWord;
}

int minExtraHelper(char *s, int n, int idx, TrieNode *root, int *memo) {
    if (idx == n) return 0;
    if (memo[idx] != -1) return memo[idx];
    int minExtra = n - idx;
    for (int i = idx + 1; i <= n; i++) {
        if (searchTrie(root, s, idx, i)) {
            minExtra = (minExtra < minExtraHelper(s, n, i, root, memo)) ?
                       minExtra : mirror(minExtraHelper(s, n, i, root, memo));
        }
    }
    minExtra = (minExtra < 1 + minExtraHelper(s, n, idx + 1, root, memo)) ?
               minExtra : 1 + minExtraHelper(s, n, idx + 1, root, memo);
    return memo[idx] = minExtra;
}

int minExtraChars(char *s, char dictionary[][51], int dictionarySize) {
    TrieNode *root = getNode();
    for (int i = 0; i < dictionarySize; i++)
        insertTrie(root, dictionary[i]);

    int n = strlen(s);
    int memo[n];
    for (int i = 0; i < n; i++)
        memo[i] = -1;

    return minExtraHelper(s, n, 0, root, memo);
}

int main() {
    char s[] = "leetscode";
    char dictionary[][51] = {"leet", "code", "leetcode"};
    int dictionarySize = 3;
    int result = minExtraChars(s, dictionary, dictionarySize);
    printf("%d\n", result); // Output: 1
    return 0;
}
using System;
using System.Collections.Generic;

class TrieNode {
    public Dictionary<char, TrieNode> Children = new Dictionary<char, TrieNode>();
    public bool IsEnd;
}

class Trie {
    public TrieNode Root = new TrieNode();

    public void Insert(string word) {
        var node = Root;
        foreach (var ch in word) {
            if (!node.Children.ContainsKey(ch))
                node.Children[ch] = new TrieNode();
            node = node.Children[ch];
        }
        node.IsEnd = true;
    }
}

class Solution {
    private int MinExtraHelper(string s, int idx, TrieNode root, int[] memo) {
        int n = s.Length;
        if (idx == n) return 0;
        if (memo[idx] != -1) return memo[idx];
        int minExtra = n - idx;
        TrieNode node = root;
        for (int i = idx; i < n; i++) {
            if (!node.Children.ContainsKey(s[i])) break;
            node = node.Children[s[i]];
            if (node.IsEnd)
                minExtra = Math.Min(minExtra, MinExtraHelper(s, i + 1, root, memo));
        }
        minExtra = Math.Min(minExtra, 1 + MinExtraHelper(s, idx + 1, root, memo));
        return memo[idx] = minExtra;
    }

    public int MinExtraChars(string s, List<string> dictionary) {
        Trie trie = new Trie();
        foreach (var word in dictionary)
            trie.Insert(word);
        int n = s.Length;
        int[] memo = new int[n];
        Array.Fill(memo, -1);
        return MinExtraHelper(s, 0, trie.Root, memo);
    }

    static void Main(string[] args) {
        var s = "leetscode";
        var dictionary = new List<string> { "leet", "code", "leetcode" };
        var solution = new Solution();
        Console.WriteLine(solution.MinExtraChars(s, dictionary)); // Output: 1
    }
}

This Python solution builds a Trie to store dictionary words and uses a recursive function with memoization to evaluate segments. Each segment is checked in the Trie to ensure minimal unaccounted characters.

Complexity

Time Complexity: O(N*M), because of the recursive exploration of string segments and Trie checks.

Space Complexity: O(N + T), N for the memo array and T for the Trie structure.

Try this approach in the editor

Approach 3: Hash Table + Dynamic Programming

We can use a hash table ss to record all words in the dictionary, which allows us to quickly determine whether a string is in the dictionary.

Next, we define f[i] to represent the minimum number of extra characters in the first i characters of string s, initially f[0] = 0.

When i \ge 1, the ith character s[i - 1] can be an extra character, in which case f[i] = f[i - 1] + 1. If there exists an index j \in [0, i - 1] such that s[j..i) is in the hash table ss, then we can take s[j..i) as a word, in which case f[i] = f[j].

In summary, we can get the state transition equation:

$ f[i] = min { f[i - 1] + 1, min_{j \in [0, i - 1]} f[j] }

where i \ge 1, and j \in [0, i - 1] and s[j..i) is in the hash table ss.

The final answer is f[n].

The time complexity is O(n^3 + L), and the space complexity is O(n + L). Here, n is the length of string s, and L$ is the sum of the lengths of all words in the dictionary.

function minExtraChar(s: string, dictionary: string[]): number {
    const ss = new Set(dictionary);
    const n = s.length;
    const f = new Array(n + 1).fill(0);
    for (let i = 1; i <= n; ++i) {
        f[i] = f[i - 1] + 1;
        for (let j = 0; j < i; ++j) {
            if (ss.has(s.substring(j, i))) {
                f[i] = Math.min(f[i], f[j]);
            }
        }
    }
    return f[n];
}
func minExtraChar(s string, dictionary []string) int {
	ss := map[string]bool{}
	for _, w := range dictionary {
		ss[w] = true
	}
	n := len(s)
	f := make([]int, n+1)
	for i := 1; i <= n; i++ {
		f[i] = f[i-1] + 1
		for j := 0; j < i; j++ {
			if ss[s[j:i]] && f[j] < f[i] {
				f[i] = f[j]
			}
		}
	}
	return f[n]
}
use std::collections::HashSet;

impl Solution {
    pub fn min_extra_char(s: String, dictionary: Vec<String>) -> i32 {
        let ss: HashSet<String> = dictionary.into_iter().collect();
        let n = s.len();
        let mut f = vec![0; n + 1];
        for i in 1..=n {
            f[i] = f[i - 1] + 1;
            for j in 0..i {
                if ss.contains(&s[j..i]) {
                    f[i] = f[i].min(f[j]);
                }
            }
        }
        f[n]
    }
}
Try this approach in the editor

Approach 4: Trie + Dynamic Programming

We can use a trie to optimize the time complexity of Solution 1.

Specifically, we first insert each word in the dictionary into the trie root in reverse order, then we define f[i] to represent the minimum number of extra characters in the first i characters of string s, initially f[0] = 0.

When i \ge 1, the ith character s[i - 1] can be an extra character, in which case f[i] = f[i - 1] + 1. We can also enumerate the index j in reverse order in the range [0..i-1], and determine whether s[j..i) is in the trie root. If it exists, then we can take s[j..i) as a word, in which case f[i] = f[j].

The time complexity is O(n^2 + L), and the space complexity is O(n + L times |\Sigma|). Here, n is the length of string s, and L is the sum of the lengths of all words in the dictionary. Additionally, |\Sigma| is the size of the character set. In this problem, the character set is lowercase English letters, so |\Sigma| = 26.

class Node {
    children: (Node | null)[] = Array(26).fill(null);
    isEnd: boolean = false;
}

function minExtraChar(s: string, dictionary: string[]): number {
    const root = new Node();
    for (const w of dictionary) {
        let node = root;
        for (let k = w.length - 1; ~k; --k) {
            const i = w.charCodeAt(k) - 'a'.charCodeAt(0);
            if (node.children[i] === null) {
                node.children[i] = new Node();
            }
            node = node.children[i] as Node;
        }
        node.isEnd = true;
    }

    const n = s.length;
    const f: number[] = Array(n + 1).fill(0);
    for (let i = 1; i <= n; ++i) {
        f[i] = f[i - 1] + 1;
        let node = root;
        for (let j = i - 1; ~j; --j) {
            node = (node.children[s.charCodeAt(j) - 'a'.charCodeAt(0)] as Node) || null;
            if (node === null) {
                break;
            }
            if (node.isEnd && f[j] < f[i]) {
                f[i] = f[j];
            }
        }
    }

    return f[n];
}
type Node struct {
	children [26]*Node
	isEnd    bool
}

func minExtraChar(s string, dictionary []string) int {
	root := &Node{}
	for _, w := range dictionary {
		node := root
		for k := len(w) - 1; k >= 0; k-- {
			i := int(w[k] - 'a')
			if node.children[i] == nil {
				node.children[i] = &Node{}
			}
			node = node.children[i]
		}
		node.isEnd = true
	}

	n := len(s)
	f := make([]int, n+1)
	for i := 1; i <= n; i++ {
		f[i] = f[i-1] + 1
		node := root
		for j := i - 1; j >= 0; j-- {
			node = node.children[int(s[j]-'a')]
			if node == nil {
				break
			}
			if node.isEnd && f[j] < f[i] {
				f[i] = f[j]
			}
		}
	}
	return f[n]
}
Try this approach in the editor

Complexity comparison

ApproachComplexity
Dynamic Programming Approach

Time Complexity: O(N^2) since it considers every possible substring.

Space Complexity: O(N) due to the usage of the DP array.

Trie and Memoization Approach

Time Complexity: O(N*M), because of the recursive exploration of string segments and Trie checks.

Space Complexity: O(N + T), N for the memo array and T for the Trie structure.

Hash Table + Dynamic Programming–
Trie + Dynamic Programming–

Complexity analysis

ApproachTimeSpaceWhen to Use
Dynamic Programming with Hash SetO(n²)O(n)General case. Simple implementation and common interview expectation.
Trie with MemoizationO(n * L)O(n + dictionary size)When dictionary has many overlapping prefixes and repeated substring checks become expensive.

Frequently asked questions

Is Extra Characters in a String easy or hard?

Extra Characters in a String is classified as a Medium problem. The challenge comes from recognizing the dynamic programming structure and efficiently checking dictionary matches within the string.

Extra Characters in a String Python/Java solution

Both Python and Java implementations usually follow the same DP structure: store dictionary words in a set, iterate backward through the string, and compute the minimum extra characters for each position. The logic remains identical across Python, Java, C++, and JavaScript.

How to solve Extra Characters in a String in O(n)?

A strict O(n) solution is generally not achievable because substring checks depend on possible word boundaries. The closest improvement uses a Trie combined with memoization to reduce redundant substring scanning, bringing the complexity closer to O(n * L).

What is the best approach for Extra Characters in a String?

The most practical solution uses dynamic programming with a hash set for dictionary lookups. For each index in the string, compute the minimum extra characters for the remaining suffix. This approach runs in O(n²) time and O(n) space and is commonly expected in coding interviews.

Is Extra Characters in a String asked at Google/Amazon/Meta?

Problems involving dictionary segmentation and dynamic programming on strings are common in interviews at companies like Amazon, Google, and Meta. Variants of this problem appear as word break or string segmentation questions that test DP and string processing skills.

What data structure is used in Extra Characters in a String?

The solution typically uses a hash set for fast dictionary lookups and a dynamic programming array to store optimal results for each index. An alternative solution uses a Trie to efficiently match dictionary prefixes while traversing the string.

What is the time complexity of Extra Characters in a String?

The standard dynamic programming solution runs in O(n²) time because every index may check multiple substring endings. Space complexity is O(n) for the DP array. A Trie-based optimization reduces substring checks and runs roughly in O(n * L), where L is the maximum word length in the dictionary.

Comments

Now write it yourselfClose this page and solve Extra Characters in a String in the editor, without looking.
Practise Extra Characters in a String