链表中经典OJ面试题图文详解

  1. 删除链表中等于给定值 val 的所有节点
    输入: 1->2->6->3->4->5->6, val = 6
    输出 : 1->2->3->4->5
    在这里插入图片描述
struct ListNode* removElements(struct ListNode* head, int val)
{
    
    
	struct ListNode* cur = head;
	struct ListNode* prev = NULL;

	while (cur)
	{
    
    
		//该节点要被删除
		if (cur->val == val)
		{
    
    
			//要删除的是头结点
			if (cur->val == head)
			{
    
    
				head->next = cur->next;
				free(cur);
				cur = head;
			}
			//非头结点
			else
			{
    
    
				prev->next = cur->next;
				free(cur);
				cur = prev->next;
			}
		}
		//没找到要删除的节点,继续向下寻找,记着先将prev的地址保存起来再移动cur
		else
		{
    
    
			prev = cur;
			cur = cur->next;
		}
	} 
}
  1. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个
    中间结点

    在这里插入图片描述
struct ListNode* middleNode(struct ListNode* head)
{
    
    
	ListNode* fast = head;
	ListNode* slow = head;

	//保证前两步走成功
	while (fast && fast->next)
	{
    
    
		fast = fast->next->next;
		slow = slow->next;
	}
	//假如是偶数个节点,返回的节点是中间的后一个
	return slow;
}

在这里插入图片描述

struct ListNode* middleNode(struct ListNode* head)
{
    
    
	ListNode* fast = head;
	ListNode* slow = head;
	ListNode* prev = NULL;

	//保证前两步走成功
	while (fast && fast->next)
	{
    
    
		//节点个数为偶数个,并且返回中间数的前一个
		if (fast == NULL)
		{
    
    
			prev = slow;
			fast = fast->next->next;
			slow = slow->next;
		}
	}
	return prev;
}
  1. 反转一个单链表。
    示例:
    输入: 1->2->3->4->5->NULL
    输出: 5->4->3->2->1->NULL
    在这里插入图片描述
    三个指针先动prev,后动cur,最后next
typedef struct ListNode ListNode;
ListNode* reverseList(ListNode* head) 
{
    
    
	ListNode* prev = NULL;
	ListNode* cur = head;
	//万一head为空,cur->next的程序崩溃,不能使用 ListNode* next = cur->next;
	ListNode* next = NULL;

	while (cur)
	{
    
     
		next = cur->next;//先将next的位置固定
		cur->next = prev;//反转->将cur的next指向prev
		prev = cur;//首先移动prev到cur位置
		cur = next;//再移动cur到next的位置
	}
	return prev;
}
  1. 输入一个链表,输出该链表中倒数第k个结点。
    在这里插入图片描述
typedef struct ListNode ListNode;
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k)
{
    
    
	if (NULL == pListHead || k == 0)
		return NULL;
	ListNode* fast = pListHead;
	ListNode* slow = pListHead;

	//1.让fast先往后走k步
	while (k--)
	{
    
    
		//检测k是否大于链表中节点k的个数
		if (NULL == fast)
			return;
		fast = fast->next;
	}

	//2 让slow和fast一块走,直达走到fast为NULL,slow刚好指向的是倒数第k个节点
	while (fast)
	{
    
    
		fast = fast->next;
		slow = slow->next;
	}
	return slow;
} 
  1. 合并两个有序链表
    在这里插入图片描述
    每次进行尾插时需要遍历新链表,很麻烦,为此引入新指针tailNode记录尾插后节点的地址
    在这里插入图片描述
    最后结果:
    在这里插入图片描述
typedef struct ListNode ListNode;
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2)
{
    
    
	//l.l1为空,合并后为l2数组
	if (NULL == l1)
		return l2;
	
	if (NULL == l2)
		return l1;

	ListNode* cur1 = l1;
	ListNode* cur2 = l2;
	ListNode* newhead = NULL;
	ListNode* tailNode = NULL;

	//l1和l2两个链表均不为空
	if (cur1->val <= cur2->val)
	{
    
    
		newhead = cur1;
		tailNode = cur1;
		cur1 = cur1->next;
	}
	else
	{
    
    
		newhead = cur2;
		tailNode = cur2;
		cur2 = cur2->next;
	}

	//开始比较两个链表中数字大小,并进行尾插
	while (cur1 && cur2)
	{
    
    
		if (cur1->val <= cur2->val)
		{
    
    
			tailNode->next = cur1;
			cur1 = cur1->next;
			//tailNode = tailNode->next;
		}
		else
		{
    
    
			tailNode->next = cur1;
			cur1 = cur1->next;
			//tailNode = tailNode->next;
		}
		tailNode = tailNode->next;
	}

	//如果有一个提前尾插结束,另一个数组还有多余
	if (cur1)
		tailNode->next = cur1;
	else
		tailNode->next = cur2;

	return newhead;
}
  1. 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前给定一个链表的头指针 ListNode* pHead,请返回重新排列后的链表的头指针。注意:分割以后保持原来的数据顺序不变。
    在这里插入图片描述
typedef struct ListNode ListNode
ListNode* partition(ListNode* pHead, int x)
{
    
    
	if (NULL == pHead)
		return NULL;

	ListNode lessxhead(0);
	ListNode* lessTail = &lessTail;
	ListNode greatxHead(0);
	ListNode* greatTail = &greatxHead;
	ListNode* cur = pHead;
	while (cur)
	{
    
    
		pHead = cur->next;

		//将cur尾插到lessHead或greatHead
		if (cur->val < x)
		{
    
    
			lessTail->next = cur;
			lessTail = cur;
		}
		else
		{
    
    
			greatTail->next = cur;
			greatTail = cur;
		}
		cur = pHead;
	}

	//最后的时候记着把greatTail->next的置位NULL
	greatTail->next = NULL;
	lessTail->next = greatxHead.next;
	return lessHead.next;
}
  1. 对于一个链表,请设计一个时间复杂度为O(n), 额外空间复杂度为O(1)的算法,判断其是否为回文结构。给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
    测试样例:
    1->2->2->1
    返回:true
    法1
bool chkPalindrome(ListNode* A) 
{
    
    
	//空链表也是一种回文结构
	if (NULL == A)
		return true;

	int array[900] = {
    
    0}; 
	//将链表中的节点放置到array的数组中
	ListNode* cur = A;
	int size = 0;
	while(cur)
	{
    
    
		array[size++] = cur->val;
		cur = cur->next;
	}

	int left = 0, right = size - 1;
		while (left < right)
		{
    
    
			if (array[left] != array[right])
				return false; 

			left++;
			right--;
		}
		return true;
}

法2 找中间节点逆置

ListNode* ReverseListNode(ListNode* head)
{
    
    
	ListNode* cur = head;
	ListNode* prev = NULL;
	ListNode* next = NULL;
	while (cur)
	{
    
    
		next = cur->next;
		cur->next = prev;
		prev = cur;
		cur = next;
	}
	return prev;
}

bool chkPalindrome(ListNode* A)
{
    
    
	if (NULL == A)
		return true;

	//找链表中的中间节点
	ListNode* fast = A;
	ListNode* slow = A;
	ListNode* prevSlow = NULL;
	while (fast && fast->next)
	{
    
    
		fast = fast->next->next;
		prevSlow = slow;
		slow = slow->next;
	}

	prevSlow->next = NULL;

	//中间节点为slow
	//将中间的节点及其后面的所有节点进行逆置
	ListNode* rightHead = ReverseList(slow);

	//检测两个链表中的节点是否相同 
	ListNode* curRight = rightHead;
	ListNode* curLeft = A;

	while (curLeft && curRight)
	{
    
    
		if (curLeft->val != curRight->val)
			return false;

		curLeft = curLeft->next;
		curRight = curRight->next;
	}

	//需要将链表还原
	prevSlow->next = ReverseList(rightHead);
	return true;
	}
}
  1. 输入两个链表,找出它们的第一个公共结点,假设链表不带环
    在这里插入图片描述
    在这里插入图片描述
  2. 给定一个链表,判断链表中是否有环
    在这里插入图片描述
bool hasCycle(struct ListNode *head) 
{
    
    
	struct ListNode* fast = head;
	struct ListNode* slow = head;

	while (fast && fast->next)
	{
    
    
		fast = fast->next->next;
		slow = slow->next;

		//在环中相遇
		if (slow == fast)
			return true;
	}
	return false;
}
  1. 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL
    在这里插入图片描述
typedef struct ListNode ListNode;
struct ListNode* hasCycle(ListNode* head)
{
    
    
	struct ListNode* fast = head;
	struct ListNode* slow = head;
	while (fast && fast->next)
	{
    
    
		fast = fast->next->next;
		slow = slow->next;

		if (fast == slow)
			return fast;
	}
	return NULL;
}

struct ListNode *detectCycle(struct ListNode *head)
{
    
    
	ListNode* PM = hasCycle(head);
	//两个指针不相等时则不带环
	if (PM == NULL)
		return NULL;

	struct ListNode* PH = head;

	while (PH != PM)
	{
    
    
		PH = PH->next;
		PM = PM->next;
	}
	return PM;
}
  1. 对链表进行插入排序
    插入排序算法:
    1.插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
    2.每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
    3.重复直到所有输入数据插入完为止
    在这里插入图片描述
typedef struct ListNode ListNode;
struct ListNode* insertionSortList(struct ListNode* head) 
{
    
    
	//链表为空或者链表中只有一个节点
	if (NULL == head || NULL == head->next)
		return head;

	//链表中至少有两个节点
	//从原链表中获取一个节点将其插入到新链表中
	ListNode* newHead = NULL;
	ListNode* cur = head;
	while (cur)
	{
    
    
		//cur指向的链表,插入到newHead指向的新链表,构成新的排序
		head = cur->next;

			//新链表中有节点,找新插入节点的位置
			ListNode* insertPos = newHead;
			ListNode* insertPosPrev = NULL;
			while (insertPos)
			{
    
    
				if (cur->val > insertPos->val)
				{
    
    
					insertPosPrev = insertPos; 
					insertPos = insertPos->next;
				}
				else
				{
    
    
					break;
				}
			}
			if (NULL == insertPosPrev)
			{
    
    
				cur->next = newHead;
				newHead = cur;
			}
			else
			{
    
    
				//cur节点中的数据比insertPos节点中的数据小
				cur->next = insertPos;
				insertPosPrev->next = cur;
			}
		cur = head;
	}
	return newHead;
}
  1. 复制带随机指针的链表
    在这里插入图片描述
typedef struct Node Node;

Node* BuyRandomListNode(int val)
{
    
    
	Node* newNode = (Node*)malloc(sizeof(Node));
	if (NULL == newNode)
		return NULL;

	newNode->val = val;
	newNode->next = NULL;
	newNode->random = NULL;

	return newNode;
}

struct Node* copyRandomList(struct Node* head) 
{
    
    
	if (NULL == head)
		return NULL;

	//1 在原链表中每个节点后插入值相等的新节点    时间复杂度O(N)
	Node* cur = head;
	Node* newNode = NULL;
	while (cur)
	{
    
    
		newNode = BuyRandomListNode(cur->val);
		if (NULL == newNode)
			return NULL;

		//插入新节点    
		newNode->next = cur->next;
		cur->next = newNode;
		cur = newNode->next;
	}
	
	//2 给新节点的随机指针域进行赋值     时间复杂度O(N)
	//将cur的指针域指向head
	cur = head;
	while (cur)
	{
    
    
		newNode = cur->next;//首先将newNode固定在cur的下一个位置
		if (cur->random)
			newNode->random = cur->random->next;

		cur = newNode->next;//最后将cur向后走一步
	}

	//3 将新节点从原链表中拆下来    时间复杂度O(N)
	Node* newHead = head->next;
	cur = head;
	while (cur->next)
	{
    
     
		newNode = cur->next;
		cur->next = newNode->next;
		cur = newNode;
	}

	//总的时间复杂度 O(3N) == O(N)
	return newHead;
}
  1. 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
    在这里插入图片描述
ListNode* deleteDuplication(ListNode* pHead)
{
    
    
	ListNode* start = pHead;
	ListNode* end = pHead;
	ListNode* prev = NULL;

	while (start)
	{
    
    
		end = start->next;

		//1 找重复节点的范围
		while (end)
		{
    
    
			//找到了
			if (start->val != end->val)
				break;

			//继续寻找
			end = end->next;
		}

		//2 删除[start, end)区间中的节点
		//先判断start的下一个是否是end,例如11123,此时start指向2,end指向3,此种情况需要进行判断,否则进入下面的循环,无重复元素,将                                  //start移动到end位置,继续寻找
		if (start->next == end)
		{
    
    
			//区间中没有重复的元素
			prev = start;
			start = end;
		}
		else
		{
    
    
			//[start, end)有重复节点
			while (start != end)
			{
    
    
				//头删
				if (start == pHead)
				{
    
    
					pHead = start->next;
					free(start);
					start = pHead;
				}
				else
				{
    
    
					//其他节点的删除方式
					prev->next = start->next;
					free(start);
					start = prev->next;
				}
			}
		}
	} 
	return pHead;
}

猜你喜欢

转载自blog.csdn.net/aaaaauaan/article/details/106392749