這一題最主要的核心思想是貪心法。貪心的方式是這樣的:在決定下一個字符的時候,總是尋找在可用的字符中(也就是上一次出現的位置距離當前遍歷的位置有k的距離或者還沒有使用過),還可以使用次數最多的字符。如果在運行過程中可用的字符集合爲空,就表示not possible to rearrange the string,返回空字符串
爲了達到這種貪心法,有兩種方式。
1. 基於heap的做法,也就是利用priorityqueue。利用heap主要就是爲了能夠O(logn)的複雜度找到符合條件的下一個字符,只要這個heap是基於剩餘使用次數排序即可。同時,你還需要一個數據結構來保存最近使用過的k個字符。因爲這種結構類似一個size爲k的window,所以用queue是比較合適的。具體做法如下:
a. 用一個int數組或者一個HashMap來保存每個字符出現過的頻率。這一題可以用int數組,因爲它保證了出現的都只是小寫的英文字符。
b. 然後把計算得到的頻率搭配對應的字符放進priorityqueue裏,comparator基於出現頻率。
c. 開始進行遍歷循環,走的次數是字符串的長度(因爲你是rearrange,所以遍歷的次數肯定與原字符串的長度是一致的)。根據上面描述的貪心法,通過priorityqueue得到當前可使用的字符,將可使用次數減一,並且從priorityqueue從除去,放進作爲記錄的window queue裏。如果queue的size大於等於k,就把queue的頭字母pop出來,如果這個字母對應的頻率還大於0,就放回到priorityqueue裏備用。
d. 不停循環c,直到循環走完,或者priorityqueue爲空。如果是前者就表示問題有解,可以輸出,後者就表示問題無解,返回空字符串即可。
根據上述算法,得到代碼如下
public String rearrangeString(String s, int k) {
int[][] chCnt = new int[26][2];
for (int i = 0; i < s.length(); i++) {
int index = s.charAt(i) - 'a';
chCnt[index][0] = index;
chCnt[index][1]++;
}
PriorityQueue<int[]> cntQueue = new PriorityQueue<>((a, b) -> (b[1] - a[1]));
for (int i = 0; i < 26; i++) {
if (chCnt[i][1] > 0) {
cntQueue.add(chCnt[i]);
}
}
Queue<Integer> usedQueue = new LinkedList<>();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if (cntQueue.isEmpty()) {
return "";
}
int[] candidate = cntQueue.poll();
candidate[1]--;
builder.append((char)(candidate[0] + 'a'));
usedQueue.add(candidate[0]);
if (usedQueue.size() >= k) {
int[] release = chCnt[usedQueue.poll()];
if (release[1] > 0) {
cntQueue.add(release);
}
}
}
return builder.toString();
}
這一題的算法是O(nlogc),你可以認爲c是一個最大爲26的常數。所以是O(n)?=。= 其實真的不好說,我也不明白基於這個理論這個解法的實測運行還是會比下一個解法要慢。
2. 解法2和解法1大致相同。就是實現貪心法的方式不同。解法2就是利用兩個int數組去記錄26個英文字符對應的頻率和上一次出現的位置,每一次要尋找一個合適的字符都遍歷這兩個數組即可,每次從兩個數組中找到上一次出現的位置與目前位置距離至少爲k並且可使用次數最大的那個即可,找不到就返回空字符串。因爲數組的size就是26,也就是一個常數複雜度,所以我們可以認爲每次貪心尋找一個字符的複雜度就是O(1)。所以整體複雜度就是O(n)。
根據算法描述,得到代碼如下:
public String rearrangeString(String s, int k) {
int[] chCnt = new int[26];
int[] nextAvailPos = new int[26];
for (int i = 0; i < s.length(); i++) {
chCnt[s.charAt(i) - 'a']++;
}
StringBuilder resultBuilder = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
int next = findBestChar(chCnt, nextAvailPos, i);
if (next == -1) {
return "";
}
char nextCh = (char)(next + 'a');
resultBuilder.append(nextCh);
nextAvailPos[next] = i + k;
chCnt[next]--;
}
return resultBuilder.toString();
}
public int findBestChar(int[] chCnt, int[] nextAvailPos, int curPos) {
int candidate = -1;
int curMaxCnt = 0;
for (int i = 0; i < chCnt.length; i++) {
if (chCnt[i] > curMaxCnt && nextAvailPos[i] <= curPos) {
curMaxCnt = chCnt[i];
candidate = i;
}
}
return candidate;
}