leetcode练习之数组31. 下一个排列

题目

实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

必须原地修改,只允许使用额外常数空间。

以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/next-permutation
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目解读

我看了题目几遍,发现完全不懂问的是什么,后来在评论区找到通俗版本。另外当不懂题目在说什么的时候,看一下答题区的方法名可以给你提示。nextPermutation,这是c++中用于全排列的函数,可以获取这个数组排序的所有数中,刚好比当前数大的那个数。

比如当前 nums = [1,2,3]。这个数是123,找出1,2,3这3个数字排序可能的所有数,排序后,刚好比123大的那个数,也就是132

如果当前 nums = [3,2,1]。这就是1,2,3所有排序中最大的那个数,那么就返回1,2,3排序后所有数中最小的那个,也就是 [1,2,3]

答题思路

思路一开始由于题目都没有明白,所以我对此题是放弃状态,后来看了官方题解后,觉得好简单。果然还是需要相信自己。

暴力法

其实当没有思路的时候,暴力法总能大力出奇迹,所以只要有想法,然后再思考如何优化就离成功不远了。

暴力法很直接,就是将数组的所有组合方式都找到,然后找到比给出的数值刚好大的组合,或者如果给出数值是最大的,那就返回最小的组合。

一遍扫描法

一遍扫描法思路的起点是思考如何找到临界状态,本题的临界状态是数组对应的数值刚好是最大的组合。那么就是数组从左到右为降序排列。因此如果数组不是临界状态的话,我们就需要找到数组的右半段是降序排列,但是中间的数比右边降序的起始值要小。

  1. 从右往左扫描数组,找到第一组nums[i] < nums[i+1],这样我们找到了i
  2. 如果i不是负数的话,也就是数组没有走完,i的右侧为降序排列的数,我们需要从中找到刚好比nums[i]大的数值nums[j],从右往左扫描,找到nums[j] > nums[i],这样我们找到了j
  3. 交换nums[i]和nums[j]的值
  4. 将i+1到数组结束的这段数值进行升序排列
  5. 由于这段本来就是降序的,即使交换了nums[i]和nums[j]也是,所以我们可以将这段slice,从nums[i+1]到nums[end]从两边到中间的两两交换即可
  6. 如果i是负数,那就是说当前排列就是最大的数值,所以还是需要进行第5步,获取到最小的数值,那么对应的交换范围就是从nums[0]到nums[end]了

PHP版本

这是我提交的第一版php代码,结果显示执行用时超过39.02%的php提交记录。感觉除了优化排序算法之外没什么点了,但是优化排序算法不是本题的考查点。后来查看了题解,觉得可以尝试将for循环替换成while试试。
执行用时:16 ms
内存消耗:14.8 MB

class Solution {

    /**
     * @param Integer[] $nums
     * @return NULL
     */
    function nextPermutation(&$nums) {
        for ($i = count($nums)-2;$i>=0&&$nums[$i]>=$nums[$i+1];$i--) {}
        if ($i>=0) {
            //find j and swap
            for ($j = count($nums)-1;$nums[$j]<=$nums[$i];$j--) {}
            $this->swap($nums,$i,$j);
        }
        $this->reverse($nums,$i+1);
    }
    function swap(&$nums,$i,$j) {
        $tmp = $nums[$i];
        $nums[$i] = $nums[$j];
        $nums[$j] = $tmp;
    }
    function reverse(&$nums,$start) {
        $end = count($nums)-1;
        while($start<$end) {
            $this->swap($nums,$start,$end);
            $start++;
            $end--;
        }
    }
}

while循环版本,这次执行用时超过了73.17%的php提交记录。惊人的发现,while比for循环在php语言中要省时,具体扩展对比见思考。
执行用时:12 ms
内存消耗:14.7 MB

class Solution {

    /**
     * @param Integer[] $nums
     * @return NULL
     */
    function nextPermutation(&$nums) {
        $i = count($nums)-2;
        while($i>=0 && $nums[$i]>=$nums[$i+1]){
            $i--;
        }
        if ($i>=0) {
            //find j and swap
            $j = count($nums)-1;
            while($nums[$j]<=$nums[$i]){
                $j--;
            }
            $this->swap($nums,$i,$j);
        }
        $this->reverse($nums,$i+1);
    }
    function swap(&$nums,$i,$j) {
        $tmp = $nums[$i];
        $nums[$i] = $nums[$j];
        $nums[$j] = $tmp;
    }
    function reverse(&$nums,$start) {
        $end = count($nums)-1;
        while($start<$end) {
            $this->swap($nums,$start,$end);
            $start++;
            $end--;
        }
    }
}

最后,我查看了php提交记录中用时最短和内存最少的代码。发现如果想要内存少,那就把我这里拆出来的swap和reverse函数去掉,写在函数体中。如果想要用时更短,那就将数组长度为0和1的状况前置判断即可。

Go版本

版本一,直接按照理解书写,结果差强人意
执行用时:4 ms
内存消耗:2.5 MB

func nextPermutation(nums []int)  {
    i:= len(nums)-2
    for ;i>=0&&nums[i]>=nums[i+1];i-- {}
    if (i>=0) {
        //find j and swap
        j:= len(nums)-1
        for ;nums[j]<=nums[i];j-- {}
        swap(nums,i,j)
    }
    reverse(nums,i+1)
}

func swap(nums []int, i, j int) {
    tmp := nums[i]
    nums[i] = nums[j]
    nums[j] = tmp
}
func reverse(nums []int, start int) {
    for end := len(nums)-1;start<end; {
        swap(nums,start,end)
        start++
        end--
    }

}

经过学习,终于优化出一版厉害起来连我自己都打的代码
执行用时:0 ms
内存消耗:2.5 MB

func nextPermutation(nums []int)  {
    i:= len(nums)-2
    for ;i>=0&&nums[i]>=nums[i+1];i-- {}
    if (i>=0) {
        //find j and swap
        j:= len(nums)-1
        for ;nums[j]<=nums[i];j-- {}
        nums[i],nums[j] = nums[j],nums[i]
    }
    start,end := i+1,len(nums)-1
    for start<end {
        nums[start],nums[end] = nums[end],nums[start]
        start++
        end--
    }
}

点亮了Golang中swap的新技能

nums[i],nums[j] = nums[j],nums[i]

思考

根据从汇编看for和while循环的效率一文从汇编命令的分析,在c语言中for和while是一样的,但是在php语言中while比for的命令更少,所以执行速度更快。

另根据C和PHP中while和for效率的一点研究一文对for和while分别进行亿级和十亿级的空循环测试,验证出在C中,对于空循环,for和效率明显要比while高。在PHP中,这种情况下while的效率明显高于for循环。

总结

  1. 暴力法的时间复杂度:O(n!)。空间复杂度:O(n)。
  2. 一遍扫描法的时间复杂度为O(n),空间复杂度为O(1)。
  3. php语言中while比for循环的效率高。
  4. go语言中的swap可以直接使用a,b=b,a的形式,不需要第三个参数来暂存。
发布了19 篇原创文章 · 获赞 1 · 访问量 239

猜你喜欢

转载自blog.csdn.net/helen920318/article/details/104801035