目录
1、移除链表元素
思路一:使用两个指针,在原来的链表上进行指向的修改。
错误反思:对空指针的引用
改正:头删时,直接改头指针head,使其指向下一个结点。
struct ListNode* removeElements(struct ListNode* head, int val) { struct ListNode *pre, *cur; pre = NULL; cur = head; while(cur) { if(cur->val != val) { pre = cur; cur = cur->next; } else { if(pre == NULL) //头删 --> 改头指针 { head = cur->next; free(cur); cur = head; } else { pre->next = cur->next; free(cur); cur = pre->next; } } } return head; }
思路二:将值不是val的结点,尾插到一个新的链表中。
易错一:最后tail->next要置空!
易错二:头删时要防止对空指针的引用。
struct ListNode* removeElements(struct ListNode* head, int val) { if(head == NULL) { return NULL; } struct ListNode *newhead, *tail, *cur; newhead = tail = NULL; cur = head; while(cur) { if(cur->val != val) { //尾插 if(tail == NULL) { newhead = tail = cur; } else { tail->next = cur; tail = tail->next; } cur = cur->next; } else { struct ListNode *next = cur->next; free(cur); cur = next ; } } if(tail) { tail->next = NULL; } return newhead; }
2、反转链表
思路一:头插法——每一个结点都进行头插
struct ListNode* reverseList(struct ListNode* head) { struct ListNode *newhead,*cur; newhead = NULL; cur = head; while(cur) { struct ListNode *next = cur->next; cur->next = newhead; newhead = cur; cur = next; } return newhead; }
思路二:三指针翻转法——使用三个相邻指针,原地调转结点指向。
struct ListNode* reverseList(struct ListNode* head) { if (head == NULL) { return NULL; } struct ListNode *n1, *n2, *n3; n1 = NULL; n2 = head; n3 = head->next; while (n2) { n2->next = n1; n1 = n2; if (n3 == NULL) { return n2; } else { n2 = n3; n3 = n3->next; } } return n2; }
3、链表的中间结点
思路一:(传统玩法,容易想到,但效率不高)
遍历两遍,第一遍求出总结点个数,第二遍历到中间节点返回。
struct ListNode* middleNode(struct ListNode* head) { //1、先统计总结点的个数 //奇数个有1个中间结点,偶数个有2个中间结点,返回第二个中间结点 //2、遍历链表,奇偶都返回count/2 + 1处的结点 int count = 0; int mid = 0; struct ListNode* cur = head; if (cur->next == NULL) { return cur; } else { while (cur) { count++; cur = cur->next; } cur = head; while (cur) { mid++; if (mid == count / 2 + 1) { break; } cur = cur->next; } return cur; } }
思路二:(只需遍历一遍)
使用快慢指针,slow和fast,slow一次走一步,fast一次走两步,当fast ->next == NULL或fast == NULL时,slow则位于中间位置。
错误反思:
一处次序不同,结果就截然不同!这是因为&&是从左至右执行,当fast为NULL时,fast ->
next 就对空指针进行了引用。
4、合并两个有序链表
错误反思:没有对malloc开辟的结点的next指针进行初始化!
- 没有对开辟的guard结点的next进行置空!这会导致当两个链表都为空时,返回的不一定是空。
- 如果next指针没有初始化为NULL,它的值可能是任意的,这可能导致程序不确定的行为。如果next指针指向了不受控制的内存区域,这可能会导致程序崩溃或安全漏洞。
改正:
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) { struct ListNode* cur1 = list1; struct ListNode* cur2 = list2; struct ListNode* guard = NULL, *tail = NULL; guard = tail = (struct ListNode*)malloc(sizeof(struct ListNode)); tail->next = NULL; while (cur1 && cur2) { if (cur1->val < cur2->val) { tail->next = cur1; tail = tail->next; cur1 = cur1->next; } else { tail->next = cur2; tail = tail->next; cur2 = cur2->next; } } if (cur1) { tail->next = cur1; } if (cur2) { tail->next = cur2; } struct ListNode* head = guard->next; free(guard); guard = NULL; return head; }
5、链表分割
错误反思:
- 没有将为尾指针的next置空,导致形成循环链表。
- 开辟的内存空间没有释放,导致内存泄漏。虽然这在oj上检测不出来,但是面试的时候这反映的是你代码能力的严谨性与规范性,面试官会考察!
改正:
class Partition { public: ListNode* partition(ListNode* pHead, int x) { struct ListNode* smallhead, * bighead = NULL; struct ListNode* smalltail, * bigtail = NULL; smallhead = smalltail = (struct ListNode*)malloc(sizeof(struct ListNode)); bighead = bigtail = (struct ListNode*)malloc(sizeof(struct ListNode)); smalltail->next = NULL; bigtail->next = NULL; struct ListNode* cur = pHead; while (cur) { if (cur->val < x) { smalltail->next = cur; smalltail = smalltail->next; } else { bigtail->next = cur; bigtail = bigtail->next; } cur = cur->next; } smalltail->next = bighead->next; bigtail->next = NULL; pHead = smallhead->next; free(smallhead); free(bighead); return pHead; } };
6、链表的回文结构
思路:
1、先找中间结点
2、从中间结点开始,将后半段逆置(可调用反转链表函数)
3、比较前半段和后半段是否相等。
class PalindromeList { public: bool chkPalindrome(ListNode* head) { struct ListNode* mid = findmidnode(head); struct ListNode* midhead = reverselist(mid); while(midhead) { if(head->val != midhead->val) { return false; } else { head = head->next; midhead = midhead->next; } } return true; } };
7、传二级指针与传一级指针的区别
传二级指针:会使用到原链表的头指针,并对原链表的头指针进行修改,返回的是原来的头指针。
传一级指针:会创建新的指针来对链表进行修改,返回的是新的头指针,需要定义一个新的指针来接收函数的返回值。
8、经验总结
- 谨慎在一个链表中既插入又删除。单链表中插入、删除不容易,尾插、头插较简单,一旦中间插入、删除就更麻烦。
- 超出内存限制的一种可能是死循环。
- 回文结构——对称。