【方法总结与错误反思】链表面试题

目录

1、移除链表元素

2、反转链表

3、链表的中间结点

4、合并两个有序链表

5、链表分割

6、链表的回文结构

7、传二级指针与传一级指针的区别

8、经验总结


1、移除链表元素

思路一:使用两个指针,在原来的链表上进行指向的修改。 

错误反思:对空指针的引用

改正:头删时,直接改头指针head,使其指向下一个结点。

struct ListNode* removeElements(struct ListNode* head, int val) 
{
    struct ListNode *pre, *cur;
    pre = NULL;
    cur = head;

    while(cur)
    {
        if(cur->val != val)
        {
            pre = cur;
            cur = cur->next;
        }
        else
        {
            if(pre == NULL) //头删 --> 改头指针
            {
                head = cur->next;
                free(cur);
                cur = head;
            }
            else
            {
                pre->next = cur->next;
                free(cur);
                cur = pre->next;
            }
        }
    }
    return head;
}

思路二:将值不是val的结点,尾插到一个新的链表中。

易错一:最后tail->next要置空!

易错二:头删时要防止对空指针的引用。

struct ListNode* removeElements(struct ListNode* head, int val) {
    if(head == NULL)
    {
        return NULL;
    }

    struct ListNode *newhead, *tail, *cur;
    newhead = tail = NULL;
    cur = head;

    while(cur)
    {
        if(cur->val != val)
        {
            //尾插
            if(tail == NULL)
            {
                newhead = tail = cur;
            }
            else
            {
                tail->next = cur;
                tail = tail->next;
            }
            cur = cur->next;
        }
        else
        {
            struct ListNode *next = cur->next;
            free(cur);
            cur = next ;
        }
    }

    if(tail)
    {
        tail->next = NULL;
    }

    return newhead;
}

2、反转链表

思路一:头插法——每一个结点都进行头插

struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode *newhead,*cur;
    newhead = NULL;
    cur = head;

    while(cur)
    {
        struct ListNode *next = cur->next;
        cur->next = newhead;
        newhead = cur;
        cur = next;
    }

    return newhead;
}

思路二:三指针翻转法——使用三个相邻指针,原地调转结点指向。

struct ListNode* reverseList(struct ListNode* head) {
    if (head == NULL) {
        return NULL;
    }

    struct ListNode *n1, *n2, *n3;
    n1 = NULL;
    n2 = head;
    n3 = head->next;

    while (n2) {
        n2->next = n1;
        n1 = n2;
        if (n3 == NULL) {
            return n2;
        } else {
            n2 = n3;
            n3 = n3->next;
        }
    }

    return n2;
}

3、链表的中间结点

思路一:(传统玩法,容易想到,但效率不高)

遍历两遍,第一遍求出总结点个数,第二遍历到中间节点返回。

struct ListNode* middleNode(struct ListNode* head)
{
    //1、先统计总结点的个数
    //奇数个有1个中间结点,偶数个有2个中间结点,返回第二个中间结点
    //2、遍历链表,奇偶都返回count/2 + 1处的结点
    int count = 0;
    int mid = 0;
    struct ListNode* cur = head;
    if (cur->next == NULL)
    {
        return cur;
    }
    else
    {
        while (cur)
        {
            count++;
            cur = cur->next;
        }
        cur = head;
        while (cur)
        {
            mid++;
            if (mid == count / 2 + 1)
            {
                break;
            }
            cur = cur->next;
        }
        return cur;
    }
}

思路二:(只需遍历一遍)

使用快慢指针,slow和fast,slow一次走一步,fast一次走两步,当fast ->next == NULL或fast == NULL时,slow则位于中间位置。

错误反思:

一处次序不同,结果就截然不同!这是因为&&是从左至右执行,当fast为NULL时,fast ->

next 就对空指针进行了引用。

4、合并两个有序链表

错误反思:没有对malloc开辟的结点的next指针进行初始化!

  • 没有对开辟的guard结点的next进行置空!这会导致当两个链表都为空时,返回的不一定是空。
  • 如果next指针没有初始化为NULL,它的值可能是任意的,这可能导致程序不确定的行为。如果next指针指向了不受控制的内存区域,这可能会导致程序崩溃或安全漏洞。

改正:

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    struct ListNode* cur1 = list1;
    struct ListNode* cur2 = list2;
    struct ListNode* guard = NULL, *tail = NULL;
    guard  = tail = (struct ListNode*)malloc(sizeof(struct ListNode));
    tail->next = NULL;

    while (cur1 && cur2)
    {
        if (cur1->val < cur2->val)
        {
            tail->next = cur1;
            tail = tail->next;
            cur1 = cur1->next;

        }
        else
        {
            tail->next = cur2;
            tail = tail->next;
            cur2 = cur2->next;
        }
    }

    if (cur1)
    {
        tail->next = cur1;
    }

    if (cur2)
    {
        tail->next = cur2;
    }

    struct ListNode* head = guard->next;
    free(guard);
    guard = NULL;
    return head;
}

5、链表分割

错误反思:

  • 没有将为尾指针的next置空,导致形成循环链表。
  • 开辟的内存空间没有释放,导致内存泄漏。虽然这在oj上检测不出来,但是面试的时候这反映的是你代码能力的严谨性与规范性,面试官会考察!

改正:

class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        struct ListNode* smallhead, * bighead = NULL;
        struct ListNode* smalltail, * bigtail = NULL;

        smallhead = smalltail = (struct ListNode*)malloc(sizeof(struct ListNode));
        bighead = bigtail = (struct ListNode*)malloc(sizeof(struct ListNode));

        smalltail->next = NULL;
        bigtail->next = NULL;

        struct ListNode* cur = pHead;

        while (cur)
        {
            if (cur->val < x)
            {
                smalltail->next = cur;
                smalltail = smalltail->next;
            }
            else
            {
                bigtail->next = cur;
                bigtail = bigtail->next;
            }
            cur = cur->next;
        }

        smalltail->next = bighead->next;
        bigtail->next = NULL;

        pHead = smallhead->next;

        free(smallhead);
        free(bighead);

        return pHead;
    }
};

6、链表的回文结构

思路:

1、先找中间结点

2、从中间结点开始,将后半段逆置(可调用反转链表函数)

3、比较前半段和后半段是否相等。

class PalindromeList {
public:
    bool chkPalindrome(ListNode* head) {
        struct ListNode* mid = findmidnode(head);
        struct ListNode* midhead = reverselist(mid);

        while(midhead)
        {
            if(head->val != midhead->val)
            {
                return false;
            }
            else
            {
                head = head->next;
                midhead = midhead->next;
            }
        }
        
        return true;
    }
};

7、传二级指针与传一级指针的区别

传二级指针:会使用到原链表的头指针,并对原链表的头指针进行修改,返回的是原来的头指针。

传一级指针:会创建新的指针来对链表进行修改,返回的是新的头指针,需要定义一个新的指针来接收函数的返回值。

8、经验总结

  1. 谨慎在一个链表中既插入又删除。单链表中插入、删除不容易,尾插、头插较简单,一旦中间插入、删除就更麻烦。
  2. 超出内存限制的一种可能是死循环。
  3. 回文结构——对称。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值