求按字典序全排列的中间值

原题地址

https://www.codewars.com/kata/58ad317d1541651a740000c5/java


一个串的全排列 例如abc 为 "abc", "acb", "bac", "bca", "cab", "cba"

求全排列的算法为 循环该串,轮流取出一个字符,之后再求剩下的串的全排列在进行合并。

    public static List Perm(String str) {
        //基本情况,只有一个字符,则他的全排列是自身,结束递归
        if (str.length() == 1) return new ArrayList<>(Arrays.asList(str));

        List list = new ArrayList<>();
        for (int i = 0; i < str.length(); i++) {
            //一个字符串分成左中右三部分,其中中间是要取的字符
            String left = str.substring(0, i);
            String center = str.substring(i, i + 1);
            String right = str.substring(i + 1, str.length());
            
            //求取出该字符后的全排列
            List subList = Perm(left + right);
            subList.forEach(s -> {
                //进行合并,将取出的字符追加在前面
                list.add(center + s);
            });
        }
        //返回结果
        return list;
    }

字典序意思是两个串比较,字符顺序小的在前,比如按照字典序"acb"<"bac" 

该题要求一个串的字典序全排列的中间值,第一反应是不能按照暴力法求出全排列再取中值,因为全排列是n!增长的。

那么接下来找规律,可以看到每个字符开头的全排列个数是相同的,假设输入为abcd,他的字典序全排列为

[abcd, abdc, acbd, acdb, adbc, adcb, bacd, badc, bcad, bcda, bdac, bdca, cabd, cadb, cbad, cbda, cdab, cdba, dabc, dacb, dbac, dbca, dcab, dcba]

可以看出,如果输入字符串为偶数的话,那么中间值就是b开头的最后一个排列即bdca,也就是以b开头的串字典序最大的值,因为字典序是最大的,所以b后面的字幕为逆字典序。

偶数的中间值位置示意如下

求按字典序全排列的中间值_第1张图片

 

如果是奇数的话,那么中值示意如下。

求按字典序全排列的中间值_第2张图片

可以看出是中间那块的中值,那么这个怎么求呢,因为奇数减去一个值就是偶数,所以还是先算出开头字母,将其减掉得出一个新的字符串,转化为偶数情况。

 

按照此规律编写代码如下

    //求按字典序全排列的中间值算法
    public static String findMidPerm(String str) {
        //因为要满足字典序,需要先对输入进行排序
        char[] arr = str.toCharArray();
        Arrays.sort(arr);
        return _findMidPerm(new String(arr));
    }

    private static String _findMidPerm(String str) {
        //基本情况,只有一个字符,返回自己
        if (str.length() == 1) return str;

        int centerIndex = (str.length() / 2);
        if (str.length() % 2 == 0) centerIndex -= 1;

        String left = str.substring(0, centerIndex);
        String center = str.substring(centerIndex, centerIndex + 1);
        String right = str.substring(centerIndex + 1, str.length());

        //偶数情况
        if (str.length() % 2 == 0) {
            //中间字符加上剩下字符的逆字典序
            return center + reverse(left + right);
        }
        //奇数情况
         else {
            //中间字符加上剩下的偶数的中间值
            return center + findMidPerm(left + right);
        }
    }

    private static String reverse(String s) {
        StringBuilder builder = new StringBuilder();
        for (int i = s.length() - 1; i >= 0; i--) {
            builder.append(s.charAt(i));
        }
        return builder.toString();
    }

 

此算法最多只要计算两次,所以为O(1)时间复杂度,不过该题的输入不是按顺序的,所以需要先进行排序,那么时间复杂度就是O(nlogn)了。

在浏览其他人答案的时候似乎还有一些别的规律,看到一个只用算一次的算法如下

    public static String findMidPerm(String strng) {
        
        char[] arrStr = strng.toCharArray();
        Arrays.sort(arrStr);
        StringBuilder s = new StringBuilder(new String(arrStr)).reverse();
        
        StringBuilder sb = new StringBuilder();
        sb.append(s.substring( s.length()/2, (s.length()+3)/2 ));
        sb.append(s.substring( 0,             s.length()/2    ));
        sb.append(s.substring( (s.length()+3)/2 ));
        
        return sb.toString();
    }

 

你可能感兴趣的:(算法与数据结构,算法)