leetcode
leetcode 301 ~ 350
K 距离间隔重排字符串

K 距离间隔重排字符串

难度:

标签:

题目描述

代码结果

运行时间: 58 ms, 内存: 0.0 MB


/*
 * LeetCode 358: Rearrange String k Distance Apart using Java Streams
 * This solution uses Java Streams to handle the rearrangement logic.
 */
import java.util.*;
import java.util.stream.*;
 
public class Solution {
    public String rearrangeString(String s, int k) {
        if (k == 0) return s;
 
        Map<Character, Long> freqMap = s.chars()
                                        .mapToObj(c -> (char) c)
                                        .collect(Collectors.groupingBy(c -> c, Collectors.counting()));
 
        PriorityQueue<Map.Entry<Character, Long>> maxHeap = new PriorityQueue<>((a, b) -> Long.compare(b.getValue(), a.getValue()));
        maxHeap.addAll(freqMap.entrySet());
 
        Queue<Map.Entry<Character, Long>> waitQueue = new LinkedList<>();
        StringBuilder result = new StringBuilder();
 
        while (!maxHeap.isEmpty()) {
            Map.Entry<Character, Long> current = maxHeap.poll();
            result.append(current.getKey());
            current.setValue(current.getValue() - 1);
            waitQueue.offer(current);
 
            if (waitQueue.size() < k) continue;
 
            Map.Entry<Character, Long> front = waitQueue.poll();
            if (front.getValue() > 0) {
                maxHeap.offer(front);
            }
        }
 
        return result.length() == s.length() ? result.toString() : "";
    }
}

解释

方法:

这个题解的思路是先统计每个字符出现的频率,然后将字符按频率从大到小排序。接下来检查出现频率最高的字符是否会导致无法构造有效的重排后字符串,如果无法构造则直接返回空字符串。如果可以构造,则创建一个长度为n的数组ans,初始化为'_'。然后从频率最高的字符开始,每次将字符放到ans中当前位置,并将当前位置向后移动k个位置。如果到达末尾,则回到开头继续。每放置一个字符,就将该字符的剩余数量减1,直到所有字符都放置完毕。最后将ans数组转换为字符串返回。

时间复杂度:

O(n^2)

空间复杂度:

O(n)

代码细节讲解

🦆
在进行字符频率统计和排序后,你是如何确保在字符放置时遵循了距离间隔k的规则?
在将字符根据频率从大到小排序之后,我从频率最高的字符开始进行放置。具体实施时,我设定初始位置(例如数组的起始位置),并在每次放置一个字符后,将当前位置向后移动k个位置。这样做可以保证每次放入的字符之间都至少有k-1个其他字符或者为空,从而满足题设的要求。如果当前位置超过了数组的长度,我会将其回绕到数组的开头,继续寻找空位进行放置,确保间隔k的规则得到遵守。
🦆
如果在重排过程中,当前索引位置已被占用,你是如何处理这种情况以确保字符间的距离维持在k?
在我的实现中,如果我按照间隔k移动到一个新的位置,并发现该位置已经被其他字符占用,我会从当前位置开始,继续向后搜索直到找到一个空的位置。这种线性探测的方法虽然可能增加了额外的计算,但可以保证即使在位置已满的情况下也能找到合适的位置来放置当前字符,并且保持每两个相同字符之间的最小间隔为k。
🦆
你提及在重排时可能需要回到数组的开头继续放置字符,这种环状处理是否可能导致无限循环?如果是,你是如何避免这种情况的?
环状处理确实有可能导致无限循环,特别是当没有足够的空间按照间隔k放置字符时。为了避免这种情况,我在开始放置字符之前进行了一个预检查,即检测频率最高的字符数量是否会导致放置失败。此外,实际放置过程中,每放置一个字符,我都会减少该字符的剩余数量,并仅当字符还有剩余时才继续寻找新的放置位置。一旦所有字符都放置完毕,循环即会结束,从而避免了无限循环。
🦆
在题解中,你检查了出现频率最高的字符是否会导致无法构造有效的重排后字符串,能否解释为什么使用表达式 `(temp[0][1] - 1) * k + 1 + count - 1 > n` 来进行这一检查?
这个表达式用于确保即使频率最高的字符也可以在字符串中按照间隔k被安排。这里,`(temp[0][1] - 1) * k + 1` 表示如果要放置频率最高的字符,至少需要的位置数。`temp[0][1] - 1` 代表除了第一个字符外的其他字符,每个字符后都需要至少k个间隔,`+ 1` 是放置第一个字符所需的位置。`+ count - 1` 考虑了如果有多个字符频率与最高频率相同,它们可能需要额外的位置。如果这个总需求超过了字符串长度n,则说明无法成功构造满足条件的重排字符串,因此返回空字符串。

相关问题

任务调度器

给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表,用字母 A 到 Z 表示,以及一个冷却时间 n。每个周期或时间间隔允许完成一项任务。任务可以按任何顺序完成,但有一个限制:两个 相同种类 的任务之间必须有长度为 n 的冷却时间。

返回完成所有任务所需要的 最短时间间隔 。

 

示例 1:

输入:tasks = ["A","A","A","B","B","B"], n = 2
输出:8
解释:A -> B -> (待命) -> A -> B -> (待命) -> A -> B
     在本示例中,两个相同类型任务之间必须间隔长度为 n = 2 的冷却时间,而执行一个任务只需要一个单位时间,所以中间出现了(待命)状态。 

示例 2:

输入:tasks = ["A","A","A","B","B","B"], n = 0
输出:6
解释:在这种情况下,任何大小为 6 的排列都可以满足要求,因为 n = 0
["A","A","A","B","B","B"]
["A","B","A","B","A","B"]
["B","B","B","A","A","A"]
...
诸如此类

示例 3:

输入:tasks = ["A","A","A","A","A","A","B","C","D","E","F","G"], n = 2
输出:16
解释:一种可能的解决方案是:
     A -> B -> C -> A -> D -> E -> A -> F -> G -> A -> (待命) -> (待命) -> A -> (待命) -> (待命) -> A

 

提示:

  • 1 <= tasks.length <= 104
  • tasks[i] 是大写英文字母
  • 0 <= n <= 100

重构字符串

给定一个字符串 s ,检查是否能重新排布其中的字母,使得两相邻的字符不同。

返回 s 的任意可能的重新排列。若不可行,返回空字符串 "" 。

 

示例 1:

输入: s = "aab"
输出: "aba"

示例 2:

输入: s = "aaab"
输出: ""

 

提示:

  • 1 <= s.length <= 500
  • s 只包含小写字母