排列序数——详解第五届蓝桥杯决赛题目

先来看题目要求:

排列序数
如果用a b c d这4个字母组成一个串,有4!=24种,如果把它们排个序,每个串都对应一个序号:
abcd 0
abdc 1
acbd 2
acdb 3
adbc 4
adcb 5
bacd 6
badc 7
bcad 8
bcda 9
bdac 10
bdca 11
cabd 12
cadb 13
cbad 14
cbda 15
cdab 16
cdba 17

现在有不多于10个两两不同的小写字母,给出它们组成的串,你能求出该串在所有排列中的序号吗?
【输入格式】
一行,一个串。

【输出格式】
一行,一个整数,表示该串在其字母所有排列生成的串中的序号。注意:最小的序号是0。

例如:
输入:
bdca

程序应该输出:
11

再例如:
输入:
cedab

程序应该输出:
70

资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意:不要使用package语句。不要使用jdk1.7及以上版本的特性。
注意:主类的名字必须是:Main,否则按无效代码处理。

拿到这道题目第一感觉,这不就是个全排列!貌似很简单的样子,如果你真的这样以为,那你就错了!这道题目如果真的用全排列穷举法来做,确实能做出来,但是绝对会超时!别忘了题目要求:CPU消耗 < 1000ms全排列的时间复杂度是O(n!),当字母数为10个时,穷举法将远超过1000ms

那不能用穷举法,还有其他办法吗?答案是有,下面分享下我的解法,只是个人解法,未必是最优的,但是可以满足题目要求(峰值内存消耗(含虚拟机) < 256M,CPU消耗 < 1000ms),如果哪位大牛有更好解法,欢迎留言互相交流 ( ^_^ )

在上代码之前,让我们先从数学角度找到解题思路,首先仔细看题目中给的示例:

abcd 0
abdc 1
acbd 2
acdb 3
adbc 4
adcb 5
bacd 6
badc 7
bcad 8
bcda 9
bdac 10
bdca 11
cabd 12
cadb 13
cbad 14
cbda 15
cdab 16
cdba 17

通过观察可以得出以下结论:
1.以a开头的排列有6个,以b和c开头的也是一样,都是6个
2.在以a开头的6个排列中,以b为第二位的有2个,以c和d为第二位的也一样,都是2个
3.顺着1,2的思路往后看第三位是一样的,以ab开头,以c为第三位的有1个,以d为第三位的也是1个
看到这里,如果你数学功底很好的话,估计已经能够自己找到规律了, <( ̄︶ ̄)>
什么?你还没找到规律?
这里写图片描述
是不是有点慌?怎么我没找到规律?难道我比其他人笨吗?
下面我来说说具体规律(已经找到规律的数学大牛请直接略过):
1.问:以a开头的排列为何是6个?
答:因为a后面还有3个字母,3的全排列个数为3!个,即6个
2.问:如果按照题意找5个字母的序号,是不是以a开头的就有后面4个字母的全排列个数个,也就是4!=24个?
答:是的,孺子可教也
3.问:那然后呢?这个规律怎么用?
答:以题目中给的cedab为例:

  • 以a开头的序列有24个
  • 以b开头的序列也是24个
  • 后面一样都是24个……

再看输入cedab第一个字母是c,所以这个序列前面一定有以a和b开头的所有序列,那就是
n1 = c前面的字母个数 x 4! = 2 x 24 = 48个

再看输入cedab第二个字母是e,所以这个序列前面一定有以c开头的第二个字母在e之前的所有序列,那就是
n2 = e前面的字母个数 x 3! = (e前面字母有abcd4个 - 4个中已经使用过的字母c 1个)x 3! = 3 x 6 = 18个

再看输入cedab第三个字母是d,所以这个序列前面一定有以ce开头的且第三个字母在d之前的所有序列,那就是
n3 = d前面的字母个数 x 2! = (d前面字母有abc 3个 - 3个中已经使用过的字母c 1个)x 2! = 2 x 2 = 4个

再看输入cedab第四个字母是a,所以这个序列前面一定有以ced开头的且第四个字母在a之前的所有序列,那就是
n4 = a前面的字母个数 x 1! = (a前面字母有a 0个 - 0个已经使用过的字母 0个)x 1! = 0 x 1 = 0个

最后只剩一个位置,也只剩一个字母,所以n5 = 0;
最后输出结果result = n1+n2+n3+n4+n5 = 48+18+4+0+0 = 70

至此这道题从数学角度已经分析完毕,是不是发现很简单?如果还是看不懂,那我也没办法了 W( ̄_ ̄)W
代码就是把这个过程转换成计算机可以认识的语言,代码如下:

package _2014年国赛B组;

import java.util.Scanner;

public class 第四题 {

    private static boolean vis[];

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        String str = in.nextLine();
        int length = str.length();
        vis = new boolean[length];
        char[] c = str.toCharArray();
        //排序c中的字符
        sort(c,length);
        //排序完成后先求当前length-1的阶乘
        int n = factorial(length-1);
        int result = 0;

        for(int i =0;i<length-1;i++){
            //获得str输入传中第i个字符前有几个未使用的字符
            int index = getIndex(str,c,i);
            //未使用字符数*后面字符数的阶乘后再累加到result
            result += index*n;
            //求下一个阶乘(这里是为了降低时间复杂度,用除法求相邻的数的阶乘,其实也可以直接连乘来求)
            n = n/(length-1-i);
        }
        //输出结果
        System.out.println(result);
        in.close();
    }

    private static int getIndex(String str, char[] c_sort,int i) {
        int index = 0;
        char c_search = str.charAt(i);
        for(int j =0,length = str.length();j<length;j++){
            //获得str输入传中第i个字符前有几个未使用的字符,要判断字符是否被使用,这里是用的vis数组
            if(vis[j]){
                //如果当前位置字符已经被使用的,那就直接continue
                continue;
            }else{
                //当前位置字符没有被使用,判断字符是否为待查找字符,若是则标记vis数组,同时返回查找到的个数,若不是则index++;
                if(c_sort[j]==c_search){
                    vis[j] = true;
                    return index;
                }else{
                    index++;
                }
            }
        }
        return -1;
    }

    //求n的阶乘
    private static int factorial(int n) {
        int result = 1;
        for(int i = 2;i<=n;i++){
            result *=i;
        }
        return result;
    }

    //排序
    private static void sort(char[] c,int length) {
        //这里因为数据量比较小,最多10个字符,所以我用了最熟悉的冒泡排序
        for(int i = 0;i<length-1;i++){
            for(int j = 0;j<length-i-1;j++){
                if(c[j+1]<c[j]){
                    char temp = c[j];
                    c[j] = c[j+1];
                    c[j+1] = temp;
                }
            }
        }
    }
}

运行结果:

jihgfedcba
3628799

实测CPU用时:<1ms

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值