【数据结构】单向链表 OJ篇

1. 移除链表元素

题目链接 : 移除链表元素

第一种思路 : 使用两个指针,一前一后判断

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val) {
    // 直接先判断head是否为空,head若为空就直接返回
    if (head == NULL) {
        return head;
    }
    struct ListNode* cur = head;
    struct ListNode* prev = NULL;
    while (cur) {
        if (cur->val != val) {
            prev = cur;
            cur = cur->next;
        } else {
            // prev == NULL 代表现在是头删 ( 如果不是头删就意味着prev不会为空)
            if (prev == NULL) {
                head = head->next;
                free(cur);
                cur = head;
            } else {
                prev->next = cur->next;
                free(cur);
                cur = prev->next;
            }
        }
    }
    return head;
}

第二种思路 : 把不是val的值尾插到新链表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val) {
    // newnode 是新链表的节点 tail负责找尾
    struct ListNode *newnode = NULL, *tail = NULL;
    struct ListNode* cur = head;
    while (cur) {
        if (cur->val != val) {
            // 尾插
            if (tail == NULL) {
                // 把 cur 赋值给tail在赋值给newnode ( 因为tail为空代表现在是空链表 直接插入 )
                newnode = tail = cur;
            } else {
                tail->next = cur;
                tail = tail->next;
            }
            cur = cur->next;
        } else {
            struct ListNode* next = cur->next;
            free(cur);
            cur = next;
        }
    }
    // 如果链表传入本身就为空 或者 删除某值后的链表为空,就不能让 tail->next 为空。因此在这里做判断一下
    if (tail) {
        tail->next = NULL;
    }

    return newnode;
}

2. 反转链表

题目链接 : 反转链表

思路 :

利用三个指针 ( prev、cur、next_node ),prev指向前一个节点;cur 指向当前节点;next_node指向下一个节点。判断cur是否为空,如果不为空,将下一个节点保存下来,然后将当cur->next点指向prev,再将 cur 赋值给 prev 再将 next_node 赋值给 cur。最后在将 prev 返回

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* cur = head;
    struct ListNode* prev = NULL;
    struct ListNode* nextNode = NULL;
    
    while (cur) {
        nextNode = cur->next;  // 保存下一个节点
        cur->next = prev;      // 反转当前节点的指针
        prev = cur;            // 移动 prev 到当前节点
        cur = nextNode;        // 移动 cur 到下一个节点
    }
    
    return prev;  // prev 现在是新的头节点
}

3. 查找链表的中间节点

题目链接 : 查找链表的中间节点

思路 : 利用两个指针,一个指针每次只走一步,另一个指针每次走两步。当走两步的指针走到尾时,走一步的指针就刚好在中间。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* middleNode(struct ListNode* head) {
    if(head == NULL){
        return NULL;
    }
    struct ListNode* fast = head;
    struct ListNode* slow = head;

    while(fast && fast->next != NULL){
		slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

4. 返回倒数第k个节点

题目链接 : 返回倒数第k个节点

思路 : 定义两个指针 ( fast 、 slow )。 fast先走k-1步,如果fast->next 不为空,则 slow 和 fast 同时走一步直到 fast->next == NULL

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
int kthToLast(struct ListNode* head, int k) {
    struct ListNode* slow = head, *fast = head;
    int i = 0;
    while(i < k-1){
        fast = fast->next;
        if(fast == NULL){ // k大于有效节点个数的情况
            return -1;
        }
        i++;
    }
    while(fast->next != NULL){
        fast = fast->next;
        slow = slow->next;
    }
    return slow->val;
    
}

5. 合并两个有序链表

题目链接 : 合并两个有序链表

思路 : 依次比较, 取小的尾插

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    struct ListNode* l1 = list1;
    struct ListNode* l2 = list2;
    struct ListNode* head = NULL;
    struct ListNode* tail = NULL; 
    // 若 l1 是空的情况,直接返回list2
    if(l1 == NULL){
        return l2;
    }
    // 若 l2 是空的情况,直接返回list1
    if(l2 == NULL){
        return l1;
    }
    // 循环,条件是两者都不为空继续,一个为空则结束
    while(l1 && l2){
        // 如果 l1的值大于等于l2
        if(l1->val >= l2->val){
            // 这个很重要! 要先判断我们的头是否为空,是空就初始化一下
            if(head == NULL){
                head = l2;
                tail = l2;
                l2 = l2->next; // 将 l2 的指向移动到下一个节点
            }
            // head不为空
            else{
                tail->next = l2;
                l2 = l2->next;
                tail = tail->next;
            }
        }
        // l1的概念与l2一样
        else{
            if(head == NULL){
                head = l1;
                tail = l1;
                l1 = l1->next;
            }
            else{
                tail->next = l1;
                l1 = l1->next;
                tail = tail->next;
            }
        }
    }
    // 假设循环结束后l1仍有节点,则直接将l1整体尾插到tail后
    // 如: 循环结束后 l1还有两个节点 2->3->NULL
    // 那直接尾插就相当于在当前的tail后插入 tail->2->3->NULL (tail取决于循环排序后的最后一个节点是什么)
    if(l1){
        tail->next = l1;
    }
    // 同上
    if(l2){
        tail->next = l2;
    }
    // 最后返回head
    return head;
}

6. 链表分割

题目链接 : 链表分割

思路1 : 大于等于 x 的一个新链表,小于 x 的一个新链表,在小于x的新链表的最后一个节点后插入大于等于x的新链表

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

// 思路 : 大于等于x的一个新链表,小于 x 的一个新链表
// 在小于x的新链表的最后一个节点后插入大于等于x的新链表
struct ListNode* partition(struct ListNode* head, int x) {
    if (head == NULL) {
        return head;
    }
    struct ListNode* cur = head;
    struct ListNode *newnode1 = NULL, *tail1 = NULL;
    struct ListNode *newnode2 = NULL, *tail2 = NULL;
    while (cur) {
        if (cur->val >= x) {
            if (newnode1 == NULL) {
                newnode1 = tail1 = cur;
                cur = cur->next;
            } else {
                tail1->next = cur;
                tail1 = tail1->next;
                cur = cur->next;
            }
        } else {
            if (newnode2 == NULL) {
                newnode2 = tail2 = cur;
                cur = cur->next;
            } else {
                tail2->next = cur;
                tail2 = tail2->next;
                cur = cur->next;
            }
        }
    }
    // 假设newnode1为空 ( 也就是所有值都小于x )
    if(newnode1 == NULL){
        return newnode2;
    }	
    // 所有值都大于 x 的情况
    if(newnode2 == NULL){
        return newnode1;
    }
    
    tail2->next = newnode1;

    tail1->next = NULL;

    return newnode2;
}

思路 2 : 与第一种思路相同,但是采用哨兵位的方式做。好处是可以不用处理这么多情况 ( 所有值都大于x 或 所有值小于x 等等 )

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* partition(struct ListNode* head, int x) {
    struct ListNode* cur = head;
    struct ListNode* LessHead, *LessTail;
    struct ListNode* GreaterHead,*GreaterTail;
    
    // 哨兵位头节点
    LessHead = LessTail = (struct ListNode*)malloc(sizeof(struct ListNode));
    GreaterHead = GreaterTail = (struct ListNode*)malloc(sizeof(struct ListNode));
    GreaterTail->next = LessTail->next = NULL;

    while(cur){
        if(cur->val >= x){
            GreaterTail->next = cur;
            GreaterTail = GreaterTail->next;       
        }
        else{ 
            LessTail->next = cur;
            LessTail = LessTail->next;
        }
        cur = cur->next;
    }
    LessTail->next = GreaterHead->next;
    GreaterTail->next = NULL;

    cur =  LessHead->next;
    free(LessHead);
    free(GreaterHead);
    return cur;
}

7. 链表的回文结构

题目链接 : 链表的回文结构

思路 : 先找中间节点,从中间节点开始,对后半链表进行反转,再和前半段链表进行比较

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* middleNode(struct ListNode* head) {
    if(head == NULL){
        return NULL;
    }
    struct ListNode* fast = head;
    struct ListNode* slow = head;

    while(fast && fast->next != NULL){
		slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* cur = head;
    struct ListNode* prev = NULL;
    struct ListNode* nextNode = NULL;
    
    while (cur) {
        nextNode = cur->next;  // 保存下一个节点
        cur->next = prev;      // 反转当前节点的指针
        prev = cur;            // 移动 prev 到当前节点
        cur = nextNode;        // 移动 cur 到下一个节点
    }
    
    return prev;  // prev 现在是新的头节点
}

bool isPalindrome(struct ListNode* head){
    struct ListNode* mid = middleNode(head);
    struct ListNode* rhead = reverseList(mid);
    
    while(head && rhead){
        if(head->val != rhead->val){
            return false;
        }
        head = head->next;
        rhead = rhead->next;
    }
    return true;
}

8. 相交链表

题目链接 : 相交链表

思路 :

  1. 先求两个链表的长度
  2. 让长的链表先走两个链表长度的差值步
  3. 然后两个链表同时走,第一个地址一样的节点就是相交的节点
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode* curA,*tailA;
    struct ListNode* curB,*tailB;

    curA = tailA = headA;
    curB = tailB = headB;
    
    int countA = 0,countB = 0;


    // 找尾
    while(tailA->next != NULL){
        countA++;
        tailA = tailA->next;
    }
    while(tailB->next != NULL){
        countB++;
        tailB = tailB->next;
    }
    // 判断尾节点是否相等,如果不相等 ( 没有相交的点 直接返回空,反之找相交的点 )
    if(tailA != tailB){
        return NULL; 
    }
    // 先找哪个链表长度比较长,针对长的那个先让它走两个链表长度差的步数
    if(countA != countB){
        if(countA > countB){
            countA -=countB;
            while(countA--){
                curA = curA->next;
            }
        }
        else{
            countB -= countA;
            while(countB--){
                curB = curB->next;
            }
        }
    }
    // 接着让两个链表同时走,找到相同地址的第一个节点就是相交节点
    while(curA && curB){
        if(curA != curB){
            curA = curA->next;
            curB = curB->next;
        }
        else{
            break;
        }
    }
    return curA;
}
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode* curA,*tailA;
    struct ListNode* curB,*tailB;

    curA = tailA = headA;
    curB = tailB = headB;
    
    int lenA = 0,lenB = 0;


    // 找尾
    while(tailA->next != NULL){
        lenA++;
        tailA = tailA->next;
    }
    while(tailB->next != NULL){
        lenB++;
        tailB = tailB->next;
    }
    // 判断尾节点是否相等,如果不相等 ( 没有相交的点 直接返回空,反之找相交的点 )
    if(tailA != tailB){
        return NULL;
    }
    struct ListNode* longlist = headA, *shortlist = headB;
    int gap = abs(lenA-lenB);
    if(lenA < lenB){
        longlist = headB;
        shortlist = headA;
    }

    while(gap--){
        longlist = longlist->next;
    }

    while(longlist && shortlist){
        if(longlist != shortlist){
            longlist = longlist->next;
            shortlist = shortlist->next;
        }
        else{
            break;
        }
    }
    return longlist;
    
}

9. 环形链表

题目链接 : 环形链表

思路 :

快慢指针,慢指针一次走一步,快指针一次走两步,两个指针从链表其实位置开始运行

如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
bool hasCycle(struct ListNode *head) {
    struct ListNode* slow, *fast;
    slow = fast = head;

    while(slow && fast && fast->next != NULL){
        fast = fast->next->next;
        slow = slow->next;

        if(fast == slow){
            return true;
        }
    }

    return false;

    
}

延伸问题:

  1. 为什么快指针走两步,慢指针走一步的方法可行?

    假设环存在,当慢指针进环时没有和快指针碰上,则快指针就会开始追击慢指针。N快指针每次走两步;慢指针每次走一步。也就是说,两个指针进入环后每次的距离都会缩减一步直到两个指针碰面。因此快指针走两步,慢指针走一步的方法是可行的。

  2. 快指针一定只能是走两步吗? 可不可以快指针是大于两步的数值,但慢指针仍是一步?

    答案是不可行的,以三步为例。当慢指针走一步,快指针走三步,假设环存在,则在环中,两个指针每次的距离都会缩减两步

    两个指针的距离变化大概是这样

    N
    N-2
    N-4
    ...
    可以减到0吗?
    如果N是偶数,则可以,N是奇数就不行
    当快指针走的步数大于2的话,都是有可能追不上的
    除非同时控制块慢指针,让他们每次的距离都是缩减2步,就能追上
    

10. 环形链表II

题目链接 : 环形链表II

思路 : 让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环运行,两个指针都是每次均走一步,最终肯定会在入口点的位置相遇

证明 : 在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* detectCycle(struct ListNode* head) {
    struct ListNode *mmet, *slow, *fast;
    meet = slow = fast = head;

    while (fast && fast->next != NULL) {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast){
            break;
        }
    }
    if (fast == NULL || fast->next == NULL) {
        return NULL;
    }
    while (meet != slow) {
        meet = meet->next;
        if (meet == slow) {
            break;
        } else {
            slow = slow->next;
        }
    }
    return meet;
}

思路2 : 在相遇点断开,再让两个链表求交点

    /**
    * Definition for singly-linked list.
    * struct ListNode {
    *     int val;
    *     struct ListNode *next;
    * };
    */
    struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode* curA,*tailA;
    struct ListNode* curB,*tailB;

    curA = tailA = headA;
    curB = tailB = headB;
    
    int countA = 0,countB = 0;


    // 找尾
    while(tailA->next != NULL){
        countA++;
        tailA = tailA->next;
    }
    while(tailB->next != NULL){
        countB++;
        tailB = tailB->next;
    }
    // 判断尾节点是否相等,如果不相等 ( 没有相交的点 直接返回空,反之找相交的点 )
    if(tailA != tailB){
        return NULL; 
    }
    // 先找哪个链表长度比较长,针对长的那个先让它走两个链表长度差的步数
    if(countA != countB){
        if(countA > countB){
            countA -=countB;
            while(countA--){
                curA = curA->next;
            }
        }
        else{
            countB -= countA;
            while(countB--){
                curB = curB->next;
            }
        }
    }
    // 接着让两个链表同时走,找到相同地址的第一个节点就是相交节点
    while(curA && curB){
        if(curA != curB){
            curA = curA->next;
            curB = curB->next;
        }
        else{
            break;
        }
    }
    return curA;
}
    struct ListNode* detectCycle(struct ListNode* head) {
        struct ListNode *meet, *slow, *fast;
        meet = slow = fast = head;

        while (fast && fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
            if(slow == fast){
                //求list1和list2的交点
                meet = slow;
                struct ListNode* list1 = meet->next;
                struct ListNode* list2 = head;
                fast->next = NULL;
                return getIntersectionNode(list1,list2);
            }
        }
        return NULL;
    }
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值