链表相关的一些题目

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

【题目】

给定一个单向链表的头节点head,节点的值类型是整型,再给定一个整pivot。实现一个调整链表的函数,将链表调整为左部分都是值小于pivot的节点,中间部分都是值等于pivot的节点,右部分都是值大于pivot的节点。除这个要求外,对调整后的节点顺序没有更多的要求。例如:链表9->0->4->5->1,pivot=3。调整后链表可以是1->0->4->9->5,也可以是0->1->9->5->4。总之,满足左部分都是小于3的节点,中间部分都是等于3的节点(本例中这个部分为空),右部分都是大于3的节点即可。对某部分内部的节点顺序不做要求。

【思路】

遍历链表,将链表按pivot值的大小划分成三个链表,分别为small表、equal表、big表,存储小于pivot的元素,等于pivot的元素,大于pivot的元素。划分完后将三个链表连接起来,返回头指针即可。

【Code】 

public static class Node
    {
        public int value;
        public Node next;
        public Node(int value)
        {
            this.value = value;
        }
    }
    public static Node SmallEqualBig(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;
        //将原链表按pivot的值进行划分,成三个链表
        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;
        }
        //将三个链表从小中大进行连接
        //small连接equal表
        if(sT!=null)
        {
            sT.next = eH;
            eT = eT == null? sT:eT;
        }
        //equal表连接big表
        if(eT!=null)
        {
            eT.next = bH;
        }
        //返回头指针
        return sH!=null?sH:eH!=null?eH:bH;
    }

复制含有随机指针节点的链表

【题目】

一种特殊的链表节点类描述如下:

public class Node {

  public int value;

  public Node next;

  public Node rand;

  public Node(int data) {

  this.value = data;

  }

}

Node类中的value是节点值,next指针和正常单链表中next指针的意义一样,都指向下一个节点,rand指针是Node类中新增的指针,这个指针可能指向链表中的任意一个节点,也可能指向null。给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个函数完成这个链表中所有结构的复制,并返回复制的新链表的头节点。

【思路】

使用哈希表,为每个原节点都新建一个新节点作为映射,遍历链表,对每个节点n,在哈希表中找其next指针和rand指针所对应的新节点,将n所对应的新节点的next指针和rand指针替换为上述在哈希表中找到的指针,往下遍历,即可完成深度拷贝任务。哈希表的作用就是对老链表和新链表的每个节点建立联系。

【Code】

public static Node CopyListRandom(Node head)
    {
        //使用哈希表的方法,空间复杂度为O(N)
        HashMap<Node, Node> map = new HashMap<Node,Node>();
        Node curNode = head;
        //建立映射
        while(curNode!=null)
        {
            map.put(curNode, new Node(curNode.value));
            curNode = curNode.next;
        }
        curNode = head;
        while(curNode!=null)
        {
            //在哈希表中去找next指针和rand指针指向的节点所对应的拷贝节点,并将其赋给当前指针的拷贝节点
            map.get(curNode).next = map.get(curNode.next);
            map.get(curNode).rand = map.get(curNode.rand);
            curNode=curNode.next;
        }
        return map.get(head);
    }

【进阶】不使用额外的数据结构,只用有限几个变量,且在时间复杂度为O(N)内完成原问题要实现的函数。

【思路】

为每个节点产生一个新的替换节点,该节点即为上述哈希表中老节点对应的映射节点,是同种概念,然后将老节点的next指针指向新的节点,新节点的next指针再指向原本老节点的next指针所指向的节点,简单来说就是新建一个节点插入在老节点之后。

例如:1->2->4->3  为每个节点构造新的替换节点后,链表变为:1->1’->2->2’->4->4’->3->3’

第二步,拷贝rand指针。当1rand指针指向2时,2next指针指向的是2的拷贝2’,此时,将1‘rand指针等于2next指针,即1’->rand=1->rand->next,即可拷贝rand指针,依次往下即可拷贝全部节点的rand指针。

第三步。分离新链表和老链表(将1next指回2,1’next指回2’)

 【Code】 

public static Node copyListRandomByBetter(Node heaNode) {
        //时间复杂度为O(N) 空间复杂度为O(1)
        if(heaNode == null)
            return null;
        Node curNode = heaNode;
        Node nextNode = null;
        //第一步:拷贝节点并插入在每个老节点之后
        while(curNode!=null)
        {
            nextNode = curNode.next;
            curNode.next = new Node(curNode.value);
            curNode.next.next = nextNode;
            curNode = nextNode;
        }
        curNode = heaNode;
        Node curCopy = null;
        //第二步:拷贝每个老节点的rand指针
        while(curNode!=null)
        {
            nextNode = curNode.next.next;
            curCopy = curNode.next;
            curCopy.rand = curNode.rand!=null? curNode.rand.next : null;
            curNode = nextNode;
        }
        //第三步:分离链表中的新老节点
        curNode = heaNode;
        Node res = heaNode.next;
        while(curNode!=null)
        {
            nextNode = curNode.next.next;
            curCopy = curNode.next;
            curNode.next = nextNode;
            curCopy.next = nextNode!=null? nextNode:null;
            curNode = nextNode;
        }
        return res;
    }

【题目】

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

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

【提示】

当一个链表有环时,设定快指针和慢指针,快指针一次走两步,慢指针一次走一步,他们肯定会存在相遇的情况,且两指针指向的内存地址相同,此时再将快指针回到表头,一次走一步,慢指针在当前位置继续一次走一步,当俩指针相遇的地点即为入环的第一个节点。这是一个数学结论。

【思路1】

使用哈希set,将head1的所有节点放入,遍历head2的节点,在set中查找是否已经存在,若当前节点已在set中存在,则当前节点是相交的第一个节点。额外空间复杂度是n。不满足题意对空间复杂度的要求,故不出Code。

【思路2】

当两链表都无环时(用提示中的技术判断): 

遍历两链表,获得两链表的长度和两个表尾节点,若两个表尾节点不相等,两链表绝不可能相交,因为是单向链表,只有一个next指针。

当表尾节点相同时,观察链表长度,获得长度差值len,让表长度较大一方先走len步,再让两表同时往下移动,当两表指针同时指向相同的内存空间时,此位置为相交的第一个节点。

当两链表 一个有环一个无环时,不可能相交。

当两链表都有环时

存在三种情况,当对两链表使用技术判别时返回的入环节点相同时,为情况2。不相同时为情况1或者3。不相同时,对入环节点1,继续往下走的过程中判别是否与节点2相等,如果相等即为情况3,不存在相等时为情况1。 

情况1:不相交,各自成环,返回null

情况2:相交,相交点在环之前。此时将技术判别返回的入环节点作为中止节点,此时又是一个无环相交问题,复用上述代码即可。 

情况3:相交,相交点在环中。两个入环节点都是相交节点,都可返回。

【Code】

    public static Node getIntersectNode(Node head1,Node head2) {
        if(head1==null || head2==null)
            return null;
        Node loop1 = getLoopNode(head1);
        Node loop2 = getLoopNode(head2);
        if(loop1==null && loop2 == null)
            return NoLoopList(loop1,loop2);
        if(loop1!=null && loop2!=null)
            return BoothloopList(head1,head2,loop1, loop2);
        return null;
    }
    public static Node getLoopNode(Node head) {
        //满足下列条件时不可能存在环
        if(head == null ||head.next == null || head.next.next == null)
            return null;
        Node fast = head.next.next;
        Node slow = head.next;
        //如果链表有环,按此规则他们一定会相遇,如无环则fast会走向null
        while(fast!=slow)
        {
            if(fast.next==null||fast.next.next==null)
                return null;
            slow = slow.next;
            fast = fast.next.next;
        }
        //判断fast
        fast = head;
        while(fast!=slow)
        {
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }
    public static Node NoLoopList(Node head1,Node head2) {
        if(head1==null||head2==null)
            return null;
        //两链表都不存在环的情况下求相交
        //先各自遍历到末端节点,并计算各自长度
        Node last1 = head1;
        Node last2 = head2;
        int n =0;
        while(last1.next!=null)
        {
            last1 = last1.next;
            n++;
        }
        while(last2.next!=null)
        {
            last2 = last2.next;
            n--;
        }
        //两单向链表如果相交,其终点必为相同
        if(last1!=last2)
            return null;
        last1 = n>0?head1:head2;
        last2 = last1 ==head1?head2:head1;
        n = Math.abs(n);
        //更长的链表先走差值n的步数
        while(n!=0)
        {
            n--;
            last1 = last1.next;
        }
        //再一起往下走,直到相遇
        while(last1!=last2)
        {
            last1 = last1.next;
            last2 = last2.next;
        }
        //跳出循环时的当前节点即为入环点
        return last1;
    }
    public static Node BoothloopList(Node head1,Node head2,Node loop1,Node loop2) {
        Node cur1 = null;
        Node cur2 = null;
        //入环点相同,证明相交点在环之前
        if(loop1==loop2)
            //此时将入环点作为末尾节点,复用noloop函数内容
        {
            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;
        }
    }

【题目】

分别实现反转单向链表和反转双向链表的函数。

【要求】

如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)

【Code】

public static Node reverseList(Node head) {
        Node reverse = null;
        Node next = null;
        while(head!=null)
        {
            //头插法
            next = head.next;
            head.next = reverse;
            reverse = head;
            head = next;
        }
        return reverse;
    }
    public static class DoubleNode {
        public int value;
        public DoubleNode last;
        public DoubleNode next;

        public DoubleNode(int data) {
            this.value = data;
        }
    }
    public static DoubleNode reverseDobleList(DoubleNode head) {
        DoubleNode pre = null;
        DoubleNode next = null;
        while(head!=null)
        {
            next = head.next;
            head.next = pre;
            head.last = next;
            pre = head;
            head = next;
        }
        return pre;
    } 
posted @ 2021-01-31 16:45  γGama  阅读(147)  评论(0)    收藏  举报