当前位置: 首页 > news >正文

K距离间隔重排字符串 (LeetCode 358) — Swift解法 + 可运行Demo

在这里插入图片描述
在这里插入图片描述

文章目录

    • 摘要
    • 描述
    • 解决方法
    • 分析问题和解决代码
      • 代码要点详解
    • 示例测试和结果
    • 时间复杂度
    • 空间复杂度
    • 总结

摘要

这道题的核心是:把字符串里的字符重新排一下顺序,让相同字符之间至少隔开 k 个位置。如果做不到,就返回空串。看上去像“排座位”,其实是一道标准的“贪心 + 堆 + 冷却队列”题。本文用 Swift 写出一份可直接运行的 Demo,顺便把实现细节和常见坑讲清楚。

描述

给定一个字符串 s 和整数 k,要求把 s 里的字符重新排列,使得任意两个相同字符在新字符串中的位置至少相隔 k

  • 如果存在这样的重排,返回重排后的字符串。
  • 如果不存在,返回 ""

直觉:频率高的字符更“难安置”,理应优先被安排;但又不能连续塞,必须“冷却”至少 k-1 个位置。于是我们需要:

  1. 一个**最大堆(优先队列)**按剩余次数挑选字符。
  2. 一个冷却队列存放刚刚用过的字符,等走过 k 步再重新投入堆。

解决方法

思路分三步:

  1. 统计每个字符的出现次数。

  2. 用最大堆按“剩余次数”挑一个当前最应该放的字符。

  3. 将每轮最多取 k 个不同字符依次放入结果,同时把它们的剩余次数减一并放入“等待区”。当这轮结束,再把等待区里还剩次数的字符放回堆,进入下一轮。

    • 如果某轮拿不到足够的不同字符,但还有字符没用完,说明无法满足间隔要求,直接返回 ""

这个分轮的方式,简单直观,也好写对。

分析问题和解决代码

下面是完整的 Swift 实现,包含一个通用的最大堆(优先队列)实现、解题函数,以及可运行的测试 Demo。

import Foundation// MARK: - Generic Max Heap
public struct PriorityQueue<Element> {private var elements: [Element] = []private let areSorted: (Element, Element) -> Bool  // max-heap: return lhs > rhspublic init(sort: @escaping (Element, Element) -> Bool) {self.areSorted = sort}public var isEmpty: Bool { elements.isEmpty }public var count: Int { elements.count }public mutating func push(_ value: Element) {elements.append(value)siftUp(from: elements.count - 1)}public mutating func pop() -> Element? {guard !elements.isEmpty else { return nil }elements.swapAt(0, elements.count - 1)let item = elements.removeLast()siftDown(from: 0)return item}public func peek() -> Element? {elements.first}private mutating func siftUp(from index: Int) {var child = indexvar parent = (child - 1) / 2while child > 0 && areSorted(elements[child], elements[parent]) {elements.swapAt(child, parent)child = parentparent = (child - 1) / 2}}private mutating func siftDown(from index: Int) {var parent = indexwhile true {let left = parent * 2 + 1let right = left + 1var candidate = parentif left < elements.count && areSorted(elements[left], elements[candidate]) {candidate = left}if right < elements.count && areSorted(elements[right], elements[candidate]) {candidate = right}if candidate == parent { return }elements.swapAt(parent, candidate)parent = candidate}}
}// MARK: - Solution
class Solution {/// Rearrange string so that same chars are at least k distance apart./// - Returns: rearranged string or "" if impossiblefunc rearrangeString(_ s: String, _ k: Int) -> String {// 快速返回:k <= 1 不需要限制if k <= 1 { return s }if s.isEmpty { return "" }// 1) 统计频率var freq = [Character: Int]()for ch in s {freq[ch, default: 0] += 1}// 2) 最大堆,按剩余次数排序(次数相同可按字母排序,方便稳定)// 元组:(char, count)var maxHeap = PriorityQueue<(Character, Int)>(sort: { (a, b) inif a.1 == b.1 {return a.0 < b.0  // 次数相等时,字典序更小的优先(不必须,但稳定)}return a.1 > b.1})for (ch, c) in freq {maxHeap.push((ch, c))}// 3) 分轮放置,每轮最多放 k 个不同字符var result: [Character] = []// 临时存放本轮用过的字符(减1后如果还有余量,等轮末再压回堆)while !maxHeap.isEmpty {var usedInThisRound: [(Character, Int)] = []var take = min(k, s.count - result.count) // 剩余位数可能不足 kfor _ in 0..<take {guard let (ch, c) = maxHeap.pop() else {// 堆空但还没填满(说明还有待放字符被卡住),失败// 注意:只有当结果未完成且堆空时,才是失败return ""}result.append(ch)if c - 1 > 0 {usedInThisRound.append((ch, c - 1))}// 若已经填满,提前结束if result.count == s.count { break }}// 轮末:把本轮用过仍有余量的字符压回堆for item in usedInThisRound {maxHeap.push(item)}// 如果堆仍有元素,但是这一轮没凑够 k 个且结果还没填满,说明必须留空位才能满足距离要求,但字符串不允许空位 => 不可能if !maxHeap.isEmpty && result.count < s.count && usedInThisRound.count < k {// 这个判断等价:本轮拿的不同字符数 < k,但后面还有字符没安排// 说明“冷却距离”要求挡住了return ""}}return String(result)}
}// MARK: - Demo (Runnable)
func demo() {let sol = Solution()let cases: [(String, Int)] = [("aabbcc", 3),     // 可行,典型示例("aaabc", 3),      // 不可行,返回 ""("aaadbbcc", 2),   // 可行("a", 2),          // 只有一个字符,k>1 也可行(本身就满足)("", 2),           // 空串("aa", 1),         // k=1 总是原样即可("aa", 2),         // 需要至少间隔 2,两个 a 无法满足 => ""]for (s, k) in cases {let ans = sol.rearrangeString(s, k)print("Input: s=\(s), k=\(k) -> Output: \(ans.isEmpty ? "\"\"" : ans)")}
}// Run demo
demo()

代码要点详解

  1. 最大堆设计

    • 我们用泛型 PriorityQueue,构造时传 sort 比较函数,让它成为“最大堆”。
    • 堆里的元素是 (Character, Int),其中 Int 是剩余频次,按频次降序;为了稳定性,频次相同按字符字典序小的优先(不必须,仅让结果可预期一点)。
  2. 分轮取 k 个不同字符

    • 每一轮最多取 k 个不同字符,确保相同字符之间天然相隔至少 k
    • 本轮用过的字符,频次减一后暂存到 usedInThisRound,等这一轮结束再放回堆,避免本轮再次被取到。
  3. 失败条件

    • 如果堆提前空了但还没填满字符串,说明被“冷却”规则卡住了,不可行。
    • 或者本轮没拿满 k 个(意味着后面位置距离不够了)且堆里还有字符,也是不可能的情况,返回空串。
  4. 为什么不是用“时间戳冷却”

    • 另一种写法是给每个字符打“readyTime”,当 i >= readyTime 才能重新入堆。这也行,但分轮更直观:一轮拿 k 个不同字符,本轮结束再回堆,自然保证间隔。

示例测试和结果

运行上面的 demo(),输出大致如下(具体顺序可能因为同频字符的 tie-breaker 有细微差别,但满足约束即可):

Input: s=aabbcc, k=3 -> Output: abcabc
Input: s=aaabc, k=3 -> Output: ""
Input: s=aaadbbcc, k=2 -> Output: abacabcd
Input: s=a, k=2 -> Output: a
Input: s=, k=2 -> Output: ""
Input: s=aa, k=1 -> Output: aa
Input: s=aa, k=2 -> Output: ""

简单解释:

  • aabbcck=3:可以按 abcabc 摆,符合任意同字母之间至少隔 3。
  • aaabck=3a 太多,中间插不进足够的“垫片”,失败。
  • aaadbbcck=2:可以,比如 abacabcd 就行。
  • 单字符或 k=1 都是“没有硬性间隔限制”,基本能过。

时间复杂度

  • 设字符串长度为 n,不同字符种类数为 σ(最多 26 个小写,或 128/256 ASCII)。
  • 每个字符都会被插入/弹出堆多次(与它的频次成正比),堆操作是 O(log σ)
  • 总体复杂度:O(n log σ)。在英文小写场景基本可视为 O(n)

空间复杂度

  • 频率表 O(σ),堆 O(σ),结果 O(n)
  • 额外空间:O(n + σ),在字符集有限时接近 O(n)

总结

这题看似是“重排字符串”,其实就是优先安排最难放的字符,再用冷却队列保证间隔。实现上:

  • 最大堆挑剩余次数最多的字符;
  • 分轮放置,每轮最多 k 个不同字符,轮末再把有余量的字符放回堆;
  • 如果某轮拿不到 k 个,但还有字符没放完,说明不可能。

这种模式在很多“调度/任务安排”的题里都能复用,比如 CPU 任务调度、订单/工位分配等。写成 Swift 后,借助一个干净的优先队列模板,整件事就顺了。欢迎把这份 Demo 拿去直接跑,也可以按你业务里的字符集和限制微调。

http://www.dtcms.com/a/340906.html

相关文章:

  • 360T7刷OpenWRT笔记
  • 深入解析EventPoller:Disruptor的轮询式事件处理机制
  • Download:几款主流的全球范围的NDVI产品参数说明和下载
  • Spring Boot 发展史
  • 机器学习——数据清洗
  • JS对象与JSON转换全解析
  • C/C++嵌入式笔试核心考点精解
  • AI 与 OCR 识别:深度融合的智能信息提取技术
  • Elasticsearch 写入全链路:从单机到集群
  • 实验8.20
  • nvidia最新论文:小型语言模型是代理人工智能的未来
  • iOS App 上架实战 从内测到应用商店发布的全周期流程解析
  • Linux 文件系统权限管理(补充)
  • 管理项目服务器连接数据库
  • Linux 文本处理三剑客:awk、grep、sed 完全指南
  • 中小型企业是否需要使用高防服务器
  • Linux-文本搜索工具grep
  • C++进阶-----C++11
  • Hangfire定时部署(.NET 8 + SQL Server)
  • Android 资源替换:静态替换 vs 动态替换
  • PHP特有的安全漏洞及渗透测试利用方法(通俗易懂)
  • 项目1总结其一
  • 49 C++ STL模板库18-类模板-pair
  • ROS 2系统Callback Group概念笔记
  • 突发!DeepSeek刚刚开源V3.1-Base
  • UTF-8 编解码可视化分析
  • 【Day 30】Linux-SQL语句
  • C/C++ 与嵌入式岗位常见笔试题详解
  • MYSQL为什么会发生死锁,怎么解决
  • 第三阶段数据-3:数据库脚本生成,备份与还原,分离与附加