LeetCode 60. 排列序列【数学,逆康托展开】困难

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。

给出集合 [1,2,3,...,n],其所有元素共有 n! 种排列。

按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:

  1. "123"
  2. "132"
  3. "213"
  4. "231"
  5. "312"
  6. "321"

给定 n 和 k,返回第 k 个排列。

示例 1:

输入:n = 3, k = 3
输出:"213"

示例 2:

输入:n = 4, k = 9
输出:"2314"

示例 3:

输入:n = 3, k = 1
输出:"123"

提示:

  • 1 <= n <= 9
  • 1 <= k <= n!

解法 逆康托展开

要想解决本题,首先需要了解一个简单的结论:对于 n n n 个不同的元素(例如数 1 , 2 , ⋯ , n 1, 2, \cdots, n 1,2,,n ),它们可以组成的排列总数目为 n ! n! n!

对于给定的 n n n k k k ,不妨从左往右确定第 k k k 个排列中的每一个位置上的元素到底是什么

我们首先确定排列中的首个元素 a 1 a_1 a1 。根据上述的结论,我们可以知道:

  • 1 1 1 a 1 a_1 a1 的排列一共有 ( n − 1 ) ! (n-1)! (n1)! 个;
  • 2 2 2 a 1 a_1 a1 的排列一共有 ( n − 1 ) ! (n-1)! (n1)! 个;
  • ⋯ \cdots
  • n n n a 1 a_1 a1 的排列一共有 ( n − 1 ) ! (n-1)! (n1)! 个。

由于我们需要求出从小到大的第 k k k 个排列,因此:

  • 如果 k ≤ ( n − 1 ) ! k \leq (n-1)! k(n1)! ,我们就可以确定排列的首个元素为 1 1 1
  • 如果 ( n − 1 ) ! < k ≤ 2 ⋅ ( n − 1 ) ! (n-1)! < k \leq 2 \cdot (n-1)! (n1)!<k2(n1)! ,我们就可以确定排列的首个元素为 2 2 2
  • ⋯ \cdots
  • 如果 ( n − 1 ) ⋅ ( n − 1 ) ! < k ≤ n ⋅ ( n − 1 ) ! (n-1) \cdot (n-1)! < k \leq n \cdot (n-1)! (n1)(n1)!<kn(n1)! ,我们就可以确定排列的首个元素为 n n n

因此,第 k k k 个排列的首个元素就是:
a 1 = ⌊ k − 1 ( n − 1 ) ! ⌋ + 1 a_1 = \lfloor \frac{k-1}{(n-1)!} \rfloor + 1 a1=(n1)!k1+1
其中 ⌊ x ⌋ \lfloor x \rfloor x 表示将 x x x 向下取整。

当我们确定了 a 1 a_1 a1 后,如何使用相似的思路,确定下一个元素 a 2 a_2 a2 呢?实际上,我们考虑以 a 1 a_1 a1 为首个元素的所有排列:

  • [ 1 , n ] \ a 1 [1,n] \backslash a_1 [1,n]\a1 最小的元素为 a 2 a_2 a2 的排列一共有 ( n − 2 ) ! (n−2)! (n2)! 个;
  • [ 1 , n ] \ a 1 [1,n] \backslash a_1 [1,n]\a1 次小的元素为 a 2 a_2 a2 的排列一共有 ( n − 2 ) ! (n-2)! (n2)! 个;
  • ⋯ \cdots
  • [ 1 , n ] \ a 1 [1,n] \backslash a_1 [1,n]\a1 最大的元素为 a 2 a_2 a2 的排列一共有 ( n − 2 ) ! (n−2)! (n2)! 个;

其中 [ 1 , n ] \ a 1 [1,n] \backslash a_1 [1,n]\a1 表示包含 1 , 2 , ⋯ n 1, 2, \cdots n 1,2,n 中除去 a 1 a_1 a1 以外元素的集合。这些排列从编号 ( a 1 − 1 ) ⋅ ( n − 1 ) ! (a_1-1) \cdot (n-1)! (a11)(n1)! 开始,到 a 1 ⋅ ( n − 1 ) ! a_1 \cdot (n-1)! a1(n1)! 结束,总计 ( n − 1 ) ! (n-1)! (n1)! 个,因此 k k k 个排列实际上就对应着这其中的第
k ′ = ( k − 1 ) m o d ( n − 1 ) ! + 1 k' = (k-1) \bmod (n-1)! + 1 k=(k1)mod(n1)!+1
个排列
。这样一来,我们就把原问题转化成了一个完全相同但规模减少 1 1 1 的子问题:求 [ 1 , n ] \ a 1 [1, n] \backslash a_1 [1,n]\a1 n − 1 n-1 n1 个元素组成的排列中,第 k ′ k' k 小的排列。

算法:

  1. 设第 k k k 个排列为 a 1 , a 2 , ⋯ , a n a_1, a_2, \cdots,a_n a1,a2,,an ,我们从左往右地确定每一个元素 a i a_i ai​ 。我们用数组 valid \textit{valid} valid 记录每一个元素是否被使用过。
  2. 我们从小到大枚举 i i i
    1. 我们已经使用过了 i − 1 i−1 i1 个元素,剩余 n − i + 1 n-i+1 ni+1 个元素未使用过,每一个元素作为 a i a_i ai 都对应着 ( n − i ) ! (n-i)! (ni)! 个排列,总计 ( n − i + 1 ) ! (n-i+1)! (ni+1)! 个排列;
    2. 因此在第 k k k 个排列中, a i a_i ai 即为剩余未使用过的元素中第 ⌊ k − 1 ( n − i ) ! ⌋ + 1 \lfloor \frac{k-1}{(n-i)!} \rfloor + 1 (ni)!k1+1 小的元素;
    3. 在确定了 a i a_i ai 后,这 n − i + 1 n-i+1 ni+1 个元素的第 k k k 个排列,就等于 a i a_i ai 之后跟着剩余 n − i n-i ni 个元素的第 ( k − 1 ) m o d ( n − i ) ! + 1 (k-1) \bmod (n-i)! + 1 (k1)mod(ni)!+1 个排列。

在实际的代码中,我们可以首先将 k k k 的值减少 1 1 1,这样可以减少运算,降低代码出错的概率。对应上述的后两步,即为:

  • 因此在第 k k k 个排列中, a i a_i ai​ 即为剩余未使用过的元素中第 ⌊ k ( n − i ) ! ⌋ + 1 \lfloor \frac{k}{(n-i)!} \rfloor + 1 (ni)!k+1 小的元素;
  • 在确定了 a i a_i ai 后,这 n − i + 1 n-i+1 ni+1 个元素的第 k k k 个排列,就等于 a i a_i ai 之后跟着剩余 n − i n-i ni 个元素的第 k m o d ( n − i ) ! k \bmod (n-i)! kmod(ni)! 个排列。

实际上,这相当于我们将所有的排列从 0 0 0 开始进行编号。

class Solution {
public:string getPermutation(int n, int k) {--k;int factory[10] = {1};for (int i = 1; i < n; ++i) factory[i] = factory[i - 1] * i;bool vis[10] = {false};string ans;for (int i = n - 1; i >= 0; --i) {int num = k / factory[i];k = k % factory[i];for (int j = 1; j <= n; ++j) {if (!vis[j]) {if (num-- == 0) {ans += '0' + j;vis[j] = true;break;}}}}return ans;}
};

复杂度分析:

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n ) O(n) O(n)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/168385.shtml

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

相关文章

28 - 生产者消费者模式:电商库存设计优化

生产者消费者模式&#xff0c;在之前的一些案例中&#xff0c;我们是有使用过的&#xff0c;相信你有一定的了解。这个模式是一个十分经典的多线程并发协作模式&#xff0c;生产者与消费者是通过一个中间容器来解决强耦合关系&#xff0c;并以此来实现不同的生产与消费速度&…

NX二次开发UF_CURVE_ask_curve_struct_data 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_ask_curve_struct_data Defined in: uf_curve.h int UF_CURVE_ask_curve_struct_data(UF_CURVE_struct_p_t curve_struct, int * type, double * * curve_data ) overview…

情感对话机器人的任务体系

人类在处理对话中的情感时&#xff0c;需要先根据对话场景中的蛛丝马迹判断出对方的情感&#xff0c;继而根据对话的主题等信息思考自身用什么情感进行回复&#xff0c;最后结合推理出的情感形成恰当的回复。受人类处理情感对话的启发&#xff0c;情感对话机器人需要完成以下几…

百战python03-分支结构

文章目录 if语句的使用练习1:英制单位英寸与公制单位厘米互换2:百分制成绩转换为等级制成绩3:输入三条边长,如果能构成三角形就计算周长和面积海伦公式4:掷骰子随机做事注:需要对python有基本了解,可查看本作者python基础专栏,有任何问题欢迎私信或评论(本专栏每章内容…

从0开始学习JavaScript--深入了解JavaScript框架

JavaScript框架在现代Web开发中扮演着关键角色&#xff0c;为开发者提供了丰富的工具和抽象层&#xff0c;使得构建复杂的、高性能的Web应用变得更加容易。本文将深入探讨JavaScript框架的核心概念、常见框架的特点以及它们在实际应用中的使用。 JavaScript框架的作用 JavaSc…

STM32 寄存器配置笔记——USART配置中断接收乒乓缓存处理

一、概述 本文主要介绍如何配置USART接收中断&#xff0c;使用乒乓缓存的设计接收数据并将其回显在PC 串口工具上。以stm32f10为例&#xff0c;配置USART1 9600波特率。具体配置参考上一章节STM32 寄存器配置笔记——USART配置 打印。 乒乓缓存的设计应用场景&#xff1a;当后面…

【ceph】如何打印一个osd的op流程,排查osd在干什么

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…

canvas高级动画001:文字瀑布流

canvas实例应用100 专栏提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。 canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重要的帮助。 文章目录 示例…

elk 简单操作手册

1.1. 基础概念 EFK不是一个软件,而是一套解决方案,开源软件之间的互相配合使用,高效的满足了很多场合的应用,是目前主流的一种日志系统。 EFK是三个开源软件的缩写,分别表示:Elasticsearch , Filebeat, Kibana , 其中Elasticsearch负责日志保存和搜索,Filebeat负责收集日志,Ki…

Spring Boot 3.2.0 现已推出

Spring Boot 3.2.0 已经发布&#xff0c;并且可以从 Maven Central 获取。 此版本添加了大量新功能和改进。有关完整的[升级说明](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.2.0-Release-Notesupgrading-from-spring-boot-31)以及[值得注意的新](ht…

EI期刊完整程序:MEA-BP思维进化法优化BP神经网络的回归预测算法,可作为对比预测模型,丰富内容,直接运行,免费

适用平台&#xff1a;Matlab 2020及以上 本程序参考中文EI期刊《基于MEA⁃BP神经网络的建筑能耗预测模型》&#xff0c;程序注释清晰&#xff0c;干货满满&#xff0c;下面对文章和程序做简要介绍。 适用领域&#xff1a;风速预测、光伏功率预测、发电功率预测、碳价预测等多…

eclipse项目移到idea上部署运行

1.配置web模块 另外&#xff0c;模块这里&#xff0c;也要加上Spring 2.配置Artifact &#xff08;用于tomcat&#xff09; 就是从上面配置的web模块&#xff0c;产生的工件 3.添加lib 一般是在web-inf/lib &#xff0c; 遇到的坑&#xff1a; jdk版本问题&#xff0c;这里…

proto语法学习笔记

proto语法学习笔记 Protocol Buffers&#xff08;Proto是由谷歌开发的一种数据序列化格式。 Proto 不是一种编程语言&#xff0c;而是一种接口描述语言&#xff08;IDL&#xff09;&#xff0c;用于定义数据结构和消息格式。 它的设计目标是提供一种简单、高效、可扩展的方法来…

使用STM32+SPI Flash模拟U盘

试验目的&#xff1a;使用STM32F103C8T6 SPI Flash&#xff08;WSQ16&#xff09;实现模拟U盘的功能 SPI Flash读写说明&#xff1a; Step1 设置SPI1 用于读取SPI Flash&#xff1b; Step2&#xff1a;设置SPI Flash 的使能信号 Step3&#xff1a;使能USB通信 Step4&#xf…

人机交互2——任务型多轮对话的控制和生成

1.自然语言理解模块 2.对话管理模块 3.自然语言生成模块

C++模拟如何实现vector的方法

任意位置插入&#xff0c;insert的返回值为新插入的第一个元素位置的迭代器&#xff1b;因为插入可能会进行扩容&#xff0c;导致start的值改变&#xff0c;所以先定义一个变量保存pos与start的相对位置&#xff1b;判断是否需要扩容&#xff1b;从插入位置开始&#xff0c;将所…

WorldWind Android上加载白模数据

这篇文章介绍下如何加载白模数据。这个白模数据的格式是shapefile格式的文件。白模数据拷贝到手机本地&#xff0c;然后读取白模数据&#xff0c;进行加载展示。 worldwind android本身是不支持加载白模数据的&#xff0c;但是可以根据现有提供的加载Polygons的方式&#xff0c…

【JUC】一篇通关JUC并发之共享模型

目录 1. 共享带来的问题1-1. 临界区 Critical Section1-2. 竞态条件 Race Condition1-3. synchronized 解决方案 1. 共享带来的问题 1-1. 临界区 Critical Section 一个程序运行多个线程本身是没有问题的问题出在多个线程访问共享资源 多个线程读共享资源其实也没有问题在多个…

基于单片机的消防巡逻小车设计

智能小车循迹与避障运动控制系统的设计 摘 要:本设计主要由STC89C52单片机来进行控制&#xff0c;通过输入输出两个端口控制驱动模块来调节电机的工作状态。本设计预利用机器视觉&#xff0c;通过识别条带状路标实现自主导航且利用超声波模块实时检测距离以实现避障功能&…

Linux:Ubuntu实现远程登陆

1、查看sshd服务是否存在 Ubuntu默认是没有安装sshd服务的&#xff0c;所以&#xff0c;无法远程登陆。 检查22端口是否存在 netstat -anp 该命令执行后&#xff0c;查看不到22端口的进程。 如果netstat无法使用&#xff0c;我们需要安装一下netstat服务 sudo apt-get install…