leetcode高頻題筆記之雙指針專題

在許多數組和鏈表的題中,都需要用到雙指針的思想來優化,本文總結歸納了幾種常見的雙指針和對應的應用案例,通過針對的性的刷題希望能熟練的掌握雙指針的運用

167.兩數之和II-輸入有序數組(頭尾指針)

在這裏插入圖片描述
定義兩個指針,一個是頭指針i,一個是尾指針j
判斷 numbers[i]+numbers[j]與target的值大大小
如果相等,就返回下標+1的數組
如果target更大,說明數小了,i向後移
如果target更小,說明數大了,j向前移

public class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int i = 0;
        int j = numbers.length - 1;
        int sum;
        while (i < j) {
            sum = numbers[i] + numbers[j];
            if (sum == target) return new int[]{i + 1, j + 1};
            else if (sum > target) j--;
            else i++;
        }
        return new int[0];
    }
}

633.平方數之和(頭尾指針)

在這裏插入圖片描述
a和b的取值在0(int)Math.sqrt(c)之間
所以就以這兩個值爲上下界進行雙指針遍歷

public class Solution {
    public boolean judgeSquareSum(int c) {
        int i = 0;
        int j = (int) Math.sqrt(c);
        while (i <= j) {
            int tmp = i * i + j * j;
            if (tmp == c) return true;
            else if (tmp > c) j--;
            else i++;
        }
        return false;
    }
}

345.反轉字符串中的元音字母(頭尾指針)

在這裏插入圖片描述
題目的意思是將字符串的正數第n個元音字母倒數第n個元音字母交換
定義頭指針i和尾指針j
定義一個char類型的數組
從頭尾同時遍歷,如果不是元音就直接加入新數組,如果是元音就等到兩端都是元音瞭然後交換寫入數組

public class Solution {
    public String reverseVowels(String s) {
        int i = 0;
        int j = s.length() - 1;
        char news[] = new char[s.length()];
        Set<Character> set = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));
        while (i <= j) {
            char ci = s.charAt(i);
            char cj = s.charAt(j);
            if (!set.contains(ci)) news[i++] = ci;
            else if (!set.contains(cj)) news[j--] = cj;
            else {
                news[i++] = cj;
                news[j--] = ci;
            }
        }
        return new String(news);
    }
}

680.驗證迴文字符串Ⅱ(頭尾指針)

在這裏插入圖片描述
定義頭指針i和尾指針j
從頭尾開始遍歷,如果相等同時向中間縮進
如果發現不等,嘗試刪去i所在數和j所在數
刪除後繼續遍歷,如果有任意一種情況能走完就符合迴文字符串

public class Solution {
    public boolean validPalindrome(String s) {
        int i = 0;
        int j = s.length() - 1;

        while (i < j) {
            if (s.charAt(i) == s.charAt(j)) {
                i++;
                j--;
            } else {
                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;
    }
}

88.合併兩個有序數組(異步指針)

在這裏插入圖片描述
從數組的尾部開始
定義一個指針k從新數組的最後一個位置開始
指針m,n分別從兩個數組的最後一個數的位置開始
比較兩個數組最後一個數的大小,大的放在新數字的指針處

如果有個數組指針爲0了,判斷是否是n的,如果是m就直接有序不需要處理,如果是n就依次填入新數組的位置

public class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int k = m-- + n-- - 1;
        while (m >= 0 && n >= 0) {
            nums1[k--] = nums1[m] > nums2[n] ? nums1[m--] : nums2[n--];
        }
        while (n >= 0) {
            nums1[k--] = nums2[n--];
        }
    }
}

21.合併兩個有序鏈表(異步指針)

在這裏插入圖片描述
從頭部開始
定義前驅指針pre
定義迭代的指針node = pre
判斷連個指針l1和l2的值,把小的賦值給node.next
當迭代到有一個爲空時,判斷一下,把不爲空的接在最後

public class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode pre = new ListNode(-1);
        ListNode node = pre;
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                node.next = l1;
                l1 = l1.next;
                node = node.next;
            } else {
                node.next = l2;
                l2 = l2.next;
                node = node.next;
            }
        }
        if (l1 == null) node.next = l2;
        else node.next = l1;
        return pre.next;
    }
}

141.環形鏈表(快慢指針)

在這裏插入圖片描述
定義快指針fast和慢指針slow
fast一次走兩步,slow一次走一步
關於爲什麼將fast設置爲head.next:其實快慢指針中fast是head或者head.next都可以找到環,因爲我們這裏判斷的是fast==slow,所以爲了簡化代碼就保證他們初始就不一致,一旦一致就是有環

public class Solution {
    public boolean hasCycle(ListNode head) {

        if (head == null || head.next == null) return false;
        ListNode slow = head;
        ListNode fast = head.next;
        while (slow != fast) {
            if (fast == null || fast.next == null) return false;
            slow = slow.next;
            fast = fast.next.next;
        }
        return true;
    }
}

fast = head版本

public class Solution {
    public boolean hasCycle(ListNode head) {

        if (head == null || head.next == null) return false;
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) return true;
        }
        return false;
    }
}

142.環形鏈表II(快慢指針)

在這裏插入圖片描述
在這裏插入圖片描述
思路:

  • 先和環形鏈表I一樣判斷有沒有環,有才進行第二步
  • 從同一起點開始,fast一次走兩步,slow一次走一步,當他們相遇之後,fast回到原點一次走一步,第二次相遇節點就是環連接點
  • 我的理解:
    • 假設第二圈發生第一次相遇,第二圈走完了就第二次相遇了
    • 第一次相遇時fast走了F+(a+b)+a
    • slow走了F+a
    • 因爲fast是slow的兩倍速度,所以第二次相遇時應該滿足fast=2*slow
    • 設slow還需要走 x遠第二次相遇,可得方程(F+2a+b+2x)=2(F+a+x),,解得F=b
    • 所以就假設fast以一倍的速度陪slow走完b,另一倍速從F出發,當fast與slow相遇時,相當於fast走過了b+F = 2b = 2倍slow的距離
    • 此時滿足第二次相遇的情況
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while (true) {
            if (fast == null || fast.next == null) return null;
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) break;//找到第一次相遇
        }
        fast = head;//將fast移動到head
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }//第二次相遇
        return fast;
    }
}

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

在這裏插入圖片描述
本題也運用到了快慢指針,說明快慢指針在鏈表題中的地位

本題思路:

  • 移動快指針n步,使快慢指針步差爲n
  • 如果此時快指針爲null,說明要刪除的節點是頭結點,直接返回head.next;
  • 如果快指針不是null,那麼就通過快慢指針查找到倒數第k+1個節點
  • 然後刪除掉倒數第k個節點
public class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {

        if (head == null || head.next == null) return null;

        ListNode fast = head;
        ListNode slow = head;

        //將fast和slow的步調差調成n,當fast到達末尾了,slow到達指定節點
        while (n-- > 0) {
            fast = fast.next;
        }
        //倒數的數就是頭結點的情況,此時fast是null
        if (fast == null){
            return head.next;
        }

        //倒數的數不是頭結點,按順序移動fast和slow節點,直到倒數第二個,測試slow節點到達刪除節點的上一個
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        //slow是待刪除節點上一個節點,刪除slow.next節點
        slow.next = slow.next.next;

        return head;

    }
}

234.迴文鏈表(快慢指針)

在這裏插入圖片描述

O(n) 時間複雜度和 O(1) 空間複雜度解法:

  • 快慢指針找到中點
  • 將鏈表分割爲兩部分
  • 選擇後一半進行翻轉
  • 翻轉後再次比較
public class Solution {
    /**
     * 快慢指針找到中點
     * 選擇一半進行翻轉
     * 翻轉後再次比較
     */
    public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) return true;

        ListNode slow = head;
        ListNode fast = head.next;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        if (fast != null) slow = slow.next;//偶數個結點,讓slow指向下一個,作爲後半段的開頭
      
        //將鏈表分爲以head和slow開頭的等長的兩段
        cut(head, slow);
        return isEqual(head, reverse(slow));
    }

    //翻轉鏈表
    private ListNode reverse(ListNode head) {
        ListNode newHead = null;
        while (head != null) {
            ListNode tmp = head.next;
            head.next = newHead;
            newHead = head;
            head = tmp;
        }
        return newHead;
    }

    //判定兩個鏈表是否相等
    private boolean isEqual(ListNode head1, ListNode head2) {
        while (head1 != null && head2 != null) {
            if (head1.val != head2.val) return false;
            head1 = head1.next;
            head2 = head2.next;
        }
        return true;
    }

    //分割鏈表
    private void cut(ListNode head, ListNode slow) {
        while (head.next != slow) {
            head = head.next;
        }
        head.next = null;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章