LeetCode大部分是medium难度不怎么按顺序题解(下)

前言

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

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

upd: 2024.6.1
儿童节快乐!
感觉要找新饭碗了……该刷题了该刷题了

正文

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(qlog\sum_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;
    }
};

91. 四数之和

之前刷到这个题的时候觉得太重复了就没做,这次正好拿来热热手。
先排序,枚举两个数然后two-pointer找剩下两个数。
细节有两个,第一是它数字重复的四元组算同一个(而不是位置重复)。这就要求每一层循环枚举数字的时候都要跳过重复的数字,包括two-pointer时枚举的数字和外面两层循环枚举的数字。
第二个细节是它最大数据范围是1e9,两个数字相加还没问题,三个数字相加就可能爆int了。所以我在涉及到三个数字计算的时候用了long long。

(话说现在码风真的变了非常多啊,大括号可舍得用了)

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        int n = nums.size();
        vector<vector<int>> Answer;
        Answer.clear();
        if (n < 4)
        {
            return Answer;
        }
        sort(nums.begin(), nums.end());
        for (int i = 0; i < n - 3; i ++)
        {
            if (i > 0 and nums[i] == nums[i - 1])
            {
                continue;
            }
            for (int j = i + 1; j < n - 2; j ++)
            {
                if (j > i + 1 and nums[j] == nums[j - 1])
                {
                    continue;
                }
                long long cur_target = (long long)target - nums[i] - nums[j];
                int p1 = j + 1, p2= n - 1;
                while (p1 < p2)
                {
                    while (p1 < p2 && nums[p1] + nums[p2] > cur_target)
                    {
                        p2 --;
                    }
                    if (p1 < p2 && nums[p1] + nums[p2] == cur_target)
                    {
                        vector<int> ans{nums[i], nums[j], nums[p1], nums[p2]};
                        Answer.push_back(ans);
                    }
                    while (p1 < p2 && nums[p1 + 1] == nums[p1])
                    {
                        p1 ++;
                    }
                    p1 ++;
                }
            }
        }
        return Answer;
    }
};

38. 外观数列

总之就是暴力。直觉上里面可能有什么规律?但是看了看题解好像也没有更高深的解法了。就这样吧~

class Solution {
public:
    string RLE(string s)
    {
        int i = 0, n = s.length();
        int count;
        string ans = "";
        while (i < n)
        {
            count = 1;
            while (i < n - 1 and s[i] == s[i + 1])
            {
                count ++;
                i ++;
            }
            ans.push_back(count + '0');
            ans.push_back(s[i]);
            i ++;
        }
        return ans;
    }
    string countAndSay(int n) {
        string cur_str = "1";
        for (int i = 2; i <= n; i ++)
        {
            cur_str = RLE(cur_str);
        }
        return cur_str;
    }
};

40. 组合总和II

总之就是暴力。一开始觉得会不会有什么快捷做法,但感觉这类输出所有方案的,爆搜+剪枝也挺经济了。
因为给的数字全都是正整数,所以排个序从前往后枚举,加到不能加了直接返回。
注意题目里说相同的组合算一个,所以枚举的时候不但不能枚举同一个位置的,也不能枚举同一个值的。

class Solution {
public:
    void dfs(vector<vector<int>>& Answer, vector<int> status, vector<int>& candidates, int target, int i, int sum)
    {
        if (sum == target)
        {
            Answer.push_back(status);
            return;
        }
        int n = candidates.size(), len = status.size();
        if (i >= n)
        {
            return;
        }
        status.push_back(0);  // placeholder
        while (i < n and sum + candidates[i] <= target)
        {
            status[len] = candidates[i];
            dfs(Answer, status, candidates, target, i + 1, sum + candidates[i]);
            i ++;
            while (i < n and candidates[i] == candidates[i - 1])
            {
                i ++;
            }
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        vector<vector<int>> Answer;
        Answer.clear();
        vector<int> status;
        status.clear();
        dfs(Answer, status, candidates, target, 0, 0);
        return Answer;
    }
};

53. 最大子数组和

\(O(n)\)的DP很好写,用\(f[i]\)表示结尾是第i个数字的最大连续子数组和,下一个位置要么就是前一段最大和加上第i个数,要么就是只有第i个数自己。DP的过程中顺便对所有\(f[i]\)求个max,就是最终的答案。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        int f[n], ans;
        f[0] = nums[0];
        ans = f[0];
        for (int i = 1; i < n; i ++)
        {
            f[i] = max(f[i - 1] + nums[i], nums[i]);
            ans = max(ans, f[i]);
        }
        return ans;
    }
};

71. 简化路径

想了半天STL怎么调用。原来deque是可以用下标访问的,但queue不可以。

class Solution {
public:
    string simplifyPath(string path) {
        deque<string> dirs;
        int i = 0, len = path.length();
        while (i < len)
        {
            string dir_name = "";
            while (i < len && path[i] == '/')
            {
                i ++;
            }
            if (i >= len)
            {
                break;
            }
            while (i < len && path[i] != '/')
            {
                dir_name.push_back(path[i]);
                i ++;
            }
            if (dir_name == ".")
            {
                continue;
            }
            else if (dir_name == "..")
            {
                if (!dirs.empty())
                {
                    dirs.pop_back();
                }
            }
            else
            {
                dirs.push_back(dir_name);
            }
        }
        string Answer = "";
        int n = dirs.size();
        for (int i = 0; i < n; i ++)
        {
            Answer = Answer + "/" + dirs[i];
        }
        if (Answer == "")
        {
            Answer = "/";
        }
        return Answer;
    }
};

72. 编辑距离

模板题。

class Solution {
public:
    int minDistance(string word1, string word2) {
        int n = word1.size(), m = word2.size();
        int f[n + 1][m + 1];
        f[0][0] = 0;
        for (int i = 1; i <= n; i ++)
        {
            f[i][0] = i;
        }
        for (int j = 1; j <= m; j ++)
        {
            f[0][j] = j;
        }
        for (int i = 1; i <= n; i ++)
        {
            for (int j = 1; j <= m; j ++)
            {
                f[i][j] = min(f[i - 1][j - 1], min(f[i - 1][j], f[i][j - 1])) + 1;
                if (word1[i - 1] == word2[j - 1])
                {
                    f[i][j] = min(f[i][j], f[i - 1][j - 1]);
                }
            }
        }
        return f[n][m];
    }
};

57. 插入区间

就是区间合并,中间判断一下新的区间该插入在哪里就可以了。

class Solution {
public:
    void update(vector<vector<int>>& Answer, int& L, int& R, vector<int>& i)
    {
        if (i[0] > R)
        {
            if (L != -1)
            {
                Answer.push_back(vector<int>{L, R});
            }
            L = i[0]; R = i[1];
        }
        else
        {
            R = max(R, i[1]);
        }
    }

    vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) {
        vector<vector<int>> Answer;
        Answer.clear();
        int L = -1, R = -1;
        bool flag = true;
        for (auto i : intervals)
        {
            if (flag)
            {
                flag = flag && (newInterval[0] >= i[0]);
                if (!flag)
                {
                    update(Answer, L, R, newInterval);
                }
            }
            update(Answer, L, R, i);
        }
        if (flag)
        {
            update(Answer, L, R, newInterval);
        }
        Answer.push_back(vector<int>{L, R});
        return Answer;
    }
};

90. 子集II

一开始想的是用二进制枚举子集然后再去重,但后来发现判重很麻烦,不如一开始枚举的时候就保证没有重复的。
所以先排序再dfs,每次找到一组相同的数字然后枚举放几个数字在子集里就可以了。

class Solution {
public:
    void dfs(vector<vector<int>>& Answer, vector<int>& nums, vector<int> subset, int i, int n)
    {
        if (i == n)
        {
            Answer.push_back(subset);
            return;
        }
        int count = 1;
        i ++;
        while (i < n && nums[i] == nums[i - 1])
        {
            i ++; count ++;
        }
        dfs(Answer, nums, subset, i, n);
        for (int k = 1; k <= count; k ++)
        {
            subset.push_back(nums[i - 1]);
            dfs(Answer, nums, subset, i, n);
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<vector<int>> Answer;
        Answer.clear();
        vector<int> subset;
        subset.clear();
        sort(nums.begin(), nums.end());
        dfs(Answer, nums, subset, 0, nums.size());
        return Answer;
    }
};

93. 复原IP地址

枚举插入点的位置就可以了。可以通过一些条件限制让它枚举得尽量少,比如枚举一个点的时候一定要在上一个点之后不超过三个数字的位置。第一个点距离开头和最后一个点距离结尾也不能超过三个数字。这样既能尽量枚举可行解,又避免判定的时候炸int。
一开始忘了判断第一个点和开头的距离,还WA了一次。心态炸辣!

class Solution {
public:
    bool valid_num(string& s, int l, int r)
    {
        int x = 0;
        if (r - l > 1 && s[l] == '0')
        {
            return false;
        }
        for (int i = l ;i < r; i ++)
        {
            x = x * 10 + (s[i] - '0');
        }
        return x >= 0 && x <= 255;
    }
    bool check(string& s, int p1, int p2, int p3)
    {
        return valid_num(s, 0, p1) && valid_num(s, p1, p2)
            && valid_num(s, p2, p3) && valid_num(s, p3, s.length());
    }
    string get_ans(string s, int p1, int p2, int p3)
    {
        string res = s;
        res.insert(p1, ".");
        res.insert(p2 + 1, ".");
        res.insert(p3 + 2, ".");
        return res;
    }
    vector<string> restoreIpAddresses(string s) {
        vector<string> Answer;
        Answer.clear();
        int len = s.length(), maxi = min(len - 2, 4);
        for (int i = 1; i < maxi; i ++)
        {
            int maxj = min(len - 1, i + 4);
            for (int j = i + 1; j < maxj; j ++)
            {
                int maxk = min(len, j + 4);
                for (int k = j + 1; k < maxk; k ++)
                {
                    if (k > len - 4 && check(s, i, j, k))
                    {
                        Answer.push_back(get_ans(s, i, j, k));
                    }
                }
            }
        }
        return Answer;
    }
};

95. 不同的二叉搜索树II

递归好题。题目中要求的点的编号有序。那么一棵n个点的二叉树就由:有序的左子树、根节点、有序的右子树构成。
而这个顺序在题目中是左序遍历。那么假设一棵二叉树的左子树有i个点,这棵二叉树就有:编号1i的左子树、编号为i+1的根节点、编号为i+2n的右子树构成。
左右子树分别满足有序的性质,那么就可以递归构造。每次只要知道i个点的所有方案和n-i-1个点的所有方案,分别把它们接在左右子树就可以了。
有一个细节是,接上右子树的时候需要把它的权值整体加上一个偏移量(i+1)。而这里存储的都是指针。所以为了防止修改权值的时候错误修改到别的树上,每次构造的时候都要把左右子树的节点单独copy一份再修改,这个copy的过程也是递归的。

/**
 * 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:
    vector<TreeNode*> mem[9];
    TreeNode* duplicate(TreeNode* root, int offset)
    {
        if (root == nullptr)
        {
            return nullptr;
        }
        TreeNode *new_root = new TreeNode(root->val + offset, duplicate(root->left, offset), duplicate(root->right, offset));
        return new_root;
    }
    vector<TreeNode*> dfs(int n)
    {
        if (!empty(mem[n]))
        {
            return mem[n];
        }
        vector<TreeNode*> Answer;
        if (n == 0)
        {
            Answer.push_back(nullptr);
        }
        else
        {
            for (int i = 0; i < n; i ++)
            {
                vector<TreeNode*> left_child = dfs(i);
                vector<TreeNode*> right_child = dfs(n - i - 1);
                for (auto left : left_child)
                {
                    for (auto right: right_child)
                    {
                        TreeNode* curr_left = duplicate(left, 0);
                        TreeNode* curr_right = duplicate(right, i + 1);
                        TreeNode* curr_root = new TreeNode(i + 1, curr_left, curr_right);
                        Answer.push_back(curr_root);
                    }
                }
            }
        }
        mem[n] = Answer;
        return Answer;
    }
    vector<TreeNode*> generateTrees(int n) {
        for (int i = 0; i <= n; i ++)
        {
            mem[i].clear();
        }
        return dfs(n);
    }
};

96. 不同的二叉搜索树

跟上一题思路一样,只不过不用输出方案,直接求方案数就可以了。

class Solution {
public:
    int f[20];
    int dfs(int n)
    {
        if (f[n] != 0)
        {
            return f[n];
        }
        if (n == 0)
        {
            f[n] = 1;
        }
        else
        {
            for (int i = 0; i < n; i ++)
            {
                f[n] += dfs(i) * dfs(n - i - 1);
            }
        }
        return f[n];
    }
    int numTrees(int n) {
        memset(f, 0, sizeof(f));
        return dfs(n);
    }
};

97. 交错字符串

被题目描述骗到了,写了一种非常愚蠢的做法。。
一开始总觉得要两个字符串交替使用,所以用\(f[i][j][0/1]\)表示用s1的前i个字符和s2的前j个字符拼接,结尾是s1/s2能不能拼接成s3的前i+j个字符。
但是实际上只要一个一个字符往里加,类似编辑距离的那种DP方式就可以了。反正只有两个字符串,不管怎么取一定是两个交替使用的。。。
这样也可以用滚动数组优化空间了。

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        int len1 = s1.length(), len2 = s2.length(), len3 = s3.length();
        bool f[len1 + 1][len2 + 1][2];
        memset(f, 0, sizeof(f));
        f[0][0][0] = f[0][0][1] = true;
        if (len3 != len1 + len2)
        {
            return false;
        }
        for (int i = 1; i <= len1; i ++)
            if (s1[i - 1] == s3[i - 1])
                f[i][0][0] = true;
            else break;
        for (int j = 1; j <= len2; j ++)
            if (s2[j - 1] == s3[j - 1])
                f[0][j][1] = true;
            else break;
        for (int i = 1; i <= len1; i ++)
            for (int j = 1; j <= len2; j ++)
            {
                int tmp = i;
                while (tmp > 0 && s1[tmp - 1] == s3[tmp + j - 1])
                {
                    f[i][j][0] |= f[tmp - 1][j][1];
                    tmp --;
                }
                tmp = j;
                while (tmp > 0 && s2[tmp - 1] == s3[tmp + i - 1])
                {
                    f[i][j][1] |= f[i][tmp- 1][0];
                    tmp --;
                }
            }
        return f[len1][len2][0] || f[len1][len2][1];
    }
};

98. 验证二叉搜索树

又写了一个比较愚蠢的做法。因为判断是否合法需要维护:当前子树是否合法;当前字数的最小值;当前子树的最大值,所以把这三个东西都维护在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 {
public:
    #define return_type pair<bool, pair<int, int>>
    return_type dfs(TreeNode* root)
    {
        return_type res = make_pair(true, make_pair(root->val, root->val));
        if (root->left == nullptr && root->right == nullptr)
        {
            return res;
        }
        if (root->left != nullptr)
        {
            return_type left_res = dfs(root->left);
            res.first = res.first && left_res.first && left_res.second.second < res.second.first;
            res.second.first = left_res.second.first;
        }
        if (!res.first) return res;
        if (root->right != nullptr)
        {
            return_type right_res = dfs(root->right);
            res.first = res.first && right_res.first && right_res.second.first > res.second.second;
            res.second.second = right_res.second.second;
        }
        return res;
    }
    bool isValidBST(TreeNode* root) {
        return_type res = dfs(root);
        return res.first;
    }
};

其实只需要做一遍左序遍历就可以了,因为二叉搜索树左序遍历天然是有序的,用一个全局变量判断搜索过程中是否有序就可以了。
有一个细节是,节点的值可能是INF_MIN。所以全局变量的初始值要么比INT_MIN还要小(用long long存储),要么就单独开一个变量跳过第一次比较(避免int变量和INT_MIN直接比较)。

/**
 * 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 current_value;
    bool unset = true;
    bool isValidBST(TreeNode* root) {
        if (root == nullptr) return true;
        bool left_valid = isValidBST(root->left);
        if (!unset && current_value >= root->val)
        {
            return false;
        }
        current_value = root->val;
        unset = false;
        bool right_valid = isValidBST(root->right);
        return left_valid && right_valid;
    }
};

102. 二叉树的层序遍历

用bfs解决。因为每层的节点要单独放在一个vector里,所以bfs正好可以保持这种结构,可以很方便地构造答案。
注意特判根节点是null的情况。这种时候就不要入队。

/**
 * 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:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> Answer;
        Answer.clear();
        queue<vector<TreeNode*>> bfs_queue;
        vector<TreeNode*> status;
        status.clear();
        if (root != nullptr)
        {
            status.push_back(root);
            bfs_queue.push(status); 
        }
        while (!bfs_queue.empty())
        {
            vector<TreeNode*> head = bfs_queue.front();
            bfs_queue.pop();
            vector<int> ans;
            ans.clear();
            vector<TreeNode*> child;
            child.clear();
            for (auto node : head)
            {
                ans.push_back(node->val);
                if (node->left != nullptr)
                {
                    child.push_back(node->left);
                }
                if (node->right != nullptr)
                {
                    child.push_back(node->right);
                }
            }
            if (!child.empty())
            {
                bfs_queue.push(child);
            }
            Answer.push_back(ans);
        }
        return Answer;
    }
};

103. 二叉树的锯齿形层序遍历

和上一道题的写法完全一样,只不过隔一层reverse一次就可以了。

/**
 * 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:
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
        vector<vector<int>> Answer;
        Answer.clear();
        queue<vector<TreeNode*>> bfs_queue;
        vector<TreeNode*> status;
        status.clear();
        if (root != nullptr)
        {
            status.push_back(root);
            bfs_queue.push(status);
        }
        int reverse_flag = 0;
        while (!bfs_queue.empty())
        {
            vector<TreeNode*> head = bfs_queue.front();
            bfs_queue.pop();
            int len = head.size();
            vector<int> ans;
            ans.clear();
            vector<TreeNode*> child;
            child.clear();
            for (auto node : head)
            {
                ans.push_back(node->val);
                if (node->left != nullptr)
                {
                    child.push_back(node->left);
                }
                if (node->right != nullptr)
                {
                    child.push_back(node->right);
                }
            }
            if (!child.empty())
            {
                bfs_queue.push(child);
            }
            if (reverse_flag)
            {
                reverse(ans.begin(), ans.end());
            }
            Answer.push_back(ans);
            reverse_flag ^= 1;
        }
        return Answer;
    }
};

105. 从前序与中序遍历序列构造二叉树

前序遍历的第一个点一定是根节点。找到这个点在中序遍历中的位置,则可以划分出左右子树。递归处理即可。

/**
 * 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:
    map<int, int> inorder_index;
    TreeNode* build(vector<int>& preorder, vector<int>& inorder, int& preorder_start, int inorder_start, int inorder_end)
    {
        if (inorder_start == inorder_end)
        {
            return nullptr;
        }
        int root_val = preorder[preorder_start++];
        TreeNode* root = new TreeNode(root_val);
        int root_inorder = inorder_index[root_val];
        root->left = build(preorder, inorder, preorder_start, inorder_start, root_inorder);
        root->right = build(preorder, inorder, preorder_start, root_inorder + 1, inorder_end);
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = inorder.size();
        for (int i = 0; i < n; i ++)
        {
            inorder_index[inorder[i]] = i;
        }
        int preorder_start = 0;
        TreeNode* root = build(preorder, inorder, preorder_start, 0, n);
        return root;
    }
};

106. 从中序与后序遍历构造二叉树

和上一道题完全一样,只不过要从右往左建树,从后往前枚举后序遍历。

/**
 * 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:
    map<int, int> inorder_index;
    TreeNode* build(vector<int>& inorder, vector<int>& postorder, int& postorder_start, int inorder_start, int inorder_end)
    {
        if (inorder_start == inorder_end)
        {
            return nullptr;
        }
        int root_val = postorder[postorder_start--];
        int root_index = inorder_index[root_val];
        TreeNode* root = new TreeNode(root_val);
        root->right = build(inorder, postorder, postorder_start, root_index + 1, inorder_end);
        root->left = build(inorder, postorder, postorder_start, inorder_start, root_index);
        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        int n = inorder.size();
        for (int i = 0; i < n; i ++)
        {
            inorder_index[inorder[i]] = i;
        }
        int postorder_start = n - 1;
        return build(inorder, postorder, postorder_start, 0, n);
    }
};

122. 买卖股票的最佳时机II

因为同时只能持有一股,所以实际上就是找到序列中的极长单调不降序列。在这个序列内部无论怎么交易都是等价的。
递推的时候可以每次和前一天比较,如果今天比前一天多,那就进行一次买卖。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int ans = 0, n = prices.size();
        int last = prices[0];
        for (int i = 1; i < n; i ++)
        {
            if (prices[i] < prices[i - 1])
            {
                last = prices[i];
            }
            else
            {
                ans += prices[i] - prices[i - 1];
            }
        }
        return ans;
    }
};

138. 随机链表的复制

笨办法,用一个map存储新旧节点的映射。先按照next复制一遍把整个新链表建立好,再用map修改random就可以了。

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    Node* copyRandomList(Node* head) {
        map<Node*, Node*> copy_map;
        Node* new_head = nullptr;
        Node* new_tail;
        for (Node* i = head; i != nullptr; i = i->next)
        {
            Node* new_node = new Node(i->val);
            new_node->random = i->random;
            if (new_head == nullptr)
            {
                new_head = new_node;
                new_tail = new_head;
            }
            else
            {
                new_tail->next = new_node;
                new_tail = new_node;
            }
            copy_map[i] = new_node;
        }
        for (Node* i = new_head; i != nullptr; i = i->next)
        {
            i->random = copy_map[i->random];
        }
        return new_head;
    }
};

146. LRU缓存

关键在于key的值域比较小,所以可以用一个表存储每个key对应的哪个节点。
由于题目要求时间O(1),所以只能用空间换时间。建立一个链表,链表的顺序就代表访问的时间顺序。链表里要存储对应的key和value。
get操作就是去表里查找这个key有没有对应的节点。找到了对应的节点就找到了value。否则返回-1。然后要把这个节点的时间顺序刷新,就是把这个点从表里摘出来放到最后。
put操作也是去表里查找对应的key。如果有,就更新value并且刷新时间顺序;否则可能要插入新节点。如果capacity还够用,就直接插入一个新的;如果不够,就把队头节点(最久没有被访问的)拿出来用。
手动实现双向链表的细节比较多。。包括要更新双向指针、维护head和tail、边界条件判断等等。

class LRUCache {
public:
    int len, cap, k[3001], val[3001], next[3001], prev[3001], head, tail;
    int key_map[10001];
    LRUCache(int capacity) {
        len = head = tail = 0;
        cap = capacity;
        memset(k, 0, sizeof(k));
        memset(val, 0, sizeof(val));
        memset(prev, 0, sizeof(prev));
        memset(next, 0, sizeof(next));
        memset(key_map, 0, sizeof(key_map));
    }
    
    int get(int key) {
        if (!key_map[key])
        {
            return -1;
        }
        int cur_node = key_map[key];
        if (prev[cur_node])
            next[prev[cur_node]] = next[cur_node];
        else
            head = next[cur_node];
        if (next[cur_node])
            prev[next[cur_node]] = prev[cur_node];
        else
            tail = prev[cur_node];
        if (tail != 0)
        {
            next[tail] = cur_node;
            prev[cur_node] = tail;
            tail = cur_node;
            next[cur_node] = 0;
        }
        else
        {
            head = tail = cur_node;
        }
        return val[cur_node];
    }
    
    void put(int key, int value) {
        if (!key_map[key])
        {
            if (len < cap)
            {
                len ++;
                k[len] = key;
                val[len] = value;
                key_map[key] = len;
                if (head == 0)
                {
                    head = tail = len;
                }
                else
                {
                    prev[len] = tail;
                    next[tail] = len;
                    tail = len;
                }
            }
            else
            {
                key_map[k[head]] = 0;
                key_map[key] = head;
                k[head] = key;
                val[head] = value;
                prev[next[head]] = 0;
                next[tail] = head;
                prev[head] = tail;
                head = next[head];
                next[next[tail]] = 0;
                tail = next[tail];
            }
        }
        else
        {
            int cur_node = key_map[key];
            if (prev[cur_node])
                next[prev[cur_node]] = next[cur_node];
            else
                head = next[cur_node];
            if (next[cur_node])
                prev[next[cur_node]] = prev[cur_node];
            else
                tail = prev[cur_node];
            if (tail != 0)
            {
                next[tail] = cur_node;
                prev[cur_node] = tail;
                tail = cur_node;
                next[cur_node] = 0;
            }
            else
            {
                head = tail = cur_node;
            }
            val[cur_node] = value;
        }
    }
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

150. 逆波兰表达式求值

后缀表达式模板。用栈维护一下操作数,每次碰到运算符弹出两个进行运算,再把结果压进去。

class Solution {
public:
    bool is_operator(string str)
    {
        return (str.length() == 1) && (str[0] == '+' || str[0] == '-' || str[0] == '*' || str[0] == '/');
    }
    int calc(int op1, int op2, char op)
    {
        switch (op)
        {
            case '+': return op1 + op2;
            case '-': return op1 - op2;
            case '*': return op1 * op2;
            case '/': return op1 / op2;
        }
        return 0;
    }
    int trans_int(string str)
    {
        int sym = (str[0] == '-')?-1:1;
        int len = str.length(), i = (str[0] == '-')?1:0;
        int x = 0;
        for (;i<len; i ++)
        {
            x = x * 10 + str[i] - '0';
        }
        return x * sym;
    }
    int evalRPN(vector<string>& tokens) {
        stack<int> nums;
        for (auto str : tokens)
        {
            if (is_operator(str))
            {
                int op1 = nums.top();nums.pop();
                int op2 = nums.top();nums.pop();
                nums.push(calc(op2, op1, str[0]));
            }
            else
            {
                nums.push(trans_int(str));
            }
        }
        return nums.top();
    }
};

155. 最小栈

题目描述说得很玄乎,实际上就是维护两个栈,一个实际数值、一个前缀最小值就可以了。往后加入数字不会破坏前面的前缀最小值。

class MinStack {
public:
    stack<int> st;
    stack<int> stmin;
    MinStack() {
        while (!st.empty()) st.pop();
        while (!stmin.empty()) stmin.pop();
    }
    
    void push(int val) {
        st.push(val);
        if (stmin.empty())
            stmin.push(val);
        else
            stmin.push(min(stmin.top(), val));
    }
    
    void pop() {
        st.pop();
        stmin.pop();
    }
    
    int top() {
        return st.top();
    }
    
    int getMin() {
        return stmin.top();
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(val);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */

162. 寻找峰值

这个题比较有意思。由于设定了端点的两个元素一定是最小值,所以它要找的其实是最靠左边的下降的斜率。
要求\(O(logn)\)所以应该用二分做。问题就在于mid的斜率分别是上升/下降时判断丢掉左半边或右半边。
如果mid的斜率是上升,那右边一定有解:要么右边存在下降的斜率(先上升后下降,一定有解),要么右边是单调上升于是解是最后一个数字。
反之,如果mid的斜率是下降,那么左边一定有解:要么左边存在上升的斜率(先上升后下降,一定有解),要么左边单调下降于是解是第一个数字。

class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        int n = nums.size();
        if (n == 1) return 0;
        if (nums[n - 1] > nums[n - 2]) return n - 1;
        int ans, mid, l = 0, r = n - 1;
        while (l <= r)
        {
            mid = (l + r) >> 1;
            if (mid == 0 && nums[mid] > nums[mid + 1]){
                ans = mid; break;
            }
            if (nums[mid] > nums[mid + 1] && nums[mid] > nums[mid - 1])
            {
                ans = mid; break;
            }
            if (nums[mid] < nums[mid + 1])
                l = mid + 1;
            else
                r = mid - 1;
        }
        return ans;
    }
};

337. 打家劫舍III

树上DP。每个节点维护两个值分别表示选它/不选它的最大值。
如果当前节点要选,那么它的子节点就一定不能选;否则子节点可选可不选。
从下往上DP,最后看根节点选或不选哪个更大就可以了。

/**
 * 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:
    pair<int, int> dfs(TreeNode* root)
    {
        if (root == nullptr)
            return make_pair(0, 0);
        pair<int, int> left = dfs(root->left);
        pair<int, int> right = dfs(root->right);
        return make_pair(root->val + left.second + right.second,
                         max(left.first, left.second) + max(right.first, right.second));
    }
    int rob(TreeNode* root) {
        pair<int, int> stat = dfs(root);
        return max(stat.first, stat.second);
    }
};

237. 删除链表中的节点

脑筋急转弯题。。要么它为什么要写一长串定义怎样才是“把链表中的节点删掉”,它看的根本不是地址而是节点的值。
所以不需要真正删掉它给定的节点,只要把当前节点的值改成下一个节点的值,然后删掉下一个节点(恰好题目保证了要删掉的一定不是最后一个点,也就是“下一个节点”一定存在),就达成了在题目的定义下删除“当前节点”。

 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    void deleteNode(ListNode* node) {
        int reserved_val = node->next->val;
        node->next = node->next->next;
        node->val = reserved_val;
    }
};

343. 整数拆分

简单小DP。注意f[i]表示的是“至少拆分成两个数字”的最大乘积,所以更新的时候还得更新一个“恰好拆分成两个数字”的情况,不能只从前面的f推

class Solution {
public:
    int integerBreak(int n) {
        int f[n + 1];
        memset(f, 0, sizeof(f));
        for (int i = 2; i <= n; i ++)
            for (int j = 1; j <= i - 1; j ++)
            {
                f[i] = max(f[i], j * f[i - j]);
                f[i] = max(f[i], j * (i - j));
            }
        return f[n];
    }
};
posted @ 2021-09-22 15:22  FromATP  阅读(86)  评论(0编辑  收藏  举报