这里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,slowPointer先走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;
}
6.两个链表的第一个公共结点
问题描述
输入两个单链表,找出他们的第一个公共结点,没有返回nullptr
算法思路
我们使用两个指针 node1,node2 分别指向两个链表 headA,headB 的头结点,然后同时分别逐结点遍历,当 node1 到达链表 headA 的末尾时,重新定位到链表 headB 的头结点;当 node2 到达链表 headB 的末尾时,重新定位到链表 headA 的头结点。这样两个指针前面走的路程都一样,当它们相遇时,所指向的结点就是第一个公共结点。后面要是有公共路程,总路程也一样长。
代码如下
/*
ListNode *getFirtstCommonNode(ListNode *headA, ListNode *headB) {
ListNode *node1 = headA;
ListNode *node2 = headB;
//若是没有公共节点 则也在nullptr相遇 总路程为m+n
while (node1 != node2) {
node1 = node1 != nullptr ? node1->next : headB;
node2 = node2 != nullptr ? node2->next : headA;
}
return node1;
}
以上6题的套路其实都非常类似
7.单链表在时间复杂度为O(1)删除链表结点
问题描述
给定单链表的头指针和一个要删除结点指针,要求时间复杂度为O(1)删除链表结点
算法思路
传统做法 遍历链表pTemp->next == pToBeDeleted 时,pTemp->next 指向pToBeDeleted->next
然后删除pToBeDeleted ,这样时间复杂度不符合O(1)。
可以直接用pToBeDeleted->next 的 value替换 pToBeDeleted->value ,然后删除pToBeDeleted->next 即可,但当pToBeDeleted是最后一个节点时,就只能用传统作法
代码如下
void DeleteNode(ListNode *pHead, ListNode *pToBeDeleted)
{
if (!pHead|| !pToBeDeleted)
return;
if (pToBeDeleted->m_pNext != NULL) {
ListNode *pNext = pToBeDeleted->next;
pToBeDeleted->next= pNext->next;
pToBeDeleted->value= pNext->value;
delete pNext;
pNext = NULL;
}
else { //待删除节点为尾节点
ListNode *pTemp = pHead;
while(pTemp->next!= pToBeDeleted)
pTemp = pTemp->next;
pTemp->next= NULL;
delete pToBeDeleted;
pToBeDeleted = NULL;
}
}
8.合并两个排序的链表
问题描述
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则
算法思路
递归方式
较小的结点指向,此结点指向下个结点和另外一个链表结点中的较小值结点
ListNode *Merge(ListNode * list1,ListNode *list2){
if(!list1)
return list2;
if(!list2)
return list1;
if(list1.val <= list2.val){
list1.next = Merge(list1.next, list2);
return list1;
}else{
list2.next = Merge(list1, list2.next);
return list2;
}
}
合并K个排序的链表 附上 leetcode版本
算法思路
class Solution {
public:
ListNode* mergeTwoLists(ListNode *a, ListNode *b) {
if ((!a) || (!b)) return a ? a : b;
ListNode head, *tail = &head, *aPtr = a, *bPtr = b;
while (aPtr && bPtr) {
if (aPtr->val < bPtr->val) {
tail->next = aPtr; aPtr = aPtr->next;
} else {
tail->next = bPtr; bPtr = bPtr->next;
}
tail = tail->next;
}
//指向还未走完的链表
tail->next = (aPtr ? aPtr : bPtr);
return head.next;
}
ListNode* mergeKLists(vector<ListNode*>& lists) {
ListNode *ans = nullptr;
for (size_t i = 0; i < lists.size(); ++i) {
ans = mergeTwoLists(ans, lists[i]);
}
return ans;
}
};
9.反转链表
问题描述
题目:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。
1->2->3->nullptr
3->2->1->nullptr
算法思路
1.头插法
定义一个pHead头结点,返回pHead->next
ListNode *reverseList(ListNode *pHead) {
ListNode m_Head;
ListNode *cur = pHead;
m_Head->next = nullptr;
while(pHead != nullptr){
ListNode *nxt = cur->next;
cur->next = m_Head->next;
m_Head->next = cur;
cur = nxt;
}
return m_Head->next;
}
2.原地翻转
用两个三个指针 pre cur nxt 分别代表上一个结点 当前结点 下个结点,每次先保存nxt为cur->next
然后再用cur->next指向上个结点pre,更新pre和cur到下个结点
ListNode *reverseList(ListNode *pHead) {
ListNode *pre = nullptr;
ListNode *cur = pHead;
while(cur != nullptr){
ListNode *nxt = cur->next;
cur->next = pre;
pre = cur;
cur = nxt;
}
return pre;
}