乘风破浪:LeetCode真题_031_Next Permutation

乘风破浪:LeetCode真题_031_Next Permutation

一、前言

这是一道经典的题目,我们实在想不出最好的方法,只能按照已有的方法来解决,同时我们也应该思考一下为什么要这样做?是怎么想到的?这比我们记住步骤更加的有用。

二、Next Permutation

2.1 问题

2.2 分析与解决

排列(Arrangement),简单讲是从N个不同元素中取出M个,按照一定顺序排成一列,通常用A(M,N)表示。当M=N时,称为全排列(Permutation)。从数学角度讲,全排列的个数A(N,N)=(N)*(N-1)*...*2*1=N!,但从编程角度,如何获取所有排列?那么就必须按照某种顺序逐个获得下一个排列,通常按照升序顺序(字典序)获得下一个排列。
     例如对于一个集合A={1,2,3,},首先获取全排列a1: 1,2,3,;然后获取下一个排列a2: 1,3,2,;按此顺序,A的全排列如下:

a1: 1,2,3;  a2: 1,3,2;  a3: 2,1,3;  a4: 2,3,1;  a5: 3,1,2;  a6: 3,2,1;  共6种。

   对于给定的任意一种全排列,如果能求出下一个全排列的情况,那么求得所有全排列情况就容易了。好在STL中的algorithm已经给出了一种健壮、高效的方法,下面进行介绍。

/**
 * current: 3   7  6  2  5  4  3  1  .
 *                    |  |     |     |
 *          find i----+  j     k     +----end
 * swap i and k :
 *          3   7  6  3  5  4  2  1  .
 *                    |  |     |     |
 *               i----+  j     k     +----end
 * reverse j to end :
 *          3   7  6  3  1  2  4  5  .
 *                    |  |     |     |
 *          find i----+  j     k     +----end
 * */
1  具体方法为:
2 a)从后向前查找第一个相邻元素对(i,j),并且满足A[i] < A[j]。易知,此时从j到end必然是降序。可以用反证法证明,请自行证明。
3 b)在[j,end)中寻找一个最小的k使其满足A[i]<A[k]。由于[j,end)是降序的,所以必然存在一个k满足上面条件;并且可以从后向前查找第一个满足A[i]<A[k]关系的k,此时的k必是待找的k。
4 c)将i与k交换。
5     此时,i处变成比i大的最小元素,因为下一个全排列必须是与当前排列按照升序排序相邻的排列,故选择最小的元素替代i。易知,交换后的[j,end)仍然满足降序排序。因为在(k,end)中必然小于i,在[j,k)中必然大于k,并且大于i。
6 d)逆置[j,end)
7      由于此时[j,end)是降序的,故将其逆置。最终获得下一全排序。
8 e) 结束
9     如果在步骤a)找不到符合的相邻元素对,即此时i=begin,则说明当前[begin,end)为一个降序顺序,即无下一个全排列,STL的方法是将其逆置成升序。

    通过上面的描述,我们可以进行一次全排列的算法,就会发现真的非常的有用,那么到底是怎么相处这种方法呢?我想其中的有一点非常重要,那就是每次都要从最右边向左边找到两个相邻的元素,使得满足小于关系。然后将小于关系左边的数字与右边第一个大于左边的数字交换顺序,这样之后再将右边的序列按照从小到大顺序来排列,这样做的好处是使得算法能继续运行下去,最妙的是,将更大的数字交换到左边,随着循环顺序的加深肯定左边的数字会越来越大,最终直至变成从大到小顺序来排列,这样就达到了目的。

public class Solution {
    public void nextPermutation(int[] nums) {
        int i = nums.length - 2;
        while (i >= 0 && nums[i + 1] <= nums[i]) {
            i--;
        }
        if (i >= 0) {
            int j = nums.length - 1;
            while (j >= 0 && nums[j] <= nums[i]) {
                j--;
            }
            swap(nums, i, j);
        }
        reverse(nums, i + 1);
    }

    private void reverse(int[] nums, int start) {
        int i = start, j = nums.length - 1;
        while (i < j) {
            swap(nums, i, j);
            i++;
            j--;
        }
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

三、总结

遇到有些问题,是大家共同的认识并且经过长期探索得到的,如果我们使用传统的方法可能需要花费非常多的时间,因此,我们平时要多做题,从而懂得更多。

原文地址:https://www.cnblogs.com/zyrblog/p/10223838.html

时间: 2024-08-30 01:57:48

乘风破浪:LeetCode真题_031_Next Permutation的相关文章

乘风破浪:LeetCode真题_002_Add Two Numbers

乘风破浪:LeetCode真题_002_Add Two Numbers 一.前言     这次的题目是关于链表方面的题目,把两个链表对应节点相加,还要保证进位,每个节点都必须是十进制的0~9.因此主要涉及到链表,指针方面的知识,以及活学活用的编程能力. 二.LeetCode真题_002_Add Two Numbers 2.1 问题介绍 2.2 分析与解决 看到这样的问题,我们首先要分析清题意,之后画出一个原理图,然后就便于解决了.可以看到主要是包括了进位的问题,因此我们每一次相加的时候需要考虑到

乘风破浪:LeetCode真题_008_String to Integer (atoi)

乘风破浪:LeetCode真题_008_String to Integer (atoi) 一.前言 将整型转换成字符串,或者将字符串转换成整型,是经常出现的,也是必要的,因此我们需要熟练的掌握,当然也有很多工具来实现了,但是在这个基础上加入一些其他的因素就是考点的所在了. 二.String to Integer (atoi) 2.1 问题理解 2.2 问题分析和解决     看到这个问题,我们就需要遍历字符串,然后判断开始的时候是不是空格,+,-,或者数字,如果不是的话就是不合理的,如果是,则继

乘风破浪:LeetCode真题_007_Reverse Integer

乘风破浪:LeetCode真题_007_Reverse Integer 一.前言 这是一个比较简单的问题了,将整数翻转,主要考察了取整和取余,以及灵活地使用long型变量防止越界的问题. 二.Reverse Integer 2.1 问题理解 2.2 问题分析与解决    可以看到通过简单地取整和取余运算就能得到答案,但是需要注意越界问题,使用long在Java中8个字节的特性来完成越界检查和处理.    我们的算法: public class Solution { /** * <pre> *

乘风破浪:LeetCode真题_013_Roman to Integer

乘风破浪:LeetCode真题_013_Roman to Integer 一.前言 上一节我们讨论了如何把阿拉伯数字转换成罗马数字,现在我们需要思考一下如何把罗马数字转换成阿拉伯数字,其实我们仔细观擦这些结构就会发现罗马数字如果前面的比后面的小,就需要用后面的减去前面的.而且如果有这样的运算,也只是两个字符拼接而成的,这为我们解题提供了思路. 二.Roman to Integer 2.1 问题 2.2 分析与解决 根据题意,我们可以明白只需要从开始到结尾遍历这些罗马数字,如果发现前一个小于后一个

乘风破浪:LeetCode真题_021_Merge Two Sorted Lists

乘风破浪:LeetCode真题_021_Merge Two Sorted Lists 一.前言 关于链表的合并操作我们是非常熟悉的了,下面我们再温故一下将两个有序链表合并成一个的过程,这是基本功. 二.Merge Two Sorted Lists 2.1 问题 2.2  分析与解决 这是比较简单的,将两个已经有序的链表合并成一个,只需要两个指针不断的遍历和比较,判断谁的大以此来变换指针即可. /** * Definition for singly-linked list. * public cl

乘风破浪:LeetCode真题_024_Swap Nodes in Pairs

乘风破浪:LeetCode真题_024_Swap Nodes in Pairs 一.前言 这次还是链表的操作,不过我们需要交换链表奇数和偶数位置上的节点,因此要怎么做呢? 二.Swap Nodes in Pairs 2.1 问题      要求是不能修改节点的元素,并且使用的空间也是常数级别的,不能使用更高的内存空间. 2.2 分析与解决      通过分析我们可以使用简单的遍历算法,通过四个指针来解决,当然我们也可以使用递归算法来解答. 使用正常遍历算法: public class Solut

乘风破浪:LeetCode真题_026_Remove Duplicates from Sorted Array

乘风破浪:LeetCode真题_026_Remove Duplicates from Sorted Array 一.前言     我们这次的实验是去除重复的有序数组元素,有大体两种算法. 二.Remove Duplicates from Sorted Array 2.1 问题      题目大意理解,就是对数组进行元素去重,然后返回去处重复之后的长度,无论我们对数组做了什么的修改,都没有关系的,只要保证再返回的长度之内的数组正确性即可.因为最后是根据长度来遍历的,因此我们不用担心. 2.2 分析

乘风破浪:LeetCode真题_025_Reverse Nodes in k-Group

乘风破浪:LeetCode真题_025_Reverse Nodes in k-Group 一.前言 将一个链表按照一定的长度切成几部分,然后每部分进行翻转以后再拼接成一个链表是比较困难的,但是这也能锻炼我们的思维能力. 二.Reverse Nodes in k-Group 2.1 问题 2.2 分析与解决     最简单的想法,我们可以将链表分成几部分,每一个部分分开考虑,比如使用头插法,正好可以将顺序颠倒一下,或者我们通过某种方式使得顺序发生改变,然后再结合起来. /** * Definiti

乘风破浪:LeetCode真题_027_Remove Element

乘风破浪:LeetCode真题_027_Remove Element 一.前言 这次是从数组中找到一个元素,然后移除该元素的所有结果,并且返回长度. 二.Remove Element 2.1 问题 2.2 分析与解决     这个题和上一题是非常相似的,只不过这次是从数组中找到给定的元素,并且删除该元素,同时返回剩余数组的长度,超过长度的元素不用管,存不存在都可以.于是我们想到了和上次一样的方法,用一个指针指向开始,一个指向结尾,开始的向后移动,如果遇到需要删除的元素,则用最后的元素替代,最后的