蘋果 M1 系列晶片同款,英特爾考慮第 14 代酷睿“Meteor Lake”CPU 部分使用臺積電 5nm 製造
- Leetcode 題解 - 雙指標
- 1. 有序陣列的 Two Sum
- 2. 兩數平方和
- 3. 反轉字串中的母音字元
- 4. 迴文字串
- 5. 歸併兩個有序陣列
- 6. 判斷連結串列是否存在環
- 7. 最長子序列
1. 有序陣列的 Two Sum
167. Two Sum II - Input array is sorted (Easy)
Input: numbers={2, 7, 11, 15}, target=9
Output: index1=1, index2=2
題目描述:在有序陣列中找出兩個數,使它們的和為 target。
使用雙指標,一個指標指向值較小的元素,一個指標指向值較大的元素。指向較小元素的指標從頭向尾遍歷,指向較大元素的指標從尾向頭遍歷。
- 如果兩個指標指向元素的和 sum == target,那麼得到要求的結果;
- 如果 sum > target,移動較大的元素,使 sum 變小一些;
- 如果 sum < target,移動較小的元素,使 sum 變大一些。
陣列中的元素最多遍歷一次,時間複雜度為 O(N)。只使用了兩個額外變數,空間複雜度為 O(1)。
class Solution { public int[] twoSum(int[] numbers, int target) { int l = 0; int r = numbers.length-1; while(l < r){ int sum = numbers[l]+numbers[r]; if(sum == target) return new int[]{l+1, r+1}; else if(sum < target) l ++; else r --; } return null; } }
2. 兩數平方和
633. Sum of Square Numbers (Easy)
Input: 5
Output: True
Explanation: 1 * 1 + 2 * 2 = 5
題目描述:判斷一個非負整數是否為兩個整數的平方和。
可以看成是在元素為 0~target 的有序陣列中查詢兩個數,使得這兩個數的平方和為 target,如果能找到,則返回 true,表示 target 是兩個整數的平方和。
本題和 167. Two Sum II - Input array is sorted 類似,只有一個明顯區別:一個是和為 target,一個是平方和為 target。本題同樣可以使用雙指標得到兩個數,使其平方和為 target。
本題的關鍵是右指標的初始化,實現剪枝,從而降低時間複雜度。設右指標為 x,左指標固定為 0,為了使 02 + x2 的值儘可能接近 target,我們可以將 x 取為 sqrt(target)。
因為最多隻需要遍歷一次 0~sqrt(target),所以時間複雜度為 O(sqrt(target))。又因為只使用了兩個額外的變數,因此空間複雜度為 O(1)。
public boolean judgeSquareSum(int target) {
if (target < 0) return false;
int i = 0, j = (int) Math.sqrt(target);
while (i <= j) {
int powSum = i * i + j * j;
if (powSum == target) {
return true;
} else if (powSum > target) {
j--;
} else {
i++;
}
}
return false;
}
3. 反轉字串中的母音字元
345. Reverse Vowels of a String (Easy)
Given s = "leetcode", return "leotcede".
思路:利用「雙指標」進行前後掃描,當左右指標都是母音字母時,進行互換並移到下一位。
class Solution {
private final static HashSet<Character> vowels = new HashSet<>(
Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));
public String reverseVowels(String s) {
if (s == null) return null;
int i = 0, j = s.length() - 1;
char[] result = new char[s.length()];
while (i <= j) {
char ci = s.charAt(i);
char cj = s.charAt(j);
if (!vowels.contains(ci)) {
result[i++] = ci;
} else if (!vowels.contains(cj)) {
result[j--] = cj;
} else {
result[i++] = cj;
result[j--] = ci;
}
}
return new String(result);
}
}
4. 迴文字串
680. Valid Palindrome II (Easy)
Input: "abca"
Output: True
Explanation: You could delete the character 'c'.
題目描述:可以刪除一個字元,判斷是否能構成迴文字串。
思路:所謂的迴文字串,是指具有左右對稱特點的字串,例如 "abcba" 就是一個迴文字串。
判斷迴文串用雙指標的,i從前往後遍歷,j從後往前遍歷。
難點:怎麼去判斷刪除一個元素後的字串是不是迴文串
解決:如果遇到不等的判斷去掉左邊或者去掉右邊是否為迴文。
public boolean validPalindrome(String s) {
for (int i = 0, j = s.length() - 1; i < j; i++, j--) {
if (s.charAt(i) != s.charAt(j)) {
return isPalindrome(s, i, j - 1) || isPalindrome(s, i + 1, j);
}
}
return true;
}
private boolean isPalindrome(String s, int i, int j) {
while (i < j) {
if (s.charAt(i++) != s.charAt(j--)) {
return false;
}
}
return true;
}
5. 歸併兩個有序陣列
88. Merge Sorted Array (Easy)
Input:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
Output: [1,2,2,3,5,6]
題目描述:把歸併結果存到第一個陣列上。
思路:雙指標 ,從後往前:
用的是倒序的方式。指標i,j,k分別指向num1陣列m-1位置,num2陣列n-1位置和num1陣列m+n-1位置,將i,j指向數值大的放在k位置。
需要從尾開始遍歷,否則在 nums1 上歸併得到的值會覆蓋還未進行歸併比較的值。
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int k = m+n;
while (n >0){
if (m >0 && nums1[m-1] > nums2[n-1]) {
nums1[--k] = nums1[--m];
}else{
nums1[--k] = nums2[--n];
}
}
}
}
6. 判斷連結串列是否存在環
141. Linked List Cycle (Easy)
思路:使用快慢指標,若指標相遇則判斷有環。具體一個指標每次移動一個節點,一個指標每次移動兩個節點,如果存在環,那麼這兩個指標一定會相遇。
public boolean hasCycle(ListNode head) {
if (head == null) {
return false;
}
ListNode l1 = head, l2 = head.next;
while (l1 != null && l2 != null && l2.next != null) {
if (l1 == l2) {
return true;
}
l1 = l1.next;
l2 = l2.next.next;
}
return false;
}
7. 最長子序列
524. Longest Word in Dictionary through Deleting (Medium)
Input:
s = "abpcplea", d = ["ale","apple","monkey","plea"]
Output:
"apple"
題目描述:刪除 s 中的一些字元,使得它構成字串列表 d 中的一個字串,找出能構成的最長字串。如果有多個相同長度的結果,返回字典序的最小字串。
思路:排序 + 雙指標 + 貪心
可以先對 dictionary 根據題意進行自定義排序;
長度不同的字串,按照字串長度排倒序。長度相同的,則按照字典序排升序;
然後我們只需要對dictionary 進行順序查詢,找到的第一個符合條件的字串即是答案。
class Solution {
public String findLongestWord(String s, List<String> dictionary) {
Collections.sort(dictionary, (a,b)->{
if (a.length() != b.length()) return b.length()-a.length();
return a.compareTo(b);
});
int n = s.length();
for(String ss: dictionary){
int m = ss.length();
int i = 0, j = 0;
while(i < n && j < m){
if(s.charAt(i)== ss.charAt(j)) j++;
i++;
}
if (j == m) return ss;
}
return "";
}
}
滑動視窗原理
本質上來說,滑窗是雙指標,一根指標指向左端點,一根指標指向右端點。
右指標移動可以表示擴張視窗,左指標移動表示縮小視窗。
如果當前元素滿足題目要求時,可以挪動右指標嘗試更優解,並且更新需要記錄的變數(元素,元素個數++等)
如果當前視窗內的元素不滿足條件,可以挪動左指標嘗試調整,並且更新需要記錄的變數(元素,元素個數--等)
通過以上步驟視窗就開始“滑動”起來,在滑動過程中,要記得及時更新答案。一般為求最大或最小。
個人觀點認為,關鍵點在於找到一個 不滿足條件的情況 使得左指標移動,這樣可以簡化問題。找到不合理的情況並且及時調整是滑動視窗的關鍵。