国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

LeetCode算法小抄 -- 鏈表(快慢指針、雙指針、回文鏈表)

這篇具有很好參考價(jià)值的文章主要介紹了LeetCode算法小抄 -- 鏈表(快慢指針、雙指針、回文鏈表)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

?申明: 未經(jīng)許可,禁止以任何形式轉(zhuǎn)載,若要引用,請(qǐng)標(biāo)注鏈接地址。 全文共計(jì)10077字,閱讀大概需要10分鐘
??更多學(xué)習(xí)內(nèi)容, 歡迎??關(guān)注??文末我的個(gè)人微信公眾號(hào):不懂開發(fā)的程序猿
個(gè)人網(wǎng)站:https://jerry-jy.co/

LeetCode算法小抄

Collection 子接口之 Queue (LeetCode上經(jīng)常用,手撕算法題?。?!)

Queue 與 Deque 的區(qū)別

Queue 是單端隊(duì)列,只能從一端插入元素,另一端刪除元素,實(shí)現(xiàn)上一般遵循 先進(jìn)先出(FIFO) 規(guī)則。

Queue 擴(kuò)展了 Collection 的接口,根據(jù) 因?yàn)槿萘繂栴}而導(dǎo)致操作失敗后處理方式的不同 可以分為兩類方法: 一種在操作失敗后會(huì)拋出異常,另一種則會(huì)返回特殊值

Queue 接口 拋出異常 返回特殊值
插入隊(duì)尾 add(E e) offer(E e)
刪除隊(duì)首 remove() poll()
查詢隊(duì)首元素 element() peek()

Deque 是雙端隊(duì)列,在隊(duì)列的兩端均可以插入或刪除元素。

Deque 擴(kuò)展了 Queue 的接口, 增加了在隊(duì)首和隊(duì)尾進(jìn)行插入和刪除的方法,同樣根據(jù)失敗后處理方式的不同分為兩類:

Deque 接口 拋出異常 返回特殊值
插入隊(duì)首 addFirst(E e) offerFirst(E e)
插入隊(duì)尾 addLast(E e) offerLast(E e)
刪除隊(duì)首 removeFirst() pollFirst()
刪除隊(duì)尾 removeLast() pollLast()
查詢隊(duì)首元素 getFirst() peekFirst()
查詢隊(duì)尾元素 getLast() peekLast()

事實(shí)上,Deque 還提供有 push()pop() 等其他方法,可用于模擬棧。

peek函數(shù)使用

Java中的peek函數(shù)是用于取出隊(duì)列中隊(duì)頭元素,但不會(huì)將其從隊(duì)列中移除。這樣,使用peek函數(shù)可以查看隊(duì)列中的隊(duì)頭元素,而不會(huì)改變隊(duì)列的狀態(tài)。

例如:

Queue<Integer> queue = new LinkedList<>(); 

queue.add(1); 

queue.add(2); 

queue.add(3);

// 查看隊(duì)頭元素 Integer first = queue.peek(); // 返回1

// 查看隊(duì)列中的元素 System.out.println(queue); // 輸出[1, 2, 3]

peek函數(shù)在Java的Queue接口中定義,可以通過實(shí)現(xiàn)該接口的類來使用該函數(shù)。常見的實(shí)現(xiàn)類有LinkedList、PriorityQueue等。

使用peek函數(shù)時(shí),需要注意以下幾點(diǎn):

  1. 如果隊(duì)列為空,peek函數(shù)會(huì)返回null,而不會(huì)拋出異常。因此,使用peek函數(shù)時(shí),需要先判斷隊(duì)列是否為空,避免出現(xiàn)空指針異常。
  2. peek函數(shù)返回的是隊(duì)列中的隊(duì)頭元素的副本,而不是隊(duì)列中的實(shí)際元素。因此,對(duì)返回的元素進(jìn)行修改并不會(huì)改變隊(duì)列中的實(shí)際元素。
  3. 如果隊(duì)列中的元素為null,peek函數(shù)也會(huì)返回null。因此,使用peek函數(shù)時(shí),需要注意區(qū)分隊(duì)列為空和隊(duì)列中存在null元素的情況。
  4. 如果隊(duì)列中的元素為不可變對(duì)象

結(jié)點(diǎn)的度:結(jié)點(diǎn)擁有的子樹的數(shù)目

樹的度:樹中結(jié)點(diǎn)的最大的度

滿二叉樹:高度為h,并且由 2^? –1個(gè)結(jié)點(diǎn)的二叉樹,被稱為滿二叉樹,滿二叉樹的結(jié)點(diǎn)的度要么為0(葉子結(jié)點(diǎn)),要么為2(非葉子結(jié)點(diǎn))

完全二叉樹:一棵二叉樹中,只有最下面兩層結(jié)點(diǎn)的度可以小于2,并且最下一層的葉結(jié)點(diǎn)集中在靠左的若干位置上。這樣的二叉樹稱為完全二叉樹。

特點(diǎn):葉子結(jié)點(diǎn)只能出現(xiàn)在最下層和次下層,且最下層的葉子結(jié)點(diǎn)集中在樹的左部。顯然,一棵滿二叉樹必定是一棵完全二叉樹,而完全二叉樹未必是滿二叉樹。

二叉查找樹:二叉查找樹,又被稱為二叉搜索樹。其特點(diǎn)如下:設(shè)x為二叉查找樹中的一個(gè)結(jié)點(diǎn),x節(jié)點(diǎn)包含關(guān)鍵字key,一句話就是左孩子比父節(jié)點(diǎn)小,右孩子比父節(jié)點(diǎn)大,還有一個(gè)特性就是 中序遍歷可以讓結(jié)點(diǎn)有序。

二叉堆和二叉樹區(qū)別:

  • 二叉堆在邏輯上其實(shí)是一種特殊的二叉樹(完全二叉樹)
  • 二叉堆是存儲(chǔ)數(shù)組,一般的鏈表二叉樹,我們操作節(jié)點(diǎn)的指針,而在二叉堆里,我們把數(shù)組索引作為指針
  • 二叉堆還分為最大堆和最小堆。最大堆的性質(zhì)是:每個(gè)節(jié)點(diǎn)都大于等于它的兩個(gè)子節(jié)點(diǎn)。類似的,最小堆的性質(zhì)是:每個(gè)節(jié)點(diǎn)都小于等于它的子節(jié)點(diǎn)。

優(yōu)先級(jí)隊(duì)列(Priority Queue)

這種數(shù)據(jù)結(jié)構(gòu)有一個(gè)很有用的功能,你插入或者刪除元素的時(shí)候,元素會(huì)自動(dòng)排序,這底層的原理就是二叉堆的操作。優(yōu)先級(jí)隊(duì)列有兩個(gè)主要 API,分別是 insert 插入一個(gè)元素和 delMax 刪除最大元素(如果底層用最小堆,那么就是 delMin)。

插入是先插到最后,然后上浮到正確位置;刪除是調(diào)換位置后再刪除,然后下沉到正確位置

放入PriorityQueue的元素,必須實(shí)現(xiàn)Comparable接口,沒有實(shí)現(xiàn)Comparable接口怎么辦?PriorityQueue允許我們提供一個(gè)Comparator對(duì)象來判斷兩個(gè)元素的順序

面試:說一說 PriorityQueue

PriorityQueue 是在 JDK1.5 中被引入的, 其與 Queue 的區(qū)別在于元素出隊(duì)順序是與優(yōu)先級(jí)相關(guān)的,即總是優(yōu)先級(jí)最高的元素先出隊(duì)。

這里列舉其相關(guān)的一些要點(diǎn):

  • PriorityQueue 利用了二叉堆的數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)的,底層使用可變長(zhǎng)的數(shù)組來存儲(chǔ)數(shù)據(jù)
  • PriorityQueue 通過堆元素的上浮和下沉,實(shí)現(xiàn)了在 O(logn) 的時(shí)間復(fù)雜度內(nèi)插入元素和刪除堆頂元素。
  • PriorityQueue 是非線程安全的,且不支持存儲(chǔ) NULLnon-comparable 的對(duì)象。
  • PriorityQueue 默認(rèn)是小頂堆,但可以接收一個(gè) Comparator 作為構(gòu)造參數(shù),從而來自定義元素優(yōu)先級(jí)的先后。

鏈表

1、單鏈表

遞歸反轉(zhuǎn)

經(jīng)典:206. 反轉(zhuǎn)鏈表

給你單鏈表的頭節(jié)點(diǎn) head ,請(qǐng)你反轉(zhuǎn)鏈表,并返回反轉(zhuǎn)后的鏈表。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        // 如果鏈表為空或者只有一個(gè)節(jié)點(diǎn)的時(shí)候,反轉(zhuǎn)結(jié)果就是它自己,直接返回即可
        if(head == null || head.next == null){
            return head;
        }
        // 遞歸反轉(zhuǎn)
        ListNode last = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return last;
    }
}

衍生題目:

反轉(zhuǎn)鏈表前 N 個(gè)節(jié)點(diǎn)
ListNode successor = null; // 后驅(qū)節(jié)點(diǎn)

// 反轉(zhuǎn)以 head 為起點(diǎn)的 n 個(gè)節(jié)點(diǎn),返回新的頭結(jié)點(diǎn)
ListNode reverseN(ListNode head, int n) {
    if (n == 1) {
        // 記錄第 n + 1 個(gè)節(jié)點(diǎn)
        successor = head.next;
        return head;
    }
    // 以 head.next 為起點(diǎn),需要反轉(zhuǎn)前 n - 1 個(gè)節(jié)點(diǎn)
    ListNode last = reverseN(head.next, n - 1);

    head.next.next = head;
    // 讓反轉(zhuǎn)之后的 head 節(jié)點(diǎn)和后面的節(jié)點(diǎn)連起來
    head.next = successor;
    return last;
}
92. 反轉(zhuǎn)鏈表 II

給你單鏈表的頭指針 head 和兩個(gè)整數(shù) left 和 right ,其中 left <= right 。請(qǐng)你反轉(zhuǎn)從位置 left 到位置 right 的鏈表節(jié)點(diǎn),返回 反轉(zhuǎn)后的鏈表 。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        // base case
        if(left == 1){
            return reverseN(head, right);
        }
        // 前進(jìn)到反轉(zhuǎn)的起點(diǎn)觸發(fā) base case
        head.next = reverseBetween(head.next, left - 1, right - 1);
        return head;
    }
    // 后繼節(jié)點(diǎn)
    ListNode successor = null;
    // 反轉(zhuǎn)以 head 為起點(diǎn)的 n 個(gè)節(jié)點(diǎn),返回新的頭結(jié)點(diǎn)
    private ListNode reverseN(ListNode head, int n){
        if(n == 1){
            // 記錄第 n + 1 個(gè)節(jié)點(diǎn)
            successor = head.next;
            return head;
        }
        // 以 head.next 為起點(diǎn),需要反轉(zhuǎn)前 n - 1 個(gè)節(jié)點(diǎn)
        ListNode last = reverseN(head.next, n - 1);
        head.next.next = head;
        // 讓反轉(zhuǎn)之后的 head 節(jié)點(diǎn)和后面的節(jié)點(diǎn)連起來
        head.next = successor;
        return last;
    }
}

迭代反轉(zhuǎn)

給定鏈表頭結(jié)點(diǎn),如何反轉(zhuǎn)整個(gè)鏈表?

「反轉(zhuǎn)以 a 為頭結(jié)點(diǎn)的鏈表」其實(shí)就是「反轉(zhuǎn) a 到 null 之間的結(jié)點(diǎn)」

// 反轉(zhuǎn)以 a 為頭結(jié)點(diǎn)的鏈表
ListNode reverse(ListNode a) {
    ListNode pre, cur, nxt;
    pre = null; cur = a; nxt = a;
    while (cur != null) {
        nxt = cur.next;
        // 逐個(gè)結(jié)點(diǎn)反轉(zhuǎn)
        cur.next = pre;
        // 更新指針位置
        pre = cur;
        cur = nxt;
    }
    // 返回反轉(zhuǎn)后的頭結(jié)點(diǎn)
    return pre;
}
反轉(zhuǎn) ab 之間的結(jié)點(diǎn)
/** 反轉(zhuǎn)區(qū)間 [a, b) 的元素,注意是左閉右開 */
ListNode reverse(ListNode a, ListNode b) {
    ListNode pre, cur, nxt;
    pre = null; cur = a; nxt = a;
    // while 終止的條件改一下就行了
    while (cur != b) {
        nxt = cur.next;
        cur.next = pre;
        pre = cur;
        cur = nxt;
    }
    // 返回反轉(zhuǎn)后的頭結(jié)點(diǎn)
    return pre;
}
25. K 個(gè)一組翻轉(zhuǎn)鏈表[hard]

給你鏈表的頭節(jié)點(diǎn) head ,每 k 個(gè)節(jié)點(diǎn)一組進(jìn)行翻轉(zhuǎn),請(qǐng)你返回修改后的鏈表。

k 是一個(gè)正整數(shù),它的值小于或等于鏈表的長(zhǎng)度。如果節(jié)點(diǎn)總數(shù)不是 k 的整數(shù)倍,那么請(qǐng)將最后剩余的節(jié)點(diǎn)保持原有順序。

你不能只是單純的改變節(jié)點(diǎn)內(nèi)部的值,而是需要實(shí)際進(jìn)行節(jié)點(diǎn)交換。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if(head == null) return null;
        // 區(qū)間 [a, b) 包含 k 個(gè)待反轉(zhuǎn)元素
        ListNode a, b;
        a = b = head;
        for(int i = 0; i < k; i++){
            // 不足 k 個(gè),不需要反轉(zhuǎn),base case
            if(b == null) return head;
            b = b.next;
        }
        // 反轉(zhuǎn)前 k 個(gè)元素
        ListNode newHead = reverse(a, b);
        // 遞歸反轉(zhuǎn)后續(xù)鏈表并連接起來
        a.next = reverseKGroup(b, k);
        return newHead;
    }
    /** 反轉(zhuǎn)區(qū)間 [a, b) 的元素,注意是左閉右開 */
    private ListNode reverse(ListNode a, ListNode b){
        ListNode pre, cur, nxt;
        pre = null; cur = a; nxt = a;
        while(cur != b){
            // 逐個(gè)結(jié)點(diǎn)反轉(zhuǎn)
            nxt = cur.next;
            cur.next = pre;
            // 更新指針位置
            pre = cur;
            cur = nxt;
        }
        // 返回反轉(zhuǎn)后的頭結(jié)點(diǎn)
        return pre;
    }
}

2、雙鏈表

21. 合并兩個(gè)有序鏈表

將兩個(gè)升序鏈表合并為一個(gè)新的 升序 鏈表并返回。新鏈表是通過拼接給定的兩個(gè)鏈表的所有節(jié)點(diǎn)組成的。

思路:

  • 先初始化一個(gè)dummy = new ListNode(0) 為空的節(jié)點(diǎn),定義一個(gè)ListNode p = dummy用來方便指針移動(dòng)
  • 如果list1 list2都不為空,作比較,小的賦值給p.next,
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
      // 虛擬頭結(jié)點(diǎn)
      ListNode dummy = new ListNode(0);
      ListNode p = dummy;
      ListNode p1 = list1, p2 = list2;

      while(p1 != null && p2 != null){
        // 比較 p1 和 p2 兩個(gè)指針
        // 將值較小的的節(jié)點(diǎn)接到 p 指針
          if(p1.val > p2.val){
             p.next = p2;
             p2 = p2.next;
          }else{
              p.next = p1;
              p1 = p1.next;
          }
          // p 指針不斷前進(jìn)
          p = p.next;
      }
        if(p1 != null){
            p.next = p1;
        }
        if(p2 != null){
            p.next = p2;
        }
    
    return dummy.next;
    }
}

86. 分隔鏈表

給你一個(gè)鏈表的頭節(jié)點(diǎn) head 和一個(gè)特定值 x ,請(qǐng)你對(duì)鏈表進(jìn)行分隔,使得所有 小于 x 的節(jié)點(diǎn)都出現(xiàn)在 大于或等于 x 的節(jié)點(diǎn)之前。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode partition(ListNode head, int x) {
         // 存放小于 x 的鏈表的虛擬頭結(jié)點(diǎn)
        ListNode dummy1 = new ListNode(0);
        // 存放大于等于 x 的鏈表的虛擬頭結(jié)點(diǎn)
        ListNode dummy2 = new ListNode(0);
        // p 負(fù)責(zé)遍歷原鏈表,類似合并兩個(gè)有序鏈表的邏輯
        ListNode p = head;
        // p1, p2 指針負(fù)責(zé)生成結(jié)果鏈表
        ListNode p1 = dummy1, p2 = dummy2;
        // 這里是將一個(gè)鏈表分解成兩個(gè)鏈表
        while(p != null){
            if(p.val > x){
                p2.next = p;
                p2 = p2.next;
            }else{
                p1.next = p;
                p1 = p1.next;
            }
            // 斷開原鏈表中的每個(gè)節(jié)點(diǎn)的 next 指針
            ListNode temp = p.next;
            p.next = null;
            p = temp;
        }
        // 連接兩個(gè)鏈表
       p1.next= dummy2.next;
       return dummy1.next;
    }
}

23. 合并 K 個(gè)升序鏈表[hard]

給你一個(gè)鏈表數(shù)組,每個(gè)鏈表都已經(jīng)按升序排列。

請(qǐng)你將所有鏈表合并到一個(gè)升序鏈表中,返回合并后的鏈表。

這個(gè)算法是面試??碱},它的時(shí)間復(fù)雜度是多少呢?算法整體的時(shí)間復(fù)雜度是 O(Nlogk),其中 k 是鏈表的條數(shù),N 是這些鏈表的節(jié)點(diǎn)總數(shù)。

**思路:**合并 k 個(gè)有序鏈表的邏輯類似合并兩個(gè)有序鏈表,難點(diǎn)在于,如何快速得到 k 個(gè)節(jié)點(diǎn)中的最小節(jié)點(diǎn),接到結(jié)果鏈表上?考慮使用優(yōu)先隊(duì)列,把k個(gè)鏈表加入隊(duì)列中,當(dāng)隊(duì)列不為空,拉出一個(gè)元素,指向到p.next

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists.length == 0){
            return null;
        }
        // 虛擬頭結(jié)點(diǎn)
        ListNode dummy = new ListNode(0);
        ListNode p = dummy;
        // 優(yōu)先級(jí)隊(duì)列,最小堆
        PriorityQueue<ListNode> pq = new PriorityQueue<>(lists.length,(a,b)->(a.val-b.val));
        // 將 k 個(gè)鏈表的頭結(jié)點(diǎn)加入最小堆
        for(ListNode head : lists){
            if(head!=null){
                pq.add(head);
            }
        }
        while(!pq.isEmpty()){
             // 獲取最小節(jié)點(diǎn),接到結(jié)果鏈表中
            ListNode node = pq.poll();
            p.next = node;
            if(node.next != null){
                pq.add(node.next);
            }
             // p 指針不斷前進(jìn)
            p = p.next;
        }
        return dummy.next;
    }
}

3、雙指針類型的題目

雙指針技巧主要分為兩類:左右指針快慢指針

19. 刪除鏈表的倒數(shù)第 N 個(gè)結(jié)點(diǎn)

給你一個(gè)鏈表,刪除鏈表的倒數(shù)第 n 個(gè)結(jié)點(diǎn),并且返回鏈表的頭結(jié)點(diǎn)。

注意細(xì)節(jié):又使用了虛擬頭結(jié)點(diǎn)的技巧,也是為了防止出現(xiàn)空指針的情況,比如說鏈表總共有 5 個(gè)節(jié)點(diǎn),題目就讓你刪除倒數(shù)第 5 個(gè)節(jié)點(diǎn),也就是第一個(gè)節(jié)點(diǎn),那按照算法邏輯,應(yīng)該首先找到倒數(shù)第 6 個(gè)節(jié)點(diǎn)。但第一個(gè)節(jié)點(diǎn)前面已經(jīng)沒有節(jié)點(diǎn)了,這就會(huì)出錯(cuò)。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        // 先找到倒數(shù)第n+1個(gè)
        ListNode x = findFromEnd(dummy, n+1);
        x.next = x.next.next;
        return dummy.next;
    }
    // 返回鏈表的倒數(shù)第 k 個(gè)節(jié)點(diǎn)
    private ListNode findFromEnd(ListNode head, int k){
        ListNode p1 = head;
        for(int i = 0; i < k; i++){
            p1 = p1.next;
        }
        ListNode p2 = head;
        while(p1 != null){
            p2 = p2.next;
            p1 = p1.next;
        }
        return p2;
    }
}

4、快慢指針的問題

876. 鏈表的中間結(jié)點(diǎn)

給你單鏈表的頭結(jié)點(diǎn) head ,請(qǐng)你找出并返回鏈表的中間結(jié)點(diǎn)。

如果有兩個(gè)中間結(jié)點(diǎn),則返回第二個(gè)中間結(jié)點(diǎn)。

思路:

我們讓兩個(gè)指針 slowfast 分別指向鏈表頭結(jié)點(diǎn) head。

每當(dāng)慢指針 slow 前進(jìn)一步,快指針 fast 就前進(jìn)兩步,這樣,當(dāng) fast 走到鏈表末尾時(shí),slow 就指向了鏈表中點(diǎn)。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode middleNode(ListNode head) {
        // 快慢指針初始化指向 head
        ListNode fast = head, slow = head; 
        // 快指針走到末尾時(shí)停止
        while(fast != null && fast.next != null){
            // 慢指針走一步,快指針走兩步
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
}

判斷鏈表是否包含環(huán)

判斷鏈表是否包含環(huán)屬于經(jīng)典問題了,解決方案也是用快慢指針:

每當(dāng)慢指針 slow 前進(jìn)一步,快指針 fast 就前進(jìn)兩步。

如果 fast 最終遇到空指針,說明鏈表中沒有環(huán);如果 fast 最終和 slow 相遇,那肯定是 fast 超過了 slow 一圈,說明鏈表中含有環(huán)。

boolean hasCycle(ListNode head) {
    // 快慢指針初始化指向 head
    ListNode slow = head, fast = head;
    // 快指針走到末尾時(shí)停止
    while (fast != null && fast.next != null) {
        // 慢指針走一步,快指針走兩步
        slow = slow.next;
        fast = fast.next.next;
        // 快慢指針相遇,說明含有環(huán)
        if (slow == fast) {
            return true;
        }
    }
    // 不包含環(huán)
    return false;
}

160. 相交鏈表

給你兩個(gè)單鏈表的頭節(jié)點(diǎn) headAheadB ,請(qǐng)你找出并返回兩個(gè)單鏈表相交的起始節(jié)點(diǎn)。如果兩個(gè)鏈表不存在相交節(jié)點(diǎn),返回 null 。

思路:

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int lenA = 0, lenB = 0;
        // 計(jì)算兩條鏈表的長(zhǎng)度
        for(ListNode p1 = headA; p1 != null; p1 = p1.next){
            lenA++;
        }
        for(ListNode p2 = headB; p2 != null; p2 = p2.next){
            lenB++;
        }
        // 讓 p1 和 p2 到達(dá)尾部的距離相同
        ListNode p1 = headA, p2 = headB;
        if(lenA > lenB){
            for(int i = 0; i < lenA -lenB; i++){
                p1 = p1.next;
            }
        } else {
            for(int i = 0; i < lenB - lenA; i++){
                p2 = p2.next;
            }
        }
        // 看兩個(gè)指針是否會(huì)相同,p1 == p2 時(shí)有兩種情況:
        // 1、要么是兩條鏈表不相交,他倆同時(shí)走到尾部空指針
        // 2、要么是兩條鏈表相交,他倆走到兩條鏈表的相交點(diǎn)
        while(p1 != p2){
            p1 = p1.next;
            p2 = p2.next;
        }
        return p1;
    }
}

5、回文鏈表

尋找回文串

核心思想是從中心向兩端擴(kuò)展

因?yàn)榛匚拇L(zhǎng)度可能為奇數(shù)也可能是偶數(shù),長(zhǎng)度為奇數(shù)時(shí)只存在一個(gè)中心點(diǎn),而長(zhǎng)度為偶數(shù)時(shí)存在兩個(gè)中心點(diǎn),所以上面這個(gè)函數(shù)需要傳入 lr。

// 在 s 中尋找以 s[left] 和 s[right] 為中心的最長(zhǎng)回文串
String palindrome(String s, int left, int right) {
    // 防止索引越界
    while (left >= 0 && right < s.length()
            && s.charAt(left) == s.charAt(right)) {
        // 雙指針,向兩邊展開
        left--;
        right++;
    }
    // 返回以 s[left] 和 s[right] 為中心的最長(zhǎng)回文串
    return s.substring(left + 1, right);
}

判斷一個(gè)字符串是不是回文串

因?yàn)榛匚拇菍?duì)稱的,所以正著讀和倒著讀應(yīng)該是一樣的,這一特點(diǎn)是解決回文串問題的關(guān)鍵。

boolean isPalindrome(String s) {
    // 一左一右兩個(gè)指針相向而行
    int left = 0, right = s.length() - 1;
    while (left < right) {
        if (s.charAt(left) != s.charAt(right)) {
            return false;
        }
        left++;
        right--;
    }
    return true;
}

234. 回文鏈表

給你一個(gè)單鏈表的頭節(jié)點(diǎn) head ,請(qǐng)你判斷該鏈表是否為回文鏈表。如果是,返回 true ;否則,返回 false

這道題的關(guān)鍵在于,單鏈表無法倒著遍歷,無法使用雙指針技巧

1、最簡(jiǎn)單的辦法就是,把原始鏈表反轉(zhuǎn)存入一條新的鏈表,然后比較這兩條鏈表是否相同

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode head2 = reverseList(head);
        while(head2 != null && head != null){
            if(head.val != head2.val) return false;
            head = head.next;
            head2 = head2.next;
        }
        return true;
    }
    private ListNode reverseList(ListNode head){
        if(head == null || head.next == null) return head;
        ListNode last = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return last;
    }
}

2、其實(shí),借助二叉樹后序遍歷的思路,不需要顯式反轉(zhuǎn)原始鏈表也可以倒序遍歷鏈表

3、快慢指針,降低空間復(fù)雜度

class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode slow, fast;
        slow = fast = head;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        // 如果fast指針沒有指向null,說明鏈表長(zhǎng)度為奇數(shù),slow還要再前進(jìn)一步:
        if(fast != null){
            slow = slow.next;
        }
        // 從slow開始反轉(zhuǎn)后面的鏈表,現(xiàn)在就可以開始比較回文串了
        ListNode left = head;
        ListNode right = reverse(slow);
        while(right != null){
            if(left.val != right.val) return false;
            left = left.next;
            right = right.next;
        }
        return true;
    }
    // 迭代反轉(zhuǎn)
    private ListNode reverse(ListNode head){
        ListNode pre = null, next = null, cur = head;
        while (cur != null) {
            next = cur.next;
            // 逐個(gè)結(jié)點(diǎn)反轉(zhuǎn)
            cur.next = pre;
            // 更新指針位置
            pre = cur;
            cur = next;
        }
    // 返回反轉(zhuǎn)后的頭結(jié)點(diǎn)
    return pre;
    }
}

總結(jié):

首先,尋找回文串是從中間向兩端擴(kuò)展,判斷回文串是從兩端向中間收縮。對(duì)于單鏈表,無法直接倒序遍歷,可以造一條新的反轉(zhuǎn)鏈表,可以利用鏈表的后序遍歷,也可以用棧結(jié)構(gòu)倒序處理單鏈表。

具體到回文鏈表的判斷問題,由于回文的特殊性,可以不完全反轉(zhuǎn)鏈表,而是僅僅反轉(zhuǎn)部分鏈表,將空間復(fù)雜度降到 O(1)。

–end–文章來源地址http://www.zghlxwxcb.cn/news/detail-404940.html

到了這里,關(guān)于LeetCode算法小抄 -- 鏈表(快慢指針、雙指針、回文鏈表)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【LeetCode】 雙指針,快慢指針解題

    【LeetCode】 雙指針,快慢指針解題

    1.刪除有序數(shù)組中的重復(fù)項(xiàng) 2.移除元素

    2024年02月11日
    瀏覽(22)
  • 【Java|golang】143. 重排鏈表---快慢指針

    【Java|golang】143. 重排鏈表---快慢指針

    給定一個(gè)單鏈表 L 的頭節(jié)點(diǎn) head ,單鏈表 L 表示為: L0 → L1 → … → Ln - 1 → Ln 請(qǐng)將其重新排列后變?yōu)椋?L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … 不能只是單純的改變節(jié)點(diǎn)內(nèi)部的值,而是需要實(shí)際的進(jìn)行節(jié)點(diǎn)交換。 示例 1: 輸入:head = [1,2,3,4] 輸出:[1,4,2,3] 示例 2: 輸入:

    2024年02月14日
    瀏覽(13)
  • 環(huán)形鏈表 II(力扣142)(快慢指針)

    環(huán)形鏈表 II(力扣142)(快慢指針)

    142.環(huán)形鏈表—力扣 給定一個(gè)鏈表的頭節(jié)點(diǎn) ?head?,返回鏈表開始入環(huán)的第一個(gè)節(jié)點(diǎn)。?如果鏈表無環(huán),則返回?null。 如果鏈表中有某個(gè)節(jié)點(diǎn),可以通過連續(xù)跟蹤 next 指針再次到達(dá),則鏈表中存在環(huán)。 為了表示給定鏈表中的環(huán),評(píng)測(cè)系統(tǒng)內(nèi)部使用整數(shù) pos 來表示鏈表尾連接到

    2023年04月25日
    瀏覽(22)
  • 快慢指針該如何操作?本文帶你認(rèn)識(shí)快慢指針常見的三種用法及在鏈表中的實(shí)戰(zhàn)

    快慢指針該如何操作?本文帶你認(rèn)識(shí)快慢指針常見的三種用法及在鏈表中的實(shí)戰(zhàn)

    很多同學(xué)都聽過 快慢指針 這個(gè)名詞,認(rèn)為它不就是定義兩個(gè)引用(指針)一前一后嗎?是的,它的奧秘很深,它的作用究竟有哪些?究竟可以用來做哪些題目?下面我將一一帶你了解和應(yīng)用 下面的本節(jié)的大概內(nèi)容,有疑惑的點(diǎn),歡迎小伙伴們留言 目錄 1.簡(jiǎn)述快慢指針 2.快慢

    2024年02月04日
    瀏覽(18)
  • 【C/C++數(shù)據(jù)結(jié)構(gòu)】鏈表與快慢指針

    【C/C++數(shù)據(jù)結(jié)構(gòu)】鏈表與快慢指針

    目錄 一、單鏈表 二、雙向循環(huán)鏈表 三、判斷鏈表是否帶環(huán) 四、鏈表的回文結(jié)構(gòu)判斷 五、復(fù)制帶隨機(jī)指針的鏈表 優(yōu)點(diǎn) :頭部增刪效率高,動(dòng)態(tài)存儲(chǔ)無空間浪費(fèi) 缺點(diǎn) :尾部增刪、遍歷效率低,不支持隨機(jī)訪問節(jié)點(diǎn) 頭結(jié)點(diǎn) :?jiǎn)捂湵眍^結(jié)點(diǎn)可有可無,帶頭結(jié)點(diǎn)更方便進(jìn)行初始

    2024年02月16日
    瀏覽(436)
  • 力扣hot100 環(huán)形鏈表 快慢指針 哈希 數(shù)學(xué)公式

    力扣hot100 環(huán)形鏈表 快慢指針 哈希 數(shù)學(xué)公式

    Problem: 142. 環(huán)形鏈表 II ????? 參考題解 ? 時(shí)間復(fù)雜度: O ( n ) O(n) O ( n ) ?? 空間復(fù)雜度: O ( 1 ) O(1) O ( 1 )

    2024年01月23日
    瀏覽(24)
  • 力扣2095.刪除鏈表的中間節(jié)點(diǎn)(java快慢指針)

    Problem: 2095. 刪除鏈表的中間節(jié)點(diǎn) 利用快慢指針,快指針每次走兩步,慢指針每次走一步(循環(huán)退出條件是fast指針不為空同時(shí)fast.next不為空),但是我們?nèi)菀装l(fā)現(xiàn)這樣到最后slow指針正好指向我們需要?jiǎng)h除的節(jié)點(diǎn),由于沒有前指針,這樣我們不便操作。此時(shí)可以借助虛擬頭節(jié)點(diǎn)

    2024年02月06日
    瀏覽(22)
  • 【每日一題Day281】LC142鏈表 Ⅱ| 快慢指針 哈希表

    【每日一題Day281】LC142鏈表 Ⅱ| 快慢指針 哈希表

    環(huán)形鏈表 Ⅱ【LC142】 給定一個(gè)鏈表,返回鏈表開始入環(huán)的第一個(gè)節(jié)點(diǎn)。 如果鏈表無環(huán),則返回 null。 如果鏈表中有某個(gè)節(jié)點(diǎn),可以通過連續(xù)跟蹤 next 指針再次到達(dá),則鏈表中存在環(huán)。 為了表示給定鏈表中的環(huán),評(píng)測(cè)系統(tǒng)內(nèi)部使用整數(shù) pos 來表示鏈表尾連接到鏈表中的位置(

    2024年02月15日
    瀏覽(17)
  • LeetCode算法小抄--滑動(dòng)窗口算法

    ?申明: 未經(jīng)許可,禁止以任何形式轉(zhuǎn)載,若要引用,請(qǐng)標(biāo)注鏈接地址。 全文共計(jì)6244字,閱讀大概需要3分鐘 ??更多學(xué)習(xí)內(nèi)容, 歡迎??關(guān)注??文末我的個(gè)人微信公眾號(hào):不懂開發(fā)的程序猿 個(gè)人網(wǎng)站:https://jerry-jy.co/ 滑動(dòng)窗口算法 思路 1、我們?cè)谧址?S 中使用雙指針中的

    2023年04月09日
    瀏覽(25)
  • 【算法|數(shù)組】快慢指針

    【算法|數(shù)組】快慢指針

    給你一個(gè)數(shù)組 nums 和一個(gè)值 val ,你需要 原地 移除所有數(shù)值等于 val 的元素,并返回移除后數(shù)組的新長(zhǎng)度。 不要使用額外的數(shù)組空間,你必須僅使用 O(1) 額外空間并 原地 修改輸入數(shù)組 。 元素的順序可以改變。你不需要考慮數(shù)組中超出新長(zhǎng)度后面的元素。 首先有一個(gè)點(diǎn)我們

    2024年02月13日
    瀏覽(25)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包