技巧类算法题是我随口提的一个概念,意思就是这道题有自己独特的思考思路,仅仅知道它所涉及的最基础的知识点(如宽泛的双指针,动规或深度优先搜索),然后自行推理解题套路十分困难。因此在此做一个整理,面试前过一遍
题目链接:下一个排列
题目描述
整数数组的一个排列就是将其所有成员以序列或线性顺序排列。下一个排列是指字典序更大的所有排列中,最小的那一个。如果这个排列已经是这几个元素所能组成的最大的字典序时,返回字典序最小的排列。
例如,123的下一个排列是132,321的下一个排列是123,1511的下一个排列是5111
传递的参数是vector的引用,因此只能在原数组上修改,空间复杂度为O(1)
问题分析
你要找下一个排列,就需要让一个大一点的数和一个小一点的数交换位置。但是你又希望字典序增大幅度尽可能地小,所以如果将vector看作一个数,你应该优先动数位较低的数,即应该从vector后面往前遍历。
那什么时候停止遍历开始进行下一步操作呢?当然是目前所访问的数,大于数组中索引比他小一位的数。这一段略抽象,举个例:
[1,2,3,6,5,4,1]
在这个数组中,如果你在654这三个数中随便交换两个,他们都会变大,因为在这个范围内,nums[i]>nums[i+1],因此要找到nums[i]<nums[i+1]的地方,才能开始交换树的位置。
易知,当访问到3和6时,开始下一步。下一步,我们要决定谁和3进行交换,如果是3和6互换,那么nums[2]的位置会急剧变大,显然不是字典序只相差一位。可如果在3后面的数中找出最小的那一个,那会把最末尾的1和3互换,字典序反而变小了。显然,我们要找到一个刚好比3大的数。
因此,从数组末尾遍历,找到第一遇到的比3大的数就行。因为3后面的数是降序,所以遇到第一个比3大的数之后,加下来遍历的数只会更大,因此可以直接break跳出循环。
在结束循环后,还有一个小细节,3后面的那一块排列,有可能变小吗?当然,把1移到6的位置就能让排列变小。既然只交换3和4后还能变小,就说明单纯地交换之后不是我们所要的结果,为了让后面的区间的排列尽可能小,可以通过sort函数使其变为升序排列。
最后,如果找不到任何一组相邻的数是升序该怎么办呢?很简单,反转数组就行。
代码
class Solution {
public:void nextPermutation(vector<int>& nums) {int n=nums.size()-1;//寻找第一次出现逆序的相邻两数//从数组尾部找起,相当于从数字较低的位找起for(int i=n;i>=1;i--){//满足if,说明找到了第一次出现升序的相邻数对if(nums[i]>nums[i-1]){//再次从末尾找起,寻找刚好比i-1要大的数for(int j=n;j>=i;j--){//找到了可以直接break,此时i-j之间的数一定比nums[j]大if(nums[j]>nums[i-1]){int temp=nums[j];nums[j]=nums[i-1];nums[i-1]=temp;break;}}//最后,将i-1后面的部分进行排序,使他们的字典序尽可能小sort(nums.begin()+i,nums.end());return;}}//因为满足for里面的if后,必定会执行return//所以函数但凡执行到这里,就说明nums完全单调递减,字典序达到了最大//此时将数组反即可//不要用排序,毕竟排序要多次比较,玩意sort底层算法用了快排,时间复杂度直接O(nlogn)//而反转函数完全不比较大小,O(n)时间复杂度搞定reverse(nums.begin(),nums.end());return;}
};