LeetCode刷题总结:(3)链表相关问题

本文精选了多个链表相关的经典算法题目,包括删除重复元素、分隔链表、奇偶链表、两数相加、删除指定节点、合并有序链表、两两交换节点、反转链表及其部分区间等,详细解析了每道题目的解题思路和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

与链表相关的问题一般都是通过维护多个节点指针,在链表中穿针引线来解决的。另外一个常用的技巧是创建一个dummy指针,使之链接到root。

 

83. 删除排序链表中的重复元素

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

示例 1:

输入: 1->1->2
输出: 1->2

示例 2:

输入: 1->1->2->3->3
输出: 1->2->3
struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

class Solution {
public:
	ListNode* deleteDuplicates(ListNode* head) {

		ListNode* resHead = head;
		ListNode* curNode = head;
		while (curNode != NULL&& curNode->next != NULL) {
			// 如果当前节点的val和下一节点的val相等,那么将当前节点链接到下下个节点,意思就是抛弃了相同的下一个节点
			if (curNode->val == curNode->next->val) {
				curNode->next = curNode->next->next;
			}
			else {	// 否则就是下一节点和当前节点不相同,那么curNode移位
				curNode = curNode->next;
			}
		}

		return head;
	}
};

 

86. 分隔链表

给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。

你应当保留两个分区中每个节点的初始相对位置。

示例:

输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5
// 思路: 就是将小于x和不小于x的划分到两个新创建的列表,最后将两个列表链接起来即可,缺点是申请了多余的空间,时间复杂度为n
class Solution1 {
public:
	ListNode* partition(ListNode* head, int x) {

		ListNode* res = new ListNode(0);
		res->next = head;
		ListNode* smallCurNode = res;
		ListNode* bigHead = new ListNode(0);
		ListNode* bigCurNode = bigHead;
		ListNode* curNode = head;
		while (curNode != NULL) {
			// 小于x,链接到小段
			if (curNode->val < x) {
				smallCurNode->next = new ListNode(curNode->val);
				smallCurNode = smallCurNode->next;
			}
			else {	// 不小于x,链接到大段
				bigCurNode->next = new ListNode(curNode->val);
				bigCurNode = bigCurNode->next;
			}
			curNode = curNode->next;
		}
		// 两个段链接起来
		smallCurNode->next = bigHead->next;

		return res->next;
	}
};

 

328. 奇偶链表

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 1:

输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL

示例 2:

输入: 2->1->3->5->6->4->7->NULL 
输出: 2->3->6->7->1->5->4->NULL

说明:

  • 应当保持奇数节点和偶数节点的相对顺序。
  • 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
// 思路: 和上一道题相似,但是要在原来的空间里的相应节点做后继指针的跳变,同理,还是记录好偶数的头结点,
// 然后奇偶列表分别做各自的连接,待循环完成,将奇偶列表链接起来,维护好奇偶列表最后一个节点的额指针变量
class Solution2 {
public:
	ListNode* oddEvenList(ListNode* head) {

		if (head == NULL || head->next == NULL)
			return head;

		ListNode* oddTail = head;
		ListNode* evenTail = head->next;
		ListNode* evenHead = evenTail;
		while (evenTail != NULL && evenTail->next!=NULL) {
			// 偶数段后继节点为奇数,所以奇数段连接到此节点
			oddTail->next = evenTail->next;
			// 奇数段tail后移
			oddTail = oddTail->next;
			// 偶数段后继节点连接至下一个偶数节点
			evenTail->next = oddTail->next;
			// 偶数段tail后移
			evenTail = evenTail->next;
		}

		// 奇数段和偶数段连接
		oddTail->next = evenHead;

		return head;
		
	}
};

 

2. 两数相加

给定两个非空链表来表示两个非负整数。位数按照逆序方式存储,它们的每个节点只存储单个数字。将两数相加返回一个新的链表。

你可以假设除了数字 0 之外,这两个数字都不会以零开头。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
class Solution3 {
public:
	ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {

		ListNode* curL1 = l1;
		ListNode* curL2 = l2;

		ListNode* res = new ListNode(0);
		ListNode* head = res;

		int inHeight = 0;	// 进位
		while (curL1 != NULL || curL2 != NULL || inHeight != 0) {
			int numL1 = 0;		// l1当前位的值
			int numL2 = 0;		// l2当前位的值
			if (curL1 != NULL) {
				numL1 = curL1->val;
				curL1 = curL1->next;		// 循环变量维护
			}
			if (curL2 != NULL) {
				numL2 = curL2->val;
				curL2 = curL2->next;		// 循环变量维护
			}

			// 计算当前位的数字
			int numNow = numL1 + numL2 + inHeight;
			// 计算进位
			inHeight = numNow / 10;
			// 得到当前位的最终数字
			numNow = numNow % 10;

			// 循环变量维护
			res->next = new ListNode(numNow);
			res = res->next;
		}

		return head->next;
	}
};

 

203. 删除链表中的节点

删除链表中等于给定值 val 的所有节点。

示例:

输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5
// 思路: 太简单
class Solution4 {
public:
	ListNode* removeElements(ListNode* head, int val) {

		ListNode* dummmyHead = new ListNode(0);
		dummmyHead->next = head;
		ListNode* curNode = dummmyHead;

		while (curNode->next != NULL) {

			if (curNode->next->val == val) {
				ListNode* delNode = curNode->next;
				curNode->next = curNode->next->next;
				delete delNode;
			}
			else
				curNode = curNode->next;
		}

		return dummmyHead->next;
	}
};

 

21. 合并两个有序链表

将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

示例:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
class Solution6 {
public:
	ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {

		ListNode* dummmyHead = new ListNode(0);
		ListNode* curNode = dummmyHead;
		ListNode* curNodeL1 = l1;
		ListNode* curNodeL2 = l2;
		while (curNodeL1 != NULL || curNodeL2 != NULL) {
			// merge
			if (curNodeL1 == NULL) {
				curNode->next = new ListNode(curNodeL2->val);
				curNode = curNode->next;
				curNodeL2 = curNodeL2->next;
			}
			else if (curNodeL2 == NULL) {
				curNode->next = new ListNode(curNodeL1->val);
				curNode = curNode->next;
				curNodeL1 = curNodeL1->next;
			}
			else if (curNodeL1->val <= curNodeL2->val) {
				curNode->next = new ListNode(curNodeL1->val);
				curNode = curNode->next;
				curNodeL1 = curNodeL1->next;
			}
			else {
				curNode->next = new ListNode(curNodeL2->val);
				curNode = curNode->next;
				curNodeL2 = curNodeL2->next;
			}
		}

		return dummmyHead->next;
	}
};

 

24. 两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

示例:

给定 1->2->3->4, 你应该返回 2->1->4->3.

说明:

  • 你的算法只能使用常数的额外空间。
  • 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
// 思路: 维护好pre,node1,node2,next四个指针,代表的意义分别为待翻转节点对的前一节点,node1和node2为待翻转的节点对,next为待翻转节点对的后继节点
class Solution7 {
public:
	ListNode* swapPairs(ListNode* head) {

		ListNode* dummmyHead = new ListNode(0);
		dummmyHead->next = head;

		ListNode* pre = dummmyHead;
		// pre的后继两个节点都有值才需要翻转,不都有值那么不需要操作了
		while (pre->next && pre->next->next) {
			ListNode* node1 = pre->next;
			ListNode* node2 = node1->next;
			ListNode* next = node2->next;

			pre->next = node2;
			node2->next = node1;
			node1->next = next;

			pre = node1;
		}

		return dummmyHead->next;
	}
};

 

206. 反转链表

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

// 思路: 维护好 pre,cur,next三个指针,分别指向当前待操作节点的前一节点,当前节点,后一节点
//		 循环不变量为cur,直到cur为空指针,跳出循环
class Solution {
public:
	ListNode* reverseList(ListNode* head) {

		ListNode* pre = NULL;
		ListNode* cur = head;
		while (cur != NULL) {
			ListNode* next = cur->next;

			// 翻转的同时维护好三个指针
			cur->next = pre;
			pre = cur;
			cur = next;
		}

		return pre;
	}
};

 

92. 反转链表 II

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。

说明:
1 ≤ m ≤ n ≤ 链表长度。

示例:

输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
// 思路: 分为两个步骤,第一步是找到m结点所在位置,第二步就是进行反转直到n结点。反转的方法就是每读到一个结点,把它插入到m结点前面位置,
// 然后m结点接到读到结点的下一个。总共只需要一次扫描,所以时间是O(n),只需要几个辅助指针,空间是O(1)。
class Solution1 {
public:
	ListNode* reverseBetween(ListNode* head, int m, int n) {
		
		ListNode* res = new ListNode(0);
		res->next = head;
		ListNode* prestart = res;
		ListNode* start = head;
		for (int i = 1; i < m; i++) {
			prestart = start;
			start = start->next;
		}

		// 翻转,将当前节点的后一节点插入到翻转段的最前面
		for (int i = m; i < n; i++) {
			// 记录当前节点的后一节点
			ListNode* temp = start->next;
			// 将当前节点后继到后一节点的后一节点
			start->next = temp->next;
			// 将temp节点,即当前节点本来的后一节点插入到翻转段的最前面
			temp->next = prestart->next;
			prestart->next = temp;
		}

		return res->next;
	}
};

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值