第一次尝试
直接hash然后遍历直到取完所有字符
如果某个字符串符合题目条件,那么使用遍历可以达到
aaabb=> a b a b a
aabcc => a b c a c
aaab => a b a a X
不行,直接for遍历还是凑不出效果,例如aaabc这样的字符串,会遍历出 abcaa 是错误的,但是 abaca确实正确的
第二次尝试,对字典里面的key排序,保证按顺序消耗掉字符,但是还是不行,说明这种方法不正确
改进思路 使用堆
又是一道要用到堆的题目,对堆不太熟练,练习了非常非常久,干
思路:
1 构建最大堆,按照字符:次数
的方式来构建,次数大的位于堆顶
2 每次取出最大的两个元素,然后依次添加进ans字符串里面
3 添加完之后,将还有剩余次数的字符添加回堆中
4 重复2、3步骤,直到堆中面临两种情况
4.1 堆中无元素,此时返回ans字符串即可
4.2 堆中有一个元素,此时判断它的次数,如果不是1次,说明重构失败,如果是1次,直接add到ans字符串,然后返回ans字符串即可
主要是取出两个最大元素, 这里一时没想到,下面是AC的C++代码,C++好久没写了,好多细节记不清楚了
c++用优先队列来实现堆,默认是大根堆,刚好符合我们的需求,不过我们要存的是pair类型的数据,所以要定义一下排序规则
C++版本
class Solution {
public:
string reorganizeString(string S) {
// 构造字典
map<char, int> dct;
auto cmp = [](const pair<char, int> &i1, const pair<char, int> &i2) { return i1.second < i2.second; };
// 优先队列
priority_queue<pair<char, int>, vector<pair<char, int>>, decltype(cmp)> que(cmp);
for (char &i: S) {
dct[i]++;
}
// O(n) 时间建树
for (auto it = dct.begin(); it != dct.end(); it++) {
que.push(*it);
}
vector<char> ans;
// O(nlog|S|),n为字符串长度,|S|为字符集大小
// log|S|是调整树的时间代价,而我们要构造n个字符,所以要遍历n次,O(n)时间
while (ans.size() != S.size()) {
auto it1 = que.top();
que.pop();
// 边界情况
if (que.empty()) {
if (it1.second != 1)
return "";
else {
ans.push_back(it1.first);
string res;
res.insert(res.begin(), ans.begin(), ans.end());
return res;
}
}
auto it2 = que.top();
que.pop();
// 添加进ans中
while (it1.second && it2.second) {
ans.push_back(it1.first);
it1.second -= 1;
ans.push_back(it2.first);
it2.second -= 1;
}
// 将剩余的字符放回队列中
if (it1.second != 0)
que.push(it1);
if (it2.second != 0)
que.push(it2);
}
// 构造字符串并返回
string res;
res.insert(res.begin(), ans.begin(), ans.end());
return res;
}
};
Python版本
Python的heapq好像不支持自定义排序规则,也就是按照’<'来比较,python是小根堆,在这里构造队列的时候需要一点技巧,我们按照-出现次数:字符
来构造队列,这样heapq就能根据出现次数来排序,因为tuple的排序是按照其内部元素顺序比较的
这里还有对出现次数的分析
class Solution:
def reorganizeString(self, S: str) -> str:
if len(S) < 2:
return S
length = len(S)
counts = collections.Counter(S)
# 特殊判断,如果某个字符数超过了(n+1)/2次,那么一定不能重构
maxCount = max(counts.items(), key=lambda x: x[1])[1]
if maxCount > (length + 1) // 2:
return ""
# 构造-value,char的队列
queue = [(-x[1], x[0]) for x in counts.items()]
heapq.heapify(queue)
ans = list()
while len(queue) > 1:
_, letter1 = heapq.heappop(queue)
_, letter2 = heapq.heappop(queue)
ans.extend([letter1, letter2])
counts[letter1] -= 1
counts[letter2] -= 1
if counts[letter1] > 0:
heapq.heappush(queue, (-counts[letter1], letter1))
if counts[letter2] > 0:
heapq.heappush(queue, (-counts[letter2], letter2))
if queue:
ans.append(queue[0][1])
return "".join(ans)
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/reorganize-string/solution/zhong-gou-zi-fu-chuan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
和上面c++版本的解答很类似,不过这里取出和插入的次数更多一些
按同样的思路改写算法
class Solution:
def reorganizeString(self, S: str) -> str:
n = len(S)
if n<2:
return S
dct = Counter(S)
max_cnt = max([v for v in dct.values()])
if max_cnt > (n+1)//2:
return ""
que = [(-v,k) for k,v in list(dct.items())]
heapq.heapify(que)
ans = []
while len(ans) !=n :
# print(que)
cnt1,c1 = heapq.heappop(que)
if len(que)==0:
if cnt1!=-1:
return ""
ans.append(c1)
break
cnt2,c2 = heapq.heappop(que)
while cnt1!=0 and cnt2!=0:
ans.append(c1)
cnt1+=1
ans.append(c2)
cnt2+=1
if cnt1!=0:
heapq.heappush(que,(cnt1,c1))
if cnt2!=0:
heapq.heappush(que,(cnt2,c2))
return ''.join(ans)
基于计数的贪心
这个方法很巧妙,贪心的思想体现得淋漓尽致,主要是分析的过程一定要搞清楚
class Solution:
def reorganizeString(self, S: str) -> str:
if len(S) < 2:
return S
length = len(S)
counts = collections.Counter(S)
maxCount = max(counts.items(), key=lambda x: x[1])[1]
if maxCount > (length + 1) // 2:
return ""
reorganizeArray = [""] * length
evenIndex, oddIndex = 0, 1
halfLength = length // 2
for c, count in counts.items():
while count > 0 and count <= halfLength and oddIndex < length:
reorganizeArray[oddIndex] = c
count -= 1
oddIndex += 2
while count > 0:
reorganizeArray[evenIndex] = c
count -= 1
evenIndex += 2
return "".join(reorganizeArray)
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/reorganize-string/solution/zhong-gou-zi-fu-chuan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
时间复杂度会比堆排序方法更优
总结
这道题写了很久,主要是对python的heapq不太熟悉,没有沿着原本的思路往下写,在想到用堆来解题的时候,也没有想到连续弹出两个元素的操作,而是冥思苦想到底怎么弹出一个元素,调整堆,再弹出第二大的元素(主要是思考太久脑子没转过来)
太久没写C++了,虽然编码的时间不长,但写的还是挺痛苦,然后转回头又写了一个Python版本的解答,对heapq的排序性质不太熟悉,像按照-出现次数:字符
来构造队列,但是忘了tuple元素怎么比较顺序,导致自己没有继续往下写,看来Python排序条件虽然不像C++那么自由,但是可以间接的达到想要的效果
主要是贪心的思想不太熟悉,其实像这种取最大/最多的,大多数情况下都要用到堆,C++里面是priority_queue
,Python里面是heapq