easy Longest Nice Substring
Problem Statement
Given a string str, return the longest nice substring of a given string.
A substring is considered nice if for every lowercase letter in the substring, its uppercase counterpart is also present, and vice versa.
If no such string exists, return an empty string.
Examples
-
Example 1:
- Input:
"BbCcXxY" - Expected Output:
"BbCcXx" - Justification: Here,
"BbCcXx"is the longest substring where each letter's uppercase and lowercase forms are present.
- Input:
-
Example 2:
- Input:
"aZAbcD" - Expected Output:
"" - Justification: There is no contiguous substring where each character exists in both its uppercase and lowercase forms.
- Input:
-
Example 3:
- Input:
"qQwWeErR" - Expected Output:
"qQwWeErR" - Justification: The entire string is the longest nice substring since every letter exists in both uppercase and lowercase forms.
- Input:
Constraints:
1 <= s.length <= 100sconsists of uppercase and lowercase English letters.
Try it yourself
Try solving this question here:
✅ Solution Longest Nice Substring
Problem Statement
Given a string str, return the longest nice substring of a given string.
A substring is considered nice if for every lowercase letter in the substring, its uppercase counterpart is also present, and vice versa. Fo example "AaBbB" is a nice string as A and a present, and B and b present.
If there are multiple, return the substring of the earliest occurrence. If no such string exists, return an empty string.
Examples
-
Example 1:
- Input:
"BbCcXxY" - Expected Output:
"BbCcXx" - Justification: Here,
"BbCcXx"is the longest substring where each letter's uppercase and lowercase forms are present.
- Input:
-
Example 2:
- Input:
"aZAbcD" - Expected Output:
"" - Justification: There is no contiguous substring where each character exists in both its uppercase and lowercase forms.
- Input:
-
Example 3:
- Input:
"qQwWeErR" - Expected Output:
"qQwWeErR" - Justification: The entire string is the longest nice substring since every letter exists in both uppercase and lowercase forms.
- Input:
Constraints:
1 <= str.length <= 100strconsists of uppercase and lowercase English letters.
Solution
To solve the "Longest Nice Substring" problem, we utilize a divide and conquer strategy. The core idea is to identify characters in the string that do not satisfy the "nice" condition—specifically, characters that do not have both their uppercase and lowercase counterparts present in the string. By iterating through the string, we locate the first such character and use it as a delimiter to split the string into two substrings: one to the left of the offending character and one to the right. This splitting ensures that any valid "nice" substring must lie entirely within one of these two segments, as the delimiter itself cannot be part of a "nice" substring.
Once the string is split, the algorithm recursively applies the same process to each of the resulting substrings. This recursive decomposition continues until all substrings are either "nice" or empty. During each recursive call, the algorithm compares the lengths of the "nice" substrings obtained from the left and right segments and returns the longer one. If no "nice" substring is found in any segment, an empty string is returned. This approach guarantees that the longest possible "nice" substring is identified by systematically eliminating invalid characters and exploring all potential valid segments.
Step-by-Step Algorithm:
-
Base Condition:
- If the input string
strhas fewer than 2 characters, return an empty string.- This step is required as "nice" substring requires each character to have both uppercase and lowercase forms, which is impossible with fewer than 2 characters.
- If the input string
-
Check for Nice Substring and Divide if Not Nice:
-
Convert the string
strto a character arrayarr. -
Create a set
setcontaining all characters inarr. It allows quick checks for the presence of both uppercase and lowercase characters. -
Iterate through each character
cinarr:- If both the uppercase and lowercase forms of
care present inset, continue to the next character. - Else, split the string at the current index
iinto:- If a non-nice character is found, split the string into two parts: left (
sub1) and right (sub2), excluding the offending character. sub1 = str.substring(0, i)(left part)sub2 = str.substring(i + 1)(right part)
- If a non-nice character is found, split the string into two parts: left (
a. Conquer:
- Recursively call
findLongestNiceSubstringonsub1andsub2to find the longest nice substrings in each part. - This step breaks down the problem into smaller subproblems, making it easier to find the longest nice substring.
b. Combine and Return:
- Compare the lengths of the substrings returned from
sub1andsub2. - Return the longer substring, as we need to ensure that the overall longest nice substring is selected.
- If both the uppercase and lowercase forms of
-
-
Return the Entire String if Nice:
- If all characters in the string satisfy the "nice" condition after the loop, return the entire string
str.
- If all characters in the string satisfy the "nice" condition after the loop, return the entire string
Algorithm walkthrough
We will walk through the algorithm with the input string str = "BbCcXxy".
-
Initial Call:
- Method:
findLongestNiceSubstring("BbCcXxy") - Input String:
"BbCcXxy"
- Method:
-
Check Base Condition:
- The string length is 7, which is greater than 2. Proceed to the next steps.
-
Create Character Set:
- Character Array:
['B', 'b', 'C', 'c', 'X', 'x', 'y'] - Character Set:
{B, b, C, c, X, x, y}
- Character Array:
-
Iterate Through Characters:
- Index 0, Character 'B':
- Both 'B' (uppercase) and 'b' (lowercase) are in the set. Continue.
- Index 1, Character 'b':
- Both 'B' and 'b' are in the set. Continue.
- Index 2, Character 'C':
- Both 'C' and 'c' are in the set. Continue.
- Index 3, Character 'c':
- Both 'C' and 'c' are in the set. Continue.
- Index 4, Character 'X':
- Both 'X' and 'x' are in the set. Continue.
- Index 5, Character 'x':
- Both 'X' and 'x' are in the set. Continue.
- Index 6, Character 'y':
- 'y' is present, but 'Y' is not in the set.
- Reason: 'y' does not have its uppercase counterpart, violating the "nice" condition.
- Index 0, Character 'B':
-
Split the String:
- Left Substring (
sub1):"BbCcXx" - Right Substring (
sub2):""(empty)- Reason: The character 'y' violates the "nice" condition, so the longest nice substring must lie in
sub1orsub2.
- Reason: The character 'y' violates the "nice" condition, so the longest nice substring must lie in
- Left Substring (
-
First Recursive Call (
findLongestNiceSubstring("BbCcXx")):- Input String:
"BbCcXx"
- Input String:
-
Check Base Condition:
- The string length is 6, which is greater than 2. Proceed to the next steps.
-
Create Character Set:
- Character Array:
['B', 'b', 'C', 'c', 'X', 'x'] - Character Set:
{B, b, C, c, X, x}
- Character Array:
-
Iterate Through Characters:
- Index 0, Character 'B':
- Both 'B' and 'b' are in the set. Continue.
- Index 1, Character 'b':
- Both 'B' and 'b' are in the set. Continue.
- Index 2, Character 'C':
- Both 'C' and 'c' are in the set. Continue.
- Index 3, Character 'c':
- Both 'C' and 'c' are in the set. Continue.
- Index 4, Character 'X':
- Both 'X' and 'x' are in the set. Continue.
- Index 5, Character 'x':
- Both 'X' and 'x' are in the set. Continue.
- End of String:
- All characters satisfy the "nice" condition.
- Index 0, Character 'B':
-
Return the Entire Substring:
- Since all characters are nice, return
"BbCcXx".- Reason: The whole string satisfies the "nice" condition and is the longest possible.
- Since all characters are nice, return
-
Compare Substrings:
sub1 = "BbCcXx"sub2 = ""- Longest Substring:
"BbCcXx"is longer than"".
Final Output
The longest nice substring of "BbCcXxy" is "BbCcXx".
Code
Here is the code for this algorithm:
import java.util.HashSet;
import java.util.Set;
public class Solution {
public String findLongestNiceSubstring(String str) {
// Base case: If the string has less than 2 characters, it cannot be "nice"
if (str.length() < 2) {
return "";
}
// Convert the string to a character array
char[] arr = str.toCharArray();
// Create a set to store all characters in the string
Set<Character> set = new HashSet<>();
for (char c : arr) {
set.add(c); // Add each character to the set
}
// Loop through the characters in the string
for (int i = 0; i < arr.length; i++) {
char c = arr[i];
// Check if both uppercase and lowercase forms of the character exist
if (
set.contains(Character.toUpperCase(c)) &&
set.contains(Character.toLowerCase(c))
) {
continue; // If they exist, move to the next character
}
// If the character doesn't satisfy the "nice" condition, split the string
// Recursively find the longest nice substring in the left and right parts
String sub1 = findLongestNiceSubstring(str.substring(0, i)); // Left part
String sub2 = findLongestNiceSubstring(str.substring(i + 1)); // Right part
// Return the longer of the two substrings
return sub1.length() >= sub2.length() ? sub1 : sub2;
}
// If all characters satisfy the "nice" condition, return the whole string
return str;
}
public static void main(String[] args) {
Solution sol = new Solution();
// Test cases
System.out.println(sol.findLongestNiceSubstring("BbCcXxY")); // Expected: ""BbCcXx"
System.out.println(sol.findLongestNiceSubstring("aZAbcD")); // Expected: ""
System.out.println(sol.findLongestNiceSubstring("qQwWeErR")); // Expected: "qQwWeErR"
}
}
Complexity Analysis
Time Complexity
The time complexity of the algorithm is driven by two factors: the recursion depth and the work performed at each recursion level.
-
Recursion Depth:
- At each recursive step, the string is split into two parts based on the first character that violates the "nice" condition.
- In the worst case, the string is split character by character, leading to a recursion depth of
, where (n) is the length of the input string.
-
Work at Each Recursion Level:
- At each recursive step, we:
- Convert the string to a character array:
. - Create a set of all characters:
. - Loop through the characters in the string to check if each character is "nice":
.
- Convert the string to a character array:
- Therefore, the work at each recursion level is
.
- At each recursive step, we:
-
Total Work:
- In the worst case, there are (n) recursive levels, and each level involves
work. - This results in a total time complexity of
.
- In the worst case, there are (n) recursive levels, and each level involves
Worst-Case Scenario Example:
- Input:
"aBcDeFgHiJkL"- Every character in the string is "not nice" because its counterpart (uppercase or lowercase) does not exist. The string will be split character by character, leading to
recursive calls and work.
- Every character in the string is "not nice" because its counterpart (uppercase or lowercase) does not exist. The string will be split character by character, leading to
Space Complexity
The space complexity is determined by:
-
Recursion Call Stack:
- The recursion depth can reach up to (n) in the worst case.
- Each recursive call adds a new frame to the stack, resulting in
space usage.
-
Set Storage:
- At each recursion level, a set is created to store all unique characters in the current substring.
- The size of the set is proportional to the length of the substring, which can be at most (n) for the first call. As the recursion progresses, the substring size decreases.
- However, only one set exists at a time per recursion level, so this adds
space.
-
Substring Storage:
- The method
str.substring(start, end)creates new substrings at each recursive step. In the worst case, this results inadditional memory because a new substring is created at each step.
- The method
Total Space Complexity:
- The overall space complexity is dominated by the substring storage and recursion stack.
- Therefore, the space complexity is
in the worst case.
🤖 Don't fully get this? Learn it with Claude
Stuck on Longest Nice Substring? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Progressively stronger hints — you still solve it.
I'm working on the problem **Longest Nice Substring** (DSA). Give me a HINT LADDER: start with the tiniest nudge, then wait. Only reveal the next, stronger hint when I ask. Do NOT show the full solution unless I type 'show solution'. Keep me doing the thinking. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
See the technique, not just code.
Explain the optimal approach to **Longest Nice Substring** with a VISUAL walkthrough: trace it on a small concrete example using ASCII art / a step-by-step diagram, narrate what changes each step, then give time & space complexity with a one-line derivation. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Catch bugs, edge cases, sub-optimality.
I'll paste my solution to **Longest Nice Substring**. Review it for correctness, missed edge cases, and time/space complexity, then coach me toward the optimal — don't just rewrite it. Ask me to paste my code now. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Lock in recognition with look-alikes.
Give me 2 problems that use the SAME underlying pattern as **Longest Nice Substring**. For each, let me attempt first, then review my answer and name the trigger signal that reveals the pattern. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.