劍指offer-------鏈表篇

面試題18.刪除鏈表的結點

在這裏插入圖片描述

常規思路

//java anan 2020.3.18
//常規思路 找到結點並刪除
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        ListNode h = new ListNode();   //新建一個頭結點  題目給定的是不帶頭結點的鏈表
        h.next = head;
        ListNode p = head;
        ListNode pre = h;
        while(p != null && p.val != val){
            pre = p;
            p = p.next;
        }        
        pre.next = p.next;  
        return h.next;
    }
}

新思路:給定的是要刪除結點的結點指針

在這裏插入圖片描述
在這裏插入圖片描述
下面這道題就是用的這種思路

面試題02.03.刪除中間結點

在這裏插入圖片描述
在這裏插入圖片描述

//java
class Solution {
    public void deleteNode(ListNode node) {
        node.val = node.next.val;
        node.next = node.next.next;
    }
}

面試題22.鏈表中倒數第k個節點

在這裏插入圖片描述

安安思路:雙指針 快慢指針

//java  anan
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode p = head;
        ListNode q = head;
        int n = 0;

        while(p != null){
            n++;
            p = p.next;
            if(n==k){
                q = head;
            }else if(n>k){
                q = q.next;
            }
        }
    
        return q;
    }
}
//java 第一個指針先走k步  然後兩個指針一起走
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode p = head;
        ListNode q = head;

        for(int i = 0; i < k; i++){
            p = p.next;
        }
        while(p != null){
            p = p.next;
            q = q.next;
        }
    
        return q;
    }
}

注意的點:魯棒性

在這裏插入圖片描述

//java
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        if(head == null || k <= 0){
            return null;
        }

        ListNode p = head;
        ListNode q = head;

        for(int i = 0; i < k; i++){
            if(p == null && i < k){
                return null;
            }
            p = p.next;
        }
        while(p != null){
            p = p.next;
            q = q.next;
        }
    
        return q;
    }
}

19.刪除鏈表的倒數第N個節點

在這裏插入圖片描述

//java
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if(head == null || n <= 0){
            return null;
        }

        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode p = dummy;
        ListNode q = dummy;

        for(int i = 0; i < n+1; i++){
            if(p == null){
                return null;
            }
            p = p.next;
        }
        while(p != null){
            p = p.next;
            q = q.next;
        }
        q.next = q.next.next;
           
        return dummy.next;
    }
}

本題和上一題的區別:
1.上一題快指針需要先走n個節點 本題快指針需要走n+1個節點 爲什麼呢?
因爲上一題找的是倒數第n個節點,定位到這個節點即可;但是本題要刪除倒數第n個節點,所以需要定位的是倒數第n+1個節點,所以快慢指針的間隔是n+1

2.上一題不需要添加啞節點,本題卻需要,爲什麼?
如果倒數第n個節點剛好是第1個節點,上一題直接返回即可,沒有什麼影響;但是本題卻要刪除,刪除勢必會影響到頭結點,所以需要定義啞結點,刪除第一個節點不需要單獨考慮。

面試題22相關題目:求鏈表的中間節點

在這裏插入圖片描述

面試題24.反轉鏈表(206)

在這裏插入圖片描述

修改next指針

//java
class Solution {
    // public void show(ListNode head){
    //     ListNode p = head;
    //     while(p != null){
    //         System.out.print(p.val + " ");
    //         p = p.next;
    //     }
    //     System.out.println();
    // }

    public ListNode reverseList(ListNode head) {
        if(head == null) return null;

        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode pre = null;
        ListNode p = head;
        ListNode post = p.next;

        while(post != null){           
            p.next = pre;
            //show(p);
            pre = p;
            p = post;
            post = post.next;
        }
        p.next = pre;
        return p;
    }
}

遞歸

//java anan 2020.3.18
class Solution {
    // public void show(ListNode head){
    //     ListNode p = head;
    //     while(p != null){
    //         System.out.print(p.val + " ");
    //         p = p.next;
    //     }
    //     System.out.println();
    // }

    public ListNode reverseList(ListNode head) {
        if(head == null)  return null;
        ListNode p = head;
        if(head.next == null) return head;
        else{
            p = reverseList(head.next); 
            ListNode q = p;
            while(q.next != null) q = q.next;           
            q.next = head;           
            head.next = null;
            //show(p);
            return p;
        }       
    }
}
//java
/*
遞歸解法
這題有個很騷氣的遞歸解法,遞歸解法很不好理解,這裏最好配合代碼和動畫一起理解。
遞歸的兩個條件:
終止條件是當前節點或者下一個節點==null
在函數內部,改變節點的指向,也就是 head 的下一個節點指向 head 遞歸函數那句
head.next.next = head
很不好理解,其實就是 head 的下一個節點指向head
*/
class Solution {
    // public void show(ListNode head){
    //     ListNode p = head;
    //     while(p != null){
    //         System.out.print(p.val + " ");
    //         p = p.next;
    //     }
    //     System.out.println();
    // }

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

學習的一個很騷的寫法:
head.next.next = head
很不好理解,其實就是 head 的下一個節點指向head

92.反轉鏈表II

在這裏插入圖片描述

安安思路:分成三段,按照反轉整個鏈表進行,然後拼接

//java anan 2020.3.20
/*
1 -> 2 -> 3 -> 4 -> 5   m=2, n=4   
定義兩個指針,左指針和右指針
左指針指向要翻轉的第一個結點的前一個結點    即指向1
右指針指向要反轉的最後一個結點的下一個結點  即指向5
翻轉234後進行拼接
*/
class Solution {
    public ListNode reverseBetween(ListNode head, int m, int n) {
        if(m == n || head == null) return head;  //考慮特殊情況,保證魯棒性

        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode left = new ListNode(0);
        ListNode right = new ListNode(0);
        ListNode p = dummy;
        int count = 0;

        //查找要翻轉的部分
        while(p != null){
            if(count == m-1) left = p;
            if(count == n){
                right = p.next;
                break;
            }
            count++;
            p = p.next;
        }
        p.next = null;    //注意找到要翻轉的部分之後,要把最後一個結點的下一個結點置空

        //進行翻轉
        ListNode rev = reverse(left.next);   
        
        //進行拼接
        left.next = rev;
        p = rev;
        while(p.next != null) p = p.next; 
        p.next = right;

        return dummy.next;
    }

    public ListNode reverse(ListNode head){
        if(head.next == null)  return head;

        ListNode res = reverse(head.next);
        head.next.next = head;
        head.next = null;
        return res;
    }
}

大佬思路:遞歸

大佬原題解 強烈推薦去看,寫的特別好
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

官方題解1:不改變鏈表結構,只交換鏈表結點的值

在這裏插入圖片描述
左指針設爲全局變量,遞歸回溯時後指針前移,此時講左指針後移,交換對應的值即可

新的技巧點:本題,只要左右指針移到兩個的中間便需要停止遞歸回溯,直接返回去,但是該怎樣直接結束返回去呢?題解裏給了一個思路:設一個flag指針,只有在滿足flag爲初始值時才進行,需要返回時改變flag的值即可。
妙哉妙哉!

//Java anan寫的 最後一步參考了題解


class Solution {
    ListNode left;
    boolean stop;

    public void reverse(ListNode head, int m, int n){       
        if(m == 1) left = head;
        if(n == 0) return ;

        reverse(head.next, m-1, n-1);

        if(left == head || head.next == left){
            this.stop = true;
            return ;
        }
        
        if(! this.stop){
            int tmp;
            tmp = left.val;
            left.val = head.val;
            head.val = tmp;
            left = left.next;
        }

    }

    public ListNode reverseBetween(ListNode head, int m, int n) {
        left = head;
        stop = false;
        reverse(head, m, n);
        return head;
    }
}

官方題解2:依次修改結點指針的指向

和自己之前做反裝整個鏈表時相似

24.兩兩交換鏈表中的節點

在這裏插入圖片描述
這兩個解法都是自己寫的
具體思路可以參見:某童鞋題解

解法1:迭代

//java anan 
class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null) return null;

        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode pre = dummy;
        ListNode p = head;
        ListNode post;

        while(p != null && p.next != null){
            post = p.next.next; 
             
            //改變指針的方向
            p.next.next = p;
            pre.next = p.next;          
            p.next = post;
                      
            pre = p;
            p = post;           
        }

        return dummy.next;
    }
}

解法2:遞歸

//java anan 
class Solution {
    public ListNode swapPairs(ListNode head) {        
        if(head == null || head.next == null) return head;
        
        ListNode ret = swapPairs(head.next.next);
        ListNode res = head.next;

        head.next.next = head;
        head.next = ret;
        return res;
    }
}

面試題25.合併兩個排序的鏈表(21)

在這裏插入圖片描述

自己之前的解法:類似於集合合併

//c   安安 2020.2
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    struct ListNode *p = l1;
    struct ListNode *q = l2;
    struct ListNode *n = NULL;   //指向新鏈表的第一個結點
    struct ListNode *r;          //指向新鏈表的最後一個結點
    struct ListNode *s;          //指向新創建的結點

    while(p && q){
        s = (struct ListNode*)malloc(sizeof(struct ListNode));
        if(p->val <= q->val){
            s->val = p->val;
            p = p->next;
        }else{
            s->val = q->val;
            q = q->next;
        }
        s->next = NULL;

        if(n == NULL){
            n = s;
            r = s;
        }else{
            r->next = s;
            r = s;
        }

        // printf("新鏈表:");
        // struct ListNode *m = n;
        // while(m){
        //     printf("%d ", m->val);
        //     m = m->next;
        // }
        // printf("\n");
    }

    if(q){
        p = q;
    }
    while(p){
        s = (struct ListNode*)malloc(sizeof(struct ListNode));
        s->val = p->val;
        s->next = NULL;
        p = p->next;

        if(n == NULL){     //特殊例子: []   [0]
            n = s;
            r = s;
        }else{
            r->next = s;
            r = s;
        }

        // printf("新新鏈表:");
        // struct ListNode *m = n;
        // while(m){
        //     printf("%d ", m->val);
        //     m = m->next;
        // }
        // printf("\n");
    }

    return n;
}

解法1:遞歸

//java anan 2020.3.20
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1 == null)   return l2;
        if(l2 == null)   return l1;
        
        if(l1.val <= l2.val){
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        }else{           
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

解法2:迭代 代碼很漂亮

//java
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode prehead = new ListNode(-1);
        ListNode prev = prehead;
        while(l1 != null && l2 != null){
            if(l1.val <= l2.val){
                prev.next = l1;
                l1 = l1.next;
            }else{
                prev.next = l2;
                l2 = l2.next;
            }
            prev = prev.next;
        }

        prev.next = (l1 == null ? l2:l1);
        return prehead.next;
    }
}

面試題35.複雜鏈表(帶隨機指針)的複製(138)

在這裏插入圖片描述
在這裏插入圖片描述

安安 哈希表

//自己的想法也是大多數人直接想到的想法,但是這中思想的時間複雜度爲O(n2)
//因爲要定位random指針  //所以官解(同劍指offer書中)用了哈希表(空間換時間)可以迅速定位random指針指向的節點
//java anan 2020.4.26
/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

/*
深拷貝是指源對象與拷貝對象互相獨立,其中任何一個對象的改動都不會對另外一個對象造成影響。舉個例子,一個人名叫張三,後來用他克隆(假設法律允許)了另外一個人,叫李四,不管是張三缺胳膊少腿還是李四缺胳膊少腿都不會影響另外一個人。
*/
class Solution {
    public Node copyRandomList(Node head) {
        if(head == null)  return null;
                
        //1.構造原鏈表中的random映射
        HashMap<Integer, Integer> map = new HashMap<>();  //哈希表  用來存儲節點random的關係  用下標表示  
        Node p=head;    //p用來遍歷原鏈表       
        for(int i=0; p != null; p=p.next, i++){
            // System.out.print("本節點對象:" + p + " ");
            // System.out.println("指向的random對象" + p.random);
            Node p1=head;   //p1用來遍歷原鏈表
            int j = 0;
            for(; p.random != p1; p1=p1.next, j++){
                ;
            }
            //System.out.println("指向的random對象的下標位置" + j);
            map.put(i, (p.random==null ? -1 : j));      //random爲null的話,對應的鍵值賦爲-1    
        }
        //showHashMap(map);


        //2.構造新鏈表
        Node dummy = new Node(-1);
        Node q=dummy;   //q指向新鏈表的最後一個節點  
        //2.1.先把新鏈表創建起來
        p = head;
        for(int i=0; p != null; p=p.next, i++){
            Node s = new Node(p.val);  //新節點
            q.next= s;
            q = s;
        }
        // show(dummy.next);
        // show(head);

        //2.2.構建random指向
        q=dummy.next;    //q用來遍歷新鏈表       
        for(int i=0; q != null; q=q.next, i++){
            if(map.get(i) == -1){
                q.random = null;
            }else{
                Node q1=dummy.next;   //q1用來遍歷新鏈表
                for(int j = 0; j < map.get(i); q1=q1.next, j++){
                    ;
                }
                q.random = q1;
            }                     
        }

        return dummy.next;
    }

    public void show(Node head){
        Node p=head;
        while(p != null){
            System.out.print(p.val + " ");
            p = p.next;
        }
        System.out.println();
    }

    public void showHashMap(HashMap<Integer, Integer> map){
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            System.out.print(entry.getKey() + "--->");
            System.out.println(entry.getValue());
        }
    }
}

官解

官解1:DFS回溯(劍指offer提到了,但是沒寫代碼)

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

public class Solution {
  // HashMap which holds old nodes as keys and new nodes as its values.
  HashMap<Node, Node> visitedHash = new HashMap<Node, Node>();

  public Node copyRandomList(Node head) {

    if (head == null) {
      return null;
    }

    // If we have already processed the current node, then we simply return the cloned version of
    // it.
    if (this.visitedHash.containsKey(head)) {
      return this.visitedHash.get(head);
    }

    // Create a new node with the value same as old node. (i.e. copy the node)
    Node node = new Node(head.val, null, null);

    // Save this value in the hash map. This is needed since there might be
    // loops during traversal due to randomness of random pointers and this would help us avoid
    // them.
    this.visitedHash.put(head, node);

    // Recursively copy the remaining linked list starting once from the next pointer and then from
    // the random pointer.
    // Thus we have two independent recursive calls.
    // Finally we update the next and random pointers for the new node created.
    node.next = this.copyRandomList(head.next);
    node.random = this.copyRandomList(head.random);

    return node;
  }
}

作者:LeetCode
鏈接:https://leetcode-cn.com/problems/copy-list-with-random-pointer/solution/fu-zhi-dai-sui-ji-zhi-zhen-de-lian-biao-by-leetcod/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

官解2:迭代 個人感覺和1差不多

官解3:交織鏈表(劍指offer)

在這裏插入圖片描述
1.扭曲鏈表
2.修改random指針
3.恢復next指針
在這裏插入圖片描述

//java 根據思路自己寫的

class Solution {
    public Node copyRandomList(Node head) {
        if(head == null)  return null;

        //創建交織鏈表
        Node p = head;
        while(p != null){
            Node s = new Node(p.val);
            s.next = p.next;
            p.next = s;
            p = p.next.next;
        }
        //show(head);

        //修改random指針
        p = head;
        while(p != null){
            if(p.random == null) p.next.random = null;
            else{
                p.next.random = p.random.next;
            }
            p = p.next.next;
        }

        //修改next指針
        Node p1 = head;    //遍歷原鏈表
        Node p2 = head.next;   //遍歷新鏈表
        Node returnNode = head.next; 
        while(p2.next != null){
            p1.next = p1.next.next;  //恢復原鏈表的next指針
            p2.next = p2.next.next;  //恢復新鏈表的next指針
            p1 = p1.next;
            p2 = p2.next;
        }
        p1.next = null;
        p2.next = null;

        return returnNode;
    }

    public void show(Node head){
        Node p=head;
        while(p != null){
            System.out.print(p.val + " ");
            p = p.next;
        }
        System.out.println();
    }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章