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);
}
}