链表算法面试题

135 阅读4分钟

1.反转链表

image.png

方法一:迭代 注意对于反转链表涉及到三个节点,因此需要设置3个指针,prev初始化为nullptr,curr初始化为head,而currnext指针在while迭代过程中初始化即可,另外在每一次迭代中仅需将curr节点指针反转即可

ListNode* reverseList(ListNode* head) {
    ListNode* prev=nullptr;
    ListNode* curr=head;
    ListNode* cnext;
    while(curr!=nullptr){
        cnext=cnext->next;
        curr->next=prev;
        prev=curr;
        curr=cnext;
    }
    return prev;
}

方法二:递归 这个思路是类似于后序遍历,从后往前操作节点

ListNode* reverseList(ListNode* head) {
    if (!head || !head->next) {
        return head;
    }
    ListNode* newHead = reverseList(head->next);
    head->next->next = head;
    head->next = nullptr;
    return newHead;
}

2.求链表倒数第K个节点

image.png

方法一:快慢指针 设置一个临时指针fast,让fast指针先走k步,之后fast与head指针一直往后走,等fast指针为nullptr时,head也走到了倒数第K个节点的位置。注意两个while循环条件都是fast指针不为nullptr

ListNode* getKthFromEnd(ListNode* head, int k) {
    ListNode *fast=head;
    while(fast!=nullptr&&k>0){
        fast=fast->next;
        k--;
    }
    while(fast!=nullptr){
        fast=fast->next;
        head=head->next;
    }
    return head;
}

方法二:求出链表长度 这个方法较为简单粗暴,先一个循环求出链表长度n,再一个循环走n-k步即可

ListNode* getKthFromEnd(ListNode* head, int k) {
    int n = 0;
    ListNode* node = nullptr;

    for (node = head; node; node = node->next) {
        n++;
    }
    for (node = head; n > k; n--) {
        node = node->next;
    }

    return node;
}

3.两条链表的第一个公共节点

image.png 类似于上面的先分别求出两条链表的长度,接着让长链表的指针先走abs(lenA-lenB)步,再两个链表的两个指针同时走直到相遇

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    ListNode* tmpA=headA;ListNode* tmpB=headB;
    int lenA=0;int lenB=0;
    while(tmpA!=nullptr){
        tmpA=tmpA->next;
        lenA++;
    }     
    while(tmpB!=nullptr){
        tmpB=tmpB->next;
        lenB++;
    }

    tmpA=headA;tmpB=headB;
    if(lenA>lenB){
        while(lenA!=lenB){
            tmpA=tmpA->next;
            lenA--;
        }
    }else{
        while(lenB!=lenA){
            tmpB=tmpB->next;
            lenB--;
        }
    }

    while(tmpA!=tmpB){
        tmpA=tmpA->next;
        tmpB=tmpB->next;
    }
    return tmpA;
}

4.判断链表是否有环

image.png 思路就是用快慢指针,fast每次移动两格(其实三格也可以,但是两格理论上更快),slow每次移动一格,如果有环迟早会相遇

  • 时间复杂度:O(N),其中 N 是链表中的节点数。当链表中不存在环时,快指针将先于慢指针到达链表尾部,链表中每个节点至多被访问两次。当链表中存在环时,每一轮移动后,快慢指针的距离将减小一。而初始距离为环的长度,因此至多移动 N 轮。
  • 空间复杂度:O(1)。我们只使用了两个指针的额外空间。
bool hasCycle(ListNode *head) {
    if (head == nullptr || head->next == nullptr) {
            return false;
    }
    ListNode* fast=head->next;
    ListNode* slow=head;

    while(slow!=fast){
        if (fast == nullptr || fast->next == nullptr) {
                return false;
        }
        fast=fast->next->next;
        slow=slow->next;
    }

    return true;
}

5.环的入口

image.png 遇到链表题画图是一个很好的解决办法。图来自力扣官方解答 image.png

假设快慢指针在紫色点相遇,此时慢指针走过的路程为a+b,快指针走过的路程为a+b+n(b+c)。 为什么慢指针走过a+b就必然与快指针相遇,而不是慢指针走过a+b+m(b+c)呢?

首先,由于快指针一定先进入环内,这点毋庸置疑。 而且,快指针是慢指针速度的二倍,即慢指针走完一圈,快指针可以走两圈 所以不论慢指针入环时,快指针在哪一点,快指针都可以在慢指针未走过一圈时追上慢指针。 而由于快指针走过的节点数是慢指针的二倍,所以得到公式: (a + b) * 2 = a + b + n (b + c) 两边抵消 a+b,得到 a + b = n (b + c) 由于我们最终要求的是a,所以 a = n (b + c) - b 然而,此时慢指针在环里面所走过的路程刚好为b,如果此时有一个指针point从头开始走向环,即x路程 那么,慢指针刚好要走过的就是 n (b + c) - b + b = n (b + c) 即 point 走a的距离到达环的入口的时刻,刚好为slow走过n圈到达入口,两个指针相遇 也即point与slow两个指针相遇的时候就是那个入口!!!!

注意slow和fast都初始化为head指针

ListNode *detectCycle(ListNode *head) {
    if(head==nullptr||head->next==nullptr)return nullptr;
    ListNode* fast=head;
    ListNode* slow=head;
    while(1){
        if(fast==nullptr||fast->next==nullptr)return nullptr;
        slow=slow->next;
        fast=fast->next->next;
        if(slow==fast)break;
    }
    ListNode* point=head;
    while(slow!=point){
        slow=slow->next;
        point=point->next;
    }
    return slow;
}