剑指offer-------链表篇

面试题06.从尾到头打印链表

在这里插入图片描述

安安思路:正向遍历链表,反转列表

#python 其实可以直接用head 不用p

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def reversePrint(self, head: ListNode) -> List[int]:
        list1 = []
        p = head
        while(p):
            #print(p.val)
            list1.append(p.val)
            p = p.next
        return list1[::-1]
//java

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        ListNode p = head;
        int count = 0;
        while(p != null){
            //System.out.println(p.val);
            count++;
            p = p.next;
        }

        int[] array = new int[count];
        p = head;
        while(p != null){
            array[--count] = p.val;
            p = p.next;
        }
        return array;
    }
}

官方题解:栈 和自己的类似

在这里插入图片描述

//java

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        Stack<ListNode> stack = new Stack<ListNode>();
        ListNode temp = head;
        while (temp != null) {
            stack.push(temp);
            temp = temp.next;
        }
        int size = stack.size();
        int[] print = new int[size];
        for (int i = 0; i < size; i++) {
            print[i] = stack.pop().val;
        }
        return print;
    }
}

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/solution/mian-shi-ti-06-cong-wei-dao-tou-da-yin-lian-biao-b/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
//java
class Solution {
    public int[] reversePrint(ListNode head) {
        LinkedList<Integer> stack = new LinkedList<Integer>();
        while(head != null) {
            stack.addLast(head.val);
            head = head.next;
        }
        int[] res = new int[stack.size()];
        for(int i = 0; i < res.length; i++)
            res[i] = stack.removeLast();
    return res;
    }
}

作者:jyd
链接:https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/solution/mian-shi-ti-06-cong-wei-dao-tou-da-yin-lian-biao-d/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

另一解法:递归

在这里插入图片描述
在这里插入图片描述

//java
class Solution {
    ArrayList<Integer> tmp = new ArrayList<Integer>();
    public int[] reversePrint(ListNode head) {
        recur(head);
        int[] res = new int[tmp.size()];
        for(int i = 0; i < res.length; i++)
            res[i] = tmp.get(i);
        return res;
    }
    void recur(ListNode head) {
        if(head == null) return;
        recur(head.next);
        tmp.add(head.val);
    }
}

作者:jyd
链接:https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/solution/mian-shi-ti-06-cong-wei-dao-tou-da-yin-lian-biao-d/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#python
class Solution:
    def reversePrint(self, head: ListNode) -> List[int]:
        return self.reversePrint(head.next) + [head.val] if head else []

作者:jyd
链接:https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/solution/mian-shi-ti-06-cong-wei-dao-tou-da-yin-lian-biao-d/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

面试题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;
    }
}

(二刷)修改next指针

//anan 2020/8/3
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null) return null;

        ListNode pre = head;
        ListNode cur = head;
        ListNode suc = head.next;

        while(cur != null){
            cur.next = pre;
            if(pre == head) pre.next = null;
            pre = cur;
            cur = suc;
            if(suc != null) suc = suc.next;
        }

        return pre;
    }
}

递归

//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;
    }
}

(二刷)迭代

//anan 2020.8.3
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode();
        ListNode p = dummy;

        while(l1 != null && l2 != null){
            if(l1.val <= l2.val){
                p.next = l1;
                l1 = l1.next;
            }else if(l1.val > l2.val){
                p.next = l2;
                l2 = l2.next;
            }
            p = p.next;
        }

        if(l2 != null){
            l1 = l2;
        }

        while(l1 != null){
            p.next = l1;
            p = p.next;
            l1 = l1.next;
        }
        return dummy.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();
    }

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安安csdn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值