2904. Shortest and Lexicographically Smallest Beautiful String
Problem Description
You are given a binary string s
(containing only '0's and '1's) and a positive integer k
.
A substring is called beautiful if it contains exactly k
ones ('1's).
Your task is to find the shortest beautiful substring. If there are multiple beautiful substrings with the same shortest length, return the lexicographically smallest one among them.
Key requirements:
- The substring must have exactly
k
ones - Among all such substrings, find the shortest one(s)
- If multiple shortest substrings exist, return the lexicographically smallest
Lexicographical comparison: A string a
is lexicographically smaller than string b
if at the first position where they differ, a
has a smaller character than b
. For binary strings, '0' is smaller than '1'.
For example, if s = "100011001"
and k = 3
:
- Some beautiful substrings with 3 ones:
"100011"
,"00011001"
,"10001100"
,"000110"
,"00110"
,"001100"
,"01100"
,"1100"
,"11001"
- The shortest ones have length 5:
"00110"
,"01100"
,"11001"
- Among these,
"00110"
is lexicographically smallest
If no beautiful substring exists (when the total number of '1's in s
is less than k
), return an empty string ""
.
Intuition
Since we need to find the shortest beautiful substring with exactly k
ones, and among all shortest ones we need the lexicographically smallest, we can think about checking all possible substrings.
The key insight is that any beautiful substring must have at least k
characters (since it needs k
ones). So for each starting position i
, we only need to check substrings starting from length k
onwards.
For each starting position i
in the string, we can examine substrings s[i:j]
where j
ranges from i+k
to the end of the string. This ensures we're looking at substrings that are at least k
characters long.
For each substring, we need to:
- Count if it has exactly
k
ones - If it does, compare it with our current answer
The comparison logic follows these priorities:
- First, prefer shorter substrings (smaller length wins)
- If lengths are equal, prefer the lexicographically smaller one
By systematically checking all possible substrings and keeping track of the best one found so far, we're guaranteed to find the optimal answer. The brute force nature of this approach is acceptable because we need to potentially examine all substrings anyway to ensure we find the lexicographically smallest among the shortest beautiful substrings.
The beauty of this approach is its simplicity - we don't need complex data structures or algorithms. We just enumerate all possibilities and keep the best one according to our criteria.
Learn more about Sliding Window patterns.
Solution Approach
The solution uses a nested loop enumeration approach to find all possible substrings and check if they meet our criteria.
Implementation walkthrough:
-
Initialize variables:
n = len(s)
to store the length of the stringans = ""
to keep track of the best beautiful substring found so far
-
Outer loop - Starting position:
- Iterate through each position
i
from0
ton-1
as potential starting points for substrings
- Iterate through each position
-
Inner loop - Ending position:
- For each starting position
i
, iteratej
fromi+k
ton+1
- This ensures we only check substrings with at least
k
characters (since a beautiful substring needs at leastk
characters to containk
ones) - Extract substring
t = s[i:j]
- For each starting position
-
Check if substring is beautiful and better:
- First, check if
t.count("1") == k
to verify it's a beautiful substring - If it is beautiful, check if it's better than our current answer using three conditions:
not ans
: No answer found yet, so this is our first beautiful substringj - i < len(ans)
: Current substring is shorter than the best one found so farj - i == len(ans) and t < ans
: Same length as current best, but lexicographically smaller
- First, check if
-
Update answer:
- If all conditions are met, update
ans = t
- If all conditions are met, update
-
Return result:
- After checking all possible substrings, return
ans
- If no beautiful substring was found,
ans
remains empty string""
- After checking all possible substrings, return
The time complexity is O(n³)
where n
is the length of string s
:
O(n²)
for the two nested loopsO(n)
for counting ones in each substring and string comparison
The space complexity is O(n)
for storing the substring t
and answer ans
.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with s = "11000111"
and k = 2
.
We need to find the shortest substring with exactly 2 ones, and if there are multiple, choose the lexicographically smallest.
Step 1: Initialize
n = 8
(length of string)ans = ""
(no beautiful substring found yet)
Step 2: Check substrings starting at position 0
i = 0
, check substrings of length at leastk = 2
:s[0:2] = "11"
→ count("1") = 2 ✓ Beautiful!- Since
ans = ""
, updateans = "11"
- Since
s[0:3] = "110"
→ count("1") = 2 ✓ Beautiful!- Length 3 > length 2 of current
ans
, skip
- Length 3 > length 2 of current
- Continue checking longer substrings from position 0, but they're all longer than our current best
Step 3: Check substrings starting at position 1
i = 1
, check substrings:s[1:3] = "10"
→ count("1") = 1 ✗ Not beautifuls[1:4] = "100"
→ count("1") = 1 ✗ Not beautifuls[1:5] = "1000"
→ count("1") = 1 ✗ Not beautifuls[1:6] = "10001"
→ count("1") = 2 ✓ Beautiful!- Length 5 > length 2 of current
ans
, skip
- Length 5 > length 2 of current
Step 4: Check substrings starting at position 2
i = 2
, check substrings:s[2:4] = "00"
→ count("1") = 0 ✗ Not beautiful- Continue... no beautiful substrings of length 2 found
Step 5: Continue checking positions 3, 4, 5
- Position 3:
s[3:5] = "00"
→ not beautiful - Position 4:
s[4:6] = "01"
→ count("1") = 1 ✗ Not beautiful - Position 5:
s[5:7] = "11"
→ count("1") = 2 ✓ Beautiful!- Length 2 = length of current
ans = "11"
- Compare lexicographically: "11" == "11", no update needed
- Length 2 = length of current
Step 6: Check position 6
i = 6
, check substrings:s[6:8] = "11"
→ count("1") = 2 ✓ Beautiful!- Same as current answer, no update
Final Result: ans = "11"
The algorithm found all beautiful substrings with exactly 2 ones:
"11"
(positions 0-2, length 2)"110"
(positions 0-3, length 3)"10001"
(positions 1-6, length 5)"11"
(positions 5-7, length 2)"11"
(positions 6-8, length 2)
Among these, the shortest ones have length 2. There are multiple occurrences of "11"
, but they're all the same string, so we return "11"
.
Solution Implementation
1class Solution:
2 def shortestBeautifulSubstring(self, s: str, k: int) -> str:
3 """
4 Find the shortest substring containing exactly k ones.
5 If multiple exist with same length, return the lexicographically smallest.
6
7 Args:
8 s: Binary string containing only '0' and '1'
9 k: Target number of '1's in the substring
10
11 Returns:
12 Shortest beautiful substring or empty string if none exists
13 """
14 n = len(s)
15 result = ""
16
17 # Try all possible starting positions
18 for start in range(n):
19 # Try all possible ending positions (minimum length k to contain k ones)
20 for end in range(start + k, n + 1):
21 # Extract current substring
22 current_substring = s[start:end]
23
24 # Check if current substring has exactly k ones
25 if current_substring.count("1") == k:
26 # Update result if:
27 # 1. Result is empty (first valid substring found)
28 # 2. Current substring is shorter than result
29 # 3. Same length but lexicographically smaller
30 if (not result or
31 end - start < len(result) or
32 (end - start == len(result) and current_substring < result)):
33 result = current_substring
34
35 return result
36
1class Solution {
2 public String shortestBeautifulSubstring(String s, int k) {
3 int stringLength = s.length();
4 String result = "";
5
6 // Iterate through all possible starting positions
7 for (int startIndex = 0; startIndex < stringLength; ++startIndex) {
8 // Try all possible ending positions (minimum length k to contain k ones)
9 for (int endIndex = startIndex + k; endIndex <= stringLength; ++endIndex) {
10 // Extract the current substring
11 String currentSubstring = s.substring(startIndex, endIndex);
12
13 // Count the number of '1's in the current substring
14 int onesCount = 0;
15 for (char character : currentSubstring.toCharArray()) {
16 onesCount += character - '0'; // Convert '1' to 1, '0' to 0
17 }
18
19 // Check if this substring has exactly k ones and is better than current result
20 if (onesCount == k &&
21 (result.isEmpty() || // First valid substring found
22 endIndex - startIndex < result.length() || // Shorter than current result
23 (endIndex - startIndex == result.length() && // Same length but
24 currentSubstring.compareTo(result) < 0))) { // lexicographically smaller
25 result = currentSubstring;
26 }
27 }
28 }
29
30 return result;
31 }
32}
33
1class Solution {
2public:
3 string shortestBeautifulSubstring(string s, int k) {
4 int stringLength = s.size();
5 string result = "";
6
7 // Iterate through all possible starting positions
8 for (int startPos = 0; startPos < stringLength; ++startPos) {
9 // Try all possible ending positions (minimum length is k to have k ones)
10 for (int endPos = startPos + k; endPos <= stringLength; ++endPos) {
11 // Extract the current substring
12 string currentSubstring = s.substr(startPos, endPos - startPos);
13
14 // Count the number of '1's in the current substring
15 int onesCount = count(currentSubstring.begin(), currentSubstring.end(), '1');
16
17 // Check if this substring is a valid beautiful substring
18 if (onesCount == k) {
19 // Update result if:
20 // 1. No result found yet (result is empty)
21 // 2. Current substring is shorter than the existing result
22 // 3. Same length but lexicographically smaller
23 if (result == "" ||
24 endPos - startPos < result.size() ||
25 (endPos - startPos == result.size() && currentSubstring < result)) {
26 result = currentSubstring;
27 }
28 }
29 }
30 }
31
32 return result;
33 }
34};
35
1/**
2 * Finds the shortest beautiful substring containing exactly k '1's.
3 * If multiple substrings have the same length, returns the lexicographically smallest one.
4 * @param s - The input binary string
5 * @param k - The required number of '1's in the substring
6 * @returns The shortest beautiful substring, or empty string if none exists
7 */
8function shortestBeautifulSubstring(s: string, k: number): string {
9 const stringLength: number = s.length;
10 let shortestSubstring: string = '';
11
12 // Iterate through all possible starting positions
13 for (let startIndex: number = 0; startIndex < stringLength; startIndex++) {
14 // Check all substrings starting from startIndex with minimum length k
15 for (let endIndex: number = startIndex + k; endIndex <= stringLength; endIndex++) {
16 // Extract the current substring
17 const currentSubstring: string = s.slice(startIndex, endIndex);
18
19 // Count the number of '1's in the current substring
20 const onesCount: number = currentSubstring
21 .split('')
22 .filter((char: string) => char === '1')
23 .length;
24
25 // Check if current substring is valid and better than the current answer
26 const isValidSubstring: boolean = onesCount === k;
27 const isFirstValid: boolean = shortestSubstring === '';
28 const isShorter: boolean = endIndex - startIndex < shortestSubstring.length;
29 const isSameLengthButSmaller: boolean =
30 endIndex - startIndex === shortestSubstring.length &&
31 currentSubstring < shortestSubstring;
32
33 if (isValidSubstring && (isFirstValid || isShorter || isSameLengthButSmaller)) {
34 shortestSubstring = currentSubstring;
35 }
36 }
37 }
38
39 return shortestSubstring;
40}
41
Time and Space Complexity
Time Complexity: O(n^3)
The algorithm uses two nested loops to generate all possible substrings:
- The outer loop runs from
i = 0
ton-1
, contributingO(n)
- The inner loop runs from
j = i+k
ton
, contributingO(n)
in the worst case - For each substring
s[i:j]
, we perform:- String slicing operation
s[i:j]
:O(j-i)
which isO(n)
in the worst case - Counting "1"s with
t.count("1")
:O(j-i)
which isO(n)
in the worst case - String comparison
t < ans
:O(min(len(t), len(ans)))
which isO(n)
in the worst case
- String slicing operation
Therefore, the total time complexity is O(n) × O(n) × O(n) = O(n^3)
.
Space Complexity: O(n)
The space usage includes:
- Variable
ans
stores a substring of maximum lengthn
:O(n)
- Variable
t
temporarily stores substrings during iteration:O(n)
in the worst case - Other variables (
n
,i
,j
) use constant space:O(1)
The dominant space usage is O(n)
for storing the substrings, resulting in an overall space complexity of O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Inefficient Early Termination
The current solution checks all possible substrings even after finding one with exactly k
ones at a given starting position. Once we find a beautiful substring starting at position i
, we could optimize by breaking the inner loop since any longer substring from the same starting position cannot be better (it won't be shorter).
Problem Code:
for start in range(n):
for end in range(start + k, n + 1):
current_substring = s[start:end]
if current_substring.count("1") == k:
# Updates result but continues checking longer substrings
Improved Solution:
for start in range(n):
for end in range(start + k, n + 1):
current_substring = s[start:end]
if current_substring.count("1") == k:
if (not result or
end - start < len(result) or
(end - start == len(result) and current_substring < result)):
result = current_substring
break # No need to check longer substrings from this start position
2. Redundant String Operations
The solution creates substring objects and counts ones repeatedly, which can be optimized using a running count approach.
Problem: Creating substrings and counting '1's for each substring is expensive.
Optimized Approach Using Sliding Window:
class Solution:
def shortestBeautifulSubstring(self, s: str, k: int) -> str:
n = len(s)
result = ""
min_length = float('inf')
# Use sliding window for better efficiency
for start in range(n):
ones_count = 0
for end in range(start, n):
if s[end] == '1':
ones_count += 1
# When we have exactly k ones
if ones_count == k:
current_length = end - start + 1
current_substring = s[start:end + 1]
# Update result based on length and lexicographical order
if (current_length < min_length or
(current_length == min_length and
(not result or current_substring < result))):
result = current_substring
min_length = current_length
break # Found k ones, no need to extend further
# Early termination if we exceed k ones
elif ones_count > k:
break
return result
3. Memory Inefficiency with Substring Storage
Storing all candidate substrings before comparison wastes memory. The solution should only keep track of the best substring found so far.
Problem: Some implementations might store all beautiful substrings first, then find the minimum:
# Inefficient approach
beautiful_substrings = []
for start in range(n):
for end in range(start + k, n + 1):
substring = s[start:end]
if substring.count("1") == k:
beautiful_substrings.append(substring)
# Then find minimum - wastes memory
if beautiful_substrings:
return min(beautiful_substrings, key=lambda x: (len(x), x))
The original solution correctly avoids this by maintaining only the best answer found so far.
Which data structure is used to implement priority queue?
Recommended Readings
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!