题目描述
在所有不同的组合数值中,第k小的组合数值是多少呢。给定一个k,返回第k小的组合数值。
示例1
输入:1
返回值:1
说明:最小的的组合数是
题目分析
组合数的概念是在给定的 n 个元素中取出 m 个元素,不考虑元素排序的组合个数,表示为;
计算组合数的数学公式为:
其中,n和m都可以从0开始,一直到限制的大小,想要获得第k小的组合数,可以获取到范围比k大的组合数,然后按照从小到大的顺序取出第k个。
想要获取组合数,可以将n和m,分别枚举,根据公式求组合数值,不过这样做的时间花费比较多,容易超时。
解题思路
方法1:统计k范围内的所有组合数,按顺序取出第k小的数
求组合数的过程如下:
fn(m,n){ for(int i=1;i<=n;i++){ for(int j=1;j<=i/2;j++){ // 根据公式求C(j,i),将结果存储 // 根据组合特性可知C(j,i) = C(i-j,i)(取出1个和取出n-1个数的组合数相同),所以j只需要枚举到i的一半即可 } } }
方法2:数学分析
根据组合数中的特例:,因为组合数都是正整数(组合方法的个数),又根据特例可知组合数组成的正整数可以连续,所以易知第k小的数,就是k。
代码实现
方法1:统计k范围内的所有组合数,按顺序取出第k小的数
public int kthSamllest (int k) { // write code here if(k==1) return 1; // 使用有顺序存储的set TreeSet<Integer> set = new TreeSet<>(); set.add(1); // 枚举所有的组合数 // i是组合元素总数 for(int i=1;i<=k;i++){ // j是从i中取出的元素数, C(j,i) = C(i-j, i),只需要遍历一半 for(int j=1;j<=i/2;j++){ int num = C(i,j); if(!set.contains(num)){ set.add(num); } } } int res = 0; // 取出前k个数 for(Integer num :set){ if(k == 1){ // 记录第k个数 res = (int)num; break; } k--; } return res; } public int C(int n, int m){ // C(m,n) = (n*(n-1)*..(n-m+1))/(m!) int res = 1; // 计算分子 for(int i=n-m+1;i<=n;i++){ res *= i; } // 计算分母 for(int i=1;i<=m;i++){ res /= i; } return res; }
时间复杂度:,因为在计算组合数的过程中,使用到了三层循环,第一层循环次数为k,第二层为k/2,第三层为 k(2*k/2=k),循环总次数为;
空间复杂度:,使用的额外空间是set中存储的组合数,虽然有重复的数不会重复存储,但接近;
方法2:数学分析
public int kthSamllest (int k) { return k; }
时间复杂度:,可以直接返回结果;
空间复杂度:,调用方法需要常量的空间。