206. 反转链表
![![[Pasted image 20241025215801.png]]](https://i-blog.csdnimg.cn/direct/aa0f2c4001924c17bc051db88de10c0d.png)
用两个指针
![![[Pasted image 20241025215926.png]]](https://i-blog.csdnimg.cn/direct/2caa4b14dbf948f3a6f319712660f508.png)
p1指向空,p2指向第一个节点
![![[Pasted image 20241025220000.png]]](https://i-blog.csdnimg.cn/direct/13632ecb6261439d8e9af6d268e938fa.png)
p2的next指向p1,把方向调过来
因为p2的next指向p1,会丢掉后面的节点,所以需要三个节点
前两个指针是为了反转,后一个指针是为了找到下一个节点
![![[Pasted image 20241025220955.png]]](https://i-blog.csdnimg.cn/direct/ad7ed48effa44ceda571e3c2a9b778f4.png)
p2给给p1,p3给给p2,p3指向下一个
![![[Pasted image 20241025221026.png]]](https://i-blog.csdnimg.cn/direct/4a38c3cf19b0466588fa8c47327916ea.png)
![![[Pasted image 20241025221110.png]]](https://i-blog.csdnimg.cn/direct/0bbf211479574d4a9799029e24bb7f51.png)
![![[Pasted image 20241025221130.png]]](https://i-blog.csdnimg.cn/direct/023f5ff04bd844cb92885c9bc0617352.png)
p2指向NULL,循环结束
![![[Pasted image 20241025221257.png]]](https://i-blog.csdnimg.cn/direct/6306a2e6068648e2bcb5e3b24b8fa9f9.png)
最后p1指向的就是链表的头
struct ListNode* reverseList(struct ListNode* head) {struct ListNode* p1, *p2, *p3;p1 = NULL;p2 = head;if (p2)p3 = p2->next;while (p2){p2->next = p1;p1 = p2;p2 = p3;if (p3)p3 = p3->next;}return p1;
}
- 开始需要判断p2是否为空,如果p2为空,直接返回p1
- 循环的时候需要判断p3是否为空,如果p3为空,就不需要再往后走了
思路2
头插法
![![[Pasted image 20241027154804.png]]](https://i-blog.csdnimg.cn/direct/546f5ce39bc54959aa291525bbac76ad.png)
创建一个newhead指针,一开始指向NULL,从上面的节点下来头插即可
![![[Pasted image 20241027154944.png]]](https://i-blog.csdnimg.cn/direct/e8cee42eee25466795b9579759bda574.png)
用next保存cur的下一个,将cur的next指向newhead,newhead指向cur
![![[Pasted image 20241027155048.png]]](https://i-blog.csdnimg.cn/direct/f85cf4df8d024a8690a3ab0a1b76ddf1.png)
再把next给给cur,next继续往下走
![![[Pasted image 20241027155145.png]]](https://i-blog.csdnimg.cn/direct/3493c6b6e86d44cc98e5909eeafff738.png)
继续头插,把cur的next指向newhead
![![[Pasted image 20241027155212.png]]](https://i-blog.csdnimg.cn/direct/b9d5b6d0f85e435fb208daa5e7d5fc3c.png)
cur给给newhead
![![[Pasted image 20241027155301.png]]](https://i-blog.csdnimg.cn/direct/c1334765856d4fe4bdb7ec63be503815.png)
next给给cur,next继续往后遍历,直到cur为空结束
struct ListNode* reverseList(struct ListNode* head) {struct ListNode* cur = head;struct ListNode* newhead = NULL;while (cur){struct ListNode* next = cur->next;cur->next = newhead;newhead = cur;cur = next;}return newhead;
}
203. 移除链表元素
删除所有值等于val的节点
通过cur指针遍历链表中的节点,遍历的过程中,用prev指针记录cur的前一个节点
![![[Pasted image 20241026134640.png]]](https://i-blog.csdnimg.cn/direct/bfb90214f3d6467c81677b5a102c5d84.png)
将prev的next指向cur的next,再删除cur
![![[Pasted image 20241026134757.png]]](https://i-blog.csdnimg.cn/direct/9c29e358005d4ed6b25e61f8a72135ae.png)
![![[Pasted image 20241026134809.png]]](https://i-blog.csdnimg.cn/direct/b99fc18004b54e12a74c1a1da320a2c1.png)
再把cur往后挪
![![[Pasted image 20241026134823.png]]](https://i-blog.csdnimg.cn/direct/0d32964e8d8e44838a162928e98ad317.png)
直到cur指向NULL,遍历结束
如果需要处理头节点,套入循环内会比较麻烦
struct ListNode* removeElements(struct ListNode* head, int val) {struct ListNode* prev = NULL, *cur = head;while (cur){if (cur->val == val){//删除if (cur == head) //如果头节点需要删除,将cur往后挪,直接删除头节点{head = cur->next;free(cur);cur = head;}else{prev->next = cur->next;free(cur);cur = prev->next;}}else{prev = cur;cur = cur->next;}}return head;
}
思路2
遍历原链表,把不是val的节点,尾插到新链表
![![[Pasted image 20241026142211.png]]](https://i-blog.csdnimg.cn/direct/5f43d2d55d3e4018a7cd36da41c9cc59.png)
需要用next指针找下一个节点,否则删除了cur,链表会断开
这时候往新链表尾插,时间复杂度会提高,因为需要找尾
只要创建一个tail指针,这样就不需要找尾
![![[Pasted image 20241026142645.png]]](https://i-blog.csdnimg.cn/direct/7a34d3a80e2e4d5e9cbcefdcde3a3da4.png)
当头节点需要删除时,直接往后遍历,直接删除头节点
struct ListNode* removeElements(struct ListNode* head, int val) {struct ListNode* cur = head;struct ListNode* newhead = NULL, * tail = NULL;while (cur){if (cur->val == val){struct ListNode* del = cur;cur = cur->next;free(del);}else{if (tail == NULL){newhead = tail = cur;}else{tail->next = cur;tail = tail->next;}cur = cur->next;}}if (tail)tail->next = NULL;return newhead;
}
最后将一个不为val的节点,尾插到新链表,但是这个节点的next还是指向原链表的下一个节点,如果这个下一个节点不是要删除的,继续往后尾插就ok,如果下一个节点要删除,这个尾插到新链表的节点指向的就不是空
但是不能每次尾插之后,把tail的next置为NULL,这样cur会指向空,不能往后遍历
要在循环结束后,给tail的next置空。
这时候如果head是空,cur就是空,循环就不会进去,newhead和tail就是空。
所以需要判断tail是否为空
876. 链表的中间结点
![![[Pasted image 20241026194539.png]]](https://i-blog.csdnimg.cn/direct/bcfafbc1122143458bc27387d47aefc7.png)
若链表中的节点数是奇数个,返回中间的那一个
若链表中的节点数是偶数个,返回中间两个的第二个
思路1
先遍历一遍,算长度,再遍历一遍,找到中间的节点
思路2
快慢指针
定义一个慢指针,和快指针,慢指针一次走一步,快指针一次走两步,快指针走到结尾的时候,慢指针走的是快指针的一半,恰好就在链表的中间
奇数个
![![[Pasted image 20241026194944.png]]](https://i-blog.csdnimg.cn/direct/b26186077d764a2fb8e498a74c7061ab.png)
![![[Pasted image 20241026195003.png]]](https://i-blog.csdnimg.cn/direct/e45ed248216e40aa8a3a9ba36034bee0.png)
![![[Pasted image 20241026195034.png]]](https://i-blog.csdnimg.cn/direct/170ec943e7b744a29fb50ead170a3b40.png)
偶数个
![![[Pasted image 20241026195116.png]]](https://i-blog.csdnimg.cn/direct/2756d3251d62423bb666656acd691213.png)
![![[Pasted image 20241026195129.png]]](https://i-blog.csdnimg.cn/direct/8091ff721a534daebf56cf4fe743ff2a.png)
![![[Pasted image 20241026195202.png]]](https://i-blog.csdnimg.cn/direct/ae3dc3409087481b9a16ea1a5147447e.png)
![![[Pasted image 20241026195215.png]]](https://i-blog.csdnimg.cn/direct/cd35ae311d6c4a5bb96f53cc55400d46.png)
struct ListNode* middleNode(struct ListNode* head) {struct ListNode* slow = head, *fast = head;while (fast && fast->next){slow = slow->next;fast = fast->next->next;}return slow;
}
面试题 02.02. 返回倒数第 k 个节点
快指针先走k步,然后再同时走,走到fast为NULL结束,slow就是倒数第k个
![![[Pasted image 20241027091517.png]]](https://i-blog.csdnimg.cn/direct/1150bc0ec0ad4bca862f191eadeadb44.png)
k=3
![![[Pasted image 20241027091536.png]]](https://i-blog.csdnimg.cn/direct/d95028568d2b49589d26b33c776b3a01.png)
fast先走3步,之后同时走
![![[Pasted image 20241027091615.png]]](https://i-blog.csdnimg.cn/direct/d854f7331e2248e0a187858cc60b4769.png)
当fast指向NULL时,slow指向倒数第3个
int kthToLast(struct ListNode* head, int k) {struct ListNode* slow = head;struct ListNode* fast = head;for (k--){//链表没有k步长,倒数第k个就算是空if (fast == NULL){return NULL;}fast = fast->next;}while (fast){fast = fast->next;slow = slow->next;}return slow->val;
}
k–,走k次
–k,走k-1次
21. 合并两个有序链表
取小的尾插
用两个指针list1和list2分别遍历两个有序链表
为了方便尾插定义两个指针head和tail,一开始都是空
尾插时,将tail给给tail的next,list2还是给给list2的next继续往后遍历
![![[Pasted image 20241026220442.png]]](https://i-blog.csdnimg.cn/direct/85fac77b136c41528b40030f45e50df8.png)
取小的尾插,list2往后走
![![[Pasted image 20241026220819.png]]](https://i-blog.csdnimg.cn/direct/04282cc776034ca9b8a920d388d11b05.png)
取小的尾插,list1往后走
![![[Pasted image 20241026221710.png]]](https://i-blog.csdnimg.cn/direct/70ba52946d484fd38f4e8d6caa624c45.png)
tail的next赋给tail
![![[Pasted image 20241027091215.png]]](https://i-blog.csdnimg.cn/direct/a9d07fd0ace641919a25374587af26d8.png)
如果有一个list指针指向空,就结束了,剩下一个链表,直接连接到后面
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {if (list1 == NULL)return list2;if (list2 == NULL)return list1;struct ListNode* head = NULL, *tail = NULL;while (list1 && list2){if (list1->val < list2->val){if (tail == NULL){head = tail = list1;}else{tail->next = list1;tail = tail->next;}list1 = list1->next;}else{if (tail == NULL){head = tail = list2;}else{tail->next = list2;tail = tail->next;}list2 = list2->next;}}if (list1){tail->next = list1;}if (list2){tail->next = list2;}return head;
}
思路2
加入哨兵位,方便尾插,最后把哨兵位free掉
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {if (list1 == NULL)return list2;if (list2 == NULL)return list1;struct ListNode* head = NULL, *tail = NULL;head = tail = (struct ListNode*)malloc(sizeof(struct ListNode));while (list1 && list2){if (list1->val < list2->val){tail->next = list1;tail = tail->next;list1 = list1->next;}else{tail->next = list2;tail = tail->next;list2 = list2->next;}}if (list1){tail->next = list1;}if (list2){tail->next = list2;}struct ListNode* del = head;head = head->next;free(del);return head;
}
这样尾插的时候可以不用判断tail是否为空