leetcode刷题篇 第二篇

38 阅读4分钟

文章仅作记录 此次刷题记录是作为时长40天的个人项目 欢迎读者,有问题可进行评论,看到有价值的问题会进行回复


接上篇,内容为上次未更完的内容

主要内容:链表

移除链表元素

java链表定义:value,next

三个构造函数

处理链表,需要注意到的点就是关于头节点的处理方式

可以设置一个虚拟节点,去统一所有的处理方式

ListNode vir = new ListNode(-1,head)


class Solution {

    public ListNode removeElements(ListNode head, int val) {

        //设置虚拟节点并添加前置节点

        if(head == null){

            return head;

        }

        ListNode vir = new ListNode(0,head);

        ListNode pre = vir;

        ListNode cur = head;

        while(cur != null){

            if(cur.val==val){

                pre.next = cur.next;

            }

            else{

                pre = cur; // 恰好可作为目标处理节点的前一节点

            }

            cur = cur.next;

        }

        return vir.next;

    }

}

设计链表

链表基础的增删操作

需要注意的点就是在操作的同时不要忘记处理链表的长度变化


class MyLinkedList {

    class ListNode {

        int val;

        ListNode next;

        ListNode(){}

        ListNode(int val){

            this.val = val;

        }

    }

    int size;

    ListNode vir;

    //初始化链表

    public MyLinkedList() {

        size = 0;

        vir = new ListNode(0);

    }

  


    public int get(int index) {

        // 判断是否合法

        if(index < 0||index >= size){

            return -1;

        }

        ListNode cur = vir;

        for (int i = 0; i <= index; i++) {

            cur = cur.next;

        }

        return cur.val;

    }

  


    public void addAtHead(int val) {

        addAtIndex(0, val);

    }

  


    public void addAtTail(int val) {

        addAtIndex(size, val);

    }

  


    public void addAtIndex(int index, int val) {

        if(index < 0){

            index = 0;

        }

        if(index > size){

            return;

        }

        size++;

        ListNode cur = vir;

        int count = 0;

        while(count != index){

            cur = cur.next;

            count++;

        }

        ListNode ins = new ListNode(val);

        ins.next = cur.next;

        cur.next = ins;

    }

  


    public void deleteAtIndex(int index) {

        if (index < 0 || index >= size) {

            return;

        }

        size--;

        if (index == 0) {

            vir = vir.next;

            return;

        }

        ListNode pred = vir;

        for (int i = 0; i < index ; i++) {

            pred = pred.next;

        }

        pred.next = pred.next.next;

    }

}

反转链表

有节省空间的需求所以不去创建一个新链表,这里直接反转next即可,反转只需要改变两个指针即可

使用双指针法去解决,设置一个前节点


class Solution {

    public ListNode reverseList(ListNode head) {

        // 双指针法

        ListNode pre = null;

        ListNode cur = head;

        ListNode temp;

        while (cur != null) {

            temp = cur.next;

            cur.next = pre;

            pre = cur;

            cur = temp;

        }

        return pre;

    }

}

第二种方法就是使用虚拟头节点的方式,使用头插法

public static ListNode reverseList1(ListNode head) {

    ListNode vir = new ListNode(-1);

   vir.next = null;

   ListNode cur = head;

   while(cur != null){

   ListNode temp = cur.next;

   cur.next = vir.next;

   vir.next = cur;

   cur = temp;

  }

 return vir.next;

}

交换链表节点

一定需要注意的点,不要忘记节点的实时变化,在思路不足够清晰而且没有图示的情况下很容易紊乱

同时注意这里是一次性移动两个位置,所以这里判断为了防止漏单个未遍历也是判断两个


class Solution {

    public ListNode swapPairs(ListNode head) {

        // 要实现两两交换的前置的是什么? 涉及到三个节点 即获取到这三个节点

        // 如何获取?

        // 假设 Vir -  1 - 2 - 3 - 4

        // 首先 vir的next切换为 2 , 之后 2 next 切换为 1  1 next切换为 3

        ListNode vir = new ListNode(-1,head);

        ListNode cur = vir;

        ListNode temp,temp2;

        while(cur.next != null && cur.next.next != null){

            temp = cur.next;

            temp2 = cur.next.next.next;

  


            cur.next = cur.next.next;

            cur.next.next = temp;

            temp.next = temp2;

  


            cur = cur.next.next;

        }

        return vir.next;

    }

}

删除链表倒数第N个节点


class Solution {

    public ListNode removeNthFromEnd(ListNode head, int n) {

        ListNode vir = new ListNode(0,head);

        int size = 0;

        ListNode cur = vir;

        ListNode pre = null;

        ListNode temp = cur;

        while(temp != null){

            size++;

            temp = temp.next;

        }

        for (int i = 0; i < (size - n); i++) {

            pre = cur;

            cur = cur.next;

        }

        pre.next = cur.next;

        return vir.next;

    }

}

注意在size=n的时候,循环不会被执行所以需要添加一个判断

但是有个问题,这里使用了两次整体循环,一次是为了获取对应size值,这无疑添加了执行时间,那么如何简化,只用一次扫描实现呢?

此时就双指针就派上用场了

特征:处理前后存在差值 也可以理解为时间差

这里使用虚拟头节点,复习一下,作用是不对头指针处进行差异化处理


class Solution {

    public ListNode removeNthFromEnd(ListNode head, int n) {

        ListNode dummyNode = new ListNode(0,head);

        ListNode slow = dummyNode;

        ListNode fast = dummyNode;

        while(n != 0){

            fast = fast.next;

            n--;

        }

        while(fast.next != null){

            fast = fast.next;

            slow = slow.next;

        }

        slow.next = slow.next.next;

        return head.next;

    }

}

链表相交

本题同样可以使用双指针,但是使用的方式和之前有不同,因为条件不同

可以理解为:将AB链拼接起来然后判断是否存在一致点

注意:这里是找指针一样,而非值一样


public class Solution {

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {

        ListNode pa = headA;

        ListNode pb = headB;

        while(pa != pb){

            if(pa == null){

                pa = headB;

            }

            else {

                pa = pa.next;

            }

            if(pb == null){

                pb = headA;

            }

            else {

                pb = pb.next;

            }

        }

        return pa;

    }

}

环形链表

这个问题有两问:

  1. 判断链表是否存在环

  2. 找到环的入口返回其值

对于第一问,我们可以使用快慢指针的方式去做 why?

特征:判断是否存在回路,存在回路快慢指针必然相遇 why?

在回路中的过程其实是:快指针去追慢指针,(2-1)每步的速度逐渐逼近慢指针的(step one)

对于第二问:

方法一:可以使用一个集合将slow走过的点都存入然后判断是否存在,不过时空复杂度很高

方法二:双指针,快慢指针相遇时,各自走过的路程差等于环的长度,随后使用间距等于环长的双指针,相遇的位置即为环的起点

方法一会超时,这里给出方法二的写法:


public class Solution {

    public ListNode detectCycle(ListNode head) {

        ListNode fast = head;

        ListNode slow = head;

        while(fast != null && fast.next != null){

            fast = fast.next.next;

            slow = slow.next;

            if(fast == slow){

                // 说明存在环

                ListNode index = slow;

                ListNode index2 = head;

                while(index != index2){

                    index = index.next;

                    index2 = index2.next;

                }

                return index;

            }

        }

        return null;

    }

}

总结

 虚拟头结点 在head处理需要进行特殊化的时 一般时进行index插入,删除,反转等

 双指针法 用于清楚指针间差值的情况 ;减少多循环 ; 两头进行操作 同时加快遍历速度

 快慢指针 判断环 ;快速寻找某些值