链表算法操作

这里10个问题参考即可这位作者博客即可,因为有些代码是java ,在这里改成C++版本
作者:辰砂
出处:https://www.cnblogs.com/tojian/p/10055036.html

//链表结构体
struct ListNode {
    int val;
    ListNode *next = nullptr;
}

1.链表的倒数第K个结点

问题描述:
输入一个链表,输出该链表中倒数第k个结点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾结点是倒数第1个结点。例如一个链表有6个结点,从头结点开始它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个结点是值为4的结点,需要保证时间复杂度。

算法思路:
设置两个指针p1,p2,从头到尾开始出发,一个指针先出发k个节点,然后第二个指针再进行出发,当第一个指针到达链表的节点的时候,则第二个指针表示的位置就是链表的倒数第k个节点的位置。

//倒数第k个结点 
ListNode* findKth(ListNode *pHead,int k){
   ListNode *first=pHead->next; //快指针 先走
   ListNode *second=pHead->next;//慢指针
   int i=0;
   while(first!=null&i++<k){
   	first=first->next;
   }
   while(first!=null){
     second=second->next;
	 first=first->next;
   }
   return second;
}

2.从尾到头打印链表(递归和非递归)

问题描述:
输入一个单链表链表,从尾到头打印链表每个节点的值。输入描述:输入为链表的表头;输出描述:输出为需要打印的“新链表”的表头。

算法思路:
首先我们想到从尾到头打印出来,由于单链表的查询只能从头到尾,所以可以想出栈的特性,先进后出。所以非递归可以把链表的点全部放入一个栈当中,然后依次取出栈顶的位置即可。

代码如下:

//非递归
void PrintReversing(ListNode * pHead){
	//利用一个栈
	Stack stack;
	ListNode *node=pHead->next;
	//将链表的结点压入
	while(node!=null){
	  stack.push(node); 
	  node=node->next;
	}
	ListNode *popNode;
	while(stack.isEmpty()){
	//获得最上面的元素
	   popNode=stack.top();
	//打印
	   printf("%d\t",popNode->value);
	//弹出元素
	stack.pop();
}

非递归的描述当中,经常会用栈或者队列这些数据结构来改写一些递归的算法。其实递归的算法的时间复杂度是递归树的高度,所以递归的层数越高,时间复杂度也就会越高的。

//递归的方式
void printRevese(ListNode *pHead){
	if(pHead!=null)
	{
		if(pHead->next!=null){
			printRevese(pHead->next);
		}
		print("%d\t",pHead->value);
	}
}

3.如何判断一个链表有环

问题描述:
有一个单向链表,链表当中有可能出现“环”,如何用程序判断出这个链表是有环链表?
不允许修改链表结构。时间复杂度O(n),空间复杂度O(1)。
链表算法操作

算法思路:
方法一、穷举遍历
遍历链表,记录已访问的节点。
将当前节点与之前以及访问过的节点比较,若有相同节点则有环。否则,不存在环。

假设从链表头节点到入环点的距离是D,链表的环长是S。那么算法的时间复杂度是0+1+2+3+….+(D+S-1) = (D+S-1)(D+S)/2 , 可以简单地理解成 O(NN)。而此算法没有创建额外存储空间,空间复杂度可以简单地理解成为O(1)。

这种方法是暴力破解的方式,时间复杂度太高。

方法二、快慢指针
首先创建两个指针1和2,同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针1每次向下移动一个节点,让指针2每次向下移动两个节点,然后比较两个指针指向的节点是否相同。如果相同,则判断出链表有环,如果不同,则继续下一次循环。

说明 :在循环的环里面,跑的快的指针一定会反复遇到跑的慢的指针 ,比如:在一个环形跑道上,两个运动员在同一地点起跑,一个运动员速度快,一个运动员速度慢。当两人跑了一段时间,速度快的运动员必然会从速度慢的运动员身后再次追上并超过,原因很简单,因为跑道是环形的。

bool isLoopList(ListNode * pHead){
   ListNode *fastPointer=pHead->next; //快指针
   ListNode *slowPointer=pHead->next;//慢指针
    //使用快慢指针,慢指针每次向前一步,快指针每次两步
    while(fastPointer != null && fastPointer->next != null){
        slowPointer = slowPointer->next;
        fastPointer = fastPointer->next.next;
        //两指针相遇则有环
        if(slowPointer == fastPointer){
            return true;
        }
    }
    return false;
}

4.链表中环的大小

问题描述
有一个单向链表,链表当中有可能出现“环”,那么如何知道链表中环的长度呢?

算法思路
由3.如何判断一个链表有环]可以知道,快慢指针可以找到链表是否有环存在,如果两个指针第一次相遇后,第二次相遇是什么时候呢?第二次相遇是不是可以认为快的指针比慢的指针多跑了一个环的长度。所以找到第二次相遇的时候就找到了环的大小

代码如下

//求环中相遇结点
ListNode *cycleNode(ListNode *pHead){
    //链表为空则返回null
    if(!pHead)
        return nullptr;
   ListNode *fastPointer=pHead->next; //快指针
   ListNode *slowPointer=pHead->next;//慢指针
    while(fastPointer!= nullptr&& fastPointer->next != nullptr){
        fastPointer= fastPointer->next->next;
        slowPointer= slowPointer->next;
        //两指针相遇,则返回相遇的结点 
        if(fastPointer== slowPointer)
            return fastPointer;
    }
    //链表无环,则返回null
    return nullptr;
}
//求环的长度
int getCycleLength(ListNode *pHead){
    ListNode *node = cycleNode(pHead);
    //node为空则代表链表无环
    if(node == nullptr)
        return 0;
    int length = 1;
    ListNode *current = node->next;
    //再次相遇则循环结束
    while(current != node){
        length++;
        current = current->next;
    }
    return length;
}

5.链表中环的入口结点

问题描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出nullptr。

算法思路
如果链表存在环,那么计算出环的长度n,然后准备两个指针slowPointer,fastPointer,fastPointer先走n步,然后slowPointer和fastPointer一块走,当两者相遇时,即为环的入口处;所以解决三个问题:如何判断一个链表有环;如何判断链表中环的大小;链表中环的入口结点。实际上最后的判断就如同链表的倒数第k个节点。

情况一
当fastPointer ==slowPointer时,fastPointer 所经过节点数为2x,slowPointer所经过节点数为x,
设环中有n个节点,fastPointer 比slowPointer多走一圈有2x=n+x; n=x; 代表 slowPointer 走的路程等于一圈环
可以看出slow实际走了一个环的步数,再让fastPointer 指向链表头部,slowPointer 位置不变。
假设链表开头到环接口的距离是y,如下图所示,那么x-y表示slowPointer指针走过的除链表开头y在环中走过的距离,那么slow再走y步,此时fastPointer 结点与slowPointer结点相遇,fastPointer == slowPointer x - y + y = x = n,即此时slowPointer指向环的入口。

结论
1、设置快慢指针,假如有环,他们最后一定相遇。
2、两个指针分别从链表头和相遇点继续出发,每次走一步,最后一定相遇与环入口。
代码如下

ListNode *EntryNodeOfLoop(ListNode *pHead)
{
	if(pHead->next == nullptr || pHead->next->next == nullptr )
	    return nullptr ;
	ListNode *fastPointer=pHead->next->next; //快指针
	ListNode *slowPointer=pHead->next;//慢指针
	while(fastPointer!= null)
	{
	    if(fastPointer == slowPointer){
	    	//这里已经确定有环且快慢指针相遇
	        fastPointer = pHead;
	        //fastPointer 往前走y长度到环开头   
	        //slowPointer 本来已经走了长度x等于n 一个环的长度 n+y = 总长度 回到环开头
	        while(fastPointer != slowPointer){
	            fastPointer = fastPointer->next;
	            slowPointer = slowPointer->next;
	        }
	        return fastPointer;
	    }
        fastPointer = fastPointer->next;
        slowPointer = slowPointer->next->next;
	}
	return null;
}

以上5题的套路其实都非常类似,第5题可以说是前面几道题的一个汇总题目吧,链表类的题利用快慢指针,两个指针确实挺多的,如下面题目7

上一篇:算法-02-反转链表


下一篇:蓄水池抽样