【程序员面试金典】01.04. 回文排列

回文排列

给定一个字符串,编写一个函数判定其是否为某个回文串的排列之一。
回文串是指正反两个方向都一样的单词或短语。排列是指字母的重新排列。
回文串不一定是字典当中的单词。

示例 1:

输入:“tactcoa”
输出:true(排列有"tacocat"、“atcocta”,等等)

题目解法

这是一道帮助理解“回文串排列”定义的题目,同时该题目也在考查回文串排列应具备哪些特点。

回文串指从正、反两个方向读都一致的字符串。因此,判断一个字符串是否为回文串排列,我们需要知道该字符串是否可以重写为一个从正反两个方向都一致的字符串。

怎样才能给出一个正、反两个方向都一致的字符串序列呢?对于大多数的字符,都必须出现偶数次,这样才能使得其中一半构成字符串的前半部分,另一半构成字符串的后半部分。至多只能有一个字符(即中间的字符)可以出现奇数次。

例如,我们知道tactcoapapa是一个回文排列,因为该字符串有2个t、4个a、2个c、2个p及一个o,其中o将会成为潜在的回文串的中间字符。

解法 1

所有偶数长度的回文排列字符串(不包括非字母字符)所有的字符必须出现偶数次奇数长度的回文排列字符串必须刚好有一个字符出现奇数次。偶数长度的字符串,不可能只包括一个出现奇数次的字符,否则其不会为偶数长度(一个奇数次字符+若干偶数次字符=奇数个字符)。同理,奇数长度的字符串不可能所有的字符都出现偶数次(偶数的和仍为偶数)。因此,一个回文串不可能包含超过一个“出现奇数次的字符”。

因此,我们可以得出第一个算法。使用散列表统计每个字符出现的次数。然后遍历散列表以便确定出现奇数次的字符不超过一个。

算法实现(JAVA)
class Solution1 {
    private static final int A_LOWER_NUMERIC_VALUE = 'a';
    private static final int Z_LOWER_NUMERIC_VALUE = 'z';
    private static final int A_UPPER_NUMERIC_VALUE = 'A';
    private static final int Z_UPPER_NUMERIC_VALUE = 'Z';
    private static final int ASCII_CHARACTER_SIZE = 128;

    public boolean canPermutePalindrome(String s) {
        int[] table = buildCharFrequencyTable(s);
        return checkMaxOneOdd(table);
    }

    /**
     * 对字符穿线的次数计数
     */
    private int[] buildCharFrequencyTable(String s) {
        int[] table = new int[ASCII_CHARACTER_SIZE];
        for (char c : s.toCharArray()) {
            int x = getCharNumber(c);
            if (x != -1) {
                table[x]++;
            }
        }
        return table;
    }

    /**
     * 将每个字符对应为一个数字,a->0,b->1,c->2,等等。
     * LeetCode区分大小写。
     * 非字母对应为-1。
     */
    private int getCharNumber(char c) {
        if (A_LOWER_NUMERIC_VALUE <= c && c <= Z_LOWER_NUMERIC_VALUE) {
            return c;
        }
        if (A_UPPER_NUMERIC_VALUE <= c && c <= Z_UPPER_NUMERIC_VALUE) {
            return c;
        }
        return -1;
    }

    /**
     * 检查最多一个字符的数目为奇数。
     */
    private boolean checkMaxOneOdd(int[] table) {
        boolean foundOdd = false;
        for (int count : table) {
            if (count % 2 == 1) {
                if (foundOdd) {
                    return false;
                }
                foundOdd = true;
            }
        }
        return true;
    }

    /**
     * 测试方法
     */
    public static void main(String[] args) {
        Solution1 solution1 = new Solution1();
        // true
        System.out.println(solution1.canPermutePalindrome("tactcoa"));
        // false
        System.out.println(solution1.canPermutePalindrome("tactcoaa"));
    }
}

解法 2

任何算法都要遍历整个字符串,因此,无法对时间复杂度再进行优化,但可稍作优化。

可以在遍历的同时检查是否有字符只出现了奇数次,而不需要在遍历结束时再进行检查。因此,在一次遍历结束时,我们即有了答案。

注: 该算法与解法1有相同时间复杂度,不一定更优。只是没有遍历散列表。

算法实现(JAVA)
class Solution {
    private static final int A_LOWER_NUMERIC_VALUE = 'a';
    private static final int Z_LOWER_NUMERIC_VALUE = 'z';
    private static final int A_UPPER_NUMERIC_VALUE = 'A';
    private static final int Z_UPPER_NUMERIC_VALUE = 'Z';
    private static final int ASCII_CHARACTER_SIZE = 128;

    public boolean canPermutePalindrome(String s) {
        int countOdd = 0;
        int[] table = new int[ASCII_CHARACTER_SIZE];
        for (char c : s.toCharArray()) {
            int x = getCharNumber(c);
            if (x == -1) {
                continue;
            }
            table[x]++;
            if (table[x] % 2 == 1) {
                countOdd++;
            } else {
                countOdd--;
            }
        }
        return countOdd <= 1;
    }

    /**
     * 将每个字符对应为一个数字,a->0,b->1,c->2,等等。LeetCode区分大小写。
     */
    private int getCharNumber(char c) {
        if (A_LOWER_NUMERIC_VALUE <= c && c <= Z_LOWER_NUMERIC_VALUE) {
            return c;
        }
        if (A_UPPER_NUMERIC_VALUE <= c && c <= Z_UPPER_NUMERIC_VALUE) {
            return c;
        }
        return -1;
    }

    /**
     * 测试方法
     */
    public static void main(String[] args) {
        Solution2 solution2 = new Solution2();
        // true
        System.out.println(solution2.canPermutePalindrome("tactcoa"));
        // false
        System.out.println(solution2.canPermutePalindrome("tactcoaa"));
    }
}

[1] 盖尔.拉克曼.麦克道尔.程序员面试金典(第6版)[M].北京:人民邮电出版社,2019.9:159-162
[2] leetcode.面试题 01.04. 回文排列

你可能感兴趣的:(#,程序员面试金典(第6版),面试,职场和发展,java)