php 实现的字典序排列算法,字典序的一个生成算法

字典序的一个生成算法。

最近在LeetCode刷题,刷到一个题,链接:

https://leetcode-cn.com/problems/permutation-sequence/

这个题要求得长度为n的字典序列的第k个排列。

我们知道,字典序列是一个长度为n(n>=1),元素为1~n的无重复整数序列。

之前还真没仔细了解过如何按照顺序,从小到大生成这个序列。这次就探究一下。

我先在纸上枚举了n=3、4、5这几种简单的序列的生成,从中找到规律,然后推理出一般方法。

以n=4为例,字典序从小到大生成如下:

1234 → 1243 → 1324 → 1342 → 1423 → 1432 → 2134 → 2143 → 2314 → 2341 → 2413 → 2431 → 3124 → 3142 → 3214 → 3241 → 3412 → 3421 → 4123 → 4132 → 4213 → 4231 → 4312 → 4321

当我们拥有了从第m个排列到m+1个排列的生成方法时,就可以写一个算法findNext(),通过k-1次生成排列,就可以求出第k次的排列。

那么接下来就是寻找字典序的规律:

我们能够知道 如果当前字典序排列为M,假设M的下一个字典序为N,N也有下一个字典序O,那么有以下推论:

1. N = findNext(M)

2. O = findNext(N)

3. M < N < O

所以可得:N是大于M的最小的排列

既然我们要生成这样的一个排列,那么就要尽可能变动位数更低的数去增大序列:

以 findNext(1243)为例,为了尽可能变动位数更低的数去增大序列,由于“43”已经是降序排列的子序列,无法通过变动“4”这个位及更低的位去增大序列,那么只能从上一位“2”去增大序列,所以我们要从“43”这个降序序列中找到一个最的数“3”,换到“2”的位置,把“2”放入降序序列中,然后重新按照升序排序,这样就生成了“1324”,即1324 = findNext(1243)

所以我们有以下思路:

1. 从最低位开始寻找最长的递减序列L的最高位i

2. 如果i是最高位,证明已经是最大的字典序,算法结束;如果不是,取i的上一位j,从L中找到大于j的最小值k,然后交换jk位置

3. 对L进行升序排序,把L变为最小序列

Java代码如下:

public class GetPermutation {

public static String getPermutation(int n, int k) {

if(n <= 0 || k <= 0){

return "";

}

int[] array = new int[n];

for (int i = 0; i < n; i++) {

array[i] = i + 1;

}

for (int i = 1; i < k; i++) {

findNext(array);

}

return intArrayToString(array);

}

public static void findNext(int[] array){

if(array != null && array.length > 1){

int left_exchange_index = -1;

//找到最长逆序的上一位

for (int i = array.length - 1; i > 0; i--) {

if(array[i - 1] < array[i]){

left_exchange_index = i - 1;

break;

}

}

//如果还有更大的序列

if(left_exchange_index != -1){

//找到交换点的位置

int right_exchange_index = findExchangeIndex(array, left_exchange_index);

//交换

exchange(array, left_exchange_index, right_exchange_index);

//对交换后的序列升序排序

sortRight(array, left_exchange_index + 1);

}

}

}

public static int findExchangeIndex(int[] array, int left_exchange_index){

int left = left_exchange_index + 1;

int right = array.length - 1;

int temp = array[left_exchange_index];

int middle = (left + right) / 2;

while(left < right){

//找到逆序内大于目标值的最小值

if(array[middle] > temp && array[middle + 1] < temp){

return middle;

}else if(array[middle] < temp){

right = middle - 1;

middle = (left + right) / 2;

}else {//array[middle + 1] > temp

left = middle + 1;

middle = (left + right) / 2;

}

}

//就剩一个,只能和它换了

if(left == right){

return left;

}

return -1;

}

public static void exchange(int[] array, int left, int right){

int temp = array[left];

array[left] = array[right];

array[right] = temp;

}

public static void sortRight(int[] array, int left){

Arrays.sort(array, left, array.length);

}

public static String intArrayToString(int[] array){

StringBuffer temp = new StringBuffer();

for(int value : array){

temp.append(value);

}

return temp.toString();

}

public static void main(String[] args) {

System.out.println(getPermutation(4, 9));

}

}

该算法能够计算出长度为n的字典序的第k个排列。

后来啊,我想了想,这个方法有些慢,毕竟k次移动,只有最后一次是有意义的,之前的k-1次移动都是白白浪费了运算。于是打算优化一下算法。

从优化生成字典序的方法开始吧,上面的算法,移动次数很多,这次优化可以采用回溯法处理。思路如下图所示:(本图来自Leetcode该题优秀题解,自己不想画图了,借用一哈)

bVby3pX?w=1139&h=432

生成字典序的优化思路如下:

1. 构造一个1~n的升序序列N

2. 从小到大逐个取N中的数,递归放入空序列M中

3. 当该序列的数全部用光时,记录该序列,拿走M中尾数字,回溯

4. 来到上一层,证明该层刚才递归用的数字已经用过了,从M尾部拿出,从N中取更大的一个数,递归放入M,回到步骤2

5. 当最外层使用了N中最大的数,并且回溯之后,证明所有序列已经生成,算法结束。

Java代码如下:

class Solution {

public List> permute(int[] nums) {

List> res = new ArrayList<>();

List temp = new ArrayList<>();

boolean[] used = new boolean[nums.length];

arrange(res, used, nums, temp);

return res;

}

public static void arrange(List> res, boolean[] used, int[] nums, List temp){

if(temp.size() == nums.length){

res.add(new ArrayList<>(temp));

return;

}

for(int i = 0; i < used.length; i++){

if(used[i] == false){

used[i] = true;

temp.add(nums[i]);

arrange(res, used, nums, temp);

used[i] = false;

temp.remove(temp.size() - 1);

}

}

return;

}

}

使用的used数组是为了记录该位置的数字是否使用过。

有了这个递归思路之后,通过剪枝的操作,就可以快速定位第k个字典序所在的分支,直接找到并返回。

以n = 4, k = 9为例 所求序列为 L

以1开头的序列一共有 3*2 = 6个

因为k = 9 > 6

所以L肯定不以1开头。

以2开头的序列也有 3*2 = 6 个

以2为开头的序列应该是第7个至第12个

因为 7 < k < 12

所以L以2开头。

以21开头的序列一共有 2*1 = 2个

以21开头的序列应该是第7个至第8个

因为 8 < k

所以L不以21开头

以23开头的序列一共有 2*1 = 2个

以23开头的序列应该是第9个至第10个

因为 k == 9

所以L以23开头且是23开头的第一个数,就是2314

求解完毕。

将剪枝操作放在递归之前,即可求解,Java代码如下:

public class GetPermutation_better {

public static String getPermutation(int n, int k) {

int[] list = new int[]{1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};

int k_inner = k;

Map used = new HashMap<>(16);

for (int i = 1; i <= n; i++) {

used.put(i, false);

}

List array = new ArrayList<>();

arrange(array, used, k_inner, list);

StringBuffer buffer = new StringBuffer();

for(int temp : array){

buffer.append(temp);

}

return buffer.toString();

}

public static void arrange(List array, Map used, int k, int[] list){

if(array.size() == used.size()) {

return;

}

int inner_k = k;

for (int i = 1; i <= used.size(); i++) {

if(used.get(i)){

continue;

}else {

int num = used.size() - array.size() - 1;

//判断当前的这个值,是否在这个分支内

if(inner_k <= list[num]){

array.add(i);

used.put(i, true);

arrange(array, used, inner_k, list);

}

else {//不在就切换到下一个分支,去掉之前的个数

inner_k = inner_k - list[num];

}

}

}

}

public static void main(String[] args) {

System.out.println(getPermutation(3, 2));

}

}

为了不再多构造一个int数组来存递增数列,将boolean数组升级为HashMap,兼具int数组与used数组的功能。

由于每层遍历从1开始,可能会遇到已经用过的数,这种情况下,不能剪枝,因为剪枝只针对还没有用过的数的分支,所以要先判断该数是否用过,再判断是否需要剪枝。

该算法不使用递归:

public class GetPermutation_best {

public static String getPermutation(int n, int k) {

int[] list = new int[]{1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};

int k_inner = k;

Map used = new HashMap<>(16);

for (int i = 1; i <= n; i++) {

used.put(i, false);

}

List array = new ArrayList<>();

arrange(array, used, k_inner, list);

StringBuffer buffer = new StringBuffer();

for(int temp : array){

buffer.append(temp);

}

return buffer.toString();

}

public static void arrange(List array, Map used, int k, int[] list){

int inner_k = k;

for (int i = 1; i < used.size(); i++) {

int num = used.size() - array.size() - 1;

int integer = inner_k / list[num];

int rest = inner_k % list[num];

int index;

if(rest == 0){

index = integer;

inner_k = list[num];

}else {

index = integer + 1;

inner_k = rest;

}

array.add(getNum(index, used));

}

array.add(getNum(1, used));

}

public static int getNum(int index, Map used){

int counter = 0;

for (int i = 1; i <= used.size(); i++) {

if(!used.get(i)){

counter++;

}

if(index == counter){

used.put(i, true);

return i;

}

}

return -1;

}

public static void main(String[] args) {

System.out.println(getPermutation(3, 3));

}

}

两种优化算法均为O(n^2)时间复杂度。

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

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

相关文章

BeetleX服务网关流量控制

为了保障后台服务应用更可靠地运行&#xff0c;网关提供了一些基础流量控制功能&#xff1b;通过这一功能可以限制流转到后台应用服务的处理量&#xff0c;从而让服务在可应对的并发范围内更可靠地运作。服务网关提供了流量控制有基础控制、IP、域名和请求路径。基础配置主要包…

【cocos2d-x】2.0升级为3.0一些常见变化纪录

1.去CC之前2.0的CC**,把CC都去掉&#xff0c;基本的元素都是保留的2.0CCSprite CCCallFunc CCNode ..3.0Sprite CallFunc Node ..2.cc***结构体改变2.0 ccp(x,y) ccpAdd(p1,p2)ccpSubccpMultccpLength(p)ccpDot(p1,p2);ccc3()ccc4()ccWHITECCPointZeroCCSizeZer…

Java Web开发——Servlet监听器

一、Servlet监听器的概念 Servlet监听器是Servlet规范中定义的一种特殊类&#xff0c;用于监听ServletContext、HttpSession和ServletRequest等域对象的创建与销毁事件&#xff0c;以及监听这些域对象中属性发生修改的事件。 监听对象&#xff1a; 1、ServletContext&#xff1…

通过Dapr实现一个简单的基于.net的微服务电商系统(十九)——分布式事务之Saga模式...

目录&#xff1a;一、通过Dapr实现一个简单的基于.net的微服务电商系统二、通过Dapr实现一个简单的基于.net的微服务电商系统(二)——通讯框架讲解三、通过Dapr实现一个简单的基于.net的微服务电商系统(三)——一步一步教你如何撸Dapr四、通过Dapr实现一个简单的基于.net的微服…

php怎么关闭oracle连接,PHP 连接 Oracle

起因由于项目的数据库需要用客户购买的Oracle数据库&#xff0c;所以需要php安装oci扩展。运行环境php : 7.2系统: windows10oracle: 11gR2安装相关环境由于php的oci8扩展还是需要使用到oracle的一些包&#xff0c;所以先下载这一些。下载完成后解压缩这个压缩包&#xff0c;并…

.NET 深度指南:Colors

作者 &#xff5c; Peter Huber译者 &#xff5c; 王强策划 &#xff5c; 丁晓昀我不知道你们是什么情况&#xff0c;但我自己在过去多年中都因为.NET 色彩&#xff08;Colors&#xff09;类中可用的色彩数量有限而头痛不已&#xff0c;为此我试图用 ColorPickers 获得匹配的色…

php 怎么打出来的,word书名号怎么打出来

书名号怎么打出来&#xff1f;书名号相信大家都不会陌生了&#xff0c;正常情况下&#xff0c;我们会将书名、歌曲名、作品名等用书名号框起来&#xff0c;这样就可以让读者一目了然。然而很多用户在编辑Word和Excel文档时&#xff0c;想输入书名号却不知从何下手&#xff0c;这…

springMVC带文件的表单数据无法绑定到参数中

2019独角兽企业重金招聘Python工程师标准>>> 在一个带enctype"multipart/form-data"属性的表单提交时发现&#xff0c;该表单中包含的其他input无法设置到对应方法参数中。 如下&#xff1a; JSP&#xff1a;带enctype"multipart/form-data"属性…

关于Retinex图像增强算法的一些新学习。

最近再次看了一下IPOL网站&#xff0c;有一篇最近发表的文章&#xff0c;名字就是Multiscale Retinex&#xff0c;感觉自己对这个已经基本了解了&#xff0c;但还是进去看了看&#xff0c;也有一些收获&#xff0c;于是抽空把他们稍微整理了下&#xff0c;原始文章及其配套代码…

如何判断 .NET Core 应用程序是以管理员身份运行

有时候&#xff0c;我们需要知道当前程序是否以管理员身份运行&#xff0c;以便执行一些需要特殊权限的操作。在github(https://github.com/dotnet/runtime/issues/25118#issuecomment-367407469)上找到了一个解决方案&#xff1a;//需要引用nuget包Mono.Posix.NETStandard pub…

Android之Content和activity、service、Application关系和attachBaseContext函数调用的时候

1、Content和activity、service、Application关系 2、Application里面attachBaseContext和onCreate函数调用顺序 Application-> attachBaseContext ();ContentProvider:onCreate()Application:onCreate()人还是容易忘记&#xff0c;先记录下来。

批量创建域账号

创建5列的csv文档&#xff08;注意文件编码&#xff09; 执行如下脚本 for /f "tokens1,2,3,4,5 delims," %a in (c:\test.csv) do dsadd user "cn%c,oumk,ouicgroup,dcicdomain,dccom" -samid %d -upn %dicdomain.com -ln %a -fn %b -pwd %e -disabled no…

如何快速编写并运行Tiny模板语言?

2019独角兽企业重金招聘Python工程师标准>>> 说到模板开发&#xff0c;当然就离不开要调试&#xff0c;要运行。 由于一般情况下模板语言都是由Java程序驱动跑的&#xff0c;因此&#xff0c;每次都需要搞一个Java类来驱动它&#xff0c;才能运行出结果。这个对于悠…

Windows 11 上大招!正式支持安卓!

面向 Windows 11 正式版用户&#xff0c;微软现已发布累积更新 KB5010414&#xff0c;更新后版本号升级至 Build 22000.527。KB5010414 是一个可选更新&#xff0c;因此除非您主动点击“获取更新”按钮&#xff0c;否则它不会下载或安装。该更新将于 2022 年 3 月向所有 Window…

博客流量分析

接连两篇Spark内核分析的文章都被推荐到CSDN首页&#xff0c;带来的流量还是很客观的&#xff0c;基本上一天最少500个独立IP的访问。这个访问量还是很客观的&#xff0c;比推荐到博客首页和侧边栏还是效果好处不少的。88%的流量来自首页。北京不愧是码农的聚集地啊&#xff0c…

WPF 基础控件之CheckBox样式

WPF开发者QQ群&#xff1a;340500857由于微信群人数太多入群请添加小编微信号yanjinhuawechat 或 W_Feng_aiQ 邀请入群需备注WPF开发者 PS&#xff1a;有更好的方式欢迎推荐。支持NugetInstall-Package WPFDevelopers.Minimal -Version 3.0.001—代码如下一、创建 Styles.Check…

Theano3.2-练习之数据集及目标函数介绍

来自http://deeplearning.net/tutorial/gettingstarted.html#gettingstarted 一、下载 在后续的每个学习算法上&#xff0c;都需要下载对应的文档&#xff0c;如果想要一次全部下好&#xff0c;那么可以复制git上面的这个教程的资料&#xff1a; git clone git://github.com/li…

C#使用NPOI导出Excel文件

欢迎您成为我的读者&#xff0c;希望这篇文章能给你一些帮助。前言今天咱们一起来看看在C#中如何使用NPOI第三方控件进行数据的导出。关于NPOI插件网上资料很多&#xff0c;大家感兴趣的可以去看看。本文使用的版本是NPOI 2.5.1。大家可在包管理器NuGet或者下面网址进行下载。h…

Android之ActivityLifecycleCallbacks的得到当前的activity的状态

1、ActivityLifecycleCallbacks的介绍 Application通过此接口提供了一套回调方法,用于让开发者对Activity的生命周期事件进行集中处理,注意是在Application里面,路径如下 android.app.Application.ActivityLifecycleCallbacks要求API 14+ (Android 4.0+) ActivityLifecyc…

开放产品开发(OPD):Archi 汉化工具下载

在OPD中&#xff0c;我们使用了ArchiMate 作为架构语言&#xff0c;这是一个高层、简单的描述语言&#xff0c;之前我也写过一系列相关的文字&#xff0c;架构语言ArchiMate &#xff0d; 开篇&#xff1a;企业架构语言ArchiMate介绍架构语言ArchiMate &#xff0d; 语言结构&a…