From 5024cafc9bca93d96a5fbcd0b19a4a9bc55cd99f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 7 Sep 2026 05:02:44 +0000 Subject: [PATCH] feat: add solution for 0940. Distinct Subsequences II --- .../analysis_daily_20260907.md | 86 +++++++++++ .../0940-distinct-subsequences-ii/problem.md | 55 +++++++ .../solution_daily_20260907.go | 47 ++++++ .../solution_daily_20260907_test.go | 138 ++++++++++++++++++ 4 files changed, 326 insertions(+) create mode 100644 problems/0940-distinct-subsequences-ii/analysis_daily_20260907.md create mode 100644 problems/0940-distinct-subsequences-ii/problem.md create mode 100644 problems/0940-distinct-subsequences-ii/solution_daily_20260907.go create mode 100644 problems/0940-distinct-subsequences-ii/solution_daily_20260907_test.go diff --git a/problems/0940-distinct-subsequences-ii/analysis_daily_20260907.md b/problems/0940-distinct-subsequences-ii/analysis_daily_20260907.md new file mode 100644 index 0000000..99d5960 --- /dev/null +++ b/problems/0940-distinct-subsequences-ii/analysis_daily_20260907.md @@ -0,0 +1,86 @@ +# 0940. Distinct Subsequences II + +[LeetCode Link](https://leetcode.com/problems/distinct-subsequences-ii/) + +Difficulty: Hard +Topics: String, Dynamic Programming +Acceptance Rate: 47.8% + +## Hints + +### Hint 1 + +Enumerating subsequences is hopeless: a string of length 2000 has up to `2^2000` of them, and the problem asks for an answer modulo `10^9 + 7` precisely because it wants you to *count* rather than *build*. Whenever you must count objects that are built one character at a time, think about scanning the string left to right and maintaining a running count that you can extend by one character at each step. That is the shape of a dynamic programming solution. + +The hard part is not the counting — it is the word **distinct**. Try the naive recurrence "each new character doubles the count, plus one for the character alone" on `"aa"` and see exactly where it breaks. + +### Hint 2 + +A single scalar `dp[i] = number of distinct subsequences of s[0..i]` is not enough state. When you append a character `c`, you need to know how many of the subsequences you already counted would collide with the new ones you are about to create — and collisions only happen with subsequences that *end in `c`*. + +So refine your state: instead of one number, keep 26 numbers, one per lowercase letter. Ask yourself what the natural definition is such that appending `c` only touches the bucket for `c`. + +### Hint 3 + +Define `dp[c]` = the number of distinct non-empty subsequences of the prefix processed so far that **end with the character `c`**. + +Now the recurrence is clean. When you process a new character `c`, every distinct subsequence seen so far can be extended by `c`, and `c` can also stand alone: + +``` +dp[c] = (dp['a'] + dp['b'] + ... + dp['z']) + 1 +``` + +Note this is an **assignment, not an addition**. The new value *replaces* the old `dp[c]`, and that replacement is exactly what removes the duplicates: any subsequence ending in `c` that existed before is regenerated by this formula (it was some prefix subsequence plus a `c`, or the bare `"c"`), so counting it again would double-count. Overwriting keeps each distinct string counted once, attributed to its *last* occurrence of `c`. + +The answer is the sum of all 26 buckets at the end. Every distinct subsequence ends in exactly one letter, so the buckets partition the answer set — no double counting across buckets. + +## Approach + +Scan the string once, left to right, maintaining an array `dp[26]` where `dp[c]` is the number of distinct non-empty subsequences of the prefix seen so far whose **last character** is `c`. + +Because each distinct subsequence has exactly one last character, these 26 buckets are a partition of the set we want to count, and the answer is `sum(dp)`. + +**The transition.** Suppose we have processed some prefix and now read character `c`. Which distinct subsequences of the new prefix end with `c`? Each such string is `t + c` where `t` is either empty or a distinct subsequence of the prefix. So the count is: + +``` +dp[c] = total + 1 where total = sum(dp[0..25]) before this step +``` + +The `+1` accounts for `t` being empty, i.e. the single-character string `"c"`. + +**Why overwriting is correct.** This is the crux. If `c` appeared earlier, `dp[c]` already held a value. But every string counted there is also counted by the new formula: it had the form `t + c` for some `t` that was a subsequence of an even earlier prefix, and any subsequence of an earlier prefix is still a subsequence of the current one. So the new value is a *superset* count, and adding instead of assigning would count those strings twice. Assignment canonicalizes each distinct subsequence to the **last** position of its final character. + +**Worked example: `s = "aba"`.** + +| step | char | `total` before | update | `dp[a]` | `dp[b]` | +|------|------|---------------|--------|---------|---------| +| init | — | 0 | — | 0 | 0 | +| 1 | `a` | 0 | `dp[a] = 0 + 1` | 1 | 0 | +| 2 | `b` | 1 | `dp[b] = 1 + 1` | 1 | 2 | +| 3 | `a` | 3 | `dp[a] = 3 + 1` | 4 | 2 | + +Answer = `4 + 2 = 6`. The bucket for `a` holds `{"a", "aa", "ba", "aba"}` and the bucket for `b` holds `{"b", "ab"}` — exactly the six strings the problem lists, with no duplicates. Notice how step 3 overwrote the `1` from step 1: the string `"a"` is counted once, attributed to the second `a`. + +Contrast with `s = "aaa"`: step 1 sets `dp[a] = 1`, step 2 sets `dp[a] = 1 + 1 = 2`, step 3 sets `dp[a] = 2 + 1 = 3`. The answer is `3`, not `7`, because the overwrite discards the stale counts each time. + +**Modular arithmetic.** All values are kept modulo `10^9 + 7`. Since we only ever add, no subtraction is involved and no negative-value fixup is needed. (A common alternative formulation uses `dp[i] = 2*dp[i-1] - dp[last[c]-1]`, which *does* subtract and therefore needs a `(x % M + M) % M` correction — the 26-bucket version avoids that trap entirely.) + +Summing 26 buckets on every character makes the scan `O(26n)`. You can drop the inner loop to get a true `O(n)` by carrying `total` as a running variable: when you overwrite `dp[c]` with `newVal`, update `total += newVal - dp[c]`. That reintroduces a subtraction, so it needs the modular fixup; the version below keeps the explicit 26-element sum for clarity, and at `n <= 2000` the difference is irrelevant. + +## Complexity Analysis + +Time Complexity: O(26n) = O(n), where n is the length of `s`. One pass over the string, with a constant 26-element sum per character. For n = 2000 this is roughly 52,000 additions. + +Space Complexity: O(1) — a fixed array of 26 counters, independent of the input length. + +## Edge Cases + +- **Single character (`"a"`)** — the smallest legal input per the constraints (`1 <= s.length`). The answer is `1`. Verifies that the `+1` term fires correctly on an empty `dp`. +- **All identical characters (`"aaa"`)** — the case that kills the naive "double it" recurrence, which would return `7`. The correct answer is `3` (`"a"`, `"aa"`, `"aaa"`). This is the single best test for whether your dedup logic works. +- **A repeated character that is not adjacent (`"aba"`)** — checks that you overwrite based on the character's own bucket rather than on adjacency. Answer `6`, not `7`. +- **All distinct characters (`"abc"`)** — no collisions at all, so the count really is `2^n - 1`. Useful as a sanity check that you are not *over*-deduplicating. +- **The full alphabet** — `2^26 - 1 = 67108863`, still under the modulus, so it validates the counting without the modulus interfering. +- **Overflow / modulus** — with n = 2000 the true count is astronomically larger than any 64-bit integer, so you must reduce at every step, not just at the end. Take the modulus both when writing `dp[c]` and when accumulating the sum. +- **Empty string** — not reachable under the stated constraints, but a robust implementation should return `0` rather than panic, since the count excludes the empty subsequence. + +This is a genuinely hard problem, and the difficulty is concentrated in one line: recognizing that `dp[c]` must be *assigned* rather than *incremented*. Once that clicks the code is about ten lines. If it did not click on your own, that is normal — the "bucket by last character" trick is a reusable pattern (it also shows up in counting distinct subsequences of a given target and in string-DP dedup problems generally), so it is worth committing to memory. diff --git a/problems/0940-distinct-subsequences-ii/problem.md b/problems/0940-distinct-subsequences-ii/problem.md new file mode 100644 index 0000000..33267d0 --- /dev/null +++ b/problems/0940-distinct-subsequences-ii/problem.md @@ -0,0 +1,55 @@ +--- +number: "0940" +frontend_id: "940" +title: "Distinct Subsequences II" +slug: "distinct-subsequences-ii" +difficulty: "Hard" +topics: + - "String" + - "Dynamic Programming" +acceptance_rate: 4781.3 +is_premium: false +created_at: "2026-09-07T05:00:21.946171+00:00" +fetched_at: "2026-09-07T05:00:21.946171+00:00" +link: "https://leetcode.com/problems/distinct-subsequences-ii/" +date: "2026-09-07" +--- + +# 0940. Distinct Subsequences II + +Given a string s, return _the number of**distinct non-empty subsequences** of_ `s`. Since the answer may be very large, return it **modulo** `109 + 7`. + +A **subsequence** of a string is a new string that is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (i.e., `"ace"` is a subsequence of `"_a_ b _c_ d _e_ "` while `"aec"` is not. + + + +**Example 1:** + + + **Input:** s = "abc" + **Output:** 7 + **Explanation:** The 7 distinct subsequences are "a", "b", "c", "ab", "ac", "bc", and "abc". + + +**Example 2:** + + + **Input:** s = "aba" + **Output:** 6 + **Explanation:** The 6 distinct subsequences are "a", "b", "ab", "aa", "ba", and "aba". + + +**Example 3:** + + + **Input:** s = "aaa" + **Output:** 3 + **Explanation:** The 3 distinct subsequences are "a", "aa" and "aaa". + + + + +**Constraints:** + + * `1 <= s.length <= 2000` + * `s` consists of lowercase English letters. diff --git a/problems/0940-distinct-subsequences-ii/solution_daily_20260907.go b/problems/0940-distinct-subsequences-ii/solution_daily_20260907.go new file mode 100644 index 0000000..65dbdbc --- /dev/null +++ b/problems/0940-distinct-subsequences-ii/solution_daily_20260907.go @@ -0,0 +1,47 @@ +package main + +// 0940. Distinct Subsequences II +// https://leetcode.com/problems/distinct-subsequences-ii/ +// +// Approach: dynamic programming with one bucket per lowercase letter. +// +// dp[c] holds the number of distinct non-empty subsequences of the prefix +// processed so far that END with the character c. Every distinct subsequence +// ends in exactly one letter, so the 26 buckets partition the answer set and +// the result is simply their sum. +// +// When we read a character c, the distinct subsequences of the new prefix +// ending in c are exactly {t + c} for t empty or any distinct subsequence of +// the prefix, giving: +// +// dp[c] = (sum of all dp) + 1 +// +// The assignment (rather than +=) is what removes duplicates: any subsequence +// ending in c that was already counted is regenerated by this formula, so +// overwriting attributes each distinct string to the LAST occurrence of its +// final character and counts it exactly once. +// +// Time: O(26n). Space: O(1). + +const distinctSubseqIIMod = 1_000_000_007 + +func distinctSubseqII(s string) int { + // dp[c] = count of distinct subsequences seen so far ending in 'a'+c. + var dp [26]int + + for i := 0; i < len(s); i++ { + total := 0 + for _, count := range dp { + total = (total + count) % distinctSubseqIIMod + } + // Overwrite, do not accumulate: the old value is already included + // in total, and re-adding it would double count. + dp[s[i]-'a'] = (total + 1) % distinctSubseqIIMod + } + + answer := 0 + for _, count := range dp { + answer = (answer + count) % distinctSubseqIIMod + } + return answer +} diff --git a/problems/0940-distinct-subsequences-ii/solution_daily_20260907_test.go b/problems/0940-distinct-subsequences-ii/solution_daily_20260907_test.go new file mode 100644 index 0000000..04141c2 --- /dev/null +++ b/problems/0940-distinct-subsequences-ii/solution_daily_20260907_test.go @@ -0,0 +1,138 @@ +package main + +import ( + "strings" + "testing" +) + +func TestSolution(t *testing.T) { + tests := []struct { + name string + s string + expected int + }{ + { + name: "example 1: all distinct characters, 2^3-1 subsequences", + s: "abc", + expected: 7, + }, + { + name: "example 2: repeated non-adjacent character forces dedup", + s: "aba", + expected: 6, + }, + { + name: "example 3: all identical characters collapse to length count", + s: "aaa", + expected: 3, + }, + { + name: "edge case: single character is the smallest legal input", + s: "a", + expected: 1, + }, + { + name: "edge case: empty string has no non-empty subsequences", + s: "", + expected: 0, + }, + { + name: "edge case: two identical characters, not 3", + s: "zz", + expected: 2, + }, + { + name: "edge case: interleaved repeats abab", + s: "abab", + expected: 11, + }, + { + name: "edge case: full alphabet gives exactly 2^26-1", + s: "abcdefghijklmnopqrstuvwxyz", + expected: 67108863, + }, + { + name: "edge case: alphabet plus a repeated 'a' gives 2^27-2", + s: "abcdefghijklmnopqrstuvwxyza", + expected: 134217726, + }, + { + name: "edge case: max length all identical characters", + s: strings.Repeat("a", 2000), + expected: 2000, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := distinctSubseqII(tt.s) + if result != tt.expected { + t.Errorf("distinctSubseqII(%q) = %v, want %v", truncate(tt.s), result, tt.expected) + } + }) + } +} + +// TestSolutionMatchesBruteForce cross-checks the DP against an exhaustive +// set-based enumeration on every short string over a small alphabet. +func TestSolutionMatchesBruteForce(t *testing.T) { + alphabet := []byte{'a', 'b', 'c'} + + var walk func(prefix []byte) + walk = func(prefix []byte) { + if len(prefix) > 0 { + s := string(prefix) + want := bruteForceDistinctSubseq(s) + if got := distinctSubseqII(s); got != want { + t.Errorf("distinctSubseqII(%q) = %v, want %v", s, got, want) + } + } + if len(prefix) == 8 { + return + } + for _, c := range alphabet { + walk(append(prefix, c)) + } + } + walk(nil) +} + +// TestSolutionLongInputStaysInModulus verifies that the count is reduced at +// every step: the true answer for a 2000-character string overflows any fixed +// width integer, so the result must remain a valid residue. +func TestSolutionLongInputStaysInModulus(t *testing.T) { + s := strings.Repeat("abcdefghijklmnopqrstuvwxyz", 77) // 2002 chars, trimmed below + s = s[:2000] + + got := distinctSubseqII(s) + if got < 0 || got >= 1_000_000_007 { + t.Errorf("distinctSubseqII(<2000 chars>) = %v, want a value in [0, 1000000007)", got) + } + if got == 0 { + t.Errorf("distinctSubseqII(<2000 chars>) = 0, which is almost certainly a bug rather than a genuine residue") + } +} + +// bruteForceDistinctSubseq enumerates every subsequence into a set. Only usable +// for very short strings (2^len subsets). +func bruteForceDistinctSubseq(s string) int { + seen := make(map[string]struct{}) + for mask := 1; mask < 1<