1. 程式人生 > 其它 >leercode劍指offer專項版

leercode劍指offer專項版

leetcode劍指Offer專項版1-20

4、只出現一次的數字

1、題目:給你一個整數陣列 nums ,除某個元素僅出現 一次 外,其餘每個元素都恰出現 三次 。請你找出並返回那個只出現了一次的元素。

  • 示例 1:
    輸入:nums = [2,2,3,2]
    輸出:3

  • 示例 2:
    輸入:nums = [0,1,0,1,0,1,100]
    輸出:100

  • 提示:
    1 <= nums.length <= 3 * \(10^4\)
    -\(2^{31}\) <= nums[i] <= \(2^{31}\) - 1
    nums 中,除某個元素僅出現 一次 外,其餘每個元素都恰出現 三次

  • 進階:你的演算法應該具有線性時間複雜度。 你可以不使用額外空間來實現嗎?

2、思路:如果我們可以將所有的數字都轉化為二進位制,然後根據每一位對3其餘,如圖所示:

最方便的是我們建立一個32位長度的陣列,然後針對每個數字的二進位制位對該陣列對應下標進行累加,最終計算結果。
但為了嚴格按照題目要求,我們通過左位移配合二進位制加法來實現累加操作,具體如圖:

class Solution {
    public int singleNumber(int[] nums) {
        /*
        所有的值>>2,輸出1的數量並%3*2^0,
        所有值>>2,輸出1的數量並%3*2^1
         */
        int res=0;
        for(int i=0;i<32;i++){
            int num=0;
            for(int j=0;j<nums.length;j++){
                num+=(nums[j]&1);
                nums[j]>>=1;
            }
            num%=3;
            if(num!=0)res=res|1<<i;
        }
        return res;
    }
}

5. 單詞長度的最大乘積

1、題目給定一個字串陣列 words,請計算當兩個字串 words[i] 和 words[j] 不包含相同字元時,它們長度的乘積的最大值。假設字串中只包含英語的小寫字母。如果沒有不包含相同字元的一對字串,返回 0。

  • 示例 1

    輸入: words = ["abcw","baz","foo","bar","fxyz","abcdef"]
    輸出: 16
    解釋: 這兩個單詞為 "abcw", "fxyz"。它們不包含相同字元,且長度的乘積最大。

  • 示例 2:

    輸入: words = ["a","ab","abc","d","cd","bcd","abcd"]
    輸出: 4
    解釋: 這兩個單詞為 "ab", "cd"。

  • 示例 3:

輸入: words = ["a","aa","aaa","aaaa"]
輸出: 0
解釋: 不存在這樣的兩個單詞。

  • 提示:

    2 <= words.length <= 1000
    1 <= words[i].length <= 1000
    words[i] 僅包含小寫字母

2、思路:位運算

我們可以用二進位制來表示出現的字母,出現的字母記作1。

a可以用1左移0位表示,
b用1左移2位表示...
z用1左移25位表示。

例如:100101,表示出現過a、c和f

如果兩個字串不存在相同字母,那麼這兩個int值的且值 & 必定是0;

class Solution {
    public static int maxProduct(String[] words) {
        int[] num=new int[1000];
        int res=0;
        for (int i=0 ;i<words.length;i++) {
            for (int j = 0; j < words[i].length(); j++) {
                num[i]|=1<<(words[i].charAt(j)-'a');
            }
        }
        for (int i=0 ;i<words.length;i++) {
            for (int j=0 ;j<words.length;j++) {
                if ((num[i]&num[j])==0)
                    res=Math.max(words[i].length()*words[j].length(),res);
                }
            }
        return res;
        }
}

7. 陣列中和為 0 的三個數

1、題目

給定一個包含 n 個整數的陣列 nums,判斷 nums 中是否存在三個元素 a ,b ,c ,使得 a + b + c = 0 ?
請找出所有和為 0 且 不重複 的三元組。

  • 提示:

    0 <= nums.length <= 3000
    \(-10 ^ 5\) <= nums[i] <= \(10 ^ 5\)

  • 示例 1:
    輸入:nums = [-1,0,1,2,-1,-4]
    輸出:[[-1,-1,2],[-1,0,1]]

  • 示例 2:
    輸入:nums = []
    輸出:[]

  • 示例 3:
    輸入:nums = [0]
    輸出:[]

2、思路

  • 陣列排序
  • 確定一個固定的指標
  • 使用雙指標對陣列進行遍歷
  • 注意去重 while (left < right && nums[left] == nums[left + 1]) left++;
class Solution {
    public static List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> ans = new ArrayList();
        int lg = nums.length;
        Arrays.sort(nums);
        for (int i = 0; i < lg; i++) {
            if (nums[i] > 0) break; 
            if (i > 0 && nums[i] == nums[i - 1]) continue; 
            int left = i + 1;
            int right = lg - 1;
            while (left < right) {
                int total = nums[i] + nums[left] + nums[right];
                if (total == 0) {
                    ans.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    while (left < right && nums[left] == nums[left + 1]) left++; 
                    left++;
                } else if (total < 0) left++;
                else if (total > 0) right--;
            }
        }
        return ans;
    }
}

9. 乘積小於 K 的子陣列

1、題目給定一個正整數陣列 nums和整數 k ,請找出該陣列內乘積小於 k 的連續的子陣列的個數。

  • 示例 1:

    輸入: nums = [10,5,2,6], k = 100
    輸出: 8
    解釋: 8 個乘積小於 100 的子陣列分別為: [10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]。
    需要注意的是 [10,5,2] 並不是乘積小於100的子陣列。

  • 示例 2:

  • 輸入: nums = [1,2,3], k = 0
    輸出: 0

  • 提示:

    1 <= nums.length <= 3 *\(10^4\)
    1 <= nums[i] <= 1000
    0 <= k <= \(10^6\)

2、思路

本題可以使用「滑動視窗」的原因:

  • 關鍵 1:如果一個連續子陣列的所有元素的乘積都嚴格小於 k,那麼這個 連續子陣列的子集(同樣也得保證是連續子陣列)的乘積也一定嚴格小於 k。

    原因我們在「關鍵字」裡也向大家強調過,數組裡的所有元素都是正整數。

  • 關鍵 2:如果某個連續子陣列的乘積大於等於 k,包含它的更長的子陣列一定也不滿足。

  • 基於以上兩點,我們可以設計「滑動視窗」演算法。因此「滑動視窗」方法是「暴力解法」的優化。

public static int numSubarrayProductLessThanK(int[] nums, int k) {
  if (k < 2) return 0;
  int res = 0;
  int left = 0;
  int multi = 1;
  int right = 0;
  while (right<nums.length){
    multi*=nums[right++];
    while ( multi >= k) {
      multi /= nums[left++];
    }
    res += right - left ;

  }
  return res;
}

10. 和為 k 的子陣列

1、題目

  • 給定一個整數陣列和一個整數 k ,請找到該陣列中和為 k 的連續子陣列的個數。
  • 提示:
  • 1 <= nums.length <= 2 * 10 ^ 4
    -1000 <= nums[i] <= 1000
    -10 ^ 7 <= k <= 10 ^ 7
    示例
  • 示例 1 :
    輸入:nums = [1,1,1], k = 2
    輸出: 2
    解釋: 此題 [1,1] 與 [1,1] 為兩種不同的情況
  • 示例 2 :
    輸入:nums = [1,2,3], k = 3
    輸出: 2

2、分析:

滑動視窗的力所不及

在套模板的同時,大家是否考慮過,假設題目同樣是求連續的子陣列,但是在陣列中出現了負數,那這種情況下還可以使用滑動視窗麼?

答案是不行的,為什麼?

我們視窗滑動的條件是什麼,while視窗內元素超過或者不滿足條件時移動,但如果陣列存在負數,遇到不滿足題意的時候,我們應該移動視窗左邊界,還是擴大視窗右邊界從而尋找到符合條件的情況呢?

3、如果這道題的取值沒有負數,那就是標準的滑窗問題,但因為有了負數,滑窗思想不能用了。
通過分析,這道題應該屬於我們上面列舉四種情況的最後一種。具體思路如下:

  • 初始化一個空的雜湊表和pre_sum=0的字首和變數
  • 設定返回值ret = 0,用於記錄滿足題意的子陣列數量
  • 迴圈陣列的過程中,通過原地修改陣列的方式,計算陣列的累加和
  • 將當前累加和減去整數K的結果,在雜湊表中查詢是否存在
  • 如果存在該key值,證明以陣列某一點為起點到當前位置滿足題意,ret加等於將該key值對應的value
  • 判斷當前的累加和是否在雜湊表中,若存在value+1,若不存在value=1
  • 最終返回ret即可
  • 但在這裡要注意剛才說到的字首和邊界問題。
  • 我們在計算這種場景時,需要考慮如果以陣列nums[0]為開頭的連續子陣列就滿足題意呢?
  • 此時候我們的雜湊表還是空的,沒辦法計算字首和!所以遇到這類題目,都需要在雜湊表中預設插入一個{0:1}的鍵值對,
  • 用於解決從陣列開頭的連續子陣列滿足題意的特殊場景。
class Solution {
    public int subarraySum(int[] nums, int k) {
        int pre_sum = 0;
        int ret = 0;
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        for (int i : nums) {
            pre_sum += i;
            ret += map.getOrDefault(pre_sum - k, 0);
            map.put(pre_sum, map.getOrDefault(pre_sum, 0) + 1);
        }
        return ret;
    }
}

11. 0 和 1 個數相同的子陣列

1、問題:給定一個二進位制陣列 nums , 找到含有相同數量的 0 和 1 的最長連續子陣列,並返回該子陣列的長度。

  • 示例 1:

    輸入: nums = [0,1]
    輸出: 2
    說明: [0, 1] 是具有相同數量 0 和 1 的最長連續子陣列。

  • 示例 2:

    輸入: nums = [0,1,0]
    輸出: 2
    說明: [0, 1] (或 [1, 0]) 是具有相同數量 0 和 1 的最長連續子陣列。

  • 提示:

    1 <= nums.length <= \(10^5\)
    nums[i] 不是 0 就是 1

2、分析

我們不妨將所有0轉化為-1,那麼如果遇到了相同數量的0和1,累加之後的結果就為0,不是就又轉化為字首和的思想了麼

解題思路如下:

  • 初始化雜湊表,並新增{0:-1}
  • 宣告字首和變數pre_sum = 0,最大子陣列長度返回值ret = 0
  • 迴圈陣列,若元素為0,pre_sum - 1,反之pre_sum + 1
  • 判斷雜湊表中是否存在值為pre_sum的key
  • 若存在pre_sum的key,使用max函式更新ret。
  • 最終返回ret即可
class Solution {
    public int findMaxLength(int[] nums) {
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, -1);
        int pre_sum = 0;
        int ret = 0;
        for (int i = 0; i < nums.length; i++) {
            pre_sum += nums[i] == 0 ? -1 : 1;
            if (map.containsKey(pre_sum)) {
                ret = Math.max(ret, i - map.get(pre_sum));
            } else {
                map.put(pre_sum, i);
            }
        }
        return ret;
    }
}

17. 含有所有字元的最短字串

1、題目:

給定兩個字串 s 和 t 。返回 s 中包含 t 的所有字元的最短子字串。如果 s 中不存在符合條件的子字串,則返回空字串 "" 。

如果 s 中存在多個符合條件的子字串,返回任意一個。

注意: 對於 t 中重複字元,我們尋找的子字串中該字元數量必須不少於 t 中該字元數量。

  • 示例 1:

    輸入:s = "ADOBECODEBANC", t = "ABC"
    輸出:"BANC"
    解釋:最短子字串 "BANC" 包含了字串 t 的所有字元 'A'、'B'、'C'

  • 示例 2:

    輸入:s = "a", t = "a"
    輸出:"a"

  • 示例 3:

    輸入:s = "a", t = "aa"
    輸出:""
    解釋:t 中兩個字元 'a' 均應包含在 s 的子串中,因此沒有符合條件的子字串,返回空字串。

  • 提示:

    1 <= s.length, t.length <= \(10^5\)
    s 和 t 由英文字母組成

2、分析

  • 滑動視窗+臨界統計

  • 記錄好每次符合條件的起始點和長度即可

    演算法

    同樣使用兩個指標定位字串 s 的子字串,其中左指標指向子字串的第 一個字元,右指標指向最後一個字元。若某一時刻兩個指標之間的字串還沒有包含字串 t 中所有的字元,則右指標右移一位新增字元,若該時刻兩個指標之間的字串已包含字串 t 中所有的字元,因為目標需要求得符合要求的最短子字串,則需要右移動左指標從子字串中刪去第一個字元。

class Solution {
    public String minWindow(String s, String t) {
        int lens = s.length();
        int lent = t.length();
        if (lent > lens) return "";
        int left = 0;
        int right = 0;
        int minstart = 0;
        int minend = lens;
        char[] s1 = s.toCharArray();
        char[] t1 = t.toCharArray();
        HashMap<Character, Integer> hashMap = new HashMap<>();
        for (char ch : t1) {
            hashMap.put(ch, hashMap.getOrDefault(ch, 0) + 1);
        }
        int numch = hashMap.size();//字元種類數目
        int flag=0;
        while (right < lens || numch == 0) {
            if (numch != 0) {
                if (hashMap.containsKey(s1[right])) {
                    hashMap.put(s1[right], hashMap.get(s1[right]) - 1);
                    if (hashMap.get(s1[right]) == 0) {
                        numch--;
                    }
                }
                right++;
            } else {
                flag=1;
                if (minend - minstart  > right - left ) {
                    minend = right;
                    minstart = left;
                }
                if (hashMap.containsKey(s1[left])) {
                    hashMap.put(s1[left], hashMap.get(s1[left]) + 1);
                    if (hashMap.get(s1[left]) == 1) {
                        numch++;
                    }
                }
                left++;
            }

        }
        if (flag==0)return "";
        return s.substring(minstart, minend);
    }
}