面試算法知識梳理(9) - 鏈表算法第一部分

面試算法代碼知識梳理系列

面試算法知識梳理(1) - 排序算法
面試算法知識梳理(2) - 字符串算法第一部分
面試算法知識梳理(3) - 字符串算法第二部分
面試算法知識梳理(4) - 數(shù)組第一部分
面試算法知識梳理(5) - 數(shù)組第二部分
面試算法知識梳理(6) - 數(shù)組第三部分
面試算法知識梳理(7) - 數(shù)組第四部分
面試算法知識梳理(8) - 二分查找算法及其變型
面試算法知識梳理(9) - 鏈表算法第一部分
面試算法知識梳理(10) - 二叉查找樹
面試算法知識梳理(11) - 二叉樹算法第一部分
面試算法知識梳理(12) - 二叉樹算法第二部分
面試算法知識梳理(13) - 二叉樹算法第三部分


一、概要

本文介紹了有關(guān)鏈表的算法的Java代碼實(shí)現(xiàn)裕便,所有代碼均可通過 在線編譯器 直接運(yùn)行绒净,算法目錄:

  • 新建鏈表
  • 反轉(zhuǎn)鏈表(遞歸和非遞歸實(shí)現(xiàn))
  • 獲得鏈表倒數(shù)第k個(gè)結(jié)點(diǎn)
  • 獲得鏈表的中間結(jié)點(diǎn)
  • 刪除鏈表結(jié)點(diǎn)
  • 交換鏈表結(jié)點(diǎn)

在本章的討論當(dāng)中,所有的鏈表都包含一個(gè)頭結(jié)點(diǎn)Node偿衰,頭結(jié)點(diǎn)不存儲(chǔ)數(shù)據(jù)挂疆,其next指針指向第一個(gè)普通鏈表結(jié)點(diǎn)改览,每個(gè)普通鏈表結(jié)點(diǎn)包含一個(gè)int類型的數(shù)據(jù)項(xiàng)。

二缤言、代碼實(shí)現(xiàn)

2.1 新建鏈表

問題描述

輸入一個(gè)int類型的數(shù)組宝当,通過該數(shù)組創(chuàng)建一個(gè)鏈表,并打印出該鏈表的所有元素胆萧。

解決思路

首先我們創(chuàng)建一個(gè)首結(jié)點(diǎn)header庆揩,之后通過遍歷數(shù)組p的方式獲得數(shù)組中元素并創(chuàng)建對應(yīng)的結(jié)點(diǎn)node,并進(jìn)行兩步操作:

  • 將首結(jié)點(diǎn)的當(dāng)前后繼結(jié)點(diǎn)跌穗,作為新結(jié)點(diǎn)的新后繼結(jié)點(diǎn)
  • 將新結(jié)點(diǎn)作為首結(jié)點(diǎn)的新后繼結(jié)點(diǎn)

因此盾鳞,最終構(gòu)建出來的鏈表中結(jié)點(diǎn)順序是和原數(shù)組相反的。

代碼實(shí)現(xiàn)

class Untitled {
    
    static class Node {
        public Node next;
        public int value;
    }
    
    static Node createList(int p[], int len) {
        Node header = new Node();
        Node curNode = null;
        for (int i=0; i<len; i++) {
            curNode = new Node();
            curNode.value = p[i];
            //將舊的第一個(gè)普通鏈表結(jié)點(diǎn)作為新結(jié)點(diǎn)的next瞻离。
            curNode.next = header.next;
            //將新結(jié)點(diǎn)作為第一個(gè)普通鏈表結(jié)點(diǎn)。
            header.next = curNode;
        }
        return header;
    }
    
    static void printList(Node header) {
        if (header != null) {
            Node node = header.next;
            while (node != null) {
                System.out.println("value=" + node.value);
                node = node.next;
            }
        }
    }
    
    public static void main(String[] args) {
        int p[] = {1,2,3,4,5};
        Node header = createList(p, p.length);
        printList(header);
    }
}

運(yùn)行結(jié)果

>> value=5
>> value=4
>> value=3
>> value=2
>> value=1

2.2 反轉(zhuǎn)鏈表

問題描述

將輸入的鏈表進(jìn)行反轉(zhuǎn)乒裆,例如在2.1中創(chuàng)建的鏈表為header->5->4->3->2->1套利,那么反轉(zhuǎn)后的鏈表為header->1->2->3->4->5

解決思路

這里我們介紹兩種方式:非遞歸實(shí)現(xiàn)和遞歸實(shí)現(xiàn)鹤耍。

實(shí)現(xiàn)代碼

class Untitled {
    
    static class Node {
        public Node next;
        public int value;
    }
    
    static Node createList(int p[], int len) {
        Node header = new Node();
        Node curNode = null;
        for (int i=0; i<len; i++) {
            curNode = new Node();
            curNode.value = p[i];
            //將舊的第一個(gè)普通鏈表結(jié)點(diǎn)作為新結(jié)點(diǎn)的next肉迫。
            curNode.next = header.next;
            //將新結(jié)點(diǎn)作為第一個(gè)普通鏈表結(jié)點(diǎn)。
            header.next = curNode;
        }
        return header;
    }
    
    //(1)非遞歸實(shí)現(xiàn)
    static void reverseList(Node header) {
        if (header == null) {
            return;
        }
        //curNode表示待反轉(zhuǎn)的結(jié)點(diǎn)稿黄。
        Node curNode = header.next;
        //nextNode表示待反轉(zhuǎn)結(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn)喊衫。
        Node nextNode = null;
        //curHeader表示已經(jīng)完成反轉(zhuǎn)的鏈表部分的第一個(gè)普通結(jié)點(diǎn)。
        Node curHeader = null;
        while (curNode != null) {
            nextNode = curNode.next;
            curNode.next = curHeader;
            curHeader = curNode;
            curNode = nextNode;
        }
        header.next = curHeader;
    }
    
    //(2)遞歸實(shí)現(xiàn)
    static void reverseListDi(Node header) {
        if (header == null) {
            return;
        }
        reverseListDiCore(header.next, header);
    }
    
    static Node reverseListDiCore(Node header, Node listHeader) {
        if (header.next == null) {
            listHeader.next = header;
            return header;
        }
        //下一個(gè)結(jié)點(diǎn)杆怕。
        Node nextNode = header.next;
        //對下一個(gè)結(jié)點(diǎn)進(jìn)行反轉(zhuǎn)族购。
        Node reverseNode = reverseListDiCore(nextNode, listHeader);
        //重新確立當(dāng)前結(jié)點(diǎn)和下一個(gè)結(jié)點(diǎn)的關(guān)系。
        reverseNode.next = header;
        header.next = null;
        return header;
    }
        
    static void printList(Node header) {
        if (header != null) {
            Node node = header.next;
            while (node != null) {
                System.out.println("value=" + node.value);
                node = node.next;
            }
        }
    }
    
    public static void main(String[] args) {
        int p[] = {1,2,3,4,5};
        Node header = createList(p, p.length);
        reverseListDi(header);
        printList(header);
    }
}

運(yùn)行結(jié)果

>> value=1
>> value=2
>> value=3
>> value=4
>> value=5

2.3 獲得鏈表的倒數(shù)第 k 個(gè)結(jié)點(diǎn)

問題描述

輸入一個(gè)鏈表陵珍,返回該鏈表的導(dǎo)入第k個(gè)結(jié)點(diǎn)(不包括首結(jié)點(diǎn)寝杖,最后一個(gè)結(jié)點(diǎn)為倒數(shù)第1個(gè)結(jié)點(diǎn)),如果鏈表的長度小于k互纯,那么返回null瑟幕。

解決思路

采用 快慢指針 的思想,讓fast先走k步留潦,然后slow指針開始和fast指針一起走只盹,當(dāng)fast位于最后一個(gè)結(jié)點(diǎn)時(shí),那么slow所在的位置就是倒數(shù)第k個(gè)結(jié)點(diǎn)兔院。

代碼實(shí)現(xiàn)

class Untitled {
    
    static class Node {
        public Node next;
        public int value;
    }
    
    static Node createList(int p[], int len) {
        Node header = new Node();
        Node curNode = null;
        for (int i=0; i<len; i++) {
            curNode = new Node();
            curNode.value = p[i];
            //將舊的第一個(gè)普通鏈表結(jié)點(diǎn)作為新結(jié)點(diǎn)的next殖卑。
            curNode.next = header.next;
            //將新結(jié)點(diǎn)作為第一個(gè)普通鏈表結(jié)點(diǎn)。
            header.next = curNode;
        }
        return header;
    }
    
    static Node getLastKNode(Node header, int k) {
        if (k < 1) {
            return null;
        }
        Node fast = header;
        Node slow = header;
        int step = 0;
        while (fast != null && fast.next != null) {
            fast = fast.next;
            step++;
            if (step >= k) {
                slow = slow.next;
            }
        }
        return slow != header ? slow : null;
    }
        
    static void printList(Node header) {
        if (header != null) {
            Node node = header.next;
            StringBuilder builder = new StringBuilder();
            while (node != null) {
                builder.append(String.valueOf(node.value));
                node = node.next;
                if (node != null) {
                    builder.append("->");
                }
            }
            System.out.println(builder.toString());
        }
    }
    
    public static void main(String[] args) {
        int p[] = {1,2,3,4,5};
        Node header = createList(p, p.length);
        printList(header);
        Node kNode = getLastKNode(header, 4);
        System.out.println("KNode=" + (kNode != null ? kNode.value : ""));
    }
}

運(yùn)行結(jié)果

>> 5->4->3->2->1
>> KNode=4

2.4 獲得鏈表的中間結(jié)點(diǎn)

問題描述

輸入一個(gè)鏈表秆乳,獲得鏈表的中間結(jié)點(diǎn):

  • 如果鏈表的長度為1懦鼠,那么返回唯一的一個(gè)結(jié)點(diǎn)
  • 如果鏈表的長度為偶數(shù)钻哩,那么返回結(jié)點(diǎn)為其第len/2個(gè)結(jié)點(diǎn),其中len為鏈表的長度
  • 如果鏈表的長度為奇數(shù)肛冶,那么len/2的值為x.5街氢,取第x.5+0.5個(gè)結(jié)點(diǎn)作為返回結(jié)點(diǎn)

解決思路

2.3類似,采用 快慢指針 的方式睦袖,fast每次走兩步珊肃,而slow每次走一步,當(dāng)fast遍歷到尾結(jié)點(diǎn)時(shí)馅笙,slow所處的位置就是中間結(jié)點(diǎn)伦乔。

實(shí)現(xiàn)代碼

class Untitled {
    
    static class Node {
        public Node next;
        public int value;
    }
    
    static Node createList(int p[], int len) {
        Node header = new Node();
        Node curNode = null;
        for (int i=0; i<len; i++) {
            curNode = new Node();
            curNode.value = p[i];
            //將舊的第一個(gè)普通鏈表結(jié)點(diǎn)作為新結(jié)點(diǎn)的next。
            curNode.next = header.next;
            //將新結(jié)點(diǎn)作為第一個(gè)普通鏈表結(jié)點(diǎn)董习。
            header.next = curNode;
        }
        return header;
    }
    
    static Node geMiddleNode(Node header) {
        if (header == null || header.next == null) {
            return null;
        }
        Node fast = header;
        Node slow = header;
        while (fast != null) {
            fast = fast.next;
            if (fast != null) {
                fast = fast.next;
            } else {
                break;
            }
            slow = slow.next;
        }
        return slow;
    }
        
    static void printList(Node header) {
        if (header != null) {
            Node node = header.next;
            StringBuilder builder = new StringBuilder();
            while (node != null) {
                builder.append(String.valueOf(node.value));
                node = node.next;
                if (node != null) {
                    builder.append("->");
                }
            }
            System.out.println(builder.toString());
        }
    }
    
    public static void main(String[] args) {
        int p[] = {1,2,3};
        Node header = createList(p, p.length);
        printList(header);
        Node kNode = geMiddleNode(header);
        System.out.println("KNode=" + (kNode != null ? kNode.value : ""));
    }
}

2.5 刪除鏈表結(jié)點(diǎn)

問題描述

輸入一個(gè)鏈表的頭結(jié)點(diǎn)header烈和,并給出位于該鏈表中的一個(gè)結(jié)點(diǎn)dNode,要求從鏈表中刪除該結(jié)點(diǎn)皿淋。

解決思路

這個(gè)問題最容易想到的做法就是找到待刪除結(jié)點(diǎn)的前驅(qū)結(jié)點(diǎn)和后繼結(jié)點(diǎn)招刹,讓前驅(qū)結(jié)點(diǎn)的next指向后繼結(jié)點(diǎn)來實(shí)現(xiàn)刪除,但是對于 待刪除結(jié)點(diǎn)不是尾結(jié)點(diǎn) 的情況窝趣,我們可以采用一個(gè)小技巧:取出待刪除結(jié)點(diǎn)的后繼結(jié)點(diǎn)疯暑,再刪除該后繼結(jié)點(diǎn),這樣就避免了尋找前驅(qū)結(jié)點(diǎn)的過程哑舒。

實(shí)現(xiàn)代碼

class Untitled {

    static class Node {
        public Node next;
        public int value;
    }

    static Node createList(int p[], int len) {
        Node header = new Node();
        Node curNode = null;
        for (int i=0; i<len; i++) {
            curNode = new Node();
            curNode.value = p[i];
            //將舊的第一個(gè)普通鏈表結(jié)點(diǎn)作為新結(jié)點(diǎn)的next妇拯。
            curNode.next = header.next;
            //將新結(jié)點(diǎn)作為第一個(gè)普通鏈表結(jié)點(diǎn)。
            header.next = curNode;
        }
        return header;
    }

    static Node getLastKNode(Node header, int k) {
        if (k < 1) {
            return null;
        }
        Node fast = header;
        Node slow = header;
        int step = 0;
        while (fast != null && fast.next != null) {
            fast = fast.next;
            step++;
            if (step >= k) {
                slow = slow.next;
            }
        }
        return slow != header ? slow : null;
    }

    static void deleteNode(Node header, Node dNode) {
        if (header == null && dNode != null) {
            return;
        }
        if (dNode.next != null) { 
            //如果不是尾結(jié)點(diǎn)洗鸵,那么取其后繼結(jié)點(diǎn)的值替換待刪除結(jié)點(diǎn)越锈。
            Node rNode = dNode.next;
            dNode.value = rNode.value;
            dNode.next = rNode.next;
        } else {
            //如果是尾結(jié)點(diǎn),那么只能采用遍歷的方式预麸。
            Node node = header;
            while (node.next != null && node.next.next != null) {
                node = node.next;
            }
            node.next = null;
        }
    }

    static void printList(Node header) {
        if (header != null) {
            Node node = header.next;
            StringBuilder builder = new StringBuilder();
            while (node != null) {
                builder.append(String.valueOf(node.value));
                node = node.next;
                if (node != null) {
                    builder.append("->");
                }
            }
            System.out.println(builder.toString());
        }
    }

    public static void main(String[] args) {
        int p[] = {1,2,3};
        Node header = createList(p, p.length);
        printList(header);
        Node kNode = getLastKNode(header, 3);
        System.out.println("KNode=" + (kNode != null ? kNode.value : ""));
        deleteNode(header, kNode);
        printList(header);
    }
}

運(yùn)行結(jié)果

>> 3->2->1
>> KNode=2
>> 3->1

2.6 交換鏈表結(jié)點(diǎn)

問題描述

給定一個(gè)單鏈表的頭結(jié)點(diǎn)header瞪浸,和兩個(gè)待交換的鏈表結(jié)點(diǎn)nodeAnodeB,交換這兩個(gè)鏈表結(jié)點(diǎn)

解決思路

交互鏈表結(jié)點(diǎn)的關(guān)鍵吏祸,在于找到這兩個(gè)結(jié)點(diǎn)的前驅(qū)和后繼結(jié)點(diǎn)对蒲,修改它們和對應(yīng)結(jié)點(diǎn)的引用關(guān)系,這里需要注意的是 交換結(jié)點(diǎn)相鄰 的情況贡翘。

代碼實(shí)現(xiàn)

class Untitled {
    
    static class Node {
        public Node next;
        public int value;
    }
    
    static Node createList(int p[], int len) {
        Node header = new Node();
        Node curNode = null;
        for (int i=0; i<len; i++) {
            curNode = new Node();
            curNode.value = p[i];
            //將舊的第一個(gè)普通鏈表結(jié)點(diǎn)作為新結(jié)點(diǎn)的next蹈矮。
            curNode.next = header.next;
            //將新結(jié)點(diǎn)作為第一個(gè)普通鏈表結(jié)點(diǎn)。
            header.next = curNode;
        }
        return header;
    }
    
    static Node getLastKNode(Node header, int k) {
        if (k < 1) {
            return null;
        }
        Node fast = header;
        Node slow = header;
        int step = 0;
        while (fast != null && fast.next != null) {
            fast = fast.next;
            step++;
            if (step >= k) {
                slow = slow.next;
            }
        }
        return slow != header ? slow : null;
    }
    
    static void swapNode(Node header, Node nodeA, Node nodeB) {
        if (header == null || nodeA == null || nodeB == null) {
            return;
        }
        if (nodeA == header || nodeB == header) {
            return;
        }
        if (nodeA == nodeB) {
            return;
        }
        //找到nodeA的前驅(qū)結(jié)點(diǎn)
        Node preA = header;
        while (preA.next != nodeA) {
            preA = preA.next;
        }
        //找到nodeB的前驅(qū)結(jié)點(diǎn)
        Node preB = header;
        while (preB.next != nodeB) {
            preB = preB.next;
        }
        //nodeA和nodeB的后繼結(jié)點(diǎn)
        Node postA = nodeA.next;
        Node postB = nodeB.next;
        //nodeA是nodeB的后繼結(jié)點(diǎn)
        if (preB == nodeA) {
            nodeA.next = postB;
            nodeB.next = nodeA;
            preA.next = nodeB;
        //nodeB是nodeA的后繼結(jié)點(diǎn)  
        } else if (preA == nodeB) {
            nodeB.next = postA;
            nodeA.next = nodeB;
            preB.next = nodeA;
        //nodeA和nodeB不相鄰
        } else {
            preA.next = nodeB;
            nodeB.next = postA;
            preB.next = nodeA;
            nodeA.next = postB;
        }
    
    }
        
    static void printList(Node header) {
        if (header != null) {
            Node node = header.next;
            StringBuilder builder = new StringBuilder();
            while (node != null) {
                builder.append(String.valueOf(node.value));
                node = node.next;
                if (node != null) {
                    builder.append("->");
                }
            }
            System.out.println(builder.toString());
        }
    }
    
    public static void main(String[] args) {
        int p[] = {1,2,3,4,5};
        Node header = createList(p, p.length);
        printList(header);
        Node nodeA = getLastKNode(header, 5);
        Node nodeB = getLastKNode(header, 1);
        swapNode(header, nodeA, nodeB);
        printList(header);
    }
}

運(yùn)行結(jié)果

>> 5->4->3->2->1
>> 1->4->3->2->5

更多文章鸣驱,歡迎訪問我的 Android 知識梳理系列:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泛鸟,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子踊东,更是在濱河造成了極大的恐慌北滥,老刑警劉巖刚操,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異再芋,居然都是意外死亡菊霜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門济赎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鉴逞,“玉大人,你說我怎么就攤上這事司训」辜瘢” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵壳猜,是天一觀的道長勾徽。 經(jīng)常有香客問我,道長统扳,這世上最難降的妖魔是什么捂蕴? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮闪幽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涡匀。我一直安慰自己盯腌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布陨瘩。 她就那樣靜靜地躺著腕够,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舌劳。 梳的紋絲不亂的頭發(fā)上帚湘,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音甚淡,去河邊找鬼大诸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛贯卦,可吹牛的內(nèi)容都是我干的资柔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼撵割,長吁一口氣:“原來是場噩夢啊……” “哼贿堰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起啡彬,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤羹与,失蹤者是張志新(化名)和其女友劉穎故硅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纵搁,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吃衅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诡渴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捐晶。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖妄辩,靈堂內(nèi)的尸體忽然破棺而出惑灵,到底是詐尸還是另有隱情,我是刑警寧澤眼耀,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布英支,位于F島的核電站,受9級特大地震影響哮伟,放射性物質(zhì)發(fā)生泄漏干花。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一楞黄、第九天 我趴在偏房一處隱蔽的房頂上張望池凄。 院中可真熱鬧,春花似錦鬼廓、人聲如沸肿仑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尤慰。三九已至,卻和暖如春雷蹂,著一層夾襖步出監(jiān)牢的瞬間伟端,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工匪煌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留责蝠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓萎庭,卻偏偏與公主長得像玛歌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子擎椰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345