2018年力扣高频算法面试题6链表

之前在CSDN写过一篇 链表与快慢指针 的笔记(判断链表是否有环、找到环的入口、反转链表):

删除链表中的节点

请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。
分析:之前有同学去哈深面试也问过类似的问题。没有给我们链表的起点,只给我们了一个要删的节点,跟我们以前遇到的情况不太一样,我们之前要删除一个节点的方法是要有其前一个节点的位置,然后将其前一个节点的next连向要删节点的下一个,然后delete掉要删的节点即可。这道题的处理方法是先把当前节点的值用下一个节点的值覆盖了,然后我们删除下一个节点即可
吐槽:谁能想到还可以用替身攻击,还可以把下一个的复制到当前节点,然后把下一个节点的删掉,神奇!!!

class Solution {
public:
    void deleteNode(ListNode* node) {
           ListNode* old_node_next = node->next;
            node->val = old_node_next->val;
            node->next = old_node_next->next;
            delete old_node_next;
    }
};

复制带随机指针的链表

给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
要求:返回这个链表的深拷贝,必须返回给定头的拷贝作为对克隆列表的引用。
其实,,,完全没看懂这个题。。。
解答:使用递归的解法,写起来相当的简洁,还是需要一个 HashMap 来建立原链表结点和拷贝链表结点之间的映射。在递归函数中,首先判空,若为空,则返回空指针。然后就是去 HashMap 中查找是否已经在拷贝链表中存在了该结点,是的话直接返回。否则新建一个拷贝结点 res,然后建立原结点和该拷贝结点之间的映射,然后就是要给拷贝结点的 next 和 random 指针赋值了,直接分别调用递归函数即可,参见代码如下:

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;

    Node() {}

    Node(int _val, Node* _next, Node* _random) {
        val = _val;
        next = _next;
        random = _random;
    }
};
*/
class Solution {
public:
    Node* copyRandomList(Node* head) {
        unordered_map<Node*, Node*> m;
        return helper(head, m);
    }
    Node* helper(Node* node, unordered_map<Node*, Node*>& m) {
        if (!node) return nullptr;
        if (m.count(node)) return m[node];
        Node *res = new Node(node->val, nullptr, nullptr);
        m[node] = res;
        res->next = helper(node->next, m);
        res->random = helper(node->random, m);
        return res;
    }
};

相交链表

编写一个程序,找到两个单链表相交的起始节点。
要求:如果两个链表没有交点,返回 null.在返回结果后,两个链表仍须保持原有的结构。可假定整个链表结构中没有循环。程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
这个题真的是easy等级!!!然而我真的没写出来,哭。一开始想的是反转链表,这样就可以从头开始比较,结果题目要求链表必须保持原有的结构。
解析:方法一:分别遍历两个链表,得到分别对应的长度。然后求长度的差值,把较长的那个链表向后移动这个差值的个数,然后一一比较即可

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (!headA || !headB) return NULL;
        int lenA = getLength(headA), lenB = getLength(headB);
        if (lenA < lenB) {
            for (int i = 0; i < lenB - lenA; ++i) headB = headB->next;
        } else {
            for (int i = 0; i < lenA - lenB; ++i) headA = headA->next;
        }
        while (headA && headB && headA != headB) {
            headA = headA->next;
            headB = headB->next;
        }
        return (headA && headB) ? headA : NULL;
    }
    int getLength(ListNode* head) {
        int cnt = 0;
        while (head) {
            ++cnt;
            head = head->next;
        }
        return cnt;
    }
};

方法二:虽然题目中强调了链表中不存在环,但是我们可以用环的思想来做,我们让两条链表分别从各自的开头开始往后遍历,当其中一条遍历到末尾时,我们跳到另一个条链表的开头继续遍历。两个指针最终会相等,而且只有两种情况,一种情况是在交点处相遇,另一种情况是在各自的末尾的空节点处相等。为什么一定会相等呢,因为两个指针走过的路程相同,是两个链表的长度之和,所以一定会相等。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (!headA || !headB) return NULL;
        ListNode *a = headA, *b = headB;
        while (a != b) {
            a = a ? a->next : headB;
            b = b ? b->next : headA;
        }
        return a;
    }
};

排序链表

在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
分析:看到时间复杂度O(nlgn)最先想到的就是把链表的数据存到vector,用sort进行排序,然后把排好序的数据重新建成一个链表…很明显这个方法效率太低.
归并排序:归并排序的核心其实是分治法 Divide and Conquer,就是将链表从中间断开,分成两部分,左右两边再分别调用排序的递归函数 sortList(),得到各自有序的链表后,再进行 merge(),这样整体就是有序的了。

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if (!head || !head->next) return head;
        ListNode *slow = head, *fast = head, *pre = head;
        while (fast && fast->next) {
            pre = slow;
            slow = slow->next;
            fast = fast->next->next;
        }
        pre->next = NULL;
        return merge(sortList(head), sortList(slow));
    }
    ListNode* merge(ListNode* l1, ListNode* l2) {
        if (!l1) return l2;
        if (!l2) return l1;
        if (l1->val < l2->val) {
            l1->next = merge(l1->next, l2);
            return l1;
        } else {
            l2->next = merge(l1, l2->next);
            return l2;
        }
    }
};

补充几个其他类型算法题:

删除排序数组中的重复项

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array
方法一:理解无碍,但是比较慢

nums.erase(unique(nums.begin(),nums.end()),nums.end());
return nums.size();

方法二:leetcode官网给的答案是用双指针做:数组完成排序后,我们可以放置两个指针i和j,其中i是慢指针,而j是快指针。只要nums[i]==nums[j],我们就增加j以跳过重复项。当我们遇到 nums[j]!=nums[i]时,跳过重复项的运行已经结束,因此我们必须把它nums[j]的值复制到nums[i+1]。然后递增i,接着我们将再次重复相同的过程,直到j到达数组的末尾为止。

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int n=nums.size();
        if(n<=1)return n;
        
        int i=0,j=1;
        while(j<n)
        {
            if(nums[i]!=nums[j])
            {
                nums[i+1]=nums[j];
                i++;
            }
             j++;
        }
        return i+1;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章