链表高频面试题

相交链表

力扣题目链接

解题思路

  1. 如果两个单链表有相交,那么它们的最后一个节点的地址必定相等,如果两个单链表不相交,那么它们的最后一个节点的地址不相等。
  2. 那么想要求出第一个相交节点,只要两个链表的长度相等,然后在同时往后走,那么它们必定会在第一个相交节点相遇。

图片解析

通过这一张图片我们可以看到单链表相交的两种情况,那么我们就可以直接得出结论了:两个单链表相交,它们的最后一个节点的地址一定相等。两个单链表不相交,它们的最后一个节点的地址一定不相等。

代码实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

typedef struct ListNode* Link;

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if (headA == NULL || headB == NULL) {//如果两个链表中有一个为空,那么必定不可能相交
        return NULL;
    }

    int la = 0;//计算headA链表的长度
    int lb = 0;//计算headB链表的长度

    Link p = headA;
    Link q = headB;

    while(p -> next) {//让p指向headA链表的最后一个节点
        p = p -> next;
        la ++;
    }
    la++;//最后一个节点没有算进长度,所以再加上

    while(q -> next) {//让q指向headB链表的最后一个节点
        q = q -> next;
        lb++;
    }
    lb++;//最后一个节点没有算进长度,所以再加上

    if (q == p) {//如果最后一个节点的地址相等,那么两个链表必定相交
        p = headA;
        q = headB;
        if (la >= lb) {//如果headA链表更长
            while(la > lb) {//让p往后走,直到两个链表的长度相等
                p = p -> next;
                la--;
            }
        }
        else {//headB链表更长
            while(lb > la) {//让q往后走,直到两个链表的长度相等
                q = q -> next;
                lb--;
            }
        }
        while(p != q) {//当它们相遇的时候必定是第一个相交节点
                p = p -> next;
                q = q -> next;
      }
    }
    else {
        return NULL;//两个链表不相交返回空
    }

    return p;//返回相交节点
}

K个一组反转链表

力扣题目链接

解题思路

  1. 把第一组单独拿出来考虑,因为涉及到链表换头。让头指针指向没有反转的k个一组的链表尾。
  2. 在每次反转之前先判断能不能有一组,因为不足一组就不用反转
  3. 反转第一个组,然后记住反转之后的链表尾,因为还要把链表连接起来
  4. 反转第二个组,然后把反转之后的链表尾指向没有反转之前链表的尾的下一个节点
  5. 把之前一个组的链表尾,指向反转之后的链表头,然后更新之前一组的链表尾,也就是把反转之后的链表尾。

算法图片解析

reverse函数就是反转链表之后再把反转之后的链表的尾接上原来的链表来不让链表断掉。

代码实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

typedef struct ListNode* Link;

Link getTail(Link head, int k)//得到从head开始的第k个节点
{
    while(--k && head) {
        head = head -> next;
    }

    return head;
}
void reverse(Link head, Link tail)//从head开始反转到tail,并且让反转之后的尾接上没有反转之前链表tail的下一个节点,来不让链表断掉
{
    Link t = tail -> next;//保存反转之前链表尾的下一个节点
    Link pre = NULL;
    Link cur = head;
    Link next = cur -> next;
    Link newTail = cur;//反转链表之后新的尾

    while(cur != tail)
    {
        cur -> next = pre;
        pre = cur;
        cur = next;
        next = next -> next;
    }

    cur -> next = pre;
    newTail -> next = t;//让反转之后的尾连接上之前链表尾的下一个节点
}

struct ListNode* reverseKGroup(struct ListNode* head, int k) {
    if (head == NULL || k == 1) {//k等于1就不用反转
        return head;
    }

    Link start = head;//一组的开始节点
    Link end = getTail(head, k);//一组的结束节点

    if (end == NULL) {//如果不满一组不用反转了
        return head;
    }

    head = end;//让头指针指向第一组的尾,因为反转之后,尾就成了新的头了
    reverse(start, end);//反转这一组

    Link lastTail = start;//上一组反转链表的尾

    while(lastTail -> next) {//如果还有节点,就可能需要反转
        start = lastTail -> next;//下一组的开始节点
        end = getTail(start, k);//下一组的结束节点

        if (end == NULL) {//如果不满一组就不用反转了,直接返回
            return head;
        }

        reverse(start, end);
        lastTail -> next = end;//让上一组反转链表的尾指向反转之后的尾,因为反转之后的尾就变成头了,所以要连接起来
        lastTail = start;//更新上一组反转链表的尾,因为反转链表之后的尾就是没有反转之前的头
    }

    return head;
}

随机链表的复制

力扣题目链接

解题思路

  1. 因为题目要求复制链表中的指针都不应指向原链表中的节点,所以我们应该先复制最原始的单链表,然后在去原链表中找到rand指针指向的节点,又因为是一摸一样的链表,所以在需要返回的链表中,它们移动的距离也是一样的。

代码实现

/**
 * Definition for a Node.
 * struct Node {
 *     int val;
 *     struct Node *next;
 *     struct Node *random;
 * };
 */

typedef struct Node* Link;

struct Node* copyRandomList(struct Node* head) {
    if (head == NULL) {//如果链表为空那么直接返回
        return NULL;
    }

    Link p = head;
    Link newHead = NULL;//需要返回的结果的头节点
    Link cur = newHead;//指向链表尾

    while(p) {//把最初的链表拷贝一份
        if (!cur) {//处理头节点
            newHead = (Link)malloc(sizeof(struct Node));

            newHead -> val = p -> val;//值的拷贝
            newHead -> next = NULL;//初始化为空
            newHead -> random = NULL;//初始化为空
            cur = newHead;
        }
        else {
            Link t = (Link)malloc(sizeof(struct Node));//开辟一个节点
            t -> val = p -> val;//值的拷贝
            t -> next = NULL;//初始化为空
            t -> random = NULL;//初始化为空
            cur -> next = t;//把它与链表连接起来
            cur = cur -> next;//继续指向链表尾
        }
        p = p -> next;
    }   

    p = head;//因为刚刚对p做了操作,所以重新从头开始
    cur = newHead;//处理指向节点的rand指针

    while(p) {
        //找到rand指针在需要返回链表中的位置
        if (p -> random) {//如果有指向一个节点
            Link q = head;//要找到p->random指针
            Link p1 = newHead;//在返回链表中得的位置,因为是一摸一样的链表,所以移动的距离也是一样的

            while(q != p -> random) {//只要不是相同的节点
                q = q -> next;//看下一个
                p1 = p1 -> next;//在返回节点,也要移动,因为是一样的链表
            }

            cur -> random = p1;//指向p1,也就是在原链表中的q位置,它们是一样的位置
        }
        p = p -> next;//处理下一个节点
        cur = cur -> next;//处理下一个节点
    }

    return newHead;//返回复制的链表
}

回文链表

力扣题目链接

解题思路

  1. 先找找到链表中的中点位置
  2. 然后反转中点之后的链表
  3. 然后就可以直接比较两个链表的指是不是一样的了,因为是单链表所以不反转是无法比较的,所以无法找到对应的节点

代码实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

typedef struct ListNode* Link;

Link reverse(Link head)
{
    Link pre = NULL;
    Link cur = head;
    Link nxt = cur -> next;

    while(nxt) {
        cur -> next = pre;
        pre = cur;
        cur = nxt;
        nxt = nxt -> next;
    }
    cur -> next = pre;

    return cur;
}

bool isPalindrome(struct ListNode* head) {
    Link slow = head;
    Link fast = head;
    Link pre = NULL;

    while(fast && fast -> next) {//利用快慢指针找到链表中的中点位置,注意循环结束的条件
        fast = fast -> next -> next;
        pre = slow;
        slow = slow -> next;
    }

    //当循环结束时,slow指针指向链表中点的位置
    Link head1 = reverse(slow);//返回反转中点位置之后的链表的头节点
    Link p =head;//指向原链表的头节点
    bool ans = true;
    Link q = head1;

    while(q) {//注意循环结束的条件
        if (q -> val != p -> val) {//如果不等于代表不是回文结构
            ans = false;
            break;
        }
        q = q -> next;
        p = p -> next;
    }

    //把链表再还原回来
    if (pre) pre -> next = reverse(head1);//链表中可能只有一个元素,那么中点就是头节点,那么pre就是空指针,因此要判断一下

    p = head;

    while(p) {
        printf("%d ", p -> val);
        p = p -> next;
    }

    return ans;//如果不是在循环里面结束的代表是回文结构
}

 环形链表

力扣题目链接

解题思路

  1. 利用快慢指针就可以判链表有没有环,如果有环那么快慢指针必定相遇,如果没有环,那么快指针一定先到链表尾

代码实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

typedef struct ListNode* Link;

bool hasCycle(struct ListNode *head) {
    Link slow = head;
    Link fast = head;

    while( fast && fast -> next )//和找中点的判断一样,因为要防止引用空指针
    {
        slow = slow -> next;//每次走一步
        fast = fast -> next -> next;//每次走两步
        if (fast == slow) {//如果快指针和慢指针相遇代表有环
            return true;
        }
    }

    return false;//如果结束还没有相遇代表没有环
}

判断一个链表有没有环,如果有环返回第一个入环节点,没有环返回NULL;

力扣题目链接

解题思路

第一步:判断链表有没有环?

判断链表有没有环,用快慢指针。如果链表有环,那么快指针和慢指针必定相遇,没有环,快指针一定比慢指针先到链表的尾部。

第二步:有环就找到第一个入环节点,没有返回NULL

先假设有如图带环链表;并在头结点处定义一个fast和一个slow指针(快慢指针),每次让fast走两步,slow走一步;则在环中,它们假设在B点相遇,假设A点则为链表入环的第一个节点。

那么怎么找到第一个入环节点呢?

先说结论:当快慢指针第一次相遇时,令快指针回到头节点,慢指针在原地不动,然后快慢指针同时移动,快指针一次走一步,慢指针一次走一步,当它们再次相遇时,相遇的节点就是第一个入环节点。

数学证明如下图:

1.假设链表长度为n;head到入口点A的距离为x;入口点A到相遇点B的距离为y;
2.因为fast所走的路程是slow所走路程的两倍,即有:fast = 2 * slow;
3.fast和slow相遇后,fast所走的路程为:链表的总长度加上入口点A到相遇点B的 距离,即:fast = n + y;slow走的路程为head到相遇点的距离,即:slow = x + y;

4.fast = 2 * slow; fast = 2(x + y) = n + y;

消元得n = x + x + y;

所以相遇点b到第一个入环节点的距离也为x;

代码实现

ListNode* findLinkedListCycle(ListNode* head) 
{
	ListNode* fast = head;//从头节点开始 
	ListNode* slow = head;//从头节点开始 
	ListNode* res = NULL;//存放结果 
	while(fast && fast -> next) {//循环条件保证了不会出现空指针调用 ,和找链表中间节点的情况一样 
		slow = slow -> next;//慢指针一次走一步 
		fast = fast -> next -> next;//快指针一次走两步 
		if (slow == fast) {//如果快慢指针相遇代表链表有环 
			fast = head;//令快指针回到头节点 
			
			while( fast != slow ) {//当两个指针再次相遇,相遇的节点就是第一个入环节点 
				fast = fast -> next;//快指针这时每次走一步 
				slow = slow -> next;//慢指针还是每次走一步 
			}	
			res = fast;//记录第一个入环节点 
			break;//因为有环所以跳出循环 
		}
	}
	return res;//返回结果 
}

 

posted @ 2023-12-31 23:22  lwj1239  阅读(31)  评论(0)    收藏  举报