题目
链接:https://leetcode-cn.com/problems/reorganize-string
给定一个字符串S,检查是否能重新排布其中的字母,使得两相邻的字符不同。
若可行,输出任意可行的结果。若不可行,返回空字符串。
示例 1:
输入: S = "aab"
输出: "aba"
示例 2:
输入: S = "aaab"
输出: ""
注意:
S 只包含小写字母并且长度在[1, 500]区间内。
基于最大堆的贪心算法
将出现频率次数最多的字母重新排列后不相邻,则可以重新排列字符使得相邻的字母都不相同。如果字母的出现次数过多则无访使得相邻得字母都不相同。
假设字符串长度为n:
- n为偶数时,奇数下标与偶数下标都为n/2个,所以每个字符出现次数不能超过n/2次
- n为奇数时,有(n+1)/2个偶数下标,每个字符得出现次数不能超过(n+1)/2
由于n为偶数时n/2与(n+1)/2相等,所以字符串每个字符最多出现(n+1)/2次
首先统计字符串中字符得出现次数,如果出现的最多次数超过了(n+1)/2则直接返回,否则将字符次数大于0的字符加入最大堆中,每次从最大堆中取出两个字符重构字符串,然后将剩余出现次数大于0的字母重新加入到最大堆中,如果最大堆为空则完成了字符串重构,如果最大堆只剩一个字符则取出该字符拼接到重构字符串最后。
class Solution {
public String reorganizeString(String S) {
if (S.length() < 2)
return S;
int[] counter = new int[26];// 记录字符出现的次数
int maxCount = 0;// 记录出现最多次数字符的频次
int len = S.length();
for (int i = 0; i < len; i++) {
char c = S.charAt(i);
counter[c - 'a']++;
maxCount = Math.max(maxCount, counter[c - 'a']);
}
if (maxCount > (len + 1) / 2) {
return "";
}
// 创建一个最大堆按照字符出现频率排序
PriorityQueue<Character> queue = new PriorityQueue<>(new Comparator<Character>() {
public int compare(Character c1, Character c2) {
return counter[c2 - 'a'] - counter[c1 - 'a'];
}
});
// 将频次大于0的字符加入堆中
for (char c = 'a'; c <= 'z'; c++) {
if (counter[c - 'a'] > 0) {
queue.offer(c);
}
}
StringBuilder sb = new StringBuilder();
while (queue.size() > 1) {
char letter1 = queue.poll();
char letter2 = queue.poll();
sb.append(letter1);
sb.append(letter2);
int idx1 = letter1 - 'a', idx2 = letter2 - 'a';
counter[idx1]--;
counter[idx2]--;
if (counter[idx1] > 0) {
queue.offer(letter1);
}
if (counter[idx2] > 0) {
queue.offer(letter2);
}
}
if (queue.size() > 0) {
sb.append(queue.poll());
}
return sb.toString();
}
}
基于计数的贪心算法
统计字符串中字符出现的次数,然后根据字符的出现次数重构字符串,如果n为奇数且出现最多的字符的次数为(n+1)/2时,改字符只能放置在偶数下标,其余情况每个字符放置在偶数下标还是奇数下标都是可行的。
维护偶数下标evenIdx和奇数下标oddIdx,初始值分别为0和1。遍历字符串的每一个字符根据出现次数选择放置的下标。
- 如果字符出现次数大于0且小于或等于n/2, 且oddIdx没有超过数组下标范围则将字符放置在oddIdx, oddIdx + 2
- 如果字符出现次数大于n/2,或者oddIdx超出数组下标范围,则将字符放置在evenIdx,evenIdx + 2
- 如果字符出现多次则重复上述操作直到该字符放置完毕
class Solution {
public String reorganizeString(String S) {
if (S.length() < 2)
return S;
int[] counter = new int[26];// 记录字符出现的次数
int maxCount = 0;// 记录出现最多次数字符的频次
int len = S.length();
for (int i = 0; i < len; i++) {
char c = S.charAt(i);
counter[c - 'a']++;
maxCount = Math.max(maxCount, counter[c - 'a']);
}
if (maxCount > (len + 1) / 2) {
return "";
}
char[] arr = new char[len];
int evenIdx = 0, oddIdx = 1;
int halfLen = len / 2;
for (char c = 'a'; c <= 'z'; c++) {
while (counter[c - 'a'] > 0 && counter[c - 'a'] <= halfLen && oddIdx < len) {
arr[oddIdx] = c;
counter[c - 'a']--;
oddIdx += 2;
}
while (counter[c - 'a'] > 0) {
arr[evenIdx] = c;
counter[c - 'a']--;
evenIdx += 2;
}
}
return new String(arr);
}
}