【数据结构】链表相关OJ题【下】

目录

1.合并两个有序链表

2.删除链表中的所有重复元素,不保留

3.删除链表中的重复元素,保留一个

4.分隔链表

5.环形链表

6.返回环形链表的入口节点

7.相交链表

8.移除链表元素

9.反转链表

10.回文链表

11.链表的中间节点


1.合并两个有序链表

        要求:将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

(1)基础做法

        主要思想:边界条件:一个链表为空,则拼接后一定是那个非空链表;若两个都为空,则返回null;若两个链表都不为空:创建新的虚拟节点,遍历两个链表,将较小的那个链表拼接到这个虚拟节点后 ;若当两个链表中一旦有一个链表遍历完成为null后,则将另一个链表直接拼接到虚拟节点后即可。

         /*//情况1:两个链表都为空:可以省略了,下面的情况2都包含了
        if(list1==null && list2==null){
            return null;
        }
        //情况2:有一个链表为null,则另一个链表就是合并后的链表
        if(list1 == null ){
           return list2;
        }
        if(list2 == null ){
            return list1;
        }
        //情况3:两个链表都不为null
        //(1)创建虚拟头结点
        ListNode dummyHead = new ListNode();
        ListNode tail = dummyHead;
        //(2)遍历两个链表,双指针
        while (list1.next != null && list2.next !=null){
            if(list1.val > list2.val){
                tail.next = list2;//--------注意拼接的是整个链表,并不是链表中的单独节点
                tail = list2;
                list2 = list2.next;
            } else {
                tail.next = list1;
                tail = list1;
                list1 = list1.next;
            }
        }
        //(3)此时说明已经将其中一个链表遍历完,则直接将另一个链表的值全部加到合并链表中
        if(list1 == null){
            tail.next = list2;
        }
        if(list2 == null){
            tail.next = list1;
        }
        //(4)返回最终的头结点
        return dummyHead.next;
    }*/

(2)递归法

 public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        //边界条件
        //情况2:有一个链表为null,则另一个链表就是合并后的链表
        if(list1==null ){
            return list2;
        }
        if(list2==null ){
            return list1;
        }
        //递归的使用
        if(list1.val <= list2.val){
            list1.next = mergeTwoLists(list1.next,list2);//此时的list1中的第一个值是最小值,采用递归方法将List1的第一个节点后的所有节点与list2的所有节点合并
            return list1;//list1保存的就是最小值头结点
        } else {
            list2.next = mergeTwoLists(list1.next,list2);//list2中的第一个值是最小值
            return list2;
        }
}

2.删除链表中的所有重复元素,不保留

    public ListNode deleteDuplicates(ListNode head) {
        // 1.base case
        if(head == null || head.next == null) {
            return head;
        }
        if(head.val != head.next.val) {
            head.next = deleteDuplicates(head.next);
            return head;
        }else {
            // 头节点就是重复的节点,先处理完头节点的情况
            ListNode newHead = head.next;
            while(newHead != null && newHead.val == head.val) {
                newHead = newHead.next;
            }
            // 此时newHead一定不是待删除的结点,最终整个传入函数,返回更新后的值即可
            return deleteDuplicates(newHead);
        }
    }

3.删除链表中的重复元素,保留一个

(1)基本做法

        主要思路:双指针法:创建一个虚拟头结点指向链表的头部head,第一个指针从dummyHead开始记作prev,第二个指针从虚拟头结点的下一个位置开始记作cur。 情况1:如果prev和cur的值不相等,说明不是重复的元素,就往后各移动一位; 情况2:如果prev和cur的值相等,说明两者保存的是重复元素,就将prev记录的值进行保留,跳过cur保存的重复值,更改prev的指向为:直接指向cur的下一个节点。同时再更新cur的指向:往后移动一位 。终止条件:cur比prev快一步,所以当cur指向的值为null时,说明遍历结束。

public ListNode deleteDuplicates(ListNode head) {
    //(1)边界条件:只存在一个头结点或者不存在节点时,肯定不存在重复元素,直接返回头结点即可
    if(head==null || head.next == null){
        return head;
    }
    //(2)走到这里最少有两个节点存在
    //创建一个虚拟头结点
    ListNode dummyHead = new ListNode(-101);
    //虚拟头结点添加到当前链表的最开始的头结点
    dummyHead.next = head;
    //创建一个头结点prev代替虚拟头结点从头开始
    ListNode prev = dummyHead;//prev作为第一个指针
    ListNode cur = prev.next;//cur作为第二个指针
    //判断指针的值
    while (cur!=null){
        if(prev.val == cur.val){
            prev = cur.next;
        }else {
            prev = prev.next;
        }
        cur = cur.next;
    }
    //返回链表的头结点
    return dummyHead.next;
}

(2)递归法:先把以head为头结点的子链表的重复元素删除,保留一次(调用递归函数), 再判断删除后的子链表的头结点与头结点head是否是重复元素,是的话删除head,返回子链表的头部,不是则返回head。

    public ListNode deleteDuplicates(ListNode head) {
        //(1)边界条件
        if(head==null || head.next == null){
            return head;
        }
        //先把以head为头结点的子链表的重复元素删除,保留一次
        head.next = deleteDuplicates(head.next);
        //现在剩下头结点
        return head.val == head.next.val ? head.next : head;
    }

4.分隔链表

        要求:给你一个链表的头节点head和一个特定值 x ,请你对链表进行分隔,使得所有小于x的节点都出现在大于或等于x的节点之前。 你应当保留两个分区中每个节点的初始相对位置。

       主要思路:遍历原链表,将所有小于x节点的放在一个子链表l1,大于x的节点放在子链表l2,最后将l2与l1进行拼接。注意:l2的尾部一定要置为null。

public ListNode partition(ListNode head, int x) {        
        //边界:如果头结点不存在或者只存在一个节点
        if(head ==null || head.next == null){
            return head;
        }
        //产生两个虚拟头结点,并给出节点尾部
        ListNode smallHead = new ListNode();
        ListNode bigHead = new ListNode();
        ListNode smallTial = smallHead;
        ListNode bigTail = bigHead;
        //遍历原链表,将所有小于x的节点尾插到小链表的尾部;相反的尾插到大链表的尾部
        //当head为空时结束遍历
        while (head!=null){
            if(head.val < x){
                smallTial.next = head;
                smallTial = head;
            }else{
                bigTail.next = head;
                bigTail = head;
            }
            head = head.next;
        }
        //结束遍历,将大链表的尾部断开,拼接大小链表
        bigTail.next = null;
        smallTial.next = bigHead.next;
        return smallHead.next;
    }

5.环形链表

        要求:给定一个链表判断该链表中是否有环。

        主要思想:快慢指针:如果存在环,则快指针一定在某一个时刻追上慢指针。如果快指针为null时还没追上,则一定没有环。

 public boolean hasCycle(ListNode head) {
        //定义两个快慢指针
        ListNode low = head;
        ListNode fast = head;
        //当快指针为null时还没相遇,说明没有环
        while (fast !=null && fast.next != null){
            //让快指针每次走两步,慢指针每次走一步
            low = low.next;
            fast = fast.next.next;
            //如果快指针与慢指针相遇了,说明有环
            if(low == fast){
                return true;
            }
        }
        return false;
    }

6.返回环形链表的入口节点

        要求:给定一个具有环的链表,要求返回该环形链表的入口节点。

        主要思想:借助一个结论实现:当快慢指针相遇的时候,引入一个新的节点从链表的头结点开始,最终这个新的节点一定会与慢指针在入口位置相遇。

public ListNode detectCycle(ListNode head) {
        //定义两个快慢指针
        ListNode low = head;
        ListNode fast = head;
        //在快指针没走完的时候
        while (fast!=null && fast.next != null){
            fast = fast.next.next;
            low = low.next;
            if(low == fast){//快慢指针相遇
                ListNode newNode = head;//引入一个新节点
                while (newNode != low){//当慢指针与新节点不相等的时候,让两者一直走
                    low = low.next;
                    newNode = newNode.next;
                }
                //说明慢指针与新节点相遇,此时相遇的位置就是入口
                return newNode;
            }
        }
        return null;
    }

7.相交链表

        要求:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回null。

       思想:使用两个引用listA和listB,同时开始遍历。 如果listA走完了,就走listB,listB也是一样,先走自身,走完了再走listA。如果两者有相交位置,一定在在相交位置相遇;否则当两者都走完还没相遇,就是没有相交节点。

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode l1 = headA;
        ListNode l2 = headB;       
        while (l1 != l2){
            l1 = (l1 == null? headB : l1.next);
            l2 = (l2 == null? headA : l2.next);
        }
        return l1;
    }

8.移除链表元素

        要求:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回新的头节点。

(1)虚拟头结点法:遍历链表,走到被删除节点的前驱,与要删除的值比较,更改指向。

public ListNode removeElements(ListNode head, int val) {    
    //判断边界条件:链表中不存在节点,则直接返回Null
        if(head == null){
            return null;
        }
        //产生一个虚拟头结点
        ListNode dummyNode = new ListNode();
        //将虚拟头结点与原链表建立联系
        dummyNode.next = head;
        //让prev作为临时节点代替head
        ListNode prev = dummyNode;
        while (prev.next != null){
            if(prev.next.val == val){
                prev.next = prev.next.next;
            }else{
                prev = prev.next;//防止出现连续的被删除的节点,只有当prev.next不是被删除的节点时才移动
            }
        }
        return dummyNode.next;
    }

(2)递归法

    public ListNode removeElements(ListNode head, int val) {
        //边界条件
        if(head == null){
            return null;
        }
        //正常情况
        //先把以head.next为头结点的子链表中的所有为val中的值删除完毕!
        head.next = removeElements(head.next,val);
        //最后判断头结点的情况:是否需要删除
        return head.val == val ? head.next:head;
    }

9.反转链表

        要求:输入链表:[1,2,3,4,5]   输出:[5,4,3,2,1]

(1)头插法

        主要思路:先产生一个虚拟头接节点,从head开始不断遍历原链表,每遍历到一个节点,就产生一个与该值相等的新节点, 将该新节点不断头插到虚拟头结点后,最终dummyHead.next就是反转后链表的头结点。

    public ListNode reverseList(ListNode head) {        
        //边界条件:
        if (head == null || head.next == null) {
            return head;
        }
        //产生虚拟头节点
        ListNode dummyHead = new ListNode();
        //遍历原链表,不断产生新节点,将该新节点不断头插到虚拟头结点之后
        while (head != null) {
            ListNode newNode = new ListNode(head.val);
            newNode.next = dummyHead.next;//将新节点与原先的节点相连接
            dummyHead.next = newNode;//将新节点头插到虚拟头结点后
            head = head.next;
        }
        return dummyHead.next;
    }

(2)在原链表移动

    public ListNode reverseList(ListNode head) {
        //边界条件
        if (head == null || head.next == null) {
            return head;
        }
        //让第一个指针指向空,第二个指针指向链表的头,当第二个指针为空的时候结束遍历
        ListNode prev = null;
        ListNode cur = head;
        while (cur!=null){
            //保存第二个指针的下一个指向(因为要对第二个指针进行操作,防止后面的值丢失。)
            ListNode next = cur.next;
            cur.next = prev;//让第二个指针指向第一个指针
            //移动两个指针的位置
            prev = cur;
            cur = next;
        }
        //最终第一个指针指向的值就是头结点
        return prev;
    }

(3)递归法 

     public ListNode reverseList(ListNode head) {
        //边界条件
        if (head == null || head.next == null) {
            return head;
        }
        //处理头节点之后的子链表:进行反转
        ListNode next = head.next;
        ListNode newHead = reverseList(head.next);
        //拼接当前头结点和转后的子链表
        head.next = null;//先断开原先链表头结点之后的节点
        next.next = head;//连接反转后链表的尾部与原先链表的头部
        return newHead;
    }

10.回文链表

        要求:回文链表:1221是,12321是,123312不是回文链表。

        主要思路:比如原链表为12321,将原链表看作两个子链表,以中间节点为其分开:L1: 12, L2: 321——>将L2进行转置结果为123,遍历L1和L2判断两者的值是否相等。在两个子链表为null之前,如果找到任意一个不相等的就返回false。

    public boolean isPalindrome(ListNode head) {     
        //边界条件:链表为null或者只有一个节点都是true
        if(head == null || head.next == null){
            return true;
        }
        //找出链表的中间节点记作middleNode
        ListNode middleNode = middleNode(head);
        //将从middleNode为头结点的链表进行反转后的头结点记作l2
        ListNode l2 = reverseList(middleNode);
        //将以head为头结点的子链表与l2子链表进行遍历判断
        while (head != null & l2!= null){
            //找反例:判断两个子链表的头结点是否相同
            if(head.val != l2.val){
                return false;
            }
            //移动两个子链表头结点的位置
            head = head.next;
            l2 = l2.next;
        }
        return true;
    }
    // 函数目标:找中间节点
    public ListNode middleNode(ListNode head) {
        // 快慢指针法
        ListNode low = head,fast = head;
        while (fast != null && fast.next != null) {
            low = low.next;
            fast = fast.next.next;
        }
        return low;
    }
    // 函数目标:反转链表
    public ListNode reverseList(ListNode head) {
        // 1.base case
        if(head == null || head.next == null) {
            return head;
        }
        ListNode next = head.next;
        ListNode newHead = reverseList(head.next);
        // 拼接当前头结点和转后的子链表
        head.next = null;
        next.next = head;
        return newHead;
    }

11.链表的中间节点

        要求:给你单链表的头结点 head ,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

        思路:快慢指针法 结论:让第一个和第二个指针都指向头结点,让第一个指针每次走一步,第二指针每次都两步, 当第二个指针走向null的时候,第一个指针所指的位置就是中间节点的位置。

     public ListNode middleNode(ListNode head) {        
        ListNode first = head;
        ListNode second = head;
        while (second!=null && second.next!=null){
            first = first.next;
            second = second.next.next;
        }
        //说明此时第二个指针指向的是Null
        return first;
    }

【数据结构】链表相关OJ题【下】_第1张图片

你可能感兴趣的:(数据结构,链表,数据结构,java)