算法-链表

一、打印两个有序链表的公共部分,给定两个有序链表的头指针head1和head2,打印两个链表的公共部分。

package FaceQuestion.链表;

/**
 * 打印两个有序链表的公共部分
 * 链表1的头结点head1,链表2的头结点为head2
 * head指针每次移动一步前,都要判断两个链表元素的大小,哪个元素小,哪个指针就走一步,另一个指针不动,如果两个元素相等,打印,并且两个指针都走一步
 */
public class PrintCommonPart {

    /**
     * 链表节点类
     */
    public static class Node{
        public int value;
        public Node next;
        public Node(int data){
            this.value=data;
        }
    }
    
    
    public static void printCommmonPart(Node head1,Node head2){
        System.out.print("公共部分为:");
        while (head1!=null&&head2!=null){//判断两个链表都不能为空
            if (head1.value<head2.value){//如果head1的值小于head2的值
                head1=head1.next;//head1走一步
            }else if (head1.value>head2.value){//同理
                head2=head2.next;
            }else {//两者相等
                System.out.print(head1.value+" ");
                //head12都走一步
                head1=head1.next;
                head2=head2.next;
            }
        }
        System.out.println();
    }
}
二、判断一个链表是否为回文结构
给定一个链表的头节点head,请判断该链表是否为回文结构。
例如:
1->2->1,返回true。
1->2->2->1,返回true。
15->6->15,返回true。
1->2->3,返回false。
进阶:
如果链表长度为N,时间复杂度达到O(N),额外空间复杂度达到O(1)。

三种方法:

1.使用一个栈,将链表元素压入栈,然后不断弹出元素与链表元素比较,需要额外O(N)的空间

2.使用快慢指针,找到链表中点,将链表中点后的部分压入栈中,需要额外O(N/2)的空间

3.使用快慢指针找到链表中点,然后将右边部分反转,再和左边部分比较

package FaceQuestion.链表;

import java.util.Stack;

/**
 * 判断链表是否回文
 * 1.使用一个栈,将链表元素压入栈,然后不断弹出元素与链表元素比较,需要额外O(N)的空间
 * 2.使用快慢指针,找到链表中点,将链表中点后的部分压入栈中,需要额外O(N/2)的空间
 * 3.使用快慢指针找到链表中点,然后将右边部分反转,再和左边部分比较
 */
public class IsPalindromeList {

    public static class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }

    /**
     * 第一种方法,使用一个栈
     * @param head
     * @return
     */
    public boolean isPalindrome1(Node head){
        Stack<Node> stack =new Stack<>();
        Node cur=head;
        while (cur!=null){
            stack.push(cur);
            cur=cur.next;
        }
        while (head!=null){
            if (head.value!=stack.pop().value){
                return false;
            }
            head=head.next;
        }
        return true;
    }

    /**
     * 第二种方法,使用快慢指针,找到链表中点,将链表中点后的部分压入栈中
     * @param head
     * @return
     */
    public boolean isPalindrome2(Node head){
        if (head==null||head.next==null){
            return true;
        }
        Node right=head.next;
        Node cur=head;
        while (cur.next!=null&&cur.next.next!=null){
            right=right.next;
            cur=cur.next.next;
        }
        Stack<Node> stack=new Stack<>();
        while (right!=null){
            stack.push(right);
            right=right.next;
        }
        while (!stack.isEmpty()){
            if (head.value!=stack.pop().value){
                return false;
            }
            head=head.next;
        }
        return true;
    }

    /**
     * 第三种方法:使用快慢指针找到链表中点,然后将右边部分反转,再和左边部分比较
     * @param head
     * @return
     */
    public boolean isPalindrome3(Node head){
        if (head ==null||head.next==null){
            return true;
        }
        Node n1=head;
        Node n2=head;
        while (n2.next!=null&&n2.next.next!=null){
            n1=n1.next;
            n2=n2.next.next;
        }
        n2=n1.next;
        n1.next=null;
        Node n3=null;
        while (n2!=null){//中点右边反转
            n3=n2.next;//保存下一个节点
            n3.next=n1;//反转节点
            n1=n2;//n1移动
            n2=n3;//n2移动
        }
        n3=n1;
        n2=head;
        boolean res=true;
        while (n1!=null&&n2!=null){
            if (n1.value!=n2.value){
                res=false;
                break;
            }
            n1=n1.next;
            n2=n2.next;
        }
        //将反转的链表恢复
        n1=n3.next;
        n3.next=null;
        while (n1!=null){
            n2=n1.next;
            n1.next=n3;
            n3=n1;
            n1=n2;
        }
        return res;
    }

}

三、将单向链表按某值划分成左边小、中间相等、右边大的形式

package FaceQuestion.链表;

/**
 * 将单向链表按某值划分成左边小、中间相等、右边大的形式
 * 1.参照快速排序的方法去进行区域的划分
 * 2.提前设置三个区域smallerequalbigger,遍历链表,将对应元素添加到对应区域中
 */
public class SmallerEqualBigger {


    public static class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }

    /**
     * 第一种方法:参照快速排序的方法去进行区域的划分,将链表节点存储到数组中,按照节点的Value进行划分
     * @param head
     * @param pivot
     * @return
     */
    public static Node listPartition1(Node head, int pivot) {
        if (head == null) {
            return head;
        }
        //获取链表总长度
        Node cur = head;
        int i = 0;
        while (cur != null) {
            i++;
            cur = cur.next;
        }
        //创建一个存储链表节点的数组
        Node[] nodeArr = new Node[i];
        i = 0;
        cur = head;
        //将链表元素存储到数组中
        for (i = 0; i != nodeArr.length; i++) {
            nodeArr[i] = cur;
            cur = cur.next;
        }
        //划分区域
        arrPartition(nodeArr, pivot);
        for (i = 1; i != nodeArr.length; i++) {
            nodeArr[i - 1].next = nodeArr[i];
        }
        nodeArr[i - 1].next = null;
        return nodeArr[0];
    }

    /**
     * 划分区域的方法,参照快排的思想
     * @param nodeArr
     * @param pivot
     */
    public static void arrPartition(Node[] nodeArr, int pivot) {
        int small = -1;
        int big = nodeArr.length;
        int index = 0;
        while (index != big) {
            if (nodeArr[index].value < pivot) {
                swap(nodeArr, ++small, index++);
            } else if (nodeArr[index].value == pivot) {
                index++;
            } else {
                swap(nodeArr, --big, index);
            }
        }
    }

    public static void swap(Node[] nodeArr, int a, int b) {
        Node tmp = nodeArr[a];
        nodeArr[a] = nodeArr[b];
        nodeArr[b] = tmp;
    }

    public Node listPartition2(Node head,int pivot){
        Node sH=null;//小于区域的头结点
        Node sT=null;//小于区域的尾节点
        Node eH=null;//等与区域的头结点
        Node eT=null;//等于区域的尾节点
        Node bH=null;//大于区域的头节点
        Node bT=null;//大于区域的尾节点

        Node next=null;//用于保存下一个节点
        while (head!=null){
            next=head.next;
            head.next=null;
            if (head.value<pivot){//当前元素小于划分值
                if (sH==null){
                    sH=head;
                    sT=head;
                }else {
                    sT.next=head;
                    sT=head;
                }
            }else if (head.value==pivot){//当前元素等于划分值
                if (eH==null){
                    eH=head;
                    eT=head;
                }else {
                    eT.next=head;
                    eT=head;
                }
            }else {//当前元素大于划分值
                if (bH==null){
                    bH=head;
                    bT=head;
                }else {
                    bT.next=head;
                    bT=head;
                }
            }
            head=next;
        }
        //连接三个区域
        if (sT!=null){
            sT.next=eH;
            eT=eT==null?sT:eT;
        }
        if (eT!=null){
            eT.next=bH;
        }
        return sH!=null?sH:eH!=null?eH:bH;
    }
}

四、链表相交的一系列问题(重点)

在本题中,单链表可能有环,也可能无环。给定两个单链表的头节点head1和head2,这两个链表可能相交,也可能不相交。请实现一个函数,如果两个链表相交,请返回相交的第一个节点;如果不相交,返回null即可。

要求:如果链表1的长度为N,链表2的长度为M,时间复杂度请达到O(N+M),额外空间复杂度请达到O(1)。

首先,需要判断一个链表是否有环,有环的话返回环的第一个节点;这个问题的思路网上一抓一大把,大体思路就是,使用快慢指针,快指针一次走两步,慢指针一次走一步,两个指针一定会在环上的某一个节点相遇(关于这一点的理解,可以参照两个人跑步,跑的快的人和跑的慢的人一定会相遇),当相遇之后,将快指针指向链表头,快慢指针此时同时都直走一步,再次相遇时就是环的第一个节点;

/**
 * 判断链表是否有环,如果链表有环,那么返回环的第一个节点,如果没有换,直接返回空
 * @param head
 * @return
 */
public Node getLoopNode(Node head){
    if (head==null||head.next==null||head.next.next==null){
        return null;
    }
    Node n1=head.next;//慢指针
    Node n2=head.next.next;//快指针
    while (n1!=n2){//当快慢指针相遇是结束循环
        if (n2.next==null||n2.next.next==null){
            return null;
        }
        n2=n2.next.next;//快指针一次走两步
        n1=n1.next;//慢指针一次走一步
    }
    //快慢指针相遇,将快指针指向链表头
    n2=head;
    while (n1!=n2){
        //快慢指针一次都直走一步,再次相遇时就是环的第一个节点
        n1=n1.next;
        n2=n2.next;
    }
    return n1;
}

有了getLoopNode方法,我们可以判断一个链表是否有环了,下一步我们需要考虑的就是两个链表是否相交的问题,这时,我们需要分三种情况进行考虑:

1.两个链表都无环

2.两个链表都有环

3.一个有环一个无环

首先我们排除第三种情况,一个有环一个无环是不可能相交的;这个可以直接排除;

所以我们现在对1,2两种情况进行分析;

当两个链表都无环时,我们可以计算两个链表的长度差,然后让长链表的头指针想走完这个长度差,这时两个链表的头结点的对应位置就相等了,然后就可以让两个头结点一起走,当两个指针指向的节点相等时(这里的相等指的是地址相同,也就是同一个节点,并不仅仅是值相等),这个节点就是相交的第一个节点

/**
 * 两个链表都无环时,判断是否相交
 * @param head1
 * @param head2
 * @return
 */
public static Node noLoop(Node head1, Node head2) {
    if (head1 == null || head2 == null) {
        return null;
    }
    Node cur1 = head1;
    Node cur2 = head2;
    int n = 0;//两条链表的长度差
    while (cur1.next != null) {
        n++;
        cur1 = cur1.next;
    }
    while (cur2.next != null) {
        n--;
        cur2 = cur2.next;
    }
    
    if (cur1 != cur2) {
        return null;
    }
    cur1 = n > 0 ? head1 : head2;
    cur2 = cur1 == head1 ? head2 : head1;
    n = Math.abs(n);
    while (n != 0) {
        n--;
        cur1 = cur1.next;
    }
    while (cur1 != cur2) {
        cur1 = cur1.next;
        cur2 = cur2.next;
    }
    return cur1;
}

当两个链表都有环时,还要分两种情况:

1.在环上相交

2.不在环上相交

如果是第二种情况,不在环上相交,那么就相当于两条无环链表相交问题,只不过尾节点改为环的起始点;

如果时第一种情况,在环上相交,返回环的起始点就可以了

/**
 * 两个链表都无环时,判断相交问题
 * @param head1
 * @param loop1
 * @param head2
 * @param loop2
 * @return
 */
public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
    Node cur1 = null;
    Node cur2 = null;
    if (loop1 == loop2) {//如果不在环上相交
        cur1 = head1;
        cur2 = head2;
        int n = 0;
        while (cur1 != loop1) {
            n++;
            cur1 = cur1.next;
        }
        while (cur2 != loop2) {
            n--;
            cur2 = cur2.next;
        }
        cur1 = n > 0 ? head1 : head2;
        cur2 = cur1 == head1 ? head2 : head1;
        n = Math.abs(n);
        while (n != 0) {
            n--;
            cur1 = cur1.next;
        }
        while (cur1 != cur2) {
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;
    } else {//在环上相交
        cur1 = loop1.next;
        while (cur1 != loop1) {
            if (cur1 == loop2) {
                return loop1;
            }
            cur1 = cur1.next;
        }
        return null;
    }
}

整体代码如下:

package FaceQuestion.链表;

/**
 * 两个链表相交的一系列问题
 */
public class FindFirstIntersectNode {

    public static class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }


    /**
     * 判断链表是否有环,如果链表有环,那么返回环的第一个节点,如果没有换,直接返回空
     * @param head
     * @return
     */
    public Node getLoopNode(Node head){
        if (head==null||head.next==null||head.next.next==null){
            return null;
        }
        Node n1=head.next;//慢指针
        Node n2=head.next.next;//快指针
        while (n1!=n2){//当快慢指针相遇是结束循环
            if (n2.next==null||n2.next.next==null){
                return null;
            }
            n2=n2.next.next;//快指针一次走两步
            n1=n1.next;//慢指针一次走一步
        }
        //快慢指针相遇,将快指针指向链表头
        n2=head;
        while (n1!=n2){
            //快慢指针一次都直走一步,再次相遇时就是环的第一个节点
            n1=n1.next;
            n2=n2.next;
        }
        return n1;
    }

    /**
     * 两个链表都无环时,判断是否相交
     * @param head1
     * @param head2
     * @return
     */
    public static Node noLoop(Node head1, Node head2) {
        if (head1 == null || head2 == null) {
            return null;
        }
        Node cur1 = head1;
        Node cur2 = head2;
        int n = 0;//两条链表的长度差
        while (cur1.next != null) {
            n++;
            cur1 = cur1.next;
        }
        while (cur2.next != null) {
            n--;
            cur2 = cur2.next;
        }

        if (cur1 != cur2) {
            return null;
        }
        cur1 = n > 0 ? head1 : head2;
        cur2 = cur1 == head1 ? head2 : head1;
        n = Math.abs(n);
        while (n != 0) {
            n--;
            cur1 = cur1.next;
        }
        while (cur1 != cur2) {
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;
    }

    /**
     * 两个链表都无环时,判断相交问题
     * @param head1
     * @param loop1
     * @param head2
     * @param loop2
     * @return
     */
    public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
        Node cur1 = null;
        Node cur2 = null;
        if (loop1 == loop2) {//如果不在环上相交
            cur1 = head1;
            cur2 = head2;
            int n = 0;
            while (cur1 != loop1) {
                n++;
                cur1 = cur1.next;
            }
            while (cur2 != loop2) {
                n--;
                cur2 = cur2.next;
            }
            cur1 = n > 0 ? head1 : head2;
            cur2 = cur1 == head1 ? head2 : head1;
            n = Math.abs(n);
            while (n != 0) {
                n--;
                cur1 = cur1.next;
            }
            while (cur1 != cur2) {
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
            return cur1;
        } else {//在环上相交
            cur1 = loop1.next;
            while (cur1 != loop1) {
                if (cur1 == loop2) {
                    return loop1;
                }
                cur1 = cur1.next;
            }
            return null;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/pgg_cold/article/details/80810604
今日推荐