关于链表的笔试、面试题

以下题目来源于leetcode

有两个链表,它们表示逆序的两个非负数。计算出两个数的和之后,同样逆序输出作为一个链表
比如:(2->1->3)+ (5->6->4)
得到:7->0->8

//Add two Numbers
class Solution
{
public:
    ListNode *AddTwoNumbers(ListNode* l1, ListNode* l2)
    {
        ListNode dummy(-1);//投节点
        int carry = 0;
        ListNode* prev = &dummy;
        for (ListNode* pa = l1, *pb = l2; pa != NULL || pb != NULL;
            pa = pa == nullptr ? 0 : pa->next, pb = pb == nullptr ? 0 : pb->next,
            prev = prev->next)
        {
            const int a = pa == nullptr ? 0 : pa->val;
            const int b = pb == nullptr ? 0 : pb->val;
            const int value = (a + b + carry) % 10;
            carry = (a + b + carry) / 10;
            prev->next = new ListNode(value);//尾插
        }
        if (carry > 0)
            prev->next = new ListNode(carry);
        return dummy.next;
    }
};

把链表中m到n的部分反转(1<=m<=n<=length)。

注意要求:在原地反转,也就是不能申请额外的空间,且只能遍历一遍。
比如:1->2->3->4->5->nullptr,m=2,n=4
return 1->4->3->2->5->nullptr
1<=m<=n<=length

ListNode* reverseBetween(ListNode *head, int m, int n)
    {
        ListNode dummy(-1);
        dummy.next = head;

        ListNode *prev = &dummy;
        for (int i = 0; i < m - 1; i++)
        {
            prev = prev->next;
        }
        ListNode* const head2 = prev;

        prev = head2->next;
        ListNode* cur = prev->next;
        for (int i = m; i < n; i++)
        {
            prev->next = cur->next;
            cur->next = head2->next;
            head2->next = cur;
            cur = prev->next;
        }
        return dummy.next;
    }

给定一个单链表和一个x,把链表中小于x的放到前面,大于等于x的放到后面,每部分元素的原始相对位置不变。
遍历一遍链表,把小于x的都挂到head1后,把大于等于x的都放到head2后,最后再把大于等于的链表挂到小于链表的后面就可以了。

ListNode* partition(ListNode* head, int x)
    {
        ListNode left_dummy(-1);//头结点
        ListNode right_dummy(-1);//头结点

        auto left_cur = &left_dummy;
        auto right_cur = &right_dummy;

        for (ListNode* cur = head; cur; cur = cur->next)
        {
            if (cur->val < x)
            {
                left_cur->next = cur;
                left_cur = cur;
            }
            else
            {
                right_cur->next = cur;
                right_cur = cur;
            }
        }
        left_cur->next = right_dummy.next;
        right_cur->next = nullptr;
        return left_dummy.next;
    }

去除链表中的重复元素,使之只出现一次。
比如:1->1->2,return 1->2
1->1->2->3->3,return 1->2->3

ListNode *delDuplicates(ListNode* head)
    {
        if (head == nullptr)
            return nullptr;
        for (ListNode* prev = head, *cur = head->next; cur; cur = cur->next)
        {
            if (prev->val == cur->val)
            {
                prev->next = cur->next;
                delete cur;
            }
            else
                prev = cur;
        }
        return head;
    }

去除链表中的重复元素,使之一次也不出现。
比如:1->2->3->3->4->4->5 return 1->2->5
1->1->1->2->3 return 2->3

//使链表中重复的元素一次也不出现
    ListNode* delDuplicate(ListNode* head)
    {
        if (!head || !head->next)
            return head;
        ListNode* p = head->next;
        if (head->val == p->val)
        {
            while (p && head->val == p->val)
            {
                ListNode* tmp = p;
                p = p->next;
                delete p;
            }
            delete head;
            return delDuplicate(p);
        }
        else
        {
            head->next = delDuplicate(head->next);
            return head;
        }
    }

在k位置处旋转链表
比如:->2->3->4->5 k = 2
return 4->5->1->2->3
先遍历一遍,得出链表长度len.注意k可能大于len,所以令 k%=len,,将尾节点next指针指向首节点,形成一个环,接着往后走len-k步,从这里断开环,就是要求的结果了。

ListNode* rotateRight(ListNode* head, int k)
    {
        if (head == nullptr || k == 0)
            return head;
        int len = 1;
        ListNode* p = head;
        while (p->next)
        {
            len++;
            p = p->next;
        }
        k = len - k%len;
        p->next = head; // 首尾相连构成环
        for (int step = 0; step < k; step++)
        {
            p = p->next;//接着向后走
        }
        head = p->next; //新的首节点
        p->next = nullptr;//断开环
        return head;
    }

成对交换节点
比如:1->2->3->4 return 2->1->4->3
题目规定不能直接交换两个节点的值,所以我们只能交换两个节点。

ListNode* swapPairs(ListNode* head)
    {
        if (head == NULL)
            return NULL;
        if (head->next == NULL)
            return head;
        ListNode* tmp = head->next;
        head->next = swapPairs(tmp->next);
        tmp->next = head;
        return tmp;
    }

给定一个链表,把最后一个结点插入到第1个结点后,倒数第二个结点插入到第2个结点后,倒数第三个结点插入到第3个结点后,以此类推……
找到中间节点,将后面的链表reverse,再合并前后的两部分

//找到中间位置,翻转后面的链表并合并
    void RecorderList(ListNode *head)
    {
        if (head == NULL || head->next == NULL)
            return;
        ListNode* slow = head, *fast = head, *prev = NULL;
        while (fast && fast->next)
        {
            prev = slow;
            slow = slow->next;
            fast = fast->next->next;
        }
        prev->next = NULL;//cut at middle
        slow = Reverse(slow);

        //merge two lists
        ListNode *cur = head;
        while (cur->next)
        {
            ListNode* tmp = cur->next;
            cur->next = slow;
            slow = slow->next;
            cur->next->next = tmp;
            cur = tmp;
        }
        cur->next = slow;
    }

    ListNode* Reverse(ListNode* head)
    {
        ListNode dummy(-1);//头节点
        dummy.next = head;
        ListNode* prev = head;
        ListNode* cur = prev->next;
        while (cur)
        {
            prev->next = cur->next;
            cur->next = dummy.next;
            dummy.next = cur;
            cur = prev->next;
        }
        return dummy.next;
    }

把原始链表k个k个的反转,如果最后剩余的不到k个结点,那么保持不变。

这道题让我们以每k个为一组来翻转链表,实际上是把原链表分成若干小段,然后分别对其进行翻转,那么肯定总共需要两个函数,一个是用来分段的,一个是用来翻转的,我们就以题目中给的例子来看,对于给定链表1->2->3->4->5,一般在处理链表问题时,我们大多时候都会在开头再加一个dummy node,因为翻转链表时头结点可能会变化,为了记录当前最新的头结点的位置而引入的dummy node,那么我们加入dummy node后的链表变为-1->1->2->3->4->5,如果k为3的话,我们的目标是将1,2,3翻转一下,那么我们需要一些指针,pre和next分别指向要翻转的链表的前后的位置,然后翻转后pre的位置更新到如下新的位置:

-1->1->2->3->4->5
 |           |
pre         next

-1->3->2->1->4->5
          |  |
         pre next
//将链表元素k个k个的旋转
    ListNode* reversekGroup(ListNode* head, int k)
    {
        if (!head || k == 1)
        {
            return head;
        }
        ListNode dummy(-1);
        ListNode* prev = &dummy, *cur = head;
        dummy.next = head;
        int i = 0;
        while (cur)
        {
            ++i;
            if (i % k == 0)
            {
                prev = reverseOnegroup(prev, cur->next);
                cur = prev->next;
            }
            else
            {
                cur = cur->next;
            }
        }
        return dummy.next;
    }

    ListNode* reverseOnegroup(ListNode* prev, ListNode* next)
    {
        ListNode *last = prev->next;
        ListNode *cur = last->next;
        while (cur != next)
        {
            last->next = cur->next;
            cur->next = prev->next;
            prev->next = cur;
            cur = last->next;
        }
        return last;
    }

设计并实现一个支持get和set操作的缓存:

get(key) - 存在时返回其值,否则返回-1;

set(key) - 不存在时插入新值,存在时更新其值,注意当容量满时,需删除最长时间没有访问的key,将其删除,并插入新的key。

这题的大致思路就是用一个hash表来保存已经存在的key, 然后用另外一个线性容器来存储其key-value值, 我们可以选择链表list, 因为需要调整结点的位置, 而链表可以在O(1)时间移动结点的位置, 数组则需要O(n).
如果新来一个set请求, 我们先去查hash表
1. 如果已经存在了这个key, 那么我们需要更新其value, 然后将其在list的结点位置移动到链表首部.
2. 如果不存在这个key, 那么我们需要在hash表和链表中都添加这个值, 并且如果添加之后链表长度超过最大长度, 我们需要将链表尾部的节点删除, 并且删除其在hash表中的记录
如果来了一个get请求, 我们仍然先去查hash表, 如果key存在hash表中, 那么需要将这个结点在链表的中的位置移动到链表首部.否则返回-1.

class LRUcache
{
public:
    LRUcache(int capacity)
    {
        _capacity = capacity;
    }

    int get(int key)
    {
        if (cacheMap.find(key) == cacheMap.end())
            return -1;

        //把当前访问节点移到链表头部,并且更新map中该节点的位置
        cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]);
        cacheMap[key] = cacheList.begin();
        return cacheMap[key]->value;
    }

    void set(int key, int value)
    {
        if (cacheMap.find(key) == cacheMap.end)
        {
            //删除链表尾部节点(最少访问的节点)
            if (cacheList.size() == _capacity)
            {
                cacheMap.erase(cacheList.back().key);
                cacheList.pop_back();
            }
            //插入新节点到链表头部,并且在map中增加该节点
            cacheList.push_front(CacheNode(key, value));
            cacheMap[key] = cacheList.begin();
        }
        else
        {
            //更新节点的值,把当前访问的节点移到链表头部,并且更新map中该节点的地址
            cacheMap[key]->value = value;
            cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]);
            cacheMap[key] = cacheList.begin();
        }
    }
private:
    struct CacheNode
    {
        int key;
        int value;
        CacheNode(int k, int v)
            :key(k)
            , value(v)
        {}
    };
    list cacheList;
    unordered_map<int, list::iterator> cacheMap;
    int _capacity;
};

你可能感兴趣的:(面试题)