算法题 全排列

leadcode 题46

    给定一个没有重复数字的序列,返回其所有可能的全排列。

 输入: [1,2,3]
 输出:
 [
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
 ]

    初始的想法,对于无重复序列,所有的可能为n!。以初始序列中任意数字a[i]开头的序列有(n-1)!种,已经选择a[i]开头,再选择a[j]作为第二个数字一共有(n-2)!种。因此可以填出所有的组合:

    1.已知一个n长不重复序列a,有n!种不同的排列;

    2.遍历a中的每一个元素,每次将a[i]作为首位,剩下的元素组成新的n-1序列

    3.直到新序列的长度为0时,该种排列结束,否则重复1,2

// code 1
class Solution {
public:
     vector<vector<int>> permute(vector<int>& nums) {
        int len = nums.size();
        int lenlen = 1;
        for(int i = 1; i < len+1; i++)
            lenlen *= i;

        vector<vector<int>> result(lenlen);
        for (int i = 0; i < lenlen; i++)
            result[i].resize(len);

        sub_permute(result, nums, 0, 0);

        return result;
    }

    void sub_permute(vector<vector<int>>& result, vector<int>& nums, int x, int y){
        int len = nums.size();
        int kinds = 1;
        int itera = 1;
        for (int i = 1; i < len; i++)
            itera *= i;
        kinds = itera * len;
        for (int j = 0; j < nums.size(); j++){
            for (int i = 0; i < itera; i++){
                result[x+i + j*itera][y] = nums[j];     //j*itera 是行上的偏移
            }
            vector<int> new_num(nums);
            new_num.erase(new_num.begin()+j);
            sub_permute(result, new_num, x+j*itera, y+1);
        }
    }
};

    临时写的代码,表意可能不太清晰。

    如果题目去掉没有重复这个条件,在上述方法中做修改很不方便。

更为一般的写法:

    思路是一致的,只不过这种方法没有开辟新的空间存储子序列,而是通过交换的方式,从首位开始逐步递归。(需要注意,交换之后还需要交换回来,回到交换前的状态)

    代码来自Q-WHai

// code 2
#include<iostream>
using namespace std;
int arr[5]={0,1,2,3,4};
void swap(int x,int y)//用于交换数组的两个数
{
	int temp=arr[x];
	arr[x]=arr[y];
	arr[y]=temp;
}
int resove(int n)//递归函数
{
 	if(n==5)//当尝试对不存在的数组元素进行递归时,标明所有数已经排列完成,输出。
	{
		for(int i=0;i<5;i++)
		cout<<arr[i]; 
		cout<<endl;
		return 0;
	}
	for(int i=n;i<5;i++)//循环实现交换和之后的全排列  
	{//i是从n开始 i=n;swap(n,i)相当于固定当前位置,在进行下一位的排列。
		swap(n,i);
		resove(n+1);
		swap(n,i); 
	}
		 
}
int main()
{
	resove(0);
}

    这种思路便于处理有重复的问题,在每次交换i,j之前先检查在j之前是否有值与j相等的数,若有,则跳过此次交换。

// code 3
private static void core(int[] array) {
    int length = array.length;
        fullArray(array, 0, length - 1);
    }
    
    private static boolean swapAccepted(int[] array, int start, int end) {
        for (int i = start; i < end; i++) {
            if (array[i] == array[end]) {    //end之前出现了和end相同的数字,跳过本次交换
                return false;
            }
        }
        return true;
    }
    
    private static void fullArray(int[] array, int cursor, int end) {
        if (cursor == end) {
            System.out.println(Arrays.toString(array));
        }
        else {
            for (int i = cursor; i <= end; i++) {
                if (!swapAccepted(array, cursor, i)) {
                    continue;
                }
                ArrayUtils.swap(array, cursor, i);
                fullArray(array, cursor + 1, end);
                ArrayUtils.swap(array, cursor, i); // 用于对之前交换过的数据进行还原
            }
        }
    }

字典序

    code1满足排列结果是字典序的(前提是先按字典序排好序列);

    code2,3都不能满足。

    方法:

    1.从右端开时,找出第一个比右边数字小的数字序号j,j=max{i|a[i] < a[i+1]}

    2.a[j]右边小于a[j]的最小的数字a[k],由a[j]的定义,其右边的数都是递减的,因此k=max{i|a[i] > a[j]}

    3.交换a[j],a[k]

    4.将a[j+1]...a[k]...a[n]逆序

    在这里不做算法证明,只做一些解释性的理解:

    当前序列情况:a[0] < ... < a[j] < a[j+1] > a[j+2] > ... > a[k] >... > a[n]。那么a[j+2] ... a[n]已经是最大字典序的子序列了,所以我们考虑再往前看一位,找到从右往左打破了递增关系的a[j]。将a[j]和恰恰大于它的a[k]交换,交换后,从a[j+1]到a[n]依然满足增序。将a[j+1] ... a[n]逆序,成为一个最小子序列。

    以上的操作相当于在最高位上换上了一个更大一点的数字,因此此时的次高位到末位需要呈现一种最小的排列。即:

    从        1 5 4 3 2    (1)

    变为     2 5 4 3 1    (2)

    最终     2 1 3 4 5    (3)

    找到l了(1)的下一个序列(3)。

 

    通过上述安字典序的方式,先排序,之后逐步寻找下一个排列,是一种解决非重复问题的非递归方法

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值