767. 重构字符串

第一次尝试

直接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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值