1. 程式人生 > >劍指Offer面試題:14.連結串列的倒數第k個節點

劍指Offer面試題:14.連結串列的倒數第k個節點

PS:這是一道出境率極高的題目,記得去年參加校園招聘時我看到了3次,但是每次寫的都不完善。

一、題目:連結串列的倒數第k個節點

題目:輸入一個連結串列,輸出該連結串列中倒數第k個結點。為了符合大多數人的習慣,本題從1開始計數,即連結串列的尾結點是倒數第1個結點。例如一個連結串列有6個結點,從頭結點開始它們的值依次是1、2、3、4、5、6。這個連結串列的倒數第3個結點是值為4的結點。

  連結串列的節點定義如下,這裡使用的是C#來定義:

    public class Node
    {
        public int Data { get; set; }
        
public Node Next { get; set; } public Node(int data) { this.Data = data; } public Node(int data, Node next) { this.Data = data; this.Next = next; } }

二、解題思路

2.1 不可行的常規解法

  為了得到倒數第k個結點,很自然的想法是先走到連結串列的尾端,再從尾端回溯k步

。當時,從連結串列結點的定義可以看出本題中的連結串列是單向連結串列,單向連結串列的結點只有從前往後的指標而沒有從後往前的指標,因此這種思路行不通,它只適用於雙向連結串列。

  如果連結串列定義中有指向前一個節點的指標,那麼此解法是可行的,我們可以修改連結串列定義:

    public class Node
    {
        public int Data { get; set; }
        // 指向後一個節點
        public Node Next { get; set; }
        // 指向前一個節點Prev
        public
Node Prev { get; set; } public Node(int data) { this.Data = data; } }

2.2 可行但不高效的常規解法

  假設整個連結串列有n個結點,那麼倒數第k個結點就是從頭結點開始的第n-k+1個結點。如果我們能夠得到連結串列中結點的個數n,那我們只要從頭結點開始往後走n-k+1步就可以了。

  那麼,這裡的重點就在於如何求連結串列中節點的個數n,只需要從頭開始遍歷連結串列,每經過一個結點,計數器加1就行了。

  但是,問題來了:這種思路需要遍歷連結串列兩次,第一次統計出連結串列中結點的個數,第二次才能找到倒數第k個結點

2.3 可行且高效的解法

  為了能夠只遍歷一次就能找到倒數第k個節點,可以定義兩個指標:

  (1)第一個指標從連結串列的頭指標開始遍歷向前走k-1,第二個指標保持不動

  (2)從第k步開始,第二個指標也開始從連結串列的頭指標開始遍歷

  (3)由於兩個指標的距離保持在k-1,當第一個(走在前面的)指標到達連結串列的尾結點時,第二個指標(走在後面的)指標正好是倒數第k個結點

  下圖展示了在有6個結點的連結串列上找倒數第3個結點的過程:

舉一反三:當我們用一個指標遍歷連結串列不能解決問題的時候,可以嘗試用兩個指標來遍歷連結串列。可以讓其中一個指標遍歷的速度快一些(比如一次在連結串列上走兩步),或者讓它先在連結串列上走若干步。

三、解決問題

3.1 程式碼實現

    public static Node FindKthToTail(Node head, uint k)
    {
        Node ahead = head;
        Node behind = null;

        for (int i = 0; i < k - 1; i++)
        {
            ahead = ahead.Next;
        }

        behind = head;

        while (ahead.Next != null)
        {
            ahead = ahead.Next;
            behind = behind.Next;
        }

        return behind;
    }

3.2 程式碼完善

  上面的程式碼存在3處魯棒性問題:

  (1)輸入的head為空指標。由於程式碼會試圖訪問空指標指向的記憶體,程式崩潰

  解決:在處理前增加判斷空指標的程式碼

  (2)輸入的以head為頭結點的連結串列的結點總數少於k。由於在for迴圈中會在連結串列上向前走k-1步,仍然會由於空指標造成程式崩潰

  解決:在for迴圈中增加判斷下一個節點是否是空指標的程式碼

  (3)輸入的引數k為0。由於k是一個無符號整數,那麼在for迴圈中k-1得到的將不是-1,而是4294967295(無符號的0xFFFFFFFF)。因此for迴圈執行的次數遠遠超出我們的預計,同樣也會造成程式崩潰

  解決:同(1),在處理前的判斷中也判斷引數k是否為0。

    public static Node FindKthToTail(Node head, uint k)
    {
        if(head == null || k == 0)
        {
            return null;
        }

        Node ahead = head;
        Node behind = null;

        for (int i = 0; i < k - 1; i++)
        {
            if(ahead.Next != null)
            {
                ahead = ahead.Next;
            }
            else
            {
                return null;
            }
        }

        behind = head;

        while (ahead.Next != null)
        {
            ahead = ahead.Next;
            behind = behind.Next;
        }

        return behind;
    }

  《劍指Offer》這本書另外的一大優點就在於作者以一個開發老鳥的角度時時刻刻地站在了魯棒性、可維護性、可擴充套件性地角度來告訴即將進入開發一線的菜鳥們做提醒,在開發中需要考慮這些東西,並通過設計測試用例進行單元測試驗證結果。

3.3 單元測試

  (1)功能測試

    // 01.測試要找的結點在連結串列中間
    [TestMethod]
    public void FindKthNodeTest1()
    {
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        Node node5 = new Node(5);

        node1.Next = node2;
        node2.Next = node3;
        node3.Next = node4;
        node4.Next = node5;

        Assert.AreEqual(NodeHelper.FindKthToTail(node1, 4), node2);
    }

    // 02.測試要找的結點是連結串列的尾結點
    [TestMethod]
    public void FindKthNodeTest2()
    {
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        Node node5 = new Node(5);

        node1.Next = node2;
        node2.Next = node3;
        node3.Next = node4;
        node4.Next = node5;

        Assert.AreEqual(NodeHelper.FindKthToTail(node1, 1), node5);
    }

    // 03.測試要找的結點是連結串列的頭結點
    [TestMethod]
    public void FindKthNodeTest3()
    {
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        Node node5 = new Node(5);

        node1.Next = node2;
        node2.Next = node3;
        node3.Next = node4;
        node4.Next = node5;

        Assert.AreEqual(NodeHelper.FindKthToTail(node1, 5), node1);
    }

  (2)特殊輸入測試

    // 04.測試空連結串列
    [TestMethod]
    public void FindKthNodeTest4()
    {
        Assert.AreEqual(NodeHelper.FindKthToTail(null, 100), null);
    }

    // 05.測試輸入的第二個引數大於連結串列的結點總數
    [TestMethod]
    public void FindKthNodeTest5()
    {
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        Node node5 = new Node(5);

        node1.Next = node2;
        node2.Next = node3;
        node3.Next = node4;
        node4.Next = node5;

        Assert.AreEqual(NodeHelper.FindKthToTail(node1, 6), null);
    }

    // 06.測試輸入的第二個引數等於0
    [TestMethod]
    public void FindKthNodeTest6()
    {
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        Node node5 = new Node(5);

        node1.Next = node2;
        node2.Next = node3;
        node3.Next = node4;
        node4.Next = node5;

        Assert.AreEqual(NodeHelper.FindKthToTail(node1, 0), null);
    }

  (3)測試結果

  ①測試用例通過情況

  ②程式碼覆蓋率情況

作者:周旭龍

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。

相關推薦

Offer試題14.連結串列倒數k節點

PS:這是一道出境率極高的題目,記得去年參加校園招聘時我看到了3次,但是每次寫的都不完善。 一、題目:連結串列的倒數第k個節點 題目:輸入一個連結串列,輸出該連結串列中倒數第k個結點。為了符合大多數人的習慣,本題從1開始計數,即連結串列的尾結點是倒數第1個結點。例如一個連結串列有6個結點,從頭結點開始

試題】 求連結串列倒數K節點

題目:輸入一個連結串列輸出連結串列中的第K個節點,(計數從1開始),連結串列節點定義如下: //定義結構 struct ListNode { ListNode() :_next(NULL) ,_data(0) {} ListNode *_next; int

Offer》題目鏈表中倒數k結點

倒數 -- 輸出 col ota pan code 輸入 tno 題目描述:輸入一個鏈表,輸出該鏈表中倒數第k個結點 題目分析:因為不能直接從鏈表的尾部遍歷,所以要分兩步走: 第一步:從鏈表的頭部開始遍歷,直至鏈表的尾部,統計出鏈表結點的個數 第二步:根據鏈表結點的個數,計

Offer.試題18.刪除連結串列中重複的節點

在一個排序的連結串列中,存在重複的結點,請刪除該連結串列中重複的結點,重複的結點不保留,返回連結串列頭指標。 例如,連結串列1->2->3->3->4->4->5 處理後為 1->2->5 思路: 設定三個指標。

Offer試題24 反轉連結串列

輸入一個連結串列,按連結串列值從尾到頭的順序返回一個ArrayList。 牛客網提交程式碼: /** * struct ListNode { * int val; * struct ListNode *next; * ListNode(int x)

offer編程-鏈表中倒數k結點

描述 節點 thead 註意 功能測試 判斷 tro return 測試用例 題目描述 輸入一個鏈表,輸出該鏈表中倒數第k個結點。 思路: 1.遍歷鏈表得到鏈表的長度l,找到從前往後的第l-k+1個節點。需要遍歷兩遍。 2.遍歷一次即可的方法:兩個指針,第一個指針從頭向尾

offer十四之鏈表中倒數k結點

gif img https question pla last 代碼 鏈表 || 一、題目 輸入一個鏈表,輸出該鏈表中倒數第k個結點。 二、思路   兩個指針,先讓第一個指針和第二個指針都指向頭結點,然後再讓第一個指正走(k-1)步,到達第k個節點。然後兩個指針同

連結串列倒數 k 節點

#include <iostream> struct ListNode { int m_nValue; ListNode* m_pNext; }; /* 查詢連結串列倒數第 k 個結點 */ ListNode* FindKthToTail(ListNode* pHea

查詢連結串列倒數k節點

演算法描述: 給出一個單向連結串列的頭指標(根指標),輸出該連結串列中倒數第k個節點的指標。連結串列的倒數第0個節點的尾節點(尾節點的next成員為NULL)。函式find_node實現上述功能,連結串列節點定義及函式宣告如下,請實現函式find_node。 typedef

連結串列(5)----查詢連結串列倒數K節點

1、連結串列定義 typedef struct ListElement_t_ { void *data; struct ListElement_t_ *next; } ListElement_t; typedef struct List_t_{

offer 試題從尾到頭列印連結串列

題目:輸入一個連結串列,按連結串列值從尾到頭的順序返回一個ArrayList。 思路:有多種放法。(1)先反轉連結串列,再列印連結串列。(2)使用棧。 /** * struct ListNode { * int val; * struct ListNode *n

Offer試題31.兩連結串列的第一公共節點

一、題目:兩個連結串列的第一個公共節點 題目:輸入兩個連結串列,找出它們的第一個公共結點。   連結串列結點定義如下,這裡使用C#語言描述: public class Node { public int key; public Node

Offer試題4.從尾到頭列印連結串列

一、題目:從尾到頭列印連結串列 題目:輸入一個連結串列的頭結點,從尾到頭反過來打印出每個結點的值。   到解決這個問題肯定要遍歷連結串列。遍歷的順序是從頭到尾的順序,可輸出的順序卻是從尾到頭。也就是說第一個遍歷到的結點最後一個輸出,而最後一個遍歷到的結點第一個輸出。這就是典型的“後進先出”,我

Offer試題25.二叉搜尋樹與雙向連結串列

一、題目:二叉搜尋樹與雙向連結串列 題目:輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的雙向連結串列。要求不能建立任何新的結點,只能調整樹中結點指標的指向。比如輸入下圖中左邊的二叉搜尋樹,則輸出轉換之後的排序雙向連結串列。   二叉搜尋樹的節點定義如下,這裡使用C#語言描述:

Offer試題16.合併兩排序的連結串列

PS:這也是一道出鏡率極高的面試題,我相信很多童鞋都會很眼熟,就像於千萬人之中遇見不期而遇的人,沒有別的話可說,唯有輕輕地問一聲:“哦,原來你也在這裡? ” 一、題目:合併兩個排序的連結串列 題目:輸入兩個遞增排序的連結串列,合併這兩個連結串列並使新連結串列中的結點仍然是按照遞增排序的。例如輸入下

Offer試題24.複雜連結串列的複製

一、題目:複雜連結串列的複製 題目:請實現函式ComplexListNode Clone(ComplexListNode head),複製一個複雜連結串列。在複雜連結串列中,每個結點除了有一個Next指標指向下一個結點外,還有一個Sibling指向連結串列中的任意結點或者NULL。   結點的定義

Offer試題12.在O(1)時間刪除連結串列結點

一、題目:在O(1)時間刪除連結串列結點 題目:給定單向連結串列的頭指標和一個結點指標,定義一個函式在O(1)時間刪除該結點。   原文采用的是C/C++,這裡採用C#,節點定義如下: public class Node<T> { // 資料域

offer 試題重建二叉樹

題目:輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。 思路:二叉樹先序是根左右,中序 是左根右。所以先找到

Offer試題17.樹的子結構

一、題目:樹的子結構 題目:輸入兩棵二叉樹A和B,判斷B是不是A的子結構。例如下圖中的兩棵二叉樹,由於A中有一部分子樹的結構和B是一樣的,因此B是A的子結構。   該二叉樹的節點定義如下,這裡使用C#語言描述: public class BinaryTreeNode {

Offer試題2.二維陣列中的查詢

一、題目:二維陣列中的查詢 題目:在一個二維陣列中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函式,輸入這樣的一個二維陣列和一個整數,判斷陣列中是否含有該整數。     例如下面的二維陣列就是每行、每列都遞增排序。如果在這個陣列中查詢數字7,則返回true;