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

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

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


一、概要

本文介紹了有關鏈表的算法的Java代碼實現,所有代碼均可通過 在線編譯器 直接運行,算法目錄:

  • 新建鏈表
  • 反轉鏈表(遞歸和非遞歸實現)
  • 獲得鏈表倒數第k個結點
  • 獲得鏈表的中間結點
  • 刪除鏈表結點
  • 交換鏈表結點

在本章的討論當中,所有的鏈表都包含一個頭結點Node,頭結點不存儲數據,其next指針指向第一個普通鏈表結點,每個普通鏈表結點包含一個int類型的數據項。

二、代碼實現

2.1 新建鏈表

問題描述

輸入一個int類型的數組,通過該數組創建一個鏈表,并打印出該鏈表的所有元素。

解決思路

首先我們創建一個首結點header,之后通過遍歷數組p的方式獲得數組中元素并創建對應的結點node,并進行兩步操作:

  • 將首結點的當前后繼結點,作為新結點的新后繼結點
  • 將新結點作為首結點的新后繼結點

因此,最終構建出來的鏈表中結點順序是和原數組相反的。

代碼實現

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];
            //將舊的第一個普通鏈表結點作為新結點的next。
            curNode.next = header.next;
            //將新結點作為第一個普通鏈表結點。
            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);
    }
}

運行結果

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

2.2 反轉鏈表

問題描述

將輸入的鏈表進行反轉,例如在2.1中創建的鏈表為header->5->4->3->2->1,那么反轉后的鏈表為header->1->2->3->4->5。

解決思路

這里我們介紹兩種方式:非遞歸實現和遞歸實現。

實現代碼

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];
            //將舊的第一個普通鏈表結點作為新結點的next。
            curNode.next = header.next;
            //將新結點作為第一個普通鏈表結點。
            header.next = curNode;
        }
        return header;
    }
    
    //(1)非遞歸實現
    static void reverseList(Node header) {
        if (header == null) {
            return;
        }
        //curNode表示待反轉的結點。
        Node curNode = header.next;
        //nextNode表示待反轉結點的下一個結點。
        Node nextNode = null;
        //curHeader表示已經完成反轉的鏈表部分的第一個普通結點。
        Node curHeader = null;
        while (curNode != null) {
            nextNode = curNode.next;
            curNode.next = curHeader;
            curHeader = curNode;
            curNode = nextNode;
        }
        header.next = curHeader;
    }
    
    //(2)遞歸實現
    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;
        }
        //下一個結點。
        Node nextNode = header.next;
        //對下一個結點進行反轉。
        Node reverseNode = reverseListDiCore(nextNode, listHeader);
        //重新確立當前結點和下一個結點的關系。
        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);
    }
}

運行結果

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

2.3 獲得鏈表的倒數第 k 個結點

問題描述

輸入一個鏈表,返回該鏈表的導入第k個結點(不包括首結點,最后一個結點為倒數第1個結點),如果鏈表的長度小于k,那么返回null。

解決思路

采用 快慢指針 的思想,讓fast先走k步,然后slow指針開始和fast指針一起走,當fast位于最后一個結點時,那么slow所在的位置就是倒數第k個結點。

代碼實現

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];
            //將舊的第一個普通鏈表結點作為新結點的next。
            curNode.next = header.next;
            //將新結點作為第一個普通鏈表結點。
            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 : ""));
    }
}

運行結果

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

2.4 獲得鏈表的中間結點

問題描述

輸入一個鏈表,獲得鏈表的中間結點:

  • 如果鏈表的長度為1,那么返回唯一的一個結點
  • 如果鏈表的長度為偶數,那么返回結點為其第len/2個結點,其中len為鏈表的長度
  • 如果鏈表的長度為奇數,那么len/2的值為x.5,取第x.5+0.5個結點作為返回結點

解決思路

2.3類似,采用 快慢指針 的方式,fast每次走兩步,而slow每次走一步,當fast遍歷到尾結點時,slow所處的位置就是中間結點。

實現代碼

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];
            //將舊的第一個普通鏈表結點作為新結點的next。
            curNode.next = header.next;
            //將新結點作為第一個普通鏈表結點。
            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 刪除鏈表結點

問題描述

輸入一個鏈表的頭結點header,并給出位于該鏈表中的一個結點dNode,要求從鏈表中刪除該結點。

解決思路

這個問題最容易想到的做法就是找到待刪除結點的前驅結點和后繼結點,讓前驅結點的next指向后繼結點來實現刪除,但是對于 待刪除結點不是尾結點 的情況,我們可以采用一個小技巧:取出待刪除結點的后繼結點,再刪除該后繼結點,這樣就避免了尋找前驅結點的過程。

實現代碼

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];
            //將舊的第一個普通鏈表結點作為新結點的next。
            curNode.next = header.next;
            //將新結點作為第一個普通鏈表結點。
            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) { 
            //如果不是尾結點,那么取其后繼結點的值替換待刪除結點。
            Node rNode = dNode.next;
            dNode.value = rNode.value;
            dNode.next = rNode.next;
        } else {
            //如果是尾結點,那么只能采用遍歷的方式。
            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);
    }
}

運行結果

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

2.6 交換鏈表結點

問題描述

給定一個單鏈表的頭結點header,和兩個待交換的鏈表結點nodeAnodeB,交換這兩個鏈表結點

解決思路

交互鏈表結點的關鍵,在于找到這兩個結點的前驅和后繼結點,修改它們和對應結點的引用關系,這里需要注意的是 交換結點相鄰 的情況。

代碼實現

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];
            //將舊的第一個普通鏈表結點作為新結點的next。
            curNode.next = header.next;
            //將新結點作為第一個普通鏈表結點。
            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的前驅結點
        Node preA = header;
        while (preA.next != nodeA) {
            preA = preA.next;
        }
        //找到nodeB的前驅結點
        Node preB = header;
        while (preB.next != nodeB) {
            preB = preB.next;
        }
        //nodeA和nodeB的后繼結點
        Node postA = nodeA.next;
        Node postB = nodeB.next;
        //nodeA是nodeB的后繼結點
        if (preB == nodeA) {
            nodeA.next = postB;
            nodeB.next = nodeA;
            preA.next = nodeB;
        //nodeB是nodeA的后繼結點  
        } 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);
    }
}

運行結果

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。