链表OJ习题

目录

链表

1.1 单链表相关经典算法OJ题1:移除链表元素

方法一:设置新链表

方法二:释放节点

1.2 单链表相关经典算法OJ题2:反转链表

方法一:将链表中的->修改成<-

方法二:创建一个新的链表,头插

方法三:创建节点,头插

1.3 单链表相关经典算法OJ题3:合并两个有序链表

创建新链表

1.4 单链表相关经典算法OJ题4:链表的中间结点

方法一:快慢指针

方法二:计数器

1.5 循环链表经典应⽤-环形链表的约瑟夫问题

方法一:环形链表

方法二:运用数组

1.6 单链表相关经典算法OJ题5:分割链表

方法一:创建两个链表

方法二:将大于x的尾插

方法三:创建新链表,运用尾插和头插

 2.1 链表的倒数第k节点

快慢指针 

2.2 链表的回文结构

找中间节点+反转链表

2.3 相交链表

快慢指针 

2.4 环形链表

快慢指针

2.4.2 环形链表2

分析

快慢指针+分析

2.5 随机链表的复制

方法一:相对尾部的长度

方法二


链表

链表OJ习题-优快云博客文章浏览阅读1.7k次,点赞34次,收藏26次。移除链表指定节点,反转链表,合并有序链表,找链表中间节点,分割链表https://blog.youkuaiyun.com/2401_87944878/article/details/144435084https://blog.youkuaiyun.com/2401_87944878/article/details/144435084https://blog.youkuaiyun.com/2401_87944878/article/details/144435084

1.1 单链表相关经典算法OJ题1:移除链表元素

方法一:设置新链表

设置一个新链表,遍历原链表,将不是val的节点复制到新链表中;

//移除链表中的元素
//方法一:设置一个新链表,遍历原链表
// 将不是val的节点复制到新链表中;

//题目给定的链表
struct ListNode {
    int val;
    struct ListNode* next;
};
typedef struct ListNode LN;

//题目给定移除链表实现函数。完善函数
struct ListNode* removeElements(struct ListNode* head, int val) {
    //先判断head是否为空链表
    if (head == NULL)
        return head;

    //设置一个空的头节点,即哨兵位
    //避免分类第一个节点是否是NULL;
    LN* newhead = (LN*)malloc(sizeof(LN));
    LN* newtail = newhead;

    LN* pcur = head;
    //遍历原链表
    while (pcur != NULL)
    {
        if (pcur->val != val)
        {
            newtail->next = pcur;
            newtail = newtail->next;
        }
        pcur = pcur->next;
    }
    //循环结束,将newtail的下一个节点置为NULL
    newtail->next = NULL;
    //保存答案,释放哨兵位
    LN* ans = newhead->next;
    free(newhead);
    newhead = NULL;
    return ans;
}

方法二:释放节点

将是val的节点释放;

//方法二:将是val的节点释放
 struct ListNode {
    int val;
    struct ListNode* next;
    
};
 typedef struct ListNode LN;
 struct ListNode* removeElements(struct ListNode* head, int val) {
     if (head == NULL)
         return head;
     //释放val的链表,要知道val前后的节点的地址
     while(head->val == val)
         head = head->next;

     //此时,第一个节点数据不是val
     LN* pcur = head;
     LN* pos = head;
     while (pos != NULL)
     {
         if (pos->val == val)
         {
             pcur->next = pos->next;
             //pcur不走
             //释放pos空间
             free(pos);
             //pos走
             pos = pcur->next;
             continue;
         }
         pcur = pos;
         pos = pos->next;
     }
     return head;
}

1.2 单链表相关经典算法OJ题2:反转链表

206. 反转链表 - 力扣(LeetCode)https://leetcode.cn/problems/reverse-linked-list/description/https://leetcode.cn/problems/reverse-linked-list/description/

方法一:将链表中的->修改成<-

//反转链表
//方法一:将->修改成<-
 struct ListNode {
    int val;
    struct ListNode* next;
    
};
 typedef struct ListNode LN;
 struct ListNode* reverseList(struct ListNode* head) {
     if (head == NULL)
         return head;

     //设置前后指针来改变箭头的指向
     LN* n1 = NULL;
     LN* n2 = head;
     LN* n3 = head->next;

     while (n3 != NULL)
     {
         //n1   n2   n3
         n2->next = n1;
         n1 = n2;
         n2 = n3;
         n3 = n3->next;
     }
     //注意:最后n2此时next是NULL;要将其手动改成n1
     n2->next = n1;
     return n2;
 }

方法二:创建一个新的链表,头插

创建新链表,将原链表的元素头插到新链表中。
虽然叫做创建新链表,但是实际上还是在原链表上进行操作;
 
//方法二:创建新链表,进行头插
struct ListNode {
    int val;
    struct ListNode* next;
    
};
typedef struct ListNode LN;
struct ListNode* reverseList(struct ListNode* head) {
    
    if (head == NULL)
        return head;

    //创建新链表
    LN* newhead = NULL;
    LN* pcur = head;
    while (pcur)
    {
        //头插
        LN* pnext = pcur->next;
        pcur->next = newhead;
        newhead = pcur;
        pcur = pnext;
    }
    return newhead;
}

方法三:创建节点,头插

与方法二类似,但是我们创作新节点来保存每个节点的数据,在将其进行头插,此方法不会该表原链表。

//方法三:创建节点,进行头插,不改变原链表
struct ListNode {
    int val;
    struct ListNode* next;

};
typedef struct ListNode LN;
struct ListNode* reverseList(struct ListNode* head) {

    if (head == NULL)
        return NULL;

    LN* newhead = NULL;
    LN* pcur = head;
    while (pcur)
    {
        //创建新节点
        LN* newnode = (LN*)malloc(sizeof(LN));
        newnode->val = pcur->val;

        //进行头插
        newnode->next = newhead;
        newhead = newnode;
        pcur = pcur->next;
    }
    return newhead;
}

1.3 单链表相关经典算法OJ题3:合并两个有序链表

创建新链表

创建新链表,比较两个来链表,将较小的插入到新链表中。
//方法一:创建新链表,将两个链表中较小的插入到新链表中

 struct ListNode {
     int val;
     struct ListNode *next;
};
 typedef struct ListNode LN;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if (list1 == NULL)
        return list2;
    if (list2 == NULL)
        return list1;

    //创建哨兵位
    LN* newhead = (LN*)malloc(sizeof(LN));
    LN* newtail = newhead;
    //创建两个指针遍历原链表
    LN* p1 = list1;
    LN* p2 = list2;

    while (p1 && p2)
    {
        //p1的值小于p2的值
        //将p1插入到newhead中
        if ((p1->val) > (p2->val))
        {
            newtail->next = p1;
            //newtail向后走,p1也向后走
            newtail = newtail->next;
            p1 = p1->next;
        }
        else
        {
            newtail->next = p2;
            newtail = newtail->next;
            p2 = p2->next;
        }
    }
    //此时一个链表已经遍历完了
      //将另外一个直接尾插
    if (p1)
        newtail->next = p1;
    if (p2)
        newtail->next = p2;

    LN* ans = newhead->next;
    //将哨兵位释放
    free(newhead);
    newhead = NULL;
    return ans;
}

1.4 单链表相关经典算法OJ题4:链表的中间结点

方法一:快慢指针

设置两个指针,遍历原链表,一个指针每次走一步,另一个指针每次走两步;
//链表的中间节点

//方法一:运用快慢指针

  struct ListNode {
     int val;
     struct ListNode *next;
 };
typedef struct ListNode LN;
struct ListNode* middleNode(struct ListNode* head) {
    if (head == NULL)
        return head;

    //设置两个指针
    LN* fast = head;
    LN* low = head;

    //偶数个时,fast是NULL的时候结束
    //奇数个时,fast->next是NULL的时候结束
    while (fast != NULL && fast->next != NULL)
    {
        low = low->next;
        fast = fast->next->next;
    }
    return low;
}

方法二:计数器

先计算一共有多少个节点,再找中间节点。

//方法二:先计算有多少个中间节点,再找中间节点
  struct ListNode {
     int val;
     struct ListNode *next;
 };
typedef struct ListNode LN;
struct ListNode* middleNode(struct ListNode* head) {
    if (head == NULL)
        return head;

    LN* pcur = head;
    int count = 0;
    //计算节点个数
    while (pcur)
    {
        count++;
        pcur = pcur->next;
    }
    //不论是奇数个还是偶数个,中间节点都是第(n/2+1)个节点
    pcur = head;
    for (int i = 1; i < count / 2 + 1; i++)
        pcur = pcur->next;

    return pcur;
}

1.5 循环链表经典应⽤-环形链表的约瑟夫问题

方法一:环形链表

将尾节点指向头节点,在进行计数,是m的被释放;直到只有一个节点为止。
//环形链表的约瑟夫问题

typedef struct Slistnode
{
	int date;
	struct Slistnode* next;
}SL;
int ysf(int n, int m) {

   //先创建第一个节点
	SL* head = (SL*)malloc(sizeof(SL));
	SL* tail = head;
	tail->next = NULL;
	//循环创建
	for (int i = 2; i <= n; i++)
	{
		SL* newnode = (SL*)malloc(sizeof(SL));
		newnode->date = i;
		newnode->next = NULL;
		tail->next = newnode;
		tail = tail->next;
	}
	 //形成环形链表
	tail->next = head;
	int count = 1;
	SL* pcur = head;
	SL* p1= head;
	//只剩下一个节点的时候,停止循环,此时其下一个节点的指针也指向其本身
	while (pcur->next != pcur)
	{
		//当是m的时候,将pcur释放,并且将pl->pcur->next
		if (count == m)
		{
			SL* pnext = pcur->next;
			p1->next = pnext;
			free(pcur);
			pcur = pnext;
			count = 1;
		}
		else
		{
			p1 = pcur;
			pcur = pcur->next;
			count++;
		}
	}
	//保留答案,释放最后一个节点
	int ans = pcur->date;
	free(pcur);
	pcur = NULL;
	return ans;
}

方法二:运用数组

创建一个数组,将数组中所有的值初始化为1,将被杀的赋值为0;
此方法,需要多次循化,比较浪费时间。
    int ysf(int n, int m) {

        int arr[n] ;
        int kill = 0;     //用kill来统计被杀掉的人数
        int count = 1;    //用count来报数
        int people = 0;   //用people来表示下标
        for(int i=0;i<n;i++)
        arr[i]=0;
        //通过count的计数,来结束循环
        while (kill < n - 1) {
            if (arr[people]!=1&&count == m) {
                arr[people] = 1;
                kill++;
                count = 1;
            }
            if (arr[people] != 1) {
                count++;
            }
            people++;
            people %= n;
        }
        //找到数组中是0的元素,并反复;
        for (int i = 0; i < n; i++) {
            if (arr[i] == 0)
                return i + 1;
        }
        return 0;
}

1.6 单链表相关经典算法OJ题5:分割链表

面试题 02.04. 分割链表 - 力扣(LeetCode)https://leetcode.cn/problems/partition-list-lcci/https://leetcode.cn/problems/partition-list-lcci/

方法一:创建两个链表

将大于x的节点和小于x的节点,分开存放,最后再将他们合并。
//分割链表

//方法一:创建两个链表,将节点分开存放
 struct ListNode {
   int val;
   struct ListNode* next;
   
};
 typedef struct ListNode LN;
struct ListNode* partition(struct ListNode* head, int x) {
	if (head == NULL)
		return head;

	//创建大小链表
	LN* big = (LN*)malloc(sizeof(LN));
	LN* bigtail = big;
	bigtail->next = NULL;
	LN* small = (LN*)malloc(sizeof(LN));
	LN* smalltail = small;
	smalltail->next = NULL;

	LN* pcur = head;
	//遍历原链表,将节点分配到新链表中
	while (pcur)
	{
		//val>x,插入到big中
		if ((pcur->val) >= x)
		{
			bigtail->next = pcur;
			bigtail = bigtail->next;
		}
		else
		{
			smalltail->next = pcur;
			smalltail = smalltail->next;
		}
		pcur = pcur->next;
	}
	//注意:此处不要忘记把bigtail的下一个节点置为NULL
	bigtail->next = NULL;
	//将大链表插入到小链表后面,将哨兵位释放掉
	smalltail->next = big->next;
	free(big);
	big = NULL;
	LN* ans = small->next;
	free(small);
	small = NULL;

	return ans;
}

方法二:将大于x的尾插

在原链表上进行修改,将大于等于x的节点尾插,并将原来位置的节点释放。

//方法二:将大于等于x的尾插
struct ListNode {
	int val;
	struct ListNode* next;
};
typedef struct ListNode LN;
struct ListNode* partition(struct ListNode* head, int x) {
	if (head == NULL)
		return head;
	
	//先找到尾节点
	LN* ptail = head;
	while (ptail->next != NULL)
		ptail = ptail->next;
	LN* node = ptail;
	//创建哨兵位,避免对第一个节点分类讨论
	LN* newhead = (LN*)malloc(sizeof(LN));
	newhead->next = head;
	//定义两个指针遍历原链表
	LN* p1 = newhead;
	LN* p2 = head;
	while (p2!=node)
	{
		//p2大于等于x
		if ((p2->val) >= x)
		{
			p1->next = p2->next;
			ptail->next = p2;
			ptail = ptail->next;
		}
		else
		{
			p1 = p2;
		}
		p2 = p2->next;
	}
	ptail->next = NULL;

	LN* ans = newhead->next;
	free(newhead);
	newhead = NULL;

	return ans;
}

方法三:创建新链表,运用尾插和头插

创建新链表将大于等于x的尾插,将小于x的头插。

//方法三:创建新链表,将大于等于x的尾插,将小于x的头插
struct ListNode {
	int val;
	struct ListNode* next;
};
typedef struct ListNode LN;
struct ListNode* partition(struct ListNode* head, int x) {
	if (head == NULL)
		return head;

	//创建头节点
	LN* newhead = (LN*)malloc(sizeof(LN));
	newhead->val = head->val;
	LN* newtail = newhead;
	newtail->next = NULL;

	LN* pcur = head->next;
	while (pcur)
	{
		//创建新节点
		LN* newnode = (LN*)malloc(sizeof(LN));
		newnode->val = pcur->val;
		newnode->next = NULL;

		//进行尾插
		if ((pcur->val) >= x)
		{
			newtail->next = newnode;
			newtail = newtail->next;
		}
		else//头插
		{
			newnode->next = newhead;
			newhead = newnode;
		}
		pcur = pcur->next;
	}
	return newhead;
}

 2.1 链表的倒数第k节点

题:返回链表倒数第k节点的值。

牛客网-题目已下线_牛客网牛客网是互联网求职神器,C++、Java、前端、产品、运营技能学习/备考/求职题库,在线进行百度阿里腾讯网易等互联网名企笔试面试模拟考试练习,和牛人一起讨论经典试题,全面提升你的技术能力https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&&tqId=11167&rp=2&ru=/activity/oj&qru=/ta/coding-interviews/question-rankinghttps://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&&tqId=11167&rp=2&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking

快慢指针 

解析:运用快慢指针,先让快指针走k步,再让快指针和慢指针(从头开始走)一起走,当快指针走到尾的时候,慢指针也刚好走到倒数第k节点处。

//定义链表
typedef struct ListNode
{
	int val;
	struct ListNode* next;
}LN;

//返回链表的倒数第k个节点
LN* ReturnK(LN* list,int k)
{
	if (list == NULL)
		return NULL;

	LN* fast = list;
	LN* slow = list;

	while (k--)
	{
		fast = fast->next;
	}
	while (fast)
	{
		fast = fast->next;
		slow = slow->next;
	}
	return slow->val;
}

2.2 链表的回文结构

链表的回文结构_牛客题霸_牛客网对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为。题目来自【牛客题霸】https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?tpId=49&&tqId=29370&rp=1&ru=/activity/oj&qru=/ta/2016test/question-rankinghttps://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?tpId=49&&tqId=29370&rp=1&ru=/activity/oj&qru=/ta/2016test/question-ranking

找中间节点+反转链表

解析:先找到中间节点,再将中间节点后面的节点反转;用两个指针前和后分别遍历链表进行对比。

//找中间节点
LN* FindMid(LN* list)
{
	LN* fast = list;
	LN* slow = list;

	while (fast && fast->next)
	{
		fast = fast->next->next;
		slow = slow->next;
	}
	return slow;
}
//反转链表
LN* reverseList(LN* list)
{
	LN* last = NULL;
	LN* cur = list;
	LN* pre = list->next;

	while (cur)
	{
		cur->next = last;
		last = cur;
		cur = pre;
		if (pre)
			pre = pre->next;
	}
	return last;
}

//判断链表的回文结构
bool chkPalindrome(LN* list)
{
	if (list == NULL)
		return true;
	//先找到中间节点
	LN* mid = FindMid(list);
	//将中间节点后面的节点反转,返回尾节点
	LN* tail=reverseList(mid);

	LN* head = list;
	//将头尾进行比较
	while (head!=mid)
	{
		if (head->val != tail->val)
			return false;

		head = head->next;
		tail = tail->next;
	}
	return true;
}

2.3 相交链表

160. 相交链表 - 力扣(LeetCode)https://leetcode.cn/problems/intersection-of-two-linked-lists/description/https://leetcode.cn/problems/intersection-of-two-linked-lists/description/

快慢指针 

解析:首先判断是否是相交链表(直接判断最后一个节点相同还是不相同就行),然后分别遍历两个链表,记录两个链表的长度;让长的链表先走多的步数(相对于短的链表),再让长短链表一起走,第一次遇见的位置就是相交节点种的第一个节点。

//相交链表,返回相交链表的第一个节点
LN* getIntersectionNode(struct ListNode* headA, struct ListNode* headB)
{
	//判断是否相交,记录链表长度
	if (headA == NULL || headB == NULL)
		return NULL;
	LN* tailA = headA;
	LN* tailB = headB;
	int Alenth = 0;
	int Blenth = 0;

	while (tailA->next)
	{
		Alenth++;
		tailA = tailA->next;
	}
	while (tailB->next)
	{
		Blenth++;
		tailB = tailB->next;
	}
	if (tailA != tailB)
		return NULL;
	//计算多出的长度,并记录长链表和短链表
	int num = abs(Alenth - Blenth);
	LN* more = headA;
	LN* less = headB;

	if (Blenth > Alenth)
	{
		 more = headB;
		 less = headA;
	}
	//长链表先走
	while (num--)
		more = more->next;
	while (more != less)
	{
		more = more->next;
		less = less->next;
	}
	return less;
}

2.4 环形链表

141. 环形链表 - 力扣(LeetCode)

快慢指针

快慢指针,让快指针先进入圈内,慢指针进入圈后,快指针开始追慢指针。注意:此处快指针一次只能走两步,慢指针一次走一步,如果快指针超过两步可能会导致一直追不到。

//环形链表
bool hasCycle(struct ListNode* head)
{
	if (head == NULL)
		return false;

	LN* fast = head;
	LN* slow = head;

	while (fast&&fast->next)
	{
		fast = fast->next->next;
		slow = slow->next;
		if (fast == slow)
			return true;
	}
	return false;
}

2.4.2 环形链表2

基于环形链表1,此处我们需要返回环形链表的入环位置。

142. 环形链表 II - 力扣(LeetCode)

分析

根据上方图可以看到:当快慢指针第一次相遇的位置距离到入环点还有C-X入环点距离起始位置L恰好也是C-X+nC。此时再让一个指针从起始位置开始走,同时slow继续走,他们两个第一次相遇的位置就是入环点,在此期间slow可能会绕环走多圈(n*C)。

快慢指针+分析

//环形链表2,返回入环节点
struct ListNode* detectCycle(struct ListNode* head)
{
	if (head == NULL)
		return NULL;
	//先找第一次相交位置
	LN* fast = head;
	LN* slow = head;
	while (fast && fast->next)
	{
		fast = fast->next->next;
		slow = slow->next;
		if (fast == slow)
			break;
	}

	if (fast == NULL || fast->next == NULL)
		return NULL;
	else
	{
		//再设置指针从起始位置开始走
		LN* cur = head;
		while (cur != slow)
		{
			cur = cur->next;
			slow = slow->next;
		}
		return cur;
	}

}

2.5 随机链表的复制

138. 随机链表的复制 - 力扣(LeetCode)

方法一:相对尾部的长度

先设置出链表的整体部分(val值和next指针),再通过原链表random指针指向的位置距离NULL的长度来找到节点的位置。

typedef struct Node
{
	int val;
	struct Node* next;
	struct Node* random;
}Node;

//链表的深度复制
struct Node* copyRandomList(struct Node* head)
{
	if (head == NULL)
		return NULL;

	//先复制val和next
	Node* copyhead = (Node*)malloc(sizeof(Node));
	Node* copytail = copyhead;
	copyhead->val = head->val;
	copyhead->next = NULL;
	copyhead->random = NULL;

	int num = 1;  //记录链表长度
	Node* cur = head->next;
	while (cur)
	{
		num++;
		Node* newnode = (Node*)malloc(sizeof(Node));
		newnode->val = cur->val;
		newnode->next = NULL;
		copytail->next = newnode;
		
		copytail = copytail->next;
		cur = cur->next;
	}
	//再找random值
	cur = head;
	Node* copycur = copyhead;
	while(cur)
	{
		int a = num;  //用a来记录相对于尾部NULL的长度
		Node* rand = cur->random;

		while (rand)
		{
			a--;
			rand = rand->next;
		}
		//让新链表去找
		Node* copyrand = copyhead;
		while (a--)
		{
			copyrand = copyrand->next;
		}
		copycur->random = copyrand;

		cur = cur->next;
		copycur = copycur->next;
	}
	return copyhead;
}

方法二

分析:方法一需要多次遍历链表,时间复杂度很高。此处我们进行优化。

如图:直接在原链表的基础上,给每一个节点的后面增加一个相同的节点;当我们找到原链表一个节点的random时,其next就是新建的节点。eg:原13的random是7,而7的next就是设置的新节点。

//链表的深度复制
struct Node* copyRandomList(struct Node* head)
{
	if (head == NULL)
		return NULL;

	//对原链表进行增加
	Node* cur = head;
	while (cur)
	{
		Node* newnode = (Node*)malloc(sizeof(Node));
		newnode->val = cur->val;
		newnode->next = cur->next;
		newnode->random = NULL;

		cur->next = newnode;
		cur = cur->next->next;
	}
	//对复制链表中的random进行修改
	cur = head;
	while (cur)
	{
		Node* rand = cur->random;
		if (rand == NULL)
		{
			cur->next->random = NULL;
		}
		else
		{
			cur->next->random = rand->next;
		}
		cur = cur->next->next;
	}
	//将每一个新增的节点取下来
	cur = head->next;
	Node* copyhead = cur;
	Node* copytail = copyhead;
	

	while (cur&&cur->next)
	{
		cur = cur->next->next;
		copytail->next = cur;
		copytail = copytail->next;
	}
	copytail->next = NULL;
	return copyhead;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

半桔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值