剑指offer中链表相关问题

剑指offer中链表相关问题,目录:
14.链表中倒数第k个结点
15.反转链表
16.合并两个排序链表
25.复杂链表的复制
36.两个链表的第一个公共结点
55.链表中环的入口结点
56.删除链表中的重复结点

14.链表中倒数第k个结点
输入一个链表,输出该链表中倒数第k个结点

/*
遍历链表一次,时间复杂度O(n)
定义两个指针。
第一个指针从链表的头指针开始遍历向前走k-1,第二个指针保持不动。
从第k步开始,第二个指针也开始遍历。
当第一个指针叨叨链表的尾部时,第二个指针正好是倒数第k个结点。
需要处理的3个鲁棒性问题:
1.输入的pListHead为空
2.输入的链表总结点数小于k
3.输入的k为0
*/
/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        if(pListHead == NULL || k==0)
            return NULL;
        
        ListNode *p1=pListHead;
        ListNode *p2=pListHead;
        for(int i=0;i<k-1;i++){
            if(p1->next != NULL){
                p1 = p1->next;
            }else{
                return NULL;
            }
            
        }
        while(p1->next!= NULL ){
            p1 = p1->next;
            p2 = p2->next;
        }
        return p2;
    }
};

15.反转链表
输入一个链表,反转链表后,输出新链表的表头。

/*
使用递归的方法进行链表反转
递归出口:pNode == NULL
假设h、i、j是3个相邻的结点,经过若干操作,我们已经将h之前的结点的指针调整完毕。
将I->next指向h,此时则无法在链表中遍历到j结点,为避免链表在i处断开,我们需要在调整i的next指针之前,保存下j结点。
我们在调整i的next结点之前,需要保存i的前一个结点h。
所以我们需要定义3个结点。
*/
/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        ListNode *pReverseHead = NULL;
        ListNode *pNode = pHead;
        ListNode *pPre = NULL;
        ListNode *pNext = NULL;
        while(pNode != NULL){
            pNext = pNode->next;
            if(pNext == NULL)
                pReverseHead =pNode;
            pNode->next = pPre;
            pPre = pNode;
            pNode = pNext;
        }
        return pReverseHead;

    }
};

16.合并两个排序链表
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

/*
使用递归方法,每次合并前比较两个头结点的大小。
鲁棒性问题:对空指针进行处理。当头结点为空指针时,将它和第二个链表合并。
*/
/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        if(pHead1 == NULL) return pHead2;
        else if(pHead2 == NULL) return pHead1;
        
        ListNode* pMergedHead = NULL;
        if(pHead1->val < pHead2->val){
            pMergedHead = pHead1;
            pMergedHead->next = Merge(pHead1->next,pHead2);
        }else{
            pMergedHead = pHead2;
            pMergedHead->next = Merge(pHead1,pHead2->next);
        }
        return pMergedHead;
        
    }
};

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

O(n^2):
第一步:复制原始链表上的每一个结点,并使用next链接起来;
第二步:设置每个结点的random指针,需要从链表头开始找O(n)步,总的时间复杂度为O(n^2);
优化:使用空间换时间,时间复杂度O(n),空间复杂度O(n)
第一步:复制原始链表上的结点,使用next链接;同时将原始链表和复制链表的<N,N’>配对信息放入哈希表。
第二步:设置复制链表上的random结点,在复制链表中,对应的N’指向S’。由于哈希表的存在,可以使用O(1)的时间找到S‘。
优化:不使用辅助空间的情况下实现O(n)时间效率
第一步:复制原始链表上的结点,使用next链接;同时,将N’链接到N的后面。
第二步:设置复制出来的random。
第三步:将这个长链表拆分成为两个链表;把奇数位置结点使用next连接起来就是原始链表。

36.两个链表的第一个公共结点
输入两个链表,找出它们的第一个公共结点。

/*
蛮力法:若第一个链表长度为m,第二个链表长度为n,时间复杂度为O(mn)。
优化:空间换时间
从链表的尾部开始向前比较,找出最后一个相同的结点。
分别将两个链表的结点放入两个栈里,揭晓来比较两个栈顶的结点是否相同。
空间复杂度O(n+m),时间复杂度O(n+m)。
优化:
第一步:遍历两个链表得到他们的长度,设较长的链表多k个结点。
第二步:先在较长的链表上走k步,接着同时在两个链表上进行遍历,找到的第一个相同结点就是第一个公共结点。
*/
/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        int length1= GetListLength(pHead1);
        int length2= GetListLength(pHead2);
        int k = length1-length2;
        ListNode *p1=pHead1,*p2=pHead2;
        //保证p1是较长的链表
        if(length2 >length1){
            p1=pHead2;
            p2=pHead1;
            k = length2-length1;
        }
        for(int i=0;i<k;i++){
            p1=p1->next;
        }
        while(p1!=NULL && p2!=NULL && p1 != p2){
            p1 = p1->next;
            p2 = p2->next;
        }
        return p1;//此时p1和p2为同一结点,任意返回其一
    }
    int GetListLength(ListNode* pHead){
        int length =0;
        ListNode* pNode = pHead;
        while(pNode != NULL){
            length++;
            pNode = pNode->next;
        }
        return length;
    }
};

55.链表中环的入口结点
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

/*
第一步:确定链表中是否有环。
定义两个指针,同时从链表头结点出发,一个指针一次走一步,另一个指针一次走两步,如果走的快的指针追上了走的慢的指针,则链表包含环。
第二步:找到环入口。
确定环结点个数n。定义两个指针指向头结点,指针1先前移n步,之后两个指针同时向前移动。当两指针相遇时,指向环入口结点。
*/
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if(pHead == NULL || pHead->next == NULL )
            return NULL;
        int count=1;
        ListNode* pListHead = NULL;
        ListNode* pNode = pHead->next;
        pListHead = pNode->next;
         
        while(pListHead != NULL && pNode != NULL)  //判断是否有环
        {
            if(pListHead == pNode)
                break;
            pNode = pNode->next;
            pListHead = pListHead->next;
            if(pListHead == NULL || pNode == NULL)
                return NULL;
            pListHead = pListHead->next;
            if(pListHead == NULL)
                return NULL;
        }
        pNode = pNode->next;
        while(pListHead != pNode)  //计算环节点个数
        {
            pNode = pNode->next;
            count++;
        }
         pListHead = pHead;
         pNode = pHead;
        for(int i=0;i<count;i++)     //查找环入口
        {
            pListHead = pListHead->next;
        }
        while(pListHead != pNode)
        {
            pListHead = pListHead->next;
            pNode = pNode->next;
        }
        return pListHead;
    }
};

56.删除链表中的重复结点
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

/*
头结点可能会被删除,需要新建指针指向头结点。
为了保证删除后链表仍然相连,需要记录前一结点的指针和后一结点的指针。
排序链表中,如果相邻两个结点相同则删除一个结点,不同则后移。
*/
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead)
    { 
        if(pHead==NULL) return NULL;
        if(pHead->next == NULL ) return pHead;
        
        ListNode* newHead=new ListNode(-1);  //新建一个指针指向头结点
        newHead->next=pHead;
        ListNode* pPre=newHead; //需要两个指针,一个指向前一个节点preNode,另一个指向当前节点node,
        ListNode* pNode=pHead;
        ListNode* pNext=NULL;
        while(pNode!=NULL && pNode->next!=NULL){
            pNext=pNode->next;
            //如果当前节点的值和下一个节点的值相等
            if(pNode->val==pNext->val){
                //向后重复查找.移动
                while(pNext!=NULL && pNode->val==pNext->val)  pNext=pNext->next;
                pPre->next=pNext;//直到不相等为止,进行连接
                pNode=pNext;
            }else{//如果当前节点的值和下一个节点的值不相等,直接向后移一个
                pPre=pNode;
                pNode=pNext;
            }
        }
        return newHead->next;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值