美文网首页
判环算法以及链表常见算法题

判环算法以及链表常见算法题

作者: 千释炎 | 来源:发表于2017-08-16 14:48 被阅读0次

  由于涉及到Floyd判环算法,故先简单阐述一下Floyd判环算法原理。

Floyd判环算法

算法原理:设置两个指针同时从头结点向后访问(当两指针后继不为null),不过两指针的访问速度不一致,第一个指针每次后移一个元素,第二个指针每次后移两个元素。若链表成环,则两指针在某一时刻必然相遇,否则不相遇

计算环长度:
当指针相遇时,保持其中一个指针不动,另一个指针向后访问,每次后移一个单位,当两指针再次相遇时,该指针移动的单位即为环的长度

计算环起始节点:
第一次相遇后,慢指针从头结点向后访问,快指针从相遇点同时向后访问,不过两者的移动速度都为1,当两者再次相遇时,该相遇的节点即为环的起始节点,分析如下:

如下图所示,假设h为链表头结点,p为两指针第一次相遇节点,m为头结点到起始节点走过的路程(经过的节点个数),n为环起始节点到p的路程,则对于第一次相遇有:

慢指针走过的路程:s=m+n+aC

快指针走过的路程:2
s=m+n+bC

a,b分别表示两指针相遇前第一次经过p点后绕环的圈数,C表示环的长度,由于快指针的速度是慢指针的两倍,时间一样,故快指针走过的路程是慢指针的两倍。

两式相减有s=(b-a)
C=m+n+a*C

故m+n恰为环长度的整数倍

慢指针从头结点到起始节点走了m,由于同时同速度快指针也走了m,而移动前快指针距起始节点距离为n,m+n恰为环长度的整数倍,故快指针移动m后也到了起始节点,即此时相遇点为起始节点


1.寻找链表的第n个节点
/**
 * 时间复杂度为O(n),空间复杂度为O(1)
 * @param head
 * @param kth
 * @return
 */
public static SNode getKthNode(SNode head,int kth){
    SNode temNode=head,kthNode=null;
    for(int count=1;count<kth;count++) {
        if(temNode!=null)
            temNode=temNode.getNext();
    }
    while(temNode!=null) {
        if(kthNode==null)
            kthNode=head;
        else
            kthNode=kthNode.getNext();
        temNode=temNode.getNext();
    }
    if(kthNode!=null)
        return kthNode;
    return null;
}

2.1判断链表是否以null结尾,是否包含环

/**
 * 时间复杂度为O(n),空间复杂度为O(1)
 * @param head
 * @return
 */
public static boolean doesLinkedListContainsLoop(SNode head) {
    if(head==null)
        return false;
    SNode fastNode=head,slowNode=head;
    while(fastNode.getNext()!=null&&fastNode.getNext().getNext()!=null) {
        slowNode=slowNode.getNext();
        fastNode=fastNode.getNext().getNext();
        if(fastNode==slowNode)
            return true;
    }
    return false;
}

2.2判断给定的链表是否以null结束。如果链表中存在环,找到环的起始节点

/**
 * @param head
 * @return
 */
public static SNode findBeginOfLoop(SNode head) {
    SNode slowNode=head,fastNode=head;
    boolean loopExists=false;
    if(head==null)
        return null;
    while(fastNode.getNext()!=null&&fastNode.getNext().getNext()!=null) {
        slowNode=slowNode.getNext();
        fastNode=fastNode.getNext().getNext();
        if(slowNode==fastNode) {
            loopExists=true;
            break;
        }
    }
    if(loopExists) {  //环存在
        slowNode=head;
        while(slowNode!=fastNode) {
            slowNode=slowNode.getNext();
            fastNode=fastNode.getNext();
        }
        return slowNode;  //返回环开始节点
    }
    return null;  //环不存在
}

2.3判断链表是否存在环,若存在,则返回环的长度,否则返回0

/**
 * @param head
 * @return
 */
public static int findLoopLength(SNode head) {
    SNode slowNode=head,fastNode=head;
    boolean loopExists=false;
    int counter=0;
    if(head==null)
        return 0;
    while(fastNode.getNext()!=null&&fastNode.getNext().getNext()!=null) {
        slowNode=slowNode.getNext();
                    counter++;
        fastNode=fastNode.getNext().getNext();
        if(slowNode==fastNode) {
            loopExists=true;
            break;
        }
    }
    if(loopExists) {
        fastNode=fastNode.getNext();
        while(fastNode!=slowNode) {
            fastNode=fastNode.getNext();
            counter++;
        }
        return counter;
    }
    return 0;
}

3.将一个循环链表变成两个循环链表

/**
 * @param head
 * @param head1
 * @param head2
 */
public static void splitList(SNode head,SNode head1,SNode head2) {
    SNode slowNode=head,fastNode=head;
    if(head==null)
        return;
    //如果循环链表有奇数个节点,则fastNode.getNext()指向head
    //如果循环链表有偶数个节点,则fastNode.getNext().getNext()指向head
    while(fastNode.getNext()!=head&&fastNode.getNext().getNext()!=head) {
        fastNode=fastNode.getNext().getNext();
        slowNode=slowNode.getNext();
    }
    if(fastNode.getNext().getNext()==head) {
        fastNode=fastNode.getNext();
    }
    //head1指向前半部分的head指针
    head1=head;
    //head2指向后半部分的指针
    if(head.getNext()!=head)
        head2=slowNode.getNext();
    //把后半部分变成环
    fastNode.setNext(slowNode.getNext());
    //把前半部分变成环
    slowNode.setNext(head);
}

4.有序链表中插入一个节点

/**
 * 时间复杂度为O(n),空间复杂度为O(1)
 * @param head
 * @param newNode
 * @return
 */
public static SNode insertSortedList(SNode head,SNode newNode) {
    SNode current=head,temp=null;
    if(head==null)
        return newNode;
    while(current!=null) {
        temp=current;
        current=current.getNext();
    }
    newNode.setNext(current);
    temp.setNext(newNode);
    return head;
}

5.1链表逆置

/**
 * 时间复杂度为O(n),空间复杂度为O(1)
 * @param head
 * @return
 */
public static SNode reverseList(SNode head) {
    SNode temp=null,nextNode=null;
    while(head!=null) {
        nextNode=head.getNext();
        head.setNext(temp);
        temp=head;
        head=nextNode;
    }
    return temp;
}

5.2逐对逆置链表,假如初始链表为1 2 3 4 x,逐对逆置后为2 1 4 3 x

/**
 * @param head
 * @return
 */
public static SNode reversePairRecursive(SNode head) {
    SNode temp;
    if(head==null||head.getNext()==null)
        return head;
    else {
        temp=head.getNext();
        head.setNext(temp.getNext());
        temp.setNext(head);
        head=temp;
        head.getNext().setNext(reversePairRecursive(head.getNext().getNext()));
        return head;
    }
}

6.判断两个链表是否相交成为一个单链表,若相交,得出两链表的相交节点

/**
 * 思路:两链表相交之后的长度必然相等,先获得两链表的长度,让更长的链表先后移两链表长度差个单位,再一起向后遍历,直到出现两节点相等或到达表尾
 * 时间复杂度为O(m,n),空间复杂度为O(1)
 * @param list1
 * @param list2
 * @return
 */
public static SNode findIntersectingNode(SNode list1,SNode list2) {
    int l1=0,l2=0,diff=0;
    SNode head1=list1,head2=list2;
    while(head1!=null) {
        l1++;
        head1=head1.getNext();
    }
    while(head2!=null) {
        l2++;
        head2=head2.getNext();
    }
    if(l1<l2) {
        head1=list2;
        head2=list1;
        diff=l2-l1;
    }else {
        head1=list1;
        head2=list2;
        diff=l1-l2;
    }
    for(int i=0;i<diff;i++)
        head1=head1.getNext();
    while(head1!=null&&head2!=null) {
        if(head1==head2)
            return head1;
        head1=head1.getNext();
        head2=head2.getNext();
    }
    return null;
}

7.寻找链表的中间节点:设置两个指针,第一个指针的移动速度是第二个指针的两倍,当第一个指针到达表尾,第二个指针即指向中间节点

/**
 * 时间复杂度为O(n),空间复杂度为O(1)
 * @param head
 * @return
 */
public static SNode findMiddle(SNode head) {
    SNode point1=head,point2=head;
    int i=0;
    while(point1.getNext()!=null) {
        if(i==0) {
            point1=point1.getNext();
            i=1;
        }else if(i==1) {
            point1=point1.getNext();
            point2=point2.getNext();
            i=0;
        }
    }
    return point2;
}

8.检查链表长度是奇数还是偶数,每次后移两个元素,返回1表示奇数,返回0表示偶数

/**
 * 时间复杂度为O(n/2),空间复杂度为O(1)
 * @param head
 * @return
 */
public static int isLinkedListLengthEven(SNode head) {
    while(head!=null&&head.getNext()!=null) {
        head=head.getNext().getNext();
    }
    if(head==null)
        return 0;
    else
        return 1;
}

9.合并两个有序链表

/**
 * 时间复杂度为O(n)
 * @param a
 * @param b
 * @return
 */
public static SNode mergeList(SNode a,SNode b) {
    SNode result=null;
    if(a==null)
        return b;
    if(b==null)
        return a;
    if(a.getData()<=b.getData()) {
        result=a;
        result.setNext(mergeList(a.getNext(), b));
    }else {
        result=b;
        result.setNext(mergeList(a,b.getNext()));
    }
    return result;
}

10.从表尾开始输出链表

/**
 * @param head
 */
public static void printListFromEnd(SNode head) {
    if(head==null)
        return;
    printListFromEnd(head.getNext());
    System.out.println(head.getData());
}

相关文章

网友评论

      本文标题:判环算法以及链表常见算法题

      本文链接:https://www.haomeiwen.com/subject/cvgvrxtx.html