[leetCode]767. 重构字符串

题目

链接: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);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值