链表相关算法题总结

1.环形链表插值

有一个整数val,如何在节点值有序的环形链表中插入一个节点值为val的节点,并且保证这个环形单链表依然有序。

给定链表的信息,及元素的值A及对应的nxt指向的元素编号同时给定val,请构造出这个环形链表,并插入该值。

测试样例:

[1,3,4,5,7],[1,2,3,4,0],2
返回:{1,2,3,4,5,7}
 ListNode* insert(vector<int> A, vector<int> nxt, int val) {
        // write code here
        if(A.empty())
            return nullptr;
        ListNode* insert_node=new ListNode(val);
        ListNode* head=new ListNode(A[0]);
        ListNode* p=head;
        for(int i=0;i<A.size()-1;i++)
        {
            p->next=new ListNode(A[nxt[i]]);
            p=p->next;
        }
        if(val<A[0])
        {
            p->next=insert_node;
            insert_node->next=head;
            return insert_node;
        }
        p->next=head;
        p=nullptr;
        ListNode* q=head;
        while(1)
        {
            if(val>q->val)
            {
                p=q;  
                q=q->next;
            }
            else
            {
               p->next=insert_node;
                insert_node->next=q;
                break;
            }
        }
      
        return head;
    }

2.链表的分化

对于一个链表,我们需要用一个特定阈值完成对它的分化,使得小于等于这个值的结点移到前面,大于该值的结点在后面,同时保证两类结点内部的位置关系不变。

给定一个链表的头结点head,同时给定阈值val,请返回一个链表,使小于等于它的结点在前,大于等于它的在后,保证结点值不重复。

测试样例:

{1,4,2,5},3
{1,2,4,5}

答:先将原链表针对val值进行分化为两个链表,然后另外两个链表配接起来。

class Divide {
public:
    ListNode* listDivide(ListNode* head, int val) {
     if(head==nullptr||head->next==nullptr)    
            return head;
    ListNode* cur = head;
	ListNode* pre = nullptr;
	ListNode* smaller = new ListNode(-1);
	ListNode* bigger = new ListNode(-1);
	ListNode* s_trail=smaller;
	ListNode* b_trail=bigger;
	while (cur != nullptr)
	{
		if (cur->val <= val)
		{
			pre->next = cur->next;//删除节点
			cur->next = s_trail->next;//接到值小于num的链表中
			s_trail->next = cur;
			s_trail = cur;
		}
		if (cur->val > val)
		{
			pre->next = cur->next;
			cur->next = b_trail->next;
			b_trail->next = cur;
			b_trail = cur;
		}
        cur = pre->next;//cur指向下一个节点
	}
	head->next = smaller->next;
	s_trail->next = bigger->next; 
	b_trail->next = nullptr;
	delete smaller;
	delete bigger;
	   return head;
    }
};

3.打印两个链表的公共结点

现有两个升序链表,且链表中均无重复元素。请设计一个高效的算法,打印两个链表的公共值部分。

给定两个链表的头指针headAheadB,请返回一个vector,元素为两个链表的公共部分。请保证返回数组的升序。两个链表的元素个数均小于等于500。保证一定有公共值

测试样例:

{1,2,3,4,5,6,7},{2,4,6,8,10}
返回:[2.4.6]

答:此题利用merge的思路,由于两个链表是有序的,我们从最左边开始比较,更小的一方往右走一步。如果值相等就加入vector两方一起走一步。

class Common {
public:
    vector<int> findCommonParts(ListNode* headA, ListNode* headB) {
        // write code here
        vector<int> outv;
        if (headA == NULL || headB == NULL)
            return outv;
        struct ListNode *pa = headA;
        struct ListNode *pb = headB;
        while ((pa!=NULL) && (pb!=NULL))
        {
            if (pa->val < pb->val) //谁小谁后移
                pa = pa->next;
            else if (pb->val < pa->val)
                pb = pb->next;
            else if (pa->val == pb->val)//相同放入数组,并同时后移
            {
                outv.push_back(pa->val);
                pa = pa->next;
                pb = pb->next;
            }
        }
        return outv;
    }
};

4.链表的k逆序

有一个单链表,请设计一个算法,使得每K个节点之间逆序,如果最后不够K个节点一组,则不调整最后几个节点。例如链表1->2->3->4->5->6->7->8->null,K=3这个例子。调整后为,3->2->1->6->5->4->7->8->null。因为K==3,所以每三个节点之间逆序,但其中的7,8不调整,因为只有两个节点不够一组。

给定一个单链表的头指针head,同时给定K值,返回逆序后的链表的头指针。

5.链表指定值清除

现在有一个单链表。链表中每个节点保存一个整数,再给定一个值val,把所有等于val的节点删掉。

给定一个单链表的头结点head,同时给定一个值val,请返回清除后的链表的头结点,保证链表中有不等于该值的其它值。请保证其他元素的相对顺序。

测试样例:

{1,2,3,4,3,2,1},2
{1,3,4,3,1}

答:就是一个简单的链表删除操作,需要注意的是如果待删除值在头部的时候需要特殊处理。

class ClearValue {
public:
    ListNode* clear(ListNode* head, int val) {
        // write code here
        if(!head)
            return nullptr;
        ListNode* p=head;
        ListNode* q=head;
        while(q)
        {
            if(q==head&&q->val==val)
            {
                head=head->next;
                q=head;
            }
            else{
              if(q->val==val)
            {
                p->next=q->next;
                 q=q->next;
            }
            else
            {
                p=q;
                 q=q->next;
            }
            }
        }
        return head;
    }
};

6.链表的回文结构

请编写一个函数,检查链表是否为回文。

给定一个链表ListNode* pHead,请返回一个bool,代表链表是否为回文。

测试样例:

{1,2,3,2,1}
返回:true
{1,2,3,2,3}
返回:false

答:当我们遇到这种逆序问题时,我们首先会想到的是“栈”这种数据结构。所以这道题也可以这样用。顺序遍历一遍链表然后入展,然后不断出栈进行匹配。如果不相等则返回false。

class Palindrome {
public:
    bool isPalindrome(ListNode* pHead) {
        // write code here
         if(!pHead)
         return false;
        stack<ListNode*> sta;
        ListNode* p=pHead;
        while(p)
        {
            sta.push(p);
            p=p->next;
        }
        p=pHead;
        while(p)
        {
            ListNode* temp=sta.top();
            sta.pop();
            if(temp->val!=p->val)
                return false;
            p=p->next;
        }
        return true;
    }
};

7.复杂链表的复制

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点)。

答:复杂链表的复制主要的问题在于定位它的那个随机节点位置时,需要从链表头开始经过O(n)时间才能找到。最终的时间复杂度为O(n²)进一步优化就是采取哈希表将每个节点的随机节点位置放入哈希表中,这样定位它就只需要O(1)的时间了。最终的时间复杂度可以到O(n)但是需要额外的O(n)的空间复杂度用来存储哈希表。还有一种方法是 先对链表的每个结点进行复制插入到插入到每个节点后面,然后将各个复制的结点的随机指向位置也进行复制一遍。最终会得到一个原链表的复制 A-A1-B-B1-C-C1 最后一步就是分化这个链表使其变为A-B-C和A1-B1-C1。

/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
*/
class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead)
    {
        if(!pHead)
            return nullptr;
        RandomListNode* p=pHead;
        while(p)
        {
            RandomListNode* cloneNode=new RandomListNode(p->label);
            cloneNode->next=p->next;
            p->next=cloneNode;
            cloneNode->random=nullptr;
            p=cloneNode->next;
        }
        p=pHead;
        while(p)
        {
            RandomListNode* pcloned=p->next;
            if(p->random)
              pcloned->random=p->random;
            p=pcloned->next;
        }
           RandomListNode* CloneHead=nullptr;
           RandomListNode* pNode=pHead;
           RandomListNode* pcloneNode=nullptr;
           
           CloneHead=pcloneNode=pHead->next;
           pHead->next=CloneHead->next;
           pNode=pNode->next;
        while(pNode)
        {
            pcloneNode->next=pNode->next;
            pcloneNode=pNode->next;
            pNode->next=pcloneNode->next;
            pNode=pNode->next;
        }
        return CloneHead;
    }
};

8.链表判定是否有环

如何判断一个单链表是否有环?有环的话返回进入环的第一个节点的值,无环的话返回-1。如果链表的长度为N,请做到时间复杂度O(N),额外空间复杂度O(1)。

给定一个单链表的头结点head(注意另一个参数adjust为加密后的数据调整参数,方便数据设置,与本题求解无关),请返回所求值。

答:判定链表是否有环,我们可以使用快慢指针的技术,快指针一次走两步,慢指针一次走一步。然后共同走一遍链表,如果没有环的话,快指针会优先慢指针到达链表尾部。如果快慢指针相遇的话,则说明有环。如果要求入环节点的话,将快指针放到链表头,然后一次走一步,快慢指针继续走,相遇的地方就是入环口。由图中简单证明一下发现将快指针重置为链表头走m的长度和环的周长R减去相遇点离入环点的距离r是相等的。(m=R-r)

class ChkLoop {
public:
    int chkLoop(ListNode* head, int adjust) {
     if(head==NULL||head->next==NULL)
        return -1;
    ListNode* fast = head;
	ListNode* slow = head;
	while (fast != nullptr&&fast->next != nullptr)
	{
		fast = fast->next->next;
		slow = slow->next;
		if (fast == slow)
			break;
	}
	if (fast == slow)
	{
		fast = head;
		while (fast != slow)
		{
			fast = fast->next;
			slow = slow->next;
		}
		return slow->val;
	}
	return -1;
    }
};

9.无环单链表相交

现在有两个无环单链表,若两个链表的长度分别为m和n,请设计一个时间复杂度为O(n + m),额外空间复杂度为O(1)的算法,判断这两个链表是否相交。

给定两个链表的头结点headAheadB,请返回一个bool值,代表这两个链表是否相交。保证两个链表长度小于等于500。

答:单链表相交的话,遍历两个链表之后的末尾应该在同一个位置。

class CheckIntersect {
public:
    bool chkIntersect(ListNode* headA, ListNode* headB) {
         if(headA==NULL||headB==NULL)
         return false;
     ListNode *pa,*pb;
     pa=headA;
     pb=headB;
     while(pa->next!=NULL)
         pa=pa->next;
     while(pb->next!=NULL)
         pb=pb->next;
     if(pa==pb)
         return true;
     else
         return false;
    }
};

11.有环单链表相交判断

如何判断两个有环单链表是否相交?相交的话返回第一个相交的节点,不想交的话返回空。如果两个链表长度分别为N和M,请做到时间复杂度O(N+M),额外空间复杂度O(1)。

给定两个链表的头结点head1head2(注意,另外两个参数adjust0adjust1用于调整数据,与本题求解无关)。请返回一个bool值代表它们是否相交

答:情况1 两个链表部分重叠长度不同,最终入环节点在同一个位置。

      情况2  两个链表是分叉的,相交部分为

class ChkIntersection {
public:
     ListNode* wherestart(ListNode* head)
     {
         if(!head)
             return nullptr;
         ListNode* fast=head;
         ListNode* slow=head;
         while(fast!=nullptr&&fast->next!=nullptr)
         {
             fast=fast->next->next;
             slow=slow->next;
             if(fast==slow)
                 break;
         }
         if(fast==slow)
         {
             fast=head;
             while(fast!=slow)
             {
                 fast=fast->next;
                slow=slow->next;
             }
             return slow;
         }
         return nullptr;
     }
    bool chkInter(ListNode* head1, ListNode* head2, int adjust0, int adjust1) {
        // write code here
        if(!head1||!head2)
            return false;
        ListNode* h1=wherestart(head1);// 前面链表入环节点
        ListNode* h2=wherestart(head2);//后面链表入环节点
        if(h1==h2)       //如果两个链表的入环节点在同一个位置的话
            return true;
        ListNode* p=h1;
        do{
            p=p->next;
        }while(p!=h1&&p!=h2);  //如果入环节点不在同一个位置的话,如果第一个入环节点绕了一圈还没遇到另一个入环节点的话
        if(p==h2)
            return true;
        else
            return false;
    }
};

12.单链表相交判断

给定两个单链表的头节点head1和head2,如何判断两个链表是否相交?相交的话返回true,不想交的话返回false。

给定两个链表的头结点head1head2(注意,另外两个参数adjust0adjust1用于调整数据,与本题求解无关)。请返回一个bool值代表它们是否相交。

答:这道题就是对所有情况做一个总结:

      1.两个链表都无环的相交

      2.一个链表有环 一个链表无环肯定不能相交

      3.一个链表有环,另一个链表也有环的相交  (两种情况)

class ChkIntersection {
public:
    ListNode* chkLoop(ListNode* head) {
        if(head==NULL||head->next==NULL)
            return NULL;
        ListNode *fast,*slow;
        fast=slow=head;
        while(fast!=NULL&&fast->next!=NULL){
            slow=slow->next;
            fast=fast->next->next;
            if(fast==slow)
                break;
        }
        if(fast==slow){
            fast=head;
            while(fast!=slow){
                fast=fast->next;
                slow=slow->next;
            }
            return slow;
        }else{
            return NULL;
        }
    }
    bool chkInter(ListNode* head1, ListNode* head2, int adjust0, int adjust1) {
        // write code here
        ListNode *start1,*start2;
        start1=chkLoop(head1);
        start2=chkLoop(head2);
        if(start1==NULL&&start2==NULL){
            start1=head1;
            start2=head2;
            while(start1->next!=NULL)
                start1=start1->next;
            while(start2->next!=NULL)
                start2=start2->next;
            if(start1==start2)
                return true;
            else
                return false;
        }else if(start1!=NULL&&start2!=NULL){
            if(start1==start2)
                return true;
            ListNode *p;
            p=start1;
            do{
                p=p->next;
            }while(p!=start1&&p!=start2);
            if(p==start2)
                return true;
            else
                return false;
             
        }else{
            return false;
        }
    }
};

13.链表的插入排序

  答:以后再写吧

14.链表的归并排序

答:链表的归并排序,在划分子序列时,由于无法随机定位到中间位置。所以需要利用到“快慢指针”的技术。一个指针一次走一步,一个指针一次走两步。当快指针走到链表尾部时,慢指针此时位置为中间位置。数组划分子序列由于可以随机定位到中间位置所以划分耗费的时间是O(1),而采用快慢指针则需要O(n),所以归并排序采用递归树分析时,每一层要多O(n)的时间。最终时间为2*nlogn。忽略常系数的话也就是O(nlogn)。

代码以后再写吧

15.链表的快速排序

答:链表的快速排序 由于单链表的物理存储特性限制,所以它只能单向遍历。所以我们之前提到过的两种partion方法,只有一种能用,那种从两边向中间靠的无法使用。

list_node* get_position(list_node* beg, list_node* end)
{
	list_node* p = beg;
	list_node* q = p->next;
	int val = beg->num;
	while (q!=end)
	{
		if (q->num < val)
		{
			swap(p->next->num, q->num);//
			p = p->next;
		}
		q = q->next;
	}
	swap(p->num, beg->num);
	return p;
}
void quick_sort(list_node* beg,list_node* end)
{
	if (beg != end)
	{
		list_node* pos = get_position(beg, end);
		quick_sort(beg, pos);
		quick_sort(pos->next, end);
	}
}

16.链表是否适合二分查找?

答:不适合 二分查找需要定位到中间点,而链表的存储特性使得它必须顺序遍历到中点。这就需要使用一种快慢指针的技术。两个指针,一个指针一次走一步,一个指针一次走两步。快指针走到尾时,慢指针此时的位置就是中间的位置。所以对链表进行二分查找时,划分一次分别需要n、n/2、n/4,n/8....1 ,加起来总重会是O(n)级别的效率。这样二分查找就不比顺序查找快了。

17.从尾到头打印链表

答:反过来打印,我们会想到有一种数据结构栈是“后进先出”的。所以我们可以直接遍历链表然后将每个节点入栈,然后不断出栈打印节点。

18.链表倒数第k个结点

答:设定两个指针,一个指针先走k步,然后两个指针一起走,先走的指针到达链表尾时,后面的指针所指的位置就是倒数第k个节点。需要注意的是要考虑链表是否有k个节点,如果不足k个节点的话,我们直接返回空节点。

ListNode* FindKthToTail(ListNode* pListHead,unsigned k)
{
    if(!pListHead)
      return nullptr;
   ListNode* p=pListHead;
   ListNode* q=nullptr;
    for(int i=0;i<k-1;i++)//所以这里只要走k-1步就行 
     {
         if(p->next!=nullptr)
            p=p->next;
         else
            return nullptr;
     }
    q=pListHead;
   while(p->next) //p走到尾结点 p算是倒数第1个节点
   {
    p=p->next;
    q=q->next;
   }
     return q;
}

19.删除链表节点

给定单向链表的头指针和一个节点指针,定义一个函数在O(1)时间内删除该节点。

答:因为我们删除节点时,需要得到待删除节点前一个位置。而题目只给我们待删除节点,按正常思路肯定是无法删除的。我们可以换个方式,我们把待删除节点的下一个节点的值覆盖待删除待删除节点,然后我们删除待删除节点后面的那个节点。

这里需要考虑几种情况:1.待删除节点为尾结点 2.待删除节点为中间节点 3.链表只有一个节点

void DeleteNode(ListNode** pListHead,ListNode* pToBeDeleted)
{
   if(!pListHead||!pToBeDeleted)
      return;
   if(pToBeDeleted->next)//待删除节点后面还有节点
    {
       pToBeDeleted->val=pToBeDeleted->next->val;
       ListNode* temp=pToBeDelted->next;
       pToBeDeleted->next=pToBeDeleted->next->next;

       delete temp;
       temp=nullptr;
    }else if((*pListHead)==pToBeDeleted)//链表只有一个节点
     {
         delete pListHead;
         PListHead=nullptr;
         *pListHead=nullptr;
     }else   //待删除节点为链表尾节点
      {
          ListNode* p=*pListHead;
          while(p->next!=pToBeDeleted)
          {
              p=p->next;
          }

          p->next=pToBeDeleted->next;
          delete pToBeDeleted;
          pToBeDeleted=nullptr;
      }
    
}l

20.反转链表

答:反转链表有两种思路,第一种定义三个指针 一个指向当前节点的前一个节点,一个指向当前节点,一个指向当前节点的后一个节点。修改时只要after = current->next;current->next = before;before = current; current = after;  

list_node* reverse(list_node* li)
{
	if (!li || !(li->next))
		return nullptr;
	list_node* before = nullptr;
	list_node* current = li;
	list_node* after = nullptr;
	while (current)
	{
		after = current->next;
		current->next = before;
		before = current;
		current = after;
	}
	return before;
}

   反转链表的另外一种思路是:既然是逆序,我们很容易想到栈这种后进先出的数据结构,首先我们将链表顺序入栈。然后依次出栈,第一个元素出栈时,让它的next指向当前的栈顶。然后再第二个元素出栈,继续next指向栈顶。这样直到栈为空,链表就是逆序的了。需要注意的是当栈为空时,之前出栈的那个节点的next就该为nul了。同时还需要将头指针改变指向。

ListNode* ReverseList(ListNode* pHead) {
        if(!pHead||!(pHead->next))
            return pHead;
        stack<ListNode*> sta;
        ListNode* h=pHead;
        ListNode* tail=nullptr;
        while(h)
        {
            sta.push(h);
            tail=h;
            h=h->next;
        }
        while(!(sta.empty()))
        {
            ListNode* temp=sta.top();
            sta.pop();
            if(!(sta.empty()))
              temp->next=sta.top();
            else
                temp->next=nullptr;
        }
        pHead=tail;
        return pHead;
    }

21.合并两个排序的链表

答:利用merge的思路

list_node* sorted_list_merge(list_node* l1, list_node* l2)
{
	if (!l1 || !l2)
	{
		if (!l1&&l2)
			return l2;
		if (!l2&&l1)
			return l1;
		return nullptr;
	}
	list_node* p1 = l1;
	list_node* p2 = l2;
	list_node* head =new list_node;
	list_node* tail =head;
	while (p1&&p2)
	{
		if (p1->num <= p2->num)
		{
			tail->next = p1;
			tail =tail->next;
			p1 = p1->next;
		}
		else
		{
			tail->next = p2;
			tail = tail->next;
			p2 = p2->next;
		}
	}
	if (p1)
	{
		tail->next = p1;
	}
	if (p2)
	{
		tail->next = p2;
	}
	return head;
}

22.两个链表的第一个公共节点

答:两个链表第一个公共节点有两种解决方法,一种是使用“栈”,借用两个栈先将两个链表分别入栈,然后再不断出栈比较,直到遇到最后一个位置相同的节点。这种解决方法需要额外的O(max(m,n))的空间复杂度。时间复杂度为O(m+n)。

  第二种方法是先算出两个链表的长度,计算出它们的长度差,然后更长的那个链表先走步数为(长度差)这么多,然后两个链表再一起走,第一个位置相同的节点就是两个链表的第一个公共节点。

unsigned GetListLength(ListNode* pHead)
{
    unsigned int length=0;
    ListNode* pNode=pHead;
    while(pNode)
    {
      ++length;
      pNode=pNode->next;
    }
    return length;
}
ListNode* FindFirstCommonNode(ListNode* pHead1,ListNode* pHead2)
{
    if(!pHead1||!pHead2)
       return nullptr;
    unsigned int length1=GetListLength(pHead1);
    unsigned int length2=GetListLength(pHead2);
    int lengthdif=length1-length2; 
    ListNode* longer=head1;
    ListNode* shorter=head2;
    if(length1<length2)
    {
      lengthdif=length2-length1;
      longer=head2;
      shoerter=head1;
    }
   for(int i=0;i<lengthdif;i++)
      longer=longer->next;
   while((longer!=nullptr)&&(shorter!=nullptr)&&longer!=shorter)
     {
        shorter=shorter->next;
        longer=longer->next;
     }
     return longer;
}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值