LeetCodeHot100_0x04

发布于:2025-03-03 ⋅ 阅读:(114) ⋅ 点赞:(0)

LeetCodeHot100_0x04

25.环形链表

求解思路: 首先,哈希秒杀,存节点,只要出现相同节点就有环。

其次、链表题很喜欢用到双指针,所以这题优先考虑双指针。我们可以定义一对快慢指针,如果链表没有环,可以保证fast指针优先遍历完链表,然后返回false;如果链表有环,则会不断进入循环,直到快慢指针一定有某一次发生相遇,返回true。
【快慢指针法】

public class Solution {
    public boolean hasCycle(ListNode head) {
        // 快慢指针 有环必相遇,没环fast为空
        ListNode fast = head;
        ListNode solw = head;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            solw = solw.next;
            if(fast == solw) return true;
        }
        return false;
    }
}


26. 环形链表Ⅱ(不会)

求解思路: 这题只能想到哈希法,判断有无重复节点,返回即可。
参考题解的快慢指针法,关键在于数学关系推导。
【哈希法】

public class Solution {
    public ListNode detectCycle(ListNode head) {
        Set<ListNode> hs = new HashSet<>();
        ListNode node = head;
        while(node != null && node.next != null) {
            if(!hs.isEmpty() && hs.contains(node)) {
                return node;
            }
            hs.add(node);
            node = node.next;
        }
        return null;
    }
}

【快慢指针法】
下图来自于LeetCode官方题解,我借助下图进行讲解总结
在这里插入图片描述
设环外长度为a,当slow指针走到环扣时,fast指针必然在环内(紫色点),记录此时slow指针距离fast指针距离为b,剩余距离为c;假设fast已经走过n圈,此时:

  • slow走过的长度: a
  • fast走过的长度:a + n(b + c) + b

由于fast指针永远比slow指针快1个单位的速度,现在可以看作fast指针还差c个单位可以追上A点的slow;所以当:

  • slow走过的长度:a+c
  • fast走过的长度:a+n(b+c)+b+2c

两者重合,根据二倍关系化简式子:
a + (n+1)b + (n+2) c = 2a + 2c
-------> a = (n+1)b + nc
------->b + n(b + c)
这下好了,发现规律了,环外距离a的长度等于 b + n 倍的环内长度,其中b是fast 和 slow相遇点(距离环口的距离),所以我们只需要再定义一个指针pre,让它和慢指针一起走,相遇的时候,就是环口了。

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        ListNode pre = head;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow) {
                while(pre != slow) {
                    pre = pre.next;
                    slow = slow.next;
                }
                return pre;
            }
        }
        return null;
    }
}


27. 合并两个有序链表(不熟)

求解思路: 新开一个链表,遍历每次把当前两个链表种较小的值加入。
之所以不熟,还是对链表的理解太弱了。直接去操作头节点,然后又忘记指针移位,导致调试了好多次都是错误的。
【双指针法】

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode newList = new ListNode();
        ListNode current = newList; // 当前指针,用于构建新链表

        if(list1 == null) return list2;
        if(list2 == null) return list1;

        while(list1 != null && list2 != null) {
            if(list1.val < list2.val) {
                current.next = list1;
                list1 = list1.next;
            }else {
                current.next = list2;
                list2 = list2.next;
            }
            current = current.next; // 移动指针
        }
        if(list1 != null) {
            current.next = list1;
        }
        if(list2 != null) {
            current.next = list2;
        }
        return newList.next;
    }
}


28. 两数相加(链表版)

求解思路: 有了上一题合并两个有序链表的经验后,这一题写的就很轻松了。咱直接模拟小学生学加法进位,用链表实现即可。

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        if(l1==null) return l2;
        if(l2==null) return l1;
        
        ListNode ans = new ListNode();
        ListNode curr = ans;
        int k = 0;

        while(l1!=null && l2!=null) {
            int value = (l1.val + l2.val + k) % 10;
            k = (l1.val + l2.val + k) / 10;
            curr.next = new ListNode(value);
            curr = curr.next;
            l1 = l1.next;
            l2 = l2.next;
        }
        while(l1!=null) {
            if(k!=0) {
                int value = (l1.val + k) % 10;
                k = (l1.val + k) / 10;
                curr.next = new ListNode(value);
                curr = curr.next;
                l1 = l1.next;
            }else{
                curr.next = l1;
                break;
            }
        }
        while(l2!=null) {
            if(k!=0) {
                int value = (l2.val + k) % 10;
                k = (l2.val + k) / 10;
                curr.next = new ListNode(value);
                curr = curr.next;
                l2 = l2.next;
            }else{
                curr.next = l2;
                break;
            }
        }
        if(k!=0) {
            curr.next = new ListNode(k);
        }
        return ans.next;
    }
}


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

求解思路: 逆向思维求解或递归求解
【逆向思维】删除倒数节点,可以先求出节点数再正向找等效位置的节点

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // 先遍历一遍计算结点数,在正向删不就好了
        ListNode curr = head;
        int cnt = 0;
        while(curr != null) {
            cnt++;
            curr = curr.next;
        }
        if(n==cnt) return head.next; // 删除头节点的情况
        int k = cnt - n;  // 需要删除节点的前一个节点
        curr = head;
        while(--k > 0) {
            curr = curr.next;
        }
        curr.next = curr.next.next;
        return head;
    }
}

【递归法】

class Solution {

    private int cnt = 0;
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // 递归写法,先深入到尾节点,然后递归回溯
        if(head == null) return null;
        head.next = removeNthFromEnd(head.next,n);
        cnt++;
        if(n==cnt) return head.next;
        return head;
    }
}


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

求解思路: 链表题做多了,类型很固定,基本上考察指针遍历和递归思想。
【双指针变量】

class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null) return head;
        ListNode p = head;
        ListNode q = head.next;
        while(p != null && p.next != null){
            q = p.next;
            int temp = q.val;
            q.val = p.val;
            p.val = temp;
            p = q.next;
        }
        return head;
    }
}

【递归法】看了题解理解的,链表题在遍历中总喜欢采用自底向上的思想,代码很简洁,但是可读性不是很好。

class Solution {
    public ListNode swapPairs(ListNode head) {
        // 1. 递归出口: 没有或只要一个的时候不用交换了
        if(head == null || head.next == null) {
            return head;
        }
        // 原链表是1->2->3->4。
        // 调用swapPairs(1)的时候,newHead是2。
        // 然后需要处理head.next = swapPairs(newHead.next),即swapPairs(3)。
        // 这时候进入处理3->4的递归层。
        // 在处理swapPairs(3)的时候,newHead是4,
        // 然后处理swapPairs(4.next)也就是swapPairs(null)。
        // 返回null,所以3的next是null。
        // 然后newHead(4)的next指向3,返回4。
        // 所以在处理3->4的递归层,返回的是4->3。
        // 回到最初的层,此时head是1,它的next被设置为4->3,
        // 所以链表变成1->4->3。
        // 然后newHead是2,它的next被设置为1,
        // 所以整个链表变为2->1->4->3。这样就完成了交换。
        ListNode newHead = head.next;
        head.next = swapPairs(newHead.next);
        newHead.next = head;
        return newHead;
    }
}


31. k个一组翻转链表(递归不会)

求解思路: 这个一眼看又是考递归了(雾),但是还是那句话,链表题不会建议转成数组来做,最后把值再填回去就好了。
【反值不反向】时间O(N)、空间O(N)

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        // 反值不反向
        List<Integer> value = new ArrayList<>();
        ListNode curr = head;
        // 值放数组
        while(curr != null) {
            value.add(curr.val);
            curr = curr.next;
        }

        // 按要求调转值
        int n = value.size();
        for(int i=0;i<n/k;i++) {
            int l = i*k;
            int r = Math.min(((i+1) * k - 1),n-1);
            while(l<=r) {
                int temp = value.get(l);
                value.set(l,value.get(r));
                value.set(r,temp);
                l++;r--;
            }
        }

        // 将值填充回链表
        int cnt = 0;
        curr = head;
        while(curr != null) {
            curr.val = value.get(cnt);
            cnt++;
            curr = curr.next;
        }
        return head;
    }
}

32. 总结:

写给自己:今天是LeetCodeHot100第四天,刷了31题,距离一天10题的目标还是有不小差距的。但是10道题确实也很吃我状态,有时候题目稍微难一点我就想抓狂。另外就是最近同步准备面试之类的事情,没办法一整天都在刷题。以前也立下过刷hot,但是都打卡几天就失败了。这次写博客其实也是希望提醒自己,菜真的要多练。

题目总结:
今天很纯粹的链表题:

  • 环形链表 哈希、双指针
    • 判断环可以用哈希找是否有重复节点出现;或者快慢指针
  • 环形链表Ⅱ 双指针、思维
    • 判断环口位置,需要用到快慢指针,存在一个数学推导过程
  • 合并两个有序链表 双指针
    • 跟归并排序的思路差不多
  • 两数相加链表版 双指针、模拟
    • 双指针模拟小学生加法运算过程,注意进位
  • 删除链表中倒数第N个节点 逆向思维、递归
    • 可以将倒数第N个点问题转换为正数第M个;或者用递归的回溯解决链表单向的限制
  • 两两交换链表中的节点 双指针、模拟、递归
    • 两个指针维护前后两个节点,再交换就可以了;用递归也很优雅
  • k个一组翻转链表 变通思维、递归
    • 链表改向麻烦,可以把值按要求给重排再放回去,一样可以达到目的。

总结:链表题常与 哈希表、双指针、递归算法相关联。遇到不会的链表题,可以尝试先将链表结构转换成更为熟悉的数组结构,再解决数组问题,最后再转换成链表。


网站公告

今日签到

点亮在社区的每一天
去签到