二、链表算法学习(代码随想录学习)

1. 基础知识

链表基础
链表定义(有构造函数版本)

// 单链表
struct ListNode {
    int val;  // 节点上存储的元素
    ListNode *next;  // 指向下一个节点的指针
    ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
};

如果不定义构造函数使用默认构造函数的话,在初始化的时候就不能直接给变量赋值!
在这里插入图片描述
删除结点:
在这里插入图片描述

2.移除链表元素

leetcode链接
思路一: 本题是无头节点的链表,未方便对首元结点进行操作,可以构造一个头节点指向首元结点,对整个链表进行迭代删除操作。(每次删除操作需释放空间)

class Solution {
public:
	ListNode* removeElements(ListNode* head, int val) {
		// 设置一个虚拟头节点,本题的链表是无头结点链表
		ListNode *dummyHead = new ListNode(0);
		dummyHead->next = head;
		ListNode *p = dummyHead;
		while (p->next != nullptr) {
			if (p->next->val == val) {  // 如果p->next的val满足条件,则删除p->next
				ListNode *tmp = p->next;
				p->next = tmp->next;
				delete tmp;   // 释放被删除结点空间
			}
			else
				p = p->next;
		}
		head = dummyHead->next;
		delete dummyHead;
		return head;
	}
};

思路二: 递归方法,每次递归使当前链表符合条件。每次对首元结点进行判断,若等于val,则删除当前结点;反之,则保留。removeElements得到一个满足题目的子链表。

class Solution {
public:
	ListNode* removeElements(ListNode* head, int val) {  // 每次递归得到一个符合条件的子链表
		if (head == nullptr)  // 跳出递归条件
			return nullptr;
		if (head->val == val) {  //若当前首元结点符合删除条件,删除当前结点
			ListNode *newHead = removeElements(head->next, val);
			delete head;
			return newHead;
		}
		else {  // 若不需要删除,则保留当前结点,判断后续子序列
			head->next = removeElements(head->next, val);
			return head;
		}
	}
};

3.设计链表

leetcode链接
本题需要自行构造链表和私有成员
在这里插入图片描述
在这里插入图片描述
使用单链表实现,其他均为链表基本操作:

class MyLinkedList {
public:
	struct ListNode {
		int val;
		struct ListNode* next;
		ListNode(int val) :val(val), next(nullptr) {}  // 设置缺省值
	};

	MyLinkedList() {
		_size = 0;
		_dummyHead = new ListNode(0);  // 生成一个空结点作头节点
	}

	int get(int index) {
		if (index > (_size - 1) || index < 0)
			return -1;
		ListNode *cur = _dummyHead->next;
		int curIndex = 0;
		while (curIndex++ != index)
			cur = cur->next;
		return cur->val;
	}

	void addAtHead(int val) {  // 头插
		ListNode *cur = new ListNode(val);
		cur->next = _dummyHead->next;
		_dummyHead->next = cur;
		_size++;
		return;
	}

	void addAtTail(int val) {  // 尾插
		ListNode *tail = _dummyHead;
		while (tail->next != nullptr)
			tail = tail->next;
		ListNode *cur = new ListNode(val);
		tail->next = cur;
		_size++;
		return;
	}

	void addAtIndex(int index, int val) {
		if (index > _size || index < 0)
			return;
		ListNode *cur = new ListNode(val);
		ListNode *ins = _dummyHead;  // ins指向插入位置前一个结点(尾插)
		int i = -1;
		while (i++ != (index - 1))
			ins = ins->next;
		cur->next = ins->next;
		ins->next = cur;
		_size++;
		return;
	}

	void deleteAtIndex(int index) {
		if (index > (_size - 1) || index < 0)
			return;
		ListNode *pre = _dummyHead;
		ListNode *cur = _dummyHead->next;
		int i = 0;
		while (i++ != index) {
			pre = cur;
			cur = cur->next;
		}
		pre->next = cur->next;
		delete cur;
		_size--;
		return;
	}

private:  // 设置 "_" 开头,表明是私有成员(是一种代码风格)
	int _size;
	ListNode* _dummyHead; // 虚拟头节点
};

4. 反转链表

leetcode链接
思路1: 创建一个新的链表,从之前的链表中每次摘出首元元素,头插法插入新链表(使用哨兵结点)

class Solution {
public:
	ListNode* reverseList(ListNode* head) {
		// 分别创建两个头节点,为反转前后的两个头节点
		ListNode *dummyHead1 = new ListNode(0,head); 
		ListNode *dummyHead2 = new ListNode(0);
		ListNode *cur = head;
		//  每次从链表1摘出一个元素,使用头插法插入链表2
		while (cur != nullptr) {   
			dummyHead1->next = cur->next;
			cur->next = dummyHead2->next;
			dummyHead2->next = cur;
			cur = dummyHead1->next;
		}
		cur = dummyHead2->next;
		delete dummyHead1;
		delete dummyHead2;
		return cur;
	}
};

思路2: 通过双指针直接将链表反转(pre表示已翻转的部分链表,cur指向待反转剩余链表的首元元素。每次将首元元素摘下头插),不使用哨兵头节点

class Solution {
public:
	ListNode* reverseList(ListNode* head) {
		// pre必须初始化,不然要报错
		ListNode *pre = NULL;
		ListNode *cur = head;
		ListNode *tmp;  // tmp保存cur->next
		while (cur) {
			tmp = cur->next;
			cur->next = pre;
			pre = cur;
			cur = tmp;
		}
		return pre;		
	}
};

思路3: 使用递归的方法,思路与思路2一致。pre每次得到已反转子链表,cur指向未反转首元元素

class Solution {
public:
	ListNode* reverse(ListNode* pre, ListNode* cur) {
		if (cur == NULL)
			return pre;
		ListNode *tmp = cur->next;
		cur->next = pre;
		return reverse(cur, tmp);
	}


	ListNode* reverseList(ListNode* head) {
		return reverse(NULL, head);
	}
};

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

leetcode链接
思路一:递归,每轮递归交换两个元素,并添加进已交换的链表中

class Solution {
public:
	ListNode* swap(ListNode* pre, ListNode* cur) {
		if (cur == NULL || cur->next == NULL)
			return cur;
		ListNode *rear = cur->next;
		ListNode *tmp = rear->next;
		cur->next = rear->next;
		rear->next = cur;
		pre->next = rear;
		return swap(cur, tmp);
	}


	ListNode* swapPairs(ListNode* head) {
		ListNode* dummyHead = new ListNode(0);
		dummyHead->next = head;
		swap(dummyHead, head);
		return dummyHead->next;
	}
};

思路二:递归写法2,swapPairs每次得到一个满足条件的子链表(链表的后半部分),交换该子链表的前两个元素,并继续返回新的子链表

class Solution {
public:
	ListNode* swapPairs(ListNode* head) {
		if (head == nullptr || head->next == nullptr)
			return head;
		// 将head与head->next交换
		ListNode *newHead = head->next;
		head->next = swapPairs(newHead->next);  // 将后续子链表操作,使其符合条件,接入在head之后
		newHead->next = head;
		return newHead;
	}
};

6. 删除链表的倒数第N个结点

leetcode链接
思路:使用快慢指针,快指针比慢指针快N个元素,当快指针遍历到尾结点时,慢指针到达应删除节点,删除该节点。

class Solution {
public:
	ListNode* removeNthFromEnd(ListNode* head, int n) {
		if (head->next == nullptr)
			return nullptr;
		// 创建一个虚拟头节点,方便操作
		ListNode *dummyHead = new ListNode(0,head);
		ListNode *pre = dummyHead;
		ListNode *curPre = dummyHead;
		ListNode *cur = dummyHead->next;  // cur为慢指针,最后指向应删除节点
		while (n--)  // 将快指针移动到第N个位置
			pre = pre->next;
		while (pre->next) {  // 向后遍历快指针
			pre = pre->next;
			curPre = cur;
			cur = cur->next;
		}
		curPre->next = cur->next;
		delete cur;
		head = dummyHead->next;
		delete dummyHead;
		return head;
	}
};

也可将快指针移动n+1个位置,则不需要多设置删除节点的前置节点的指针,而是直接找到该前置节点指针

7. 链表相交

思路: 定义两个指针分别指向两个链表的头节点,将长链表指针移动两链表的差值,使二者剩余结点相同。循环比较两个指针是否相等,直至遍历到尾结点或得到相同指针。

class Solution {
public:
	ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
		// 为A,B创建虚拟头结点
		ListNode *dummyHeadA = new ListNode(0);
		dummyHeadA->next = headA;
		ListNode *dummyHeadB = new ListNode(0);
		dummyHeadB->next = headB;
		int countA=0, countB=0;
		ListNode *cur = dummyHeadA;
		while (cur->next) {
			countA++;
			cur = cur->next;
		}
		cur = dummyHeadB;
		while (cur->next) {
			countB++;
			cur = cur->next;
		}

		int gap = abs(countA - countB);
		// 将长的链表的当前指针移动二者之间的差值,保证后续长度相等
		if (countA > countB) {
			cur = dummyHeadA->next;
			while (gap--)
				cur = cur->next;
		}
		else {
			cur = dummyHeadB->next;
			while (gap--)
				cur = cur->next;
		}
		// 找到短链表
		ListNode *shortCur = countA <= countB ? dummyHeadA->next : dummyHeadB->next;
		while (cur) {  // 循环比较指针是否相等
			if (cur == shortCur)
				break;
			// 将两边的指针向后移
			cur = cur->next;
			shortCur = shortCur->next;
		}
		delete dummyHeadA, dummyHeadB;
		return cur;
	}
};

8.环形链表Ⅱ

leetcode链接
学习链接:把环形链表讲清楚

判断是否有环: 使用快慢指针,快指针每次移动两格,慢指针每次移动一格。当快慢指针相遇时,则说明有环,则必定在环中相遇。

问:为什么若有环,则快慢指针必定相遇?
答:因为快指针先进入环,在环中若干圈后,慢指针进入环。而快指针每次移动两格,慢指针每次移动一格,则快指针每次以相对1格的速度接近慢指针。因为1必定是环中元素数量的因数,所以必定会相遇。

找到环的入口: 利用数学公式,判断环的入口。假设头节点到环的入口节点的节点数为x(包含头节点,不包含入口节点),入口节点到快慢指针相遇节点的节点数为(既不包含入口节点,又不包含相遇节点),相遇节点到入口节点的节点数为z(即包含相遇节点,又包含入口节点)。
在这里插入图片描述
那么相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。

因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:
即2 * (x+y) = x + y+n(y+z)

经整理: x = (n - 1) (y + z) + z
表示快指针转了n-1圈后,相遇节点到入口节点的距离 = 慢指针到入口节点的距离

问:为什么slow指针相遇时,移动的距离是x+y,而不是x+y+n(y+z),即多转了n圈
答:考虑到slow指针走一圈的位移,fast指针能位移2圈,因此,slow指针未走完一圈即会相遇。
在这里插入图片描述

class Solution {
public:
	ListNode *detectCycle(ListNode *head) {
		// 定义快慢指针
		ListNode *fast = head;
		ListNode *slow = head;
		while(fast != nullptr && fast->next != nullptr){
			fast = fast->next->next;
			slow = slow->next;
			if (fast == slow) {  //若是有环,则会相等
				ListNode *cur = head;
				// 同时从起点和相遇节点开始遍历,若二者相遇,则一定是在入口节点
				while (cur != slow) {
					cur = cur->next;
					slow = slow->next;
				}
				return cur;
			}
		}
		return nullptr;
	}
};

9.总结

链表总结
链表总结
图片来源: 代码随想录知识星球 (opens new window)成员:海螺人

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值