Leetcode 每日一题和每日一题的下一题刷题笔记 20/30

431 阅读7分钟

Leetcode 每日一题和每日一题的下一题刷题笔记 20/30

写在前面

这是我参与更文挑战的第20天,活动详情查看:更文挑战

快要毕业了,才发现自己被面试里的算法题吊起来锤。没办法只能以零基础的身份和同窗们共同加入了力扣刷题大军。我的同学们都非常厉害,他们平时只是谦虚,口头上说着自己不会,而我是真的不会。。。乘掘金鼓励新人每天写博客,我也凑个热闹,记录一下每天刷的前两道题,这两道题我精做。我打算每天刷五道题,其他的题目嘛,也只能强行背套路了,就不发在博客里了。

本人真的只是一个菜鸡,解题思路什么的就不要从我这里参考了,编码习惯也需要改进,各位如果想找刷题高手请教问题我觉得去找 宫水三叶的刷题日记 这位大佬比较好。我在把题目做出来之前尽量不去看题解,以免和大佬的内容撞车。

另外我也希望有得闲的大佬提供一些更高明的解题思路给我,欢迎讨论哈!

好了废话不多说开始第二十天的前两道题吧!

2021.6.20 每日一题

1600. 皇位继承顺序

这题目读的人都瞌睡了,老外把族谱叫 family tree,不知道对各位有没有什么启发。这道题实际上是在玩一个树的数据结构,一个人可能有很多孩子,这就是一个多叉树。然后确定继承顺序,在中国继承顺序一般是嫡长子继承制,但是外国可能有那么点不太一样,具体有哪些不一样,请去看题面。题目一开始就想到了每个地区的人都会做到这道题,所以直接把继承规则举了个例子,防止做题的人自觉带入自己文化里的刻板印象。

好,现在假如各位和我一样已经大概理解了题目想表达的意思,那么,请把例子中的族谱画出来,是什么形状的多叉树呢?

从第一个例子开始

比方说,假设王国由国王,他的孩子 Alice 和 Bob (Alice 比 Bob 年长)和 Alice 的孩子 Jack 组成。

1600. 皇位继承顺序.png

看一看,是不是多叉树的前序遍历?这个例子里面人太少了,不好说明问题,看下一个例子

输入: ["ThroneInheritance", "birth", "birth", "birth", "birth", "birth", "birth", "getInheritanceOrder", "death", "getInheritanceOrder"]

[["king"], ["king", "andy"], ["king", "bob"], ["king", "catherine"], ["andy", "matthew"], ["bob", "alex"], ["bob", "asha"], [null], ["bob"], [null]]

输出: [null, null, null, null, null, null, null, ["king", "andy", "matthew", "bob", "alex", "asha", "catherine"], null, ["king", "andy", "matthew", "alex", "asha", "catherine"]]

解释:

ThroneInheritance t= new ThroneInheritance("king"); // 继承顺序:king

t.birth("king", "andy"); // 继承顺序:king > andy

t.birth("king", "bob"); // 继承顺序:king > andy > bob

t.birth("king", "catherine"); // 继承顺序:king > andy > bob > catherine

t.birth("andy", "matthew"); // 继承顺序:king > andy > matthew > bob > catherine

t.birth("bob", "alex"); // 继承顺序:king > andy > matthew > bob > alex > catherine

t.birth("bob", "asha"); // 继承顺序:king > andy > matthew > bob > alex > asha > catherine

t.getInheritanceOrder(); // 返回 ["king", "andy", "matthew", "bob", "alex", "asha", "catherine"]

t.death("bob"); // 继承顺序:king > andy > matthew > bob(已经去世)> alex > asha > catherine

t.getInheritanceOrder(); // 返回 ["king", "andy", "matthew", "alex", "asha", "catherine"]

不要觉得这么磨叽,我自己家也没有什么宝贝等我去继承,我做这道题费什么劲?你把这个继承的王位改成你未来的小孩要继承的你的花呗,是不是一下觉得卷起来了[手动狗头],而且国家现在又让年轻人放开生小孩,不然未来没劳动力自己八十岁(假如还在的话)还在电脑前面 C·R·U·D 那画面太美不敢看。

现在画一画这个族谱看一看这个国王的花呗最后得谁来还:

1600. 皇位继承顺序-第 2 页.png

那个红叉表示人老掉了,逃过一劫,不用还祖传的花呗了。这个人的名字不会出现在继承者名单上了。

注意看 t.getInheritanceOrder() 这个是不是返回的还活着的继承者们(顺序还是多叉树的前序遍历),对吧。

那怎么从继承者名单里把那些不用还花呗的去掉呢?当然是给这些人再建立一个表了,用哈希集合很不错,反正这一大家子不会有重名。是不是越做越像生死簿管理系统了[手动狗头]。

birth(string parentName, string childName) 就是家族添新丁,death(string name) 就是在另外一个小本本上把老掉的人名字记下来,以后看到这些人要从继承者名单里面划掉。getInheritanceOrder() 就是把继承者名单拿出来更新一下念一遍给这些人听。ThroneInheritance(string kingName) 就是新创建一个祖传花呗族谱,所有的孩子都要还他们老祖宗的花呗,他们自己的花呗另说。祖传花呗族谱可以用哈希映射,键是人名字,值是他的孩子们,还没有孩子的,值就是空出来的。好了,需求对的差不多了,可以写代码了。


class ThroneInheritance {
private:
    unordered_map<string, vector<string>> ant_credit_pay_list;
    unordered_set<string> free_list;
    string king;

public:
    ThroneInheritance(string kingName): king{move(kingName)} {}
    
    void birth(string parentName, string childName) {
        ant_credit_pay_list[move(parentName)].push_back(move(childName));
    }
    
    void death(string name) {
        free_list.insert(move(name));
    }
    
    vector<string> getInheritanceOrder() {
        vector<string> ans;

        function<void(const string&)> preorder = [&](const string& name) {
            if (!free_list.count(name)) {
                ans.push_back(name);
            }
            if (ant_credit_pay_list.count(name)) {
                for (const string& childName: ant_credit_pay_list[name]) {
                    preorder(childName);
                }
            }
        };

        preorder(king);
        return ans;
    }
};

/**
 * Your ThroneInheritance object will be instantiated and called as such:
 * ThroneInheritance* obj = new ThroneInheritance(kingName);
 * obj->birth(parentName,childName);
 * obj->death(name);
 * vector<string> param_3 = obj->getInheritanceOrder();
 */

image.png

这道题我自己是写不出来的,C++各种新特性我玩不转,把题解里面的匿名函数和 move 这个东西玩明白了对我来说就算学到东西了。

不要纠结什么 std::functionLambda expressions,也不要纠结 std::move 是不是把原来的对象移走了,不要纠结语法,学习语言是相通的,多说比你多看语法书的提高要快不止一点点,当然说得多了也是要看看语法书的,有些方言和口癖要及时纠正。这种写法只是一种规范,这个匿名函数出现的理由也很简单,外面这个函数签名都定死了,我里面就是想递归一下。不管我用不用 std::move 我肯定是不会去动原来的那个对象的,这也是一种自然而然的习惯和规范,照着写终归没坏处。别折腾自己,就像非要在深圳找东北厨子给你做家里面那一口锅包又,你不花钱谁花钱。

2021.6.20 每日一题下面的题

209. 长度最小的子数组

前缀和它又回来了。。。思路不用说了吧,两个位置的前缀和大于 target 的情况有很多,每次找到一种情况要更新最小的两个位置之差。


class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        int n = nums.size();
        if (n == 0) {
            return 0;
        }
        int ans = INT_MAX;
        vector<int> sums(n + 1, 0);
        for (int i = 1; i <= n; i++) {
            sums[i] = sums[i - 1] + nums[i - 1];
        }
        for (int i = 1; i <= n; i++) {
            int target = s + sums[i - 1];
            auto bound = lower_bound(sums.begin(), sums.end(), target);
            if (bound != sums.end()) {
                ans = min(ans, static_cast<int>((bound - sums.begin()) - (i - 1)));
            }
        }
        return ans == INT_MAX ? 0 : ans;
    }
};

image.png

题解里还有暴力和滑动窗口(双指针)的方法,这两个方法都很不错,而且不出意外之前我应该积累过。

这类题用双指针也挺爽的,想象这个长长的数组上有个毛毛虫在牯牛牯牛,每次都要把最小长度更新一下,爬到最后最小的长度就出来了。


class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        int n = nums.size();
        if (n == 0) {
            return 0;
        }
        int ans = INT_MAX;
        int start = 0, end = 0;
        int sum = 0;
        while (end < n) {
            sum += nums[end];
            while (sum >= s) {
                ans = min(ans, end - start + 1);
                sum -= nums[start];
                start++;
            }
            end++;
        }
        return ans == INT_MAX ? 0 : ans;
    }
};

image.png

这种双指针的思路在找到比目标大的那一段以后毛毛虫长度就固定了。其实这种思路确实可以写贪吃蛇的代码,二维空间也可以,记录位置就行。

小结

std::functionLambda expressionsstd::move。前缀和和双指针两种方法。

参考链接