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.pswp.cn/bicheng/93992.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/93992.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

React native Navigation 详解

Tab Navigator(标签导航器) 概念 Tab Navigator 是 React Navigation 中用于创建底部或顶部标签栏导航的组件。它允许用户在不同的屏幕之间快速切换,每个标签对应一个独立的屏幕。 基本用法 import {createBottomTabNavigator } from @react-navigation/bottom-tabs; im…

[GraphRAG]完全自动化处理任何文档为向量知识图谱:AbutionGraph如何让知识自动“活”起来?

在当今信息爆炸的时代&#xff0c;企业和研究人员面对大量非结构化文档时&#xff0c;如何高效地提取、存储和查询其中的知识&#xff0c;已成为一个核心挑战。传统的关键词检索早已无法满足深层次语义关联和智能问答的需求。 每天面对成百上千份PDF论文、Excel报告、行业白皮…

模拟tomcat接收GET、POST请求

访问&#xff1a; http://localhost:10086/mytomcatMyTomcat/ └── src/└── com/└── zhang/├── MyServer.java├── MyRequest.java├── MyResponse.java├── MyMapping.java├── MyServlet.java└── MyHttpServlet.java核心类功能说明 MyServer.java 服务…

氯化钇:科技与高性能材料的核心元素

氯化钇是钇元素的氯化物&#xff0c;广泛应用于高性能材料、催化剂、光电技术等领域。作为稀土元素之一&#xff0c;钇因其独特的物理和化学特性&#xff0c;在现代工业中具有重要地位&#xff0c;而氯化钇则是其中的关键化合物之一。氯化钇的优势与特点1. 化学稳定性强氯化钇具…

【数据结构初阶】--排序(五):计数排序,排序算法复杂度对比和稳定性分析

&#x1f618;个人主页&#xff1a;Cx330❀ &#x1f440;个人简介&#xff1a;一个正在努力奋斗逆天改命的二本觉悟生 &#x1f4d6;个人专栏&#xff1a;《C语言》《LeetCode刷题集》《数据结构-初阶》 前言&#xff1a;今天这篇博客就给大家将一个计数排序&#xff0c;然乎就…

Incredibuild 新增 Unity 支持:击破构建时间过长的痛点

任何开发过复杂 Unity 项目的团队都会告诉你&#xff1a;构建速度已成为生产流程中的核心痛点。Unity 灵活且强大&#xff0c;但随着项目规模扩大&#xff08;尤其是包含 3D 资源、复杂着色器和庞大内容管线的项目&#xff09;&#xff0c;构建过程会逐渐变成一项隐性成本。 多…

大数据接口 - 收入评估(社保评级)API

请求端点 {"post": "https://api.tianyuanapi.com/api/v1/JRZQ09J8?t13位时间戳" }请求头字段名类型必填描述Access-Idstring是账号的 Access-Id对于业务请求参数 通过加密后得到 Base64 字符串&#xff0c;将其放入到请求体中&#xff0c;字段名为 data&…

C++八股 —— 设计模式

文章目录一、创建型模式1. 单例模式2. 工厂模式二、结构型模式1. 装饰器模式2. 代理模式三、行为型模式1. 观察者模式2. 策略模式一、创建型模式 1. 单例模式 C八股 —— 单例模式_c 单例模式-CSDN博客 2. 工厂模式 参考&#xff1a;【设计模式】工厂模式详解-----简单工厂…

在openeuler中如何使用 firewalld 开放指定端口

在 OpenEuler 中使用 firewalld 开放指定端口的操作步骤如下&#xff0c;需区分临时开放&#xff08;重启后失效&#xff09;和永久开放&#xff08;重启后保留&#xff09;两种场景&#xff1a;一、查询端口当前状态首先确认端口是否已开放&#xff0c;避免重复配置&#xff1…

【Java进阶】Java JIT 编译器深度解析与优化实践

Java JIT 编译器深度解析与优化实践Java JIT 编译器深度解析与优化实践一、JIT 编译器核心原理1. JIT 工作流程2. 热点代码检测机制二、Java 8 JIT 优化升级1. 分层编译优化2. 方法内联增强3. 循环优化升级4. 逃逸分析增强5. 向量化支持三、JIT友好代码设计原则1. 方法设计优化…

【本地部署问答软件Apache Answer】Answer开源平台搭建:cpolar内网穿透服务助力全球用户社区构建

文章目录前言1. 本地安装Docker2. 本地部署Apache Answer2.1 设置语言选择简体中文2.2 配置数据库2.3 创建配置文件2.4 填写基本信息3. 如何使用Apache Answer3.1 后台管理3.2 提问与回答3.3 查看主页回答情况4. 公网远程访问本地 Apache Answer4.1 内网穿透工具安装4.2 创建远…

华为数通认证学习

1、华为人才认证官网&#xff0c;https://e.huawei.com/cn/talent/portal/#/ 很全面的网站&#xff0c;包含了概述、了解认证、参加考试、学习资源、认证资讯四个板块。可以了解华为认证的整个流程、下载学习资源&#xff08;培训教材、视频课程等&#xff09;&#xff0c;以及…

Android-ContentProvider的跨应用通信学习总结

一、ContentProvider的概念1. ContentProvider 是什么&#xff1f;&#xff08;核心概念&#xff09;ContentProvider 是 Android 四大组件之一。它的核心职责是管理和共享应用的结构化数据。我们可以把它想象成一个应用的**“数据大使馆”**。在一个国家里&#xff08;Android…

Java数据结构第二十六期:解密位图,海量数据处理的 “空间魔法”

专栏&#xff1a;Java数据结构秘籍 个人主页&#xff1a;手握风云 目录 一、位图 1.1. 概念 1.2. 面试题 1.3. 位图的实现 1.4. 位图的应用 一、位图 1.1. 概念 在数据结构中&#xff0c;位图&#xff08;也称为位数组、位向量或位集&#xff09;是一种紧凑的方式来表示一…

芯科科技即将重磅亮相IOTE 2025深圳物联网展,以全面的无线技术及生态覆盖赋能万物智联

作为低功耗无线连接领域的创新性领导厂商&#xff0c;Silicon Labs&#xff08;亦称“芯科科技”&#xff09;将于8月27至29日携其最前沿的人工智能&#xff08;AI&#xff09;和物联网&#xff08;IoT&#xff09;解决方案在深圳举办的IOTE 2025国际物联网展中盛大展出。这场亚…

Linux上安装多个JDK版本,需要配置环境变量吗

简短回答&#xff1a;不需要同时配置多个 JDK 的 JAVA_HOME 和 PATH&#xff0c;但你可以安装多个版本&#xff0c;并通过灵活的方式在它们之间切换。 文章目录✅ 正确做法&#xff1a;安装多个 JDK&#xff0c;但只让一个生效&#xff08;通过环境变量或 alternatives&#xf…

MySQL有哪些高可用方案

大家好&#xff0c;我是锋哥。今天分享关于【MySQL有哪些高可用方案】面试题。希望对大家有帮助&#xff1b; MySQL有哪些高可用方案? 超硬核AI学习资料&#xff0c;现在永久免费了&#xff01; MySQL 高可用方案是指确保 MySQL 数据库在面对硬件故障、网络故障、负载过重等…

【Windows】Windows平台基于加速地址安装vcpkg并集成到Visual Studio 2017

基础运行环境 启动&#xff1a; 适用于 VS 2017 的 x64 本机工具命令提示 ninja 下载压缩包 https://gh-proxy.com/https:/github.com/ninja-build/ninja/releases/download/v1.13.1/ninja-win.zip 直接解压到c:/Windows (无需配置环境变量) CMake 下载安装包 https://gh-proxy…

LLMs之MCP:Chrome MCP的简介、安装和使用方法、案例应用之详细攻略

LLMs之MCP&#xff1a;Chrome MCP的简介、安装和使用方法、案例应用之详细攻略 目录 Chrome MCP的简介 1、特点 2、与类似项目的比较 Chrome MCP的安装和使用方法 1、安装 2、使用方法 加载 Chrome 扩展 与 MCP 协议客户端一起使用 使用 STDIO 连接&#xff08;替代方…

【Java EE】多线程-初阶 synchronized 关键字 - 监视器锁 monitor lock

synchronized 关键字 - 监视器锁 monitor lock5. synchronized 关键字 - 监视器锁 monitor lock5.1 synchronized 的特性5.2 synchronized 使⽤⽰例5.3 Java 标准库中的线程安全类本节⽬标• 掌握 synchronized关键字5. synchronized 关键字 - 监视器锁 monitor lock &#xf…