回文真是一个百考不厌的主题,从easy到hard,从dfs到dp。好像可以跟各种算法都扯上那么一点关系。那么这篇文章就小小的总结下他跟排列的题型吧( ̄0  ̄)y
1 回文验证问题
问题:Valid Palindrome
Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignoring cases.
For example,
"A man, a plan, a canal: Panama" is a palindrome.
"race a car" is not a palindrome.
Input:
- 字符串 :: String
Output:
- 这个字符串是不是回文
Intuition:
根据回文镜像性质,那就是首尾两个指针往中间扫就可以啦。但是,字符串中也许会有大小写,其他的字符这些都是可以被忽略的,那么这个情况也需要判断下。
Code:
TC : O(n) SC: O(1)
public boolean isPal(String s){
if (s.isEmpty()) {
return true;
}
int left = 0;
int right = s.length() - 1;
while(head <= tail) {
lchar = s.charAt(left);
rchar = s.charAt(right);
if (!Character.isLetterOrDigit(lchar)) {
left++;
} else if(!Character.isLetterOrDigit(rchar)) {
right--;
} else {
if (Character.toLowerCase(lchar) != Character.toLowerCase(rchar)) {
return false;
}
left++;
right--;
}
}
return true;
}
问题:Valid Palindrome II
Given a non-empty string s, you may delete at most one character. Judge whether you can make it a palindrome.
Input:
- 字符串 :: String
Output:
- 这个字符串(最多可删一个字符)是不是回文
Intuition:
刚开始想判断字符串中奇数个数的字符有几个不就行了?但仔细一看不对,好像相对位置不能改变。。那就老老实实的拼吧,遇到了不match的字符的时候分别有两种可能:删除左边不match的字符,或者删除右边不match的字符. 接着就判断s.sub(l + 1, j)或者 s.sub(l, r - 1)是不是palindrome啦。
Code:
TC:O(n) SC: O(1)
public boolean isPalII(String s){
int left = 0;
int right = s.length() - 1;
while(left < right){
if (s.charAt(left) != s.charAt(right)){
return helper(s, left + 1, right) || helper(s, left, right - 1);
}
left++;
right--;
}
return true;
}
public boolean helper(String s, int left, int right){
while(left < right){
if(s.charAt(left) != s.charAt(right)){
return false;
}
left++;
right--;
}
return true;
}
2 回文排列问题
题目: Palindrome Permutation
Given a string, determine if a permutation of the string could form a palindrome.
Input:
一个字符串:: String
Output:
这个字符串的permutation中有没有可以形成回文的 :: Boolean
Intuition:
对于这种问是或否的问题,就不要简单复杂化,我们实在没必要把每个permutation写出来然后再一个个判断是不是回文对不对?那么想想看回文还有什么独有的性质?那就是所有字母成对出现或者只有一个落单的字母。那么,就检查给定的字串是不是符合这一性质就OK啦。
Code:
TC: O(s.length()) SC:O(256)
public boolean PalindromPermutation(String s){
if (s == null || s.length() == 0){
return true;
}
int[] cs = new int[256];
//get character frequency
for (int i = 0; i < s.length(); i++){
char cur = s.charAt(i);
cs[cur]++;
}
boolean odd = false;
for (int i = 0; i < cs.length; i++){
if((cs[i] & 1) == 1){
if(odd){
return false;
}
odd = true;
}
}
return true;
}
题目: Palindrome Permutation
Given a string s, return all the palindromic permutations (without duplicates) of it. Return an empty list if no palindromic permutation could be form.
Input:
Input:
一个字符串:: String
Output:
这个字符串的permutation中可以形成回文的字符串 :: List
Intuition:
这下避无可避了吧,那没办法了老老实实的拼吧。唯一可以shortcut的地方就是在开始的时候判断下能不能有可以组成回文的排列,如果一个都没有就直接返回空的list。
找Permutation很直接的方法就是backtracking了,另外因为这个permutation是回文,那么我们也不能傻乎乎的一个字母一个字母的拼,不要忘了回文的镜像性质,拼一半就好,另一半直接粘贴镜像。
举个:
input是“abcabc”, 一半的permutation可以选择的范围是{'a', 'b', 'c'},那么recursion tree是这个样子滴:
比如说我们得到一半的string为“abc”,黏贴翻转后的string是"abccba".
还有一个可以剪枝的地方就是遇到duplicate的字母可以只用第一个字母,跳过后面的duplicates。例如“abbbba”,在a往下这层有两个字母可以选‘b’和‘b’,但是无论选哪个b都是‘ab’这一种组合,那么我们干啥还要傻乎乎的每条道都走到黑呢。。。
Code:
TC:O(2^n) SC:O(2^n)
public List PPII(String s){
if (s == null || s.length() == 0){
return new ArrayList<>();
}
List res = new ArrayList<>();
List dic = new ArrayList<>(); //store characters to make half palindrome
int[] map = new int[256];
String mid = "";
int odd = 0;
for (int i = 0; i < s.length(); i++){
char cur = s.charAt(i);
map[cur]++;
if ((map[cur] & 1) == 1){
odd++;
} else {
odd--;
}
}
if (odd > 1){
return res;
}
//prepare dictionary
for (int i = 0; i < map.length; i++){
for (int j = 0; j < map[i]/2; j++){
dic.add((char)(i));
}
if (map[i] % 2 == 1){
mid += (char)(i);
}
}
boolean[] vis = new boolean[s.length()];
StringBuilder sb = new StringBuilder();
helper(res, dic, vis, sb, mid); //backtracking
return res;
}
public void helper(List res, List dic, boolean[] vis, StringBuilder sb, String mid){
if (dic.size() == sb.length()){
res.add(sb.toString() + mid + sb.reverse().toString());
sb.reverse();
return;
}
for (int i = 0; i < dic.size(); i++){
//prune
if (i > 0 && dic.get(i) == dic.get(i - 1) && !vis[i - 1]){
continue;
}
if (!vis[i]){
vis[i] = true;
sb.append(dic.get(i));
helper(res, dic, vis, sb, mid);
sb.deleteCharAt(sb.length() - 1);
vis[i] = false;
}
}
}
其实在寻找permuation这个部分,我们还可以用swap的方法来实现,省去记录vis数组,代码也简化不少。
backtracking: helper(res, dic, sb, mid, 0);
//helper function
public void helper(List res, List dic, StringBuilder sb, String mid, int start) {
if(start >= list.size()){
res.add(sb.toString() + mid + sb.reverse().toString());
sb.reverse();
return;
}
for (int i = start; i < dic.size(); i++){
//prune
if (i > start && dic.get(i) == dic.get(i - 1) && !vis[i - 1]){
continue;
}
swap(dic, start, i)
helper(res, dic, sb, mid);
swap(dic, start, i);
}
Reference
https://discuss.leetcode.com/topic/8282/accepted-pretty-java-solution-271ms
https://leetcode.com/problems/valid-palindrome-ii/description/
https://leetcode.com/problems/palindrome-permutation/
https://leetcode.com/problems/palindrome-permutation-ii/description/
https://discuss.leetcode.com/topic/28807/short-java-ac-solution-by-swapping-permutation/2