leetcode刷题记录3: 2021.2.21

文章目录

  1. 区域和检索(题目编号:307,中等,link,2022.1.6)

  • 一种想法,前缀和数组,再用map额外记录哪些位置的元素发生了变化以及变化前后的差值。测试阶段,遍历map中的key(位置下标),如果出现在求和区间,就对区间初始和作调整。结果正确,但是会超时,原因是需要多次遍历map,如果map元素变多,耗时将大幅增加。
  • 使用线段树,把更新和查找区间和的耗时都降为log(N)。线段树数组的长度得是原数组长度的4倍,为什么呢?怎么计算?
class NumArray {
public:
    vector<int> arr, tree;
    int size;

    NumArray(vector<int>& nums) {
        size = nums.size();
        arr = nums;
        tree = vector<int>(nums.size()*4);  //这里的最大容量容易出错
        build_tree(arr, tree, 0, 0, size - 1);
    }

    void build_tree(vector<int>& arr, vector<int>& tree, int node, int start, int end) {
        if(start == end){
            tree[node] = arr[start];
        }
        else{
            int mid = (start + end) / 2;
            int left_node = 2 * node + 1;
            int right_node = 2 * node + 2;
            build_tree(arr, tree, left_node, start, mid);
            build_tree(arr, tree, right_node, mid + 1, end);
            tree[node] = tree[left_node] + tree[right_node];
        }
    }
    
    void update(int index, int val) {
        update_tree(arr, tree, 0, 0, size - 1, index, val);
    }
    
    void update_tree(vector<int>& arr, vector<int>& tree, int node, int start, int end, int index, int val){
        if(start == end){
            arr[index] = val;
            tree[node] = val;
        }
        else{
            int mid = (start + end) / 2;
            int left_node = 2 * node + 1;
            int right_node = 2 * node + 2;
            if(start <= index && mid >= index){
                update_tree(arr, tree, left_node, start, mid, index, val);
            }
            else{
                update_tree(arr, tree, right_node, mid + 1, end, index, val);
            }
            tree[node] = tree[left_node] + tree[right_node];
        }
    }
    int sumRange(int left, int right) {
        return query_tree(arr, tree, 0, 0, size - 1, left, right);
    }

    int query_tree(vector<int>& arr, vector<int>& tree, int node, int start, int end, int L, int R){
        if(L > end || R < start){
            return 0;
        }
        else if(start == end){
            return tree[node];
        }
        else if(L <= start && end <= R){
            return tree[node];
        }
        else{
            int mid = (start + end) / 2;
            int left_node = 2 * node + 1;
            int right_node = 2 * node + 2;
            int sum_left = query_tree(arr, tree, left_node, start, mid, L, R);
            int sum_right = query_tree(arr, tree, right_node, mid+1, end, L, R);
            return sum_left + sum_right;
        }
    }
};

/**
 * Your NumArray object will be instantiated and called as such:
 * NumArray* obj = new NumArray(nums);
 * obj->update(index,val);
 * int param_2 = obj->sumRange(left,right);
 */
  • 还有树状数组的写法?
  1. 二叉搜索树中的插入(题目编号:701,中等link,2022.1.7)

  • 如果当前节点的值小于要插入的值,就往右子树寻找合适的插入位置,否则往左子树找。如果对应子树是空的,那就直接构建一个新的节点,插入完成。
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if(!root) return new TreeNode(val);
        TreeNode* pos = root;
        while(pos){
            if(pos->val > val){
                if(pos->left == nullptr){
                    pos->left = new TreeNode(val);
                    break;
                }
                else{
                    pos = pos->left;
                }
            }
            else{
                if(pos->right == nullptr){
                    pos->right = new TreeNode(val);
                    break;
                }
                else{
                    pos = pos->right;
                }
            }
        }
        return root;
    }
};
  1. 合并K个升序链表(题目编号23:困难,link,2022.1.7)

  • 可选择分治法,每次两两合并,每次链表数量减半,单个链表的长度加倍。
  • 注意容易错的点,合并两个单链表时,先创建一个dummy节点(而不是节点指针,会导致超时错误),再用另一个指针指向该dummy节点。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        return mergeKListsFunc(lists, 0, lists.size() - 1);
    }

    ListNode* mergeKListsFunc(vector<ListNode*>& lists, int start, int end) {
        if(start == end){
            return lists[start];
        }
        if(start > end) return nullptr;
        int mid = (start + end) >> 1;
        return merge2Lists(mergeKListsFunc(lists, start, mid), mergeKListsFunc(lists, mid + 1, end));
    }

    ListNode* merge2Lists(ListNode* L1, ListNode* L2) {
        if(!L1 && L2) return L2;
        if(L1 && !L2) return L1;
        ListNode dummy;
        ListNode *node = &dummy;
        while(L1 && L2){
            if(L1->val < L2->val){
                node->next = L1;
                L1 = L1->next;
            }
            else{
                node->next = L2;
                L2 = L2->next;
            }
            node = node->next;
        }
        if(L1){
            node->next = L1;
        }
        if(L2){
            node->next = L2;
        }
        return dummy->next;
    }
};
  • 还可使用优先队列法,用优先队列记录每个链表尚未被排序的第一个节点,自动排序得到值最小的一个顶点,加入到合成链表末尾。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    struct Status {
        int val;
        ListNode *ptr;
        bool operator<(const Status &rhs) const {
            return val > rhs.val;
        }
    };

    priority_queue<Status> q;

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        for(auto node: lists){
            if(node) q.push({node->val, node});
        }
        ListNode head, *tail = &head;
        while(!q.empty()){
            auto f = q.top();q.pop();
            tail->next = f.ptr;
            tail = tail->next;
            if(f.ptr->next) q.push({f.ptr->next->val, f.ptr->next});
        }
        return head.next;
    }
};
  1. 消灭怪物的最大数量(题目编号1921:中等,link,2022.1.10)

  • 本来考虑使用map记录(到达城堡的时间,怪物的数量),思路应该是正确的,实现起来有点bug。题解是把到达城堡的时间放到vector里然后排序,如果耗时小于下标,就表示该怪物无法在到达城堡前消灭。
class Solution {
public:
    int eliminateMaximum(vector<int>& dist, vector<int>& speed) {
        vector<int> time;
        int n = dist.size();
        for(int i = 0; i < n; ++i){
            time.push_back((dist[i] - 1) / speed[i]);
        }
        sort(time.begin(), time.end());
        for(int i = 0; i < n; ++i){
            if(time[i] < i){
                return i;
            }
        }
        return n;
    }
};
  1. 搜索二维矩阵(题目编号:74,中等,link,2022.1.11)

  • 两次二分搜索,第一次对第一列搜索,目标是找到小于target的最靠下的行,第二次对那一行进行搜索,目标是判断target是否在这一行。时间复杂度log(row)+log(col)。
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int r = matrix.size();
        if(r == 0) return false;
        int c = matrix[0].size();
        if(c == 0) return false;
        int lo = 0, hi = r - 1, row = 0;
        while(lo <= hi) {
            int mid = (lo + hi) / 2;
            int cur = matrix[mid][0];
            if(cur == target) {
                return true;
            }
            else if(cur > target) {
                hi = mid - 1;
            }
            else{
                row = mid;
                lo = mid + 1;
            }
        }
        lo = 1, hi = c - 1;
        while(lo <= hi){
            int mid = (lo + hi) / 2;
            if(matrix[row][mid] == target) {
                return true;
            }
            else if(matrix[row][mid] < target) {
                lo = mid + 1;
            }
            else {
                hi = mid - 1;
            }
        }
        return false;
    }
};
  1. 任务调度器(题目编号621:中等,link,2022.1.16)

  • 按填充的思路来做,首先找到出现任务最多的任务,均匀散开,间距n,接下来往n个空格填充,可能有多个任务的频次相同,依次填充出现频次最高,接着从倒数第二行开始,填充任务次多的。需要区分两种情况,填充时是否把n个空位填满了,如果填满了,那么相同任务之间的间距就大于n了,不需要填充空位,此时需要的总时间为总任务书,如果没有填满,n个格子剩余的位置就处于待机状态。官方题解讲的比较好,link
class Solution {
public:
    int leastInterval(vector<char>& tasks, int n) {
        vector<int> taskCount(26);
        for(char c: tasks) {
            ++taskCount[c - 'A'];
        }
        int maxCount = 0, numTask = 0;
        for(int i = 0; i < 26; ++i) {
            if(taskCount[i] > maxCount) {
                maxCount = taskCount[i];
                numTask = 1;
            }
            else if(taskCount[i] == maxCount) {
                ++numTask;
            }
        }
        return max(int((maxCount - 1) * (n + 1) + numTask), int(tasks.size()));
    }
};
  1. 二叉树最大宽度(题目编号662:link,中等,2022.1.18)

  • 最开始想了一种笨方法,遍历所有的节点,把null也放进去,结果应该是对的,但是浪费了很多push,pop的时间,导致超时。
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int widthOfBinaryTree(TreeNode* root) {
        if(!root) return 0;
        queue<TreeNode*> q;
        q.push(root);
        int ans = 1, cur_len = 1, tmp_ans = 1;
        while(tmp_ans > 0) {
            //tmp_len表示上一层次弹出队列的节点数
            //tmp_ans表示当前层次计算得到的答案,从第一个非null节点到最后一个非null节点
            //tmp_null记录从上一个非null节点到现在经历了多少了null
            //tmp_node记录当前层次放入队列的节点数
            //cur_len表示上一层次总共放入队列的节点数
            tmp_ans = 0;
            int tmp_len = 0, tmp_null = 0, tmp_node = 0;
            while(tmp_len < cur_len) {
                TreeNode* n = q.front();
                q.pop();
                if(tmp_ans == 0){
                    if(n){
                        tmp_ans = 1;
                    }
                } 
                else{
                    if(n){
                        tmp_ans += tmp_null + 1;
                        tmp_null = 0;
                    }
                    else{
                        ++tmp_null;
                    }
                }
                if(n){
                    q.push(n->left);
                    q.push(n->right);
                }
                else{
                    q.push(nullptr);
                    q.push(nullptr);
                }
                ++tmp_len;
                tmp_node += 2;
            }
            cur_len = tmp_node;
            ans = max(ans, tmp_ans);
        }
        return ans;
    }
};
  • 其实可以在层次遍历的基础上,跟之前一样,只push非空节点,此外再把非空节点在所在层次的位置信息放到队列里,这样就不用通过遍历每个null和非null节点来统计最大宽度。最痛苦的是数值溢出问题,存放的位置坐标很快就超过了long存储的范围。策略:每行从最左边的非空节点开始,重新计算下标,忽略左边全null的元素。
  • long全改成int也可以通过,因为题目保证答案不会超过int型。
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int widthOfBinaryTree(TreeNode* root) {
        if(!root) return 0;
        queue<TreeNode*> q_node;
        queue<long> q_pos;
        q_node.push(root);
        q_pos.push(1);
        long ans = 1;
        while(!q_node.empty()) {
            int sz = q_node.size();
            long left = q_pos.front();
            while(sz--) {
                TreeNode* n = q_node.front();
                long cur_pos = q_pos.front();
                q_node.pop();
                q_pos.pop();
                ans = max(ans, cur_pos - left + 1);
                if(n->left){
                    q_node.push(n->left);
                    q_pos.push(2 * (cur_pos - left + 1) - 1);
                }
                if(n->right){
                    q_node.push(n->right);
                    q_pos.push(2 * (cur_pos - left + 1));
                }
            }
        }
        return ans;
    }
};
  1. 实现前缀树(题目编号:剑指Offer II 062,link,中等,2022.1.19)

  • 用数组实现树结构
class Trie {
private:
    vector<Trie*> children;
    bool isEnd;

    Trie* searchPrefix(string prefix) {
        Trie* node = this;
        for(char c: prefix) {
            int pos = c - 'a';
            if(node->children[pos] != nullptr) {
                node = node->children[pos];
            }
            else{
                return nullptr;
            }
        }
        return node;
    }

public:
    /** Initialize your data structure here. */
    Trie(): children(26), isEnd(false) {

    }
    
    /** Inserts a word into the trie. */
    void insert(string word) {
        Trie* node = this;
        for(char c: word) {
            int pos = c - 'a';
            if(node->children[pos] == nullptr){
                node->children[pos] = new Trie();
            }
            node = node->children[pos];
        }
        node->isEnd = true;
    }
    
    /** Returns if the word is in the trie. */
    bool search(string word) {
        Trie* node = searchPrefix(word);
        return node != nullptr && node->isEnd;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) {
        Trie* node = searchPrefix(prefix);
        return node != nullptr;
    }
};

/**
 * Your Trie object will be instantiated and called as such:
 * Trie* obj = new Trie();
 * obj->insert(word);
 * bool param_2 = obj->search(word);
 * bool param_3 = obj->startsWith(prefix);
 */
  1. 从链表中删去总和值为零的连续节点(题目编号1171:link,中等,2022.1.20)

  • 区间和为0,那么就该想到用前缀和数组,如果两个位置i和j处的前缀和相等,那么i+1~j的区间和就是0,可以删掉。
  • 用map记录前缀和和对应的节点,如果前缀和重复出现,那么就需要把中间这段链表断开,此外还需要把中间这段链表对应的前缀和从map中删掉。
  • 另一种方法,两次遍历,第一次用map记录每个前缀和最后一次出现的链表节点,第二次遍历,每次都把前缀和第一次和最后一次出现的中间区间从链表中断开。
  • 链表注意前边要加一个dummy节点,指向head,避免head被删掉。
  • 注意map删除元素是erase函数,不是remove。
//第一种解法,一次遍历,但是需要回过头从map中删掉元素
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeZeroSumSublists(ListNode* head) {
        map<int, ListNode*> mp;
        ListNode* dummy = new ListNode(-1);
        dummy->next = head;
        ListNode* p = head;
        int preSum = 0;
        mp[0] = dummy;
        while(p) {
            preSum += p->val;
            if(mp.find(preSum) != mp.end()) {
                ListNode* node = mp[preSum];
                ListNode* del = node->next;
                node->next = p->next;
                int dSum = preSum;
                while(del != p) {
                    dSum += del->val;
                    mp.erase(dSum);
                    del = del->next;
                }
            }
            else{
                mp[preSum] = p;
            }
            p = p->next;
        }
        return dummy->next;
    }
};
//第二种解法,两次遍历
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeZeroSumSublists(ListNode* head) {
        ListNode* dummy = new ListNode(-1, head);
        ListNode* p = head;
        int preSum = 0;
        map<int, ListNode*> mp;
        mp[0] = dummy;
        while(p) {
            preSum += p->val;
            mp[preSum] = p;
            p = p->next;
        }
        p = dummy;
        preSum = 0;
        while(p) {
            p->next = mp[preSum]->next;
            p = p->next;
            if(p) {
                preSum += p->val;
            }
            else{
                break;
            }
        }
        return dummy->next;
    }
};
  1. 重新排列后的最大子矩阵(题目编号1727:link,中等,2022.1.27)

  • 本题跟之前的一些题目很类似,解决思路是统计每个位置往上看连续的1的数量得到一个新的矩阵up。接下来,对每一行及进行递增排序,对(i,j)位置来说,j+1列开始的右边的列都至少有up[i][j]个连续的1,这时候可以更新最大矩阵面积了。题目不难,可作为面试题。
class Solution {
public:
    int largestSubmatrix(vector<vector<int>>& matrix) {
        int rows = matrix.size(), cols = matrix[0].size();
        vector<vector<int>> up(rows, vector<int>(cols));
        for(int i = 0; i < rows; ++i){
            for(int j = 0; j < cols; ++j) {
                if(matrix[i][j] == 1){
                    if(i == 0){
                        up[i][j] = 1;
                    }
                    else{
                        up[i][j] = matrix[i][j] + up[i - 1][j];
                    }
                }
                
            }
        }
        int ans = 0;
        for(int i = 0; i < rows; ++i){
            sort(up[i].begin(), up[i].end());
            for(int j = 0; j < cols; ++j){
                ans = max(ans, up[i][j] * (cols - j));
            }
        }
        return ans;
    }
};
  1. 重排链表(题目编号:剑指Offer II 026,link,中等,2022.1.27)

  • 用快慢指针法找到链表中间节点,然后把后半部分翻转,然后再合并两个链表。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    void reorderList(ListNode* head) {
        ListNode* dummy = new ListNode();
        dummy->next = head;
        ListNode *slow = dummy, *fast = dummy;
        while(fast && fast->next){
            slow = slow->next;
            fast = fast->next->next;
        }
        ListNode* tail = reverseList(slow->next);
        slow->next = nullptr;
        slow = head;
        while(slow && tail){
            ListNode* next1 = slow->next;
            ListNode* next2 = tail->next; 
            slow->next = tail;
            tail->next = next1;
            slow = next1;
            tail = next2;
        }
    }

    ListNode* reverseList(ListNode* head) {
        if(!head) return nullptr;
        ListNode *pre, *cur, *next;
        pre = nullptr;
        cur = head;
        while(cur) {
            next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};
  1. K次调整数组大小浪费的最小总空间(题目编号1950:link,中等,2022.1.27)

  • 解法是动态规划,要从n个数分k段递推到n+1个数分k+1段。预先统计i~j区间分为一段的成本,然后遍历i0从0到i,选择从哪个地方开始作为最后一段。
  • 此题难度中等,可作为面试题,需要一些思路分析才能做出来
class Solution {
public:
    int minSpaceWastedKResizing(vector<int>& nums, int k) {
        int n = nums.size();
        vector<vector<int>> dp(n, vector<int>(k+2, INT_MAX / 2));
        vector<vector<int>> g(n, vector<int>(n));
        for(int i = 0; i < n; ++i){
            int max_val = INT_MIN;
            int sum = 0;
            for(int j = i; j < n; ++j){
                max_val = max(max_val, nums[j]);
                sum += nums[j];
                g[i][j] = max_val * (j - i + 1) - sum;
            }
        }
        for(int i = 0; i < n; ++i){
            for(int j = 1; j <= k+1; ++j){
                for(int i0 = 0; i0 <= i; ++i0){
                    dp[i][j] = min(dp[i][j], (i0 == 0? 0: dp[i0 - 1][j - 1]) + g[i0][i]);
                }
            }
        }
        return dp[n - 1][k + 1];
    }
};
  1. 描述绘画结果(题目编号:1943,link,中等,2022.2.21)

  • 难度中等,但是也比较巧妙,记录关键位置处颜色的增减量。
  • 由于LeetCode网站出问题,无法提交测试。待验证代码。
  • 需后面再自己写一遍
class Solution {
public:
    vector<vector<long long>> splitPainting(vector<vector<int>>& segments) {
        unordered_map<int, long long> ump;
        for(auto&& v: segments) {
            int l = v[0], r = v[1], c = v[2];
            if(ump.count(l) == 0){
                ump[l] = 0;
            }
            ump[l] += c;
            if(ump.count(r) == 0){
                ump[r] = 0;
            }
            ump[r] -= c;
        }
        vector<pair<int, long long>> vp;
        for(auto&& [k, v]: ump) {
            vp.emplace_back(k, v);
        }
        sort(vp.begin(), vp.end());
        vector<vector<long long>> ans;
        int n = vp.size();
        for(int i = 0; i < n; ++i) {
            if(vp[i].second) {
                ans.emplace_back(vector<long long> {vp[i].first, vp[i+1].first, vp[i].second});
            }
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值