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
}
}
複製程式碼