Day7-2021.1.15 剑指offer的七道链表题目的整理。06.从尾到头打印链表+18 .删除链表的节点+22 .链表中倒数第k个节点+ 24.反转链表+25.合并两个排序的链表+35.复杂链

Day7-2021.1.15 剑指offer的七道链表题目的整理。06.从尾到头打印链表+18 .删除链表的节点+22 .链表中倒数第k个节点+ 24.反转链表+25.合并两个排序的链表+35.复杂链表的复制+52.两个链表的第一个公共节点

LeetCode题解汇总

当前已更新下面的剑指offer的七道链表题目:
剑指 Offer 06.从尾到头打印链表
剑指 Offer 18 .删除链表的节点
剑指 Offer 22 .链表中倒数第k个节点
剑指 Offer 24 .反转链表
剑指 Offer 25 .合并两个排序的链表
剑指 Offer 35 .复杂链表的复制
剑指 Offer 52 .两个链表的第一个公共节点

自己写的程序的规格结构,如下图:
在这里插入图片描述

剑指 Offer 06.从尾到头打印链表

思考:利用栈“先进后出”的性质。遍历链表,存储结点值。遍历输出栈值。业务代码还是很容易写的。

引申可见 剑指 Offer 24 .反转链表

为了在IDEA上方便调试,需要实现“nums2ListNode”,即数组转链表的业务代码。

private static ListNode nums2ListNode(int[] nums) {
    // 1.首先先去创建头结点
    ListNode head = new ListNode(nums[0]);
    // 2.创建结点 node 去遍历数组元素
    ListNode node = head;
    // 3.从数组的第2个元素依次遍历
    for (int i = 1; i < nums.length; i++) {
        // 4.创建临时结点
        ListNode temp = new ListNode(nums[i]);
        node.next = temp;
        // 更新 node 节点
        node = temp;
    }
    return head;
}

为了在IDEA上方便调试,需要实现“printListNode”,即链表内容打印的业务代码。

private static void printListNode(ListNode head) {
    StringBuffer res = new StringBuffer();
    while (head.next != null) {
        res.append(head.val + "->");
        head = head.next;
    }
    res.append(head.val);
    System.out.println(res);
}

完整代码:

ArrayList如何转换为int[]数组 https://blog.csdn.net/huanghanqian/article/details/73920439
这个是java8的特性
int[] res = list.stream().mapToInt(Integer::intValue).toArray();

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Stack;

public class Offer06 {
    public static void main(String[] args) {
        int[] nums = new int[]{1, 3, 2};
        ListNode head = nums2ListNode(nums);
        printListNode(head);
        System.out.println(reversePrint(head));
    }

    private static void printListNode(ListNode head) {
        StringBuffer res = new StringBuffer();
        while (head.next != null) {
            res.append(head.val + "->");
            head = head.next;
        }
        res.append(head.val);
        System.out.println(res);
    }

    private static ListNode nums2ListNode(int[] nums) {
        // 1.首先先去创建头结点
        ListNode head = new ListNode(nums[0]);
        // 2.创建结点 node 去遍历数组元素
        ListNode node = head;
        // 3.从数组的第2个元素依次遍历
        for (int i = 1; i < nums.length; i++) {
            // 4.创建临时结点
            ListNode temp = new ListNode(nums[i]);
            node.next = temp;
            // 更新 node 节点
            node = temp;
        }
        return head;
    }

    public static int[] reversePrint(ListNode head) {
        Stack<Integer> stack = new Stack<>();
        ArrayList<Integer> list = new ArrayList<>();
        while (head != null) {
            stack.push(head.val);
            head = head.next;
        }
        while (!stack.isEmpty()) {
            list.add(stack.pop());
        }
        // ArrayList<Integer>如何转换为int[]数组 https://blog.csdn.net/huanghanqian/article/details/73920439
        // 这个是java8的特性
        int[] res = list.stream().mapToInt(Integer::intValue).toArray();
        return res;
    }
}

ListNode的代码放在外边

public class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
    }
}
剑指 Offer 18 .删除链表的节点

思考:这个考虑可能删除头结点,尾节点,等情况,需要采用“虚拟头结点”+“快慢指针”的操作。

要删除一个节点,那么该节点的前一个节点指向该节点的后一个节点,并将该节点的next指向null,则实现了删除指定节点的操作。

import java.util.ArrayList;
import java.util.Stack;

public class Offer18 {
    public static void main(String[] args) {
        int[] nums = new int[]{4, 5, 1, 9};
        ListNode head = nums2ListNode(nums);
        printListNode(head);
        ListNode resHead = deleteNode(head, 5);
        printListNode(resHead);
    }

    private static void printListNode(ListNode head) {
        StringBuffer res = new StringBuffer();
        while (head.next != null) {
            res.append(head.val + "->");
            head = head.next;
        }
        res.append(head.val);
        System.out.println(res);
    }

    private static ListNode nums2ListNode(int[] nums) {
        // 1.首先先去创建头结点
        ListNode head = new ListNode(nums[0]);
        // 2.创建结点 node 去遍历数组元素
        ListNode node = head;
        // 3.从数组的第2个元素依次遍历
        for (int i = 1; i < nums.length; i++) {
            // 4.创建临时结点
            ListNode temp = new ListNode(nums[i]);
            node.next = temp;
            // 更新 node 节点
            node = temp;
        }
        return head;
    }

    public static ListNode deleteNode(ListNode head, int val) {
        // 1.创建虚拟头结点
        ListNode dummyNode = new ListNode(0);
        dummyNode.next = head;
        // 2.创建快慢指针
        ListNode fast = dummyNode.next;
        ListNode slow = dummyNode;
        // 3.遍历查找需要删除的节点
        while (fast != null) {
            // 一个要删除的节点的值
            if (fast.val == val) {
                // 该节点的前一个节点指向该节点的后一个节点
                slow.next = fast.next;
                // 将该节点的next指向null。这里好像是加不加都可以。
                fast.next = null;
                break;
            }
            // 更新节点
            slow = slow.next;
            fast = fast.next;
        }
        // 这里是考虑到头结点就是要删除的节点的情况
        return dummyNode.next;
    }
}
剑指 Offer 22 .链表中倒数第k个节点

思考:“快慢指针”,先让快指针走k步,然后快慢指针同时向前走,当快指针到达链表末尾,慢指针指向了目标节点。

public class Offer22 {
    public static void main(String[] args) {
        int[] nums = new int[]{1, 2, 3, 4, 5};
        ListNode head = nums2ListNode(nums);
        printListNode(head);
        ListNode resHead = getKthFromEnd(head, 2);
        printListNode(resHead);
    }

    private static void printListNode(ListNode head) {
        StringBuffer res = new StringBuffer();
        while (head.next != null) {
            res.append(head.val + "->");
            head = head.next;
        }
        res.append(head.val);
        System.out.println(res);
    }

    private static ListNode nums2ListNode(int[] nums) {
        // 1.首先先去创建头结点
        ListNode head = new ListNode(nums[0]);
        // 2.创建结点 node 去遍历数组元素
        ListNode node = head;
        // 3.从数组的第2个元素依次遍历
        for (int i = 1; i < nums.length; i++) {
            // 4.创建临时结点
            ListNode temp = new ListNode(nums[i]);
            node.next = temp;
            // 更新 node 节点
            node = temp;
        }
        return head;
    }

    public static ListNode getKthFromEnd(ListNode head, int k) {
        // 1.创建快慢指针
        ListNode fast = head;
        ListNode slow = head;
        // 2.先让快指针走k步
        for (int i = 0; i < k; i++) {
            fast = fast.next;
        }
        // 3.然后快慢指针同时向前走,当快指针到达链表末尾,慢指针指向了目标节点。
        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}
剑指 Offer 24 .反转链表

思考:“双指针”,1.保存cur的下一个节点。2.转向,后节点指向前节点。3.更新节点,两个节点都向前走一步。这个做题步骤特别统一。不能采用“虚拟头结点”,pre应该直接设置为null。

public class Offer24 {
    public static void main(String[] args) {
        int[] nums = new int[]{1, 2, 3, 4, 5};
        ListNode head = nums2ListNode(nums);
        printListNode(head);
        ListNode resHead = reverseList(head);
        printListNode(resHead);
    }

    private static void printListNode(ListNode head) {
        StringBuffer res = new StringBuffer();
        while (head.next != null) {
            res.append(head.val + "->");
            head = head.next;
        }
        res.append(head.val);
        System.out.println(res);
    }

    private static ListNode nums2ListNode(int[] nums) {
        // 1.首先先去创建头结点
        ListNode head = new ListNode(nums[0]);
        // 2.创建结点 node 去遍历数组元素
        ListNode node = head;
        // 3.从数组的第2个元素依次遍历
        for (int i = 1; i < nums.length; i++) {
            // 4.创建临时结点
            ListNode temp = new ListNode(nums[i]);
            node.next = temp;
            // 更新 node 节点
            node = temp;
        }
        return head;
    }

    public static ListNode reverseList(ListNode head) {
        // 1.创建双指针
        ListNode pre = null;
        ListNode cur = head;
        while (cur != null) {
            // 2.保存cur的下一个节点
            ListNode nextNode = cur.next;
            // 3.转向,后节点指向前节点。
            cur.next = pre;
            // 4.更新节点,两个节点都向前走一步。
            pre = cur;
            cur = nextNode;
        }
        // 经过while,cur指向为null,pre指向尾节点。
        return pre;
    }
}
剑指 Offer 25 .合并两个排序的链表

思考:新建一个链表,比较链表l1和l2的值,依次将元素添加到新链表中。

【力扣官方将这个题目归为了分治算法

先去写 newNode = newNode.next;是不行的,newNode.next现在是null,必须先去newNode.next=l1,这样给newNode.next赋值,才能更新newNode节点:newNode = newNode.next;

public class Offer25 {
    public static void main(String[] args) {
        int[] nums1 = new int[]{1, 2, 4};
        int[] nums2 = new int[]{1, 3, 4};
        ListNode l1 = nums2ListNode(nums1);
        ListNode l2 = nums2ListNode(nums2);
        printListNode(l1);
        printListNode(l2);
        ListNode resHead = mergeTwoLists(l1, l2);
        printListNode(resHead);
    }

    private static void printListNode(ListNode head) {
        StringBuffer res = new StringBuffer();
        while (head.next != null) {
            res.append(head.val + "->");
            head = head.next;
        }
        res.append(head.val);
        System.out.println(res);
    }

    private static ListNode nums2ListNode(int[] nums) {
        // 1.首先先去创建头结点
        ListNode head = new ListNode(nums[0]);
        // 2.创建结点 node 去遍历数组元素
        ListNode node = head;
        // 3.从数组的第2个元素依次遍历
        for (int i = 1; i < nums.length; i++) {
            // 4.创建临时结点
            ListNode temp = new ListNode(nums[i]);
            node.next = temp;
            // 更新 node 节点
            node = temp;
        }
        return head;
    }

    public static ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummyNode = new ListNode(0);
        ListNode newNode = dummyNode;
        // 循环结束条件 && 使得l1,l2其中一个为空时,则跳出while循环。
        while (l1 != null && l2 != null) {
            // 获取l1,l2中较小的节点,添加到新链表 newNode 中。
            if (l1.val < l2.val) {
                newNode.next = l1;
                // 更新链表 l1
                l1 = l1.next;
            } else {
                newNode.next = l2;
                // 更新链表 l2
                l2 = l2.next;
            }
            // 更新新链表 newNode
            newNode = newNode.next;

        }
        // l1,l2其中一个为空。
        newNode.next = l1 != null ? l1 : l2;
        return dummyNode.next;
    }
}
剑指 Offer 35 .复杂链表的复制 【需要重刷】

思考:这个题目难度明显比前面的几道题目要难一点。代码参考自1:+代码参考自2,java版本:

首先,先新建一个Node类

public class Node {
    int val;
    Node next;
    Node random;

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

测试用例:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]。明显,自己不能用IDEA去创建测试用例。

image-20210115103628209

1.哈希表方法,视频讲解如下:bilibili视频讲解:小美图解:java版 小美图解剑指offer35题 复杂链表的复制 算法刷题。这个视频多看几次,加深理解。

首先利用 HashMap 来一个不用思考的代码。
遍历第一遍链表,我们不考虑链表之间的相互关系,仅仅生成所有节点,然后把它存到 HashMap 中,val 作为 key,Node 作为 value。
遍历第二遍链表,将之前生成的节点取出来,更新它们的 next 和 random 指针。

import java.util.HashMap;

public class Offer35 {
    public static void main(String[] args) {
        System.out.println(" 测试用例:`head = [[7,null],[13,0],[11,4],[10,2],[1,0]]`。");
        System.out.println("明显,自己不能用IDEA去创建测试用例。");
    }

    public Node copyRandomList(Node head) {
        // 给定的链表为空(空指针),因此返回 null。
        if (head == null) {
            return null;
        }
        // 新建 HashMap 遍历存储 链表的节点+节点值。
        // 键值对都是Node类型。
        HashMap<Node, Node> map = new HashMap<>();
        Node cur = head;
        // 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
        while (cur != null) {
            Node val = new Node(cur.val);
            map.put(cur, val);
            cur = cur.next;
        }
        // 遍历完成后返回到头结点
        cur = head;
        // 构建新链表的 next 和 random 指向
        while (cur != null) {
            if (cur.next != null) {
                map.get(cur).next = map.get(cur.next);
            }
            if (cur.random != null) {
                map.get(cur).random = map.get(cur.random);
            }
            cur = cur.next;
        }
        // 返回新链表的头节点
        return map.get(head);
    }
}

2.bilibili视频讲解【采用了递归的方法。可以先不看这个解法。感觉看了下面的代码,更加困惑了。】来自 子烁爱学习

import java.util.HashMap;

public class Offer35_bilibili {
    public static void main(String[] args) {
        System.out.println(" 测试用例:`head = [[7,null],[13,0],[11,4],[10,2],[1,0]]`。");
        System.out.println("明显,自己不能用IDEA去创建测试用例。");
    }

    HashMap<Node, Node> map = new HashMap<>();

    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        if (map.containsKey(head)) {
            return map.get(head);
        }
        Node tempNode = new Node(head.val);
        map.put(head, tempNode);
        // 这里有一个递归调用哦。
        tempNode.next = copyRandomList(head.next);
        // 这里有一个递归调用哦。上面的递归完成,这里才会执行。
        tempNode.random = copyRandomList(head.random);
        return tempNode;
    }
} 
剑指 Offer 52 .两个链表的第一个公共节点 【需要重刷】

思考:一种方法是:用A的一个节点,去比较B的所有节点。复杂度是O(n^2)。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode copyheadB=headB;
        while(headA!=null){
            while(headB!=null){
                if(headA==headB){
                    return headA;
                }else{
                    headB=headB.next;
                }
            }
            headA=headA.next;
            headB=copyheadB;
        }
        return null;
    }
}

思考:另一个巧妙的思路是:我们使用两个指针 node1,node2 分别指向两个链表 headA,headB 的头结点,然后同时分别逐结点遍历,当 node1 到达链表 headA 的末尾时,重新定位到链表 headB 的头结点;当 node2 到达链表 headB 的末尾时,重新定位到链表 headA 的头结点。这样,当它们相遇时,所指向的结点就是第一个公共结点。

假如两个没有交点,两个链表一直交换不会死循环吗?

答:如果没有重合部分,那么 a 和 b 在某一时间点 一定会同时走到 null,从而结束循环。System.out.println(null==null);// 输出为 true while (PA != PB) 在这里会跳出while循环,到达下一步 return PA,也就是 return null。

如果有重合部分,分两种情况。

长度相同的话, a 和 b 一定是同时到达相遇点,然后返回。
长度不同的话,较短的链表先到达结尾,然后指针转向较长的链表。此刻,较长的链表继续向末尾走,多走的距离刚好就是最开始介绍的解法,链表的长度差,走完之后指针转向较短的链表。然后继续走的话,相遇的位置就刚好是相遇点了。

视频讲解:小美图解剑指Offer 52题 两个链表的第一个公共节点 java版本

headA:4->1->8->4->5
headB:5->0->1->8->4->5

headAB:4->1->8->4->5->5->0->1->8->4->5

headBA:5->0->1->8->4->5->4->1->8->4->5

image-20210115094510717

测试用例如上图,但是我在IDEA上不会设置测试用例,最终代码如下。代码参考自1:+代码参考自2:

public class Offer52 {
    public static void main(String[] args) {
        int[] nums1 = new int[]{4, 1, 8, 4, 5};
        int[] nums2 = new int[]{5, 0, 1, 8, 4, 5};
        ListNode headA = nums2ListNode(nums1);
        ListNode headB = nums2ListNode(nums2);
        printListNode(headA);
        printListNode(headB);
        ListNode resHead = getIntersectionNode(headA, headB);
        printListNode(resHead);
    }

    private static void printListNode(ListNode head) {
        StringBuffer res = new StringBuffer();
        while (head.next != null) {
            res.append(head.val + "->");
            head = head.next;
        }
        res.append(head.val);
        System.out.println(res);
    }

    private static ListNode nums2ListNode(int[] nums) {
        // 1.首先先去创建头结点
        ListNode head = new ListNode(nums[0]);
        // 2.创建结点 node 去遍历数组元素
        ListNode node = head;
        // 3.从数组的第2个元素依次遍历
        for (int i = 1; i < nums.length; i++) {
            // 4.创建临时结点
            ListNode temp = new ListNode(nums[i]);
            node.next = temp;
            // 更新 node 节点
            node = temp;
        }
        return head;
    }

    public static ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode PA = headA;
        ListNode PB = headB;
        while (PA != PB) {
            PA = (PA == null ? headB : PA.next);
            PB = (PB == null ? headA : PB.next);
        }
        return PA;
    }
}

今日语录:
这世上的爱,原本有千万种模样。他们的生活中,虽然没有甜言蜜语、海誓山盟,却有无数个庸常日子里的点滴温暖,和那些柴米油盐里的守望相助。因为懂得,所以慈悲。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值