1 寻找链表的中间节点:最简单的方法是,先遍历一遍链表,计算出链表的长度,然后计算出中间节点的位置,然后再遍历一遍,边遍历边计算,直到找到中间节点,这个方法略显啰嗦,最坏的情况需要遍历2次链表,代码如下:
ListNode* findMiddle(ListNode* head){
if(!head) return NULL;
int length = 0;
while(head) { head = head->next; length ++ ; }
int middle = (lenght+1)/2;
--middle;
while(middle--) head = head->next;
return head;
}
另一个更灵巧的方法是,用两个指针,慢指针每次走一步,快指针每次走两步,当快指针走到链表的末端(NULL)时,慢指针正好指向了中间节点,代码如下:
ListNode* findMiddle(ListNode* head){
if(!head) return NULL;
ListNode* slow = head;
ListNode* fast = head->next;
while(fast && fast->next){
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
2 检测链表是否有环:经典的做法也是用快慢指针,如果没有环,快指针一定先到达链表的末端(NULL),如果有环,快、慢指针一定会相遇在环中,代码如下:
bool hasCircle(ListNode* head, ListNode **node){
if(!head) {
*node = NULL; return false; }
ListNode* slow = head;
ListNode* fast = head->next;
while(fast && fast->next && fast!=slow){
slow = slow->next;
fast = fast->next->next;
}
if(fast==slow) {
*node = fast; return true; }
else {
*node = NULL; return false; }
}
3 检测环的入口:经典的做法是先检测是否有环,如果有环,则计算出环的长度,然后使用前后指针(不是快慢指针),所谓的前后指针就是一个指针先出发,走了若干步以后,第二个指针也出发,然后两个指针一起走,当前后指针相遇时,它们正好指向了环的入口,代码如下:
ListNode* detectEntry(ListNode* head){
//判断是否有环,无环则返回NULL
ListNode* node = NULL;
if(!hasCircle(head, &node)) return NULL;
//计算环的长度
int circleLen = 1; ListNode *temp = node->next;
while(temp!=node) { temp = temp->next; circleLen ++; }
//使用前后指针,此时fast每次也只移动一步
ListNode *first = head , *second = head;
while(circleLen--) first = first->next; // first指针先出发
//然后两个指针同时向前走,每次走一步
while(first!=second){ first = first->next; second = second->next ; }
return first;
}
如果允许使用额外的内存,可以有更简单的做法,即一边遍历,一边将节点放在map中,当某个节点第二次出现在map中时,它就是入口节点,代码如下:
ListNode* detectEntry(ListNode* head){
if(!head) return NULL;
map<ListNode*,bool> hm;
while(!head){
if(hm.count(head)) return head;
else hm[head] = true;
head = head->next;
}
return NULL;
}
4 链表翻转:假设原链表为1->2->3,翻转以后的链表应该是1<-2<-3,即节点3变成了头节点,代码如下:
ListNode* reverseList(ListNode *head){
if(!head || !head->next) return head;
ListNode* p = head->next;
head->next = NULL;
while(p){
ListNode* t = p->next;
p->next = head;
head = p;
p = t;
}
return head;
}
5 删除链表中的节点,注意这里只给出要删除的那个节点,不给出链表头(假设被删除节点不是尾节点),代码如下:
void removeNode(ListNode *target){
if(!target) return ;
ListNode *t = target->next;
target->val = t->val;
target->next = t->next; //用target的下一个节点覆盖了target,然后删除下一个节点即可
delete t;
}
如果被删除节点是尾节点,上面的代码就无法将target上一个节点的next置为NULL,所以只有给了头节点后,才能遍历到target的上一个节点,并把其next置为NULL。
6 回文链表的检测:所谓回文链表,即链表元素关于中间对称,,如1->2->3->2->1,比较简单的方法是用一个栈,先顺序遍历链表,并把每个节点放入栈中,遍历完成后,栈的出栈顺序正好是原链表的逆,代码如下:
bool isPalindrome(ListNode* head){
if(!head) return true; //空链表姑且认为是回文的
stack<ListNode*> s;
ListNode* p = head;
while(p) { s.push(p); p = p->next ; }
int length = s.size();
length /= 2;
while(length--) {
p = s.top();
s.pop();
if(p->val!=head->val) return false;
head = head->next;
}
return true;
}
上面代码的空间复杂度为O(N),其实还有空间复杂度为O(1)的算法,也很灵巧,运用了之前提到的一些技巧,代码如下:
bool isPalindrome(ListNode* head) {
if (!head || !head->next) return true; //空链表或只有一个元素的链表都认为是回文的
//寻找链表的中间节点
ListNode* slow = head;
ListNode* fast = head->next;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
//反转后半段链表
ListNode* prev = slow; //prev目前是前半段链表的尾节点,后半段链表的头结点
ListNode* p = slow->next;
slow->next = nullptr;
while (p)