LeetCode刷題(1-30)

LeetCode刷起來,時間成本好高,嗚嗚。。。

1、兩數之和(哈希表)

題目:給定一個整數數組 nums 和一個目標值 target,請你在該數組中找出和爲目標值的那 兩個 整數,並返回他們的數組下標。

1、思路:

存儲數據一般能想到的就是數組、鏈表、哈希表:

數組是易於查找不易於增刪,鏈表是易於增刪不易於查找,而且在數據量很大的時候,他們的劣勢會十分明顯。對於大數據的數據存儲,適合用hash表。hash表是數組和鏈表的結合體,在一定的情況下,它能擁有數組和鏈表的優點。

想要查詢數組中的某一個數時,需要遍歷整個數組才能查到。想要查詢練哈希表的某一個數時,直接通過hash算法計算出桶下標就可以查詢出值,時間複雜度O(1)

1、由於哈希查找的時間複雜度爲 O(1),所以可以利用哈希容器 map 降低時間複雜度

2、使用map的key存放數組中的值,使用value存放數組的索引

3、遍歷數組,判斷map中是否存在target-nums[i]的key值,如果存在返回下標索引,如果不存在就添加到map中
在這裏插入圖片描述
4、時間複雜度:遍歷數組的長度爲O(n)

2、代碼:

/**
 * 給定 nums = [2, 7, 11, 15], target = 9
 *
 * 因爲 nums[0] + nums[1] = 2 + 7 = 9,所以返回 [0, 1]
 *
 *  time:O(n)
 *  space:O(n)
 */

public class Solution {
    public int[] twoSum(int[] nums, int target) {
        HashMap<Integer,Integer> map = new HashMap<>();
        for(int i=0;i<nums.length;i++){
            if(map.containsKey(target-nums[i])){
                return new int[]{map.get(target-nums[i]),i};
            }
            map.put(nums[i],i);
        }
        return null;
    }
}
/**
    time:O(n) :遍歷一遍數組
    space:O(n)
 */

2、兩數相加(鏈表)

題目:給出兩個 非空 的鏈表用來表示兩個非負的整數。其中,它們各自的位數是按照 逆序 的方式存儲的,並且它們的每個節點只能存儲 一位 數字。

如果,我們將這兩個數相加起來,則會返回一個新的鏈表來表示它們的和。您可以假設除了數字 0 之外,這兩個數都不會以 0 開頭。

1、思路

1、將兩個鏈表看成是相同長度的進行遍歷,如果一個鏈表較短則在後面補 0

2、鏈表逆序存儲數字的位數,鏈表對應位相加,如果進位表現爲往後進1(正常爲往前)

3、如果兩個鏈表全部遍歷完畢後,進位值爲1,則在新鏈表後面加節點1

小技巧:對於鏈表問題,返回結果爲頭結點時,通常需要先初始化一個預先指針 pre,該指針的下一個節點指向真正的頭結點head。使用預先指針的目的在於鏈表初始化時無可用節點值,而且鏈表構造過程需要指針移動,進而會導致頭指針丟失,無返回結果。

2、代碼

/**
 * 輸入:(2 -> 4 -> 3)
 *     + (5 -> 6 -> 4)
 * 輸出:7 -> 0 -> 8
 *
 * 原因:342 + 465 = 807
 */
class ListNode {
    int val;
    ListNode next;
    //節點類的構造函數
    ListNode(int x) {
        val = x;
    }
}

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        /**
         * 1、如果需要返回頭結點,通常預先定義一個dummy節點,該節點的next指針指向真正的頭結點
         * 2、定義兩個指針 cur1、cur2 分別用來遍歷兩個鏈表
         * 3、初始化一個變量 sum,用來存放鏈表節點相加的和
         * 4、因爲 dummy節點不能動,因此定義一個cur指向dummy,用來遍歷新鏈表
         */
        ListNode dummy = new ListNode(0);
        ListNode cur1 = l1;
        ListNode cur2 = l2;
        int sum = 0;
        ListNode cur = dummy;

        /**
         * 1、遍歷兩個鏈表
         * 2、將節點1的值與sum相加
         * 3、將節點2的值與sum相加
         * 4、將sum % 10掛在新的鏈表中,(之所以sum%10,因爲如果有進位需要將個位數掛上去,十位數進1)
         * 5、sum = sum/10,得出進位,向後進1
         * 6、鏈表循環結束後,判斷最後一位相加是否進1,如果進1,要加在鏈表最後
         */
        while (cur1!=null || cur2!=null){
            if(cur1!=null){
                sum += cur1.val;
                cur1 = cur1.next;
            }
            if(cur2!=null){
                sum += cur2.val;
                cur2 = cur2.next;
            }

            cur.next = new ListNode(sum%10);
            sum /= 10;
            cur = cur.next;
        }
        if(sum==1){
            cur.next = new ListNode(1);
        }
        return dummy.next;
    }
}

/**
    time:O(n) :兩個鏈表都遍歷一遍
    space:O(n):每次都new一個新節點,開闢了空間
 */

3、無重複字符的最長字串(滑動窗口)

題目:給定一個字符串,請你找出其中不含有重複字符的 最長子串 的長度。

/**
 * 輸入: "abcabcbb"
 * 輸出: 3
 * 解釋: 因爲無重複字符的最長子串是 "abc",所以其長度爲 3。
 *
 * 輸入: "bbbbb"
 * 輸出: 1
 * 解釋: 因爲無重複字符的最長子串是 "b",所以其長度爲 1。
 */

1、思路

使用滑動窗口算法:

1、定義兩個指針start 和end:start代表不重複字符子串的起點,end不斷向下一個窗口滑動

2、當end遇到子串中的重複字符時,就讓start指向map中該重複字符的下一個字符,就是將那個重複字符以及它前面的字符都扔掉,讓窗口繼續向下滑動,尋找更長的字串。

3、注意:對於HashMap有一個特點,鍵相同,值覆蓋。

在這裏插入圖片描述

1、通過動圖理解思路:
在這裏插入圖片描述

2、代碼

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        if(s.length()==0 || s==null){
            return 0;
        }
        //定義HashMap去重:鍵相同,值覆蓋
        HashMap<Character,Integer> map = new HashMap<>();
        int length = s.length();
        int res = 0;
        //start指向不重複字符子串的起始位置,end不斷向窗口後面移動
        for(int start=0,end=0;end<length;end++){
            //當map中包含end指向的字符時,那就從start指向map中重複字符的下一個字符
            //就是將窗口向下移動,將map中和end指向的字符重複的字符以及它之前的字符從窗口中移出。
            if(map.containsKey(s.charAt(end))){
                start = Math.max(start,map.get(s.charAt(end))+1);
            }
            //將end指向的字符添加進map,鍵相同值覆蓋
            map.put(s.charAt(end),end);
            res = Math.max(res,end-start+1);
        }
        return res;
    }
    
    /**
    	time:O(n)
    	space:O(n)
    */

4、尋找兩個正序數組的中位數(二分法,重要!)

題目:給定兩個大小爲 m 和 n 的正序(從小到大)數組 nums1 和 nums2。請你找出這兩個正序數組的中位數,並且要求算法的時間複雜度爲 O(log(m + n))。你可以假設 nums1 和 nums2 不會同時爲空。

/**
 * 示例 1:
 *
 * nums1 = [1, 3]
 * nums2 = [2] 
 * 
 * 則中位數是 2.0
 *
 * nums1 = [1, 2]
 * nums2 = [3, 4]
 *
 * 則中位數是 (2 + 3)/2 = 2.5
 */

1、思路:

1、先複習一下二分查找算法:

假設在 [left, right] 範圍內搜索某個元素 value, mid == (left+ right) / 2
◼ 如果 value < mid,去 [left, mid-1] 範圍內二分搜索
◼ 如果 value > mid,去 [mid + 1, right]範圍內二分搜索
◼ 如果 value == mid,直接返回 mid

public static int binarySearch(int[] arr,int value){
    int left = 0;
    int right = arr.length-1;
    while(left<=right){
        int mid = (left+right) >> 1;
        if(value<arr[mid]){
            right = mid-1;
        }else if(value>arr[mid]){
            left = mid+1;
        }else{
            return mid;
        }
    }
    return -1;
}

2、本題思路:

在這裏插入圖片描述

其中,N1=4,N2=6,size=4+6=10.

1、現在有的是兩個已經排好序的數組,結果是要找出這兩個數組中間的數值,如果兩個數組的元素個數爲偶數,則輸出的是中間兩個元素的平均值。

2、可以想象,如果將數組1隨便切一刀(如在3和5之間切一刀),數組1將分成兩份,數組1左別的元素個數爲1,右邊的元素的個數爲3。

由於數組1和數組2最終分成的左右兩份的個數是確定的,都是所有元素的個數的一半(size/2=5)所以我們也可以知道,此時對數組2應該切的一刀的位置應該在10和11之間,數組2左邊的個數爲4,右邊的個數爲2.才能使兩個數組左右兩邊的元素個數加起來的和(1+4=2+3)相等。

另外,我們記在數組1靠近這一刀的左別的元素爲L1(3),右邊元素爲R1(5).同理,記在數組2靠近這一刀的左別的元素爲L2(10),右邊元素爲R2(11).
如果這一刀的位置是正確的,則應該有的結果是
L1<=R2
L2<=R1
這樣就能確保,左邊的元素都小於右邊的元素了。

3、所以,我們只需要直接找出在數組1切這一刀的正確位置就可以了。
爲了減少查找次數,我們對短的數組進行二分查找。將在數組1切割的位置記爲cut1,在數組2切割的位置記爲cut2,cut2=(size/2)-cut1。
cut1,cut2分別表示的是數組1,數組2左邊的元素的個數。

4、切這一刀的結果有三種
1)L1>R2 則cut1應該向左移,才能使數組1較多的數被分配到右邊。
2)L2>R1 則cut1應該向右移,才能使數組1較多的數被分配到左邊。
3)其他情況(L1<=R2 L2<=R1),cut1的位置是正確的,可以停止查找,輸出結果。

5、其他說明
1)考慮到邊界條件,就是cut的位置可能在邊緣,就是cut1=0或者cut1=N1,cut2=0或者cut2=N2的這些情況,我們將min和max兩個特殊值分別加在數組1和數組2的兩端,就可以統一考慮了。還有N1個數爲0的時候,直接輸出結果即可。

2)爲了減少查找時間,使用的是二分查找,就是cut1的位置是一半一半的查找的,實現時間只要log(N),不然就會超時。所以,我們不能只是簡單地將cut1–或者cut1++,而是要記下每次cut1的區域範圍,我們將cut1的範圍記錄下來,用[cutL,cutR]表示。一開始cut1的範圍是[cutL,cutR]=[0,N1],
如果L1>R2 則cut1應該向左移,才能使數組1較多的數被分配到右邊。cut1的範圍就變成了[cutL,cut1-1],下次的cut1的位置就是cut1 = (cutR - cutL) / 2 + cutL;。
如果L2>R1 則cut1應該向右移,才能使數組1較多的數被分配到左邊。cut1的範圍就變成了[cut1+1,cutR],下次的cut1的位置就是cut1 = (cutR - cutL) / 2 + cutL;。

3)數組的元素個數和是奇數的情況下,中間的元素應該就是min(R1,R2),只需另外處理輸出就可以了。

2、代碼

public class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {

        //爲了減少查找次數,對短的數組進行二分查找,查找切一刀的位置
        if(nums1.length>nums2.length){
            return findMedianSortedArrays(nums2,nums1);
        }

        //根據length計算:cut2 = length/2-cut1
        int length = nums1.length+nums2.length;
        //數組1刀口左邊的元素的個數
        int cut1 = 0;
        //數組2刀口左邊元素的個數
        int cut2 = 0;

        /**
         * 時間複雜度出現了log,二分查找,需要更新二分查找的範圍
         * 我們要查找的是那個刀口的位置在哪:[cutR,cutL]代表刀口cut1要二分查找的範圍
         */
        int cutL = 0;
        int cutR = nums1.length;

        //進行二分查找算法:
        while (cut1<=nums1.length){
            cut1 = (cutR-cutL)/2 + cutL;
            cut2 = length/2-cut1;

            /**
             * 1、求出L1(cut1左側的元素)、R1(cut1元素)、L2(cut1左側的元素)、R2(cut2元素)
             * 2、邊界條件,cut的位置可能在邊緣:
             		cut1=0、cut1=nums1.length、cut2=0、cut2=nums2.length的情況
             * 3、將min和max兩個特殊值分別加在數組1和數組2的兩端,就可以了
             */
            double L1 = (cut1==0) ? Integer.MIN_VALUE : nums1[cut1-1];
            double L2 = (cut2==0) ? Integer.MIN_VALUE : nums2[cut2-1];
            double R1 = (cut1==nums1.length) ? Integer.MAX_VALUE : nums1[cut1];
            double R2 = (cut2==nums2.length) ? Integer.MAX_VALUE : nums2[cut2];

            //判斷查找到的這個刀口是否合理,若不合理更新二分查找的範圍

            /**
             * 注意這裏並不是讓cut1--/cut++:
             * 1、如果L1>R2 則cut1應該向左移,才能使數組1較多的數被分配到右邊。
             *    cut1的範圍就變成了[cutL,cut1-1],下次的位置是cut1 = (cutR - cutL) / 2 + cutL
             *
             * 2、如果L2>R1 則cut1應該向右移,才能使數組1較多的數被分配到左邊。
             *    cut1的範圍就變成了[cut1+1,cutR],下次的位置是cut1 = (cutR - cutL) / 2 + cutL
             *
             * 3、cut1的位置是正確的(L1<=R2 L2<=R1),可以停止查找,輸出結果。
             */
            if(L1>R2){
                cutR = cut1-1;
            }else if(L2>R1){
                cutL = cut1+1;
            }else{
                //如果數組的總長度爲偶數
                if(length%2==0){
                    /**
                     * 取出L1、L2中的最大值,這個值在兩個數組排序後最靠近刀口
                     * 取出R1、R2中的最大值,這個值在兩個數組排序後最靠近刀口
                     */
                    L1 = (L1>L2 ? L1:L2);
                    R1 = (R1<R2 ? R1:R2);
                    return (L1+R1)/2;
                }else{
                    //如果數組的總長度爲奇數,那麼中位數一定爲R1、R2中的最小值
                    R1 = (R1 < R2 ? R1 : R2);
                    return R1;
                }
            }

        }
        //沒有找到
        return -1;
    }
}

/**
time:log(min(m,n))
sapce:O(1),僅在遞歸時使用了一個棧空間
*/

5、最長迴文子串(dp)

題目:給定一個字符串 s,找到 s 中最長的迴文子串。你可以假設 s 的最大長度爲 1000。

/**
 * 輸入: "babad"
 * 輸出: "bab"
 * 注意: "aba" 也是一個有效答案。
 *
 * 輸入: "cbbd"
 * 輸出: "bb"
 */

1、思路1(dp)

1、轉移狀態:設dp 爲二維數組,其中dp[i][j]代表字符串s 從下標i 到下表j的子串

​ 如果dp[i,j]是迴文子串,那麼dp[i,j]=true,否則dp[i,j]=false
在這裏插入圖片描述
2、轉移方程:dp(i, j) = (dp(i + 1, j - 1) && s[i] == s[j])

如果dp(i + 1, j - 1) 是迴文子串並且s[i]==s[j],就說明是dp(i,j)迴文子串
在這裏插入圖片描述

我們說對於一個題分析能不能用動態規劃,不可能一下就看出轉移方程了,應該怎麼分析?

可以考慮從前分析確定初始條件,從後分析確定前面一步和當前步有什麼關係。

(1)先寫一個比較長的迴文子串:
在這裏插入圖片描述

(2)如果添加一個使這個字符串是迴文子串它應該滿足什麼條件? s[i] == s[j]

在這裏插入圖片描述

(3)第n-1步如果是迴文子串,怎麼表示?dp(i + 1, j - 1)

3、轉移邊界條件:

要滿足(i+1)-(j-1)>=0 即這個範圍內至少要有0個字符(爲0時指同一個字符,爲1時代表兩個字符),

才能使用dp[i-1,j+1],即j-i<2

4、返回值:s.subString(i,j+1)

2、代碼1

假如說外層遍歷到最後一個字符j=s.length-1

內層開始遍歷,那麼dp[i,j]就是判斷(i,j)這段範圍內的字符是否是迴文子串,這樣所有情況都能考慮到
在這裏插入圖片描述

class Solution {
    public String longestPalindrome(String s) {
        //1、進行空值判斷
        if(s.length()==0 || s==null){
            return  s;
        }
        //2、初始化變量
        int length = s.length();
        String res = "";
        int max = 0;
        //3、dp[i][j]代表(i,j)範圍內的字符串是否是迴文子串
        boolean[][] dp = new boolean[length][length];

        for(int j=0;j<length;j++){
            //i<=j,當i=j時代表同一個字符
            for(int i=0;i<=j;i++){
                dp[i][j] = (s.charAt(i)==s.charAt(j)) && ((j-i<=2) || dp[i+1][j-1]);
                //如果是迴文子串
                if(dp[i][j]){
                    //如果迴文子串的長度>max
                    if(j-i+1>max){
                        max = j-i+1;
                        res = s.substring(i,j+1);
                    }
                }
            }
        }
        return res;
    }
}

/**
 * time:雙層for循環,爲O(n2)
 * space:因爲開闢了二維數組,爲O(n2)
 */

3、思路2(中心擴散)

迴文串一定是對稱的,所以我們可以每次循環選擇一箇中心,進行左右擴展,判斷左右字符是否相等即可。

由於存在奇數的字符串和偶數的字符串,所以我們需要從一個字符開始擴展,或者從兩個字符之間開始擴展
在這裏插入圖片描述

4、代碼2

很明顯,代碼2更好理解:

public class Solution2 {
    String res = "";
    public String longestPalindrome(String s) {
        //1、空值判斷
        if(s.length()==0 || s==null) return s;

        //2、遍歷字符串
        for(int i=0;i<s.length();i++){
            //字符串的長度爲奇數時,以一個爲中心字符開始擴散
            help(s,i,i);
            //字符串的長度爲偶數時,以兩個字符爲中心開始擴散
            help(s,i,i+1);
        }
        return res;
    }

    private String help(String s, int left, int right) {
        //如果判斷成立,就讓left向左移動,讓right向右移動,向兩端擴散比較是否相等
        while (left>=0 && right<s.length() && s.charAt(left)==s.charAt(right)){
            left--;
            right++;
        }
        String cur = s.substring(left+1,right);
        if(cur.length()>res.length()){
           res = cur;
        }
        return res;
    }
}
/**
 * 	time:O(n2)
 *  space:O(1)
 */

6、Z字型變換(瞭解)

題目:將一個給定字符串根據給定的行數,以從上往下、從左到右進行 Z 字形排列。

比如輸入字符串爲 “LEETCODEISHIRING” 行數爲 3 時,排列如下:

在這裏插入圖片描述

1、思路

考查數學的找規律:

1、使用StringBuilder將每一行最終顯示的數據拼接起來

2、

在這裏插入圖片描述
3、

在這裏插入圖片描述

2、代碼

class Solution {
    public String convert(String s, int numRows) {

        if(numRows < 2) return s;

        //1、初始化StringBuilder
        StringBuilder[] sb = new StringBuilder[numRows];
        for(int i=0;i<sb.length;i++){
            sb[i] = new StringBuilder("");
        }

        //2、通過數學規律拼接字符
        for(int i=0;i<s.length();i++){
            int index = i % (2* numRows-2);
            index = index<numRows ? index : 2*numRows-2-index;
            sb[index].append(s.charAt(i));
        }

        //3、將各個循環通過sb[0]拼接起來
        for(int i=1;i<sb.length;i++){
            sb[0].append(sb[i]);
        }
        return sb[0].toString();
    }
}

7、整數反轉(取模、求商)

題目:給出一個 32 位的有符號整數,你需要將這個整數中每位上的數字進行反轉。

假設我們的環境只能存儲得下 32 位的有符號整數,則其數值範圍爲 [−231, 231 − 1]。請根據這個假設,如果反轉後整數溢出那麼就返回 0。

/**
 * 輸入: 123
 * 輸出: 321
 *
 * 輸入: -123
 * 輸出: -321
 *
 * 輸入: 120
 * 輸出: 21
 */

1、思路

1、對於一個多位整數,使用求模+求餘結合的方式取出他的每一位數:

1234:如何取出1234 ?
4----: 1234 % 10 = 41234/10=123
3----: 123 % 10 = 3123/10=12
2----: 12 % 10 = 212/10=1
1----: 1 % 10 = 11/10=0

2、取出每位數之後如何將各位數反轉組合爲一個新的整數?

res = 0; 每次取模的結果*10再加上下次取模的結果
1----:res = res * 10 + 1234 % 10 = 41234/10=123
3----:res = res * 10 + 123 % 10 = 43123/10=12
2----:res = res * 10 + 12 % 10 = 43212/10=1
1----:res = res * 10 + 1 % 10 = 43211/10=0

3、整數存儲範圍爲是[-231,231],當x過大時,反轉後result會出現溢出:

例如x=1534236469,反轉後result應爲9646324351,但Integer.MAX_VALUE=2147483647,導致溢出,最終result=1056389759,不是我們想要的結果,所以使用long來保存結果,判斷溢出時,返回result=0。這是解決內存溢出的常用解決方案。

2、代碼

class Solution {
    public int reverse(int x) {
        //防止整型溢出,因此使用long來保存result,判斷溢出時,返回0
        long res = 0;
        while(x!=0){
            res = res*10 + x%10;
            x /= 10;
            if(res>Integer.MAX_VALUE || res<Integer.MIN_VALUE){
                return 0;
            }
        }
        return (int)res;
    }
}
/**
 * time:O(n) 只有一個while循環
 * space:O(1)
 */

8、字符串轉成整數

題目:請你來實現一個 atoi 函數,使其能將字符串轉換成整數。

首先,該函數會根據需要丟棄無用的開頭空格字符,直到尋找到第一個非空格的字符爲止。接下來的轉化規則如下:

如果第一個非空字符爲正或者負號時,則將該符號與之後面儘可能多的連續數字字符組合起來,形成一個有符號整數。
假如第一個非空字符是數字,則直接將其與之後連續的數字字符組合起來,形成一個整數。
該字符串在有效的整數部分之後也可能會存在多餘的字符,那麼這些字符可以被忽略,它們對函數不應該造成影響。

注意:假如該字符串中的第一個非空格字符不是一個有效整數字符、字符串爲空或字符串僅包含空白字符時,則你的函數不需要進行轉換,即無法進行有效轉換。

在任何情況下,若函數不能進行有效的轉換時,請返回 0 。

提示:

本題中的空白字符只包括空格字符 ' ' 。
假設我們的環境只能存儲 32 位大小的有符號整數,那麼其數值範圍爲 [−2^31,  2^31 − 1]。如果數值超過這個範圍,請返回 INT_MAX (2^31 − 1) 或 INT_MIN (−2^31) 。

1、思路

在這裏插入圖片描述

1、首位空格:字符串前面如果有空格,使用trim()函數去除

2、只包含空格:return 0;

2、符號位:3種情況,即 ‘’+’’ , ‘‘−’’ , ''無符號",新建一個變量保存符號位,返回前判斷正負。

3、數字位:

​ (1)將字符轉換成數字:此 “數字字符的ASCII” 與 “0的ASCII相減”

​ (2)將字符進行拼接res = 10*res-ascii©-ascii(0);

2、代碼

class Solution {
    public int myAtoi(String str) {
        //字符串爲空
        if(str.length()== 0|| str == null) return 0;

        //1、使用trim()函數去除首部空格
        str = str.trim();

        //2、字符串僅包含空白字符時,不能轉換返回0
        if(str.length()==0){
            return 0;
        }

        //符號位,初始化爲1,代表是'+'
        int sign = 1;
        //如果是有符號位,那麼start = 1,如果是無符號位start = 0;
        int start = 0;
        //爲了防止字符串中的字符拼接起來造成內存溢出,使用long來保存結果res
        long res = 0;

        //3、取出字符串的第一個字符,判斷符號位
        char firstChar = str.charAt(0);
        if(firstChar == '+'){
            sign=1;
            start ++;
        }else if(firstChar == '-'){
            sign = -1;
            start++;
        }

        //4、遍歷字符串
        for(int i=start;i<str.length();i++){
            //判斷是否是數字
            if(!Character.isDigit(str.charAt(i))){
                return (int) res*sign;
            }
            //將字符轉換爲數字並拼接起來
            res = res*10 + (str.charAt(i)-'0');

            //判斷是否內存溢出,就是結果是否超過Integer的最大值
            if( sign ==1 && res>Integer.MAX_VALUE) return Integer.MAX_VALUE;
            if(sign==-1 && res>Integer.MAX_VALUE) return Integer.MIN_VALUE;
        }
        return (int) res*sign;
    }
}
/**
     time:O(n)
     space:O(1)
 */

9、迴文數(取模、求商)

題目:判斷一個整數是否是迴文數。迴文數是指正序(從左向右)和倒序(從右向左)讀都是一樣的整數。

* 輸入: 121
* 輸出: true
*
* 輸入: -121
* 輸出: false
* 解釋: 從左向右讀, 爲 -121 。 從右向左讀, 爲 121- 。因此它不是一個迴文數。
*
* 輸入: 10
* 輸出: false
* 解釋: 從右向左讀, 爲 01 。因此它不是一個迴文數。

1、思路

由示例可以看出:

1、如果這個數時負數,一定不是迴文數

2、如果這個是10、100、1000等這種類型,其最後一位是0,那麼第一位也一定爲0纔是迴文串,只有0滿足

3、如果一個數時迴文串,那麼它反轉之後應該和這個數相同,需要對這個整數進行反轉:res*10+x%10,x/10

2、代碼

class Solution {
    public boolean isPalindrome(int x) {
        //1、如果這個數時負數,或者這個數爲10、100、1000等數
        if(x<0 || (x%10==0 && x!=0)){
            return false;
        }

        //2、先用一個數保存x的值
        int tmp = x;

        //因爲沒有提到內存溢出的情況,所以使用int存儲即可,否則是需要使用long的
        int rev = 0;

        //3、對這個整數進行反轉
        while (x>0){
            rev = rev*10 + x%10;
            x /= 10;
        }

        //4、判斷反轉前後兩個數是否形同
        return tmp==rev;
    }
}
// time:O(n)
// space :O(1)

10、正則表達式匹配(dp)

題目:給你一個字符串 s 和一個字符規律 p,請你來實現一個支持 '.''*' 的正則表達式匹配

'.' 匹配任意單個字符
'*' 匹配零個或多個前面的那一個元素

所謂匹配,是要涵蓋 整個字符串 s的,而不是部分字符串。

說明:

s 可能爲空,且只包含從 a-z 的小寫字母。
p 可能爲空,且只包含從 a-z 的小寫字母,以及字符 . 和 *。

示例:

輸入:
s = "aa"
p = "a"
輸出: false
解釋: "a" 無法匹配 "aa" 整個字符串。

輸入:
s = "aa"
p = "a*"
輸出: true
解釋: 因爲 '*' 代表可以匹配零個或多個前面的那一個元素, 在這裏前面的元素就是 'a'。因此,字符串 "aa" 可被視爲 'a' 重複了一次。

輸入:
s = "ab"
p = ".*"
輸出: true
解釋: ".*" 表示可匹配零個或多個('*')任意字符('.')。

輸入:
s = "aab"
p = "c*a*b"
輸出: true
解釋: 因爲 '*' 表示零個或多個,這裏 'c' 爲 0 個, 'a' 被重複一次。因此可以匹配字符串 "aab"。

輸入:
s = "mississippi"
p = "mis*is*p*."
輸出: false

1、思路

使用動態規劃來做,剛開始做起來感覺很難,答案都很複雜,但是想明白了也就好理解了。

dp[i][j] 表示 s 的前 i 個字符串是否能被 p 的前 j個字符串匹配。

動態規劃思考的時候都是利用第n-1步來推導第n步,進而推導狀態方程。

怎麼想轉移方程?首先想的時候從已經求出了 dp[i-1][j-1] 入手,再加上已知 s[i]p[j],要想的問題就是怎麼去求 dp[i][j]

已知 dp[i-1][j-1] 意思就是前面子串都匹配上了,不知道新的一位的情況。

那就分情況考慮,所以對於新的一位 p[j] s[i] 的值不同,要分情況討論:

1、假如說最後 p[j] = s[i] ,就是說新加的一位相等,那麼狀態仍爲之前的狀態:dp[i][j] = dp[i-1][j-1]
2、因爲s可能爲空,且只包含從 a-z 的小寫字母。p可能爲空,且只包含從 a-z 的小寫字母,以及字符 . 和 *。
  從 p[j] 可能的情況來考慮,讓 p[j]=各種能等於的東西。
 	1、p[j]='.':此時p[j]可以作爲任意一個字符,狀態仍爲之前的狀態:dp[i][j] = dp[i-1][j-1]
 	2、p[j]='*':這各需要分情況討論:
		1、p[j-1] != s[i],讓'*'前面字符消失,狀態爲:dp[i][j] = dp[i][j-2];
		2、p[j-1] = s[i] || p[j-1] = '.'
			1、假如 * 匹配前面的一個字符,dp[i][j] = dp[i][j-1]
				aa ----> aa
				a* ----> a
			2、假如 * 匹配前面的兩個字符,dp[i][j] = dp[i-1][j]
				aa ----> aa
				a* ----> aa (左對角線的已經相等,因此需要右對角線也相等)
			3、假如'*'前面的是'.',就是'.*'可以匹配任意字符,不用考慮,因此dp[i][j] = dp[i][j-2]

說明:
'*' 的含義是 匹配零個或多個前面的那一個元素,所以要考慮他前面的元素 p[j-1]。
'*' 跟着他前一個字符走,前一個能匹配上 s[i],* 纔能有用,
前一個都不能匹配上 s[i],* 也無能爲力,只能讓前一個字符消失,也就是匹配 0次前一個字符

2、代碼

public class Solution {
    public boolean isMatch(String s, String p) {
        /**
         * dp[i][j] 表示 s 的前 i 個字符串是否能被 p 的前 j 個字符串匹配。
         */
       boolean[][] dp = new boolean[s.length()+1][p.length()+1];
       //初始化dp[0][0],不存放任何數據
       dp[0][0] = true;
       //ab---'c*ab':c*刪除,這種情況後面無法考慮因此需要先處理這種情況
       for(int i=0;i<p.length();i++){
           if(p.charAt(i)=='*' && dp[0][i-1] ){
               dp[0][i+1] = true;
           }
       }

       for(int i=0;i<s.length();i++){
           for(int j=0;j<p.length();j++){
               
               //p,s新增加的一位字符相等,那就不用考慮了,直接等於上一個狀態
               if(p.charAt(j) == s.charAt(i)){
                   dp[i+1][j+1] = dp[i][j];
               }
               
               //p新增的一位字符爲'.',那麼可以爲任意字符,直接等於上一個狀態
               if(p.charAt(j) == '.'){
                   dp[i+1][j+1] = dp[i][j];
               }

               //p新增的一位字符爲'*' 分情況考慮:
               if(p.charAt(j) == '*'){
                   //1、如果'*'的前一個字符不等於s[i],'*'匹配0個前一個字符,將前一個字符消除掉
                   if(p.charAt(j-1) != s.charAt(i) && p.charAt(j-1)!='.'){
                       dp[i+1][j+1] = dp[i+1][j-1];
                   }else {
                       //2、如果'*'的前一個字符等於s[i],分情況考慮:
                       //如果*匹配一個前一個字符
                       //如果*匹配兩個前一個字符
                       //如果*的前一個字符爲'.'
                       dp[i+1][j+1] = dp[i+1][j] || dp[i][j+1] || dp[i+1][j-1];
                   }
               }
           }
       }
        return dp[s.length()][p.length()];
    }
}

/**
 * time:O(n^2) 雙層for循環
 * space:O(n^2) 二維數組
 */

11、盛最多水的容器(雙指針)

**題目:**給你 n 個非負整數 a1,a2,…,an,每個數代表座標中的一個點 (i, ai) 。在座標內畫 n 條垂直線,垂直線 i 的兩個端點分別爲 (i, ai) 和 (i, 0)。找出其中的兩條線,使得它們與 x 軸共同構成的容器可以容納最多的水。

說明:你不能傾斜容器,且 n 的值至少爲 2。

在這裏插入圖片描述

圖中垂直線代表輸入數組 [1,8,6,2,5,4,8,3,7]。在此情況下,容器能夠容納水(表示爲藍色部分)的最大值爲 49

1、思路:

分治法、動態規劃都用不上,要想得到 O(n)的解法只有使用雙指針一條路。雙指針法平時遇到過,但是自己只有用的多才能想到慢慢積累吧。

這一題的題意就是求兩根柱子圍起來的面積的最大值。

1、在一開始,我們考慮相距最遠的兩個柱子所能容納水的面積。水的寬度是兩根柱子之間的距離 ;水的高度取決於兩根柱子之間較短的那個。設每一狀態下水槽面積爲S(i,j),(0<=i<j<n),由於水槽的實際高度由兩板中的短板決定,則可得面積公式 S(i,j)=min(h[i],h[j])×(j−i)

2、在每一個狀態下,無論長板或短板收窄 1格,都會導致水槽 底邊寬度−1:

若向內移動長板,水槽的短板 min(h[i],h[j])不變或變小,下個水槽的面積一定小於當前水槽面積。(因爲面積取決於短板)

若向內移動短板,水槽的短板 min(h[i],h[j])可能變大,因此水槽面積 S(i,j)可能增大,因此向內收窄短板就可以獲取面積最大值。

2、代碼:

class Solution {
    public int maxArea(int[] height) {
        //數組存放的值是每個柱子的高度

        //定義一個變量存放兩根柱子圍成的面積
        int area = 0;
        //定義res存放柱子圍成面積的最大值
        int res = 0;
        //定義一個指針指向最左邊的柱子
        int left  = 0;
        //定義一個指針指向最右邊的柱子
        int right = height.length-1;
        
        while (left<right){
            area = (right-left)*Math.min(height[left],height[right]);
            res = Math.max(res,area);
            if(height[left]<height[right]){
                left++;
            }else{
                right--;
            }
        }
        return res;
    }
}
/**
time:O(n)
space:O(1)
*/

13、羅馬數字轉整數(哈希表)

**題目:**羅馬數字包含以下七種字符: I, V, X, L,C,D 和 M。

字符 數值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000

例如, 羅馬數字 2 寫做 II ,即爲兩個並列的 1。12 寫做 XII ,即爲 X + II 。 27 寫做 XXVII, 即爲 XX + V + II 。

通常情況下,羅馬數字中小的數字在大的數字的右邊。但也存在特例,例如 4 不寫做 IIII,而是 IV。數字 1 在數字 5 的左邊,所表示的數等於大數 5 減小數 1 得到的數值 4 。同樣地,數字 9 表示爲 IX。這個特殊的規則只適用於以下六種情況:

I 可以放在 V (5) 和 X (10) 的左邊,來表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左邊,來表示 40 和 90。 
C 可以放在 D (500) 和 M (1000) 的左邊,來表示 400 和 900。

給定一個羅馬數字,將其轉換成整數。輸入確保在 1 到 3999 的範圍內。

1、思路:

這個題面試也考的挺多的,而且這種思想是經常用的,用HashMap來存儲鍵值對,列出所有可能的鍵值對並判斷

1、字符對應數字,首先將所有的組合的可能性列出,並添加到哈希表中

2、然後對字符串進行遍歷,由於組合只有兩種,一種是 1 個字符,一種是 2 個字符,其中 2 個字符優先於 1 個字符

3、先判斷兩個字符的組合在哈希表中是否存在,存在則將值取出加到結果 ans 中,並向後移2個字符。

​ 不存在則將判斷當前 1 個字符是否存在,存在則將值取出加到結果 ans 中,並向後移 1 個字符

4、返回res即可。

2、代碼:

    public int romanToInt(String s) {
        HashMap<String,Integer> map = new HashMap<>();
        //向哈希表中存放數據的時候,值一定要從小到大存入,否則會報空指針異常
        map.put("I", 1);
        map.put("IV", 4);
        map.put("V", 5);
        map.put("IX", 9);
        map.put("X", 10);
        map.put("XL", 40);
        map.put("L", 50);
        map.put("XC", 90);
        map.put("C", 100);
        map.put("CD", 400);
        map.put("D", 500);
        map.put("CM", 900);
        map.put("M", 1000);

        int res = 0;
        //遍歷字符串
        for(int i=0;i<s.length();){
            //截取字符串中的兩個字符,判斷是否在map中存在,如果存在就取出對應的值
            if(i+1<s.length() && map.containsKey(s.substring(i,i+2))){
                res += map.get(s.substring(i, i + 2));
                i+=2;
            }else{
                //否則就截取一個字符,取出對應的值
                res += map.get(s.substring(i,i+1));
                i++;
            }
        }
        return res;
    }
}

14、最長公共前綴(字符串)

**題目:**編寫一個函數來查找字符串數組中的最長公共前綴。如果不存在公共前綴,返回空字符串 ""

示例 1:

輸入: ["flower","flow","flight"]
輸出: "fl"

示例 2:

輸入: ["dog","racecar","car"]
輸出: ""
解釋: 輸入不存在公共前綴。

1、思路:

先來看indexOf()函數的用法:

  • public int indexOf(int ch): 返回指定字符在字符串中第一次出現處的索引,如果此字符串中沒有這樣的字符,則返回 -1。
  • int indexOf(String str): 返回指定字符串在字符串中第一次出現處的索引,如果此字符串中沒有這樣的字符串,則返回 -1。
public class Test {
    public static void main(String args[]) {
        String Str = new String("www.runoob.com");
        String SubStr1 = new String("runoob");
        String SubStr2 = new String("com");
 
        System.out.print("查找字符 o 第一次出現的位置 :" );
        System.out.println(Str.indexOf( 'o' ));
       
        System.out.print("子字符串 SubStr1 在 str 中第一次出現的位置:" );
        System.out.println(Str.indexOf( SubStr1 )); //4
      
        System.out.print("子字符串 SubStr2 在 str 中第一次出現的位置 :" );
        System.out.println(Str.indexOf( SubStr2 )); //11
    }
}

注意這兒是求最長公共前綴,也就是說這些字符串的前綴是相同的。如果子串在字符串中返回結果一定爲0

2、代碼:

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if(strs==null || strs.length==0){
            return "";
        }

        //假設第一個字符串就是最長公共前綴
        String res = strs[0];
        //遍歷字符串數組
        for(int i=1;i<strs.length;i++){
            //最長公共前綴子串res如果找到了,一定在strs[i]的起始位置,就是index=0的位置
            //如果沒有找到就不爲0
            while (strs[i].indexOf(res) !=0 ){
                res = res.substring(0,res.length()-1);
            }
        }
        return res;
    }
}

15、三數之和(數組–雙指針)

**題目:**給你一個包含 n 個整數的數組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?請你找出所有滿足條件且不重複的三元組。

注意:答案中不可以包含重複的三元組。

示例:
給定數組 nums = [-1, 0, 1, 2, -1, -4],

滿足要求的三元組集合爲:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

1、思路:

1、首先對數組進行排序,排序後固定一個數 nums[i],再使用左右指針指向 nums[i]後面的兩端,數字分別爲 nums[L] 和 nums[R],計算三個數的和 sum判斷是否滿足爲 0,滿足則添加進結果集

2、如果 nums[i] == nums[i−1],則說明該數字重複,會導致結果重複,跳過

3、當 sum== 0 時,nums[L] == nums[L+1] 則會導致結果重複,應該跳過,L++
當 sum == 0 時,nums[R] == nums[R−1]則會導致結果重複,應該跳過,R−−

4、時間複雜度:O(n^2),n爲數組長度

在這裏插入圖片描述

2、代碼:

class Solution {
    public static List<List<Integer>> threeSum(int[] nums) {
        
        List<List<Integer>> ans = new ArrayList();

        //先排序,排序後可以去重
        Arrays.sort(nums);

        //固定三個數中的一個數,nums[i]
        for (int i = 0; i < nums.length ; i++) {
            
            //如果nums[i]==nums[i-1] ,說明重複,跳過nums[i-1]
            if(i > 0 && nums[i] == nums[i-1]) continue; // 去重

            //定義左右指針,分別指向nums[i]後面數的兩端
            int L = i+1;
            int R = nums.length-1;

            //遍歷,判斷三數之和
            while(L < R){
                int sum = nums[i] + nums[L] + nums[R];
                if(sum == 0){
                    ans.add(Arrays.asList(nums[i],nums[L],nums[R]));
                    while (L<R && nums[L] == nums[L+1]) L++; // 去重
                    while (L<R && nums[R] == nums[R-1]) R--; // 去重
                    L++;
                    R--;
                } else if (sum < 0) {
                    L++;
                } else if (sum > 0) {
                    R--;
                }
            }
        }
        return ans;
    }
}

16、最接近的三數之和(數組–雙指針)

**題目:**給定一個包括 n 個整數的數組 nums 和 一個目標值 target。找出 nums 中的三個整數,使得它們的和與 target 最接近。返回這三個數的和。假定每組輸入只存在唯一答案。

例如,給定數組 nums = [-1,2,1,-4], 和 target = 1.

與 target 最接近的三個數的和爲 2. (-1 + 2 + 1 = 2).

1、思路:

本題和上一題基本差不多:

本題目因爲要計算三個數,如果靠暴力枚舉的話時間複雜度會到 O(n3),需要降低時間複雜度
0、首先進行數組排序,時間複雜度 O(nlogn)
1、在數組 nums 中,進行遍歷,每遍歷一個值利用其下標i,形成一個固定值 nums[i]
2、再使用前指針指向 start = i + 1 處,後指針指向 end = nums.length - 1 處,也就是結尾處
3、根據 sum = nums[i] + nums[start] + nums[end] 的結果,判斷 sum 與目標 target 的距離,如果更近則更新結果 res
4、同時判斷 sum 與 target 的大小關係,因爲數組有序,如果 sum > target 則 end–,如果 sum < target 則 start++,如果 sum == target 則說明距離爲 0 直接返回結果

整個遍歷過程,固定值爲 n 次,雙指針爲 n 次,時間複雜度爲O(n^2)
總時間複雜度:O(nlogn) + O(n^2) = O(n^2)

2、代碼:

public class Solution {
    public int threeSumClosest(int[] nums, int target) {
        //初始化一個res
        int res = nums[0]+nums[1]+nums[2];
        
        //對數組進行排序
        Arrays.sort(nums);

        //遍歷nums,固定一個nums[i]
        for(int i=0;i<nums.length;i++){
            int left = i+1;
            int right = nums.length-1;
            while(left<right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum < target) {
                    left++;
                } else {
                    right--;
                }
                //判斷sum和res誰更接近target
                if (Math.abs(target - sum) < Math.abs(target - res)) {
                    res = sum;
                }
            }
        }
        return res;
    }
}

18、四數之和(數組–雙指針)

**題目:**給定一個包含 n 個整數的數組 nums 和一個目標值 target,判斷 nums 中是否存在四個元素 a,b,c 和 d ,使得 a + b + c + d 的值與 target 相等?找出所有滿足條件且不重複的四元組。

注意:答案中不可以包含重複的四元組。

示例:

給定數組 nums = [1, 0, -1, 0, -2, 2],和 target = 0。

滿足要求的四元組集合爲:
[
  [-1,  0, 0, 1],
  [-2, -1, 1, 2],
  [-2,  0, 0, 2]
]

1、思路:

和三數之和解法相同,沒什麼大的區別,不同的是,三數之和需要固定一個數,然後使用左右指針,但是四數之和需要固定兩個數然後使用左右指針。

2、代碼:

public class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> list = new ArrayList<>();
        if(nums.length<4) return list;

        //對數組進行排序
        Arrays.sort(nums);

        //遍歷數組,固定一個數
        for(int i=0;i<nums.length;i++){
            //需要去重
            if(i>0 && nums[i] == nums[i-1]) continue;
            for(int j=i+1;j<nums.length-1;j++){
                //需要去重
                if(j>i+1 && nums[j]== nums[j-1]) continue;
                //定義左指針
                int left=j+1;
                //定義右指針
                int right = nums.length-1;
                while (left<right){
                    int sum = nums[i]+nums[j]+nums[left]+nums[right];
                    if(sum==target){
                        list.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
                        //去重
                        while (left<right && nums[left]==nums[left+1]) left++;
                        while (left<right && nums[right]==nums[right-1]) right--;
                        left++;
                        right--;
                    }else if(sum<target){
                        left++;
                    }else{
                        right--;
                    }
                }
            }
        }
        return list;
    }
}
/**
 * time:O(n^3)+O(nlogn)=O(n^3)
 * time:O(n)
 */

19、 刪除鏈表的倒數第N個節點(快慢指針)

**題目:**給定一個鏈表,刪除鏈表的倒數第 n 個節點,並且返回鏈表的頭結點。

示例:

給定一個鏈表: 1->2->3->4->5, 和 n = 2.

當刪除了倒數第二個節點後,鏈表變爲 1->2->3->5.

1、思路

整體思路是讓前面的指針先移動n步,之後前後指針共同移動直到前面的指針到尾部爲止
1、首先設立預先指針dummy
2、設預先指針dummy 的下一個節點指向 head,設前指針爲 fast,後指針爲 slow,二者都等於dummy
3、fast 先向前移動n步,之後 fast 和 slow 共同向前移動,此時二者的距離爲 n,當 fast到尾部時,slow 的位置恰好爲倒數第 n 個節點
4、因爲要刪除該節點,所以要移動到該節點的前一個才能刪除,所以循環結束條件爲 fast.next != null
5、刪除後返回dummy.next,爲什麼不直接返回 head 呢,因爲 head 有可能是被刪掉的點
時間複雜度:O(n)

在這裏插入圖片描述

2、代碼:

public class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        //定義一個節點dummy指向頭結點,防止head被刪了
        ListNode dummy = new ListNode(0);
        dummy.next = head;

        ListNode fast = dummy;
        ListNode slow = dummy;
        
        //讓快指針先走n步
        for(int i=0;i<n;i++){
            fast = fast.next;
        }
        
        while (fast.next!=null){
            fast = fast.next;
            slow = slow.next;
        }
        //刪除slow的下一個節點,讓slow.next指向slow.next.next
        slow.next = slow.next.next;
        return dummy.next;
    }
}

20、有效的括號(棧)

**題目:**給定一個只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判斷字符串是否有效。

有效字符串需滿足:

左括號必須用相同類型的右括號閉合。
左括號必須以正確的順序閉合。

注意空字符串可被認爲是有效字符串。

輸入: "()"
輸出: true

輸入: "()[]{}"
輸出: true

輸入: "([)]"
輸出: false

輸入: "{[]}"
輸出: true

1、思路:

1、如果遇到'(',就向棧中壓入')'

2、如果遇到'[',就向棧中壓入']'

3、如果遇到'{',就向棧中壓入'}'

4、如果以上條件都不滿足,說明要麼是')'要麼是']'要麼是'}',那就從棧中彈出一個字符看是否與該字符相同,如果不相同返回false

在這裏插入圖片描述

2、代碼:

public class Solution {
    public boolean isValid(String s) {
        if(s.length()==0 || s==null) return true;

        Stack<Character> stack = new Stack<>();

        //將字符串裝換爲字符數組
        char[] charArray = s.toCharArray();
        //遍歷這個字符數組
        for(int i=0;i<charArray.length;i++){
            if(charArray[i]=='('){
                stack.push(')');
            }else if(charArray[i]=='['){
                stack.push(']');
            }else if(charArray[i]=='{'){
                stack.push('}');
            }else {
                //如果棧爲空,返回false 或者stack.pop()!=charArray[i],返回false
                if(stack.isEmpty() || stack.pop()!=charArray[i] ){
                    return false;
                }
            }
        }
        //最終如果棧爲空,返回true
        return stack.isEmpty();
    }
}

21、合併兩個有序鏈表(鏈表)

**題目:**將兩個升序鏈表合併爲一個新的升序鏈表並返回。新鏈表是通過拼接給定的兩個鏈表的所有節點組成的。

示例:

輸入:1->2->4, 1->3->4
輸出:1->1->2->3->4->4

1、思路:

1、使用 dummy->next 來保存需要返回的頭節點

2、判斷 l1l2 哪個更小,就新建一個節點添加到鏈表的尾部

3、直到有一邊爲 null ,即可將另一個鏈表剩餘的部分都接上

在這裏插入圖片描述
2、代碼:

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        //創建一個節點dummy指向鏈表的頭結點
        ListNode dummy = new ListNode(0);

        //因爲dummy節點不能移動,因此創建一個指針cur指向dummy
        ListNode cur = dummy;

        //遍歷兩個鏈表比較節點的值的大小
        while (l1!=null && l2!=null){
            if(l1.val<l2.val){
                cur.next = new ListNode(l1.val);
                l1 = l1.next;
            }else{
                cur.next = new ListNode(l2.val);
                l2 = l2.next;
            }
            cur = cur.next;
        }

        if(l1!=null){
            cur.next = l1;
        }else{
            cur.next = l2;
        }
        return dummy.next;
    }
}

22、括號生成(遞歸回溯)

**題目:**數字 n 代表生成括號的對數,請你設計一個函數,用於能夠生成所有可能的並且 有效的 括號組合。

示例:

輸入:n = 3
輸出:[
       "((()))",
       "(()())",
       "(())()",
       "()(())",
       "()()()"
     ]

1、思路:

遞歸回溯算法,深度優先搜索

1、深度優先遍歷,先遞歸遍歷左括號,(,((,(((

2、遞歸的時候以(爲主,如果剩餘的(的個數大於0,優先遍歷(

3、如果剩餘的(的個數<0,說明if()條件不滿足,繼續向下遞歸)

4、如果中間出現(的剩餘的個數大於)的剩餘的個數,就剪枝

時間複雜度:O(n!)–O(2^n)

在這裏插入圖片描述
2、代碼:

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> list = new ArrayList<>();
        if(n==0) return list;
        dfs(list,"",n,n);
        return list;
    }
    /**
     * 深度優先搜索
     * @param list 存放結果的集合
     * @param s 拼接結果的字符串
     * @param left 左括號剩餘的個數
     * @param right 右括號剩餘的個數
     */
    private void dfs(List<String> list, String s, int left, int right) {
        if(left==0 && right==0){
            list.add(s);
            return;
        }

        //剪枝
        if(left>right){
            return;
        }
        
        //如果左括號還有剩餘就深度搜索生成 (、((、(((
        if(left>0){
            dfs(list,s+"(",left-1,right);
        }
        
        if(right>0){
            dfs(list,s+")",left,right-1);
        }
    }
}

23、合併K個排序鏈表(鏈表)

**題目:**合併 k 個排序鏈表,返回合併後的排序鏈表。請分析和描述算法的複雜度。

示例:

輸入:
[
  1->4->5,
  1->3->4,
  2->6
]
輸出: 1->1->2->3->4->4->5->6

1、思路:

在講思路之前,需要去學習下如何使用優先隊列並通過自定義排序規則排序:

優先級隊列的元素按照其自然順序進行排序,或者根據構造隊列時提供的 Comparator 進行排序,具體取決於所使用的構造方法。

public class Student {

    private String name;  //名字
    private int score;    //分數
    
    //省略getter/setter/構造方法...
}
public class App {
    public static void main(String[] args) {

        /**
         * PriorityQueue:優先隊列,默認按照從小到大的順序排序
         *
         * 也通過實現Comparator接口指定排序規則
         */
        PriorityQueue<Student> q = new PriorityQueue<Student>(new Comparator<Student>() {
            /**
             * 這個compare方法很容易搞混
             * 如果返回1,就代表前者o1的權重大,排序默認按照從小到大的權重排,所以排序:o2 o1
             * 如果返回-1,就代表後者o2的權重大,排序:o1 o2
             * @param o1
             * @param o2
             */
            public int compare(Student o1, Student o2) {
                //按照分數低到高
                return o1.getScore() - o2.getScore();
            }
        });
        //入列
        q.offer(new Student("dafei", 20));
        q.offer(new Student("will", 17));
        q.offer(new Student("setf", 30));
        q.offer(new Student("bunny", 22));

        //出列
        System.out.println(q.poll().getScore());  //17
        System.out.println(q.poll().getScore());  //20
        System.out.println(q.poll().getScore());  //22
        System.out.println(q.poll().getScore());  //30
    }
}

下面就打算使用優先隊列來合併K個排序鏈表:

1、定義優先隊列並實現Comparator接口,自定義排序規則:比較鏈表的頭結點的值的大小進行排序

2、將K個排序鏈表加入到優先隊列中

3、定義一個dummy節點指向頭節點,由於dummy節點不能移動,需定義一個Cur指向dummy

4、如果隊列不爲空就將鏈表彈出,讓cur.next=queue.poll(),cur = cur.next

5、同時需要將cur.next指向的節點放入隊列繼續進行排序。

在這裏插入圖片描述

2、代碼:

/**
time:O(nlogk)
space:O(n)
*/
public class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists==null) return null;

        ListNode dummy = new ListNode(0);
        ListNode cur = dummy;

        //按照自定義排序規則創建一個優先隊列
        PriorityQueue<ListNode> queue = new PriorityQueue<>(new Comparator<ListNode>() {
            //按照鏈表的頭結點的大小升序排序
            @Override
            public int compare(ListNode o1, ListNode o2) {
                //2-3=-1,說明3的權重大,3排在後面:2、3
                return o1.val-o2.val;
            }
        });

        //把K個有序鏈表放入隊列中,放入之後會按照頭節點大小從小到大排序
        for(ListNode list :lists){
            if(list!=null){
                queue.offer(list);
            }
        }

        //遍歷這個隊列
        while (!queue.isEmpty()){
            cur.next = queue.poll();
            cur = cur.next;
            //如果鏈表後面還有數,要將後面的數放入隊列繼續比較排序
            if(cur.next!=null){
                queue.offer(cur.next);
            }
        }
        return dummy.next;
    }
}

26、刪除排序數組中的重複項(雙指針)

**題目:**給定一個排序數組,你需要在 原地 刪除重複出現的元素,使得每個元素只出現一次,返回移除後數組的新長度。不要使用額外的數組空間,你必須在 原地 修改輸入數組 並在使用 O(1) 額外空間的條件下完成。

示例 1:

給定數組 nums = [1,1,2], 

函數應該返回新的長度 2, 並且原數組 nums 的前兩個元素被修改爲 1, 2。 

你不需要考慮數組中超出新長度後面的元素。

示例 2:

給定 nums = [0,0,1,1,1,2,2,3,3,4],

函數應該返回新的長度 5, 並且原數組 nums 的前五個元素被修改爲 0, 1, 2, 3, 4。

你不需要考慮數組中超出新長度後面的元素。

1、思路:

1、使用雙指針count、i

2、開始讓count=1、i=1因爲nums數組中的第一個數一定是保留的,我們只需要才能夠第二個數開始即可

3、如果nums[i-1]=nums[i],將nums[count++] = nums[i]

4、最後count的大小就是數組的長度

在這裏插入圖片描述
2、代碼:

/**
 * time : O(n)
 * space:O(1)
 */
public class Solution {
    public int removeDuplicates(int[] nums) {
        if(nums.length==0 || nums==null) return 0;

        //定義一個count指針,用來執行nums數組的下標,nums[0]一定是保留的,因此count=1
        int count =1;

        for(int i=1;i<nums.length;i++){
            //如果nums[i]!=nums[i-1],就將nums[count] = nums[i]
            if(nums[i] != nums[i-1]){
                nums[count++] = nums[i];
            }
        }
        return count;
    }
}

27、移除元素(雙指針)

**題目:**給你一個數組 nums 和一個值 val,你需要 原地 移除所有數值等於 val 的元素,並返回移除後數組的新長度。不要使用額外的數組空間,你必須僅使用 O(1) 額外空間並 原地 修改輸入數組。元素的順序可以改變。你不需要考慮數組中超出新長度後面的元素。

示例 1:

給定 nums = [3,2,2,3], val = 3,

函數應該返回新的長度 2, 並且 nums 中的前兩個元素均爲 2。

你不需要考慮數組中超出新長度後面的元素。

示例 2:

給定 nums = [0,1,2,2,3,0,4,2], val = 2,

函數應該返回新的長度 5, 並且 nums 中的前五個元素爲 0, 1, 3, 0, 4。

注意這五個元素可爲任意順序。你不需要考慮數組中超出新長度後面的元素。

1、思路:

在這裏插入圖片描述

2、代碼:

class Solution {
    public int removeElement(int[] nums, int val) {
        if(nums.length == 0) return 0;
        int count =0;
        for(int i=0;i<nums.length;i++){
            if(nums[i] != val){
                nums[count] = nums[i];
                count++;
            }
        }
        return count;
    }
}

28、實現 strStr() 函數

題目:給定一個 haystack 字符串和一個 needle 字符串,在 haystack 字符串中找出 needle 字符串出現的第一個位置 (從0開始)。如果不存在,則返回 -1。

示例 1:

輸入: haystack = "hello", needle = "ll"
輸出: 2

示例 2:

輸入: haystack = "aaaaa", needle = "bba"
輸出: -1

說明:

當 needle 是空字符串時,我們應當返回什麼值呢?這是一個在面試中很好的問題。

當 needle 是空字符串時我們應當返回 0 。這與C語言的 strstr() 以及 Java的 indexOf() 定義相符。

代碼:

public class Solution {
    public int strStr(String haystack, String needle) {
        for(int i=0;i<=haystack.length()-needle.length();i++){
            if(haystack.substring(i,i+needle.length()).equals(needle)){
                return i;
            }
        }
        return -1;
    }
}

29、兩數相除

**題目:**給定兩個整數,被除數 dividend 和除數 divisor。將兩數相除,要求不使用乘法、除法和 mod 運算符。返回被除數 dividend 除以除數 divisor 得到的商。整數除法的結果應當截去(truncate)其小數部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2

示例 1:

輸入: dividend = 10, divisor = 3
輸出: 3
解釋: 10/3 = truncate(3.33333..) = truncate(3) = 3

示例 2:

輸入: dividend = 7, divisor = -3
輸出: -2
解釋: 7/-3 = truncate(-2.33333..) = -2

提示:

被除數和除數均爲 32 位有符號整數。
除數不爲 0。
假設我們的環境只能存儲 32 位有符號整數,其數值範圍是 [−231,  231 − 1]。本題中,如果除法結果溢出,則返回 231 − 1。

代碼:

public class Solution {
    public int divide(int dividend, int divisor) {
        //1、對於數值運算需要考慮正負號問題
        int sign = 1;
        if(dividend<0 && divisor>0 || dividend>0 && divisor<0) sign=-1;

        //2、因爲除數和被除數均爲32位正數,並且只能存儲32位有符號正數,需要考慮越界問題,使用long類型存儲
        long ldividend = Math.abs((long)dividend);
        long ldivisor = Math.abs((long)divisor);

        //3、考慮被除數<除數,被除數<0的情況
        if(ldividend<ldivisor || ldividend==0) return 0;

        //4、計算結果
        long lres = divide(ldividend,ldivisor);
        int res = 0;
        if(lres>Integer.MAX_VALUE){
            res = (sign==1)  ? Integer.MAX_VALUE:Integer.MIN_VALUE;
        }else{
            res = (int)(sign*lres);
        }
        return res;
    }

    public long divide(long ldividend,long ldivisor){
        if(ldividend<ldivisor) return 0;
        long sum = ldivisor;
        long multiply = 1;
        while ((sum+sum) <= ldividend){
            sum += sum;
            multiply += multiply;
        }
        return multiply + divide(ldividend-sum,ldivisor);
    }
}

30、串聯所有單詞的子串(滑動窗口)

**題目:**給定一個字符串 s 和一些長度相同的單詞 words。找出 s 中恰好可以由 words 中所有單詞串聯形成的子串的起始位置。注意子串要與 words 中的單詞完全匹配,中間不能有其他字符,但不需要考慮 words 中單詞串聯的順序。

示例 1:

輸入:
  s = "barfoothefoobarman",
  words = ["foo","bar"]
輸出:[0,9]
解釋:
從索引 0 和 9 開始的子串分別是 "barfoo" 和 "foobar" 。
輸出的順序不重要, [9,0] 也是有效答案。

示例 2:

輸入:
  s = "wordgoodgoodgoodbestword",
  words = ["word","good","best","word"]
輸出:[]

思路:

在這裏插入圖片描述

代碼:

public class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> list = new ArrayList<>();

        if(s==null || words==null ||s.length()==0 || words.length==0) return new ArrayList<>();

        int n = words.length;
        int m = words[0].length();

        HashMap<String,Integer> map = new HashMap<>();
        //鍵存放字符串,值存放字符串出現拿的次數
        for(String s1:words){
            map.put(s1,map.getOrDefault(s1,0)+1);
        }

        for(int i=0;i<=s.length()-n*m;i++){
            //將map複製一份
            HashMap<String,Integer> copy = new HashMap<>(map);

            //字符串的長度
            int k = n;
            //遍歷字符串的指針
            int j = i;

            while (k>0){
                String str = s.substring(j, j + m);
                if(!copy.containsKey(str) || copy.get(str)<1){
                    break;
                }
                //鍵相同,值覆蓋
                copy.put(str,copy.get(str)-1);
                j+=m;
                k--;
            }
            if(k==0){
                list.add(i);
            }
        }
        return list;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章