算法刷题 4(链表交换+删除倒数n节点+链表相交+环形链表)

bu目录

 一张图概括链表​

 总结

1、两两交换链表的节点 206

思路

2、删除倒数第n个节点 19

思路

注意点

3、链表相交

思路1---长短链对齐

代码1---长短链对齐

思路2-- 双指针 两链合并

代码2-- 双指针 两链合并

思路3 -- 哈希集合

代码3 -- 哈希集合

4、环形链表 142

理解

图示

推导

思路

注意点


 一张图概括链表

 总结

  • 循环条件,什么情况应该判断指针本身为空呢?
  • 答:看操作的不是虚拟头结点,是的话就不用判断本身,不是的话需要判断;另外看这个这个遍历的指针最后需要走到哪里  需不需要对最后一个节点做操作
  • 学链表的时候注意画图设计简单case边界case来调试,现在写链表的题都是可以脑子有链表图debug了
  • 虚拟头结点作用?
  • 每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题。
  • 链表多往双指针上想!
  • 不要把值和节点的概念混淆起来,节点是一个实例,占用一块空间,值只是它的成员变量。值怎样和节点本身没有任何关系,一个实例只由它的地址唯一确定!
  • 三道题套路解决递归问题 | lyl's blog 里面有递归的题解 很好的博客!

1、两两交换链表的节点 206

24. 两两交换链表中的节点 - 力扣(LeetCode)

思路

  • 保存2个交换后会断的结点,画图更新即可
  • 外层while要有两个next的非空检测
  • 如果在循环之外定义了 temp 和 temp1,这导致它们在循环的每次迭代中都不会更新。这意味着 temp 和 temp1 在第一次迭代之后就不再指向正确的节点,这会破坏链表的结构。
class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummyHead = new ListNode(-1, head);
        ListNode cur = dummyHead;//虚拟节点常用2步曲
        
        while(cur.next != null && cur.next.next != null) {//用&&  不然无限循环!一个next放前面
            ListNode temp = cur.next; //这两个临时变量放循环里面,不然你就更新它们,你这不是附空值,你得放循环里
            ListNode temp1 = temp.next.next;//这两个节点得提前储存,不然改链表指向后就定位不到了
            //三步改写
            cur.next = cur.next.next;
            cur.next.next = temp;
            temp.next = temp1;
            
            cur = cur.next.next;//别忘了移动
        }

        return dummyHead.next;
    }
}

2、删除倒数第n个节点 19

19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

核心就是,保持两个指针距离为n,这样就能准确找到倒数第n个节点的位置了。 
 

思路

  • 一眼快慢指针
  • 要记得指向要删除的元素的前一个,这样才好删,所以fast到n+1步,慢的正好指到要删除节点的前一个点

注意点

  • 边界值带入一个特例算一下
  • 当节点只要不是指向虚拟结点时,都要判断自身是否为null,不能直接调用next!!!
        //思路:双指针,快的先动n+1步再走,慢的正好指到要删除节点的前一个点
        ListNode dummy = new ListNode(-1, head);
        //ListNode cur = dummy;
        ListNode fast = dummy, slow = dummy;
        int i = 1;//从1开始,带入一个数字算一下
        while (i <= n + 1){//快的先到指定位置
            fast = fast.next;
            i++;
        }
        while (fast != null) {
            //当节点只要不是指向虚拟结点时,都要判断自身是否为null,不能直接调用next
            fast = fast.next;
            slow = slow.next;//结束时slow正好在要删除节点的前一个
        }

        slow.next = slow.next.next;
        return dummy.next;
    }

3、链表相交

面试题 02.07. 链表相交 - 力扣(LeetCode)

两个链表的交点不是数值相同,而是指针相同。因此我们可以让 AB 两个链表末尾对齐,比较两个指针所指的位置是否相同判断是否存在交点。

思路1---长短链对齐

  1. 算出2链长度
  2. for 循环将长链先走lenB-lenA步
  3. 两链同时向前走,相等就返回

注意点

  • 算完长度后移动长链时,要重新将指针摆回头结点
  • 两种向后移动代码,参看代码块:一种while(curA != null),一种 while(curA != curB),但是条件判断放的位置不同

代码1---长短链对齐

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        //先行移动长链来实现同步移动
        ListNode curA = headA;
        ListNode curB = headB;
        int lenA = 0, lenB = 0;
        //算长度
        for (; curA != null; curA = curA.next){
            lenA++;
        }
        for (; curB != null; curB = curB.next){
            lenB++;
        }
        curA = headA;//重新刷新赋值,不用再写ListNode了
        curB = headB;
        //对齐
        if (lenA >= lenB) {
            for (int i = 0; i < lenA - lenB; i++) {
                curA = curA.next;
            }
        } else {
            for (int i = 0; i < lenB - lenA; i++) {
                curB = curB.next;
            }            
        }
        //开始移动,相同就返回;注意不是判断值,是判断指针是否相同
        // while (curA != curB) {//这种while中带限制写法,等while运行完再进行比较
        //     curA = curA.next;
        //     curB = curB.next;
        //     }
        // if (curA == curB) {
        //     return curA;
        // }
        // return null;//一直没有返回空

        while (curA != null) {//上面那种写法是全部比一遍,也是停在相同节点处
        //注意:得先进行比较,不然就漏掉第一个点了
            if (curA == curB) {//这种写法就是遇到指向相同的就返回了
                return curA;
            }
            curA = curA.next;
            curB = curB.next;
        }
        return null;
    }

思路2-- 双指针 两链合并

使用双指针的方法,可以将空间复杂度降至 O(1)

当链表 headA 和 headB都不为空时,创建两个指针 pA 和 pB,初始时分别指向两个链表的头节点 headA和 headB,然后将两个指针依次遍历两个链表的每个节点。具体做法如下:

  • 每步操作需要同时更新指针 pA和 pB
  • 如果指针 pA不为空,则将指针 pA 移到下一个节点;如果指针 pB不为空,则将指针 pB移到下一个节点。
  • 如果指针 pA 为空,则将指针 pA移到链表 headB的头节点;如果指针 pB为空,则将指针 pB移到链表 headA 的头节点。
  • 当指针 pA和 pB 指向同一个节点或者都为空时,返回它们指向的节点或者 null

核心:curA == null ? headB : curA.next;

代码2-- 双指针 两链合并

两种--写不写复杂表达式

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        //两链合并遍历找交点
        ListNode curA = headA;
        ListNode curB = headB;

        //如果你想提升点速度,就加一个空判断
        if (headA == null || headB == null) {
            return null;
        }

        while (curA != curB) {
            //?后面不用写两个curA=,已经有了
            curA = curA == null ? headB : curA.next;//双等号
            curB = curB == null ? headA : curB.next;
        }
        return curA;//这个不相交也能直接输出null
    }
//方法二:双指针:长短指针都遍历一遍,会在一个值相遇,进行返回
        ListNode temp = headA, temp1 = headB;
        while (temp != temp1) {
            if (temp == null) {
                temp = headB;
            } else {
                temp = temp.next;
            }
            if (temp1 == null) {
                temp1 = headA;
            } else {
                temp1 = temp1.next;
            }
        }
        return temp1;
    }

思路3 -- 哈希集合

哈希做法比较好理解

思路

1、将A链存入set

2、用个指针指向b,找B链在集合中有没有一致的即可

空间复杂度O(m+n)

代码3 -- 哈希集合

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        //哈希集合做法,将a存入,再查询b中有没有一致的
        Set<ListNode> visited = new HashSet<ListNode>();
        ListNode cur = headA;
        //将A链存入set
        while (cur != null) {
            visited.add(cur);
            cur = cur.next;
        }
        //找B链有没有一致的
        cur = headB;//已经定义过的变量不用再加变量名
        while (cur != null) {
            if (visited.contains(cur)){
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }

4、环形链表 142

142. 环形链表 II - 力扣(LeetCode)

理解

  1. 是否有环:快慢指针相交
  2. 入环节点:index1和index2相遇
  3. 首先判断是否有环,其次寻找交点
  4. 这种链表的寻找入口、寻找尾部节共同节点啥的基本都是双指针;

图示

推导

思路

  • 快慢链表相遇等于有环
  • 快慢指针2步、1步,如果相遇,进行入口判断
  • 因为慢x+y,快x+y+n(y+z),这两个相等,推导一下x = (n - 1) (y + z) + z,所以x=z,那就2个指针,一个放头结点、一个放相遇点,相等就可以了
  • 以fast为条件先进去判断while循环
  • 判断是否有环:if (fast == slow),记录相遇点,推导之后相遇点和head的中间就是环入口!
  • 开始找入环节点,这个要放在if的判断条件下面,while (index1 != index2) 就能找到

注意点

  • 找入环节点,这个要放在if的判断条件下面
  • 最外层的while循环,没有定虚拟节点,你就得考虑fast自身是否为空;并且因为fast是走2步,所以要判断next
public ListNode detectCycle(ListNode head) {
        //快慢链表相遇等于有环
        ListNode fast = head, slow = head;
        //以fast为条件先进去判断
        //这个while循环,没有定虚拟节点,你就得考虑fast自身是否为空
        while(fast != null && fast.next != null){//因为fast是走2步,所以要判断next
            fast = fast.next.next;
            slow = slow.next;
            //开始判断是否有环
            if (fast == slow) {
                ListNode index1 = head;
                ListNode index2 = fast;//记录相遇点,推导之后相遇点就是环入口
                //开始找入环节点,这个要放在if的判断条件下面
                while (index1 != index2) {
                    index1 = index1.next;
                    index2 = index2.next;
                }
                return index1;
            } 
        }
        return null;//没有就返回空
    }

拜拜~~

  • 30
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值