We need to count how many unique substrings exist in the given string. A substring is any contiguous portion of the string, and we only want to count each unique one once. The empty string does not count.
For example, in "aba", the substrings are: "a", "b", "a", "ab", "ba", "aba". But "a" appears twice, so the distinct count is 5 (not 6).
The total number of substrings in a string of length n is n*(n+1)/2 (picking a start and end position). The challenge is counting how many of those are duplicates. We can enumerate all substrings and add them to a set, or we can use a Trie or rolling hash to avoid creating string objects at all.
1 <= s.length <= 500 → With n up to 500, the total number of substrings is at most 500 * 501 / 2 = 125,250. An O(n^2) approach that processes each substring is fine, so the cost that matters is the per-substring work of hashing or comparison.s consists of lowercase English letters → Only 26 possible characters, so a Trie node needs at most 26 children, which makes a fixed-size array per node practical.Generate every possible substring, add it to a HashSet, and let the set handle deduplication. At the end, the size of the set is the answer.
For a string of length n, we pick every starting index i (0 to n-1) and every ending index j (i+1 to n), extract the substring s[i..j], and add it to the set. Duplicates collapse to a single entry.
The cost is in the substring extraction. Each call creates a new string object, and hashing that string takes time proportional to its length. So while the two loops are O(n^2), the work per substring can be up to O(n), making this O(n^3) overall.
i from 0 to n-1:j from i+1 to n:Loading animation...
The next approach avoids rebuilding and rehashing strings by reusing the work already done for shorter substrings.
Every substring of s is a prefix of some suffix of s. In "abc", the substring "ab" is a prefix of the suffix "abc", and "bc" is a prefix of the suffix "bc". So if we insert all n suffixes of s into a Trie, the set of all root-to-node paths covers exactly the set of substrings of s.
That turns the count into a node-counting problem: insert every suffix into a Trie, and each time inserting a character creates a new node, that node is one distinct substring not seen before. The total number of nodes created equals the number of distinct substrings.
This avoids creating string objects. We walk character by character, either following an existing edge or creating a new node.
Two properties make the node count exact. First, no substring is missed: every substring is a prefix of some suffix we insert, and inserting that suffix walks through the node for every one of its prefixes. Second, no substring is double-counted: a given path from the root spells out one fixed string, so its final node is created once and reused on every later insertion that shares that prefix. When we insert the suffix "aba" and later the suffix "a", the second insertion reuses the 'a' node from the first, creating no node and adding nothing to the count.
i from 0 to n-1 (this represents inserting the suffix starting at i):current to the root of the Trie.s[j] where j goes from i to n-1:c = s[j] - 'a'.current.children[c] is null, create a new node and increment the counter.current to current.children[c].Loading animation...
The Trie uses O(n^2 * 26) space because each node stores an array of 26 children. The next approach represents each substring as a single number instead of a node, which lowers the per-entry memory.
Represent each substring by a hash instead of by a node, and store the hashes in a set. The number of distinct hashes equals the number of distinct substrings, as long as no two different substrings collide to the same hash.
A polynomial rolling hash makes extending a substring cheap. Fixing the start index i and growing the end index j by one, the hash of s[i..j+1] is hash * base + s[j+1], computed from the previous hash in O(1). So for each start position we sweep the end position once, hashing all n*(n+1)/2 substrings in O(n^2) total time.
The risk is a hash collision: two distinct substrings mapping to the same value would be counted once and undercount the answer. To make that improbable, we hash each substring under two independent base-modulus pairs and treat a substring as seen only when both hashes match.
mod1 and mod2, and two bases base1 and base2.i from 0 to n-1:hash1 = 0 and hash2 = 0.j from i to n-1:hash1 = (hash1 * base1 + s[j]) % mod1.hash2 = (hash2 * base2 + s[j]) % mod2.Loading animation...