關於鏈表的常見算法題(一)

關於鏈表的常見算法題(二)java

關於鏈表的常見算法題(一)

鏈表一直是面試最喜歡考的數據結構,靈活而又多變,代碼每每也不是特別長。node

從尾到頭打印鏈表

輸入一個鏈表的頭節點,從尾到頭反過來打印出每一個節點的值面試

例如鏈表爲1->2->3,打印出3 2 1算法

有三種思路能夠參考:數據結構

  1. 棧:棧自然是先進後出的,在遍歷鏈表時,把值按順序放入棧中,最後出棧就是逆序了。
  2. 既然想到了棧,那麼遞歸自然是一個棧結構,同樣的,訪問到一個節點,先遞歸輸出它的後面節點,再輸出本身,這樣鏈表就反過來了
  3. 頭插法:使用頭插法能夠獲得一個逆序的鏈表。
    • 1->2->3,加入一個頭節點head,只用於操做中轉,不存儲數據
    • 先把head->1,而後繼續從2->3中取
    • 變爲head->2->1,最後變爲head->3->2->1,從head.next開始輸出就能夠了
public class _06 {
    static class ListNode {
        int value;

        public ListNode(int value) {
            this.value = value;
        }

        ListNode next;
    }

    public static void main(String[] args) {
        ListNode listNode1 = new ListNode(1);
        ListNode listNode2 = new ListNode(2);
        ListNode listNode3 = new ListNode(3);
        listNode1.next = listNode2;
        listNode2.next = listNode3;
        _06 test = new _06();
// List<Integer> list = test.printListFromQueue(listNode1);
// List<Integer> list = test.printListFromDG(listNode1);
        List<Integer> list = test.printListFromTC(listNode1);
        for (Integer i : list) {
            System.out.println(i);
        }
    }

    /** * 棧 */
    public List<Integer> printListFromQueue(ListNode listNode) {
        Stack<Integer> stack = new Stack<>();
        while (listNode != null) {
            stack.add(listNode.value);
            listNode = listNode.next;
        }
        List<Integer> list = new ArrayList<>();
        while (!stack.isEmpty()) {
            list.add(stack.pop());
        }
        return list;
    }

    /** * 遞歸 */
    public List<Integer> printListFromDG(ListNode listNode) {
        List<Integer> list = new ArrayList<>();
        if (listNode != null) {
            list.addAll(printListFromDG(listNode.next));
            list.add(listNode.value);
        }
        return list;
    }

    /** * 頭插法 */
    public List<Integer> printListFromTC(ListNode listNode) {
        //頭插法構建逆序鏈表

        //新建一個頭部節點,只用於中轉,不存儲值
        ListNode head = new ListNode(-1);
        //鏈表反轉
        while (listNode != null) {
            ListNode temp = listNode.next;
            listNode.next = head.next;
            head.next = listNode;
            listNode = temp;
        }
        List<Integer> list = new ArrayList<>();
        //真正的值是從第一個節點開始
        head = head.next;
        while (head != null) {
            list.add(head.value);
            head = head.next;
        }
        return list;
    }
}
複製代碼

刪除鏈表的節點

在O(1)的時間內刪除鏈表節點

給定單身鏈表的頭指針和一個節點指針,定義一個函數在O(1)內刪除這個節點。函數

解題思路
  • 若是這個節點不是尾節點,那麼能夠直接把下一個節點的值賦值給這個節點,再把這個節點指向下下個節點,時間複雜度爲O(1)post

    如,1->2->3->4,要刪除的是2,那麼先把2的下一個節點值3,賦值給2的節點,變爲1-3-3-4,而後把第二個節點的位置,指向下下個節點4,這樣,就變爲了1-3-4,2這個節點這刪除了。this

  • 若是是尾指針,那麼找到前一個節點,把前一個節點指向null,時間複雜度爲O(N)spa

    若是進行N次操做,這個算法的平均時間複雜度爲O(1).net

/** * 刪除指定節點 */
    public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
        if (head == null || tobeDelete == null) {
            return null;
        }
        if (tobeDelete.next != null) {
            //要刪除的不是尾節點
            ListNode next = tobeDelete.next;
            tobeDelete.value = next.value;
            tobeDelete.next = next.next;
        } else {
            if (head == tobeDelete) {
                head = null;
            } else {
                ListNode curNode = head;
                while (curNode.next != tobeDelete) {
                    curNode = curNode.next;
                }
                curNode.next = null;
            }
        }
        return head;
    }
複製代碼
刪除鏈表中重複的節點

在一排序的鏈表中,如何刪除重複的節點

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

1->4

解題思路
  • 頭節點同樣有可能重複
  • 遍歷整個鏈表,若是當前節點與next的下一個節點相同,則均可以刪除,而後把當前節點的前一個節點與下下個節點相連,咱們必須保證前一個節點是無一個無重複的節點相連,這一想就是遞歸~~
/** * 刪除重複的節點 */
    public ListNode deleteDuplication(ListNode pHead) {
        if (pHead == null || pHead.next == null) {
            return pHead;
        }
        ListNode next = pHead.next;
        //當頭節點有重複時,一直找到沒有重複的爲止
        if (pHead.value == next.value) {
            while (next != null && pHead.value == next.value) {
                next = next.next;
            }
            return deleteDuplication(next);
        } else {
            pHead.next = deleteDuplication(pHead.next);
            return pHead;
        }
    }
複製代碼

鏈表中倒數第K個節點

輸入一個單向鏈表,輸出這個鏈表中倒數第k個節點

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

倒數第三個點爲4

解題思路

這是一個單向鏈表,若是咱們使用遍歷,很明顯不方便從尾開始遍歷的,這時候咱們能夠想到的方法是雙指針法。

  • 定義兩個指針p1和p2,p2先不動,p1走k-1步,這時p1指向3,p2指向1
  • 如今p1和p2同時向後遍歷,當p1遍歷到6,它的next爲null時,p2所指向的值就是咱們所要的值
public ListNode FindKthToTail(ListNode head, int k) {
        if (head == null) {
            return null;
        }
        ListNode p1 = head;
        while (p1 != null && k > 0) {
            p1 = p1.next;
            k--;
        }
        if (k > 0) {
            return null;
        }
        ListNode p2 = head;
        while (p1 != null) {
            p1 = p1.next;
            p2 = p2.next;
        }
        return p2;
    }
複製代碼

反轉鏈表

解題思路
  • 頭插法(迭代)

    • 前面其實已經用到了,使用一個頭head,只作中轉,而不存儲數據。
  • 遞歸

    ​ 分解成最簡單的兩個節點,就next.next = head,這樣就能把1->2反轉成2->1。1的next是2,它的next指向1,就已經完成了2->1

public ListNode reverseList(ListNode head){
        if (head==null||head.next==null){
            return head;
        }
        ListNode next = head.next;
        head.next = null;
        ListNode newHead = reverseList(next);
        next.next = head;
        return newHead;
    }

    public ListNode reverseList2(ListNode head){
        if (head==null||head.next==null){
            return head;
        }
        ListNode newList = new ListNode(-1);

        while (head!=null){
            ListNode next = head.next;
            head.next = newList.next;
            newList.next = head;
            head = next;
        }
        return newList.next;
    }
複製代碼

合併兩個排序的鏈表

輸入兩個遞增排序的鏈表,合併這兩個鏈表並使新的鏈表中的節點仍然是遞增的。

1-3-5-7

2-4-6-8

1-2-3-4-5-6-7-8

解題思路

首先咱們要比較兩個鏈表頭,發現1比2小,那麼新鏈表的頭就是1,而後繼續比較,這時仍是比較鏈表頭,咱們想到了,這就是個遞歸啊,把頭繼續比較,依次加進新的鏈表中,因此本題能夠用遞歸來解,固然也能使用迭代。

/** * 遞歸 */
    public ListNode merge(ListNode node1, ListNode node2) {
        if (node1 == null) {
            return node2;
        }
        if (node2 == null) {
            return node1;
        }
        if (node1.value < node2.value) {
            node1.next = merge(node1.next, node2);
            return node1;
        } else {
            node2.next = merge(node1, node2.next);
            return node2;
        }
    }

    /** * 迭代 */
    public ListNode merge2(ListNode node1, ListNode node2) {
        ListNode head = new ListNode(-1);
        ListNode cur = head;
        while (node1 != null && node2 != null) {
            if (node1.value <= node2.value) {
                cur.next = node1;
                node1 = node1.next;
            } else {
                cur.next = node2;
                node2 = node2.next;
            }
            cur = cur.next;
        }
        if (node1 != null) {
            cur.next = node1;
        }
        if (node2 != null) {
            cur.next = node2;
        }
        return head.next;
    }
複製代碼

兩個鏈表的第一個公共節點

輸入兩個鏈表,找出它們的第一個公共節點

1-2-3-6-7

4-5-6-7

解題思路
  • 暴力破解法

    在第一個鏈表上順序遍歷到一個節點,就到第二個鏈表上遍歷每個節點,若是有一個節點與這個值相等,就找到了。可是時間複雜度爲O(mn)

  • 咱們發現,公共部分必定是在尾部,若是咱們從後面開始找會更快,最後一個相同點就是咱們要找的點,不過這是單向鏈表,因此咱們想到了棧的結構——後進先出

  • 求差法

    其實咱們用棧,只是爲了想同時能到達兩個鏈表的尾節點。當兩鏈表長度不一樣時,咱們從頭開始遍歷,到後面的時間就不一致。咱們能夠先遍歷兩個鏈表,獲得它們的長度,而後,鏈表長的先走先走多的幾個節點,接着同時在兩個鏈表遍歷,找到的第一個相同點就是咱們要找的第一個公共點

/** * 用棧取 */
    public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        Stack<ListNode> stack1 = new Stack<>();
        Stack<ListNode> stack2 = new Stack<>();
        
        //分別放入棧中
        while (pHead1 != null) {
            stack1.push(pHead1);
            pHead1 = pHead1.next;
        }
        while (pHead2 != null) {
            stack2.push(pHead2);
            pHead2 = pHead2.next;
        }

        ListNode result = null;
        //從棧頂取值,取到不相等時,上一個就是第一個公共節點
        while (!stack1.isEmpty() && !stack2.isEmpty() && stack1.peek() == stack2.peek()) {
            stack1.pop();
            result = stack2.pop();
        }
        return result;
    }

    /** * 差值法 */
    public ListNode findFirstCommonNode2(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        int count1 = 1;
        int count2 = 1;
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        //獲取兩個鏈表的長度
        while (p1.next != null) {
            p1 = p1.next;
            count1++;
        }
        while (p2.next != null) {
            p2 = p2.next;
            count2++;
        }

        //哪一個長就先走差值步
        if (count1 > count2) {
            int dif = count1 - count2;
            while (dif != 0) {
                pHead1 = pHead1.next;
                dif--;
            }
        } else {
            int dif = count2 - count1;
            while (dif != 0) {
                pHead2 = pHead2.next;
                dif--;
            }
        }
        
        //同時走,當相等時獲得公共節點
        while (pHead1 != null && pHead2 != null) {
            if (pHead1 == pHead2) {
                return pHead1;
            }
            pHead1 = pHead1.next;
            pHead2 = pHead2.next;
        }
        return null;
    }
複製代碼

By《劍指offer》


個人CSDN

下面是個人公衆號,歡迎你們關注我

相關文章
相關標籤/搜索