1. 程式人生 > >String相關

String相關

目錄

3無重複字元的最長子串

思路:

  • 新建陣列int[128],用來記錄各個字元處在字串中的最新位置。注意位置從1開始,一方面區別預設的0,另一方面,下面調整left的值時,可以直接移動到剛好去掉重複字元的位置。
  • 設定左右邊界,右為遍歷中的i
    • 每遇到新的字元,看是否需要調整left位置。取當前left位置和在record中記錄的當前遍歷到的字元的位置的最大值。如果該字元的位置大於left,說明新字元與視窗中的某個值重複了。所以left更新為該字元前一次出現的位置。
    • 記錄/更新當前字元的位置,記得是right+1
    • 調整視窗最大值
// 新建陣列存放各個字元的最新位置
int[] record = new int[128];
// 設定左
int left = 0, res = 0, n = s.length();

// 遍歷
for (int right = 0; right < n; right++) {
    // 每遇到新的字元,看是否需要調整left位置
    left = Math.max(left, record[s.charAt(right)]);
    // 記錄當前字元的位置
    record[s.charAt(right)] = right - 1;
    // 判斷新視窗的長度
    res = Math.max(res, right - left + 1);
}
return res;

5最長迴文子串

思路:

  • 少於兩個節點的情況
  • 設定maxLen和start記錄最大值及最大值位置
  • 遍歷,優化條件n - i > maxLen / 2
    • 設定左右指標
    • 右指標去重
    • i調整到右指標
    • 向兩邊擴充套件
    • 檢查max是否需要更新
  • 返回結果s.substring(start, start + maxLen)
if (s.length() < 2) return s;

int n = s.length();
int maxLen = 0;
int start = 0;

for (int i = 0; i < n && n-i > maxLen/2; i++){
    int left = i, right = i;
    
    while (right < n - 1 && s.charAt(right) == s.charAt(right + 1)) right ++;
    
    i = right;
    
    while (right < n - 1 && left > 0 && s.charAt(right+1) == s.charAt(left-1)){
        right++;
        left--;
    }
    
    if (maxLen < right - left + 1){
        maxLen = right - left + 1;
        start = left;
    }
}

return s.substring(start, start + maxLen);

8字串轉換整數(atoi), 9迴文數,7整數反轉

思路:

  • 變數設定:結果變數、遍歷座標、符號變數、長度、界限
  • 去開頭的空格,去完後檢查是否已到盡頭(全為空的情況)
  • 判斷第一個非空字元是否為正負號,並記錄在sign變數中。這裡用else if結束
  • 迴圈,只要沒有超過範圍而且是數字
    • 新增數字前檢查是否會溢位
    • 數字拼接res = res * 10 + (s.charAt(i++) - '0');
  • return res * sign
int i = 0, n = s.length(), sign = 1, res = 0, limit = Integer.MAX_VALUE / 10;

while(i < n && s.charAt(i) == ' ') i++;
if (i >= n) return 0;

if (s.charAt(i) == '+') i++;
else if (s.charAt(i) == '-') {
    i++;
    sign = -1;
}

while (i < n && s.charAt(i) >= '0' && s.charAt(i) <= '9'){
    if (res > limit || (res == limit && s.charAt(i) > '7')){
        return sign == -1 ? Integer.MIN_VALUE : Integer.MAX_VALUE;
    }
    res = res * 10 + (s.charAt(i++) - '0');
}

return res * sign;

相關:迴文數字/反轉數字

int reversedNum = 0;
// 只需反轉到一半,或者一半多一個字
while (x > reversedNum) {
    // 下面用於反轉數字
// while (x != 0) {
//     if (Math.abs(res) > limit) return 0;
    reversedNum = reversedNum * 10 + x % 10;
    x /= 10;
}

// 判斷剛好一半,還是一半多一個字
return x == reversedNum || x == reversedNum / 10;

28實現strStr(), 459重複的子字串(KMP)

Java的 indexOf()。

思路:

  • 根據haystack和needle的長度判斷可能性:如果needle為空,返回0,如果haystack為空或者haystack的長度小於needle的,返回-1;
  • 遍歷haystack,從每個字元開始遍歷needle,只要出現不同就break。如果順利完成needle的遍歷,那就返回當前haystack的index
  • 如果遍歷完haystack,說明沒有找到,返回-1
int m = hs.length();
int n = nd.length();
if (n == 0) return 0;
if (m == 0 || m < n) return -1;

// 注意到達m-n+1時,說明剛好能夠容納一個needle,所以用小於
for (int i = 0; i < m - n + 1; i++){
    int j = 0;
    for (j = 0; j < n; j++){
        if (hs.charAt(i+j) != nd.charAt(j)) break;
    }
    if (j == n) return i;
}

return -1;

更好的方法是KMP,可參考下題的方法。

459重複的子字串

// "abcabc"的dp陣列為[-1 0 0 0 1 2 3],dp陣列長度要比原字串長度多一個。
// 那麼我們看最後一個位置數字為3,就表示重複的字串的字元數有3個。
// 如果是"abcabcabc",那麼dp陣列為[-1 0 0 0 1 2 3 4 5 6]
int j = 0, k = -1, n = s.length();
int[] dp = new int[n+1];
dp[0] = -1;
while (j < n) {
    // 過程:k=-1時,會給陣列新增0,下一次k不為-1了,就要比較j和k的字是否相同。
    // 如果相同,k和j就可以一同前進,陣列開始新增為1,即有重複字串有一個了。
    // 如果不同,k變回-1,j不動。
    // 如果出現了重複字串,k會與j相隔這個重複字串的長度。
    // 如果發現當前重複的字串不對,通過k = dp[k],就能讓k回到前面找是否有匹配的。如果沒有就從新在-1開始
    if (k == -1 || s.charAt(j) == s.charAt(k)) {
        dp[++j] = ++k;
    } else {
        k = dp[k];
    }
}

// dp[n] != 0 表示最後一個字元也配對了。否則第二個條件中0 % 任何數(除了0)都為0,就肯定返回true了
// (dp[n] % (n - dp[n])) == 0 中 (n - dp[n])得到重複子字串的長度,即上面的abc
return dp[n] != 0 && (dp[n] % (n - dp[n])) == 0;

上面可以用來求next陣列,dp大小隻需n,最後返回dp即可。

返回的next陣列可用於上面的strStr。程式碼與上面幾乎一樣,就多加了 k < m和 (k == m) ? j - k : -1;

int n = haystack.length(), m = needle.length(), j = 0, k = 0;
int[] next;
next = getNext(needle);
while (j < n && k < m) {
    if (k == -1 || haystack.charAt(j) == needle.charAt(k)) {
        ++j;
        ++k;
    } else {
        k = next[k];
    }
}
return (k == m) ? j - k : -1;

43字串相乘

思路:

  • 0的情況
  • 用一個數組記錄每對乘積的結果(相乘結果最多m+n位),遍歷更新陣列,從後往前記錄
  • 定義一個carry,遍歷將陣列的值轉化為結果值中的單個數。
  • 去掉多餘的0,同時確定string開始合併的位置。
  • 利用stringbuilder合併
// 設定變數:長度、陣列開始更新的位置
int m = num1.length(), n = num2.length(), k = m + n - 2;

// 0的情況
if ((m == 1 && num1.equals("0")) || (n == 1 && num2.equals("0"))) return "0";

// 儲存兩個單數字乘積結果的陣列
// 遍歷更新陣列
int[] record = new int[m+n];
for (int i = 0; i < m; i++){
    for (int j = 0; j < n; j++){
        // 注意這裡要 +=,因為有i+j兩次等於相同的數。
        // 另外,最後一個位置此時並不會用上。例如99 * 99中,數組裡的數分別是[81, 162, 81, 0]
        record[k-i-j] += (num1.charAt(i) - '0') * (num2.charAt(j) - '0');
    }
}

// 將陣列的值轉化為結果值
// 加總,將各乘積整理成單個數。此處用上最後一個位置,[1 0 8 9]
int carry = 0;
for (int i = 0; i < m + n; i++){
    record[i] += carry;
    carry = record[i] / 10;
    record[i] %= 10;
}

// 去掉多餘的0,同時確定string開始合併的位置
int i = m + n - 1;
if (record[i] == 0) i--;

// 利用stringbuilder合併
StringBuilder res = new StringBuilder();
while (i >= 0) res.append(record[i--]);

// 返回結果
return res.toString();

71簡化路徑

思路:

  • split
  • 遍歷,如果不是空和".",那麼就判斷新增還是刪除了
    • 如果不等於"..",新增
    • 否則判斷listPath.size()是否大於0,是就去掉最後一個檔案
  • 如果遍歷後是空的話返回"/",如果有多個"/"只保留一個。
String[] paths = path.split("/");
List<String> res = new ArrayList<>();

for (String str : paths) {
    if (!str.isEmpty() && !str.equals(".")){
        if (!str.equals("..")) res.add(str);
        else {
            // 如果是"..",且size() > 0,要去掉最後一個檔案
            if (res.size() > 0) res.remove(res.size() - 1);
        }
    }
}

if (res.size() == 0) return "/";

StringBuilder builder = new StringBuilder();
for (String re : res) {
    builder.append("/").append(re);
}

return builder.toString();

93復原IP地址

思路:

  • 新建ArrayList儲存結果

  • 建立函式遞迴尋找四個數字的組合(函式中包含:待檢驗的string、已確定數量的n,例如1表示已經得出255.xx、目前拼接到的)

    • 遍歷1到4,由於每位最多3個數字。如果s剩餘的長度不及遍歷的數字k,就結束迴圈。用一位,兩位,三位來嘗試,判斷是否合法val > 255 || k != String.valueOf(val).length()。合法的話,說明配置了一個,遞迴呼叫helper時n+1,out + 配置好的值val,另外如果n==3,則要加"."

    • 如果n達到了4,還要檢查s是否為空,否則會出現"2.5.5.2"

public static List<String> restoreIpAddresses(String s) {
    List<String> res = new ArrayList<String>();
    helper(s, 0, "", res);
    return res;
}

/**
 * @param n 已確定的位數,例如 255. 就確定了一位
 * @param out 當前累積的結果,如 255. 或 255.1
 */
public static void helper(String s, int n, String out, List<String> res) {
    if (n == 4) {
        // 儲存結果,s.isEmpty()是最後一段剛好3個數的情況
        // 要檢查s是否已經為空,否則會出現"2.5.5.2"
        if (s.isEmpty()) res.add(out);
        return;
    }

    for (int k = 1; k < 4; ++k) {
        // 剩餘的數可能組不成3個數,如23,此時就沒必要嘗試k = 3了,否則s.substring(0, k)會outOfIndex
        if (s.length() < k) break;
        // 用一位,兩位,三位來嘗試,分別判斷其合不合法,如果合法,則呼叫遞迴繼續分剩下的字串,最終和求出所有合法組合
        int val = Integer.parseInt(s.substring(0, k));
        // 比如當k=3時,說明應該是個三位數,但如果字元是"010",那麼轉為整型val=10,再轉回字串就是"10",此時的長度和k值不同了
        if (val > 255 || k != String.valueOf(val).length()) continue;
        helper(s.substring(k), n + 1, out + val + (n == 3 ? "" : "."), res);
    }
}

60第k個排列

給定 nk,返回第 k 個排列。

思路:

  • 新建一個ArrayList,儲存[1, ..., n]
  • 新建一個數組儲存[1, 1!, 2!, 3!, ..., n-1!]階乘
  • k—,把排名轉換為下標
StringBuilder sb = new StringBuilder();
for (int i = n; i >= 1; i--) {
    // 除以 n - 1 的階乘,即16/3! = 2,在第三組全排序中
    int idx = k / f[i - 1];
    // nums.get(idx)取得3
    sb.append(nums.get(idx));
    // 去掉已選的數字
    nums.remove(idx);
    // 取餘數,更新k在新組中的排名,即第三組中的第16%3! = 4個(0開始)
    k %= f[i - 1];
}
return sb.toString();

151翻轉字串裡的單詞(344反轉字串、557反轉字串中的單詞 III)

思路:

  • split
  • 判斷是否為空
  • 遍歷,從後往前,只要不為空,就新增,並加上空格
  • 返回結果,記得trim
151翻轉字串裡的單詞
輸入: "the sky is blue",
輸出: "blue is sky the".

344反轉字串
輸入: "A man, a plan, a canal: Panama"
輸出: "amanaP :lanac a ,nalp a ,nam A"

557反轉字串中的單詞 
輸入: "Let's take LeetCode contest"
輸出: "s'teL ekat edoCteeL tsetnoc" 

14最長公共字首

思路:

  • 取第一個string作為prefix
  • 遍歷,從第二個開始
    • 迴圈,只要str.indexOf(prefix) != 0,prefix去掉最後一個字元
  • 返回prefix

20有效的括號

思路:

  • 新建stack
  • 遍歷
    • 遇到做括號入棧
    • 否則,先判斷stack是否為空,為空返回false,否則頂層和各個有括號匹配,不匹配返回false
  • 返回stack.isEmpty()

567字串的排列(瞭解)

輸入: s1 = "ab" s2 = "eidbaooo"
輸出: True
解釋: s2 包含 s1 的排列之一 ("ba").

思路:

  • 把s1都放進int[128]中,放一個,record[i]++;
  • 遍歷s2找匹配,遍歷到的都-1
  • 當cnt減到0時,開始判斷視窗大小是否剛好等於s1大小,否則縮小left,如果縮小時減去了s1中的值,則cnt+1,繼續上一步的遍歷。
// 建立陣列計數s1的字元
int[] record = new int[128];
for (char i: s1.toCharArray()){
    record[i]++;
}
// 遍歷,從s2中尋找配對的
for (int right = 0; right < n2; right++){
    if (record[s2.charAt(right)]-- > 0) cnt--;

    // 如果匹配數等於s1的長度,遍歷
    while(cnt == 0){
        // 如果視窗大小剛好與s1的一樣,則找到了
        if (right - left + 1 == n1) return true;
        // 收縮左邊界,如果去掉了s1中的字元,那麼s1的匹配數+1
        if (++record[s2.charAt(left++)] > 0) cnt++;
    }
}
return false;