LeetCode 双周赛 90 题解

plus2047 于 2022-10-29 发布

双周赛笔记从简,有兴趣可以留言讨论,C++ 强势回归。

1. 差值数组不同的字符串

image

我承认这个题目把它写成两行不利于阅读。

基本思路是,把每个 word 转换成它对应的 diff 数组并把这个数组转换成字符串(不转换好像也行),然后排序,那个特殊的字符串一定在第一个或者最后一个。

class Solution:
    def oddString(self, words: List[str]) -> str:
        diff = sorted([(",".join(str(ord(x[j + 1]) - ord(x[j])) for j in range(len(x) - 1)), x) for x in words])
        return diff[0][1] if diff[0][0] != diff[1][0] else diff[-1][1]

2. 距离字典两次编辑以内的单词

image

我上周发现我还是有喜欢 C++ 代码的读者的,所以以后特别适合 C++ 的题目,还是给出 C++ 代码。

这个题目适合暴力解,C++ 可以确保不超时。

class Solution {
public:
    vector<string> twoEditWords(vector<string>& queries, vector<string>& dictionary) {
        vector<string> res;
        for(auto& q: queries) {
            for(auto& w: dictionary) {
                int diff = 0;
                for(int i = 0; i < q.size(); i++) {
                    diff += q[i] != w[i];
                }
                if(diff <= 2) {
                    res.push_back(q);
                    break;
                }
            }
        }
        return res;
    }
};

3. 摧毁一系列目标

image

偏数学。某个输入对应的所有被摧毁的目标是关于 space 同余的,因为 t % space= nums[i] % space + c * space % space = nums[i] % space.

所以,我们可以先统计一下每个余数对应的目标数量,要求的输入就是余数是目标数量最大的余数的输入中最小的一个。

class Solution:
    def destroyTargets(self, nums: List[int], space: int) -> int:
        nums.sort()
        # 所有的余数
        mod = [x % space for x in nums]
        # 余数 count
        cnt = collections.Counter(mod)
        # count 最多的余数的 count 数量
        max_cnt = max(cnt.values())

        for m, x in zip(mod, nums):
            if cnt[m] == max_cnt:
                return x
        return -1

4. 下一个更大元素 IV

image

人老了,这种难度的题目都需要使用 C++ 专有数据结构了。

这个题目明显跟单调栈有点关联,但想要构建出基于栈的题解并不是那么容易。反而基于链表或者有序 map 的解法比较容易理解。

这类题目一种常常会有帮助的思路是考虑从大到小求解。我们考虑使用一个 map, key 是数字原本的 index, 然后将所有数字从大到小加入这个 map, 则「下一个更大元素」很容易使用指针运算找到。特别注意,下一个更大元素是严格大于而不是大于等于,因此需要特殊处理一下相等的数字。

class Solution {
public:
    vector<int> secondGreaterElement(vector<int>& nums) {
        int n = nums.size();
        
        // 将原数组逆序排序,并保留原本的 index
        vector<pair<int, int>> pairs(n);
        for(int i = 0; i < n; i++) {
            pairs[i] = {nums[i], i};
        }
        sort(pairs.begin(), pairs.end());
        reverse(pairs.begin(), pairs.end());
        
        vector<int> res(n);
        map<int, int> vals;  // <index, val>

        // 下面两行是特殊写法,可以保证 i, j 指向一个「相等区间」的 begin, end
        for(int i = 0, j = 0; i < n; i = j) {
            while(j < n and pairs[j].first == pairs[i].first) j++;

            for(int k = i; k < j; k++) {
                // 第一个更大元素
                auto it = vals.lower_bound(pairs[k].second);
                // 第二个更大元素
                if(it != vals.end()) it++;
                res[pairs[k].second] = it != vals.end() ? it->second : -1;
            }

            // 把这个区间加入到 map 中
            for(int k = i; k < j; k++) {
                vals[pairs[k].second] = pairs[k].first;
            }
        }
        
        return res;
    }
};

但有序 map 是一种 C++ 特有的数据结构,在 python 中难以实现这个解法。

讨论区大佬们给出了双单调栈写法,记录在这里。这个解法的重点在于,第一个单调栈 s 跟一般的单调栈相似,可以认为它记录的是「没有找到下一个更大元素的数」。这个解法的精妙之处在于,s 出栈之后,又入栈了第二个单调栈 t, t 中保存的相当于「没有找到第二个更大元素的数」。特别注意从 s 转移到 t 时需要保持原顺序,不要因为出栈操作变成逆序。

这类解法如果仔细去思考是可以构建出来的,但总体来说在实战中并不推荐,因为首先其应用范围特别窄,只适合特定题目,其次其操作过于复杂,难设计也难实现。

# https://leetcode.cn/problems/next-greater-element-iv/solution/by-endlesscheng-q6t5/
class Solution:
    def secondGreaterElement(self, nums: List[int]) -> List[int]:
        ans, s, t = [-1] * len(nums), [], []
        for i, x in enumerate(nums):
            while t and nums[t[-1]] < x:
                ans[t.pop()] = x
            j = len(s) - 1
            while j >= 0 and nums[s[j]] < x:
                j -= 1
            t += s[j + 1:]
            del s[j + 1:]
            s.append(i)
        return ans