2.leetcode--链表

PS:虚拟头节点是真的好用, 凡是需要考虑左右边界的问题, 加个虚拟头节点准没错

1.删除链表倒数第K个节点

题目要求:

难度:✨✨

解答:一前一后双指针法 要删除倒数第K个节点,就要找到倒数第K+1个节点

  • 创建一个虚拟头节点pre,指向链表head
  • right left 指针都先指向pre

  • right先走K步 然后 right 和 left 再开始同步走 直到right.next为null

  • 此时 left指向倒数K+1个节点

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode pre = new ListNode(0);
        pre.next = head;
        ListNode right = pre, left = pre;
        while(n != 0) {
            right = right.next;
            n--;
        }
        while(right.next != null) {
            right = right.next;
            left = left.next;
        }
        left.next = left.next.next;
        return pre.next;
          
    }
}

2.链表是否有环(环形链表)

题目要求:

难度:✨✨

解答:2倍速双指针

  • 慢指针 slow 走了 k 步,那么快指针 fast 一定走了 2k 步
  • 相遇时 他们一定在环中某个点。 假设当前点距离环的起点距离为X,则环的起点距离链表起点head距离就为K-X
  • 那么 将任意一个指针指向head节点 两个指针在同步走K-X步 就一定可以相遇。
public ListNode detectCycle(ListNode head) {
         ListNode fast, slow;
        fast = slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) break;
        }
        // 上面的代码跳出循环时 要么 两指针相遇  要么遇到null
        if (fast == null || fast.next == null) {
            // fast 遇到空指针说明没有环
            return null;
        }
        // 重新指向头结点
        slow = head;
        // 快慢指针同步前进,相交点就是环起点
        while (slow != fast) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

3.环形单链表的约瑟夫问题

题目要求:

据说著名犹太历史学家Josephus有过以下故事:在罗马人占领乔塔帕特后,39个犹太人与Josephus 及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,报数到3的人就自杀,然后再由下一个人重新报1,报数到3的人再自杀,这样依次下去,直到剩下最后一个人时,那个人可以自由选择自己的命运。这就是著名的约瑟夫问题。现在请用单向环形链表描述该结构并呈现整个自杀过程。

输入:一个环形单向链表的头节点head和报数的值m。返回:最后生存下来的节点,且这个节点自己组成环形单向链表,其他节点都删掉。

难度:✨✨ ✨

解答:直接算出谁最后生存

  1. 普通解法:不断遍历链表,报数m就删除,然后修复成环形,直到最后。O(n×m)
  2. 进阶解法:
  • 节点B与其所报的数X的对应关系:设链表长为i
    B(节点编号)X(循环报的数)
    11
    22
    .....
    ii
    1i+1
    2i+2

    推出:节点编号=(报的数-1)%链表长度+1

  • 节点删除前后对应关系:链表长度为i,删除后为i-1old=(new+s-1)%i +1

  • old=(new+m-1)%i+1。至此,我们终于得到了Num(i-1)—new和 Num(i)-old

    public Node josephusKil12 (Node head, int m){
        if (head == null ll head.next == head ll m< 1){
            return head;
        }
        Node cur = head.next;
        int tmp = 1;// tmp ->list sizewhile (cur != head){
            tmp++;
            cur =cur.next;
        )
        tmp = getLive (tmp,m); 
        while(--tmp !=0){
            head = head.next;
        )
        head .next = head;
        return head;
    
        public int getLive(int i, int m) {
            if(i==1){
                return 1;
            )
        return (getLive (i - 1,m) +m- 1)%i + l;
    )
    

4.相交链表

题目要求:

难度:🌟

解答:

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

            if(p2==null){
                p2=headA;
            }else{
                p2=p2.next;
            }
        }
        return p1;
    }
}

5.合并K个有序链表

题目要求:

难度:🌟🌟🌟

解答:

  • 优先队列PriorityQueue,重写比较器Comparator 实现最小优先
  • 有K个链表,最小优先队列的大小就为K
  • 每次从队列中取出一个节点currentnode,当currentnode.next不为空时就将其补入队列
  • 创造一个虚拟头节点和遍历指针
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        ListNode newhead=new ListNode(-100);
        ListNode p=newhead;

        PriorityQueue<ListNode> queue=new PriorityQueue(new Comparator<ListNode>() {
            @Override
            public int compare(ListNode o1, ListNode o2) {
                return o1.val-o2.val;
            }
        });
        for(ListNode head:lists){
            if(head!=null){
                queue.add(head);
            }
        }

        while(!queue.isEmpty()){
            ListNode currentnode=queue.poll();
            p.next=currentnode;
            if(currentnode.next!=null){
                queue.add(currentnode.next);
            }
            p=p.next;
        }
        return newhead.next;
    }
}

6.反转链表中的一部分

题目要求:

难度:🌟🌟

解答:

  • 双指针头插法
  • 设置两个指针,一个守卫指针guard指向left左边第一个节点。
  • 一个边界指针boundary指向left。
  • 循环将boundary.next头插法插入到guard后面。
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode virtualHead = new ListNode(-1);
        virtualHead.next=head;

        ListNode guard = virtualHead;
        ListNode boundary = virtualHead.next;

        for(int i=0;i<left-1;i++){ //先移动到起点
            guard=guard.next;
            boundary=boundary.next;
        }
        for(int k=0;k<right-left;k++){
            ListNode insert = boundary.next;
            boundary.next=boundary.next.next;
            insert.next=guard.next;
            guard.next=insert;
        }
        return virtualHead.next;
    }
}

7.反转链表的前N个节点

题目要求:

难度:🌟

解答:

  • 递归
  • 不要跳进递归,而是要根据刚才的函数定义,来弄清楚这段代码会产生什么结果
  •  head 节点在递归反转之后不一定是最后一个节点了(因为不是翻转整个链表),所以要记录后驱 successor(第 n + 1 个节点),反转之后将 head 连接上。

class Solution {
    public ListNode reverseList(ListNode head,int n) {
        ListNode successor = null; // 后驱节点
        return reverse N(head,n);
    }
    // 反转以 head 为起点的 n 个节点,返回新的头结点
    public ListNode reverseN(ListNode head, int n) {
        if (n == 1) {
            // 记录第 n + 1 个节点
            successor = head.next;
            return head;
        }
        // 以 head.next 为起点,需要反转前 n - 1 个节点
        ListNode last = reverseN(head.next, n - 1);
        head.next.next = head;
        // 让反转之后的 head 节点和后面的节点连起来
        head.next = successor; 🌟🌟🌟
        return last;
    }
}

PS:若为翻转整个链表

ListNode reverse(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }
    ListNode last = reverse(head.next);
    head.next.next = head;
    head.next = null; 🌟🌟🌟
    return last;
}

8.K个一组翻转链表

题目要求:

难度:🌟🌟🌟

解答:

  • 从head开始 K个一组翻转,翻转结束后 递归 将第K+1个节点作为head开始翻转
  • 定义两个边界指针 指向第1个和第K+1个节点
  • 再将整个链表连起来
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if(head == null) return null;
        ListNode boundary_A,boundary_B;
        boundary_A=boundary_B=head;
        //找到区间
        for(int i=0;i<k;i++){
            if(boundary_B == null){
                return head;
            }else{
                boundary_B=boundary_B.next;
            }
        }
        ListNode newhead = reverse(boundary_A,boundary_B);
        boundary_A.next=reverseKGroup(boundary_B,k);
        return newhead;
    }

    public ListNode reverse(ListNode boundary_A,ListNode boundary_B){
        ListNode previous,current,theNext;
        previous=null;
        current=theNext=boundary_A;
        while(theNext != boundary_B){ //挨个翻转
            theNext=current.next;
            current.next=previous;
            previous=current;
            current=theNext;
        }
        return previous;
    }

}

9.判断回文链表

题目要求:

难度:🌟

解答:

(1)

  • 先将链表元素押入栈中,并记录链表长度。
  • 遍历链表前1/2 部分,每遍历一个节点就stack.pop() 比较是否相等。
class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head == null) return false;
        ListNode p = head;
        Stack<Integer> stack = new Stack();

        int length = 0;
        while(p != null){
            stack.push(p.val);
            p=p.next;
            length++;
        }
        length >>= 1;
        while(length>=0){
            if(head.val != stack.pop()){
                return false;
            }
            head=head.next;
            length--;
        }
        return true;
    }
}

(2)递归

  • 利用递归逆序打印链表的思想
  • 先递归,找到最后一个节点,再从尾部逆序+头部正序比较
private void printListNode(ListNode head) {
    if (head == null)
        return;
    printListNode(head.next);
    System.out.println(head.val);
}

逆序打印单链表
-----------------------------------------------------

class Solution {
    ListNode head;
    public boolean isPalindrome(ListNode head) {
        this.head=head;
        ListNode temp=head;
        return check(temp);
    }

    public boolean check(ListNode temp) {
        if(temp == null){// head从前往后 temp先递归到最后 然后从后往前
            return true;
        }
        boolean result = check(temp.next) && (temp.val == head.val);
        head=head.next;
        return result;
    }


}

(3)反转后半部分链表

  • 先用 快慢双指针 找到链表中点,注意处理链表为奇数个点情况
  • 反转后半部分链表
  • 一一比较

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值