【Leetcode】358. Rearrange String k Distance Apart(配数学证明)

题目地址:

https://leetcode.com/problems/rearrange-string-k-distance-apart/

给定一个字符串 s s s,其长度为 l l l,给定一个非负整数 k k k,问是否可以将其重排,使得相距 k k k范围内的两个字母都不相等(恰好相距 k k k是可以的,但再近的就不能相等了)。如果能,返回重排后的字符串;否则返回空串。题目保证只含小写英文字母。

思路是贪心。想象我们有 l l l个空位,将其分成 l / k l/k l/k个大块,每个大块长度为 l / k l/k l/k,如果后面还有空位没有分配,则它们在最后成为一个长度小于 l / k l/k l/k的大块。那我们不允许在同一个大块里存在相同的字符,所以我们的策略是,在每个大块里,依次填出现次数最多的 k k k个字符(如果到了最后一个大块了,那么这个大块的长度小于 k k k,则填写的字符数小于 k k k。这里选择出现最多的 k k k个字符,原因在于直觉上来说,”潜在地“它们占用的地方最多,需要最先被分配掉),填写的时候,要按照字典序填,这样保证块与块之间也不会产生相同字母距离小于 k k k的情况(这里的字典序实际上可以是任何一种序,只需要是 26 26 26个英文字母的全序关系即可,不一定要按照 a b c d abcd abcd的顺序)。填完第一个大块之后,用相同的手法填第二个大块,以此类推。如果发现填某个大块的时候,比如该大块长度为 k k k,但发现挑不出 k k k个未使用的不同字母了,则不存在方案,返回空串。这个算法其实是这么个意思,每次挑选的字母都是还剩余的字母中,出现次数最多,前面 k k k距离范围内没有,并且字典序最小的字母,进行拼接。一旦发现挑不出来满足条件的了,就说明无解,返回空串。

算法正确性证明:
首先,如果算法返回一个非空串,则必然存在合法方案,这一点是显然的(因为算法已经构造出来了)。下面证明如果存在合法方案,则一定不会返回空串。如果 l ≤ k l\le k lk,存在合法方案意味着每个字母只出现 1 1 1次,显然算法不会返回空串,结论成立。假设对于 l < n l<n l<n结论都是对的,对于长度为 n n n的情形。首先因为存在一个合法方案,设合法方案的排列是 s = a 1 a 2 . . . a n − 1 a n s=a_1a_2...a_{n-1}a_n s=a1a2...an1an,我们考虑 a n a_n an在原串中的出现次数。首先,如果 a n a_n an不是出现次数最多(或最多之一)的字母,那么考虑 s s s中出现次数最多的字母 x x x,一定存在某个位置的 x x x使得 a n a_n an x x x调换之后仍然是合法方案(如果不存在,则说明 a n a_n an无论与哪个位置的 x x x调换都会有冲突,这其实与 a n a_n an不是出现次数最多之一这个条件矛盾了),所以我们不妨设 a n a_n an是出现次数最多或最多之一的字母,那么显然去掉最后一个字母后, a 1 a 2 . . . a n − 1 a_1a_2...a_{n-1} a1a2...an1也是满足条件的合法方案,由归纳假设,可以用贪心法构造出另一个”更规范“的合法方案 s ′ s' s,但是这里,我们强行定义 a n a_n an这个字母的字典序是排最后的,用这个字典序去构造贪心的方案 s ′ s' s。接着把 a n a_n an放在 s ′ s' s最前面,这样也得到了一个合法串,去掉前 k k k个字符后,之后的串也是合法的,由归纳假设,它们也可以重排成一个”更规范“的合法方案,但这一次将 a n a_n an的字典序强行规定为是最前的,连同前 k k k个字符所得的串,就是 s s s按照贪心所得的串(这里的贪心也是将 a n a_n an看成字典序排第一),所以不会返回空串,结论正确。

代码方面可以用最大堆来实现。代码如下:

import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;

public class Solution {
    public String rearrangeString(String s, int k) {
    	// 注意要对k等于0或1特殊考虑
        if (k <= 1) {
            return s;
        }
        
        // 求一下每个字母出现了多少次
        int[] count = new int[26];
        for (int i = 0; i < s.length(); i++) {
			count[s.charAt(i) - 'a']++;
        }
        
        // 出现次数大的优先,出现次数一样则字典序小的优先
        PriorityQueue<Character> maxHeap = new PriorityQueue<>((c1, c2) -> {
            if (count[c1 - 'a'] != count[c2 - 'a']) {
                return -Integer.compare(count[c1 - 'a'], count[c2 - 'a']);
            }
            
            return Character.compare(c1, c2);
        });
        
        for (char ch = 'a'; ch <= 'z'; ch++) {
            if (count[ch - 'a'] > 0) {
                maxHeap.offer(ch);
            }
        }
        
        StringBuilder sb = new StringBuilder();
        List<Character> list = new ArrayList<>();
        
        int len = s.length();
        while (!maxHeap.isEmpty()) {
        	// k是当前填的大块的长度
            k = Math.min(len, k);
            for (int i = 0; i < k; i++) {
                if (!maxHeap.isEmpty()) {
                    char cur = maxHeap.poll();
                    sb.append(cur);
                    count[cur - 'a']--;
                    len--;
                    // 如果没用完,则用list记下来
                    if (count[cur - 'a'] > 0) {
                        list.add(cur);
                    }
                } else {
                    return "";
                }
            }
            
            maxHeap.addAll(list);
            list.clear();
        }
        
        return sb.toString();
    }
}

时空复杂度 O ( l ) O(l) O(l)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值