409 Longest Palindrome[Easy]
- 题目: 给出一个字符串,求这个字符串能够组合出来的最长回文串
- 思路: 不能直接用一个hash数组来做记录奇偶数,因为会出现多个总数为奇数的字符和一个总数为奇数的长字符。
- 还要进行判断建一个HashSet需要挑出最长的奇数字符串 没有将奇数字符串中的成对出现的偶数部分加入到最后的结果中。
class Solution {
public int longestPalindrome(String s) {
if (s == null || s.length() == 0) {
return 0;
}
HashSet<Character> set = new HashSet<>();
int count = 0;
for (int i = 0; i < s.length(); i++) {
if (set.contains(s.charAt(i))) {
set.remove(s.charAt(i));
count++;
} else {
set.add(s.charAt(i));
}
}
if (set.isEmpty()) {
return count * 2;
}
return count * 2 + 1;
}
}
复制代码
9. Palindrome Number[Easy]
- 题目: 判断一个整数是不是回文的
- 思路: 先将整数转为一个字符串数组,两个指针从两端向中间走
class Solution {
public boolean isPalindrome(int x) {
String nums = Integer.toString(x);
int left = 0;
int right = nums.length() - 1;
while (left < right) {
if (nums.charAt(left) != nums.charAt(right)) {
return false;
} else {
left++;
right--;
}
}
return true;
}
}
复制代码
234. Palindrome Linked List[Easy]
- 题目: 判断一个单向链表是否是回文串
- 思路: 复制一个相同的链表,进行一个reverse操作,然后用两个指针从两个链表的头分别向后移动并逐个比对 O(n)的空间复杂度 O(n)的时间复杂度。 优化:先用快慢指针找到该链表的中点,将后半段reverse,然后用两个指针同向移动
class Solution {
public boolean isPalindrome(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
if (fast != null) { //这里需要做一个奇偶的判断,如果fast不为null,那么slow需要向前移动一位,跳过中间值
slow = slow.next;
}
slow = reverse(slow);
ListNode left = head;
ListNode right = slow;
while (right != null) {
if (left.val != right.val) {
return false;
}
left = left.next;
right = right.next;
}
return true;
}
private ListNode reverse(ListNode node) {
ListNode prev = null;
ListNode curr = node;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
}
复制代码
49. Group Anagrams[Medium]
- 思路: 用一个hashtable来存 数组来存一个集合? 如何能找到不同字母组合的一致性? 只需要包含相同的字母即可, 顺序没有关系
- 需要的是一个flag来做记录 两个选择: 1. 类似
a1b2c3
的结构 2.直接排序得到有序结果
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
if (strs == null || strs.length == 0) {
return new ArrayList<>();
}
List<List<String>> res = new ArrayList<>();
Map<String, List<String>> map = new HashMap<>();
int[] count = new int[26];
for (String str : strs) {
Arrays.fill(count, 0);
for (char c : str.toCharArray()) {
count[c - 'a']++;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 26; i++) {
if (count[i] != 0) {
sb.append((char)(i + 'a')).append(count[i]);
}
}
String flag = sb.toString();
if (!map.containsKey(flag)) {
map.put(flag, new ArrayList<>());
}
map.get(flag).add(str);
}
return new ArrayList<>(map.values());
}
}
复制代码
266.Palindrome Permutation[Easy]
- 题目:给出一个字符串,判断该字符串的permutation能否组成给一个回文串
- 思路: 开一个长度256的数组来记录每个字符出现的次数,最多仅允许存在一个出现奇数次的字符
class Solution {
public boolean canPermutePalindrome(String s) {
if (s.length() == 0) {
return true;
}
boolean oneOdd = false;
int[] counts = new int[256];
for (int i = 0; i < s.length(); i++) {
counts[s.charAt(i)]++; //这里直接用s.charAt(i)作为ASCII编码
}
for (int k = 0; k < counts.length; k++) {
if (counts[k] % 2 != 0) {
if (oneOdd == true) {
return false;
}
else {
oneOdd = true;
}
}
}
return true;
}
}
复制代码
267. Palindrome PermutationII [Medium]
- 思路: Permutation 如何找到? DFS + backtracking, 回文串的permuration是通过两个指针进行简单的镜像对称来实现的 在找到的所有的permutation当中判断出来哪些是Palindrome放到res当中。效率太低。
需要判断的是字符串的奇偶性, 用一个oneOdd
变量来做标记,如果是奇数个, 最中间一定是一个单字符, 双指针的起点是i-1
和i+1
,如果是偶数个, 起点为i
和i + 1
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>(); //奇数字符多于一个直接return空字符
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;
}
}
}
复制代码
5. Longest Palindromic Substring[Medium]
- 题目: 找出最长的回文子串
- 思路: 中心点枚举直接做,写一个得到回文串长度的function, Corner Case是只有一个字符和只有两个字符时的情况。while循环的终点根据指针最后停下的位置来确定。
class Solution {
public String longestPalindrome(String s) {
if (s == null || s.length() == 0) {
return "";
}
int start = 0;
int longest = 0;
int currLen = 0;
for (int i = 0; i < s.length(); i++) {
currLen = calculatePalindromeLength(s, i, i);
if (currLen > longest) {
longest = currLen;
start = i - currLen / 2;
}
currLen = calculatePalindromeLength(s, i, i+1);
if (currLen > longest) {
longest = currLen;
start = i - currLen / 2 + 1;
}
}
return s.substring(start, start + longest);
}
private int calculatePalindromeLength(String s, int left, int right) {
int len = 0;
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
len += left == right ? 1 : 2;
left--;
right++;
}
return len;
}
}
复制代码
647. Palindromic Substrings
- 题目: 给一个字符串,找到所有的回文子串,可以包含重复元素
- 思路: 和Palindrome Partitioning一样, 找出所有的子串, subset,判断每个子串是否是Palindrome,不需要使用回溯找subset,直接用中心点枚举遍历所有substring即可
public class Solution {
int count = 0;
public int countSubstrings(String s) {
if (s == null || s.length() == 0) return 0;
for (int i = 0; i < s.length(); i++) { // i is the mid point
extendPalindrome(s, i, i); // odd length;
extendPalindrome(s, i, i + 1); // even length
}
return count;
}
private void extendPalindrome(String s, int left, int right) {
while (left >=0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
count++;
left--;
right++;
}
}
}
复制代码
131. Palindrome Partitioning[Medium]
- 题目: 给出一个字符串s,求出切分s得到所有的s都是回文串,并将回文串添加到List当中返回
- 思路: 使用backtracking,找出不同的切分方法
class Solution {
public List<List<String>> partition(String s) {
List<List<String>> res = new ArrayList<>();
helper(s, res, new ArrayList<>(), 0);
return res;
}
public void helper(String s,
List<List<String>> res,
List<String> currList,
int start) {
if (currList.size() > 0 && start >= s.length()) {
res.add(new ArrayList(currList));
}
for (int i = start; i < s.length(); i++) {
if (isPalindrome(s, start, i)) {
currList.add(s.substring(start, i + 1));
helper(s, res, currList, i + 1);
currList.remove(currList.size() - 1);
}
}
}
private boolean isPalindrome(String s, int i, int j) {
while (i < j) {
if (s.charAt(i) != s.charAt(j)) {
return false;
}
i++;
j--;
}
return true;
}
}
复制代码
132. Palindrome Partitioning II
- 题目: 给出一个字符串s, 求出最少需要切割几次才能够让所有的substring都是回文串
- 思路:
336. Palindrome Pairs[Hard]
- 题目: 给出一个String List words,找出所有能够拼接为一个回文串的两个String的index并存在列表之中返回
- 思路: 如何判断两个String能够拼接为一个回文串? 如何找到所有的字符串?
- 如何判断两个String能够拼接为一个回文串? 先拼接 再直接双指针遍历做判断即可 分类讨论:
- case 1: blank string, 如果words当中存在回文串,那么该回文串+空串就可以作为一对结果
- case 2: 对称拼接 s2 是 s1 的reverse
- case 3: 非对称拼接: s2的一部分和s1的一部分对称 或 s1的一部分和s2的一部分对称
- 如何判断两个String能够拼接为一个回文串? 先拼接 再直接双指针遍历做判断即可 分类讨论:
class Solution {
public List<List<Integer>> palindromePairs(String[] words) {
List<List<Integer>> res = new ArrayList<>();
HashMap<String, Integer> map = new HashMap<>();
int n = words.length;
for (int i = 0; i < n; i++) {
map.put(words[i], i);
}
// case 1: blank
if (map.containsKey("")) {
int blankIdx = map.get("");
for (int i = 0; i < n; i++) {
if (isPalindrome(words[i])) {
if (i == blankIdx) {
continue;
}
res.add(Arrays.asList(blankIdx, i));
res.add(Arrays.asList(i, blankIdx));
}
}
}
// case 2: found the reverse string
for (int i = 0; i < n; i++) {
String reverseStr = reverse(words[i]);
if (map.containsKey(reverseStr)) {
int found = map.get(reverseStr);
if (found == i) continue;
res.add(Arrays.asList(i, found));
}
}
//case 3: nonsymmetric
for (int i = 0; i < n; i++) {
String cur = words[i];
for (int cut = 1; cut < cur.length(); cut++) {
if (isPalindrome(cur.substring(0, cut))) {
String reverseCut = reverse(cur.substring(cut));
if(map.containsKey(reverseCut)) {
int found = map.get(reverseCut);
if (found == i) {
continue;
}
res.add(Arrays.asList(found, i));
}
}
if (isPalindrome(cur.substring(cut))) {
String reverseCut = reverse(cur.substring(0, cut));
if(map.containsKey(reverseCut)) {
int found = map.get(reverseCut);
if (found == i) {
continue;
}
res.add(Arrays.asList(i, found));
}
}
}
}
return res;
}
private String reverse(String s) {
StringBuilder sb = new StringBuilder(s);
return sb.reverse().toString();
}
private boolean isPalindrome(String s) {
int i = 0;
int j = s.length() - 1;
while (i < j) {
if (s.charAt(i) != s.charAt(j)) {
return false;
}
i++;
j--;
}
return true;
}
}
复制代码
相关题目:
46. Permutations [Medium]
- 思路: Backtracking, 使用一个辅助的
used
数组来进行判断
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
if (nums == null || nums.length == 0) {
return res;
}
int n = nums.length;
boolean[] used = new boolean[n];
helper(res, new ArrayList<>(), used, nums);
return res;
}
private void helper(List<List<Integer>> res, List<Integer> curr, boolean[] used, int[] nums) {
if (curr.size() == nums.length) {
res.add(new ArrayList(curr));
}
for (int i = 0; i < nums.length; i++) {
if (!used[i]) {
curr.add(nums[i]);
used[i] = true;
helper(res, curr, used, nums);
used[i] = false;
curr.remove(curr.size() - 1);
}
}
}
}
复制代码
有重复元素
Arrays.sort(nums) // 排序这样所有重复的数
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1]) { continue; } // 跳过会造成重复的情况
复制代码
78. Subsets [Medium]
- 思路: for 循环的起点不同,Permutation是必须利用每一个字符的,所以每次循环都从字符串的起点
i = 0
开始遍历,而Subset问题是不能回头的,所以不管之前的字符是否使用过,都只能从当前字符往后走,每次循环的起点是i = start
, 同时递归function当中的起点也应该是i + 1
。
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> results = new ArrayList<>();
Arrays.sort(nums);
helper(results, new ArrayList<>(), nums, 0);
return results;
}
// 1.递归的定义: 在nums中找到所有的subset,每个元素都有两个选择, 加入和不加入
private void helper(List<List<Integer>> results,
List<Integer> subset,
int[] nums,
int start) {
//2. 递归的拆解 deep copy -> create new subset ArrayList
//每一层都加当前的新元素
//添加顺序: [] [1] [1,2] [1,2,3] [1,3] [2] [2,3] [3]
results.add(new ArrayList(subset));
for (int i = start; i < nums.length; i++) {
//[] -> [1] or [1] -> [1, 2]
subset.add(nums[i]);
helper(results, subset, nums, i + 1);
subset.remove(subset.size() - 1);
}
// 3. 递归的出口
// 当for循环的条件不满足时,直接什么都不执行 => return
}
}
复制代码