|
| 1 | +# [Problem 3474: Lexicographically Smallest Generated String](https://leetcode.com/problems/lexicographically-smallest-generated-string/description/?envType=daily-question) |
| 2 | + |
| 3 | +## Initial thoughts (stream-of-consciousness) |
| 4 | +We need to build a string word of length L = n + m - 1 such that for every i in [0..n-1]: |
| 5 | +- If str1[i] == 'T', then word[i..i+m-1] == str2. |
| 6 | +- If str1[i] == 'F', then word[i..i+m-1] != str2. |
| 7 | + |
| 8 | +"Lexicographically smallest" suggests a greedy fill from left to right choosing smallest possible characters. The 'T' constraints force exact characters on certain positions (overlapping windows must be consistent). After applying those constraints there will be remaining free positions we can set. The only danger is creating any 'F' window that becomes exactly str2. For an 'F' window, it becomes invalid only when its last unassigned position is filled with the specific needed character that makes every position match str2. So track, for each 'F' window, how many assigned positions match and how many positions are still unassigned — then forbid filling the last free slot with the matching character for that window. That suggests an incremental greedy algorithm: fill forced 'T' positions, then for each free position pick smallest letter not forbidden by any 'F' window that would be completed by that choice. |
| 9 | + |
| 10 | +## Refining the problem, round 2 thoughts |
| 11 | +Refinements and details: |
| 12 | +- First apply all 'T' windows and check consistency. If two T-windows force different characters on same position => impossible. |
| 13 | +- Maintain for each window s (0..n-1) two counters: unassigned_count[s] and match_count[s] (how many assigned characters in that window equal the corresponding char in str2). |
| 14 | +- For each window with str1[s] == 'F' and unassigned_count == 1, the single unassigned position pos is forbidden to be assigned the char that would make that window match str2 entirely. Maintain forbid[pos] as a set of letters forbidden there. |
| 15 | +- Iterate positions left-to-right. For any unassigned position pos, choose smallest char 'a'..'z' not in forbid[pos]. After assignment update affected windows s in [pos-m+1 .. pos] ∩ [0..n-1]: decrement their unassigned_count and increment match_count if char equals required str2 char. If any 'F' window's unassigned_count becomes 1, add the appropriate forbid; if any 'F' window's unassigned_count becomes 0 and match_count==m then we have an invalid final string — our forbids should prevent this, but we still check and return "" if it happens. |
| 16 | +- Complexity: initial T filling and initial counting is O(n*m) in worst case (n up to 1e4, m up to 500 -> up to ~5e6 operations), and the greedy filling updates at most m windows per position so overall also O(n*m). This is acceptable. Memory O(L + n) with L = n + m - 1. |
| 17 | + |
| 18 | +Now implement. |
| 19 | + |
| 20 | +## Attempted solution(s) |
| 21 | +```python |
| 22 | +from typing import List |
| 23 | + |
| 24 | +def lexicographicallySmallestGeneratedString(str1: str, str2: str) -> str: |
| 25 | + n = len(str1) |
| 26 | + m = len(str2) |
| 27 | + L = n + m - 1 |
| 28 | + |
| 29 | + # assigned characters of word (None if not assigned yet) |
| 30 | + assigned: List[str] = [None] * L |
| 31 | + |
| 32 | + # 1) Apply 'T' constraints: every 'T' window must equal str2 |
| 33 | + for i, ch in enumerate(str1): |
| 34 | + if ch == 'T': |
| 35 | + # enforce word[i + j] == str2[j] for j in [0..m-1] |
| 36 | + for j in range(m): |
| 37 | + pos = i + j |
| 38 | + c = str2[j] |
| 39 | + if assigned[pos] is None: |
| 40 | + assigned[pos] = c |
| 41 | + elif assigned[pos] != c: |
| 42 | + # conflict |
| 43 | + return "" |
| 44 | + |
| 45 | + # 2) For each window s, compute unassigned_count and match_count |
| 46 | + unassigned_count = [0] * n |
| 47 | + match_count = [0] * n |
| 48 | + for s in range(n): |
| 49 | + uc = 0 |
| 50 | + mc = 0 |
| 51 | + for j in range(m): |
| 52 | + pos = s + j |
| 53 | + a = assigned[pos] |
| 54 | + if a is None: |
| 55 | + uc += 1 |
| 56 | + else: |
| 57 | + if a == str2[j]: |
| 58 | + mc += 1 |
| 59 | + unassigned_count[s] = uc |
| 60 | + match_count[s] = mc |
| 61 | + |
| 62 | + # Validate T windows and immediate invalid F windows |
| 63 | + for s in range(n): |
| 64 | + if str1[s] == 'T': |
| 65 | + if unassigned_count[s] != 0 or match_count[s] != m: |
| 66 | + # should not happen since we enforced, but just in case |
| 67 | + return "" |
| 68 | + else: # 'F' |
| 69 | + if unassigned_count[s] == 0 and match_count[s] == m: |
| 70 | + # already equals str2 -> impossible |
| 71 | + return "" |
| 72 | + |
| 73 | + # 3) Build initial forbid sets: for positions that are the last unassigned in some F-window |
| 74 | + forbid: List[set] = [set() for _ in range(L)] |
| 75 | + for s in range(n): |
| 76 | + if str1[s] == 'F' and unassigned_count[s] == 1: |
| 77 | + # find the only unassigned position |
| 78 | + for j in range(m): |
| 79 | + pos = s + j |
| 80 | + if assigned[pos] is None: |
| 81 | + forbid[pos].add(str2[j]) |
| 82 | + break |
| 83 | + |
| 84 | + # 4) Fill remaining positions left-to-right greedily with smallest allowed letter |
| 85 | + from string import ascii_lowercase |
| 86 | + for pos in range(L): |
| 87 | + if assigned[pos] is not None: |
| 88 | + continue |
| 89 | + chosen = None |
| 90 | + for c in ascii_lowercase: |
| 91 | + if c in forbid[pos]: |
| 92 | + continue |
| 93 | + # c is allowed here |
| 94 | + chosen = c |
| 95 | + break |
| 96 | + if chosen is None: |
| 97 | + # no possible char |
| 98 | + return "" |
| 99 | + assigned[pos] = chosen |
| 100 | + |
| 101 | + # update windows that include pos: s in [pos - m + 1, pos] |
| 102 | + s_left = max(0, pos - m + 1) |
| 103 | + s_right = min(n - 1, pos) |
| 104 | + for s in range(s_left, s_right + 1): |
| 105 | + # update only for windows (T windows are already consistent and shouldn't be modified here) |
| 106 | + # decrement unassigned count |
| 107 | + prev_uc = unassigned_count[s] |
| 108 | + prev_mc = match_count[s] |
| 109 | + unassigned_count[s] = prev_uc - 1 |
| 110 | + if chosen == str2[pos - s]: |
| 111 | + match_count[s] = prev_mc + 1 |
| 112 | + else: |
| 113 | + match_count[s] = prev_mc |
| 114 | + |
| 115 | + if str1[s] == 'F': |
| 116 | + # If window completed and matches str2 -> invalid (should have been forbidden) |
| 117 | + if unassigned_count[s] == 0 and match_count[s] == m: |
| 118 | + return "" |
| 119 | + # If window now has exactly one unassigned, add the forbid for that remaining position |
| 120 | + if unassigned_count[s] == 1: |
| 121 | + for j in range(m): |
| 122 | + p = s + j |
| 123 | + if assigned[p] is None: |
| 124 | + forbid[p].add(str2[j]) |
| 125 | + break |
| 126 | + |
| 127 | + return ''.join(assigned) |
| 128 | +``` |
| 129 | +- Notes about approach: |
| 130 | + - First enforce all 'T' windows exactly, detecting conflicts early. |
| 131 | + - Maintain unassigned and match counters per window, and a per-position forbid set which collects letters that would complete some 'F' window to equal str2. |
| 132 | + - Greedily assign smallest letter not forbidden at each free position and update windows. The only way an 'F' window can become invalid at assignment time is if we fill its last unassigned slot with the exact matching char; forbids prevent that. |
| 133 | + - Time complexity: O(n*m) worst case (initial enforcement and per-position updates over overlapping windows). With given constraints (n <= 10^4, m <= 500) this is acceptable (on the order of a few million operations). |
| 134 | + - Space complexity: O(n + m) ~ O(n + m) for arrays of length ~L and per-window counts. |
0 commit comments