A string s
is nice if, for every letter of the alphabet that s
contains, it appears both in uppercase and lowercase. For example, "abABB"
is nice because 'A'
and 'a'
appear, and 'B'
and 'b'
appear. However, "abA"
is not because 'b'
appears, but 'B'
does not.
Given a string s
, return the longest substring of s
that is nice. If there are multiple, return the substring of the earliest occurrence. If there are none, return an empty string.
Example 1:
Input: s = "YazaAay" Output: "aAa" Explanation: "aAa" is a nice string because 'A/a' is the only letter of the alphabet in s, and both 'A' and 'a' appear. "aAa" is the longest nice substring.
Example 2:
Input: s = "Bb" Output: "Bb" Explanation: "Bb" is a nice string because both 'B' and 'b' appear. The whole string is a substring.
Example 3:
Input: s = "c" Output: "" Explanation: There are no nice substrings.
Constraints:
1 <= s.length <= 100
s
consists of uppercase and lowercase English letters.最长的美好子字符串。
当一个字符串 s 包含的每一种字母的大写和小写形式 同时 出现在 s 中,就称这个字符串 s 是 美好 字符串。比方说,"abABB" 是美好字符串,因为 'A' 和 'a' 同时出现了,且 'B' 和 'b' 也同时出现了。然而,"abA" 不是美好字符串因为 'b' 出现了,而 'B' 没有出现。
给你一个字符串 s ,请你返回 s 最长的 美好子字符串 。如果有多个答案,请你返回 最早 出现的一个。如果不存在美好子字符串,请你返回一个空字符串。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-nice-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这是一道官方定义的简单题,但是其实不是那么简单。
题意不难理解,乍一看像是滑动窗口可以解决的问题,但是仔细想想,滑动窗口左右指针移动的条件不好找。这里我提供一个分治的做法。首先我们遍历一遍 input 字符串,用 hashset 记录好每个字母出现的次数。我们再次扫描 input 字符串,此时对于我们遇到的每个字母,我们都用 hashset 判断一下这个字母是否大小写都存在,如果不满足这个条件,说明这个字母一定不在满足题意的子串内,换言之满足题意的子串一定在当前这个字母的左边或者右边。这就是分治的来源了。如果当前字母的大小写在 hashset 中都存在,则接着扫描,直到我们找到第一个有大小写缺失的字母,我们从这个字母开始做分治。这个做法时间复杂度最差是O(n^2),比如类似 "aaaaaaaaaa" 这种例子。
这里还有一个细节需要注意,我们需要返回的是最早出现的满足题意的子串,所以分治的时候尽量优先取靠左的子串。
时间O(n^2) - worst case
空间O(n) - hashset
Java实现
1 class Solution { 2 public String longestNiceSubstring(String s) { 3 // corner case 4 if (s.length() < 2) { 5 return ""; 6 } 7 8 // normal case 9 char[] input = s.toCharArray(); 10 HashSet<Character> set = new HashSet<>(); 11 for (char c : input) { 12 set.add(c); 13 } 14 15 for (int i = 0; i < input.length; i++) { 16 char c = input[i]; 17 if (set.contains(Character.toUpperCase(c)) && set.contains(Character.toLowerCase(c))) { 18 continue; 19 } 20 String sub1 = longestNiceSubstring(s.substring(0, i)); 21 String sub2 = longestNiceSubstring(s.substring(i + 1)); 22 return sub1.length() >= sub2.length() ? sub1 : sub2; 23 } 24 return s; 25 } 26 }
官方题解也提供了另外的做法,这里我再分享一个枚举的做法。这里我们需要用两个 for 循环去卡住子串。对于子串中的每个字母,我们都用位运算的办法去找这个字母对应的 ASCII 码与 A 或者 a 的差值,记为 lower 和 upper,取决于到底是大写字母还是小写字母。如果一个字母在子串中大写和小写都存在,那么这个字母的 lower 值和 upper 值应该是相同的。如果此时 for 循环的两个指针之间的距离即是符合题意的子串的长度。我们用这个办法去找到符合题意的最长的子串。注意这个做法中 j 指针是如何走的,跟一般两层的 for 循环有区别。
时间O(n^2)
空间O(1)
Java实现
1 class Solution { 2 public String longestNiceSubstring(String s) { 3 int len = s.length(); 4 int maxPos = 0; 5 int maxLen = 0; 6 for (int i = 0; i < len; i++) { 7 int lower = 0; 8 int upper = 0; 9 for (int j = i; j < len; j++) { 10 if (Character.isLowerCase(s.charAt(j))) { 11 lower |= 1 << (s.charAt(j) - 'a'); 12 } else { 13 upper |= 1 << (s.charAt(j) - 'A'); 14 } 15 if (lower == upper && j - i + 1 > maxLen) { 16 maxPos = i; 17 maxLen = j - i + 1; 18 } 19 } 20 } 21 return s.substring(maxPos, maxPos + maxLen); 22 } 23 }
LeetCode 题目总结