1/27,Palindrome
Valid Palindrome
string two pointer类的问题,两指针对撞型,用迭代排除掉左右两边都不考虑的字符。注意:considering only alphanumeric characters and ignoring cases.
class Solution {
public boolean isPalindrome(String s) {
if (s == null || s.length() == 0) return true;
char[] arr = s.toCharArray();
int n = arr.length;
int i = 0, j = n - 1;
while (i < j) {
while (i < j && !Character.isLetterOrDigit(arr[i])) i++;
while (i < j && !Character.isLetterOrDigit(arr[j])) j--;
if (i < j) {
char left = Character.toLowerCase(arr[i]);
char right = Character.toLowerCase(arr[j]);
if (left != right) {
return false;
}
i++;
j--;
}
}
return true;
}
}
再遇到这道题一定要再做之前make sure前提,是不是case sensitive,是不是只用考虑数字和字母等。
1/12复习注,a lot of questions to ask, e.g. is an empty string a valid palindrome? what constitute a string? case sensitive?
inner while loop的作用就是找到要比较的chars。
2/10补充,
如何上一题我们只是判断一个string或者array是不是palindrome,可以O(n) time,O(n^2) extra space (array slicing + recursive function calls) 但是,如果n很长,在这道题用这个recursive会stack overflow
class Solution {
public boolean isPalindrome(String s) {
return isCheck(s.toCharArray(), 0, s.length() - 1);
}
private boolean isCheck(char[] arr, int start, int end) {
if (start > end - 1) {
return true;
}
while (start < end && !Character.isLetterOrDigit(arr[start])) start++;
while (start < end && !Character.isLetterOrDigit(arr[end])) end--;
if (start >= end) return true;
return Character.toLowerCase(arr[start]) == Character.toLowerCase(arr[end])
&& isCheck(arr, start + 1, end - 1);
}
}
Valid Palindrome 2
好题,用greedy的方法来做,从两边开始check,如果valid直接返回true,otherwise,check左删除一个或者右删除一个character能否valid。
time: O(n) each of two checks of whether some substring is a palindrome is O(N)
space: O(1) only pointers are stored in memory.
class Solution {
public boolean isPalindromeRange(String s, int i, int j) {
while (i < j) {
if (s.charAt(i) != s.charAt(j)) {
return false;
}
i++;
j--;
} return true;
}
public boolean validPalindrome(String s) {
if (s == null || s.length() == 0) return true;
for (int i = 0; i < s.length() / 2; i++) {
int left = i, right = s.length() - 1 - i;
if (s.charAt(left) != s.charAt(right)) {
return (isPalindromeRange(s, left + 1, right) ||
isPalindromeRange(s, left, right - 1));
}
} return true;
}
}
Longest Palindromic Substring
并不难,关键是要想到palindrome的长度可能是奇数,可能是偶数,怎么能充分检查所有情况。做法是在string的每个位置,用two pointers反向check palindrome,由于存在奇偶性,同一个位置检查两遍才行。在two pointers向两边移动的同时,update最长的长度,储存左右边界。时间复杂度O(n^2)。注意,设置max和lo为全局变量,这样设计会使代码更加简洁。
class Solution {
int max = 0;
int lo = 0;
public String longestPalindrome(String s) {
if (s == null || s.length() == 0) return "";
for (int i = 0; i < s.length(); ++i) {
getPalindrome(s, i, i);
getPalindrome(s, i, i + 1);
}
return s.substring(lo, lo + max);
}
private void getPalindrome(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
// check the length after the while loop
if (max < right - left - 1) {
max = right - left - 1;
lo = left + 1;
}
}
}
1/12,总结一些简单的关于Longest题目。
- 一个共同点是在迭代过程中使用变量保存max length。
Longest palindrome substring和Longest substring without repeating chars比较,都用到two pointer。
Longest common prefix高频题。
Longest consecutive sequence使用hash set解决。
Palindrome Permutation
检查一个字符串是否是permutation of a palindrome。利用hashtable,第一次遍历统计次数,第二次遍历检查次数是奇数的个数。
这种方法只检查了字母,如果中间有whitespace要如何处理需要check,是当作一个独立的letter还是直接ignore。
class Solution {
public boolean canPermutePalindrome(String s) {
if (s == null || s.length() == 0) return true;
Map<Character, Integer> map = new HashMap<>();
for (int i = 0; i < s.length(); ++i) {
char c = s.charAt(i);
map.put(c, map.getOrDefault(c, 0) + 1);
}
int odd = 0;
for (char key : map.keySet()) {
if (map.get(key) % 2 == 1) {
odd++;
}
}
return odd <= 1;
}
}
Palindrome Permutation 2
Given a string s, return all the palindromic permutations (without duplicates) of it. Return an empty list if no palindromic permutation could be form.
运用上一题的idea,来一个略暴力的构建string。Basic idea is that 首先找到中间center点,在过程中如何发现构成不了valid的Palindromes,直接返回空list。在构建Palindromes过程中,从center位置向两边extend。time O(n!), space O(n)
the idea is to build a count map for the chars in s // and terminate if s cannot form a palindrome // then do DFS from the center until reaching the left/right end
class Solution {
public List<String> generatePalindromes(String s) {
if (s == null || s.length() == 0) return new ArrayList<String>();
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<>();
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);
hash[i] += 2;
array[left] = '.';
array[right] = '.';
}
}
}
Longest Palindrome
Given a string which consists of lowercase or uppercase letters, find the length of the longest palindromes that can be built with those letters.
This is case sensitive, for example
"Aa"
is not considered a palindrome here.Given s =
"abccccdd"
return7
One longest palindrome that can be built is
"dccaccd"
, whose length is7
字符串中的字母可以随便使用。palindrome可能的构成方式有两种,一种是全部是偶数次的字符,另一种是偶数次的字符和一个奇数次的字符(如果有)。所以问题的关键是找出所有出现了奇数次的字符的个数。最后,最大长度等于总长度减去(奇数次字符的个数-1)。
class Solution:
def longestPalindrome(self, s):
odd_num = {}
for char in s:
if char in odd_num:
del odd_num[char] ## pay attention,当一个出现了两次时,就删掉
else:
odd_num[char] = True
remove = len(odd_num)
if remove > 0:
remove -= 1
return len(s) - remove
shortest palindrome
标注为hard,实际上这个题有两种解法,第一种解法是O(n^2), 第二种解法是KMP,O(n),
Let's first see the basic logic of this question. Given a string, out aim is to add a string in the front to make the new string a palindrome. Say the given string is S, the string we add in the front is denoted as P, so the result string is PS, which needs to be a palindrome. According to the definition of palindrome, we can rewrite string PS into:
PS = PQP' where QP' = S and P' is the reverse of string P
So, if we want P as short as possible, in other words, we want Q as long as possible. Then the original problem can be converted into the problem that find the largest palindrome starting with the first character.
First method, using two pointer, time: O(n ^ 2) worst case: aaaaaabcaaaaaa
class Solution {
public String shortestPalindrome(String s) {
if (s == null || s.length() <= 1) return s;
int i = 0;
int end = s.length() - 1;
int j = end;
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;
}
}
Second method, using KMP, refer: http://yucoding.blogspot.com/2015/10/leetcode-question-shortest-palindrome.html
Time: O(n) space: O(n)