Algorithm Notes
  • Introduction
  • Search & Backtracking 搜索与回溯
    • Tree 与 BackTracking 的比较
    • Subsets, Combination 与 Permutation
    • Subsets & Combinations & Combination Sum
    • 枚举法
    • N 皇后 + 矩阵 Index Trick
    • Sudoku 数独 + 矩阵 Index Trick
    • Word Ladder I & II
    • Number of ways 类
    • DFS flood filling
    • Strobogrammatic 数生成
    • String 构造式 DFS + Backtracking
    • Word Pattern I & II
    • (G) Binary Watch
    • (FB) Phone Letter Combination
    • 常见搜索问题的迭代解法
  • String,字符串类
    • 多步翻转法
    • Substring 结构和遍历
    • Palindrome 问题
    • Palindrome Continued
    • String / LinkedList 大数运算
    • 序列化与压缩
    • 5/24 String 杂题
    • Knuth–Morris–Pratt 字符串匹配
    • Lempel–Ziv–Welch 字符串压缩算法
    • (G) Decode String
    • (G) UTF-8 Validation
  • Binary Tree,二叉树
    • 各种 Binary Tree 定义
    • LCA 类问题
    • 三序遍历,vertical order
    • Post order traversal 的应用
    • Min/Max/Balanced Depth
    • BST
    • 子树结构
    • Level Order traversal
    • Morris 遍历
    • 修改结构
    • 创建 / 序列化
    • 子树组合,BST query
    • 路径与路径和
    • NestedInteger 类
    • (FB) 从 Binary Tree Path 看如何递归转迭代
    • (FB) Binary Tree Path 比较路径大小
    • 比较好玩的 Binary Tree 概率题
  • Segment & Fenwick Tree,区间树
    • Segment Tree 基础操作
    • Segment Tree 的应用
    • Fenwick Tree (Binary Indexed Tree)
    • Range Sum Query 2D - Immutable
  • Union-Find,并查集
    • Union-Find,并查集基础
    • Union-Find, 并查集应用
  • Dynamic Programming, 动态规划
    • 6/20, 入门 House Robber
    • 7/12, Paint Fence / House
    • 6/24, 滚动数组
    • 6/24, 记忆化搜索
    • 6/24, 博弈类 DP
    • 博弈类DP, Flip Game
    • 6/25, 区间类DP
    • 6/27, subarray 划分类,股票
    • 7/2, 字符串类
    • Bomb Enemies
    • 8/2,背包问题
    • (G) Max Vacation
    • (11/4新增) AST 子树结构 DP
  • LinkedList,链表
    • 6/9, LinkedList,反转与删除
    • 6/11, LinkedList 杂题
    • (FB) 链表的递归与倒序打印
  • LinkedIn 面经,算法题
    • 6/17, LinkedIn 面经题
    • 6/28, LinkedIn 面经题
    • 7/6, LinkedIn 面经
    • Shortest Word Distance 类
    • DFA Parse Integer
  • Two Pointers,双指针
    • 3 Sum, 3 Sum Closest / Smaller, 4 Sum
    • 对撞型,灌水类
    • 对撞型,partition类
    • Wiggle Sort I & II
    • 双指针,窗口类
    • 双指针,窗口类
    • Heap,排序 matrix 中的 two pointers
  • Bit & Math,位运算与数学
    • Bit Manipulation,对于 '1' 位的操作
    • Math & Bit Manipulation, Power of X
    • 坐标系 & 数值计算类
    • Add Digits
    • 用 int 做字符串 signature
  • Interval 与 扫描线
    • Range Addition & LCS
    • 7/5, Interval 类,扫描线
  • Trie,字典树
    • 6/9, Trie, 字典树
  • 单调栈,LIS
    • 4/13 LIS
    • 栈, 单调栈
    • Largest Divisible Subset
  • Binary Search 类
    • Matrix Binary Search
    • Array Binary Search
    • Find Peak Element I & II
    • **Median of Two Sorted Arrays
  • Graph & Topological Sort,图 & 拓扑排序
    • 有向 / 无向 图的基本性质和操作
    • 拓扑排序, DFS 做法
    • 拓扑排序, BFS 做法
    • Course Schedule I & II
    • Alien Dictionary
    • Undirected Graph, BFS
    • Undirected Graph, DFS
    • 矩阵,BFS 最短距离探索
    • 欧拉回路,Hierholzer算法
    • AI, 迷宫生成
    • AI, 迷宫寻路算法
    • (G) Deep Copy 无向图成有向图
  • 括号与数学表达式的计算
  • Iterator 类
  • Majority Element,Moore's Voting
  • Matrix Inplace Operations
  • 常见数据结构设计
  • (G) Design / OOD 类算法题
  • 随机算法 & 数据结构
  • (FB) I/O Buffer
  • (FB) Simplify Path, H-Index I & II
  • (FB) Excel Sheet, Remove Duplicates
  • Integer 的构造,操作,序列化
  • Frequency 类问题
  • Missing Number 类,元素交换,数组环形跳转
  • 8/10, Google Tag
  • (FB) Rearrange String k Distance Apart
  • Abstract Algebra
    • Chap1 -- Why Abstract Algebra ?
    • Chap2 -- Operations
    • Chap3 -- The Definition of Groups
Powered by GitBook
On this page
  • 5/22 String, Palindrome 问题
  • Longest Palindromic Substring
  • Valid Palindrome
  • Palindrome Permutation
  • Palindrome Permutation II
  • [重点] Shortest Palindrome

Was this helpful?

  1. String,字符串类

Palindrome 问题

PreviousSubstring 结构和遍历NextPalindrome Continued

Last updated 4 years ago

Was this helpful?

5/22 String, Palindrome 问题

Palindrome 的定义是,给定 S, S=S'

KMP 算法的核心是 failure function ,在 currect position 不匹配的情况下,在之前的substring中寻找最长的 suffix 后缀,使得它和 substring 中的 prefix 前缀相同。

考虑到总共可能的 palindrome 数量,我就先写了一个比较挫的写法,middle-out 由每个字符作为起点,往两边扫。

Palindrome 长度可能是奇数,也可能是偶数。所以指定 seed 往两边扩展的时候要注意先把 start / end 指针推到“不是 seed” 的位置上。

去 LeetCode 论坛看了一圈,发现他们用的写法也基本就是这么挫的。。

这个问题是有 O(n)O(n)O(n) 解法的,见 , 不过对于面试的时间来讲要求有点太高了,就和要求你手写 KMP 一样。

public class Solution {
    public String longestPalindrome(String s) {
        if(s.length() <= 1) return s;
        if(s.length() == 2){
            if(s.charAt(0) == s.charAt(1)) return s;
            else return s.substring(1);
        }

        int maxStart = 0;
        int maxEnd = 0;
        int maxLength = 1;

        for(int i = 0; i < s.length(); i++){
            int start = i - 1;
            while(start >= 0 && s.charAt(start) == s.charAt(i)) start --;
            int end = i + 1;
            while(end < s.length() && s.charAt(end) == s.charAt(i)) end ++;

            while(start >= 0 && end < s.length()){
                if(s.charAt(start) == s.charAt(end)){
                    start --;
                    end ++;
                } else {
                    break;
                }
            }
            if(end - start - 1 > maxLength){
                maxLength = end - start - 1;
                maxStart = start + 1;
                maxEnd = end - 1;
            }
        }

        return s.substring(maxStart, maxEnd + 1);
    }
}

Trivial problem. 稍微烦一点的地方在于我们要忽略字符,并且不区分大小写(原始 String 里有字符也有大小写)

ASC II 表里,一个字母的小写值与大写值的差正好是32 (小写字母值更大)

Character.isLetterOrDigit(char chr)

str.toLowerCase();

public class Solution {
    public boolean isPalindrome(String s) {
        if(s.length() <= 1) return true;
        s = s.toLowerCase();

        int start = 0;
        int end = s.length() - 1;

        while(start < end){
            while(start < end && !Character.isLetterOrDigit(s.charAt(start)))
                start ++;
            while(start < end && !Character.isLetterOrDigit(s.charAt(end)))
                end --;

            if(s.charAt(start) == s.charAt(end)){
                start ++;
                end --;
            } else {
                return false;
            }
        }

        return true;
    }
}

Trivial problem. 扫一遍所有字符,如果出现过奇数次数的字符超过一个,就不能构造出 Palindrome 了。考察的就是一个简单的对 Palindrome 结构的理解。

public class Solution {
    public boolean canPermutePalindrome(String s) {
        if(s.length() <= 1) return true;

        int[] hash = new int[256];
        for(int i = 0; i < s.length(); i++){
            int index = (int) s.charAt(i);
            hash[index] ++;
        }

        boolean oneOdd = false;
        for(int i = 0; i < 256; i++){
            if(hash[i] % 2 == 0){
                continue;
            } else {
                if(oneOdd) return false;
                oneOdd = true;
            }
        }

        return true;
    }
}

下面的代码是一个比较粗暴的第一版本,因为要返回所有正确解,属于一个搜索问题。根据 palindrome 结构做 DFS + backtracking.

看了下论坛,发现别人的提交也都是这个思路。。

public class Solution {
    public List<String> generatePalindromes(String s) {
        int[] hash = new int[256];
        for(int i = 0; i < s.length(); i++){
            int index = (int) s.charAt(i);
            hash[index] ++;
        }
        char center = '.';
        boolean oneOdd = false;
        for(int i = 0; i < 256; i++){
            if(hash[i] % 2 == 0){
                continue;
            } else {
                if(oneOdd) return new ArrayList<String>();

                oneOdd = true;
                center = (char) i;
                hash[i] --;
            }
        }

        char[] array = new char[s.length()];
        List<String> list = new ArrayList<String>();

        if(oneOdd) {
            array[s.length() / 2] = center;
            dfs(list, array, hash, s.length() / 2 - 1, s.length() / 2 + 1);
        } else {
            dfs(list, array, hash, s.length() / 2 - 1, s.length() / 2);    
        }

        return list;
    }

    private void dfs(List<String> list, char[] array, int[] hash, int left, int right){
        if(left < 0 || right >= array.length){
            list.add(new String(array));
            return;
        }

        for(int i = 0; i < 256; i++){
            if(hash[i] <= 0) continue;

            array[left] = (char) i;
            array[right] = (char) i;
            hash[i] -= 2;

            dfs(list, array, hash, left - 1, right + 1);

            array[left] = '.';
            array[right] = '.';
            hash[i] += 2;
        }
    }
}

这题在 LeetCode 上的标注难度为 "Hard".

我一开始的写法比较的简单粗暴,直接用写了一个 isPalindrome 函数去判断一个 substring 是不是 palindrome,然后从给定的 string 左面开始一直往右扫,去找到从最左边字符开始,最长的 palindrome,然后把剩下的右边部分复制一份,翻转,接到原来的 string 上.

但是超时了,时间复杂度太高。挂在了一个“aaaaaaaa...” 非常长但是结构非常简单的 test case 上。

这个思路是没有问题的,但是逐个 substring 去调用 O(n) 时间的函数还是时间复杂度太高了,没能有效利用 palindrome 的结构性质。

public class Solution {
    public String shortestPalindrome(String s) {
        if(s.length() <= 1) return s;
        // Find the lonest palindrome starting from left most of string
        int maxLength = 1;
        for(int i = 1; i < s.length(); i++){
            if(isPalindrome(s, 0, i)){
                maxLength = Math.max(maxLength, i + 1);
            }
        }
        StringBuilder sb = new StringBuilder(s.substring(maxLength));
        String toAdd = sb.reverse().toString();

        return toAdd + s;

    }

    private boolean isPalindrome(String s, int left, int right){
        while(left < right){
            if(s.charAt(left) != s.charAt(right)) return false;
            left ++;
            right --;
        }

        return true;
    }
}

根据这个思路的另一种做法是,依然利用 longest palindrome 一定是从第一个字符开始的特点,从字符串末尾不断向起点逼近,寻找最终的位置。

如果出现 i j 位置的字符串不同的情况,则重置 i = 0 , j = end - 1 继续看。

不过本质上讲,这种做法和我刚才写的第一种没有任何区别,只不过是一个从里向外,一个从外向里。。。于是这个写法在 large test case 上依然 TLE,还没有利用好 substring 结构的精髓。

public class Solution {
    public String shortestPalindrome(String s) {
        if(s.length() <= 1) return s;

        int i = 0;
        int end = s.length() - 1;
        int j = end;

        StringBuilder sb = new StringBuilder();

        while(i < j){
            if(s.charAt(i) == s.charAt(j)){
                i ++;
                j --;
            } else {
                i = 0;
                end --;
                j = end;
            }
        }

        return new StringBuilder(s.substring(end + 1)).reverse().toString() + s;

    }

}

KMP 算法的核心是 next function ,在 currect position 不匹配的情况下,在之前的substring中寻找最长的 suffix 后缀,使得它和 substring 中的 prefix 前缀相同。

这个问题能成功 AC 的关键就是。。一言不合就上 KMP !

第二天重写发现的问题:

Pattern 顺序不能错,是 SS' 而不是 S'S

public class Solution {
    public String shortestPalindrome(String s) {
        if(s.length() <= 1) return s;
        int[] kmp = getKmpTable(s + "#" + new StringBuilder(s).reverse().toString());
        int length = kmp[kmp.length-1];

        return new StringBuilder(s.substring(length)).reverse().toString() + s;

    }

    private int[] getKmpTable(String pattern){
        int M = pattern.length();
        int[] next = new int[M];

        int k = 0;
        for (int q = 1; q < M; q++) {
            while(k > 0 && pattern.charAt(k) != pattern.charAt(q)) {
                k = next[k-1];
            }
            if (pattern.charAt(k) == pattern.charAt(q)) {
                k++;
            }
            next[q] = k;
        }
        return next;
    }

}

[重点]

这个解法的代码是参考的 ,2015年9月写的,那个时候还没有大数据的 test case.

于是在论坛 里出现了字符串大杀器,KMP. 关于这个问题, 的帖子讲的非常赞,推荐一读。

如果我们的字符串可以分拆成两段 S=QPS = QPS=QP, 我们想要求的是最长的 palindrome QQQ. 设 S′S'S′ 为 String SSS 的反序字符串,给定

SS′SS'SS′ = QPP′Q′QPP'Q'QPP′Q′ ,由于 QQQ 是 palindrome,可知 Q=Q′Q = Q'Q=Q′,二者分别是是 SS′SS'SS′ 的前缀与后缀,因此可以直接通过计算 SS′SS'SS′ 的 failure function 求出 QQQ 的最大长度。

Valid Palindrome
Palindrome Permutation
Palindrome Permutation II
Shortest Palindrome
这个帖子
这个帖子
Yu's Garden
Longest Palindromic Substring
Manacher's Algorithm