算法刷题3【剑指offer系列之链表】

2020.05.31

1、链表倒序输出为数组

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
思路1:直接遍历链表,每次插入到0
的位置,但是这样数组后面的元素都需要移动,效率低。
思路2:使用栈,先全放入栈,再输出
注意:因为不能改变输入的链表,所以不能将链表倒序再输出。
在这里插入图片描述

 public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> res=new ArrayList<>();
        if (listNode==null){
            return res;
        }
        ArrayDeque<ListNode> stack=new ArrayDeque<>();
        //1、依次遍历存入栈
        while (listNode!=null){
            stack.push(listNode);
            listNode=listNode.next;
        }
        //2、从栈弹出结算
        while (!stack.isEmpty()){
            res.add(stack.pop().val);
        }
        return res;
    }

2、输入一个链表,输出该链表中倒数第k个结点

思路:这题思路不难,但是需要注意处理k和链表长度的关系
使用快慢指针,快指针先走k,然后和慢指针一起走,快指针走到了结尾(null),则慢指针所指向的结点就是倒数第个结点。相当于将倒数第k的长度k转移到顺数第k

   /**
     * 快慢指针的应用:关键是处理好k和链表长度的关系
     * @param head
     * @param k
     * @return
     */
    public  static ListNode FindKthToTail(ListNode head,int k) {
        if (head==null||k<=0){
            return null;
        }
        ListNode fast=head,slow=head;
        //鲁棒性:处理k的大小关系
        while (fast!=null&&k>0){
            fast=fast.next;
            k--;
        }
        //处理k>长度的情况,鲁棒性
        if (k>0){
            return null;
        }
        while (fast!=null){
            slow=slow.next;
            fast=fast.next;
        }
        return slow;
    }
扩展:寻找链表的中间结点

在这里插入图片描述

3、链表反转

输入一个链表,反转链表后,输出新链表的表头。
思路:这题是震哥字节一面的算法题。
(1)使用cur结点依次遍历链表
(1)使用next结点保存下一个结点,这样才能成功遍历到下一个
(2)使用pre结点表示前一个,这样cur.next=pre就完成了逆序。而且因为第一次循环的cur=head,cur.next=pre,所以其实pre就是需要返回的结果链表的头结点。

 /**
     * @param head
     * @return
     */
    public ListNode Reverse(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode pre=null,cur=head,next=null;
        while (cur!=null){
            //1、先报错下一个结点
            next=cur.next;
            //2、实现逆序
            cur.next=pre;
            //3、后面的两步都是为了能够继续遍历链表
            pre=cur;
            cur=next;
        }
        //4、pre就是结果链表的头结点
        return pre;
    }

4、外排法合并链表

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
思路:就是归并排序的链表形式,其实比归并排序更加简单。如果数数组的归并排序,还需要在merge函数那里使用辅助数组,先merge到辅助数组,然后再还原到arr。链表的merge只需要引入一个结果结点res,然后cur=res,使用cur去结算结果。

public  static ListNode Merge(ListNode list1, ListNode list2) {
        if (list1 == null) {
            return list2;
        }
        if (list2==null){
            return list1;
        }
        ListNode res=new ListNode(-1);
        //注意不是res.next,因为这时res.next=null
        ListNode cur=res;
        while (list1!=null&&list2!=null){
            if (list1.val<=list2.val){
                cur.next=list1;
                list1=list1.next;
            }else {
                cur.next=list2;
                list2=list2.next;
            }
            cur=cur.next;
        }
        //因为是链表,所以不需要使用while循环
        if (list1!=null){
            cur.next=list1;
        }
        if (list2!=null){
            cur.next=list2;
        }
        return res.next;
    }

2020.06.01

5、复制复杂链表(链表类型题目的boss)

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
关键是需要根据random指针找到随机结点,能够完成random的构建。
思路1:使用hashmap:hash存的key是每个节点、value是复制节点,这样就能很快找到复制节点,再处理指针
(1)遍历链表,依次存入map,key就是cur结点,value是以cur.val生成的复制结点
(2)再次遍历链表,从map中获取当前结点的对应的value(也就是复制结点)复制结点的next就是map中从cur.next中获取,map.get(cur).next = map.get(cur.next);
同理随机指针也是这样操作:map.get(cur).random = map.get(cur.random);
在这里插入图片描述

/**
 * 使用hash表实现,比较简单
 * @param head
 * @return
 */
public static Node copyListWithRand1(Node head) {
    HashMap<Node, Node> map = new HashMap<Node, Node>();
    Node cur = head;
    //1.遍历链表,将节点放入hash表,value为新产生的节点
    while (cur != null) {
        map.put(cur, new Node(cur.value));
        cur = cur.next;
    }
    //2.重新回到头结点,遍历链表
    cur = head;
    while (cur != null) {
        //cur的value也就是复制的节点的next为cur.next对应的value
        map.get(cur).next = map.get(cur.next);
        //随机节点也是一样处理
        map.get(cur).rand = map.get(cur.rand);
        cur = cur.next;
    }
    //3、返回头结点对应的value(也就是头结点对应的复制节点)
    return map.get(head);
}

思路2:其实也可以将random指针的关系记录到链表本身,将复制结点cloneNode放在当前结点cur的next,这样,cloneNode.next=cur.next.next;同时也可以通过cloneNode.random=cur.random.next找到随机结点但是需要进行拆分处理,将复杂的问题拆分成小的问题再分别处理。
在这里插入图片描述在这里插入图片描述

 public RandomListNode Clone(RandomListNode pHead) {
        if (pHead == null) {
            return null;
        }
        //1、遍历复制结点
        RandomListNode curNode = pHead, cloneNode = null, nextNode = null;
        while (curNode != null) {
            cloneNode = new RandomListNode(curNode.label);
            nextNode = curNode.next;
            //挂链
            curNode.next = cloneNode;
            cloneNode.next = nextNode;
            curNode = nextNode;
        }
        //2、处理随机结点
        curNode = pHead;
        while (curNode != null) {
            cloneNode = curNode.next;
            //注意什么时候都需要进行判断是否为空指针
            if (curNode.random != null) {
                cloneNode.random = curNode.random.next;
            }
            //注意这里因为有了复制结点,需要两次next以后才是next结点
            curNode = curNode.next.next;
        }
        //3、拆分:只需按照next来拆--最难
        curNode = pHead;
        RandomListNode res = curNode.next;
        while (curNode != null) {
            cloneNode = curNode.next;
            //拆分原链表:下面是不需要判断的,因为如果curNode!=null,则curNode.next肯定不为null
            //因为实际上就是复制结点
            // if (curNode.next!=null){
            nextNode = curNode.next.next;
            curNode.next = nextNode;
            //拆分复制链表
            if (cloneNode.next != null) {
                cloneNode.next = cloneNode.next.next;
            }
            curNode = nextNode;

        }
        return res;
    }

6、找出它们的第一个公共结点

输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
思路:可以使用栈,两个链表先入栈,然后依次弹栈,找到最后一个相同的结点,这个结点就是第一个公共结点,因为如果链表相交,显然最后结点一样的,但是这样需要辅助空间,显然不是最优解。
在这里插入图片描述最优解:使用四个变量,分别记录pHead1的end1,len1;pHead2的end2,len2;(避免了使用栈的辅助空间)
(1)如果end1!=end2,则肯定没有相交的结点
(2)如果end1==end2,则先算出len1和len2的差值num,长的链表先走num,然后在一起走,遇到相等的,那就是第一个相交结点

public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        int len1 = 0, len2 = 0;
        ListNode end1 = pHead1, end2 = pHead2;
        while (end1.next != null) {
            end1 = end1.next;
            len1++;
        }
        while (end2.next != null) {
            end2 = end2.next;
            len2++;
        }
        //结尾不同的话肯定没有公共节点
        if (end1 != end2) {
            return null;
        }
        int more = 0;
        //end1指向长的
        if (len1 >= len2) {
            more = len1 - len2;
            end1 = pHead1;
            end2 = pHead2;
        } else {
            more = len2 - len1;
            end1 = pHead2;
            end2 = pHead1;
        }
        //长的先走
        for (int i = 0; i < more; i++) {
            end1 = end1.next;
        }
        //再一起走
        while (end1 != end2) {
            end1 = end1.next;
            end2 = end2.next;
        }
        return end1;
    }

7、找出链表的入环节点

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
思路:使用快慢指针,快指针每次走2步,慢指针每次走1步,两个指针相遇的时候一定是在环内,相遇以后快指针回到起点(慢指针保持不动),然后快慢指针一起走,再次相遇的结点就是入环结点。
在这里插入图片描述

public ListNode EntryNodeOfLoop(ListNode pHead) {
        if (pHead==null){
            return null;
        }
        ListNode fast=pHead,slow=pHead;
        //1、找到相交结点
        while (fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if (slow==fast){
                break;
            }
        }
        //2、说明是因为没有环退出循环的
        if (fast==null||fast.next==null){
            return null;
        }
        //3、fast回到起点,slow不动
        fast=pHead;
        while (fast!=slow){
            fast=fast.next;
            slow=slow.next;
        }
        return slow;
    }
扩展:两个单链表相交的一系列问题(找到两个有环单链表的第一个相交结点)

在这里插入图片描述
问题1:判断链表是否有环?有环的话返回入环节点,否则返回null

在这里插入图片描述
问题2:找出两个无环链表的第一个相交结点
在这里插入图片描述
如果一个链表有环一个无环,不可能相交,因为链表是线性结构。
问题3:两个有环链表相交的第一个结点。
在这里插入图片描述

package com.iyuanyuan.list;

/**
 * 〈一句话功能简述〉
 * 〈两个单链表相交的一系列问题〉
 *
 * @author wenjun
 * @create 2020/6/1
 * @since 1.0.0
 */
public class FindFirstIntersectNode {

    /**
     * 链表节点
     */
    public static class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }

    /**
     * 主要过程函数
     * @param head1
     * @param head2
     * @return
     */
    public static Node getIntersectNode(Node head1, Node head2) {
        if (head1==null||head2==null){
            return null;
        }
        Node loop1 = getLoopNode(head1);
        Node loop2 = getLoopNode(head2);
        //单链表相交问题
        if (loop1==null&&loop2==null){
            return noLoop(head1,head2);
        }else if (loop1!=null&&loop2!=null){
            return bothLoop(head1,loop1,head2,loop2);
        }else {
            //不可能存在一个有环一个无环
            return null;
        }
    }

    /**
     * 1、判断是否有环
     * @param head
     * @return
     */
    public static Node getLoopNode(Node head) {
        if (head==null){
            return null;
        }
        Node fast=head,slow=fast;
        while (fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if (slow==fast){
                break;
            }
        }
        if (fast.next==null){
            return null;
        }
        fast=head;
        while (fast!=slow){
            fast=fast.next;
            slow=slow.next;
        }
        return slow;
    }

    /**
     * 2、无环单链表第一个相交结点
     * @param head1
     * @param head2
     * @return
     */
    public static Node noLoop(Node head1, Node head2) {
        if (head1==null||head2==null){
            return null;
        }
        Node end1=head1,end2=head2;
        int len1=0,len2=0,d=0;
        while (end1.next!=null){
            end1=end1.next;
            len1++;
        }
        while (end2.next!=null){
            end2=end2.next;
            len2++;
        }
        if (len1>=len2){
            end1=head1;
            end2=head2;
            d=len1-len2;
        }else {
            end1=head2;
            end2=head1;
            d=len2-len1;
        }
        for (int i=0;i<d;i++){
            end1=end1.next;
        }
        while (end1!=end2){
            end1=end1.next;
            end2=end2.next;
        }
        return end1;
    }

    /**
     * 3、找到两个有环链表相交的第一个结点
     * @param head1
     * @param loop1
     * @param head2
     * @param loop2
     * @return
     */
    public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
        if (head1==null||loop1==null||head2==null||loop2==null){
            return null;
        }
        if (loop1==loop2){
            //1、说明是情况2,但是不能直接调用上面的noLoop,因为结束的条件不一样
            Node end1=head1,end2=head2;
            int len1=0,len2=0,d=0;
            while (end1.next!=loop1){
                end1=end1.next;
                len1++;
            }
            while (end2.next!=loop2){
                end2=end2.next;
                len2++;
            }
            if (len1>=len2){
                end1=head1;
                end2=head2;
                d=len1-len2;
            }else {
                end1=head2;
                end2=head1;
                d=len2-len1;
            }
            for (int i=0;i<d;i++){
                end1=end1.next;
            }
            while (end1!=end2){
                end1=end1.next;
                end2=end2.next;
            }
            return end1;
        }else {
            //2、说明是case1或者case3
            Node cur=loop1.next;
            while (cur!=loop1){
                //需要先判断再遍历下一个,这样才不会错过第一个
                if (cur==loop2){
                    //说明找到了loop1和loop2的相等的,也就是case3
                    return loop1;
                }
                cur=cur.next;
            }
            //退出循环说明走了一圈还没找到loop2,则是case1,没有相交
            return null;
        }
    }


    public static void main(String[] args) {
        // 1->2->3->4->5->6->7->null
        Node head1 = new Node(1);
        head1.next = new Node(2);
        head1.next.next = new Node(3);
        head1.next.next.next = new Node(4);
        head1.next.next.next.next = new Node(5);
        head1.next.next.next.next.next = new Node(6);
        head1.next.next.next.next.next.next = new Node(7);

        // 0->9->8->6->7->null
        Node head2 = new Node(0);
        head2.next = new Node(9);
        head2.next.next = new Node(8);
        head2.next.next.next = head1.next.next.next.next.next; // 8->6
        System.out.println(getIntersectNode(head1, head2).value);

        // 1->2->3->4->5->6->7->4...
        head1 = new Node(1);
        head1.next = new Node(2);
        head1.next.next = new Node(3);
        head1.next.next.next = new Node(4);
        head1.next.next.next.next = new Node(5);
        head1.next.next.next.next.next = new Node(6);
        head1.next.next.next.next.next.next = new Node(7);
        head1.next.next.next.next.next.next = head1.next.next.next; // 7->4

        // 0->9->8->2...
        head2 = new Node(0);
        head2.next = new Node(9);
        head2.next.next = new Node(8);
        head2.next.next.next = head1.next; // 8->2
        System.out.println(getIntersectNode(head1, head2).value);

        // 0->9->8->6->4->5->6..
        head2 = new Node(0);
        head2.next = new Node(9);
        head2.next.next = new Node(8);
        head2.next.next.next = head1.next.next.next.next.next; // 8->6
        System.out.println(getIntersectNode(head1, head2).value);
    }
}

8、有序链表删除重复节点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
思路:在遍历的同时判断是否为重复节点,则删除,关键是需要记录前一个结点
(1)因为第一个结点就有可能重复,需要被删除,所以需要先新建一个结点res,res.next=pHead,返回结果就是res.next,而不是新建结点直接指向pHead
(2)使用pre记录重复结点的前一个结点,这样才能保证正确钩链;使用cur遍历链表,如果遇到cur.val=cur.next.var,则使用while循环,一直遍历到cur.val!=cur.next.val,此时的cur就是最后一个重复结点,还需要cur=cur.next,这样就找到了第一个不重复的结点,然后令pre.next=cur;如果一开始就遇到cur.val!=cur.next.var,则直接pre=cur,cur=cur.next,继续遍历下一个
(3)最后返回res.next就是结果
需要注意的问题:
1、比较节点重复应该是比较数值,而不是对象引用;
2、如何将新的链表返回?需要新建res结点,不能直接使用pHead(因为可能被删除)
在这里插入图片描述

public class DeleteDuplication {

    public ListNode deleteDuplication(ListNode pHead){
        if (pHead==null){
            return null;
        }
        //开始遍历
        ListNode res=new ListNode(-1);
        res.next=pHead;
        ListNode pre=res,cur=pHead;
        while (cur!=null){
            //1、找到重复的结点
            if (cur.next!=null&&cur.val==cur.next.val){
                //需要一直找下去,找到最后一个重复结点
                while (cur.next!=null&&cur.val==cur.next.val){
                    cur=cur.next;
                }
                //pre连接后一个结点
                cur=cur.next;
                pre.next=cur;
            }else {
                //2、没有重复的则直接往后遍历
                pre=cur;
                cur=cur.next;
            }
        }
        return res.next;
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值