1. 程式人生 > 其它 >資料結構與演算法--單鏈表相關面試題

資料結構與演算法--單鏈表相關面試題

技術標籤:java資料結構與演算法連結串列資料結構演算法java

此文章僅作為自己學習過程中的記錄和總結,同時會有意地去用英文來做筆記,一些術語的英譯不太準確,內容如有錯漏也請多指教,謝謝!


一、概述

  1. 獲取單鏈表的有效元素個數【新浪面試題1】
  2. 獲取單鏈表倒數第k個結點【新浪面試題2】
  3. 反轉單鏈表【騰訊面試題】
  4. 從尾到頭列印單鏈表【百度面試題】
  5. 合併兩個有序的單鏈表,合併之後的連結串列依然有序

此文章會根據以上五個問題分別給出實現程式碼,以及一些注意事項。在文末會給出檢測程式碼。

(關於屬性、構造器、結點結構及基本方法,具體可見:資料結構與演算法–單鏈表(Single Linked List)


二、獲取單鏈表的有效元素個數

此題難度不高,沒有多少筆記。

    /**
     * Get the length of the list.
     * 獲取單鏈表的有效長度【新浪面試題1】
     *
     * @return The amount of the effective nodes
     */
    public int getLength() {
        HeroNode temp = head.next;
        int count = 0;

        while (temp != null) {
            count++
; temp = temp.next; } return count; }

三、獲取單鏈表倒數第k個結點

關於這個問題,有很多解決的方法,此處我只就其中兩個方法做筆記。

  1. 蠻力演算法(不推薦);
  2. 通過兩個相隔k距離的“指標”一起移動來找到目標結點(更優解)
  • -方法①:蠻力演算法
    首先,通過getLength()方法獲取單鏈表的長度。
    之後,通過 (getLength()-k) 的迴圈,來找到目標結點。
    /**
     * Find the No.k penultimate node in a single linked list.
     * 查詢單鏈表中的倒數第k個結點【新浪面試題2】
     * <p>
     * 方法:蠻力演算法。
     * 首先,通過getLength()方法獲取單鏈表的長度。
     * 之後,通過getLength()-k的迴圈,來找到目標結點。
     *
     * @return The No.k penultimate node in a single linked list
     */
public HeroNode findLastIndexNode(int index) { if (head.next == null) { System.out.println("The list is empty, cannot find node..."); return null; } /* Method : brutal force algorithm. First, get the length of the list. Judge if it's valid. Then, loop again to find the target node. */ int length = getLength(); if (index > length || index <= 0) { // Check if the index given is valid or not. throw new RuntimeException("The given index is illegal (bigger than length / smaller than 1)..."); } HeroNode temp = head.next; for (int i = 0; i < length - index; i++) { temp = temp.next; } return temp; }

該方法的缺點:效率相對較低,由於getLength()方法需要遍歷單鏈表,之後又要再一次迴圈去找到目標結點,因此此方法需要兩次遍歷(迴圈)。

  • -方法②:通過兩個相隔k距離的“指標”一起移動來找到目標結點
    首先,讓第一個“指標”移動k距離,再讓二者一起移動。
    之後,當第一個“指標”到達單鏈表末尾時(通過輔助計數器判斷),第二個“指標”所指即為目標結點。
   /**
     * Find the No.k penultimate node in a single linked list.
     * 查詢單鏈表中的倒數第k個結點【新浪面試題2】
     * <p>
     * 方法:通過兩個相隔k距離的“指標”一起移動來找到目標結點。(更優解)
     * 首先,讓第一個“指標”移動k距離,再讓二者一起移動。
     * 之後,當第一個“指標”到達單鏈表末尾時(通過輔助計數器判斷),第二個“指標”所指即為目標結點。
     *
     * @return The No.k penultimate node in a single linked list
     */
    public HeroNode findLastIndexNode(int index) {
        if (head.next == null) {
            System.out.println("The list is empty, cannot find node...");
            return null;
        }
        /* Method : use two indicators that have the interval of "k".
           First, let one of the indicator moves "k".
           Then, let the two indicators move together.
           When the first one gets to the end of the list,
           The second indicator is now pointing to the target node.
           運用兩個相隔k的“指標”來找到目標結點。
         */
        HeroNode first = head.next;
        HeroNode second = head.next;
        int count = 0;
        while (first != null) {
            count++;
            if (count > index) {
                // Already has an interval of "k" between the two indicators,
                // they can move together.
                // 此時兩個“指標”已經相隔k,一起移動。
                second = second.next;
            }
            first = first.next;
        }
        if (index > count) { // If the given index is invalid.
            throw new RuntimeException("The given index is illegal (bigger than length / smaller than 1)...");
        }
        return second;
    }

相比之下,第二種方法只需要一次遍歷,效率更高。


四、反轉單鏈表

此題較難,需要多琢磨。

-方法:遍歷原連結串列,每遍歷一個結點,就將其取出,並放在新的連結串列reverseHead的最前端,此過程相當於使用頭插法建立新的單鏈表。(注意:輔助變數的使用)

	/**
     * Reverse the single linked list.
     * 將單鏈表反轉【騰訊面試題】
     * <p>
     * 方法:遍歷原連結串列,每遍歷一個結點,就將其取出,
     * 並放在新的連結串列reverseHead的最前端(頭插法)。
     * (注意:輔助變數的使用)
     */
    public void reverseList() {
        if (head.next == null || head.next.next == null) {
            return;
        }
        HeroNode reverseHead = new HeroNode(0, "", "");
        HeroNode cur = head.next;
        HeroNode next = null;
        while (cur != null) {
            next = cur.next;
            cur.next = reverseHead.next;
            reverseHead.next = cur;
            cur = next;
        }
        head.next = reverseHead.next;
    }

五、從尾到頭列印單鏈表

-方法

  1. 採用遞迴方法(不推薦,效率低)
  2. 先將單鏈表反轉,再遍歷列印(不推薦,因為會破壞原連結串列結構,需要復原,效率低)
  3. 利用棧(stack),遍歷時將各個結點壓入棧中。之後再次遍歷,將各個結點逐個出棧(推薦)(雖然遍歷兩次,但時間複雜度為2n,可看作O(n))

此處只記錄方法③的程式碼實現。

   /**
     * Print the nodes in the list reversely.
     * 從尾到頭列印單鏈表【百度面試題】
     * <p>
     * 方法①:採用遞迴方法。(不推薦,效率低)
     * <p>
     * 方法②:先將單鏈表反轉,再遍歷列印。(不推薦,因為會破壞原連結串列結構,需要復原,效率低)
     * <p>
     * 方法③:利用棧(stack),遍歷時將各個結點壓入棧中。之後再次遍歷,將各個結點逐個出棧。(推薦)
     * (雖然遍歷兩次,但時間複雜度為2n,可看作O(n))
     */
    public void reverseShow() {
        if (head.next == null) {
            System.out.println("The list is empty...");
            return;
        }
        HeroNode temp = head.next;
        Stack<HeroNode> heroNodeStack = new Stack<>();
        // Push the nodes into the stack.
        while (temp != null) {
            heroNodeStack.push(temp);
            temp = temp.next;
        }
        // Pop the nodes from the stack.
        while (!heroNodeStack.empty()) { // Or "heroNodeStack.size() > 0"
            System.out.println(heroNodeStack.pop());
        }
    }

此處的出棧條件可以是:

  1. !(heroNodeStack.empty())
  2. heroNodeStack.size() > 0

六、合併兩個有序的單鏈表,使合併後的連結串列依然有序

-方法:
通過兩個“指標”分別指示兩個連結串列的當前位置。每次比較二者所指向元素的大小,根據所需順序列印。

特別注意此題中輔助變數的使用。
(此例中,我通過結點的屬性“Name”來排序)

  /**
     * Merge two single linked lists(having been ordered) by order.
     * 合併兩個有序的單鏈表,合併之後的連結串列依然有序
     * <p>
     * 方法:通過兩個“指標”分別指示兩個連結串列的當前位置。每次比較二者所指向元素的大小,
     * 根據所需順序列印。
     */
    public void mergeList(SingleLinkedList anotherList) {
        if (head.next == null || anotherList.head.next == null) {
            System.out.println("One of the lists is empty, cannot merge...");
            return;
        }
        
        // 指示單鏈表1上當前所判斷到的結點
        HeroNode temp1 = head.next;
        // 指示單鏈表2上當前所判斷到的結點
        HeroNode temp2 = anotherList.head.next;
        // 指示最近一個判斷要加入合併連結串列的結點
        HeroNode cur = null;
        // 指示cur的下一個結點,起輔助作用,為了使得合併之後仍能繼續遍歷單鏈表
        HeroNode next = null;
        // 合併連結串列的頭結點
        HeroNode mergedHead = new HeroNode(0, "", "");
        
        // 先讓合併連結串列的頭指標指向符合條件的結點
        if (temp1.getName().compareToIgnoreCase(temp2.getName()) <= 0) {
            mergedHead.next = temp1;
            cur = temp1;
            temp1 = temp1.next;
        } else {
            mergedHead.next = temp2;
            cur = temp2;
            temp2 = temp2.next;
        }
        // 逐個判斷並插入合併連結串列
        while ((temp1 != null) && (temp2 != null)) {
            if (temp1.getName().compareToIgnoreCase(temp2.getName()) <= 0) {
                next = temp1.next; // 將即將被合併的結點的下一個結點儲存起來
                cur.next = temp1; // 讓合併連結串列的最後一個結點指向即將被合併的結點
                cur = temp1; // 此時已經合併,讓被合併的結點成為合併連結串列的最後一個結點
                temp1 = next; // 儲存起來的結點成為單鏈表1上當前所被判斷到的結點
            } else {
                next = temp2.next;
                cur.next = temp2;
                cur = temp2;
                temp2 = next;
            }
        }
        // 當單鏈表2已經遍歷結束,只需要將單鏈表1剩餘的結點插入到合併連結串列
        while (temp1 != null) {
            next = temp1.next;
            cur.next = temp1;
            cur = temp1;
            temp1 = next;
        }
        // 當單鏈表1已經遍歷結束,只需要將單鏈表2剩餘的結點插入到合併連結串列
        while (temp2 != null) {
            next = temp2.next;
            cur.next = temp2;
            cur = temp2;
            temp2 = next;
        }
        head = mergedHead;
    }

七、測試程式碼

/*
 SingleLinkedListDemo

 Zzay

 2021/01/18
 */
package com.zzay.linkedlist.demo;


import com.zzay.linkedlist.impl.HeroNode;
import com.zzay.linkedlist.impl.SingleLinkedList;

/**
 * CONTENTS:
 * (1) Get the length of the single linked list. 獲取單鏈表有效結點個數【新浪面試題1】
 * (2) Find the No.k penultimate node in a single linked list. 獲取單鏈表倒數第k個結點【新浪面試題2】
 * (3) Reverse the single linked list. 反轉單鏈表【騰訊面試題】
 * (4) Print the nodes in the list reversely. 從尾到頭列印單鏈表【百度面試題】
 * (5) Merge two single linked lists(having been ordered) by order. 合併兩個有序的單鏈表,合併之後的連結串列依然有序
 * 
 * @author Zzay
 * @version 2021/01/18
 */
public class SingleLinkedListDemo {

    public static void main(String[] args) {

        SingleLinkedList singleLinkedList = new SingleLinkedList();

        /* Add nodes into the single linked list by order (ascending). */
        singleLinkedList.addByOrder(new HeroNode(1, "Jay", "Yellow Chocolate"));
        singleLinkedList.addByOrder(new HeroNode(4, "Paul Pierce", "Truth"));
        singleLinkedList.addByOrder(new HeroNode(2, "Kobe Bryant", "Black Mamba"));
        singleLinkedList.addByOrder(new HeroNode(3, "Dirk Nowitzki", "German Race Car"));

        /* Show the contents of the single linked list. */
        singleLinkedList.show();

        /* Get the length of the single linked list.
           獲取單鏈表的有效元素個數【新浪面試題1】 */
        System.out.println("The length of the linked list is: " + singleLinkedList.getLength());
        
        /* Find the No.k penultimate node in a single linked list.
           獲取單鏈表倒數第k個結點【新浪面試題2】 */
        try {
            System.out.println(singleLinkedList.findLastIndexNode(3)); // Valid
//            System.out.println(singleLinkedList.findLastIndexNode(5)); // Out of range(bigger).
//            System.out.println(singleLinkedList.findLastIndexNode(0)); // Out of range(smaller)
            System.out.println();
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());
        }
        
        /* Reverse the single linked list.
           反轉單鏈表【騰訊面試題】 */
        singleLinkedList.reverseList();
        System.out.println("Here's the reversed version of the list:");
        singleLinkedList.show();
        singleLinkedList.reverseList();
        System.out.println();
        
        /* Print the nodes in the list reversely.
           從尾到頭列印單鏈表【百度面試題】 */
        System.out.println("Here we print the list reversely:");
        singleLinkedList.reverseShow();
        System.out.println();
        
        /* Merge two single linked lists(having been ordered) by order.
           合併兩個有序的單鏈表,合併之後的連結串列依然有序 */
        SingleLinkedList anotherList = new SingleLinkedList();
        anotherList.addByOrder(new HeroNode(5, "Allen Iverson", "The Answer"));
        anotherList.addByOrder(new HeroNode(6, "Michael Jordan", "Air Jordan"));
        singleLinkedList.mergeList(anotherList);
        singleLinkedList.show();
    }
}