算法训练营第四天|24.两两交换链表中的节点、19.删除链表中的倒数第N个节点、面试题0207链表相交、142.环形链表II

今天是第四天,也是学习链表的第二天,主要做了四个题,做个简单的总结:

1.对于链表的操作,尽量创建一个虚拟头结点会简化很多操作;

2.对于链表节点的指针移动,之前一直很迷糊,什么时候该创建临时变量,先移哪个,后移哪个,很容易造成死循环,听了Carl老师的讲解之后真的是茅塞顿开,对于要移动指针的节点,需要将该指针原先指向的节点先暂存下,否则移动操作完成之后,这个原先被指向的节点就找不着了,如果后续还需要对这个节点进行操作的话,就无法完成了。

也就是要把原先那个指向切断,则需要将该指向的对应节点暂存。

理解了这个,对于两两交换链表中的节点这题就非常容易完成了。

24.两两交换链表中的节点

public ListNode swapPairs(ListNode head) {
        ListNode dummyHead = new ListNode(0);//建一个虚拟头结点,指向head
        dummyHead.next = head;

        ListNode curr = dummyHead;
        while(curr.next != null && curr.next.next != null) {

            //此时的结构是这样
            //curr --> 1 --> 2 --> 3 --> 4 --> ...--> null
            //在调整curr.next从原来指向1到指向2之前,得先把1暂存下,不然后面2没法找到这个1节点了,因为指向1的指针被指给2了
            //同样的,在调整2的next由原来指向3改成指向1之前,必须要把3先暂存下
            //不然2.next指向1之后,3就找不到了
            ListNode node1 = curr.next; //暂存现在的1
            ListNode node2 = curr.next.next.next;//暂存现在的3

            curr.next = curr.next.next; //将curr从指向1改成指向2,改完之后curr.next就是指向2了

            //接下来操作2的next指向,由原来的指向3改为指向1
            curr.next.next = node1; //此时2指向1了

            //接下来操作原来的1指向3
            node1.next = node2;

            //调整完之后,指针变成了curr-->2-->1-->3
            //我们需要把curr再移动到1的位置,开始下一次遍历
            //此时1就是curr.next.next

            curr = curr.next.next;

        }

        return dummyHead.next;
    }

19.删除链表中的倒数第N个节点

这一题相对来说比较简单,步骤有以下三点:

1.先算出链表的长度;

2.根据题意给出的N,找到要删除的节点的前一个节点,记为curr;

3.将要删除节点的前一个节点的指针,指向要删除节点的后继节点,也就是curr.next = curr.next.next即可。

    public ListNode removeElements(ListNode head, int target) {
        //这里有两种方法,为什么有两种,因为头结点和后续的节点,对应的删除操作是不一样的
        //如果不借助一个虚拟头结点,则需要分开处理当前链表头结点和非头结点的删除
        //这里为什么是while循环而不是if,是因为有可能新的head节点的值还会是target
        while(head != null && head.val == target) {
            head = head.next;
        }
        //处理完head之后,处理其他节点,这里设定一个临时的指针,用这个指针来移动
        ListNode curr = head;

        //因为后续要取curr.next和curr.next.val所以需要两个非null的判断
        while(curr != null && curr.next != null) {
            if(curr.next.val == target) {
                curr.next = curr.next.next;
            }else {
                curr = curr.next;
            }
        }
        return head;
        //借助一个虚拟头结点,可以将原来节点的头结点和非头结点使用同样的操作来删除
//        ListNode dummy = new ListNode();
//        dummy.next = head;
//
//        ListNode curr = dummy;
//        while(curr != null &&curr.next != null) {
//            if(curr.next.val == target) {
//                curr.next = curr.next.next;
//            }else {
//                curr = curr.next;
//            }
//        }
//        return dummy.next;
    }

    public class ListNode {
        private int val;

        private ListNode next;
        public ListNode() {

        }
        ListNode(int val) {
            this.val = val;
        }

        ListNode(int val, ListNode head) {
            this.val = val;
            this.next = head;
        }
    }

面试题2.7链表相交

这一题一开始有点迷糊,不知道从哪里下手,看了代码随想录的思路,才明白应该怎么做。

链表相交的数学解释应该是从两个链表尾部开始对齐,一直对齐到短的那个链表头部,从短链表的头部一直到尾部,都是可能得相交点,不会出现相交点在长链表的超过短链表长度之外的位置(从尾部对齐开始)。

搞清了这个数学解释之后,就比较好弄了,唯一有点容易出错的地方是对齐的边界,我代码里的distance那个地方,从0开始到distance,应该是<distance,而不是<=;

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        //链表相交的数学解释是在相交点之后,两个链表合二为一,也就是相交点及之后的链表完全一样

        //两个链表相交,必然是从最短的那个链表头部开始,所以应该将长的链表移动到和短的链表末尾对齐的位置,然后逐个比较

        //所以要先求链表长度,再求差值,然后移动较长的那个链表指针
        int lenA = len(headA);
        int lenB = len(headB);

        ListNode longA = null;
        ListNode smallB = null;
        int distance = 0;
        distance = lenA > lenB ? lenA - lenB : lenB - lenA;

        if(lenA > lenB) {
            longA = headA;
            smallB = headB;
        }else {
            longA = headB;
            smallB = headA;
        }

        for(int i = 0; i < distance; i++) {
            longA = longA.next;//移动长的链表到和短链表末尾对齐的位置
        }

        while(longA != null) {
            if(longA == smallB) {
                return longA;
            }
            longA = longA.next;
            smallB = smallB.next;
        }
        return null;
    }

    public int len(ListNode head) {
        int i = 0;
        while(head != null) {
            i++;
            head = head.next;
        }

        return i;
    }

142.环形链表II

这一题以前看过别的思路,如果只是判断链表是否有环的话,应该用一个快指针,一个慢指针,两个指针有重合就表示有环,但是这一题需要返回入环的节点,用快慢指针要涉及到计算入环节点,这里我用了个偷懒的办法,遍历链表,将元素都存入Set集合,每次存之前判断下是否已经存过,有即表示回到环的入口了,返回这个节点就行。

public ListNode detectCycle(ListNode head) {
        //用一个set记录所有遍历过的node,每次遍历都去set里面查找,如果有重复的,证明是环的入口
        HashSet<ListNode> set = new HashSet<ListNode>();
        if(head == null) return null;
        while(head != null) {
            if(!set.contains(head)) {
                set.add(head);
            }else {
                return head;
            }
            head = head.next;
        }
        return null;
    }

总结:链表的操作乍一看挺简单的,就是一个节点里有value有next指针,但是真操作起来,对于移动的位置,是否需要虚拟头结点,各种交换,还是挺麻烦的,需要很仔细,并且要理清指针移动的底层逻辑。感觉这一块还是得经常复习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值