1. 程式人生 > 其它 >2021-7-11 LeetCode

2021-7-11 LeetCode

今天開始刷中等難度的題了,今天刷的幾個還好,沒我之前想象的那麼恐怖,基本上能做,只是可能考慮得沒那麼細緻

兩數相加

2. 兩數相加 - 力扣(LeetCode) (leetcode-cn.com)

你兩個 非空 的連結串列,表示兩個非負的整數。它們每位數字都是按照 逆序 的方式儲存的,並且每個節點只能儲存 一位 數字。

請你將兩個數相加,並以相同形式返回一個表示和的連結串列。

你可以假設除了數字 0 之外,這兩個數都不會以 0 開頭。

例如:2->4->3和5->6->4,輸出結果為7->0->8,因為342 + 465 = 807

思路:

騰訊的50個題簡單程度的差不多刷完了,開始做中等難度的題了,但這第一個怎麼感覺比很多簡單題還要簡單。幾分鐘之內一遍過了。

給出的連結串列位數是從低到高,這就很好處理。同時遍歷兩個連結串列再相加再加上上一輪運算的進位就行了。若遇到連結串列遍歷到尾節點,就把值當成0就行了。

運算過程加上一個判斷是否進位,若sum > 9則結果為sum - 10,並把flag為置為1。否則直接把sum作為結果,並把flag值置為0。其中sum = num1 + num2 + flag,flag 為 進位標誌,1表示上一輪運算有進位。

程式碼:

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    ListNode res = new ListNode();  // 結果的頭節點
    ListNode node = res;
    int flag = 0;   // 是否進位
    while (true) {
        int num1 = l1 == null ? 0 : l1.val;     // 若為null代表已遍歷結束,相當於此處值為0
        int num2 = l2 == null ? 0 : l2.val;
        int sum = num1 + num2 + flag;    // 兩數相加再加上前一位的進位
        // 發生進位則將結果設為sum值取餘,並把進位標誌置為1
        if (sum > 9) {
            node.val = sum - 10;
            flag = 1;
        } else {
            node.val = sum;
            flag = 0;
        }
        l1 = l1 != null ? l1.next : null;
        l2 = l2 != null ? l2.next : null;
        // 兩個連結串列都到鏈尾,且未發生進位的時候,退出迴圈(若兩連結串列都到鏈尾,但發生了進位,則下一個節點值為1,而不是直接退出)
        if (l1 == null && l2 == null && flag == 0)
            break;
        node.next = new ListNode();
        node = node.next;
    }
    return res;
}

最長迴文子串

5. 最長迴文子串 - 力扣(LeetCode) (leetcode-cn.com)

給你一個字串 s,找到 s 中最長的迴文子串。

輸入:s = "babad"
輸出:"bab"
解釋:"aba" 同樣是符合題意的答案。
輸入:s = "cbbd"
輸出:"bb"

思路:

我以前用Python 寫過這個題,今天重新用Java寫了一遍。再理一下思路,當時是自己想出來的這個方法,查了一下,好像這個方法還有個名字加中心擴散法。

就是遍歷字串的每個字元,並以這個字元為中心遍歷他的左右的字元,找到以這個字元為中心的最長迴文子串。若比之前記錄的要長,則更新最長迴文子串的座標。需要注意的是,中心有兩種情況,中心為一個字元,例如aba,中心為兩個字元,例如abba。我做的時候比較無腦,用了兩個指標來遍歷字串。若中心為兩個字元,即s[i] == s[i+1]則,左右兩個指標為i和i+1。若中心為一個字元,則他們兩個都等於i。這樣就可以使用統一的一個函式,而不用單獨再寫一遍。在這道題的評論區裡我找到了這個思路的優化版本,將在後面寫出來。

程式碼:

public static void center(int left, int right, String s, int[] res) {
    // 遍歷到左右不相等或遍歷完陣列
    while(left >= 0 && right < s.length()) {
        // 不相等則直接跳出迴圈
        if (s.charAt(left) != s.charAt(right)) {
            break;
        }
        left--;
        right++;
    }
    // 若得到的長度大於現有最長迴文子串的長度則更新
    // 注:left需要+1,right需要-1,所以長度為right - left -2
    if (right -left - 2 > res[1] - res[0]) {
        res[0] = left + 1;
        res[1] = right - 1;
    }
}
public static String longestPalindrome(String s) {
    int[] res = {0,0};  // 儲存最長迴文子串的其實和結束下標
    for (int i = 0; i < s.length() - 1; i++) {
        // 中心為兩個字元的情況,例如abba
        if (s.charAt(i) == s.charAt(i+1))
            center(i,i+1,s,res);
        // 無論中心是一個還是兩個,都需要執行一遍,因為有可能同時為一個和兩個,例如,abbba
        center(i, i, s, res);
    }
    return s.substring(res[0], res[1] + 1);
}

優化版本:

將中間所有相同的字元看成一個整體(一個字元)例如:abcccba堪稱,abcba。這樣一來,真正統一了中心為一個字元和兩個字元的情況,並且由於中間所有一樣的字元被看作一個整體,所以遇到一樣的字元可以直接跳過,而不用再遍歷一遍。提高了效率。

public static int center(int cen, String s, int[] res) {
    int last = cen;
    // 找到最後一個相同的字元
    while(last < s.length() - 1 && s.charAt(cen) == s.charAt(last + 1)) {
        last++;
    }
    int tmp = last; // 儲存最後一個相同字元,用於返回
    while(last < s.length() - 1 && cen > 0 && s.charAt(cen - 1) == s.charAt(last + 1)) {
        cen--;
        last++;
    }

    // 若得到的長度大於現有最長迴文子串的長度則更新
    if (last -cen > res[1] - res[0]) {
        res[0] = cen;
        res[1] = last;
    }
    return tmp;
}
public static String longestPalindrome(String s) {
    int[] res = {0,0};  // 儲存最長迴文子串的其實和結束下標
    for (int i = 0; i < s.length() - 1; i++) {
        // center函式返回了最後一個相同的字元,中間一樣的字元看成一個整體,所以跳過這些字元
        i = center(i, s, res);
    }
    return s.substring(res[0], res[1] + 1);
}

字串轉換整數

8. 字串轉換整數 (atoi) - 力扣(LeetCode) (leetcode-cn.com)

請你來實現一個 myAtoi(string s) 函式,使其能將字串轉換成一個 32 位有符號整數(類似 C/C++ 中的 atoi 函式)。

函式 myAtoi(string s) 的演算法如下:

  • 讀入字串並丟棄無用的前導空格
  • 檢查下一個字元(假設還未到字元末尾)為正還是負號,讀取該字元(如果有)。 確定最終結果是負數還是正數。 如果兩者都不存在,則假定結果為正。
  • 讀入下一個字元,直到到達下一個非數字字元或到達輸入的結尾。字串的其餘部分將被忽略。
  • 將前面步驟讀入的這些數字轉換為整數(即,"123" -> 123, "0032" -> 32)。如果沒有讀入數字,則整數為 0 。必要時更改符號(從步驟 2 開始)。
  • 如果整數數超過 32 位有符號整數範圍 [−2^31, 2^31 − 1] ,需要截斷這個整數,使其保持在這個範圍內。具體來說,小於 −2^31 的整數應該被固定為 −2^31 ,大於 2^31 − 1 的整數應該被固定為 2^31 − 1 。
  • 返回整數作為最終結果。
輸入:s = "42"
輸出:42
輸入:s = "   -42"
輸出:-42
輸入:s = "4193 with words"
輸出:4193
輸入:s = "words and 987"
輸出:0
輸入:s = "-91283472332"
輸出:-2147483648

思路:

額,乍一看,很簡單,字串轉換程數字嘛,不難,就用類似兩數相加那道題的思路,只不過加了一個提取數字。

可當寫起來才發現,這道題是真的噁心,坑超級多。

先上程式碼,再來說有哪些坑。

程式碼:

public int myAtoi(String s) {
    long res = 0;	// 結果
    int flag = 1;	// 結果是正還是負
    boolean isStart = false;	// 是否已經開始讀取數字,注:沒開始前,空白符 加減號是要對應處理的,開始後只處理數字。
    char ch;
    ArrayList<Integer> numList = new ArrayList<>();
    for (int i = 0; i < s.length(); i++) {
        ch = s.charAt(i);
        if (ch == ' ' && !isStart)
            continue;
        if ((ch == '-' || ch == '+') && !isStart) {
            isStart = true;
            flag = ch == '-' ? -1 : 1;
            continue;
        }
        isStart = true;
        if (ch >'9' || ch < '0')
            break;
        numList.add(ch - '0');
    }
    for (int i : numList) {
        res = res * 10 + i;
        if (flag * res > 2147483647) {
            res = 2147483647;
            break;
        }
        if (flag * res < -2147483648) {
            res = 2147483647;
            res += 1;
            break;
        }

    }
    res = flag * res;
    return (int)res;
}

踩的坑:

  • 看錯題目,這個怪我,不怪題噁心,只忽略前置空白符,我開始做的時候忽略了所有空白符
  • +開始的字串是正常的,例如+123,確實,合情合理,但誰字串轉數字的時候把加號寫進去啊
  • 加號和減號以及空白符都差不多,在最前面的時候,它是合法的,在後面出現就是不合法的,寫的時候沒考慮到。例如 123+456結果應該為123
  • int型溢位,正負數最大值不對稱,正數為2147483647,負數為-2147483648,需要單獨處理
  • 為了防止溢位這個問題,過程中我用了long型別來存資料,最後返回時再轉成int,但沒想到的是,測試資料裡含有使long溢位的值,嗐。

不過,通過這道題,懂得了考慮問題應該更細緻,特別是邊界問題。以及當嘗試把2147483648賦值給res的時候會報溢位錯誤,因為int型別存不下,所以需要兩步操作,開始想的是res = 1 + 2147483647,但a = b + c實際是先用一塊記憶體來存b + c的值再賦值給a,所以這樣不可行,所以改為了res = 2147483647;res += 1;這兩步操作