zoukankan      html  css  js  c++  java
  • Leetcode 大部分是medium难度不怎么按顺序题解(下)

    前言

    万万没想到,一年过去了,我还在刷leetcode。。。
    上一篇题解里面字太多,编辑器都卡住了,所以另开一篇文档
    第一轮面试阴沟里翻车,祈祷我后天二轮能过155551
    期末季面试真太顶了,一个礼拜之前接到邮件,因为中间有考试愣是最后只剩下两天准备面试,好家伙

    220. 存在重复元素III

    看到这题,第一眼:主席树!第二眼:离散化+树状数组!
    然后看了一眼题解学了一下这个hash做法,还是很精妙的

    这个题里面有两个距离:k和t。k可以用滑动窗口解决,而t就要把数字分组,比如t=3的时候就分成[0,2][3,5][6,9]....这样。
    可以发现,当加入一个数字的时候,如果它那个组里已经有一个数,那就一定输出true。
    另外答案还有可能出现在相邻的两个组里。
    由于刚才说如果一个组里有两个数直接输出答案,那么可以知道在每个状态下一个组里最多只有一个数。记一下这个数的下标,每次拿出来比较就可以了。

    但是这个题主要是,它细节是真的多。。。
    最重要的问题就是数字范围问题。由于我这里想直接用数字整除t的结果来分组,必须要把t加一。而这就导致在t=2147483647的时候会爆int,所以我这个写法需要很多强转long long
    另外,直接整除在处理负数的时候也会遇到问题。因为这个整除它严格来说是“向零取整”。还是以t=2为例,这会导致0所在的那个组范围实际上是[-2,2]而不是[0,2],就会出问题。
    所以我需要让负数向下取整。需要一个特判,在getkey函数中。

    另外就是,用map这类东西做哈希表的时候要区分“组为空”或“组里的数字是0”。因为map元素如果是空,访问出来的结果也是0。。。
    这里直接就把记录在map里的下标都从1开始了。

    写的贼丑。

    class Solution {
    private:
        long long getkey(long long num, long long t) {
            if (num >= 0) return num/t;
            else return (num/t)-1;
        }
    public:
        bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int _t) {
            unordered_map<long long, int> table;
            int n = nums.size();
            if (n == 1) return false;
            long long t = (long long)_t+1;
            k = k + 1;
            for (int i = 0; i < min(n, k); i ++) {
                long long key = getkey(nums[i], t);
                if (table[key] != 0) return true;
                table[key] = i + 1;
                if (table[key-1] != 0) {
                    int tar = table[key-1];
                    if (abs((long long)nums[i] - nums[tar-1]) < t)
                        return true;
                }
                if (table[key+1] != 0) {
                    int tar = table[key+1];
                    if (abs((long long)nums[i] - nums[tar-1]) < t)
                        return true;
                }
            }
            for (int i = k; i < n; i ++) {
                long long key = getkey(nums[i-k], t);
                table[key] = 0;
                key = getkey(nums[i], t);
                if (table[key] != 0) return true;
                table[key] = i + 1;
                if (table[key-1] != 0) {
                    int tar = table[key-1];
                    if (abs((long long)nums[i] - nums[tar-1]) < t)
                        return true;
                }
                if (table[key+1] != 0) {
                    int tar = table[key+1];
                    if (abs((long long)nums[i] - nums[tar-1]) < t)
                        return true;
                }
            }
            return false;   
        }
    };
    

    221. 最大正方形

    二分。因为有边长为k的正方形就一定有边长比k小的正方形,反之亦然。
    预处理一个二维前缀和,判定的时候枚举所有边长为mid的正方形即可。
    注意处理全0矩阵的情况。

    class Solution {
    private:
        int n, m;
        vector<vector<int>> s;
        void buildSum(vector<vector<char>> &matrix) {
            for (int i = 0; i <= n; i ++) {
                vector<int> tmp;
                tmp.clear();
                for (int j = 0; j <= m; j ++)
                    tmp.push_back(0);
                s.push_back(tmp);
            }
            for (int i = 1; i <= n; i ++)
                for (int j = 1; j <= m; j ++)
                    s[i][j] = (matrix[i-1][j-1]-'0') + s[i-1][j] + s[i][j-1] - s[i-1][j-1];
        }
        int calc(int x, int y, int xx, int yy) {
            return s[xx+1][yy+1] - s[x][yy+1] - s[xx+1][y] + s[x][y]; 
        }
        bool check(int len) {
            for (int i = 0; i < n-len+1; i ++)
                for (int j = 0; j < m-len+1; j ++) {
                    int sum = calc(i, j, i+len-1, j+len-1);
                    if (sum == len * len) return true;
                }
            return false;
        }
        int divide(int l, int r) {
            int mid, ans = l;
            while (l <= r) {
                mid = (l+r)>>1;
                if (check(mid)) {
                    ans = max(ans, mid);
                    l = mid + 1;
                } else r = mid - 1;
            }
            return ans*ans;
        }
    public:
        int maximalSquare(vector<vector<char>>& matrix) {
            n = matrix.size();
            m = matrix[0].size();
            buildSum(matrix);
            if (s[n][m] == 0) return 0;
            return divide(1, min(n, m));
        }
    };
    

    222. 完全二叉树的节点个数

    这个题O(n)的做法很简单,遍历就可以了。

    要缩减复杂度的话,考虑它是一个完全二叉树。完全二叉树决定节点个数的就只有两个量:它的高度和它最底层的节点数目。
    它的高度可以通过一直顺着左儿子走来求出,而最底层的节点数目可以发现满足二分单调性:从某一个点开始,左边全都有点,右边全都没有点。只要找到这个分界点就可以了。
    那么就是每次二分一个mid,检查最底层的第mid个节点存不存在。
    可以发现,如果把最底层的节点从左往右从0开始编号,那么它的编号的二进制就描述了从根节点走到它的路径。
    举个栗子,就题目里样例的那棵二叉树。底层节点可以顺序编号为0 1 2 3(3号节点不存在)。那么如果要看2号节点存不存在,2号节点的二进制是10
    (因为从顶到底最多只需要走两步,所以保留二进制的后两位即可。一般地,一个高度为h的二叉树保留h-1位即可)
    2号节点的二进制是10,说明要先往右走一步(1)、再往左走一步(0)。
    这样判定的复杂度是(O(h))也就是(O(logn)),然后二分的复杂度也是(O(logn)),总的复杂度是(O(log^2n))

    /**
     * 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 {
    private:
        int h;
        int getdepth(TreeNode *root) {
            int res = 0;
            while (root != NULL) {
                res ++;
                root = root->left;
            }
            return res;
        }
        bool check(TreeNode *root, int id) {
            for (int i = h-1; i >= 1; i --) {
                if ((id >>(i-1)) & 1)
                    root = root->right;
                else root = root->left;
                if (root == NULL) return false;
            }
            return true;
        }
        int divide(TreeNode *root, int l, int r) {
            int mid, ans = 1;
            while (l <= r) {
                mid = (l+r) >> 1;
                if (check(root, mid-1)) {
                    ans = max(ans, mid);
                    l = mid + 1;
                } else r = mid - 1;
            }
            return ans;
        }
    public:
        int countNodes(TreeNode* root) {
            if (root == NULL) return 0;
            h = getdepth(root);
            int lastdep = divide(root, 1, 1<<(h-1));
            return (1<<(h-1))-1 + lastdep;
        }
    };
    

    223. 矩形面积

    这个题一眼就可以分类讨论,但情况特别多(完全包含的;相离的;左边覆盖的;右边覆盖的等等),如果要算覆盖面积的话非常不好做。
    这里采用了离散化+填格子的方法。横纵坐标分别离散化,坐标范围缩小到4*4。然后枚举两个矩阵覆盖的每个格子,覆盖到的+1。最后计算每个被覆盖过的格子面积总和就可以了。
    注意最后计算格子面积的时候坐标要用离散化之前的。

    class Solution {
    private:
        int va[10], vb[10], ca, cb;
        int mat[10][10];
        void trans(int *v, int &cnt, int &n1, int &n2, int &n3, int &n4) {
            v[1] = n1; v[2] = n2; v[3] = n3; v[4] = n4;
            sort(v+1, v+4+1);
            cnt = unique(v+1, v+4+1) - v - 1;
            n1 = lower_bound(v+1, v+cnt+1, n1) - v;
            n2 = lower_bound(v+1, v+cnt+1, n2) - v;
            n3 = lower_bound(v+1, v+cnt+1, n3) - v;
            n4 = lower_bound(v+1, v+cnt+1, n4) - v;
        }
        void add(int x, int y, int xx, int yy) {
            for (int i = x; i < xx; i ++)
                for (int j = y; j < yy; j ++)
                    mat[i][j] ++;
        }
        int getArea(int x, int y) {
            int l1 = va[x+1] - va[x];
            int l2 = vb[y+1] - vb[y];
            return l1 * l2;
        }
    public:
        int computeArea(int A, int B, int C, int D, int E, int F, int G, int H) {
            trans(va, ca, A, C, E, G);
            trans(vb, cb, B, D, F, H);
            add(A, B, C, D);
            add(E, F, G, H);
            int ans = 0;
            for (int i = 1; i <= ca; i ++)
                for (int j = 1; j <= cb; j ++)
                    if (mat[i][j] != 0)
                        ans += getArea(i, j);
            return ans;
        }
    };
    

    227. 基本计算器

    双栈法计算中缀表达式。
    需要注意字符串末尾有空格的情况。

    class Solution {
    private:
        int pri(char c) {
            if (c == '+' || c == '-')
                return 1;
            if (c == '*' || c == '/')
                return 2;
            return 0;
        }
        int calc(int a, int b, char opt) {
            switch (opt) {
                case '+': return a+b;
                case '-': return a-b;
                case '*': return a*b;
                case '/': return a/b;
            }
            return 0;
        }
        int getnum(int &ptr, string &s, int len) {
            int x = 0;
            while (ptr < len && (s[ptr] < '0' || s[ptr] > '9'))
                ++ptr;
            while (ptr < len && s[ptr] <= '9' && s[ptr] >= '0') {
                x = x * 10 + (s[ptr] - '0');
                ptr ++;
            }
            return x;
        }
        char getopt(int &ptr, string &s, int len) {
            while (ptr < len && pri(s[ptr]) == 0)
                ++ptr;
            if (ptr >= len) return 0;
            return s[ptr++];
        }
    public:
        int calculate(string s) {
            int ptr = 0, len = s.size();
            int now, res;
            char opt;
            if (len == 0) return 0;
            deque<int> nums;
            deque<char> ops;
            now = getnum(ptr, s, len);
            nums.push_back(now);
            while (ptr < len) {
                opt = getopt(ptr, s, len);
                if (ptr >= len) break;
                now = getnum(ptr, s, len);
                res = now;
                if (ops.empty()) ops.push_back(opt);
                else {
                    if (pri(ops.back()) < pri(opt)) {
                        res = calc(nums.back(), res, opt);
                        nums.pop_back();
                    } else ops.push_back(opt);
                }
                nums.push_back(res);
            }
            res = nums.front(); nums.pop_front();
            while (!nums.empty()) {
                opt = ops.front(); ops.pop_front();
                now = nums.front(); nums.pop_front();
                res = calc(res, now, opt);
            }
            return res;
        }
    };
    

    229. 求众数

    第一眼看到这个题就想起来那个求出现次数大于n/2的数字的题。那个题是让不同的数字相互抵消,最后剩下的那个就是大于n/2的数字。
    然后就考虑把那个做法套到这个题里面。因为出现次数大于n/3的数字最多有两个,所以就维护两个数。
    还是考虑相互抵消的思路。在求大于n/2的题目里,一个数字最多会被抵消n/2次。又保证众数存在,所以出现次数大于n/2的那个数最后一定能留下。
    在这道题里也要保证一个数字最多会被抵消的次数不能超过n/3。否则众数就有可能被抵消掉。
    但是也要保证最坏情况下(有数字恰好出现了n/3次,例如原题的第三个样例[1,1,1,2,2,3,3,3])抵消的次数一定要达到n/3,否则就消不掉“坏数”。

    那么思路就是每次遇到不同的数字时,让维护的那两个数字同时被抵消一次。
    这样,每次抵消都会少三个数(新来的和维护的两个),总共只有n个数,所以抵消次数不会超过n/3。
    对于任意一个出现次数不大于n/3的数字,一定有超过2*(n/3)个数字和它不相等。即使每次抵消要消耗两个数字,也足够把它全部消掉。

    class Solution {
    public:
        vector<int> majorityElement(vector<int>& nums) {
            int ext[2], cnt[2];
            int n = nums.size();
            cnt[0] = cnt[1] = 0;
            for (int i = 0; i < n; i ++) {
                bool addin = false;
                for (int j = 0; j < 2; j ++)
                    if (cnt[j] > 0 && ext[j] == nums[i]) {
                        ++cnt[j]; addin = true; break;
                    }
                if (addin) continue;
    
                for (int j = 0; j < 2; j ++)
                    if (cnt[j] == 0) {
                        cnt[j] = 1; ext[j] = nums[i];
                        addin = true; break;
                    }
                if (addin) continue;
    
                cnt[0] --; cnt[1] --;
            }
            vector<int> ans; ans.clear();
            for (int i = 0; i < 2; i ++)
                if (cnt[i] > 0) {
                    int check = 0;
                    for (int j = 0; j < n; j ++)
                        if (nums[j] == ext[i]) ++check;
                    if (check > n/3) ans.push_back(ext[i]);
                }
            return ans;
        }
    };
    

    230. 二叉搜索树中第K小的元素

    就普通的平衡树Find操作,算个size然后从顶至底查就行了。时间复杂度(O(n))(因为要dfs一遍)
    按理来说其实也可以不dfs做,就用普通的平衡树FindNext那种操作每次找下一个节点。
    具体我记得是如果当前节点有右子树就找右子树最靠左的儿子,如果没有右子树就往上找父亲,找到最近的一个从左儿子上去的父亲就是。
    这样做的话时间复杂度应该是(O(klogn))的。因为每次查找的最坏复杂度是(O(logn))
    k比较小的时候这样一个个找会比较好吧。。。k一大就退化成(O(nlogn)),还不如dfs一遍。

    /**
     * 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 {
    private:
        unordered_map<TreeNode*, int> size;
        void dfs(TreeNode *root) {
            size[root] = 1;
            if (root->left != NULL) {
                dfs(root->left);
                size[root] += size[root->left];
            }
            if (root->right != NULL) {
                dfs(root->right);
                size[root] += size[root->right];
            }
        }
        TreeNode* Find(TreeNode* root, int k) {
            int val;
            if (root->left != NULL)
                val = size[root->left];
            else val = 0;
            if (k == val + 1)
                return root;
            if (k <= val) return Find(root->left, k);
            else return Find(root->right, k - val - 1);
        }
    public:
        int kthSmallest(TreeNode* root, int k) {
            size.clear();
            if (root == NULL) return 0;
            dfs(root);
            return Find(root, k)->val;
        }
    };
    

    232. 用栈实现队列

    随便翻的时候翻到这个题,想起自己两年前计概考试的时候这题就没做出来,吓出一身冷汗,赶紧做一做。
    进了A栈一堆元素以后,要“出队”出的是那个栈底的元素,那就必须要把栈底的元素翻到上面来,那就必定要一个一个往外弹,弹到另一个栈(B栈)里存起来。
    一开始脑子有点叉劈,非得想维护栈里元素正确的顺序,就觉得应该把B栈里的元素再倒回A栈里。实际上没必要,因为B栈里就是按照正确的“出队”顺序存的,出的时候可以直接从B里面出。
    而入栈的时候还是往A栈里面入,显然,B没空的时候不能从A栈往B栈倒元素,否则就破坏了B的正确顺序。但是当B出空了,就可以把A的元素倒过去。
    这样每个元素一定会入A栈一次,入B栈一次,然后就被弹出去。均摊复杂度是O(n)的。

    class MyQueue {
    public:
        /** Initialize your data structure here. */
        stack<int> ins, outs;
        MyQueue() {
            while (!ins.empty()) ins.pop();
            while (!outs.empty()) outs.pop();
        }
        
        /** Push element x to the back of queue. */
        void push(int x) {
            ins.push(x);
        }
    
        void trans() {
            while (!ins.empty()) {
                int tmp = ins.top();
                ins.pop();
                outs.push(tmp);
            }
        }
        
        /** Removes the element from in front of queue and returns that element. */
        int pop() {
            if (outs.empty()) trans();
            int res = outs.top();
            outs.pop();
            return res;
        }
        
        /** Get the front element. */
        int peek() {
            if (outs.empty()) trans();
            return outs.top();
        }
        
        /** Returns whether the queue is empty. */
        bool empty() {
            return ins.empty() && outs.empty();
        }
    };
    
    /**
     * Your MyQueue object will be instantiated and called as such:
     * MyQueue* obj = new MyQueue();
     * obj->push(x);
     * int param_2 = obj->pop();
     * int param_3 = obj->peek();
     * bool param_4 = obj->empty();
     */
    

    238. 除自身以外数组的乘积

    显然就是搞一个前缀和,一个后缀和,每次乘起来就行了。
    不让开额外空间也比较简单,因为它说输出数组不算额外空间,所以就先把对应位置的前缀和存在输出数组里,然后从后往前遍历,一边维护后缀和一边往输出数组里面乘就可以了。

    class Solution {
    public:
        vector<int> productExceptSelf(vector<int>& nums) {
            vector<int> ans; ans.clear();
            int n = nums.size(), tmp;
            if (n == 0) return ans;
    
            tmp = 1; ans.push_back(1);
            for (int i = 0; i < n-1; i ++) {
                tmp = tmp * nums[i];
                ans.push_back(tmp);
            }
            tmp = 1;
            for (int i = n-1; i >= 1; i --) {
                tmp = tmp * nums[i];
                ans[i-1] = ans[i-1] * tmp;
            }
            return ans;
        }
    };
    

    239. 滑动窗口最大值

    惊了,真就年纪大了连个单调队列都写不对。。。
    忘了单调队列要用双向的,整个单向的队列在那搞来搞去。。

    class Solution {
    public:
        vector<int> maxSlidingWindow(vector<int>& nums, int k) {
            deque<int> q;
            int n = nums.size();
            vector<int> ans;
            for (int i = 0; i < n; i ++) {
                while (!q.empty() && q.front() <= i-k) q.pop_front();
                while (!q.empty() && nums[i] > nums[q.back()]) q.pop_back();
                q.push_back(i);
                if (i >= k-1) ans.push_back(nums[q.front()]);
            }
            return ans;
        }
    };
    
  • 相关阅读:
    SQLServer 可疑
    String与Long互转
    洛谷 P5644
    洛谷 P3783
    洛谷 P4663
    洛谷 P3438
    Atcoder Grand Contest 054 题解
    迭代器失效问题
    Solution -「CF 232E」Quick Tortoise
    Solution -「NOI 2020」「洛谷 P6776」超现实树
  • 原文地址:https://www.cnblogs.com/FromATP/p/14300539.html
Copyright © 2011-2022 走看看