leetcode_060 Permutation Sequence

题意分析:1~n组成的序列从小到大排序,按顺序找出k个序列,并输出。

解题思路:

                     对于数值n,排列组合共有n!种排列。

                     第一位每个数组开头的序列有(n-1)!个序列,

                     依此类推,第二位每一个数开头有(n-2)!个序列。

                     因数字不重复,故用sign标记数字是否使用过,data存阶层的数,每次循环找到没有使用的数中第

                     k/data[i]个数就是当前位的数字。

                     其详细过程见程序中注释。

class Solution 
{
	public:
		// 利用库函数实现(超时) 
	    string getPermutation(int n, int k) 
		{
	        string str = string("123456789").substr(0, n);
	        for(int i = 1; i <= k-1; i++)
	            next_permutation(str.begin(), str.end());
	        return str;
	    }
	    // 递归实现(超时) 
	    string getPermutation(int n, int k) 
		{
			string result;
			vector<int> nums;
	        for (int i = 1; i <= n; i++)
				nums.push_back(i);
	        for(int i = 1; i <= k-1; i++)
	            nextPermutation(nums);
	        for (int i = 0; i < nums.size(); i++)
	        {
	        	result += nums[i] + '0';
			}
			return result;
	    }
	    
	    void myswap(int &x, int &y)
		{
			int t;
			t = x;
			x = y;
			y = x;
		}
		void nextPermutation(vector<int> &nums)
		{
			int n = nums.size();
			if (n == 1)
				return;
			for (int i = n - 2, j = n - 1; i >= 0; i--, j--)
			{
				//执行第一步,从后向前,找第一个不满足递增的数nums[i] 
				if (nums[i] < nums[j])
				{
					int k = n - 1;
					//执行第二步,从后向前,找第一个比nums[i]大的数nums[k] 
					while (nums[k] <= nums[i])
						k--;
					//执行第三步,交换两个数nums[i]和num[k] 
					swap(nums[i], nums[k]);
					//执行第四步,对nums[i] 后面的数进行翻转
					reverse(nums.begin() + j, nums.end());
					return ;
				}
			}
			//当没第一步未找到合适的数,则对整个进行翻转 
			reverse(nums.begin(), nums.end());
		}

		// 上面超时的原因是算法都是逐个求排列,下面利用非逐个求,而是直接构造第k个排列。以n=4,k=17为例,数组src=[1,2,3,...]。
		// 计算第17个排列的第一个数。首先直到(n-1)!=3!=6,即以1和2开头的排列总共有6*2=12个<17,故第17个排列的第一个数不可能为
		// 1或者2,6*3>17,故第17个排列第一个数为3。即第17个排列的第一数是原数组(原数组有序)的第m=upper(17/6)=3个数。第一个
		// 数固定后,从src数组中删除概述,相当于在当前src的基础上求k-(m-1)*(n-1)!=17-2*6=5个排列,在递归求解。
		string getPermutation(int n, int k)
		{
			string str = string("123456789").substr(0, n);
			string result(n, ' ');
			for (int i = 0; i < n; i++)
			{
				result[i] = helper(str, k);
			}
			return result;
		}
		// 以s中字符构造全排列中,返回第k个排列的第一个字符,并删除s中该字符,s中字符递增有序
		char helper(string &s, int &k)
		{
			int tmp = factorial(s.size() - 1);
			int i = (k - 1) / tmp;
			char res = s[i];
			s.erase(i, 1);
			k -= i * tmp;    // 更新k
			return res; 
		}
		int factorial(int n)
		{
			int res = 1;
			for (int i = 2; i <= n; i++)
				res *= i;
			return res;
		 }
		// 上面算法的非递归实现(未超时) 
		string getPermutation(int n, int k)
		{
			int total = factorial(n);
			string candidate = string("123456789").substr(0, n);
			string res(n, ' ');
			for (int i = 0; i < n; i++)   // 依次计算排列的每个位
			{
				total /= (n - i);
				int index = (k - 1) / total;
				res[i] = candidate[index];
				candidate.erase(index, 1);
				k -= index * total;
			}
			return res; 
		}
		int factorial(int n)
		{
			int res = 1;
			for (int i = 2; i <= n; i++)
			{
				res *= i;
			}
			return res;
		} 
		// 整体思路:先初始化字符串"1,2,...,n",然后从0位开始逐步确定每一位的数字,第i位后面的数字有(n-1-i)!种排法,若假设k<(n-1-i)!,则
		// 可确定第i位的数字。 
		string getPermutation(int n, int k)
		{
			// 初始化字符串"1...n" 
			int Kn = 1;
			string res(n, '1');
			for (int i = 2; i <= n; i++)
			{
				Kn *= i;
				res[i - 1] = i + '0';
			}
			int temp = n;
			for (int i = 0; i < res.size(); i++)
			{
				Kn /= temp;
				for (int j = i + 1; j < n && k > Kn; j++)
				{
					swap(res[i], res[j]);
					k -= Kn;
					if (k == 1)
						break; 
				}
				temp--;
			}
			return res; 
		}		
	    // 非递归实现(未超时) 
		string getPermutation(int n, int k)
		{
			int i;
			int j;
			int data[10];
			int sign[10];
			data[1] = 1;
			for (i = 2; i <= n; i++)
			{
				data[i] = data[i - 1] * i;
			}
			for (int i = 0; i < 10; i++)
			{
				sign[i] = 0;
			}
			string s = "";
			i = i - 2;
			k--;
			while (i >= 0)
			{
				int temp = k / data[i];
				for (j = 1; j < 10; j++)
				{
					if (sign[j] == 0)
						temp--;
					if (temp < 0)
						break;
				}
				sign[j] = 1;
				s += j + '0';
				k %= data[i];
				i--;
			}
			return s;
		}  
};


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值