leetcode刷题记录04(2023-04-09)【颜色分类(快排partition思想) | 最小覆盖子串(哈希表+滑动窗口+needCnt很重要) | 子集(深搜) | N字形变换(找对周期)】

75. 颜色分类

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

必须在不使用库内置的 sort 函数的情况下解决这个问题。

示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

示例 2:
输入:nums = [2,0,1]
输出:[0,1,2]

提示:
n == nums.length
1 <= n <= 300
nums[i] 为 0、1 或 2

进阶:
你能想出一个仅使用常数空间的一趟扫描算法吗?

主要思路就是,把0从左往右排,把2从右往左排,这样中间剩下的将都是1了,进而实现了分类。

官方的题解也是利用了这个思路,其实也是快排中的 partition 思想。一次排序,把数组分为 2 个部分。

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int left = 0;
        int right = nums.size() - 1;
        int i = 0;
        while (i <= right) {
            if (nums[i] == 0 && i != left) {
                swap(nums[i], nums[left]);
                left++;
            }
            else if (nums[i] == 2 && i != right) {
                swap(nums[i], nums[right]);
                right--;
            }
            else {
                i++;
            }
        }
    }
};

int main() {
    Solution sol;
    vector<int> vec = { 1,2,0 };
    sol.sortColors(vec);
    for (int i = 0; i < vec.size(); i++) {
        cout << vec[i] << " ";
    }
}

76. 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。

注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
解释:最小覆盖子串 “BANC” 包含来自字符串 t 的 ‘A’、‘B’ 和 ‘C’。

示例 2:
输入:s = “a”, t = “a”
输出:“a”
解释:整个字符串 s 是最小覆盖子串。

示例 3:
输入: s = “a”, t = “aa”
输出: “”
解释: t 中两个字符 ‘a’ 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。

提示:
m = = s . l e n g t h m == s.length m==s.length
n = = t . l e n g t h n == t.length n==t.length
1 < = m , n < = 1 0 5 1 <= m, n <= 10^5 1<=m,n<=105
s 和 t 由英文字母组成

进阶:你能设计一个在 o(m+n) 时间内解决此问题的算法吗?

这道题目有几个点需要注意:

首先,根据提示如下:

可以知道是利用哈希表+滑动窗口来做。

这样可以解,但无法在O(m+n)的时间复杂度内完成,因为我们要判断哈希表中的所有元素是否都为0,如果都为0了,那么证明找到了一个可行解,而且需要判断当前字符是否在给出的子串t中,一旦判断这个,复杂度就上去了,查找至少也是 l o g n logn logn 的复杂度,总的复杂度就会变成 m ∗ l o g n m*logn mlogn

优化方法是,采用了一个 needCnt,这个是本题的精华

还需要注意的是, n e e d [ c ] < 0 need[c]<0 need[c]<0 时都是我们不需要的字母(或者需要,但是当前串中有多余的),只有 n e e d [ c ] > 0 need[c]>0 need[c]>0 才是我们需要的,当 n e e d [ c ] = = 0 need[c]==0 need[c]==0 说明我们找到了一个可行串。

#include<vector>
#include<iostream>
#include<map>
using namespace std;

class Solution {
public:
    string minWindow(string s, string t) {
        map<char, int> need; // 还需要的各个字母的数量
        int needCnt = t.size(); // 还需要的字母总数量
        int minLen = 0x3f3f3f3f;
        int start = 0;
        for (int i = 0; i < t.size(); i++) {
            need[t[i]]++;
        }
        int left = 0;
        for (int i = 0; i < s.size(); i++) {
            if (need[s[i]] > 0) { // 如果是需要的字母
                needCnt--;
            }
            need[s[i]]--;         // need中的负数表示这个字母是我们不需要的
            if (needCnt == 0) {   // 滑动窗口包含了所有元素
                while (true)
                {
                    if (need[s[left]] == 0) {// 说明遇到了一个有用到的字符
                        break;
                    }
                    need[s[left]]++; // 没有用的字符,need++,0以下证明这个字符是无用的,这样保证当为need[s[i]]==0的时候是正好需要的
                    left++;          // 移动左边窗口
                }
                if (minLen > i - left + 1) {
                    minLen = i - left + 1;
                    start = left;
                }
                need[s[left]]++; // 更新哈希表为缩小窗口后的字母需要数量
                needCnt++;       // 更新需要的字母总数量
                left++;          // 缩小窗口    
            }
        }
        if (minLen == 0x3f3f3f3f) {
            return "";
        }
        return s.substr(start, minLen);
    }
};

int main() {
    Solution sol;
    cout << sol.minWindow("a", "aa");
}

78. 子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:
输入:nums = [0]
输出:[[],[0]]

提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
nums 中的所有元素 互不相同

这道题属于集合问题,没有想到。

思路是,对于数组中的每一个元素,它都有可能加入,有可能不加入。所以采用递归法很好解决,有点像回溯。所以最终的子集数量一定为 2 n 2^n 2n 个(包括空集),其中, n n n 为集合中元素个数。

#include<vector>
#include<iostream>
using namespace std;

class Solution {
public:
    void dfs(int cur, vector<int>& nums, vector<int>& t, vector<vector<int>>& ans) {
        if (cur == nums.size()) {
            ans.push_back(t);
            return;
        }
        t.push_back(nums[cur]); // 加入当前这一个
        dfs(cur + 1, nums, t, ans);
        t.pop_back();           // 不加入当前这个
        dfs(cur + 1, nums, t, ans);
    }

    vector<vector<int>> subsets(vector<int>& nums) {
        vector<int> t;
        vector<vector<int>> ans;
        dfs(0, nums, t, ans);
        return ans;
    }
};

int main() {
    Solution sol;
    vector<int> vec = { 1,2,3 };
    vector<vector<int>> res = sol.subsets(vec);
    for (int i = 0; i < res.size(); i++) {
        for (int j = 0; j < res[i].size(); j++) {
            cout << res[i][j] << " ";
        }
        cout << endl;
    }
}

另一种方法也很巧妙,如下:

首先,我们确定答案有 2 n 2^n 2n 个,那么我们就生成这些个mask即可,他们一定有n个二进制位。

0表示不包含当前位置的元素,1表示包含当前位置的元素,组合就罗列出来了。

class Solution {
public:
    vector<int> t;
    vector<vector<int>> ans;

    vector<vector<int>> subsets(vector<int>& nums) {
        int n = nums.size();
        for (int mask = 0; mask < (1 << n); ++mask) {
            t.clear();
            for (int i = 0; i < n; ++i) {
                if (mask & (1 << i)) {
                    t.push_back(nums[i]);
                }
            }
            ans.push_back(t);
        }
        return ans;
    }
};

6. N 字形变换

将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:

P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“PAHNAPLSIIGYIR”。

请你实现这个将字符串进行指定行数变换的函数:

string convert(string s, int numRows);

示例 1:

输入:s = “PAYPALISHIRING”, numRows = 3
输出:“PAHNAPLSIIGYIR”
示例 2:
输入:s = “PAYPALISHIRING”, numRows = 4
输出:“PINALSIGYAHRPI”
解释:
P I N
A L S I G
Y A H R
P I
示例 3:

输入:s = “A”, numRows = 1
输出:“A”

提示:

1 < = s . l e n g t h < = 1000 1 <= s.length <= 1000 1<=s.length<=1000
s 由英文字母(小写和大写) 、 ′ , ′ 和 ′ . ′ 组成 s 由英文字母(小写和大写)、',' 和 '.' 组成 s由英文字母(小写和大写),.组成
1 < = n u m R o w s < = 1000 1 <= numRows <= 1000 1<=numRows<=1000

这道题目也很绕,主要是要找到周期 2 r − 2 2r-2 2r2 个字母一个循环,不是很好想出来,但是空间复杂度是O(1),代码如下:

class Solution {
public:
    string convert(string s, int numRows) {
        int n = s.size(), r = numRows;
        if (r == 1 || r >= n) {
            return s;
        }
        string res = "";
        int t = 2 * r - 2;                    // 一个周期的大小
        for (int i = 0; i < r; i++) {         // 行数
            for (int j = 0; j + i < n; j += t) {
                res += s[j + i];              // 一个周期中左边的字母
                if (0 < i && i < r - 1 && j + t - i < n) {
                    res += s[j + t - i];      // 这个周期中的第二个字母
                }
            }
        }
        return res;
    }
};

一种比较好想的做法是用二维数组去模拟一下,但是空间复杂度是 O(n) 级别,如下:

class Solution {
public:
    string convert(string s, int numRows) {
        int r = numRows;
        if (r == 1 || r >= s.size())return s;
        int flag = -1;
        vector<string> vec(r, "");
        int curRow = 0;
        for (int i = 0; i < s.size(); i++) {
            if (curRow % r == 0||curRow % r==r-1) {
                flag = -flag;
            }
            vec[curRow] += s[i];
            curRow += flag;
        }
        string res = "";
        for (int i = 0; i < vec.size(); i++) {
            res += vec[i];
        }
        return res;
    }
};
  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cherries Man

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值