leetcode46、47、31 全排列、下一个排列(非递归解法,减少空间复杂度)

31. 下一个排列

整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。
给你一个整数数组 nums ,找出 nums 的下一个排列。
必须 原地 修改,只允许使用额外常数空间。

解法:
需要找到第一个比该排列大的排列,那么首先就要找到原序列中相邻两个数左侧小于右侧的数,如 [1,3,2] 中的1,下标记为i
此时需要找到从右开始第一个大于ai的数下标j,此时需要将i,j数字互换,因为我们要找到的是第一个大于该排列的数,互换后此时并不是第一个大于,需要保证i后面的最小,因此把i后面的排列反转。
很容易看出来[1,3,2]中ai为1,aj为2,交换后[2,3,1],此时并不是最小需要反转[1,3]

void nextPermutation(vector<int> &nums)
{
    int i=nums.size()-2;
    while(i>=0&&nums[i]>=nums[i+1]) i--;
    if(i>=0)
    {
        int j=nums.size()-1;
        while (nums[j]<=nums[i]) j--;
        swap(nums[i],nums[j]);
        
    }
    reverse(nums.begin()+i+1,nums.end());

}

46. 全排列
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

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

解法一:
最基础的解法使用回溯法,n个位置使用深度优先搜索枚举每一个位置上的数,使用vis标记之前已经使用过的数,防止重复使用。

void dfs(vector<vector<int>> &ans,vector<int> &path,unordered_map<int,int> &vis,vector<int> nums)
{
	if(path.size()==nums.size())
	{
		ans.push_back(path);
		return;
	}
	for(int x:nums)
	{
		if(vis[x]) continue;
		vis[x]=1;
		path.push_back(x);
		dfs(ans,path,vis,nums);
		vis[x]=0;
		path.pop_back();
	}
}

vector<vector<int>> permute ( vector<int>& nums )
{
	vector<vector<int>> ans;
	vector<int> path;
	unordered_map<int,int> vis;
	for(int i:nums)
	{
		vis[i]=1;
		path.push_back(i);
		dfs(ans,path,vis,nums);
		vis[i]=0;
		path.clear();
	}
	return ans;
}

解法二:
解法一中使用了额外的数组vis和path来记录搜索解,可以维护原来的数组,用下标index记录当前需要填写数字的位置,index左侧都是填写好了的数字,右边即为待填写的数字。每次选择右边待填写的数字和要填写的数字进行交换操作。这样可以减少空间复杂度

void dfs(vector<vector<int>> &ans,int index,vector<int> nums)
{
	if(index==nums.size())
	{
		ans.push_back(nums);
		return;
	}
	for(int i=index;i<nums.size();i++)
	{
		swap(nums[index],nums[i]);
		dfs(ans,index+1,nums);
		swap(nums[index],nums[i]);

	}
}
vector<vector<int>> permute ( vector<int>& nums )
{
	vector<vector<int>> ans;
	dfs(ans,0,nums);
	return ans;
}

解法三:

上述解法仍使用递归法,会产生额外的递归栈的空间使用情况,如何把该问题转换为迭代解法。即可减少空间复杂度。

非递归解法一:

当 n=1 时,数组中只有一个数 a1,其全排列只有一种,即为 a1
当 n=2 时,数组中此时有 a1a2,其全排列有两种,a1a2 和 a2a1,那么此时考虑和上面那种情况的关系,可以发现,其实就是在 a1 的前后两个位置分别加入了 a2
当 n=3 时,数组中有 a1a2a3,此时全排列有六种,分别为 a1a2a3, a1a3a2, a2a1a3, a2a3a1, a3a1a2, 和 a3a2a1。那么根据上面的结论,实际上是在 a1a2 和 a2a1 的基础上在不同的位置上加入 a3 而得到的。
_ a1 _ a2 _ : a3a1a2, a1a3a2, a1a2a3
_ a2 _ a1 _ : a3a2a1, a2a3a1, a2a1a3

class Solution {
public:
vector<vector<int>> permute(vector<int>& num) {
        vector<vector<int>> res{{}};
	for(int a:num)
	{
		for(int k=res.size();k;k--) //这里需要用倒序,因为res在下面中在增加新的答案,否则不能跳出循环
		{
			vector<int> t=res.front();
			res.erase(res.begin());//这里需要之前的都清除掉
			for(int i=0;i<=t.size();i++)
			{
				vector<int> ans=t;
				ans.insert(ans.begin()+i,a);
				res.push_back(ans);
			}
		}
	}
        return res;
    }


};

知乎原文

非递归解法二:
此时就可以使用31题中下一个排列的方法,首先对整个数组进行排序,不断寻找下一个排列,直到找不到下一个排列,即不存在左侧数字小于右侧数字

vector<vector<int>> permuteUnique(vector<int> &nums)
{
    vector<vector<int>> res;
    sort(nums.begin(), nums.end());
    res.push_back(nums);
    while (true)
    {
        int i = nums.size() - 2;
        int j = nums.size() - 1;
        while (i >= 0 && nums[i] >= nums[i + 1])
            i--;
        if (i >= 0)
        {
            while (nums[j] <= nums[i])
                j--;
            swap(nums[i], nums[j]);
        }
        else break;
        reverse(nums.begin() + i + 1, nums.end());
        res.push_back(nums);
        
    }
    return res;
}

47. 全排列 II
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

示例 1:

输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]

与上题不同是加了数字可重复条件

解法一
回溯法,仍然使用上述回溯法,那么需要解决的是如何使得重复的数字不会重复得填在该位置上。

此时先对整个数组进行排序,完成排序后,相同的数字就相邻了,可以这样思考,相同的数字中也存在先后顺序,比如1,2,2中,我们设定先填了左边的2,才能填写右边的2,这样就不会出现重复填写。如
1,2a,2b
2a,1,2b
2a,2b,1
如果2a,2b随意顺序就会出现问题
那么只需要判断

if(vis[i]||(i&&nums[i-1]==nums[i]&&!vis[i-1])) continue;

即访问过该数或者该数和前一位数相同,但是前一位数未访问,跳过。这样就不会出现重复情况

void dfs(vector<vector<int>>& ans,vector<int>& path,unordered_map<int,int>& vis,vector<int>& nums)
{
	if(path.size()==nums.size())
	{
		ans.push_back(path);
		return;
	}
	for(int i=0;i<nums.size();i++)
	{
		if(vis[i]||(i&&nums[i-1]==nums[i]&&!vis[i-1])) continue;
		vis[i]=1;
		path.push_back(nums[i]);
		dfs(ans,path,vis,nums);
		path.pop_back();
		vis[i]=0;
	}
}
vector<vector<int>> permuteUnique ( vector<int>& nums )
{
    sort(nums.begin(),nums.end());
	vector<vector<int>> ans;
	vector<int> path;
	unordered_map<int,int> vis;
	dfs(ans,path,vis,nums);
	return ans;
}

方法二

使用上述非递归解法二:
因为本身寻找到的下一个排列就不会有重复状况出现。

vector<vector<int>> permuteUnique(vector<int> &nums)
{
    vector<vector<int>> res;
    sort(nums.begin(), nums.end());
    res.push_back(nums);
    while (true)
    {
        int i = nums.size() - 2;
        int j = nums.size() - 1;
        while (i >= 0 && nums[i] >= nums[i + 1])
            i--;
        if (i >= 0)
        {
            while (nums[j] <= nums[i])
                j--;
            swap(nums[i], nums[j]);
        }
        else break;
        reverse(nums.begin() + i + 1, nums.end());
        res.push_back(nums);
        
    }
    return res;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值