回文问题

回文真是一个百考不厌的主题,从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是这个样子滴:

回文问题_第1张图片
image.png

比如说我们得到一半的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

你可能感兴趣的:(回文问题)