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

    前言

    但是就没办法,平时也没啥理由整天刷题,真到了要面试的时候手感没了还就是得刷leetcode找手感。
    就不像以前搞OI的时候每天就只管刷题,手感如何都不会丢。
    所以我还在刷leetcode。我的老天爷,想想现在找工作要刷leetcode,以后万一想跳槽了还得刷leetcode。
    可能哪一天互联网泡沫破灭了我回家开奶茶店了就不用刷leetcode了。
    leetcode我(哔————)

    upd: 2021.10.24
    马上要搞一场英文面试。我太害怕了。。
    用英文写一阵子题解练练先

    正文

    322. 零钱兑换

    简单DP。因为amount的范围很小所以直接开数组就行了。
    注意最后判断无解。答案最大是amount(若干个面值为1的拼起来),所以只要把INF设置为大于amount的值就行了。

    class Solution {
    public:
        int coinChange(vector<int>& coins, int amount) {
            int f[10010], n = coins.size();
            memset(f, 127, sizeof(f));
            f[0] = 0;
            for (int i = 1; i <= amount; i ++)
                for (int j = 0; j < n; j ++)
                    if (coins[j] <= i)
                        f[i] = min(f[i], f[i - coins[j]] + 1);
            if (f[amount] <= amount)
                return f[amount];
            else return -1;
        }
    };
    

    324. 摆动排序

    首先有一个结论:有解的数组必定可以分成“较大”和“较小”两部分,满足“较大”部分的最小数大于等于“较小”部分的最大数,且两部分的数字个数最多相差1。
    证明这个结论很简单。首先构造一个初始序列:因为一定有解,所以把解的偶数位置上的数字放一堆,奇数位置的放一堆。
    显然奇数部分是“较小”的,偶数部分是“较大”的。
    如果这样的两个序列不满足要求,那么“较大”那部分的最小数肯定小于“较小”那部分的最大数。这两个数交换一下位置,两个部分数字的个数没变。重复这种操作最后一定能满足要求。
    所以只要把这两部分分开就可以了。

    能O(n)时间做出来的关键就在于数组里每个数字不大于5000。这样就可以用桶排序的思路做。
    我的做法用了O(n)的额外空间。基本思路就是先桶排序,然后把较大的一半放在奇数位,较小的一半放在偶数位。
    具体实现里面我首先让奇数位的序列递增,偶数位的序列递减。但这样做出来最后可能有相等的相邻数字(比如样例2)
    所以最后再扫一遍把偶数位的序列倒过来就可以了。

    class Solution {
    public:
        void wiggleSort(vector<int>& nums) {
            int cnt[50010], mx = 0, n = nums.size();
            for (int i = 0; i < n; i ++) {
                cnt[nums[i]] ++;
                mx = max(mx, nums[i]);
            }
            int p1 = 0, p2 = mx;
            nums.clear();
            while (p1 <= p2) {
                while (p1 <= p2 && cnt[p1] == 0) p1 ++;
                while (p1 <= p2 && cnt[p2] == 0) p2 --;
                if (p1 > p2) break;
                if (cnt[p1] > 0) {
                    nums.push_back(p1); cnt[p1] --;
                }
                if (cnt[p2] > 0) {
                    nums.push_back(p2); cnt[p2] --;
                }
            }
            p1 = 0; p2 = n - 1;
            if (p2 & 1) p2 --;
            while (p1 <= p2) {
                swap(nums[p1], nums[p2]);
                p1 += 2; p2 -= 2;
            }
        }
    };
    

    450. 删除二叉搜索树中的节点

    因为要维护二叉树的顺序性质,删掉一个点以后来补上它的一定是比它小的第一个点或比它大的第一个点。
    用哪个点都可以,这里选用比它小的第一个点。

    按照如下步骤完成:

    • 找到待删点A,记录A的父亲F
    • 如果A没有左儿子,直接将用A的右儿子代替A即可。需要修改F的儿子指针。
    • 如果A有左儿子,找到左儿子中最右边的节点L,记录L的父亲LF。
    • 摘掉节点L。注意如果LF就是A,那么L是LF的左儿子;否则L是LF的右儿子。改的指针不同,要分类讨论一下。
    • 让L继承A的左右儿子信息,并修改F的儿子指针。此时L代替了A的位置。
    • 如果A是根节点,那么把root指针改成L。
    /**
     * 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* deleteNode(TreeNode* root, int key) {
            TreeNode *tar, *fa;
            fa = NULL;
            tar = root;
            while (tar != NULL && key != tar->val) {
                fa = tar;
                if (tar->val > key) tar = tar->left;
                else tar = tar->right;
            }
            if (tar == NULL) return root;
    
            if (tar->left == NULL) {
                if (fa != NULL) {
                    if (fa->left == tar) fa->left = tar->right;
                    else fa->right = tar->right;
                }
                if (tar == root) root = tar->right;
                return root;
            }
    
            TreeNode *lnode, *lfa;
            lnode = tar->left;
            lfa = tar;
            while (lnode->right != NULL) {
                lfa = lnode;
                lnode = lnode->right;
            }
            if (lfa == tar) {
                lfa->left = lnode->left;
            } else {
                lfa->right = lnode->left;
            }
            lnode->left = tar->left;
            lnode->right = tar->right;
            if (fa != NULL) {
                if (fa->left == tar) fa->left = lnode;
                else fa->right = lnode;
            }
            if (tar == root) root = lnode;
            return root;
        }
    };
    

    451. 根据字符出现频率排序

    STL大杂烩。自从习惯用auto以后代码变好看了很多。

    先用一个unordered_map统计出每种字符出现多少次,再把字符和出现频率用pair绑定起来,按照出现频率排序。最后把字符一个个塞到答案字符串里面就行了。

    class Solution {
    public:
        string frequencySort(string s) {
            unordered_map<char, int> cnt;
            vector< pair<int, char> > f;
            int len = s.size();
            if (len == 0) return "";
            for (int i = 0; i < len; i ++)
                cnt[s[i]] += 1;
            for (auto it = cnt.begin(); it != cnt.end(); it ++) {
                f.push_back(make_pair(it->second, it->first));
            }
            sort(f.begin(), f.end());
            int n = f.size();
            string ans;
            ans.clear();
            for (int i = n - 1; i >= 0; i --) {
                auto cur = f[i];
                for (int j = 0; j < cur.first; j ++)
                    ans.push_back(cur.second);
            }
            return ans;
        }
    };
    

    334. 递增的三元子序列

    这个题还挺有意思的。
    考虑如何才能只扫一遍序列就尽可能找到递增的三元组。
    递增三元组肯定是从递增二元组扩展来的。而一个好的递增二元组一定要尽量让较大的那个数字更小,还要让位置尽量靠左。
    但是扫描序列的时候是从左到右依次扫描的,所以位置靠左这个并不是一个限制因素,因为扫描到第i个数字的时候见到的二元组一定在i的左边。
    所以方案就有了:从左往右扫描,维护“最优二元组”,即递增且较大数字最小的二元组。
    每次扫描到一个新数字的时候看看这个数字能不能接在“最优二元组”后面。如果最优二元组都接不上,那前面也没有二元组能接上它。
    而维护最优二元组的思路类似。一个二元组一定是某个数字后面接上一个数字形成的。在从左往右扫描的过程中,这个数字应该尽量小。
    所以在扫描的时候只要维护最小值就可以了。

    下面代码里用Minpos维护最小值位置,b1和b2维护最优二元组的位置。
    实际上b1这个变量没什么用。可以把它删掉。不过考虑到可读性还是留着了。

    class Solution {
    public:
        bool increasingTriplet(vector<int>& nums) {
            int n = nums.size();
            int Minpos = 0, b1 = -1, b2 = -1;
            for (int i = 1; i < n; i ++) {
                if (b1 != -1 && b2 != -1 && nums[i] > nums[b2])
                    return true;
                if (nums[i] < nums[Minpos])
                    Minpos = i;
                else if (nums[i] > nums[Minpos]) {
                    if (b1 == -1 && b2 == -1) {
                        b1 = Minpos; b2 = i;
                    } else if (nums[i] < nums[b2]) {
                        b1 = Minpos; b2 = i;
                    }
                }
            }
            return false;
        }
    };
    

    452. 用最少数量的箭引爆气球

    题目说得曲里拐弯的,还什么球体什么二维平面的,实际上就是说给定一堆线段,要你选最少数量的点,保证每个线段都覆盖了至少一个点。

    一看就是贪心。一开始想的是按左端点排序,因为最左边的线段肯定要用一个点覆盖一下,而我们希望这个点能覆盖到剩下的尽量多的线段。
    但这样是有问题的,因为按左端点排序的话,仅凭第一个线段无法确定最优的点,不一定把点选在第一个线段的右端点就是最优的。

    那么换一种思路,按照右端点排序。因为我们从左往右扫描,肯定希望选中的点尽量靠右。而右端点决定了要覆盖这条线段,最靠右的界限。
    那么按照右端点排序后的第一条线段肯定是要被覆盖的。如果选择它的右端点作为覆盖点,它后面的线段右端点都是大于等于这个点的,只需要保证左端点小于等于这个点就可以了。
    于是我们可以一直往后找,直到碰到第一个左端点大于这个点的线段,就可以停下来了。
    虽然后面也有可能还存在左端点小于等于这个点的,但后面的那些线段一定也可以被新的“第一条线段”覆盖到,是一样的。
    于是依次扫描每个线段,碰到第一个左端点大于选定点的就答案+1,然后更新选定点即可。

    class Solution {
    public:
        int findMinArrowShots(vector<vector<int>>& points) {
            vector< pair<int, int> > seg;
            int n = points.size();
            for (int i = 0; i < n; i ++)
                seg.push_back(make_pair(points[i][1], points[i][0]));
            sort(seg.begin(), seg.end());
            int ans = 1, ptr = 0, cur = seg[0].first;
            while (ptr < n) {
                while (ptr < n && seg[ptr].second <= cur)
                    ptr ++;
                if (ptr >= n) break;
                cur = seg[ptr].first;
                ans ++;
            }
            return ans;
        }
    };
    

    467. 环绕字符串中唯一的子字符串

    这个题也挺有意思的。第一眼看没什么思路,如果直接枚举的话O(n^2)肯定过不了。
    显然p字符串可以分成一段一段循环意义下连续的子串。我们统计也是在每个连续子串的内部统计。
    关键是题目要求不重复,如何在统计每个子串的时候保证不统计到重复的子串。
    考虑如何描述不同的连续子串。因为它可以循环很多次a-z,所以不能简单地用开头字母和结尾字母来描述。
    但是可以用开头/结尾字母,加上这个子串的长度来描述。
    确定了这种描述方式以后,统计上实际就比较简单了,因为注意到长度这一点就可以发现,对于某个开头,如果长度k出现了,那么比k小的长度一定也都出现了。
    所以对于每一种开头,我们只需要记录出现过的最大长度,最后把每一种开头的最大长度加起来就可以了。

    class Solution {
    private:
        char nxt(char c) {
            return (c - 'a' + 1) % 26 + 'a';
        }
    public:
        int findSubstringInWraproundString(string p) {
            int cnt[30], Max[30];
            memset(Max, 0, sizeof(Max));
            int n = p.size(), ptr = 0;
            while (ptr < n) {
                int head = ptr;
                while (ptr + 1 < n && p[ptr + 1] == nxt(p[ptr])) {
                    ptr ++;
                }
                for (int i = head; i <= ptr; i ++) {
                    Max[p[i] - 'a'] = max(Max[p[i] - 'a'], ptr - i + 1);
                }
                ptr ++;
            }
            int ans = 0;
            for (int i = 0; i < 26; i ++)
                ans += Max[i];
            return ans;
        }
    };
    

    513. 找树左下角的值

    首先,如果dfs时先走左子树再走右子树,那么所有深度相同的节点中最先遍历到的一定是最左边的。
    所以只要维护一个最大深度就可以了。只有新节点的深度严格大于当前最大深度时才更新。
    这样最后ans里存的就是第一次遇到最大深度时的节点,就是最左下角的节点。

    /**
     * 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 max_depth;
        TreeNode *ans;
        void dfs(TreeNode *u, int d) {
            if (d > max_depth) {
                max_depth = d;
                ans = u;
            }
            if (u->left != NULL)
                dfs(u->left, d + 1);
            if (u->right != NULL)
                dfs(u->right, d + 1);
        }
    public:
        int findBottomLeftValue(TreeNode* root) {
            max_depth = 0;
            ans = NULL;
            dfs(root, 1);
            return ans->val;
        }
    };
    

    516. 最长回文子序列

    按长度DP。f[i][j]表示字符串i..j这一段的最长回文子序列。
    递推的时候,如果s[i]==s[j],说明f[i][j]的子序列可以在f[i+1][j-1]的基础上左右各延长一个字符。
    另外,f[i][j]的结果可以无条件继承f[i+1][j]或f[i][j-1],代表s[i]或s[j]不参与匹配。

    class Solution {
    public:
        int longestPalindromeSubseq(string s) {
            int f[1010][1010];
            memset(f, 0, sizeof(f));
            int n = s.size();
            for (int i = 0; i < n; i ++)
                f[i][i] = 1;
            for (int k = 2; k <= n; k ++)
                for (int i = 0; i + k - 1 < n; i ++) {
                    int j = i + k - 1;
                    if (s[i] == s[j])
                        f[i][j] = max(f[i][j], f[i + 1][j - 1] + 2);
                    f[i][j] = max(f[i][j], f[i + 1][j]);
                    f[i][j] = max(f[i][j], f[i][j - 1]);
                }
            return f[0][n - 1];
        }
    };
    

    519. 随机翻转矩阵

    肯定是调用一次random,所以把矩阵展开成一个序列。
    一开始想的是用两个数组记录每一行有多少个1和每一列有多少个1,random到一个数字以后再去查找。
    但这样关键是如果把矩阵按行展开,则只能找到对应的行坐标,难以找到对应的列坐标;如果按列展开也类似。

    于是只能纯当做一个一维问题去做。问题变成在一个很长的序列里,每次随机找一个0变成1。
    期望当然是把0的坐标排在序列前部,并且维护0的总个数,这样直接随机访问一个下标就可以了。
    显然这个序列是无法直接维护的。考虑序列初始的状态,全都是0,序列的第i位就是坐标i。
    当某个0变成1以后,我们要把最后一个0的位置换到前面来。然后最后一个0的位置就被踢出下一次的随机范围。
    所以每次操作都只会有一个坐标发生变化。用一个hash表维护发生变化的坐标即可。
    如果当前坐标在表里,就按照哈希表维护的坐标找;否则就按照它的默认坐标找。

    还有一个问题是如何生成均匀随机的随机数。一开始想用rand函数取余,但突然意识到这样并不是均匀随机的。
    因为rand函数的范围不一定能整除我想要的范围,比如rand的范围是0.。32767,但我想要0..32766范围的整数。
    这样需要对32767取模。显然0和32767的取模结果相同,而其余都不同。这样0的概率比其它高了一倍!
    上网查了查,发现C++有现成的STL函数可以生成均匀随机。于是就学习了一下(背了一下板子)

    class Solution {
    private:
        int m, n, zeros;
        unordered_map<int, int> mp;
    public:
        Solution(int _m, int _n) {
            m = _m; n = _n;
            mp.clear();
            zeros = m * n;
        }
    
        int get_random(int l, int r) {
            unsigned seed = chrono::system_clock::now().time_since_epoch().count();
            mt19937 gen(seed);
            uniform_int_distribution<int> distr(l, r);
            return distr(gen);
        }
        vector<int> flip() {
            int tar, idx = get_random(0, zeros - 1);
            vector<int> ans;
            if (mp[idx] != 0)
                tar = mp[idx];
            else tar = idx;
            ans.push_back(tar / n);
            ans.push_back(tar % n);
            if (mp[zeros - 1] == 0)
                mp[idx] = zeros - 1;
            else mp[idx] = mp[zeros - 1];
            zeros --;
            return ans;
        }
        
        void reset() {
            zeros = m * n;
            mp.clear();
        }
    };
    
    /**
     * Your Solution object will be instantiated and called as such:
     * Solution* obj = new Solution(m, n);
     * vector<int> param_1 = obj->flip();
     * obj->reset();
     */
    

    470. 用 Rand7() 实现 Rand10()

    Obviously, calling rand7() twice could get a uniform distribution of 49 numbers,
    so we could just use 40 of them, and abort the last 9 numbers, then mod 10 and return the reminder.

    Use (x*7+y) as the index of random pair (x, y). If the index >= 40, get another new pair of (x, y).

    Notice: Don't re-generate one of (x, y) only. Otherwise there could be a dead loop, or the distribution of numbers could be non-uniform.

    // The rand7() API is already defined for you.
    // int rand7();
    // @return a random integer in the range 1 to 7
    
    class Solution {
    public:
        int rand10() {
            int tmp, n1, n2;
            do {
                n1 = rand7();
                n2 = rand7();
                tmp = (n1 - 1) * 7 + (n2 - 1);
            } while (tmp >= 40);
            return tmp % 10 + 1;
        }
    };
    

    525. 连续数组

    Maintain a counter, if meet a 0, add the counter by -1; if meet an 1, add the counter by 1.
    Then we could get a sequence similar to "prefix sum".
    The task is to find two equal numbers in this sequence, and their distance should be as far as possible.

    We could use a hash table to record the first appearance of a value in prefix sum.
    Then we scan the "prefix sum sequence" from left to right.
    When we get a new prefix sum, check if it's in the hash table.
    If so, we met a possible answer. If not, add it in the hash table.

    The time complexity of this algorithm is O(n), space complexity O(n).

    class Solution {
    public:
        int findMaxLength(vector<int>& nums) {
            unordered_map<int, int> ext;
            int maxlen = 0, sum = 0, n = nums.size();
            for (int i = 0; i < n; i ++) {
                sum += (nums[i] == 0) ? -1 : 1;
                if (sum == 0) {
                    maxlen = max(maxlen, i + 1);
                } else {
                    if (ext[sum] != 0) {
                        maxlen = max(maxlen, (i + 1) - ext[sum]);
                    } else {
                        ext[sum] = i + 1;
                    }
                }
            }
            return maxlen;
        }
    };
    

    526. 优美的排列

    Consider filling in each position in the permutation from left to right.
    At each position we should focus on what position we are filling, and which numbers have been used.
    So we could use dynamic programming to solve this problem.

    Let f[i][j] be the number of perms when we have filled in the first i positions, and j is a bitmap represents which numbers have been used.
    For example, f[2][0xb001010] represents we have filled in 2 positions, and number 2 and 4 have been used.

    This problem is better to be solved using dfs with memory instead of traditional DP.
    When we are calculating f[i][j], we must know which f[i-1][k] could be used.
    Then we just enumerate each number v that has been used in state j, check if v%i==0 or i%v==0 (because we are filling in the ith position)
    Wipe this number v out of state j could we get the state k.

    The time complexity of the worst situation is (O(n2^n)), but in practice it will be much lower.
    Space complexity (O(n2^n))

    class Solution {
    private:
        int f[16][32768];
        int search(int n, int i, int state) {
            if (f[i][state] != 0) {
                return f[i][state];
            }
            for (int k = 1; k <= n; k ++)
                if ((state & (1 << (k - 1))) && (k % i == 0 || i % k == 0)) {
                    int newstate = state - (1 << (k - 1));
                    f[i][state] += search(n, i - 1, newstate);
                }
            return f[i][state];
        }
    public:
        int countArrangement(int n) {
            memset(f, 0, sizeof(f));
            f[0][0] = 1;
            return search(n, n, (1 << n) - 1);
        }
    };
    

    528. 按权重随机选择

    Use C++ STL to generate a uniform distribution of rand numbers. Same as problem 519.
    For example, if the weight array is [1, 3], we can just generate a random number in range 1..4.
    If the number is 1, then return index 0; if the number is 2, 3, 4, return index 1.

    That requires us to find the first index that its prefix sum of weight is equal to or larger than the random number.
    Because the prefix sum array is in increasing order, we could just use binary search (C++ lower_bound())

    Time complexity is (O(qlogsum_i w_i)), space complexity (O(n))

    class Solution {
    private:
        unsigned seed = chrono::system_clock::now().time_since_epoch().count();
        mt19937 gen;
        uniform_int_distribution<int> distr;
        vector<int> sum;
    public:
        Solution(vector<int>& w) {
            int cur = 0;
            sum.clear();
            for (auto v : w) {
                cur += v;
                sum.push_back(cur);
            }
            gen = mt19937(seed);
            distr = uniform_int_distribution<int>(1, cur);
        }
        int pickIndex() {
            int randnum = distr(gen);
            int pos = lower_bound(sum.begin(), sum.end(), randnum) - sum.begin();
            return pos;
        }
    };
    
    /**
     * Your Solution object will be instantiated and called as such:
     * Solution* obj = new Solution(w);
     * int param_1 = obj->pickIndex();
     */
    

    529. 扫雷游戏

    Use depth-first-search to uncover grids.
    When clicking a grid, first check if it's an "M". If so, the game is over.
    If not, check if there's any "M" adjacent to it. If so, change the grid as the number of adjacent "M"s.
    If there is no "M" adjacent to it, change the grid to "B", and recursively uncover adjacent grids.

    class Solution {
    private:
        int d[8][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}, {1, 1}, {1, -1}, {-1, -1}, {-1, 1}};
        int count(vector<vector<char>> &board, int n, int m, int x, int y) {
            int cnt = 0;
            for (int i = 0; i < 8; i ++) {
                int u = x + d[i][0], v = y + d[i][1];
                if (u < n && u >= 0 && v < m && v >= 0 && (board[u][v] == 'M' || board[u][v] == 'X'))
                    cnt ++;
            }
            return cnt;
        }
        void reveal(vector<vector<char>> &board, int n, int m, int x, int y) {
            if (board[x][y] == 'M') {
                board[x][y] = 'X';
                return;
            }
            int cnt = count(board, n, m, x, y);
            if (cnt != 0) {
                board[x][y] = cnt + '0';
                return;
            }
            board[x][y] = 'B';
            for (int i = 0; i < 8; i ++) {
                int u = x + d[i][0], v = y + d[i][1];
                if (u < n && u >= 0 && v < m && v >= 0 && board[u][v] == 'E') {
                    reveal(board, n, m, u, v);
                }
            }
            return;
        }
    public:
        vector<vector<char>> updateBoard(vector<vector<char>>& board, vector<int>& click) {
            vector<vector<char>> ans;
            int n = board.size(), m = board[0].size();
            ans.clear();
            for (int i = 0; i < n; i ++) {
                vector<char> tmp;
                tmp.clear();
                for (int j = 0; j < m; j ++) {
                    tmp.push_back(board[i][j]);
                }
                ans.push_back(tmp);
            }
            reveal(ans, n, m, click[0], click[1]);
            return ans;
        }
    };
    

    532. 数组中的k-diff数对

    The description of this problem is not right...
    According to the definition of k-diff pairs, (3, 1) and (1, 3) should be different pairs,
    but if your input is [3, 1, 3, 5, 3], the std output is 2, not 4. So (3, 1) and (1, 3) are the same.

    Use a hash table to record the existence of integers, and another hash table to record whether a pair has been counted.
    Scan the array from left to right. When a position is reached, the numbers in the hash table are those which are left to it.
    So it's equivalent to enumerating the latter number in the pair.
    When enumerating a number, check if there is any number able to form a pair with it.
    If so, check if the pair has been counted. In the "checked" table, we record pairs by the bigger number (not the latter).
    Then update the "exist" table and the "checked" table.

    class Solution {
    public:
        int findPairs(vector<int>& nums, int k) {
            unordered_map<int, bool> ext, checked;
            int ans = 0;
            ext.clear();
            checked.clear();
            for (auto v: nums) {
                ans += (ext[v + k] && !checked[v + k]);
                checked[v + k] |= ext[v + k];
                ans += (ext[v - k] && !checked[v]);
                checked[v] |= ext[v - k];
                ext[v] = true;
            }
            return ans;
        }
    };
    

    537. 复数乘法

    If we can divide the string into real part and imaginary part, then we can easily get the two parts of the answer.
    Obviously, the char that divide the two parts are "+" and "i".
    So we could easily get the string format of the two parts, then use stoi() to convert them into integers.
    After calculating the answer, use to_sting() to convert the two parts of the answer into strings, then concat them with "+" and "i".

    class Solution {
    private:
        pair<int, int> to_pair(string num) {
            string tmp;
            int x, y;
            tmp.clear();
            for (auto c : num) {
                if (c != '+' && c != 'i') {
                    tmp.push_back(c);
                } else if (c == '+') {
                    x = stoi(tmp);
                    tmp.clear();
                } else {
                    y = stoi(tmp);
                }
            }
            return make_pair(x, y);
        }
    public:
        string complexNumberMultiply(string num1, string num2) {
            pair<int, int> v1, v2, v;
            v1 = to_pair(num1);
            v2 = to_pair(num2);
            v = make_pair(v1.first * v2.first - v1.second * v2.second, 
                v1.first * v2.second + v1.second * v2.first);
            string ans;
            ans = to_string(v.first) + "+" + to_string(v.second) + "i";
            return ans;
        }
    };
    

    538. 把二叉搜索树转化为累加树

    To convert a binary-search tree to a greater-sum tree, we should walk through the tree from right to left.
    When reaching a node, first get the sum of its right child, then add the sum to its value, then recursively go through its left child.
    The problem is, the value of a node should be carried into its left child, and we should add it to the right-most node in its left child.

    Let "additional value" means the value carried down from a node's ancestor.
    So we kept walking right in a subtree. Once we met a node without a right child, stop and add the additional value.
    Notice that the additional value should be added only once here.
    Then we just add up other nodes normally. When going left, the "additional value" is simply the value of the current node.

    In the implementation below, the return value of "dfs" procedure is the sum of the entire subtree.

    /**
     * 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 {
        int dfs(TreeNode *u, int add) {
            int sum;
            if (u == NULL) return 0;
            if (u->right == NULL) {
                u->val += add;
            } else {
                sum = dfs(u->right, add);
                u->val += sum;
            }
            if (u->left != NULL) {
                return dfs(u->left, u->val);
            } else return u->val;
        }
    public:
        TreeNode* convertBST(TreeNode* root) {
            dfs(root, 0);
            return root;
        }
    };
    

    539. 最小时间差

    The key point of this problem is that the time is recurrent.
    So the difference between 23:59 and 00:00 is 1 minute, not 1439 minutes.
    To solve this problem, simply use a bucket to store all minute values that appeared.
    For a minute value k, store k and k + 1440 (24 * 60 = 1440)
    At last, enumerate each distinct value in the bucket. If a value appeared twice, the answer is 0.
    Otherwise, maintain the last-appeared value in the bucket, get the difference and update the answer.

    class Solution {
    private:
        int convert(string time) {
            string tmp;
            int h, m;
            tmp.clear();
            for (auto c : time) {
                if (c == ':') {
                    h = stoi(tmp);
                    tmp.clear();
                } else tmp.push_back(c);
            }
            m = stoi(tmp);
            return h * 60 + m;
        }
    public:
        int findMinDifference(vector<string>& timePoints) {
            const int day = 24 *60;
            int minutes[day * 2 + 1];
            memset(minutes, 0, sizeof(minutes));
            for (auto str : timePoints) {
                int m = convert(str);
                minutes[m] ++;
                minutes[m + day] ++;
            }
            int last = -1, ans = day * 2;
            for (int i = 0; i <= day * 2; i ++) {
                if (minutes[i] > 1)
                    return 0;
                if (minutes[i] != 0) {
                    if (last == -1) last = i;
                    else {
                        ans = min(ans, i - last);
                        last = i;
                    }
                }
            }
            return ans;
        }
    };
    

    540. 有序数组中的单一元素

    (O(n)) solution is very easy, just xor all numbers, and the repeated number will be wiped out, remaining that unique number.

    To design an (O(logn)) solution, we would first think about binary search.
    To use binary search, the problem should be monotonic.
    By observing this sequence, we could find that, before the unique number appears, the "pair of numbers" is always located in an even index after an odd index.
    But after the unique number appears, the "pair of numbers" will be located in an odd index after an even index.

    According to this monotonic feature, we can perform a binary search.
    Notice that there are many corner cases. You should carefully design the checks, in case of bound error.

    class Solution {
    public:
        int singleNonDuplicate(vector<int>& nums) {
            int l, r, n, mid = -1;
            l = 0;
            r = nums.size() - 1;
            n = nums.size();
            while (l <= r) {
                mid = (l + r) >> 1;
                if ((mid == n - 1 || nums[mid] != nums[mid + 1]) && (mid == 0 || nums[mid] != nums[mid - 1])) {
                    break;
                }
                if (mid % 2 == 0 && mid != n - 1 && nums[mid] == nums[mid + 1] || mid % 2 == 1 && nums[mid] == nums[mid - 1]) {
                    l = (mid & 1) ? (mid + 1) : (mid + 2);
                } else {
                    r = (mid & 1) ? (mid - 1) : (mid - 2);
                }
            }
            if (mid != -1) return nums[mid];
            else return 0;
        }
    };
    
  • 相关阅读:
    C#中正则表达式的分组构造
    CompressionModule压缩模块
    BS程序代码与安全与基本攻击/防御模式
    mssql 性能优化的一些认识
    sql语句基础
    利用.net2.0中的GZip或Deflate压缩文件
    一个完整的ID生成器,并极大地降低了并发重复的概率,转换成十六进制 时间戳
    IE中的条件注释表
    DevExpress的10个使用技巧
    几种检查调试CSS布局的有效方法
  • 原文地址:https://www.cnblogs.com/FromATP/p/15319845.html
Copyright © 2011-2022 走看看