leetcode刷题(三)

面试题 02.04. 分割链表

编写程序以 x 为基准分割链表,使得所有小于 x 的节点排在大于或等于 x 的节点之前。如果链表中包含 x,x 只需出现在小于 x 的元素之后(如下所示)。分割元素 x 只需处于“右半部分”即可,其不需要被置于左右两部分之间。

翻译:将小于x的数移到大于或者等于x的数之前

题解

先找到第一个大于或者等于x的节点\(pos\),然后遍历之后的节点,发现小于x的节点就移到\(pos\)之前。考察的操作:链表节点的删除插入

实际上直接插在头节点之前也行。整道题看起是不是很像快排的\(partion\)操作,说明能用链表实现快排 ><

class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        if (!head || !head->next) return head;

        ListNode* root = new ListNode(-1);
        root->next = head;

        head = root;
        while(head->next != NULL) {
            if ((*(head->next)).val >= x) break;
            head = head->next;
        }

        ListNode* start = head, *pre = head;
        head = head->next; // 上面的while循环得到的是第一个大于或者等于x的节点 之前的节点

        while(head != NULL) {
            if ((*head).val < x) {
                pre->next = head->next;
                head->next = start->next;
                start->next = head;
            
                head = pre->next;
            }
            else {
                pre = head;
                head = head->next;
            }
        }

        return root->next;
    }
};

1367. 二叉树中的列表

给你一棵以 root 为根的二叉树和一个 head 为第一个节点的链表。

如果在二叉树中,存在一条一直向下的路径,且每个点的数值恰好一一对应以 head 为首的链表中每个节点的值,那么请你返回 True ,否则返回 False 。

一直向下的路径的意思是:从树中某个节点开始,一直连续向下的路径

题解

思路比较明显:枚举节点,然后判断以这个节点开头是否能和给的链表匹配成功。(真的暴力)

md,复杂度计算错了。尽管在一次匹配过程中可能要遍历\(2^{len}\)个节点,但实际的树只有\(n\)个节点,所以时间复杂度\(O(n*min(2^{len}, n))\)。另外,我都不会写带返回值的DFS了,难受~~~

/*
不太喜欢这样的题,不过官方的code真的很nice,学习学习(https://leetcode-cn.com/problems/linked-list-in-binary-tree/solution/er-cha-shu-zhong-de-lie-biao-by-leetcode-solution/)
*/
class Solution {
public:
    bool DFS(ListNode* head, TreeNode* root) {
        if (head == NULL) return true;
        if (root == NULL) return false;
        if ((*head).val != (*root).val) return false;
        return DFS(head->next, root->left) || DFS(head->next, root->right);
    }
    bool isSubPath(ListNode* head, TreeNode* root) {
        if (head == NULL) return true;
        if (root == NULL) return false;
        return DFS(head, root) || isSubPath(head, root->left) || isSubPath(head, root->right);
    }
};

面试题35. 复杂链表的复制(好题)

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

题解

在每个节点的后面插入一个新的节点,更新random指针后,取出奇数节点,就是复制后的链表。

想到用map实现也算是妙啊

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if (head == NULL) return head;

        map<Node*, Node*> mp;     // 知道 map 和 unordered_map 的区别吗

        Node* start = head;
        while(start != NULL) {
            mp[start] = new Node(start->val);
            start = start->next;
        }

        start = head;
        while(start != NULL) {
            mp[start]->next = mp[start->next];
            mp[start]->random = mp[start->random];
            start = start->next;
        }

        return mp[head];
    }
};

1171. 从链表中删去总和值为零的连续节点

给你一个链表的头节点 head,请你编写代码,反复删去链表中由 总和 值为 0 的连续节点组成的序列,直到不存在这样的序列为止。

删除完毕后,请你返回最终结果链表的头节点。

你可以返回任何满足题目要求的答案。

题解

常规思路是枚举区间,时间复杂度\(O(n^2)\).

连续区间的和可由前缀和相减得到,所以只需要判断当前位置的前缀和是否在该位置以前出现过,出现,则说明这两个位置之间的区间和为0。因为是链表,故用map存一下拥有相同前缀和的节点。时间复杂度大概是\(O(nlog_2(n))\)

加一个限制条件:最终序列的长度要最短。怎么破?

class Solution {
public:
    ListNode* removeZeroSumSublists(ListNode* head) {
        ListNode* root = new ListNode(-1);
        root->next = head;

        ListNode* h = root;

        map<int, ListNode*> mp;
        mp[0] = root;

        int sum = 0, temp_sum = 0;
        while(h->next != NULL) {
            h = h->next;
            sum += h->val;
            if (mp.find(sum) != mp.end()) {
                ListNode* temp = mp[sum]->next;
                mp[sum]->next = h->next;
                
                temp_sum = sum;
                while(temp != h) {
                    temp_sum += temp->val;
                    mp.erase(temp_sum);      // 删除和为0的整个区间,避免干扰(红黑树的删除操作要调整节点,能不用就不用)
                    temp = temp->next;
                }
            }
            else mp[sum] = h;
        }

        return root->next;
    }
};

1019. 链表中的下一个更大节点

给出一个以头节点 head 作为第一个节点的链表。链表中的节点分别编号为:node_1, node_2, node_3, ... 。

每个节点都可能有下一个更大值(next larger value):对于 node_i,如果其 next_larger(node_i) 是 node_j.val,那么就有 j > i 且 node_j.val > node_i.val,而 j 是可能的选项中最小的那个。如果不存在这样的 j,那么下一个更大值为 0 。

返回整数答案数组 answer,其中 answer[i] = next_larger(node_{i+1}) 。

题解

单调栈,从后向前遍历,如果当前元素小于栈顶则压入,否则,就弹出栈顶元素。

说哈复杂度,每个数都只入栈一次,所以\(n\)次循环中,总的入栈出栈次数最大可能是\(2n\),故复杂度\(O(n)\)

class Solution {
public:
    vector<int> nextLargerNodes(ListNode* head) {
        vector<int> num;
        while(head != NULL) num.push_back(head->val), head = head->next;

        int n = (int)num.size();
        stack<int> st;
        st.push(0x3f3f3f3f);

        //vector<int> ans(n, 0);  少用一个vector,空间减少了,时间倒增加了几十ms
   
        for (int i = n - 1; i >= 0; --i) {
            while(num[i] >= st.top()) st.pop();

            int t = num[i];
            num[i] = st.top();

            st.push(t);
        }

        for (int &x: num) if (x == 0x3f3f3f3f) x = 0;
        return num;
    }
};

面试题 02.03. 删除中间节点

实现一种算法,删除单向链表中间的某个节点(除了第一个和最后一个节点,不一定是中间节点),假定你只能访问该节点

题解

刷多了思维有点僵化,md,调换值就可以了

/*   如果是面试,多半就over了   */
class Solution {
public:
    void deleteNode(ListNode* node) {
        if (node == NULL) return;
        while(node->next != NULL) {
            node->val = node->next->val;
            if (node->next->next == NULL) {
                node->next = NULL;
                break;
            }
            node = node->next;
        }
    }
};

实际上,将下一个节点的值赋给当前节点,然后删除下一个节点

class Solution {
public:
    void deleteNode(ListNode* node) {
        node->val = node->next->val;
        node->next = node->next->next;
    }
};
posted @ 2020-04-29 16:24  天之道,利而不害  阅读(409)  评论(0编辑  收藏  举报