通配符匹配
字符串、动态规划
思路:
动态规划法:
1.定义数组dp[i][j]:s的前i个字符串匹配p的前j个字符串
2.数组之间的关系:假设p[j] = '*' 那么如果使用星号就等于dp[i - 1][j],如果不使用就等于dp[i][j - 1],假设为问号或者s和p匹配,那么就等于dp[i - 1][j - 1]
3.初始化数组:dp[0][0] = true,并且当p = '*'且s为空时,dp[0][i] = true
字符串匹配法:
设定两个标识位,分别标识当存在星号时,i 和 j 的位置。逻辑是假设为问号或者s和p匹配,那么就i++、j++,当是星号时,从匹配空遍历后面的s字符串,当发现不匹配时,就返回标识位继续遍历,直到最后。
代码:
动态规划法:
class Solution {
public:
bool isMatch(string s, string p) {
int n = s.size() , m = p.size();
vector<vector<bool>> dp(n + 1 , vector<bool>(m + 1));
dp[0][0] = true;
for(int i = 1; i <= m; i++) {
if(p[i - 1] == '*') dp[0][i] = true;
else break;
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
if(p[j - 1] == '*') {
dp[i][j] = dp[i][j - 1] | dp[i - 1][j];
} else if(p[j - 1] == '?' || p[j - 1] == s[i - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
}
}
return dp[n][m];
}
};
字符串匹配法:
class Solution {
public:
bool isMatch(string s, string p) {
int i = 0 , j = 0 , iStar = -1 , jStar = -1 , n = s.size() , m = p.size();
while(i < n) {
if(p[j] == '?' || s[i] == p[j]) {
i++; j++;
} else if(j < m && p[j] == '*') {
jStar = j++;
iStar = i;
} else if(iStar >= 0) {
i = ++iStar;
j = jStar + 1;
} else return false;
}
while(j < m && p[j] == '*') j++;
return j == m;
}
};
总结
- 动态规划数组中,i 和 j 分别表示的是s和p匹配的字符数量,所以在匹配字符串时记得要 -1
- 字符串法中,当匹配完成后,如果p的结尾存在多余的星号时记得“删除”
347. 前 K 个高频元素
解题思路:
堆
使用哈希表存储出现次数数组,优先队列来找到前k个出现频率最高的数。优先队列的排序方式以count值从堆顶到堆尾从小到大排序。
桶排序
这也是最先能想到的思路,使用哈希表存储出现次数数组,使用二维数组从小到大存储次数和数据。然后除前k个值即可。
快速排序法:
不需要每个都排序,随机选取一个数,以此为中间值,分为左边和右边,若k小于左边的数组数量,说明k在左子数组,再对左边数组进行分割。若k大于左边的数组数量,证明前k个数必须都包括左子数组,剩下的对右子数组在进行分割。
代码:
堆
class Solution {
public:
static bool cmp(pair<int, int>& m, pair<int, int>& n) {
return m.second > n.second;
}
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int , int> mii;
for(int i = 0; i < nums.size(); i++) {
mii[nums[i]]++;
}
priority_queue<pair<int , int> , vector<pair<int , int>> , decltype(&cmp)> q(cmp);
for(auto& [num , count] : mii) {
if(q.size() == k) {
if(q.top().second < count) {
q.pop();
q.emplace(num , count);
}
} else {
q.emplace(num , count);
}
}
vector<int> res;
while(!q.empty()) {
res.push_back(q.top().first);
q.pop();
}
return res;
}
};
桶排序
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
map<int , int> mii;
for(int i = 0; i < nums.size(); i++) {
mii[nums[i]]++;
}
vector<vector<int>> ivec(nums.size() + 1);
vector<int> res(k);
int count = 0;
for(auto it = mii.begin(); it != mii.end(); it++) {
ivec.at(it -> second).push_back(it -> first);
}
for(int i = ivec.size() - 1; i >= 0; i--) {
for(int j = 0; j < ivec[i].size(); j++) {
res.at(count) = ivec.at(i).at(j);
count++;
if(count == k) return res;
}
}
return res;
}
};
快速排序法:
class Solution {
public:
vector<int> res;
void qsort(vector<pair<int , int>>& v , int start , int end , int k) {
int picked = rand() % (end - start + 1) + start;
swap(v[start] , v[picked]);
int num = v[start].second;
int index = start;
for(int i = start + 1; i <= end; i++) {
if(v[i].second >= num) {
swap(v[i] , v[++index]);
}
}
swap(v[start] , v[index]);
if(k <= index - start) {
qsort(v , start , index - 1 , k);
} else {
for(int i = start; i <= index; i++) {
res.push_back(v[i].first);
}
if(k > index - start + 1) {
qsort(v , index + 1 , end , k - (index - start + 1));
}
}
}
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int , int> mii;
for(int i = 0; i < nums.size(); i++) {
mii[nums[i]]++;
}
vector<pair<int , int>> values;
for(auto& kv : mii) {
values.push_back(kv);
}
qsort(values , 0 , values.size() - 1 , k);
return res;
}
};
总结:
堆
- 如何使用自定义排序方法来使用优先队列的排序方式
- 如何使用优先队列
桶排序
- 如何巧妙的对次数进行排序。我们先创建一个nums.size() + 1的二维数组,然后根据次数作为索引进行数据的存储,这样的数组就是从小到大排序了
- 因为索引值从0开始,要根据出现次数进行排序,我们要创建nums.size() + 1的数组。不然会越界
快速排序法
- 为了避免最坏情况的出现,即每次取的中枢数组的元素都位于数组的两端,我们使用随机抽取中位数的方法
- 如何正确的进行自定义的快速排序。
- 以num为中位数,num为nums[picked]
- index为除开start以外,符合左右子数组规律的下标
- 最后进行start和index的交换,就可以完成快速排序
- 只有k > index - start + 1的时候才进行右子数组的快速排序
- end值是value.size() - 1,不是nums.size() - 1
扩展:
TOPK问题:
TOP-K问题是面试中的常见题型,具体表现为:
- 海量数据
- 求最大(最小)的K个值.
具体实现的方法主要有2种:
- 堆方法
- 快速排序法