搞懂單鏈表常見(jiàn)面試題

搞懂單鏈表常見(jiàn)面試題

Hello 繼上次的 搞懂基本排序算法,這個(gè)一星期,我總結(jié)了,我所學(xué)習(xí)和思考的單鏈表基礎(chǔ)知識(shí)和常見(jiàn)面試題,這些題有的來(lái)自 《劍指 offer》 ,有的來(lái)自《程序員代碼面試指南》,有的來(lái)自 leetCode,不是很全面,但都具有一定代表性,相信大家看完以后一定跟我一樣,對(duì)面試的時(shí)候算法題又多了一份自信。

什么是單鏈表

鏈表(Linked list)是一種常見(jiàn)的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),是一種線性表,但是并不會(huì)按線性的順序存儲(chǔ)數(shù)據(jù),而是在每一個(gè)節(jié)點(diǎn)里存到下一個(gè)節(jié)點(diǎn)的指針(Pointer),簡(jiǎn)單來(lái)說(shuō)鏈表并不像數(shù)組那樣將數(shù)組存儲(chǔ)在一個(gè)連續(xù)的內(nèi)存地址空間里,它們可以不是連續(xù)的因?yàn)樗麄兠總€(gè)節(jié)點(diǎn)保存著下一個(gè)節(jié)點(diǎn)的引用(地址),所以較之?dāng)?shù)組來(lái)說(shuō)這是一個(gè)優(yōu)勢(shì)。

對(duì)于單鏈表的一個(gè)節(jié)點(diǎn)我們經(jīng)常使用下邊這種代碼表示:

public class Node{
    //節(jié)點(diǎn)的值
    int value;
    //指向下一個(gè)節(jié)點(diǎn)的指針(java 中表現(xiàn)為下一個(gè)節(jié)點(diǎn)的引用)
    Node next;
    
    public void Node(int value){
        this.value = value;
    }
}

單鏈表的特點(diǎn)

  1. 鏈表增刪元素的時(shí)間復(fù)雜度為O(1),查找一個(gè)元素的時(shí)間復(fù)雜度為 O(n);
  2. 單鏈表不用像數(shù)組那樣預(yù)先分配存儲(chǔ)空間的大小,避免了空間浪費(fèi)
  3. 單鏈表不能進(jìn)行回溯操作,如:只知道鏈表的頭節(jié)點(diǎn)的時(shí)候無(wú)法快讀快速鏈表的倒數(shù)第幾個(gè)節(jié)點(diǎn)的值。

單鏈表的基本操作

上一節(jié)我們說(shuō)了什么是單鏈表,那么我們都知道一個(gè)數(shù)組它具有增刪改查的基本操作,那么我們單鏈表作為一種常見(jiàn)的數(shù)據(jù)結(jié)構(gòu)類型也是具有這些操作的那么我們就來(lái)看下對(duì)于單鏈表有哪些基本操作:

獲取單鏈表的長(zhǎng)度

由于單鏈表的存儲(chǔ)地址不是連續(xù)的,鏈表并不具有直接獲取鏈表長(zhǎng)度的功能,對(duì)于一個(gè)鏈表的長(zhǎng)度我們只能一次去遍歷鏈表的節(jié)點(diǎn),直到找到某個(gè)節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)為空的時(shí)候得到鏈表的總長(zhǎng)度,注意這里的出發(fā)點(diǎn)并不是一個(gè)空鏈表然后依次添加節(jié)點(diǎn)后,然后去讀取已經(jīng)記錄的節(jié)點(diǎn)個(gè)數(shù),而是已知一個(gè)鏈表的頭結(jié)點(diǎn)然后去獲取這個(gè)鏈表的長(zhǎng)度:

public int getLength(Node head){
    
    if(head == null){
        return 0;
    }
    
    int len = 0;
    while(head != null){
        len++;
        head = head.next;
    }  
    return len;  
}

查詢指定索引的節(jié)點(diǎn)值或指定值得節(jié)點(diǎn)值的索引

由于鏈表是一種非連續(xù)性的存儲(chǔ)結(jié)構(gòu),節(jié)點(diǎn)的內(nèi)存地址不是連續(xù)的,也就是說(shuō)鏈表不能像數(shù)組那樣可以通過(guò)索引值獲取索引位置的元素。所以鏈表的查詢的時(shí)間復(fù)雜度要是O(n)級(jí)別的,這點(diǎn)和數(shù)組查詢指定值得元素位置是相同的,因?yàn)槟阋檎业臇|西在內(nèi)存中的存儲(chǔ)地址都是不一定的。

    /** 獲取指定角標(biāo)的節(jié)點(diǎn)值 */
    public int getValueOfIndex(Node head, int index) throws Exception {

        if (index < 0 || index >= getLength(head)) {
            throw new Exception("角標(biāo)越界!");
        }

        if (head == null) {
            throw new Exception("當(dāng)前鏈表為空!");
        }

        Node dummyHead = head;

        while (dummyHead.next != null && index > 0) {
            dummyHead = dummyHead.next;
            index--;
        }

        return dummyHead.value;
    }

    
    /** 獲取節(jié)點(diǎn)值等于 value 的第一個(gè)元素角標(biāo) */
    public int getNodeIndex(Node head, int value) {
    
            int index = -1;
            Node dummyHead = head;
    
            while (dummyHead != null) {
                index++;
                if (dummyHead.value == value) {
                    return index;
                }
                dummyHead = dummyHead.next;
            }
    
            return -1;
    }

鏈表添加一個(gè)元素

學(xué)過(guò)數(shù)據(jù)結(jié)構(gòu)的朋友一定知道鏈表的插入操作,分為頭插法,尾插法,隨機(jī)節(jié)點(diǎn)插入法,當(dāng)然數(shù)據(jù)結(jié)構(gòu)講得時(shí)候也是針對(duì)一個(gè)已經(jīng)構(gòu)造好的(保存了鏈表頭部節(jié)點(diǎn)和尾部節(jié)點(diǎn)引用)的情況下去插入一個(gè)元素,這看上去很簡(jiǎn)單,如果我們?cè)谥恢酪粋€(gè)鏈表的頭節(jié)點(diǎn)的情況下去插入一個(gè)元素,就不是那么簡(jiǎn)單了,就對(duì)于頭插入法我們只需要構(gòu)造一個(gè)新的節(jié)點(diǎn),然后將這個(gè)節(jié)點(diǎn)的 next 指針指向已知鏈表的頭節(jié)點(diǎn)就可以了。

1、 在已有鏈表頭部插入一個(gè)節(jié)點(diǎn)

public Node addAtHead(Node head, int value){
     Node newHead = new Node(value);
     newHead.next = head;
     return newHead;
}

2、在已有鏈表的尾部插入一個(gè)節(jié)點(diǎn):

public void addAtTail(Node head, int value){
     Node node = new Node(value);
     Node dummyHead = head;
     
     //找到未節(jié)點(diǎn) 注意這里是當(dāng)元素的下一個(gè)元素為空的時(shí)候這個(gè)節(jié)點(diǎn)即為未節(jié)點(diǎn)
     while( dummyHead.next != null){
        dummyHead = dummyHead.next;
     }
     
     dummyHead.next = node;   
}

3、在指定位置添加一個(gè)節(jié)點(diǎn)

// 注意這里 index 從 0 開(kāi)始
 public Node insertElement(Node head, int value, int index) throws Exception {
   //為了方便這里我們假設(shè)知道鏈表的長(zhǎng)度
   int length = getLength(head);
   if (index < 0 || index >= length) {
       throw new Exception("角標(biāo)越界!");
   }

   if (index == 0) {
       return addAtHead(head, value);
   } else if (index == length - 1) {
       addAtTail(head, value);
   } else {

       Node pre = head;
       Node cur = head.next;
       //
       while (pre != null && index > 1) {
           pre = pre.next;
           cur = cur.next;
           index--;
       }

       //循環(huán)結(jié)束后 pre 保存的是索引的上一個(gè)節(jié)點(diǎn) 而 cur 保存的是索引值當(dāng)前的節(jié)點(diǎn)
       Node node = new Node(value);
       pre.next = node;
       node.next = cur;
   }
   return head;
}

在指定位置添加一個(gè)節(jié)點(diǎn),首先我們應(yīng)該找到這個(gè)索引所在的節(jié)點(diǎn)的前一個(gè),以及該節(jié)點(diǎn),分別記錄這兩個(gè)節(jié)點(diǎn),然后將索引所在節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)的 next 指針指向新節(jié)點(diǎn),然后將新節(jié)點(diǎn)的 next 指針指向插入節(jié)點(diǎn)即可。與其他元素并沒(méi)有什么關(guān)系,所以單鏈表插入一個(gè)節(jié)點(diǎn)時(shí)間復(fù)雜度為 O(1),而數(shù)組插入元素就不一樣了如果將一個(gè)元素插入數(shù)組的指定索引位置,那么該索引位置以后元素的索引位置(內(nèi)存地址)都將發(fā)生變化,所以一個(gè)數(shù)組的插入一個(gè)元素的時(shí)間復(fù)雜度為 O(n);所以鏈表相對(duì)于數(shù)組插入的效率要高一些,刪除同理。

鏈表刪除一個(gè)元素

由于上邊介紹了鏈表添加元素的方法這里對(duì)于鏈表刪除節(jié)點(diǎn)的方法不在詳細(xì)介紹直接給出代碼:

1、 刪除頭部節(jié)點(diǎn) 也就是刪除索引為 0 的節(jié)點(diǎn):

    public Node deleteHead(Node head) throws Exception {
        if (head == null) {
            throw new Exception("當(dāng)前鏈表為空!");
        }
        return head.next;
    }

2、 刪除尾節(jié)點(diǎn)

    public void deleteTail(Node head) throws Exception {

        if (head == null) {
            throw new Exception("當(dāng)前鏈表為空!");
        }

        Node dummyHead = head;
        while (dummyHead.next != null && dummyHead.next.next != null) {
            dummyHead = dummyHead.next;
        }
        dummyHead.next = null;
    }

3、 刪除指定索引的節(jié)點(diǎn):

public Node deleteElement(Node head, int index) throws Exception {

   int size = getLength(head);
   
   if (index < 0 || index >= size) {
       throw new Exception("角標(biāo)越界!");
   }

   if (index == 0) {
       return deleteHead(head);
   } else if (index == size - 1) {
       deleteTail(head);
   } else {
       Node pre = head;

       while (pre.next != null && index > 1) {
           pre = pre.next;
           index--;
       }

       //循環(huán)結(jié)束后 pre 保存的是索引的上一個(gè)節(jié)點(diǎn) 將其指向索引的下一個(gè)元素
       if (pre.next != null) {
           pre.next = pre.next.next;
       }
   }

   return head;
}

由單鏈表的增加刪除可以看出,鏈表的想要對(duì)指定索引進(jìn)行操作(增加,刪除),的時(shí)候必須獲取該索引的前一個(gè)元素。記住這句話,對(duì)鏈表算法題很有用。

單鏈表常見(jiàn)面試題

介紹了鏈表的常見(jiàn)操作以后,我們的目標(biāo)是學(xué)習(xí)鏈表常見(jiàn)的面試題目,不然我們學(xué)他干嘛呢,哈哈~ 開(kāi)個(gè)玩笑那么我們就先從簡(jiǎn)單的面試題開(kāi)始:

尋找單鏈表的中間元素

同學(xué)們可能看到這道面試題笑了,咋這么簡(jiǎn)單,拿起筆來(lái)就開(kāi)始寫(xiě),遍歷整個(gè)鏈表,拿到鏈表的長(zhǎng)度len,再次遍歷鏈表那么位于 len/2 位置的元素就是鏈表的中間元素。

咱也不能說(shuō)這種方法不對(duì),想想一下一個(gè)騰訊的面試官坐在對(duì)面問(wèn)這個(gè)問(wèn)題,這個(gè)回答顯然連自己這一關(guān)都很難過(guò)去。那么更漸快的方法是什么呢?或者說(shuō)時(shí)間復(fù)雜度更小的方法如何實(shí)現(xiàn)這次查找?這里引出一個(gè)很關(guān)鍵的概念就是 快慢指針?lè)?/strong>,這也是面試官想考察的。

假如我們?cè)O(shè)置 兩個(gè)指針 slow、fast 起始都指向單鏈表的頭節(jié)點(diǎn)。其中 fast 的移動(dòng)速度是 slow 的2倍。當(dāng) fast 指向末尾節(jié)點(diǎn)的時(shí)候,slow 正好就在中間了。想想一下是不是這樣假設(shè)一個(gè)鏈表長(zhǎng)度為 6 , slow 每次一個(gè)節(jié)點(diǎn)位置, fast 每次移動(dòng)兩個(gè)節(jié)點(diǎn)位置,那么當(dāng)fast = 5的時(shí)候 slow = 2 正好移動(dòng)到 2 的節(jié)點(diǎn)的位置。

所以求解鏈表中間元素的解題思路是:

    public Node getMid(Node head){
      if(head == null){
         return null;
      }
      
      Node slow = head;
      Node fast = head;
      
      // fast.next = null 表示 fast 是鏈表的尾節(jié)點(diǎn)
      while(fast != null && fast.next != null){
         fast = fast.next.next;
         slow = slow.next;
      }
      return slow;
    }

判斷一個(gè)鏈表是否是循環(huán)鏈表

首先此題也是也是考察快慢指針的一個(gè)題,也是快慢指針的第二個(gè)應(yīng)用。先簡(jiǎn)單說(shuō)一下什么循環(huán)鏈表,循環(huán)鏈表其實(shí)就是單鏈表的尾部指針指向頭指針,構(gòu)建成一個(gè)環(huán)形的鏈表,叫做循環(huán)鏈表。 如 1 -> 2 - > 3 -> 1 -> 2 .....。為什么快慢指針再循環(huán)鏈表中總能相遇呢?你可以想象兩個(gè)人在賽跑,A的速度快,B的速度慢,經(jīng)過(guò)一定時(shí)間后,A總是會(huì)和B相遇,且相遇時(shí)A跑過(guò)的總距離減去B跑過(guò)的總距離一定是圈長(zhǎng)的n倍。這也就是 Floyd判環(huán)(圈)算法

那么如何使用快慢指針去判斷一個(gè)鏈表是否為環(huán)形鏈表呢:


private static boolean isLoopList(Node head){

        if (head == null){
            return false;
        }

        
        Node slow = head;
        Node fast = head.next;
        
        //如果不是循環(huán)鏈表那么一定有尾部節(jié)點(diǎn) 此節(jié)點(diǎn) node.next = null
        while(slow != null && fast != null && fast.next != null){
            if (fast == slow || fast.next == slow){
                return true;
            }
            // fast 每次走兩步  slow 每次走一步
            fast =fast.next.next;
            slow = slow.next;
        }
        //如果不是循環(huán)鏈表返回 false
        return false;
    }

已知一個(gè)單鏈表求倒數(shù)第 N 個(gè)節(jié)點(diǎn)

為什么這個(gè)題要放在快慢指針的后邊呢,因?yàn)檫@個(gè)題的解題思想和快慢指針相似,我們可以想一下:如果我們讓快指針先走 n-1 步后,然后讓慢指針出發(fā)。快慢指針每次都只移動(dòng)一個(gè)位置,當(dāng)快指針移動(dòng)到鏈表末尾的時(shí)候,慢指針是否就正處于倒數(shù)第 N 個(gè)節(jié)點(diǎn)的位置呢。

是這里把這兩個(gè)指針?lè)Q之為快慢指針是不正確的,因?yàn)榭炻羔樖侵敢粋€(gè)指針移動(dòng)的快一個(gè)指針移動(dòng)的慢,而此題中 快指針只是比慢指針先移動(dòng)了 n-1 個(gè)位置而已,移動(dòng)速度是相同的。

如果上邊的講解不好理解,這里提供另外一種思路,就是想象一下,上述快慢指針的移動(dòng)過(guò)程,是否就相當(dāng)于一個(gè)固定窗口大小為 n 的滑動(dòng)窗口:

  1. n = 1 fast 指針不移動(dòng) fast 到達(dá)最后一個(gè)節(jié)點(diǎn) 即 fast.next 的時(shí)候 slow 也到達(dá)尾部節(jié)點(diǎn)滿條件
  2. n = len fast 指針移動(dòng) n-1(len -1 ) 次 fast 到達(dá)最后一個(gè)節(jié)點(diǎn) slow 位于頭節(jié)點(diǎn)不變 滿足條件 兩個(gè)臨界值均滿足我們這種假設(shè)。
  3. 1< n < len 的時(shí)候我們假設(shè) n = 2 ,那么 fast 比 slow 先移動(dòng)一步,也就是窗口大小為 2, 那么當(dāng) fast.next = null 即 fast 已經(jīng)指向鏈表最后一個(gè)節(jié)點(diǎn)的時(shí)候,slow 就指向了 倒數(shù)第二個(gè)節(jié)點(diǎn)。

下面我們來(lái)看下函數(shù)實(shí)現(xiàn):

     /**
     * 注意我們一般說(shuō)倒數(shù)第 n 個(gè)元素 n 是從 1 開(kāi)始的
     */
    private Node getLastIndexNode(Node head, int n) {

        // 輸入的鏈表不能為空,并且 n 大于0
        if (n < 1 || head == null) {
            return null;
        }

        n = 10;
        // 指向頭結(jié)點(diǎn)
        Node fast = head;
        // 倒數(shù)第k個(gè)結(jié)點(diǎn)與倒數(shù)第一個(gè)結(jié)點(diǎn)相隔 n-1 個(gè)位置
        // fast 先走 n-1 個(gè)位置
        for (int i = 1; i < n; i++) {
            // 說(shuō)明還有結(jié)點(diǎn)
            if (fast.next != null) {
                fast = fast.next;
            }else {
                // 已經(jīng)沒(méi)有節(jié)點(diǎn)了,但是i還沒(méi)有到達(dá)k-1說(shuō)明k太大,鏈表中沒(méi)有那么多的元素
                return null;
            }
        }

        Node slow = head;
        // fast 還沒(méi)有走到鏈表的末尾,那么 fast 和 slow 一起走,
        // 當(dāng) fast 走到最后一個(gè)結(jié)點(diǎn)即,fast.next=null 時(shí),slow 就是倒數(shù)第 n 個(gè)結(jié)點(diǎn)
        while (fast.next != null) {
            slow = slow.next;
            fast = fast.next;
        }
        // 返回結(jié)果
        return slow;
}

刪除單鏈表的倒數(shù)第 n 個(gè)節(jié)點(diǎn)

看到這個(gè)題時(shí)候樂(lè)了,這考察的知識(shí)點(diǎn)不就是一道求解倒數(shù)第 n 個(gè)節(jié)點(diǎn)的進(jìn)化版么。但是我們也說(shuō)過(guò),如果想操作鏈表的某個(gè)節(jié)點(diǎn)(添加,刪除)還必須知道這個(gè)節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)。所以我們刪除倒數(shù)第 n 個(gè)元素就要找到倒數(shù)第 n + 1 個(gè)元素。然后將倒數(shù)第 n + 1個(gè)元素 p 的 next 指針 p.next 指向 p.next.next

我們找到倒數(shù)第 n 個(gè)節(jié)點(diǎn)的時(shí)候,先讓 fast 先走了 n-1 步,那么我們刪除倒數(shù)第 n 個(gè)節(jié)點(diǎn)的時(shí)候就需要 讓 fast 先走 n 步,構(gòu)建一個(gè) n+1 大小的窗口,然后 fast 和 slow 整體平移到鏈表尾部,slow 指向的節(jié)點(diǎn)就是 倒數(shù)第 n+1 個(gè)節(jié)點(diǎn)。

這里我們還可以使用滑動(dòng)窗口的思想來(lái)考慮臨界值:

  1. n = 1 的時(shí)候我們需要構(gòu)建的窗口為 2,也就是當(dāng) fast.next = null 的時(shí)候 slow 在的倒數(shù)第二個(gè)節(jié)點(diǎn)上,那么可想而知是滿足我們的條件的。

  2. 當(dāng) 1 < n < len 的時(shí)候我們總是能構(gòu)建出這樣的一個(gè) len + 1大小的窗口,n 最大為 len -1 的時(shí)候,slow 位于頭節(jié)點(diǎn),fast 位于未節(jié)點(diǎn),刪除倒數(shù)第 n 個(gè)元素,即刪除正數(shù)第二個(gè)節(jié)點(diǎn),slow.next = slow.next.next 即可。

  3. 當(dāng) n > len 的時(shí)候可想而知,我們要找的倒數(shù)第 n 個(gè)元素不存在,此時(shí)返回 頭節(jié)點(diǎn)就好了

  4. n = len 的時(shí)候比較特殊,循環(huán)并沒(méi)有因?yàn)榈箶?shù)第 len 個(gè)元素不存在而終止,并進(jìn)行了 fast = fast.next; 循環(huán)結(jié)束后 fast 指向 null , 且此時(shí) slow 位于頭節(jié)點(diǎn),所以我們要?jiǎng)h除的節(jié)點(diǎn)是頭節(jié)點(diǎn),只需要在循環(huán)結(jié)束后判斷 如果 fast == null 返回 head.next 即可

下面我們來(lái)看解法:

/**
 * 刪除倒是第 n 個(gè)節(jié)點(diǎn) 我們就要找到倒數(shù)第 n + 1 個(gè)節(jié)點(diǎn), 如果 n > len 則返回原列表
 */
private Node deleteLastNNode(Node head, int n) {

   if (head == null || n < 1) {
       return head;
   }

   Node fast = head;
   
   //注意 我們要構(gòu)建長(zhǎng)度為 n + 1 的窗口 所以 i 從 0 開(kāi)始
   for (int i = 0; i < n; i++) {
       //fast 指針指向倒數(shù)第一個(gè)節(jié)點(diǎn)的時(shí)候,就是要?jiǎng)h除頭節(jié)點(diǎn)
       if (fast == null) {
           return head;
       } else {
           fast = fast.next;
       }
   }

   // 由于 n = len 再循環(huán)內(nèi)部沒(méi)有判斷直接前進(jìn)了一個(gè)節(jié)點(diǎn),臨界值 n = len 的時(shí)候 循環(huán)完成或 fast = null
   if (fast == null){
       return head.next;
   }

   //此時(shí) n 一定是小于 len 的 且 fast 先走了 n 步
   Node pre = head;

   while (fast.next != null) {
       fast = fast.next;
       pre = pre.next;
   }

   pre.next = pre.next.next;

   return head;
}

旋轉(zhuǎn)單鏈表

題目:給定一個(gè)鏈表,旋轉(zhuǎn)鏈表,使得每個(gè)節(jié)點(diǎn)向右移動(dòng)k個(gè)位置,其中k是一個(gè)非負(fù)數(shù)。
如給出鏈表為 1->2->3->4->5->NULL and k = 2,
return 4->5->1->2->3->NULL.

做完,刪除倒數(shù)第 n 個(gè)節(jié)點(diǎn)的題,我們?cè)诳粗李}是不是很簡(jiǎn)單了,這道題的本質(zhì)就是,找到 k 位置節(jié)點(diǎn) 將其變成尾節(jié)點(diǎn),然后原來(lái)鏈表的尾節(jié)點(diǎn)指向原來(lái)的頭節(jié)點(diǎn)

private Node rotateList(Node head, int n) {

   int start = 1;

   Node fast = head;

   //先讓快指針走 n 給個(gè)位置
   while (start < n && fast.next != null) {
       fast = fast.next;
       start++;
   }


   //循環(huán)結(jié)束后如果 start < n 表示 n 整個(gè)鏈表還要長(zhǎng) 旋轉(zhuǎn)后還是原鏈表
   //如果 fast.next = null 表示 n 正好等于原鏈表的長(zhǎng)度此時(shí)也不需要旋轉(zhuǎn)
   if (fast.next == null || start < n) {
       return head;
   }

   //倒數(shù)第 n + 1個(gè)節(jié)點(diǎn)
   Node pre = fast;
   //旋轉(zhuǎn)后的頭節(jié)點(diǎn)
   Node newHead = fast.next;

   while (fast.next != null) {
       fast = fast.next;
   }
   //原鏈表的最后一個(gè)節(jié)點(diǎn)指向原來(lái)的頭節(jié)點(diǎn)
   fast.next = head;
   //將旋轉(zhuǎn)的節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)變?yōu)槲补?jié)點(diǎn)
   pre.next = null;

   return newHead;
}

翻轉(zhuǎn)單鏈表

翻轉(zhuǎn)一個(gè)單鏈表,要求額外的空間復(fù)雜度為 O(1)

翻轉(zhuǎn)單鏈表是我感覺(jué)比較難的基礎(chǔ)題,那么先來(lái)屢一下思路:一個(gè)節(jié)點(diǎn)包含指向下一節(jié)點(diǎn)的引用,翻轉(zhuǎn)的意思就是對(duì)要原來(lái)指向下一個(gè)節(jié)點(diǎn)引用指向上一個(gè)節(jié)點(diǎn)

  1. 找到當(dāng)前要反轉(zhuǎn)的節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)并用變量保存因?yàn)橄乱淮我崔D(zhuǎn)的是它
  2. 然后讓當(dāng)前節(jié)點(diǎn)的 next 指向上一個(gè)節(jié)點(diǎn), 上一個(gè)節(jié)點(diǎn)初始 null 因?yàn)轭^結(jié)點(diǎn)的翻轉(zhuǎn)后變?yōu)槲补?jié)點(diǎn)
  3. 當(dāng)前要反轉(zhuǎn)的節(jié)點(diǎn)變成了下一個(gè)要比較元素的上一個(gè)節(jié)點(diǎn),用變量保存
  4. 當(dāng)前要比較的節(jié)點(diǎn)賦值為之前保存的未翻轉(zhuǎn)前的下一個(gè)節(jié)點(diǎn)
  5. 當(dāng)前反轉(zhuǎn)的節(jié)點(diǎn)為 null 的時(shí)候,保存的上一個(gè)節(jié)點(diǎn)即翻轉(zhuǎn)后的鏈表頭結(jié)點(diǎn)

ok,不知道按照上邊我寫(xiě)的步驟能否理解一個(gè)鏈表的翻轉(zhuǎn)過(guò)程。如果不理解自己動(dòng)手畫(huà)一下可能更好理解哈,注意在畫(huà)的時(shí)候一次只考慮一個(gè)節(jié)點(diǎn),且不要考慮已經(jīng)翻轉(zhuǎn)完的鏈表部分。

下面我們來(lái)看下實(shí)現(xiàn)過(guò)程:

public Node  reverseList(Node head){
   //頭節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)為 null
   Node pre = null;
   Node next = null;
   
   while(head != null){
       next = head.next;
       head.next = pre;
       pre = head;
       head = next;
   }
}

翻轉(zhuǎn)部分單鏈表

題目要求:要求 0 < from < to < len 如果不滿足則不翻轉(zhuǎn)

這類題還有一類進(jìn)階題型,就是翻轉(zhuǎn)鏈表 from 位置到 to 位置的節(jié)點(diǎn),其實(shí)翻轉(zhuǎn)過(guò)程是相似的,只是我們需要找到位于 from 的前一個(gè)節(jié)點(diǎn),和 to 的下一個(gè)節(jié)點(diǎn) 翻轉(zhuǎn)完 from 和 to 部分后將 from 的上一個(gè)節(jié)點(diǎn)的 next 指針指向翻轉(zhuǎn)后的to,將翻轉(zhuǎn)后 from 節(jié)點(diǎn)的 next 指針指向 to 節(jié)點(diǎn)下一個(gè)節(jié)點(diǎn)。

  1. 遍歷整個(gè)鏈表 遍歷過(guò)程需要統(tǒng)計(jì)鏈表的長(zhǎng)度 len ,from 節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn) fPosPre , 翻轉(zhuǎn)開(kāi)始的節(jié)點(diǎn) from ,翻轉(zhuǎn)結(jié)束的節(jié)點(diǎn) to ,節(jié)點(diǎn)to 節(jié)點(diǎn)的后一個(gè)節(jié)點(diǎn) tPosNext 。
  2. 循環(huán)后判斷條件 0 < from < to < len 的條件是否滿足,如果不滿足返回 head
  3. 進(jìn)行 from 到 to 節(jié)點(diǎn)翻轉(zhuǎn)
  4. 翻轉(zhuǎn)完后判斷 如果翻轉(zhuǎn)的起點(diǎn)不是 head 則返回 head,如果反轉(zhuǎn)的鏈表是起點(diǎn),那么翻轉(zhuǎn)后 toPos 就是頭結(jié)點(diǎn)。

下面我們開(kāi)看代碼(你可能有更簡(jiǎn)便的解法,省去幾個(gè)變量,但是下面的解法應(yīng)該是最好理解的);


    private Node reversePartList(Node head, int from, int to) {
        
        Node dummyHead = head;

        int len = 0;

        Node fPosPre = null;
        Node tPosNext = null;
        Node toPos = null;
        Node fromPos = null;

        while (dummyHead != null) {
            //因?yàn)?len = 0 開(kāi)始的所以 len 先做自增一
            len++;

            if (len == from) {
                fromPos = dummyHead;
            } else if (len == from - 1) {
                fPosPre = dummyHead;
            } else if (len == to + 1) {
                tPosNext = dummyHead;
            } else if (len == to) {
                toPos = dummyHead;
            }

            dummyHead = dummyHead.next;
        }

        //不滿足條件不翻轉(zhuǎn)鏈表
        if (from > to || from < 0 || to > len || from > len) {
            return head;
        }


        Node cur = fromPos;
        Node pre = tPosNext;
        Node next = null;

        while (cur != null && cur != tPosNext) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        // 如果翻轉(zhuǎn)的起點(diǎn)不是 head 則返回 head
        if (fPosPre != null) {
            fPosPre.next = pre;
            return head;
        }
        // 如果反轉(zhuǎn)的鏈表是起點(diǎn),那么翻轉(zhuǎn)后 toPos 就是頭結(jié)點(diǎn)
        return toPos;
    }

單鏈表排序

在我的上一篇文章中說(shuō)到了數(shù)組基本的排序方法 搞懂基本排序方法,對(duì)于鏈表來(lái)說(shuō)也有上述幾種排序方法,如果感興趣的朋友也可以使用冒泡排序,選擇排序,快速排序去實(shí)現(xiàn)單鏈表的排序,由于鏈表的不可回溯行,對(duì)于鏈表來(lái)說(shuō)歸并排序是個(gè)不錯(cuò)的排序方法。我們知道歸并通過(guò)遞歸,可以實(shí)現(xiàn),那么對(duì)于單鏈表來(lái)說(shuō)也是可以的。

單鏈表的歸并排序

歸并的中心思想在于在于已知兩個(gè)鏈表的時(shí)候,如果按順序歸并這兩個(gè)鏈表。其實(shí)這也是一道面試題按照元素的大小合并兩個(gè)鏈表那么我們就先看下如何合并兩個(gè)鏈表 我們稱這個(gè)過(guò)程為 merge 。

private Node merge(Node l, Node r) {

   //創(chuàng)建臨時(shí)空間
   Node aux = new Node();
   Node cur = aux;

   //由于鏈表不能方便的拿到鏈表長(zhǎng)度 所以一般使用 while l == null 表示鏈表遍歷到尾部
   while (l != null && r != null) {
       if (l.value < r.value) {
           cur.next = l;
           cur = cur.next;
           l = l.next;
       } else {
           cur.next = r;
           cur = cur.next;
           r = r.next;
       }
   }
   //當(dāng)有一半鏈表遍歷完成后 另外一個(gè)鏈表一定只剩下最后一個(gè)元素(鏈表為基數(shù))
   if (l != null) {
       cur.next = l;
   } else if (r != null) {
       cur.next = r;
   }

   return aux.next;
}

返回的 Node 節(jié)點(diǎn)為歸并完成后的鏈表頭節(jié)點(diǎn)。那么歸并排序的核心過(guò)程也完成了,想想我們想要?dú)w并一個(gè)數(shù)組還需要一個(gè)劃分操作 中心節(jié)點(diǎn) mid 是誰(shuí),看到這里是不是笑了,之前我們已經(jīng)講過(guò)如何尋找一個(gè)鏈表的中間元素,那么是不是萬(wàn)事具備了,ok 我們來(lái)實(shí)現(xiàn)鏈表的歸并排序:

private Node mergeSort(Node head) {

   //遞歸退出的條件 當(dāng)歸并的元素為1個(gè)的時(shí)候 即 head.next 退出遞歸
   if (head == null || head.next == null) {
       return head;
   }

   Node slow = head;
   Node fast = head;

   //尋找 mid 值
   while (fast.next != null && fast.next.next != null) {
       slow = slow.next;
       fast = fast.next.next;
   }

   Node left = head;
   Node right = slow.next;

   //拆分兩個(gè)鏈表 如果設(shè)置鏈表的最后一個(gè)元素指向 null 那么 left 永遠(yuǎn)等于 head 這鏈表 也就無(wú)法排序
   slow.next = null;
   
   //遞歸的劃分鏈表
   left = mergeSort(left);
   right = mergeSort(right);

   return merge(left, right);
}

單鏈表的插入排序

回想一下數(shù)組的插入排序,我們從第二個(gè)數(shù)開(kāi)始遍歷數(shù)組,如果當(dāng)前考察的元素值比下一個(gè)元素的值要大,則下一個(gè)元素應(yīng)該排列排列在當(dāng)前考察的元素之前,所以我們從已經(jīng)排序的元素序列中從后向前掃描,如果該元素(已排序)大于新元素,將該元素移到下一位置(賦值也好,交換位置也好)。但是由于鏈表的不可回溯性,我們只能從鏈表的頭節(jié)點(diǎn)開(kāi)始找,這個(gè)元素應(yīng)該要在的位置。

我們來(lái)看下代碼實(shí)現(xiàn):

    public Node insertionSortList(Node head) {
        if (head == null || head.next == null) return head;

        Node dummyHead = new Node(0);
        Node p = head;
        dummyHead.next = head;
      //p 的值不小于下一節(jié)點(diǎn)元素考察下一節(jié)點(diǎn)
        while (p.next != null) {
            if (p.value <= p.next.value) { 
                p = p.next;
            } else {
                //p 指向 4
                Node temp = p.next;
                Node q = dummyHead;
                p.next = p.next.next;

                //從頭遍歷鏈表找到比當(dāng)前 temp 值小的第一個(gè)元素插入其后邊 整個(gè)位置一定在 頭節(jié)點(diǎn)與 q 節(jié)點(diǎn)之間
                while (q.next.value < temp.value && q.next != q)
                    q = q.next;

                temp.next = q.next;
                //重新連接鏈表 注意 else 的過(guò)程并沒(méi)有改變 p 指針的位置
                q.next = temp;
            }
        }
        return dummyHead.next;
    }

劃分鏈表

題目 : 按某個(gè)給定值將鏈表劃分為左邊小于這個(gè)值,右邊大于這個(gè)值的新鏈表 如一個(gè)鏈表 為 1 -> 4 -> 5 -> 2 給定一個(gè)數(shù) 3 則劃分后的鏈表為 1-> 2 -> 4 -> 5

此題不是很難,就是遍歷一遍鏈表,就可以完成,我們新建一兩個(gè)鏈表,如果遍歷過(guò)程中,節(jié)點(diǎn)值比給定值小則劃在左鏈表中,反之放在右鏈表中,遍歷完成后拼接兩個(gè)鏈表就好。不做過(guò)多解釋直接看代碼。

 private Node partition(Node head , int x){
    if(head == null){
        return = null;
    }
    
    Node left = new Node(0);
    Node right = new Node(0);
    
    Node dummyLeft = left;
    Node dummyRight = right;
    
    while(head != null){
        if(head.value < x){
            dummyLeft.next = head;
            dummyLeft = dummyLeft.next;
        }else{
            dummyRight.next = head;
            dummyRight = dummyRight.next;
        }
        head = head.next;
    }
    
    dummyLeft.next = right.next;
    right.next = null;
    
    return left.next;
 }

鏈表相加求和

題目: 假設(shè)鏈表中每一個(gè)節(jié)點(diǎn)的值都在 0-9 之間,那么鏈表整體可以代表一個(gè)整數(shù)。
例如: 9->3->7 可以代表 937
給定兩個(gè)這樣的鏈表,頭節(jié)點(diǎn)為 head1 head2 生成鏈表相加的新鏈表。
如 9->3->7 和 6 -> 3 生成的新鏈表應(yīng)為 1 -> 0 -> 0 -> 0

此題如果明白題意的情況并不難解決,首先理解怎么取加兩個(gè)鏈表,即鏈表按照,尾節(jié)點(diǎn)往前的順序每一位相加,如果有進(jìn)位則在下一個(gè)節(jié)點(diǎn)相加的時(shí)候算上,每一位加和為新鏈表的一個(gè)結(jié)點(diǎn)。這看上去跟數(shù)學(xué)加法一樣。所以我們的解題思路為:

  1. 翻轉(zhuǎn)要相加的兩個(gè)鏈表,這樣就可以從原鏈表的尾節(jié)點(diǎn)開(kāi)始相加。
  2. 同步遍歷兩個(gè)逆序鏈表,每一個(gè)節(jié)點(diǎn)的值相加,通過(guò)是要使用變量記錄是否進(jìn)位。
  3. 當(dāng)鏈表遍歷完成后 判斷是否還有進(jìn)位 如果有再添加一個(gè)結(jié)點(diǎn),
  4. 再次翻轉(zhuǎn)兩個(gè)鏈表使其復(fù)原,并翻轉(zhuǎn)新鏈表,則得到的題解。

private Node addLists(Node head1, Node head2) {
        head1 = reverseList(head1);
        head2 = reverseList(head2);
        //進(jìn)位標(biāo)識(shí)
        int ca = 0;
        int n1 = 0;
        int n2 = 0;
        int sum = 0;

        Node addHead = new Node(0);
        Node dummyHead = addHead;

        Node cur1 = head1;
        Node cur2 = head2;

        while (cur1 != null || cur2 != null) {
            n1 = cur1 == null ? 0 : cur1.value;
            n2 = cur2 == null ? 0 : cur2.value;

            sum = n1 + n2 + ca;

            Node node = new Node(sum % 10);
            System.out.println( sum % 10);
            ca = sum / 10;

            dummyHead.next = node;

            dummyHead = dummyHead.next;

            cur1 = cur1 == null ? null : cur1.next;
            cur2 = cur2 == null ? null : cur2.next;
        }

        if (ca > 0) {
            dummyHead.next = new Node(ca);
        }

        head1 = reverseList(head1);
        head2 = reverseList(head2);

        addHead = addHead.next;
        return reverseList(addHead);
    }
    
    private  Node reverseList(Node head) {
        Node cur = head;
        Node pre = null;
        Node next = null;

        while (cur != null) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        //注意這里返回的是賦值當(dāng)前比較元素
        return pre;
    }

刪除有序/無(wú)序鏈表中重復(fù)的元素

刪除有序鏈表中的重復(fù)元素

刪除有序鏈表中的重復(fù)元素比較簡(jiǎn)單,因?yàn)殒湵肀旧碛行颍匀绻刂抵貜?fù),那么必定相鄰,所以刪除重復(fù)元素的方法為:

如一個(gè)鏈表為 36 -> 37 -> 65 -> 76 -> 97 -> 98 -> 98 -> 98 -> 98 -> 98
刪除重復(fù)元素后為: 36 -> 37 -> 65 -> 76 -> 97 -> 98

 private void delSortSame(Node head) {
        if (head == null || head.next == null) {
            return;
        }

        Node dummy = head;
        while (dummy.next != null) {
            if (dummy.value == dummy.next.value) {
                dummy.next = dummy.next.next;
            } else {
                dummy = dummy.next;
            }
        }
    }

刪除無(wú)序鏈表中的重復(fù)元素

刪除無(wú)序鏈表中的重復(fù)元素,就要求我們必須使用一個(gè)指針記住當(dāng)前考察元素 cur 的上一個(gè)元素 pre ,并以此遍歷考察元素之后的所有節(jié)點(diǎn),如果有重復(fù)則將 pre 指針的 next 指針指向當(dāng) cur.next; 重復(fù)遍歷每個(gè)節(jié)點(diǎn),直至鏈表結(jié)尾。

如一個(gè)鏈表刪除重復(fù)元素前為: 0 -> 0 -> 3 -> 5 -> 3 -> 0 -> 1 -> 4 -> 5 -> 7
刪除重復(fù)元素后為: 0 -> 3 -> 5 -> 1 -> 4 -> 7

private void delSame(Node head) {

   if (head == null || head.next == null) {
       return;
   }
   
   Node pre = null;
   Node next = null;
   Node cur = head;

   while (cur != null) {
       //當(dāng)前考察的元素的前一個(gè)節(jié)點(diǎn)
       pre = cur;
       //當(dāng)前考察元素
       next = cur.next;
       //從遍歷剩余鏈表刪除重復(fù)元素
       while (next != null) {
           if (cur.value == next.value) {
               //刪除相同元素
               pre.next = next.next;
           }else {
               //移動(dòng)指針
               pre = next;
           }
           //移動(dòng)指針
           next = next.next;
       }
       //考察下一個(gè)元素
       cur = cur.next;
   }
}

重排鏈表

其實(shí)這也是一系列的題目,主要考察了我們對(duì)于額外空間復(fù)雜度為O(1) 的鏈表操作。我們先看第一道題:

按照左右半?yún)^(qū)的方式重新排列組合單鏈表

題目 給定一個(gè)單鏈表L: L0→L1→…→Ln-1→Ln, 重新排列后為 L0→Ln→L1→Ln-1→L2→Ln-2→… 要求必須在不改變節(jié)點(diǎn)值的情況下進(jìn)行原地操作。

我們先來(lái)分析一下題目,要想重排鏈表,必須先找到鏈表的中間節(jié)點(diǎn),然后分離左右兩部鏈表,然后按左邊一個(gè),右邊一個(gè)的順序排列鏈表。我們假設(shè)鏈表為基數(shù)的時(shí)候, N/2 位置的節(jié)點(diǎn)算左半鏈表, 那么右半鏈表就會(huì)比左半鏈表多一個(gè)節(jié)點(diǎn)。當(dāng)左半鏈表為最后一個(gè)節(jié)點(diǎn)的時(shí)候我們只需要將剩余的右半鏈表設(shè)為其下一個(gè)節(jié)點(diǎn)即可。 N 為偶數(shù)的時(shí)候就好說(shuō)了,N/2 + 1 為右半鏈表的開(kāi)始,重拍最后只需要將左半鏈表為最后一個(gè)節(jié)點(diǎn)指向 null,恰巧此時(shí)右半鏈表為 null 所以重拍最后一步就是 left.next = right 下面我們來(lái)看題解:


private void relocate1(Node head) {
   //如果鏈表長(zhǎng)度小于2 則不需要重新操作
   if (head == null || head.next == null) {
       return;
   }

   //使用快慢指針 遍歷鏈表找到鏈表的中點(diǎn)
   Node mid = head;
   Node right = head.next;

   while (right.next != null && right.next.next != null) {
       mid = mid.next;
       right = right.next.next;
   }

   //拆分左右半?yún)^(qū)鏈表
   right = mid.next;
   mid.next = null;

   //按要求合并
   mergeLR(head, right);

}

private void mergeLR(Node left, Node right) {
   Node temp = null;
   while (left.next != null) {
       temp = right.next;

       right.next = left.next;
       left.next = right;

       //這里每次向后移動(dòng)兩個(gè)位置 也就是原來(lái)的 left.next
       left = right.next;
       right = temp;
   }
   left.next = right;
}

今日頭條的一個(gè)重排鏈表題目

給定一個(gè)鏈表 1 -> 92 -> 8 -> 86 -> 9 -> 43 -> 20 鏈表的特征是奇數(shù)位升序,偶數(shù)位為降序,要求重新排列鏈表并保持鏈表整體為升序

這道題和左右半?yún)^(qū)重排鏈表類似,其實(shí)這可以理解為一個(gè)已經(jīng)進(jìn)行重排后的鏈表,現(xiàn)在要執(zhí)行上一道重排的逆過(guò)程。要滿足這個(gè)條件,我們必須假設(shè)偶數(shù)位最小的節(jié)點(diǎn)大于奇數(shù)位最大的元素。我想出題人也是這意思。如果不是的話也不麻煩上邊我們也講了歸并排序的方法,只是一次歸并而已。下面來(lái)看滿足數(shù)位最小的節(jié)點(diǎn)大于奇數(shù)位最大的元素的解法:

此題考察了面試者對(duì)鏈表的基本操作以及如何翻轉(zhuǎn)一個(gè)鏈表

 private Node relocate2(Node head) {

        //新建一個(gè)左右連個(gè)鏈表的頭指針
        Node left = new Node();
        Node right = new Node();


        Node dummyLeft = left;
        Node dummyRight = right;

        int i = 0;
        while (head != null) {
            //因?yàn)?i 從0 開(kāi)始 鏈表的頭節(jié)點(diǎn)算是奇數(shù)位所以 i 先自增 再比較
            i++;
            if (i % 2 == 0) {
                dummyRight.next = head;
                dummyRight = dummyRight.next;
            } else {
                dummyLeft.next = head;
                dummyLeft = dummyLeft.next;
            }
            //每次賦值后記得將下一個(gè)節(jié)點(diǎn)置位 null
            Node next = head.next;
            head.next = null;
            head = next;
        }

        right = reverseList(right.next);
        dummyLeft.next = right;

        return left.next;
    }

判斷兩個(gè)單鏈表(無(wú)環(huán))是相交

題目: 判斷兩個(gè)無(wú)環(huán)鏈表是否相交,如果相交則返回第一個(gè)相交節(jié)點(diǎn),如果不想交返回 null 。

我們來(lái)分析一下這道題,我們假設(shè)兩個(gè)單鏈表相交,那從相交的節(jié)點(diǎn)開(kāi)始到結(jié)束,一直到兩個(gè)鏈表都結(jié)束,那么后邊這段鏈表相當(dāng)于是共享的。我們還可以知道如果將這兩個(gè)鏈表的末尾對(duì)齊,這兩個(gè)鏈表的尾節(jié)點(diǎn)一定是相等的,所以我們的解題思路如下:

  1. 想讓一個(gè)鏈表遍歷一遍,并記錄其長(zhǎng)度
  2. 在遍歷另一個(gè)鏈表,遍歷過(guò)程中 n 每次自減一
  3. 遍歷結(jié)束后,指針 cur1 指向鏈表 head1 的最后一個(gè)節(jié)點(diǎn),同理指針 cur2 指向 head2 的最后一個(gè)節(jié)點(diǎn),如果此時(shí) cur1 != cur2 那么根據(jù)題意這兩個(gè)鏈表不想交。
  4. 遍歷結(jié)束后,我們假設(shè) hea1 要比 head2 長(zhǎng),那么 n 一定為正數(shù),代表了 head1 頭節(jié)點(diǎn)指針如果向右移動(dòng) n 個(gè)數(shù) 剩余鏈表的長(zhǎng)度將和 head2 一樣長(zhǎng)
  5. 此后 point1 和 point2 一起走那么這兩個(gè) point 指向的節(jié)點(diǎn)總會(huì)相等,第一次相等的點(diǎn)即為兩個(gè)鏈表相交的點(diǎn)。
 private Node intersect(Node head1, Node head2) {
        if (head1 == null || head2 == null) {
            return null;
        }

        Node cur1 = head1;
        Node cur2 = head2;

        int n = 0;

        while (cur1.next != null) {
            n++;
            cur1 = cur1.next;
        }

        while (cur2.next != null) {
            n--;
            cur2 = cur2.next;
        }

        if (cur1 != cur2) {
            return null;
        }

        //令 cur1 指向 較長(zhǎng)的鏈表,cur2 指向較短的鏈表
        if (n > 0) {
            cur1 = head1;
            cur2 = head2;
        } else {
            cur1 = head2;
            cur2 = head1;
        }

        n = Math.abs(n);

        //較長(zhǎng)的鏈表先走 n 步
        while (n != 0) {
            cur1 = cur1.next;
        }

        //兩個(gè)鏈表一起走 第一次相等節(jié)點(diǎn)即為相交的第一個(gè)節(jié)點(diǎn)
        while (cur1 != cur2) {
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        
        return cur1;
    }

總結(jié)

上篇文章搞懂排序算法評(píng)論有人說(shuō),文章太長(zhǎng)了。沒(méi)想到這篇文章寫(xiě)著寫(xiě)著又這么長(zhǎng)了。還請(qǐng)大家耐下心來(lái)看,每到題自己耐下心來(lái)做一遍。等大家都搞懂以后,相信大家也就差不多無(wú)所畏懼單鏈表的面試題了。

歡迎大家關(guān)注我的個(gè)人博客地址,本文算法題也上傳到我的 github上了。NodePractice 后續(xù)我將開(kāi)始學(xué)習(xí)數(shù)組,和字符串的算法題。相信不久將來(lái)又能見(jiàn)到我的又臭又長(zhǎng)的文章了。

最后 愿天不負(fù)有心人。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • //leetcode中還有花樣鏈表題,這里幾個(gè)例子,冰山一角 求單鏈表中結(jié)點(diǎn)的個(gè)數(shù)----時(shí)間復(fù)雜度O(n)這是最...
    暗黑破壞球嘿哈閱讀 1,535評(píng)論 0 6
  • 單鏈表 單鏈表問(wèn)題與思路 找出單鏈表的倒數(shù)第K個(gè)元素(僅允許遍歷一遍鏈表)使用指針追趕的方法。定義兩個(gè)指針fast...
    wxkkkkk閱讀 617評(píng)論 0 0
  • 2. Add Two Numbers 先初始化兩個(gè)結(jié)點(diǎn),一個(gè)用來(lái)做head,一個(gè)作為指引node不斷向下延續(xù)的指針...
    Morphiaaa閱讀 928評(píng)論 0 0
  • ——致拜倫 我已在曠野中 黑夜便消去了現(xiàn)實(shí) 顛簸的長(zhǎng)車載我驅(qū)弛 踉踉蹌蹌的橫掠川途 自然界一時(shí)...
    十里哥香閱讀 129評(píng)論 0 0