算法班笔记 第七章 基于排列、图的DFS

第七章 基于排列、图的DFS

  1. 全排列问题如何使用深度优先搜索来实现?和全子集问题的异同在哪儿?
  2. 全排列问题的 Follow up: Permutation II。如何去重?
  3. 如何求一个排列的下一个排列?
  4. 如何求一个排列是第几个排列?

如何求下一个排列

问题描述

给定一个若干整数的排列,给出按整数大小进行字典序从小到大排序后的下一个排列。若没有下一个排列,则输出字典序最小的序列。
例如1,2,31,3,23,2,11,2,31,1,5 → 1,5,1

原题一个要求原地修改,一个要求返回新的排列 

算法描述

如果上来想不出方法,可以试着找找规律,我们关注的重点应是原数组末尾。

从末尾往左走,如果一直递增,例如...9,7,5,那么下一个排列一定会牵扯到左边更多的数,直到一个非递增数为止,例如...6,9,7,5。对于原数组的变化就只到6这里,和左侧其他数再无关系。6这个位置会变成6右侧所有数中比6大的最小的数,而6会进入最后3个数中,且后3个数必是升序数组

所以算法步骤如下:

  • 从右往左遍历数组nums,直到找到一个位置i,满足nums[i] > nums[i - 1]或者i0
  • i不为0时,用j再次从右到左遍历nums,寻找第一个nums[j] > nums[i - 1]。而后交换nums[j]nums[i - 1]注意,满足要求的j一定存在!且交换后nums[i]及右侧数组仍为降序数组
  • nums[i]及右侧的数组翻转,使其升序。

Q:i为0怎么办?
A:i为0说明整个数组是降序的,直接翻转整个数组即可。

Q:有重复元素怎么办?
A:在遍历时只要严格满足nums[i] > nums[i - 1]nums[j] > nums[i - 1]就不会有问题。

Q:元素过少是否要单独考虑?
A:当元素个数小于等于1个时,可以直接返回。

 

全排列问题

全排列问题是“排列式”深度优先搜索问题的鼻祖。很多搜索的问题都可以用类似全排列的代码来完成。包括我们前面学过的全子集问题的一种做法。

这一小节中我们需要掌握:

  1. 普通的全排列问题怎么做
  2. 有重复的全排列问题怎么做?如何在搜索类问题中去重?
    1. 从1做极小改动
  3. 如何实现一个非递归的全排列算法?

基本思路

非递归的全排列,采用的是迭代方式,在如何求下一个排列中,我们讲过如何求下一个排列,那么我们只需要不断调用这个nextPermutation方法即可

一些可以做得更细致的地方

  • 为了确定何时结束,建议在迭代前,先对输入nums数组进行升序排序,迭代到降序时,就都找完了。有心的同学可能还记得在nextPermutation当中,当且仅当数组完全降序,那么从右往左遍历的指针i最终会指向0。所以可以为nextPermutation带上布尔返回值,当i为0时,返回false,表示找完了。要注意,排序操作在这样一个NP问题中,消耗的时间几乎可以忽略。
  • 当数组长度为1时,nextPermutation会直接返回false;当数组长度为0时,nextPermutationi会成为-1,所以返回false的条件可以再加上i-1

 

如何求一个排列是第几个排列?

题目描述

给出一个不含重复数字的排列,求这些数字的所有排列按字典序排序后该排列的编号,编号从1开始。
例如排列[1,2,4]是第1个排列。

算法描述

只需计算有多少个排列在当前排列A的前面即可。如何算呢?举个例子,[3,7,4,9,1],在它前面的必然是某位置i对应元素比原数组小,而i左侧和原数组一样。也即[3,7,4,1,X][3,7,1,X,X][3,1或4,X,X,X][1,X,X,X,X]
而第i个元素,比原数组小的情况有多少种,其实就是A[i]右侧有多少元素比A[i]小,乘上A[i]右侧元素全排列数,即A[i]右侧元素数量的阶乘。i从右往左看,比当前A[i]小的右侧元素数量分别为1,1,2,1,所以最终字典序在当前A之前的数量为1×1!+1×2!+2×3!+1×4!=39,故当前A的字典序为40。

具体步骤:

  • permutation表示当前阶乘,初始化为1,result表示最终结果,初始化为0。由于最终结果可能巨大,所以用long类型。
  • i从右往左遍历A,循环中计算A[i]右侧有多少元素比A[i]小,计为smallerresult += smaller * permutation。之后permutation *= A.length - i,为下次循环i左移一位后的排列数。
  • 已算出多少字典序在A之前,返回result+1

 

Q:为了找寻每个元素右侧有多少元素比自己小,用了O(n^2)的时间,能不能更快些?
A:可以做到O(nlogn)!但是很复杂,这是另外一个问题了,可以使用BST,归并排序或者线段树

Q:元素有重复怎么办?
A:好问题!元素有重复,情况会复杂的多。因为这会影响A[i]右侧元素的排列数,此时的排列数计算方法为总元素数的阶乘,除以各元素值个数的阶乘,例如[1,1,1,2,2,3],排列数为6!÷(3!×2!×1!)。
为了正确计算阶乘数,需要用哈系表记录A[i]及右侧的元素值个数,并考虑到A[i]与右侧比其小的元素A[k]交换后,要把A[k]的计数减一。用该哈系表计算正确的阶乘数。
而且要注意,右侧比A[i]小的重复元素值只能计算一次,不要重复计算!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值