[算法總結] 13 道題搞定 BAT 面試——字符串

1. KMP 算法

談到字符串問題,不得不提的就是 KMP 算法,它是用來解決字符串查找的問題,能夠在一個字符串(S)中查找一個子串(W)出現的位置。KMP 算法把字符匹配的時間複雜度縮小到 O(m+n) ,而空間複雜度也只有O(m)。由於「暴力搜索」的方法會反覆回溯主串,致使效率低下,而KMP算法能夠利用已經部分匹配這個有效信息,保持主串上的指針不回溯,經過修改子串的指針,讓模式串儘可能地移動到有效的位置。html

具體算法細節請參考:java

1.1 BM 算法

BM算法也是一種精確字符串匹配算法,它採用從右向左比較的方法,同時應用到了兩種啓發式規則,即壞字符規則 和好後綴規則 ,來決定向右跳躍的距離。基本思路就是從右往左進行字符匹配,遇到不匹配的字符後從壞字符表和好後綴表找一個最大的右移值,將模式串右移繼續匹配。
字符串匹配的KMP算法git

2. 替換空格

劍指offer:替換空格
請實現一個函數,將一個字符串中的每一個空格替換成「%20」。例如,當字符串爲We Are Happy.則通過替換以後的字符串爲We%20Are%20Happy。正則表達式

public class Solution { public String replaceSpace(StringBuffer str) { StringBuffer res = new StringBuffer(); int len = str.length() - 1; for(int i = len; i >= 0; i--){ if(str.charAt(i) == ' ') res.append("02%"); else res.append(str.charAt(i)); } return res.reverse().toString(); } }

3. 最長公共前綴

Leetcode: 最長公共前綴
編寫一個函數來查找字符串數組中的最長公共前綴。若是不存在公共前綴,返回空字符串 ""。算法


首先對字符串數組進行排序,而後拿數組中的第一個和最後一個字符串進行比較,從第 0 位開始,若是相同,把它加入 res 中,不一樣則退出。最後返回 res數組

class Solution { public String longestCommonPrefix(String[] strs) { if(strs == null || strs.length == 0) return ""; Arrays.sort(strs); char [] first = strs[0].toCharArray(); char [] last = strs[strs.length - 1].toCharArray(); StringBuffer res = new StringBuffer(); int len = first.length < last.length ? first.length : last.length; int i = 0; while(i < len){ if(first[i] == last[i]){ res.append(first[i]); i++; } else break; } return res.toString(); } }

4. 最長迴文串

LeetCode: 最長迴文串
給定一個包含大寫字母和小寫字母的字符串,找到經過這些字母構形成的最長的迴文串。在構造過程當中,請注意區分大小寫。好比 "Aa" 不能當作一個迴文字符串。app


統計字母出現的次數便可,雙數才能構成迴文。由於容許中間一個數單獨出現,好比「abcba」,因此若是最後有字母落單,總長度能夠加 1。ide

class Solution { public int longestPalindrome(String s) { HashSet<Character> hs = new HashSet<>(); int len = s.length(); int count = 0; if(len == 0) return 0; for(int i = 0; i<len; i++){ if(hs.contains(s.charAt(i))){ hs.remove(s.charAt(i)); count++; }else{ hs.add(s.charAt(i)); } } return hs.isEmpty() ? count * 2 : count * 2 + 1; } }

4.1 驗證迴文串

Leetcode: 驗證迴文串
給定一個字符串,驗證它是不是迴文串,只考慮字母和數字字符,能夠忽略字母的大小寫。
說明:本題中,咱們將空字符串定義爲有效的迴文串。函數

兩個指針比較頭尾。要注意只考慮字母和數字字符,能夠忽略字母的大小寫。
google

class Solution { public boolean isPalindrome(String s) { if(s.length() == 0) return true; int l = 0, r = s.length() - 1; while(l < r){ if(!Character.isLetterOrDigit(s.charAt(l))){ l++; }else if(!Character.isLetterOrDigit(s.charAt(r))){ r--; }else{ if(Character.toLowerCase(s.charAt(l)) != Character.toLowerCase(s.charAt(r))) return false; l++; r--; } } return true; } }

4.2 最長迴文子串

LeetCode: 最長迴文子串
給定一個字符串 s,找到 s 中最長的迴文子串。你能夠假設 s 的最大長度爲1000。


以某個元素爲中心,分別計算偶數長度的迴文最大長度和奇數長度的迴文最大長度。

class Solution { private int index, len; public String longestPalindrome(String s) { if(s.length() < 2) return s; for(int i = 0; i < s.length()-1; i++){ PalindromeHelper(s, i, i); PalindromeHelper(s, i, i+1); } return s.substring(index, index+len); } public void PalindromeHelper(String s, int l, int r){ while(l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)){ l--; r++; } if(len < r - l - 1){ index = l + 1; len = r - l - 1; } } }

4.3 最長迴文子序列

LeetCode: 最長迴文子序列
給定一個字符串s,找到其中最長的迴文子序列。能夠假設s的最大長度爲1000。
最長迴文子序列和上一題最長迴文子串的區別是,子串是字符串中連續的一個序列,而子序列是字符串中保持相對位置的字符序列,例如,"bbbb"可使字符串"bbbab"的子序列但不是子串。


動態規劃:
dp[i][j] = dp[i+1][j-1] + 2 if s.charAt(i) == s.charAt(j)
otherwise, dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1])

class Solution { public int longestPalindromeSubseq(String s) { int len = s.length(); int [][] dp = new int[len][len]; for(int i = len - 1; i>=0; i--){ dp[i][i] = 1; for(int j = i+1; j < len; j++){ if(s.charAt(i) == s.charAt(j)) dp[i][j] = dp[i+1][j-1] + 2; else dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]); } } return dp[0][len-1]; } }

5. 字符串的排列

Leetcode: 字符串的排列
給定兩個字符串 s1 和 s2,寫一個函數來判斷 s2 是否包含 s1 的排列。
換句話說,第一個字符串的排列之一是第二個字符串的子串。


咱們不用真的去算出s1的全排列,只要統計字符出現的次數便可。可使用一個哈希表配上雙指針來作。

class Solution { public boolean checkInclusion(String s1, String s2) { int l1 = s1.length(); int l2 = s2.length(); int [] count = new int [128]; if(l1 > l2) return false; for(int i = 0; i<l1; i++){ count[s1.charAt(i) - 'a']++; count[s2.charAt(i) - 'a']--; } if(allZero(count)) return true; for(int i = l1; i<l2; i++){ count[s2.charAt(i) - 'a']--; count[s2.charAt(i-l1) - 'a']++; if(allZero(count)) return true; } return false; } public boolean allZero(int [] count){ int l = count.length; for(int i = 0; i < l; i++){ if(count[i] != 0) return false; } return true; } }

6. 打印字符串的全排列

劍指offer:字符串的排列
輸入一個字符串,按字典序打印出該字符串中字符的全部排列。例如輸入字符串abc,則打印出由字符a,b,c所能排列出來的全部字符串abc,acb,bac,bca,cab和cba。

把問題拆解成簡單的步驟:
第一步求全部可能出如今第一個位置的字符(即把第一個字符和後面的全部字符交換[相同字符不交換]);
第二步固定第一個字符,求後面全部字符的排列。這時候又能夠把後面的全部字符拆成兩部分(第一個字符以及剩下的全部字符),依此類推。這樣,咱們就能夠用遞歸的方法來解決。

public class Solution { ArrayList<String> res = new ArrayList<String>(); public ArrayList<String> Permutation(String str) { if(str == null) return res; PermutationHelper(str.toCharArray(), 0); Collections.sort(res); return res; } public void PermutationHelper(char[] str, int i){ if(i == str.length - 1){ res.add(String.valueOf(str)); }else{ for(int j = i; j < str.length; j++){ if(j!=i && str[i] == str[j]) continue; swap(str, i, j); PermutationHelper(str, i+1); swap(str, i, j); } } } public void swap(char[] str, int i, int j) { char temp = str[i]; str[i] = str[j]; str[j] = temp; } }

7. 第一個只出現一次的字符

劍指offer: 第一個只出現一次的字符
在一個字符串(0<=字符串長度<=10000,所有由字母組成)中找到第一個只出現一次的字符,並返回它的位置, 若是沒有則返回 -1.

先在hash表中統計各字母出現次數,第二次掃描直接訪問hash表得到次數。也能夠用數組代替hash表。

import java.util.HashMap; public class Solution { public int FirstNotRepeatingChar(String str) { int len = str.length(); if(len == 0) return -1; HashMap<Character, Integer> map = new HashMap<>(); for(int i = 0; i < len; i++){ if(map.containsKey(str.charAt(i))){ int value = map.get(str.charAt(i)); map.put(str.charAt(i), value+1); }else{ map.put(str.charAt(i), 1); } } for(int i = 0; i < len; i++){ if(map.get(str.charAt(i)) == 1) return i; } return -1; } }

8. 翻轉單詞順序列

劍指offer: 翻轉單詞順序列
LeetCode: 翻轉字符串裏的單詞

藉助trim()和 split()就很容易搞定

public class Solution { public String reverseWords(String s) { if(s.trim().length() == 0) return s.trim(); String [] temp = s.trim().split(" +"); String res = ""; for(int i = temp.length - 1; i > 0; i--){ res += temp[i] + " "; } return res + temp[0]; } }

9. 旋轉字符串

Leetcode: 旋轉字符串
給定兩個字符串, A 和 B。
A 的旋轉操做就是將 A 最左邊的字符移動到最右邊。 例如, 若 A = 'abcde',在移動一次以後結果就是'bcdea' 。若是在若干次旋轉操做以後,A 能變成B,那麼返回True。


一行代碼搞定

class Solution { public boolean rotateString(String A, String B) { return A.length() == B.length() && (A+A).contains(B); } }

9.1 左旋轉字符串

劍指offer: 左旋轉字符串
彙編語言中有一種移位指令叫作循環左移(ROL),如今有個簡單的任務,就是用字符串模擬這個指令的運算結果。對於一個給定的字符序列S,請你把其循環左移K位後的序列輸出。例如,字符序列S=」abcXYZdef」,要求輸出循環左移3位後的結果,即「XYZdefabc」。是否是很簡單?OK,搞定它!

在第 n 個字符後面將切一刀,將字符串分爲兩部分,再從新並接起來便可。注意字符串長度爲 0 的狀況。

public class Solution { public String LeftRotateString(String str,int n) { int len = str.length(); if(len == 0) return ""; n = n % len; String s1 = str.substring(n, len); String s2 = str.substring(0, n); return s1+s2; } }

9.2 反轉字符串

LeetCode: 反轉字符串
編寫一個函數,其做用是將輸入的字符串反轉過來。

class Solution { public String reverseString(String s) { if(s.length() < 2) return s; int l = 0, r = s.length() - 1; char [] strs = s.toCharArray(); while(l < r){ char temp = strs[l]; strs[l] = strs[r]; strs[r] = temp; l++; r--; } return new String(strs); } }

10. 把字符串轉換成整數

劍指offer: 把字符串轉換成整數
將一個字符串轉換成一個整數(實現Integer.valueOf(string)的功能,可是string不符合數字要求時返回0),要求不能使用字符串轉換整數的庫函數。 數值爲0或者字符串不是一個合法的數值則返回0。

public class Solution { public int StrToInt(String str) { if(str.length() == 0) return 0; int flag = 0; if(str.charAt(0) == '+') flag = 1; else if(str.charAt(0) == '-') flag = 2; int start = flag > 0 ? 1 : 0; long res = 0; while(start < str.length()){ if(str.charAt(start) > '9' || str.charAt(start) < '0') return 0; res = res * 10 + (str.charAt(start) - '0'); start ++; } return flag == 2 ? -(int)res : (int)res; } }

11. 正則表達式匹配

劍指offer:正則表達式匹配
請實現一個函數用來匹配包括’.’和’*’的正則表達式。模式中的字符’.’表示任意一個字符,而’*’表示它前面的字符能夠出現任意次(包含0次)。 在本題中,匹配是指字符串的全部字符匹配整個模式。例如,字符串」aaa」與模式」a.a」和」ab*ac*a」匹配,可是與」aa.a」和」ab*a」均不匹配


動態規劃:
這裏咱們採用dp[i+1][j+1]表明s[0..i]匹配p[0..j]的結果,結果天然是採用布爾值True/False來表示。
首先,對邊界進行賦值,顯然dp[0][0] = true,兩個空字符串的匹配結果天然爲True;
接着,咱們對dp[0][j+1]進行賦值,由於 i=0 是空串,若是一個空串和一個匹配串想要匹配成功,那麼只有多是p.charAt(j) == '*' && dp[0][j-1]
以後,就能夠愉快地使用動態規劃遞推方程了。

public boolean isMatch(String s, String p) { if (s == null || p == null) { return false; } boolean[][] dp = new boolean[s.length()+1][p.length()+1]; dp[0][0] = true; for (int j = 0; i < p.length(); j++) { if (p.charAt(j) == '*' && dp[0][j-1]) { dp[0][j+1] = true; } } for (int i = 0 ; i < s.length(); i++) { for (int j = 0; j < p.length(); j++) { if (p.charAt(j) == '.') { dp[i+1][j+1] = dp[i][j]; } if (p.charAt(j) == s.charAt(i)) { dp[i+1][j+1] = dp[i][j]; } if (p.charAt(j) == '*') { if (p.charAt(j-1) != s.charAt(i) && p.charAt(j-1) != '.') { dp[i+1][j+1] = dp[i+1][j-1]; } else { dp[i+1][j+1] = (dp[i+1][j] || dp[i][j+1] || dp[i+1][j-1]); } } } } return dp[s.length()][p.length()]; }

12. 表示數值的字符串

劍指offer: 表示數值的字符串
請實現一個函數用來判斷字符串是否表示數值(包括整數和小數)。例如,字符串」+100″,」5e2″,」-123″,」3.1416″和」-1E-16″都表示數值。 可是」12e」,」1a3.14″,」1.2.3″,」+-5″和」12e+4.3″都不是。

設置三個標誌符分別記錄「+/-」、「e/E」和「.」是否出現過。

public class Solution { public boolean isNumeric(char[] str) { int len = str.length; boolean sign = false, decimal = false, hasE = false; for(int i = 0; i < len; i++){ if(str[i] == '+' || str[i] == '-'){ if(!sign && i > 0 && str[i-1] != 'e' && str[i-1] != 'E') return false; if(sign && str[i-1] != 'e' && str[i-1] != 'E') return false; sign = true; }else if(str[i] == 'e' || str[i] == 'E'){ if(i == len - 1) return false; if(hasE) return false; hasE = true; }else if(str[i] == '.'){ if(hasE || decimal) return false; decimal = true; }else if(str[i] < '0' || str[i] > '9') return false; } return true; } }

13. 字符流中第一個不重複的字符

劍指offer: 字符流中第一個不重複的字符
請實現一個函數用來找出字符流中第一個只出現一次的字符。例如,當從字符流中只讀出前兩個字符」go」時,第一個只出現一次的字符是」g」。當從該字符流中讀出前六個字符「google」時,第一個只出現一次的字符是」l」。

用一個哈希表來存儲每一個字符及其出現的次數,另外用一個字符串 s 來保存字符流中字符的順序。

import java.util.HashMap; public class Solution { HashMap<Character, Integer> map = new HashMap<Character, Integer>(); StringBuffer s = new StringBuffer(); //Insert one char from stringstream public void Insert(char ch) { s.append(ch); if(map.containsKey(ch)){ map.put(ch, map.get(ch)+1); }else{ map.put(ch, 1); } } //return the first appearence once char in current stringstream public char FirstAppearingOnce() { for(int i = 0; i < s.length(); i++){ if(map.get(s.charAt(i)) == 1) return s.charAt(i); } return '#'; } }
相關文章
相關標籤/搜索