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

232 阅读5分钟

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

写在前面

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

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

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

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

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

2021.6.22 每日一题

剑指 Offer 38. 字符串的排列

这道题能学到的方法是回溯法,就是试一下对不对,不对先往回一步看其他的可能分支。提到回溯就会有人提到树结构的深度优先搜索,道理是一样的。

因为要往前回一步,这里要找个东西保存能退回的地方。我们的目标是找出不重复的所有排列,所以对于原始字符串中重复的字符,一是哈希,二是规定回溯条件。规定重复的字符全部回溯到一个位置上。这个规定隐含了一句话,这些重复的字符要放一块,不然回溯的时候会很乱(回溯的“试一试”这一步肯定是拿元素字符串里的字符挨个去试,如果相重复的字符没有放到一块,那么回溯的时候没法保证重复的字符全部回溯到一个位置上,可以手推一下,这种情况下回溯是先回溯到重复字符出现的第二次第三次第 n 次然后逐步回溯到重复字符出现的第一次,和我说的不一样)。讲到这里让我想起来最近的洛基剧集,里面的时间管理局把时间线上的分支抹掉其实也有这种思想在里面。我要把洛基捣乱引发的分支全部抹去,要先把这些分支找出来,排序的时候不要按照时间顺序,而是按照引发时间分支的时间犯的名字字母顺序,把洛基直接引发的时间线分支放一起,回溯直接回溯到他第一次捣乱时直接引发的那个分支上,其他分支自然也会消失。(可能例子不太恰当,但是能有感性认识的共鸣,我举例子的目的就达到了)

下面写代码


class Solution {
public:
    vector<string> rec;
    vector<int> vis;
    void backtrack(const string& s, int i, int n, string& perm) {
        if (i == n) {
            rec.push_back(perm);
            return;
        }
        for (int j = 0; j < n; j++) {
            if (vis[j] || (j > 0 && !vis[j - 1] && s[j - 1] == s[j])) {
                continue;
            }
            vis[j] = true;
            perm.push_back(s[j]);
            backtrack(s, i + 1, n, perm);
            perm.pop_back();
            vis[j] = false;
        }
    }
    vector<string> permutation(string s) {
        int n = s.size();
        vis.resize(n);
        sort(s.begin(), s.end());
        string perm;
        backtrack(s, 0, n, perm);
        return rec;
    }
};

代码里 rec 记录着全部的不重复的排列,vis 记录着全部能回溯的位置。里面处理重复字符的手法就是先排序,sort(s.begin(), s.end()) 把重复字符放到一起,之后在回溯里面不要往重复字符出现的第二次第三次第 n 次上面回溯,处理代码是 if (vis[j] || (j > 0 && !vis[j - 1] && s[j - 1] == s[j])) { continue; },意思是现在这个位置 j 刚看到,还不能说它是能回溯的一个点;或者 j 是中间某个位置,它前一个位置不能回溯,然后它自己和前一个位置上字符还重复,那它自己这个位置也不能回溯。

重复的字符处理逻辑写完了,后面就是深度优先搜索的思想了,试一下,然后回溯。

看一下运行情况:

image.png

这道题如果用 C++ 还有一个非常简便的写法:


class Solution {
public:
    vector<string> permutation(string s) {
        sort(s.begin(), s.end());
        vector<string> ans;
        do ans.push_back(s);
        while (next_permutation(s.begin(), s.end()));
        return ans;
    }
};

image.png

2021.6.22 每日一题下面的题

1608. 特殊数组的特征值

这道题英文题目说明了它要干什么,输入一个数组,输出一个数 x,数组里有 x 个数大于 x。找不到就输出 -1。

找“前缀和”这道题,这里应该叫后缀和了,因为是倒着遍历可能的 x,代码如下


class Solution {
public:
    int specialArray(vector<int>& nums) {
        int n = nums.size();
        vector<int> cnt(n + 1);
        for (int num : nums)
            cnt[min(num, n)]++;
        for (int i = n; i >= 0; i--) {
            if (i < n)
                cnt[i] += cnt[i + 1];
            if (cnt[i] == i)
                return i;
        }
        return -1;
    }
};

image.png

小结

回溯法找全排列,同时要规定回溯条件,防止重复。

后缀和找特殊数组的特殊值。

参考链接