题目描述🌍
给你一个字符串 s ,仅反转字符串中的所有元音字母,并返回结果字符串。
元音字母包括 'a'、'e'、'i'、'o'、'u',且可能以大小写两种形式出现。
示例 1:
输入:s = "hello"
输出:"holle"
示例 2:
输入:s = "leetcode"
输出:"leotcede"
提示:
1 <= s.length <= 3 * 105s由 可打印的 ASCII 字符组成
双指针🎼
解题思路
历经上一题,这题很容易想到使用双指针来求解:一个指向数组开头,一个指向数组末尾,移动过程中一旦双方都指向元音字母【移动过程类似快排】,就交换。
因为元音字母的个数是固定的,所以我们可以使用「数组」或者「哈希表」来存储。
代码
Java
class Solution {
public String reverseVowels(String s) {
char[] array = s.toCharArray();
int length = s.length();
int left = 0;
int right = length - 1;
while (left < right) {
// 找到数组左边第一个元音字母
while (left < length && !isVowels(array[left]))
left++;
// 找到数组右边第一个元音字母
while (right >= 0 && !isVowels(array[right]))
right--;
if (left < right) {
char temp = array[left];
array[left] = array[right];
array[right] = temp;
}
left++;
right--;
}
// 将 char[] 转化为 String; 也可以是 return new String(array);
return String.valueOf(array);
}
// 判断是否为元音字母
private boolean isVowels(char character) {
return "aeiouAEIOU".indexOf(character) >= 0;
}
}
C++
class Solution {
public:
string reverseVowels(string s) {
int length = s.size();
int left = 0;
int right = length - 1;
while (left < right) {
while (left < length && !isVowel(s[left]))
++left;
while (right >= 0 && !isVowel(s[right]))
--right;
if (left < right) {
swap(s[left], s[right]);
++left;
--right;
}
}
return s;
}
bool isVowel(char c) {
string s = "aeiouAEIOU";
// string::npos 表示字符串不存在的位置
return s.find(c) != string::npos;
}
};
时间复杂度:
空间复杂度:
递归🪓
解题思路
能用「迭代」实现的肯定能用「递归」实现(反之不能),稍微贴下代码。
鉴于「双指针」解法使用数组存储,「递归」就用哈希表存储吧。
代码
Java
class Solution {
// 元音字母列表[注意构造函数的实参形式]
private Set<Character> hashSet = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));
// String s => char[] arr
private char[] arr;
public String reverseVowels(String s) {
this.arr = s.toCharArray();
vowelsRecursion(0, s.length() - 1);
return String.valueOf(arr);
}
// 交换左右元音字母
private void vowelsRecursion(int left, int right) {
if (left >= right)
return;
// 是否找到元音字母
boolean isLeft = hashSet.contains(arr[left]);
boolean isRight = hashSet.contains(arr[right]);
if (isLeft && isRight) {
char temp = arr[left];
arr[left++] = arr[right];
arr[right--] = temp;
}
vowelsRecursion(isLeft ? left : left + 1, isRight ? right : right - 1);
}
}
C++
class Solution {
public:
unordered_set<char> vowels{'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'};
string reverseVowels(string s) {
vowelsRecursion(s, 0, s.size() - 1);
return s;
}
void vowelsRecursion(string &s, int left, int right) {
if (left >= right)
return;
bool is_left = vowels.find(s[left]) != vowels.end();
bool is_right = vowels.find(s[right]) != vowels.end();
if (is_left && is_right)
swap(s[left++], s[right--]);
vowelsRecursion(s, is_left ? left : left + 1, is_right ? right : right - 1);
}
};
时间复杂度:
空间复杂度:
双指针 & ASCII 表🚀
未曾设想的道路(明明以前用过这种方法😭),这题的思路源于「三叶姐」的题解,三叶姐 yyds!
前两种方法在判断是否为元音字母的时候,「Java」都需要调用方法 indexOf() 或 contains(),而调用函数也是需要一定的开销(虽然不大),所以存不存在一种不需要调用函数即可判断是否为元音字母的解法呢?
题目提到 s 是由可打印的 ASCII 码组成,而每一个元音字母在 ASCII 表中又具有唯一性,所以我们不妨通过 ASCII 码表来解决这题。
思路
还是用双指针求解,不过寻找元音字母的方式稍微发生改变。
⭐分析:每一个 char 型字符都可以转化为对应的 int 型数字,我们可以设置一个容量 128 的 boolean[],每一个位置对应 ASCII 码表中的某一个字符。将元音字母在该 boolean[] 中的位置全设为 true,在后续遍历 char[] 过程中,一旦发现当前元素对应的 boolean 值为 true,那么该字符就是元音字母了。
代码
Java
class Solution {
// 128 个 ASCII 字符
static boolean[] hash = new boolean[128];
static char[] vowels = {'a', 'e', 'i', 'o', 'u'};
static {
// 将元音字母对应的索引值设为 true
for (char vowel : vowels) {
// ASCII 第一位为空字符: ' '
hash[vowel - ' '] = hash[Character.toUpperCase(vowel) - ' '] = true;
}
}
public String reverseVowels(String s) {
char[] array = s.toCharArray();
int left = 0;
int right = array.length - 1;
while (left < right) {
// 如果两个皆为元音字母, 就交换
if (hash[array[left] - ' '] && hash[array[right] - ' ']) {
swap(array, left++, right--);
} else {
if (!hash[array[left] - ' '])
left++;
if (!hash[array[right] - ' '])
right--;
}
}
return String.valueOf(array);
}
private void swap(char[] ch, int lo, int hi) {
char temp = ch[lo];
ch[lo] = ch[hi];
ch[hi] = temp;
}
}
C++
class Solution {
public:
bool ascii[128];
char vowel[5] = {'a', 'e', 'i', 'o', 'u'};
string reverseVowels(string s) {
// 将元音字母对应的索引值设为 true
for (char v: vowel) {
ascii[v - ' '] = ascii[toupper(v) - ' '] = true;
}
int left = 0;
int right = s.size() - 1;
while (left < right) {
if (ascii[s[left] - ' '] && ascii[s[right] - ' ']) {
swap(s[left++], s[right--]);
} else {
if (!ascii[s[left] - ' '])
++left;
if (!ascii[s[right] - ' '])
--right;
}
}
return s;
}
};
时间复杂度:
空间复杂度:
知识点🌓
「C++」string::npos
在 MSDN 中有如下说明:
basic_string::npos
// Definition
static const size_type npos = -1;
The constant is the largest representable value of type size_type. It is assuredly larger than max_size();
hence it serves as either a very large value or as a special code.
以上的意思是 string::npos 是一个常量,用于表示 string 类型中不存在的位置。
⭐npos 可以表示 string 的结束位置,是 string::type_size 类型的,也就是 find() 返回的类型。find() 函数在找不到指定值的情况下会返回 string::npos。
int main() {
string s = "hello world!";
string sub = "world";
cout << boolalpha;
cout << "does substr exist? " << (s.find(sub) != string::npos) << endl; // true
return 0;
}
最后🌅
该篇文章为 「LeetCode」 系列的 No.28 篇,在这个系列文章中:
- 尽量给出多种解题思路
- 提供题解的多语言代码实现
- 记录该题涉及的知识点
👨💻争取做到 LeetCode 每日 1 题,所有的题解/代码均收录于 「LeetCode」 仓库,欢迎随时加入我们的刷题小分队!