leetcode单链表问题集

文章目录

        • 反转链表
        • k个一组反转链表
        • 合并有序链表
        • 合并k个有序链表
        • 环形链表
        • 环形链表入口
        • 删除倒数第N个节点
        • 相交链表的交点
        • 移除链表指定元素
        • 回文链表
        • 奇偶链表
        • 两数相加
        • 旋转链表

刷了几天leetcode ,发现以前的很多解法都很冗余,代码不够精简,重新整理了一波,准备做个集合。
每一题解法争取用最少的行数解决问题
以下是链表相关问题,持续更新中…

反转链表

  1. 反转过程中需要保存当前节点 curr 的下一个节点 next,当 next 空时,反转结束,返回 curr
  2. 全程只需要关注 curr 是否为空即可。
  3. 每一轮反转结束,需要更新 prev,curr 的值,而 next 是在最开始就更新的。
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* prev = NULL;
    struct ListNode* curr = head;
    struct ListNode* next = NULL;
    while(curr)
    {
        next = curr->next; //save next
        curr->next = prev; //reverse
        if (next==NULL){   //last node of list
            break;
        }
        prev = curr; //move forword
        curr = next; //move forword
    }
    return curr;
}

k个一组反转链表

  1. 这题用递归解法会比较简单
  2. 找到翻转点很重要,代码里 kTail 是当前k个一组节点的尾节点,为了找到 kTail ,需要从头节点开始走 k-1 步。
  3. 通过 kTail 是否空来判断是否需要翻转,如果 kTail 为空,说明剩下的节点数小于 K个了。
  4. 同时需要保存下一组 k 个节点的头节点,也就是 nextHead
  5. 只关注当前一组节点的翻转,翻转代码与上述翻转单链表一样,唯一不同的是,break 的地方变成了 next==nextHead
  6. 当前组的K个节点翻转完毕之后,需要将尾节点(oldHead)的next 指向下一组节点的头节点。
struct ListNode* reverseKGroup(struct ListNode* head, int k) {
    struct ListNode* kTail = head;   //k个节点的尾节点
    struct ListNode* nextHead = NULL;//下一组k个节点的头节点

    int n = k-1;
    while(n-- && kTail){
        kTail = kTail->next;
    }
    if (kTail){
        nextHead = kTail->next;
    }
    
    if (kTail){ //满足k个节点即可反转
        struct ListNode* oldHead = head; //保存下旧的头部
        struct ListNode* prev = NULL;
        struct ListNode* curr = head;
        struct ListNode* next = NULL;
        while(curr){
            next = curr->next;
            curr->next = prev;
            if(next == nextHead){ //翻转到下一组的头节点就截止
                break;
            }
            prev = curr;
            curr = next;
        }
        oldHead->next = reverseKGroup(next, k); //旧的头节点(即翻转后的尾节点)指向下一组的头节点
        return curr; //返回翻转后新的head
    }
    return head; //不满足k个节点则直接返回 head
}

合并有序链表

  1. 合并之前需要判两个链表是否有空链表
  2. 合并时需要一个头指针和一个尾指针,尾指针不断更新,最后返回的是头指针
  3. 合并完之后需要再次判断两个链表剩余的部分
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) {
    if (l1 == NULL || l2 == NULL)
        return l1 != NULL? l1 : l2;
    
    struct ListNode head = {0, NULL}; //virtual head node
    struct ListNode *tail = &head;
    while(l1 && l2){
        if (l1->val < l2->val){
            tail->next = l1;
            l1=l1->next;
        }else{
            tail->next = l2;
            l2=l2->next;
        }
        tail=tail->next;
    }
    tail->next = l1==NULL ? l2:l1;
    return head.next;
}

合并k个有序链表

//找到K个链表中最小的头节点
struct ListNode* findMinNode(struct ListNode** lists, int listsSize, int*index)
{
    int init=0;
    struct ListNode* min = NULL;
    for (int i=0;i<listsSize;i++){
        if (lists[i]==NULL) 
            continue;
    
        if (init==0){ //初始化第一个最小节点
            min=lists[i];
            *index = i;
            init=1;
        } else {
            if (lists[i]->val < min->val){
                *index = i;
                min=lists[i];
            }
        }
    }
    return min;
}
struct ListNode* mergeKLists(struct ListNode** lists, int listsSize) {
    struct ListNode head = {0, NULL};
    struct ListNode* tail = &head;
    struct ListNode* minNode = NULL;
    int i = 0;
    while(minNode = findMinNode(lists, listsSize, &i)){
        tail->next = minNode;
        tail = tail->next;    
        lists[i] = lists[i]->next;
    }
    return head.next;
}

环形链表

  1. 快慢指针找到相遇点
bool hasCycle(struct ListNode *head) {
    struct ListNode *slow = head;
    struct ListNode *fast = head;
    while(slow && fast && fast->next){
        slow = slow->next;
        fast = fast->next->next;
        if (fast == slow)
            return true;
    }
    return false;
}

环形链表入口

  1. 先快慢指针找到相遇点
  2. 再2个指针分别从起点和相遇点开始,直到再次相遇则为入口
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode *slow = head;
    struct ListNode *fast = head;
    struct ListNode *meet = NULL;
    //find meet node
    while(slow && fast && fast->next){
        slow = slow->next;
        fast = fast->next->next;
        if (slow==fast){
            meet = slow;
            break;
        }
    }
    slow = head;
    while(slow && meet){
        if (meet==slow){ //find entry point
            return meet;
        }
        slow = slow->next;
        meet = meet->next;
    }
    return NULL;
}

删除倒数第N个节点

  1. 快指针先向前走n步,注意是n步,不是n-1步
  2. 快慢指针同时走,直到 fast 或 fast->next 为空
  3. 如果 fast 为空,表示要删除的是第一个节点,此时 slow 指向的是 head
  4. 如果 fast !=NULL && fast->next==NULL,表示要删除的是head节点之后的节点,此时slow 指向的是被删节点的前置节点。
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while(fast && n--){ 
        fast=fast->next;
    }
    while(slow && fast && fast->next){ 
        slow = slow->next;
        fast = fast->next;
    }
    
    if (fast==NULL){ //删除的是第一个节点
        head = head->next;
    }else if (slow && slow->next){ //删除的不是第一个节点
        slow->next = slow->next->next;
    }
    return head;
}

相交链表的交点

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if (headA==NULL || headB==NULL){
        return NULL;
    }
    //先找到其中一个链表的尾节点
    struct ListNode *tailA = headA;
    while(tailA->next){
        tailA = tailA->next;
    }
    //将尾节点和头结点连起来形成环
    tailA->next = headA;
    
    //问题转化为求环的入口
    struct ListNode *slow = headB;
    struct ListNode *fast = headB;
    struct ListNode *meet = NULL;
    while(slow && fast && fast->next){
        slow = slow->next;
        fast = fast->next->next;
        if (slow==fast){
            meet = slow;
            break;
        }
    }
    slow = headB;
    while(slow && meet){
        if (slow==meet){
            break;
        }
        slow = slow->next;
        meet = meet->next;
    }
    将尾节点和头结点解开
    tailA->next = NULL;
    return meet;
}

移除链表指定元素

struct ListNode* removeElements(struct ListNode* head, int val) {
    //移除最开始相等的节点,直到第一个不相等的
    while(head && head->val == val){
        head = head->next;
    }
    //head 一定是不相等的节点,此时如果下一个节点为空可以直接返回了
    if (head==NULL || head->next==NULL)
        return head;
    
    //2个指针一前一后
    struct ListNode* slow = head;
    struct ListNode* fast = head->next;
    while(slow && fast){
        if (fast->val==val){ //快指针相等的话,慢指针直接删除该节点
            slow->next = fast->next;
        }else{
            slow = slow->next;
        }
        fast = fast->next;
    }
    return head;
}

回文链表

以下面2个链表为例

list1: 1->2->3->2->1
list2: 1->2->3->2
  1. 首先需要处理特殊情况,即链表长度为0,1,2 的时候。
  2. 接下来用快慢指针,直到 fast 或 fast->next 为空
  3. 如果fast 为空则表示链表长度为偶数,此时 slow 指向链表第 n/2 +1 个节点,也就是list1的第3 个节点
  4. 如果fast 不为空则 fast->next 为空,链表长度为奇数,此时 slow 指向链表中间节点,也就是 list2 的第3个节点
  5. 针对偶数链表做翻转,需要反转 1->2 的部分,其下一个节点正是 slow
  6. 针对奇数链表做反转,也只需要反转 1->2 的部分,其下一个节点正是 slow
  7. 所以当 next==slow 的时候反转停止,返回反转后的头节点,也就是 slow 的上一个节点
  8. 接下来同时反向遍历比较,这里针对奇偶有区别,奇数链表的右半部分从 slow->next 开始,偶数链表右半部分从 slow 开始。
struct ListNode* reverse(struct ListNode* head, struct ListNode* givenNode){
    struct ListNode* prev = NULL;
    struct ListNode* curr = head;
    struct ListNode* next = NULL;
    while(curr){
        next = curr->next;
        curr->next = prev;
        if (next == givenNode){
            break;
        }
        prev = curr;
        curr = next;
    }
    return curr;
}

bool isPalindrome(struct ListNode* head) {
    //1个节点
    if (head && head->next==NULL){
        return true;
    }
    //2个节点
    if (head && head->next && head->next->next==NULL){
        if (head->val == head->next->val){
            return true;
        }else{
            return false;
        }
    }
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while (slow!=NULL && fast!=NULL && fast->next!=NULL) {
        slow = slow->next;
        fast = fast->next->next;
    }

    struct ListNode* left = reverse(head, slow);
    struct ListNode* right = NULL;
    if (fast){  //奇数链表
        right = slow->next;
    }else{      //偶数链表,此时slow指向第len/2+1个节点
        right = slow;
    }
    while(left && right){
        if (left->val != right->val){
            return false;
        }else{
            left  = left->next;
            right = right->next;
        }
    }
    return true;
}

奇偶链表

  1. 用4个指针分别指向奇数链表的首尾节点和偶数链表的首尾节点
  2. 最后再把偶数链表和奇数链表连起来
struct ListNode* oddEvenList(struct ListNode* head) {
    if (head==NULL || head->next==NULL)
        return head;
    struct ListNode* oddHead = head; //奇数节点的头
    struct ListNode* oddTail = oddHead; //奇数节点的尾
    
    struct ListNode* eventHead = head->next; //偶数节点头
    struct ListNode* eventTail = eventHead; //偶数节点尾
    
    bool isOdd = true;
    head = head->next->next; //从第3个节点开始
    while(head){
        printf("%d\n", head->val);
        if (isOdd){
            oddTail->next = head;
            oddTail = oddTail->next;
            isOdd = false;
        }else{
            eventTail->next = head;
            eventTail = eventTail->next;
            isOdd = true;
        }
        head = head->next;
    }
    if (!isOdd) //一定要将偶数节点的最后一个节点置为空
        eventTail->next = NULL;
    
    oddTail->next = eventHead;
    return oddHead;
}

两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

  1. 和合并有序链表比较相似,只是多了一个进位的操作。同时要考虑其中一个链表更长的情况。
struct ListNode*newNode(int val)
{
    struct ListNode* node = malloc(sizeof(struct ListNode));
    node->val = val;
    node->next = NULL;
    return node;
}

struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) {
    struct ListNode head = {0,NULL};
    struct ListNode* tail = &head;

    struct ListNode* p1 = l1;
    struct ListNode* p2 = l2;
    int carry = 0; //进位
    while(p1 && p2){
        int sum = p1->val + p2->val + carry;
        carry = sum/10;
        tail->next = newNode(sum%10);
        tail = tail->next;
        p1 = p1->next;
        p2 = p2->next;
    }
    if (carry != 0){
        while(p1){
            int sum = p1->val + carry;
            carry = sum/10;
            tail->next = newNode(sum%10);
            tail = tail->next;
            p1 = p1->next;
        }
        while(p2){
            int sum = p2->val + carry;
            carry = sum/10;
            tail->next = newNode(sum%10);
            tail = tail->next;
            p2 = p2->next;
        }
        if (carry!=0){
            tail->next = newNode(carry);
            tail = tail->next;
        }
    }else{
        tail->next = p1==NULL?p2:p1;
    }
    return head.next;    
}

旋转链表

  1. 2个指针分别找到链表解除链接的点,要注意是否是原地旋转
struct ListNode* rotateRight(struct ListNode* head, int k) {
    if (head==NULL || k<=0)
        return head;

    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while(k--){
        fast = fast->next;
        if(fast == NULL){ //k>len 的时候从头开始
            fast = head;
        }
    }
    if (slow==fast){ // 相等表示 k%len==0
        return head;
    }
    while(slow && fast && fast->next){
        fast = fast->next;
        slow = slow->next;
    }
    struct ListNode* newHead = slow->next;
    slow->next = NULL;
    fast->next = head;
    return newHead;
}

你可能感兴趣的:(算法)