面试复习-------算法与数据结构------链表

如果一个操作可能会改变头指针,那么参数应该是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;
}

(5)合并两个已排序的链表(剑指offer17

递归的代码比较简洁,非递归代码如下:

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将其删除

你可能感兴趣的:(算法与数据结构)