如果一个操作可能会改变头指针,那么参数应该是ListNode** phead
头插法和尾插法是指在建立单链表的时候,时间复杂度都是O(n)
将节点添加到一个已存在链表的尾部也是O(n),那么为何尾插法可以O(n)而不是O(n^2),原因是在创建的时候会维护一个链表尾部的指针。所以不用每次都循环寻找链表尾节点。
(1)反转链表(剑指offer16)
为了防止断链需要设置三个指针分别指向pPrev、pNode、pNext
ListNode* reverse(ListNode* phead)
{
if(phead==NULL)
return NULL;
ListNode* pre;
ListNode* index;
ListNode* pnext;
ListNode* result;
index=phead;
pre=NULL;
while(index!=NULL)
{
pnext=index->next;
if(pnext==NULL)
{
result=index;
}
index->next=pre;
pre=index;
index=pnext;
}
return result;
}
(2)O(1)时间删除链表节点(剑指offer13)
思路:将该节点的值换成后一个节点的值,再将后一个节点删除;有点取巧
如果是尾节点,则还是得花费O(n)
(3)链表中倒数第k个节点(剑指offer15)
快慢指针的使用,一个指针先走K步,然后两个指针一起走直到第一个指针到达尾部;需要考虑链表不足K的情况
(附:在做链表题的时候(或者说涉及到指针的时候),一定考虑鲁棒性;即对于一个参数pNode一定要判断:(pNode == NULL); 要使用pNode->NEXT的时候,一定要判断(pNode->NEXT == NULL))
ListNode* find_k(ListNode* phead,int k)
{
if(phead==NULL||k<=0)
return NULL;
ListNode* index1=phead;
ListNode* index2=NULL;
//先让一个指针到k-1个节点
for(int i=0;inext != NULL)
index1=index1->next;
else
return NULL;
}
index2 = phead;
while (index1->next!=NULL)
{
index1=index1->next;
index2=index2->next;
}
return index2;
}
类似的快慢指针的题还有:
--------求链表的中间节点(快慢指针,一个一步每次一个两步每次);
--------判断单链表是否存在环(快慢指针,判断他们是否会相遇);
(4)公共节点问题:利用特性,公共节点之后的节点都是公共节点,即只能为Y型
------判断两个链表是否相交;直接可以看这两个链表的尾节点是否相同
------求两个链表的第一个公共节点(剑指offer37)首先获取两个链表的长度,然后算出长度差x,令一个指针先走x步,之后两个指针一起走,直至两个指针指向的节点相同
int getListLength(ListNode* phead)
{
int length=0;
ListNode* pnode=phead;
while(pnode!=NULL)
{
pnode=pnode->next;
++length;
}
return length;
}
ListNode* FindFirstCommonNode(ListNode* phead1,ListNode* phead2)
{
int length1= getListLength(phead1);
int length2= getListLength(phead2);
int DifLength=(length1>length2)?(length1-length2):(length2-length1);
ListNode* pnode1=phead1;
ListNode* pnode2=phead2;
if(length1>length2)
for(int i=0;inext;
else if(length1next;
while(pnode1!=NULL && pnode2 != NULL && pnode1 != pnode2)
{
pnode1=pnode1->next;
pnode2=pnode2->next;
}
return pnode1;
}
递归的代码比较简洁,非递归代码如下:
ListNode* list_sort(ListNode* phead1,ListNode* phead2)
{
if(phead1 == NULL)return phead2;
if(phead2 == NULL)return phead1;
ListNode* result = NULL;
ListNode* pNode1 = phead1;
ListNode* pNode2 = phead2;
if(phead1->key < phead2->key){
result = phead1;
pNode1 = phead1->next;
}
else{
result = phead2;
pNode2 = phead2->next;
}
ListNode* pCurrent = result;
while(pNode1 != NULL && pNode2 != NULL ){
if(pNode1->key < pNode2->key){
pCurrent->next = pNode1;
pNode1 = pNode1->next;
}else{
pCurrent->next = pNode2;
pNode2 = pNode2->next;
}
pCurrent = pCurrent->next;
}
if(pNode1 != NULL){
pCurrent->next = pNode1;
}
if(pNode2 != NULL){
pCurrent->next = pNode2;
}
return result;
}
两个链表合成一个链表的常用技巧就是使用一个pCurrent指针指向当前节点,每次只需添加到pCurrent后面,并且pCurrent后移一位即可。
(6)复杂链表的复制(剑指offer26)
在原始链表的每一个节点N后面创建一个N'节点,复制值和指针,最后再将该链表分解成两个链表。
(7)左旋转链表
类似于左旋转字符串,即将链表的最右边K个节点移到链表头指针之前;
由于k可能大于链表长度,可以先遍历链表统计长度并获得尾指针,然后旋转k%length个节点即可;
ListNode* rotateRight(ListNode* head, int k) {
if(head == NULL)return head;
int length = 1;
ListNode* pNode = head;
while(pNode->next != NULL){
pNode = pNode->next;
++length;
}
pNode->next = head;
k = k % length; //真正需要转移的个数,包含了k>length的情况
if(k !=0){
for(int i = 0; i < length - k; ++i){
pNode = pNode->next;
}
}
ListNode* pNext = pNode->next;
pNode->next = NULL;
return pNext;
}
(8)技巧:有时候遍历链表的时候,pPre不知道从何处初始化(可能要手动处理一个使pNode = phead->next),有的时候可能会涉及到删除pHead节点;这个时候可以手动构造一个节点放在头结点之前,最后返回该节点的next指针域即可。例:删除排序链表中值相同的节点(leetcode82):
ListNode* deleteDuplicates(ListNode* head) {
if(head == NULL)return NULL;
ListNode* pHead = new ListNode(-1);
pHead->next = head;
ListNode* pPre = pHead;
ListNode* pNode = head;
while(pNode!=NULL){
while(pNode->next!=NULL && pNode->val == pNode->next->val)pNode = pNode->next;
if(pPre->next != pNode){
pPre->next = pNode->next;
pNode = pNode->next;
}else{
pPre = pNode;
pNode = pNode->next;
}
}
ListNode* result = pHead->next;
delete pHead;
return result;
}
手动构造了一个pHead,这个时候就可以使pPre = pHead了。
注意最后要用delete将其删除。