LeetCode 題解記錄 - 雙指針

刷題 - 雙指針

[TOC]java


合併兩個有序數組

88. 合併兩個有序數組 簡單git

題目描述:github

給定兩個有序整數數組 nums1 和 nums2,將 nums2 合併到 nums1 中,使得 num1 成爲一個有序數組。數組

輸入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6],       n = 3

輸出: [1,2,2,3,5,6]
複製代碼

解題思路:bash

跟堆排序中的合併兩個有序數組的方法相似。函數

從尾部進行遍歷,比較兩個數組的最大值,放入到第一個數組中的末尾。ui

public void merge(int[] nums1, int m, int[] nums2, int n) {
    if (nums1 == null || nums2 == null) {
        return;
    }
    int p1 = m - 1;
    int p2 = n - 1;
    while (p1 >= 0 && p2 >= 0) {
        nums1[p1 + p2 + 1] = nums1[p1] > nums2[p2] ? nums1[p1--] : nums2[p2--];
    }
    while (p2 >= 0) {
        nums1[p2] = nums2[p2];
        p2--;
    }
}
複製代碼

移除元素

27. 移除元素 簡單spa

題目描述: 給定一個數組 nums 和一個值 val,你須要原地移除全部數值等於 val 的元素,返回移除後數組的新長度。.net

不要使用額外的數組空間,你必須在原地修改輸入數組並在使用 O(1) 額外空間的條件下完成。指針

元素的順序能夠改變。你不須要考慮數組中超出新長度後面的元素。

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

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

你不須要考慮數組中超出新長度後面的元素。
複製代碼

解題思路: 使用雙指針, i 是慢指針, j 是快指針, 原地置換元素, 最後返回新數組的長度.(因爲 nums 數組是傳參引用, 因此在方法內部修改後, 外部調用時是修改後的數組內容)

public int removeElement(int[] nums, int val) {
    int i = 0;
    for (int j = 0; j < nums.length; j ++) {
        if (nums[j] != val) {
            nums[i++] = nums[j];
        }
    }
    return i;
}
複製代碼

無重複字符的最長子串

3. 無重複字符的最長子串 中等

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

輸入: "abcabcbb"
輸出: 3 
解釋: 由於無重複字符的最長子串是 "abc",因此其長度爲 3。
複製代碼

解答思路: 使用滑動窗口,雙指針進行左閉右開[i, j),每次遍歷刷新下標,保證set中不重複,取最長的ans,

代碼:

public int lengthOfLongestSubstring(String s) {
    int n = s.length();
    Set<Character> set = new HashSet<>();
    int ans = 0, i = 0, j = 0;
    while (i < n && j < n) {
        if (!set.contains(s.charAt(j))){
            set.add(s.charAt(j++));
            ans = Math.max(ans, j - i);
        }
        else {
            // 有重複的狀況下,去掉最左邊
            set.remove(s.charAt(i++));
        }
    }
    return ans;
}
複製代碼

進階版

解答思路: 遇到重複的字符,將滑動窗口的左邊直接滑到第一個重複的字符下標。若是 s[j] 在 [i, j) 範圍內有與 j' 相同的字符, 能夠直接跳過 [i, j']範圍內的全部元素, 將 i 下標置爲 j' + 1. 使用 HashMap 來存儲字符對應的下標.

public static int lengthOfLongestSubstring(String s) {
    Map<Character, Integer> map = new HashMap<>();
    int length = s.length(), ans = 0;
    for (int i = 0, j = 0; j < length; j++) {
        char temp = s.charAt(j);
        if (map.containsKey(temp)) {
            i = Math.max(map.get(temp), i);
        }
        ans = Math.max(ans, j - i + 1);
        map.put(temp, j + 1);
    }
    return ans;
}
複製代碼

串聯全部單詞的子串

30. 串聯全部單詞的子串 困難

題目描述: 給定一個字符串 s 和一些長度相同的單詞 words。找出 s 中剛好能夠由 words 中全部單詞串聯造成的子串的起始位置。

注意子串要與 words 中的單詞徹底匹配,中間不能有其餘字符,但不須要考慮 words 中單詞串聯的順序。

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

解題思路:

使用兩個 HashMap 保存出現的字符和次數

遍歷給定字符(直到總長度 totalLen減去單個待查找字符的長度wordLen), 在內部循環中, 以 wordLen 做爲長度切分給定的字符。若是連續子字符中包含查詢字符,繼續找;若是不包含查詢字符,調成內層循環,從下一個字符開始查詢。最後內層循環結束後,判斷查找次數是否一致,若是是,保存起始下標到結果集中。

代碼:

public List<Integer> findSubstring(String s, String[] words) {
    List<Integer> result = new ArrayList<>();
    int wordSize = words.length;
    if (wordSize == 0) {
        return result;
    }
    // words 數組中每一個字符的長度都一致, 取第一個
    int wordLength = words[0].length();
    // 字符數組有可能有重複的, 使用 allWordMap 存放每一個 word 出現的次數
    HashMap<String, Integer> allWordMap = new HashMap<>();
    for (String word : words) {
        allWordMap.put(word, allWordMap.getOrDefault(word, 0) + 1);
    }

    for (int i = 0; i < s.length() - wordSize * wordLength + 1; i++) {
        HashMap<String, Integer> hashMap = new HashMap<>();
        // 單趟循環中, 成功匹配的次數
        int num = 0;
        while (num < wordSize) {
            // 根據查找字符的長度進行截斷
            String word = s.substring(i + num * wordLength, i + (num + 1) * wordLength);
            if (allWordMap.containsKey(word)) {
                hashMap.put(word, hashMap.getOrDefault(word, 0) + 1);
                // 還有可能超過 words 中原來的數量
                if (hashMap.get(word) > allWordMap.get(word)) {
                    break;
                }
            } else {
                // 若是沒有查詢到, 跳過此次循環, 查找下一個字符
                break;
            }
            num++;
        }
        if (num == wordSize) {
            result.add(i);
        }
    }
    return result;
}
複製代碼

旋轉鏈表

61. 旋轉鏈表 中等

題目描述:

給定一個鏈表,旋轉鏈表,將鏈表每一個節點向右移動 k 個位置,其中 k 是非負數。

輸入: 1->2->3->4->5->NULL, k = 2
輸出: 4->5->1->2->3->NULL
解釋:
向右旋轉 1 步: 5->1->2->3->4->NULL
向右旋轉 2 步: 4->5->1->2->3->NULL
複製代碼

解題思路

①:遍歷鏈表,得到鏈表長度和最後一個節點的地址。

②:計算獲得須要移動的次數(k % length)

③:移動,以示例做爲例子,k = 2,表示右移兩次,能夠認爲是作了如下三步操做:

  • 末尾節點指向了頭部節點:5 -> 1
  • 第三個節點變成了頭部節點:head = 3
  • 第二個節點變成了末尾節點:2.next -> null

代碼:

public ListNode rotateRight(ListNode head, int k) {
    if (head == null || k == 0) {
        return head;
    }
    int length = 1;
    ListNode tempNode = head;
    ListNode endNode = null;
    while (tempNode.next != null) {
        length++;
        tempNode = tempNode.next;
    }
    endNode = tempNode;
    // 獲得須要移動的次數
    int moveNum = k % length;
    if (moveNum == 0) {
        return head;
    }
    // 找到待替換的下標
    int findIndex = 1, index = length - moveNum + 1;
    tempNode = head;
    while (tempNode.next != null) {
        findIndex++;
        if (findIndex == index) {
            endNode.next = head;
            head = tempNode.next;
            tempNode.next = null;
            break;
        }
        tempNode = tempNode.next;
    }
    return head;
}
複製代碼

最小覆蓋子串

76. 最小覆蓋子串 困難

題目描述:(原題描述不完整,還有不少隱含條件,在評論區找出來的)

給定一個字符串 S 和一個字符串 T,請在 S 中找出包含 T 全部字母的最小子串。

示例:

輸入: S = "ADOBECODEBANC", T = "ABC"
輸出: "BANC"
說明:

若是 S 中不存這樣的子串,則返回空字符串 ""。
若是 S 中存在這樣的子串,咱們保證它是惟一的答案。
複製代碼

隱含條件:

①
輸入:
s='a'
t='aa'
輸出:
''
②
參數的全集是大寫字母和小寫字母
複製代碼

解題思路,參考這篇文章

1. 注意到題目的關鍵:"全部字母的最小子串",也就是說兩個串都只能是字母。
2. 因而,能夠開闢一個大小爲64的數組,來存放數組中字母的頻率(Frequency)。準確的說,
   經過字母的ASCII碼做爲數組的索引,開闢空間的大小爲26+6+26=58:26個大寫字母,26個小寫字母,
   還有中間的6個非字母  A~Z[65~90]  非字母[91~96]  a~z[97~122]
3. 滑動窗口的使用:分三種狀況來移動窗口:(這裏令當前窗口的左右邊界分別爲l,r,窗口的大小爲winSize=r-l+1)
   1) 當winSize < t.size()  r++;  也就是窗口右邊界向右移動
   2) 當winSize == t.size() :
	   2.1) 當窗口中的字符已經符合要求了,直接返回return,已經找到了
	   2.2) 不然r++,窗口右邊界向右移動
   3) 當winSize > t.size()
	   2.1) 當窗口中的字符已經符合要求了,l++,窗口左邊界向右移動
	   2.2) 不然r++,窗口右邊界向右移動

4. 上面是滑動窗口的使用思路,具體實現上有必定的不一樣,下面是須要考慮到的要點:
   1) 啥叫做窗口中的字符已經符合要求了?
   1) 窗口滑動時的操做是關鍵
   2) 要考慮到數組越界的問題
複製代碼

代碼實現(解答答案中耗時最少的回答):

public String minWindow(String s, String t) {
    int lenS = s.length(), lenT = t.length();
    if(lenS < lenT){
        return "";
    }
    int[] tCount = new int[256];
    for(int i=0;i<lenT;i++){
        tCount[t.charAt(i)] ++;
    }
    int[] sCount = new int[256];
    int left = 0, right = 0;
    //保存窗口中等於字符串t的字符的數量,當count等於lenT時,說明找到一個子串
    int count = 0;
    int minLen = lenS + 1, start = -1;
    while(left < lenS){
        if(right < lenS && count < lenT){
            //right右滑一格
            char charRight = s.charAt(right);
            sCount[charRight] ++;
            if(sCount[charRight] <= tCount[charRight]){
                count ++;
            }
            right ++;
        }else{
            char charLeft = s.charAt(left);
            //更新最小字串的長度和起始索引
            if(count == lenT && right - left < minLen){
                minLen = right - left;
                start = left;
            }
            //left右滑一格
            sCount[charLeft] --;
            // 這裏只會在 sCount 中,被去掉要查詢的字符,纔會減小窗口中的總數;
            // 若是被去掉是其它無關字符,只須要 left 移動,總量 count 不須要改變
            if(sCount[charLeft] < tCount[charLeft]){
                count --;
            }
            left ++;
        }
    }
    if(start != -1){
        return s.substring(start, start + minLen);
    }
    return "";
}
複製代碼

數組中的 k-diff 數對

532. 數組中的K-diff數對 簡單

題目描述: 給定一個整數數組和一個整數 k, 你須要在數組裏找到不一樣的 k-diff 數對。這裏將 k-diff 數對定義爲一個整數對 (i, j), 其中 i 和 j 都是數組中的數字,且兩數之差的絕對值是 k.

示例 1:

輸入: [3, 1, 4, 1, 5], k = 2
輸出: 2
解釋: 數組中有兩個 2-diff 數對, (1, 3) 和 (3, 5)。
儘管數組中有兩個1,但咱們只應返回不一樣的數對的數量。
複製代碼

解題思路:

使用 map 保存給定的數組,每一個數字出現的次數。根據 k 值判斷兩個數字間的差值是否符合條件。

代碼:

public int findPairs(int[] nums, int k) {
    int ans = 0;
    HashMap<Integer, Integer> map = new HashMap<>();
    for (int num : nums) {
        map.put(num, map.getOrDefault(num, 0) + 1);
    }
    if (k == 0) {
        for (int key : map.keySet()) {
            if (map.get(key) > 1) {
                ans++;
            }
        }
    } else if (k > 0) {
        for (int key : map.keySet()) {
            if (map.containsKey(key + k)) {
                ans++;
            }
        }
    } else {
        for (int key : map.keySet()) {
            if (key >= 0) {
                continue;
            }
            if (map.containsKey(key - k)) {
                ans++;
            }
        }
    }
    return ans;
}
複製代碼

進階版

將數組排好序以後進行比較:

public int findPairs(int[] nums, int k) {
    if (k < 0) {
        return 0;
    }
    Arrays.sort(nums);
    int length = nums.length;
    int ans = 0, i = 0, j = 1;
    if (k == 0) {
        while (j < length) {
            if (nums[j] == nums[i]) {
                ans++;
            }
            int a = i++;
            while (nums[i] == nums[a]) {
                i++;
                if (i == length) {
                    return ans;
                }
            }
            j = i + 1;
        }
    } else {
        while (j < length) {
            while (nums[j] - nums[i] < k) {
                j++;
                if (j == length) {
                    return ans;
                }
            }
            if (nums[j] - nums[i] == k) {
                ans++;
            }
            int a = i++;
            while (nums[i] == nums[a]) {
                i++;
            }
        }
    }
    return ans;
}
複製代碼

字符串的排列

567. 字符串的排列 中等

題目描述:

給定兩個字符串 s1 和 s2,寫一個函數來判斷 s2 是否包含 s1 的排列。

換句話說,第一個字符串的排列之一是第二個字符串的子串。

輸入: s1 = "ab" s2 = "eidbaooo"
輸出: True
解釋: s2 包含 s1 的排列之一 ("ba").

注意:

輸入的字符串只包含小寫字母
兩個字符串的長度都在 [1, 10,000] 之間

複製代碼

解題思路:

因爲只包含小寫字母,可使用數組保存待匹配的字符出現的次數。而後經過兩個指針的移動,匹配到 s1 長度的 s2 子串。

代碼:

public boolean checkInclusion(String s1, String s2) {
    int[] count = new int[26];
    char[] pattern = s1.toCharArray();
    char[] str = s2.toCharArray();
    for (int i = 0; i < pattern.length; i++) {
        count[pattern[i] - 'a']++;
    }
    int left = 0, right = 0;
    while (right < str.length){
        if (count[str[right] - 'a'] != 0) {
            count[str[right] - 'a']--;
            right++;
            if (right - left == pattern.length) {
                return true;
            }
        } else if (left == right) {
            left++;
            right++;
        } else {
            count[str[left] - 'a']++;
            left++;
        }
    }
    return false;
}
複製代碼

我的博客項目地址

但願各位幫忙點個star,給我加個小星星✨


參考資料:

一、數組—滑動窗口/字母哈希表簡單實現 實踐leetcode438, leetcode76

相關文章
相關標籤/搜索