#数据结构与算法学习笔记#剑指Offer24:复杂链表复制 + 4种实现方法比较 + 哈希表使用 + 测试用例(Java、C/C++)

234 篇文章 1 订阅
80 篇文章 0 订阅

2018.8.30     《剑指Offer》从零单刷个人笔记整理(66题全)目录传送门​​​​​​​

临近开学事情忽然有点多了,开学之后打算新开两个征程:LeetCode和PAT,假期已经有点囤货了,开学再整理记录一下。

这道题初看很简单,一个个结点复制就完事了,好像没什么不同。细细一想,不对,next的可以直接复制,但是random的要怎么对应呢。于是比较直接能够想到的解法是以下的思路一,通过对链表结点的记录,实现新链表random的赋值。下面提供四种思路并进行比较:

方法一:标记法,复杂度o(n^2)。第一次复制,先按next顺序从头复制到新链表,同时利用链表结点值对每一个结点的位置进行记录(例如头结点的label值记录为0,代表其location);第二次复制,将所有结点的random结点根据其所在位置复制到新链表上。

方法二:哈希表法,复杂度o(n),空间换时间,代码简洁。第一次遍历,将每一个结点和新结点一一映射到Hashmap上;第二次遍历,复制所有next值与random值。

方法三:加长拆分法,复杂度o(n),效率高。第一步,复制链表的每一个结点并插在该结点之后;第二步,复制每一个结点的ramdom值;第三步,分离长链表 (注意这道题不能返回pHead,还要保留原链表的结构)。

方法四:递归法,复杂度o(n),空间效率略低。转化为一个头结点和除去头结点剩余部分,剩余部分操作和原问题一致,递归复制每一个结点。


题目描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)


Java实现(四种方法):

/**
 * 
 * @author ChopinXBP 
 * 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。
 * (注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
 *
 */

import java.util.HashMap;

public class RandomListNode_24 {

	public static class RandomListNode {
	    int label;
	    RandomListNode next = null;
	    RandomListNode random = null;

	    RandomListNode(int label) {
	        this.label = label;
	    }
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		RandomListNode newhead0 = new RandomListNode(0);
		RandomListNode newhead1 = new RandomListNode(1);
		RandomListNode newhead2 = new RandomListNode(2);
		newhead0.next = newhead1;
		newhead0.random = newhead2;
		newhead1.next = newhead2;
		newhead1.random = null;
		newhead2.random = newhead1;
		RandomListNode p = newhead0;
		while(p.next != null){
			System.out.print(p.label);
			p = p.next;
		}
		System.out.print(p.label);
		System.out.print('\n');
		p = newhead0;
		for(int i = 0; i < 3; i++){
			if(p.random == null){
				System.out.print('#');
			}else{
				System.out.print(p.random.label);
			}
			p = p.next;
		}
		System.out.print('\n');
		
		RandomListNode newheadx = Clone2(newhead0);
		p = newheadx;
		while(p.next != null){
			System.out.print(p.label);
			p = p.next;
		}
		System.out.print(p.label);
		System.out.print('\n');
		p = newheadx;
		for(int i = 0; i < 3; i++){
			if(p.random == null){
				System.out.print('#');
			}else{
				System.out.print(p.random.label);
			}
			p = p.next;
		}
	}
	
	//方法一:标记法,复杂度o(n^2)
    public static RandomListNode Clone(RandomListNode pHead)
    {
    	if(pHead == null)return null;
    	RandomListNode newhead = new RandomListNode(pHead.label);
    	RandomListNode pnew = newhead;
    	RandomListNode pold = pHead;
    	int count = 0;
    	
    	//先按next顺序从头复制链表
    	while(pold.next != null){
    		pold.label = count;			//标记当前结点所在链表位置
    		RandomListNode ptmp = new RandomListNode(pold.next.label);
    		pnew.next = ptmp;
    		pold = pold.next;
    		pnew = pnew.next;
    		count++;
    	}
    	
    	//若只有一个结点,可以直接返回。返回前需要考察其random指针指向的是null还是本结点,然后进行赋值。
    	if(count == 0){
    		if(pHead.random != null) newhead.random = newhead;
    		return newhead;
    	}
    	pold.label = count;			//收尾工作,标记最后一个结点
    	
    	pnew = newhead;
    	pold = pHead;
    	RandomListNode pnewtmp;
    	for(int i = 0; i <= count; i++){
    		//若该结点random值为空则保留,若不空则从头遍历到该结点所在位置对random进行赋值
    		if(pold.random != null){
    			int location = pold.random.label;	//寻找random结点所在位置
        		pnewtmp = newhead;
            	while(location > 0){
            		pnewtmp = pnewtmp.next;
            		location--;
            	}
            	pnew.random = pnewtmp;
    		}
        	if(i != count){
        		pnew = pnew.next;
        		pold = pold.next;
        	}
    	}
    	return newhead;    	
    }
    
    //方法二:哈希表法,复杂度o(n),空间换时间
	public static RandomListNode Clone2(RandomListNode pHead) {
        HashMap<RandomListNode, RandomListNode> map = new HashMap<RandomListNode, RandomListNode>();
		RandomListNode cur = pHead;
		while (cur != null) {
			map.put(cur, new RandomListNode(cur.label)); // 将新旧结点配对在哈希表里
			cur = cur.next;
		}
		cur = pHead;
		while (cur != null) {
			map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
			cur = cur.next;
		}
		return map.get(pHead);
	}


    //方法三:加长拆分法,复杂度o(n),效率高
    public static RandomListNode Clone3(RandomListNode pHead)
    {
    	if(pHead == null)return null;    	
    	RandomListNode p = pHead;
    	
    	//若只有一个结点,可以直接返回。返回前需要考察其random指针指向的是null还是本结点,然后进行赋值。
    	if(p.next == null){
    		RandomListNode newhead = new RandomListNode(pHead.label);
    		if(pHead.random != null) newhead.random = newhead;
    		return newhead;
    	}
    	
    	//第一步:复制链表的每一个结点并插在该结点之后
    	while(p != null){
    		RandomListNode ptmp = new RandomListNode(p.label);
    		ptmp.next = p.next;
    		p.next = ptmp;
    		p = ptmp.next;
    	}
    	//收尾工作,p回到表头
		p = pHead;
		
    	//第二步:复制每一个结点的ramdom值
    	while(p != null){
    		if(p.random != null){
    			p.next.random = p.random.next;
    		}
    		p = p.next.next;
    	}
		
		//第三步,分离长链表   	
    	//这道题不能返回pHead,还要保留原链表的结构(实际上pHead有其他引用记录着,所以可以进行操作,但是要保证原链表结构不变)
    	RandomListNode newhead = new RandomListNode(0);	
    	p = newhead;
    	while(pHead!=null){
            p.next = pHead.next; 
            p = p.next;
            pHead.next = pHead.next.next; 
            pHead = pHead.next;
        }
        return newhead.next;
    }
    
    //方法四:递归,思路清晰,复杂度o(n),空间效率低
    //此题转化为一个头结点和除去头结点剩余部分,剩余部分操作和原问题一致,递归复制每一个结点。
	public static RandomListNode Clone4(RandomListNode pHead) {
		if (pHead == null)
			return null;

		RandomListNode newNode = new RandomListNode(pHead.label);

		newNode.random = pHead.random;
		newNode.next = Clone(pHead.next);

		return newNode;
	}

	
	
}

C++实现(方法二):

class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead)
    {
        if(pHead==NULL) return NULL;
 
        map<RandomListNode*,RandomListNode*> m;
        RandomListNode* pHead1 = pHead;
        RandomListNode* pHead2 = new RandomListNode(pHead1->label);
        RandomListNode* newHead = pHead2;
        m[pHead1] = pHead2;
        while(pHead1){
            if(pHead1->next) pHead2->next = new RandomListNode(pHead1->next->label);
            else pHead2->next = NULL;
            pHead1 = pHead1->next;
            pHead2 = pHead2->next;
            m[pHead1] = pHead2;
        }
 
        pHead1 = pHead;
        pHead2 = newHead;
        while(pHead1){
            pHead2->random = m[pHead1->random];
            pHead1 = pHead1->next;
            pHead2 = pHead2->next;
        }
        return newHead;
    }
};

C++实现(方法三):

/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
*/
class Solution {
public:
    //复制原始链表的任一节点N并创建新节点N',再把N'链接到N的后边
    void CloneNodes(RandomListNode* pHead)
    {
        RandomListNode* pNode=pHead;
        while(pNode!=NULL)
        {
            RandomListNode* pCloned=new RandomListNode(0);
            pCloned->label=pNode->label;
            pCloned->next=pNode->next;
            pCloned->random=NULL;
              
            pNode->next=pCloned;
              
            pNode=pCloned->next;
        }
    }
    //如果原始链表上的节点N的random指向S,则对应的复制节点N'的random指向S的下一个节点S'
    void ConnectRandomNodes(RandomListNode* pHead)
    {
        RandomListNode* pNode=pHead;
        while(pNode!=NULL)
        {
            RandomListNode* pCloned=pNode->next;
            if(pNode->random!=NULL)
                pCloned->random=pNode->random->next;
            pNode=pCloned->next;
        }
    }
    //把得到的链表拆成两个链表,奇数位置上的结点组成原始链表,偶数位置上的结点组成复制出来的链表
    RandomListNode* ReConnectNodes(RandomListNode* pHead)
    {
        RandomListNode* pNode=pHead;
        RandomListNode* pClonedHead=NULL;
        RandomListNode* pClonedNode=NULL;
          
        //初始化
        if(pNode!=NULL)
        {
            pClonedHead=pClonedNode=pNode->next;
            pNode->next=pClonedNode->next;
            pNode=pNode->next;
              
        }
        //循环
        while(pNode!=NULL)
        {
            pClonedNode->next=pNode->next;
            pClonedNode=pClonedNode->next;
            pNode->next=pClonedNode->next;
            pNode=pNode->next;
        }
          
        return pClonedHead;
          
    }
    //三步合一
    RandomListNode* Clone(RandomListNode* pHead)
    {
        CloneNodes(pHead);
        ConnectRandomNodes(pHead);
        return ReConnectNodes(pHead);
    }
};

C++实现(方法四):

class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead)
    {
        if(pHead==NULL)
            return NULL;
         
        //开辟一个新节点
        RandomListNode* pClonedHead=new RandomListNode(pHead->label);
        pClonedHead->next = pHead->next;
        pClonedHead->random = pHead->random;
         
        //递归其他节点
        pClonedHead->next=Clone(pHead->next);
         
        return pClonedHead;
    }
};

测试代码:

// ====================测试代码====================
void Test(char* testName, ComplexListNode* pHead)
{
    if(testName != NULL)
        printf("%s begins:\n", testName);

    printf("The original list is:\n");
    PrintList(pHead);

    ComplexListNode* pClonedHead = Clone(pHead);

    printf("The cloned list is:\n");
    PrintList(pClonedHead);
}

//          -----------------
//         \|/              |
//  1-------2-------3-------4-------5
//  |       |      /|\             /|\
//  --------+--------               |
//          -------------------------
void Test1()
{
    ComplexListNode* pNode1 = CreateNode(1);
    ComplexListNode* pNode2 = CreateNode(2);
    ComplexListNode* pNode3 = CreateNode(3);
    ComplexListNode* pNode4 = CreateNode(4);
    ComplexListNode* pNode5 = CreateNode(5);

    BuildNodes(pNode1, pNode2, pNode3);
    BuildNodes(pNode2, pNode3, pNode5);
    BuildNodes(pNode3, pNode4, NULL);
    BuildNodes(pNode4, pNode5, pNode2);

    Test("Test1", pNode1);
}

// m_pSibling指向结点自身
//          -----------------
//         \|/              |
//  1-------2-------3-------4-------5
//         |       | /|\           /|\
//         |       | --             |
//         |------------------------|
void Test2()
{
    ComplexListNode* pNode1 = CreateNode(1);
    ComplexListNode* pNode2 = CreateNode(2);
    ComplexListNode* pNode3 = CreateNode(3);
    ComplexListNode* pNode4 = CreateNode(4);
    ComplexListNode* pNode5 = CreateNode(5);

    BuildNodes(pNode1, pNode2, NULL);
    BuildNodes(pNode2, pNode3, pNode5);
    BuildNodes(pNode3, pNode4, pNode3);
    BuildNodes(pNode4, pNode5, pNode2);

    Test("Test2", pNode1);
}

// m_pSibling形成环
//          -----------------
//         \|/              |
//  1-------2-------3-------4-------5
//          |              /|\
//          |               |
//          |---------------|
void Test3()
{
    ComplexListNode* pNode1 = CreateNode(1);
    ComplexListNode* pNode2 = CreateNode(2);
    ComplexListNode* pNode3 = CreateNode(3);
    ComplexListNode* pNode4 = CreateNode(4);
    ComplexListNode* pNode5 = CreateNode(5);

    BuildNodes(pNode1, pNode2, NULL);
    BuildNodes(pNode2, pNode3, pNode4);
    BuildNodes(pNode3, pNode4, NULL);
    BuildNodes(pNode4, pNode5, pNode2);

    Test("Test3", pNode1);
}

// 只有一个结点
void Test4()
{
    ComplexListNode* pNode1 = CreateNode(1);
    BuildNodes(pNode1, NULL, pNode1);

    Test("Test4", pNode1);
}

// 鲁棒性测试
void Test5()
{
    Test("Test5", NULL);
}

int _tmain(int argc, _TCHAR* argv[])
{
    Test1();
    Test2();
    Test3();
    Test4();
    Test5();

    return 0;
}

#Coding一小时,Copying一秒钟。留个言点个赞呗,谢谢你#

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值