应付面试的算法
道理是这么个道理,就是任何题目都有一个大致的框架,套路,多举一反三,熟练掌握框架即可,否则做再多的题,下次见到陌生的题还是不会做,或者做过的题做一阵子也会忘记。
欢迎大家看我写的题解 我的题解 - 力扣 (LeetCode)
C++跑的面试算法对运行时间要求很严格,因为语言效率比较高,因此上,要注意一些细节的优化
以本题为例,我这个代码能跑 265/268 个例子,但是这个例子就是过不了,直接执行内存溢出了
我总结了两个原因:
-
执行的逻辑太复杂了!这个一开始就要详细考虑, 否则大概率是调试不出来的。 思路的框架是一开始就要定下来的,应该遵循的逻辑是简单,健壮!
-
设计算法的时候,应该考虑大数据量,应该考虑性能。
通过本次经验,可以总结的一个亮点是:
- 变量的命名可以通过前缀相似来保持紧密的关系说明。
题目
[76] 最小覆盖子串
algorithms
Hard (48.37%)
Likes: 3420
Dislikes: 0
Total Accepted: 918.2K
Total Submissions: 1.9M
Testcase Example: '"ADOBECODEBANC"\n"ABC"'
给你一个字符串 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 是最小覆盖子串。
s=t done
示例 3:
输入: s = "a", t = "aa" s < t || count(s)< count(t) false 输出: "" 解释: t 中两个字符 'a' 均应包含在 s 的子串中, 因此没有符合条件的子字符串,返回空字符串。
提示:
m == s.length
n == t.length
1 <= m, n <= 10^5
s 和 t 由英文字母组成
进阶:你能设计一个在 o(m+n) 时间内解决此问题的算法吗?
手动推演一下:
l=0,r=1 A
l=0,r=2 AD
l=0,r=3 ADO
...
l=0,r=6 ADOBEC done
l=1,r=6 DOBEC false
l=1,r=7 DOBECO false
...
l=1,r=11,DOBECODEBA done
CODEBA
...
ODEBANC done
...
BANC done
思路介绍
- 核心的思路是 双指针+滑动窗口,双指针是滑动窗口的基础,所以写到一起也能理解
- 本题的思路是最小覆盖,因此滑动窗口的数据接口就是一个 hash map, 这个也可以理解
- 看起来很朴素的算法,实际上一个大的框架是,外层的循环滑动右边的指针,内部的循环滑动左边的指针
- 滑动窗口的法则之一是,左边的指针一定要不能超过右边的指针
最终的版本
268/268 cases passed (27 ms)
Your runtime beats 38.97 % of cpp submissions
Your memory usage beats 61.74 % of cpp submissions (11.3 MB)
string minWindow(string s, string t) {
if(s == t ) return s;
if(s.size() < t.size()) return "";
unordered_map<char, int> t_counter, s_counter;
for(auto c: t) t_counter[c] += 1;
// for(auto c: s) s_counter[c] += 1;
// if(t_counter.size() > s_counter.size()) return "";
//print_helper(t_counter, "Init t_couter ");
int win_l = 0, win_r = 0;
unordered_map<char, int> win;
int win_match = 0;
// win[s[0]] = 1;
// if(t_counter.contains(s[0]) && t_counter[s[0]] == 1) win_match += 1;
string ret = "";
int ret_size = s.size() + 1; // 初始化的设计
int ret_l = 0;
int ret_r = 0;
int RAW_REF = ret_size;
while(win_r < s.size()){
//cout << ">>> Round start l " << win_l << ", r " << win_r << endl;
//cout << "Match Less, "<< "Slide right, r is " << win_r+1 << endl;
//cout << "Slide right, r is " << win_r+1 << endl;
if(t_counter.contains(s[win_r])) win[s[win_r]] += 1;
if(t_counter.contains(s[win_r]) && t_counter[s[win_r]] == win[s[win_r]]){
//cout << "Match plus, match is " << win_match+1 << " char is " << s[win_r] << endl;
win_match+=1;
}
win_r += 1;
while(win_match == t_counter.size()){
int tmp_size = win_r - win_l;
if(tmp_size < ret_size){
//print_helper(win, "Compare success, win contains Win is ");
//string tmp = s.substr(win_l, win_r-win_l);
//cout << "Find Smaller, replace ret with tmp " << tmp << endl;
//cout << "Find Smaller, l " << win_l << ", r " << win_r << endl;
//ret = tmp;
ret_l = win_l;
ret_r = win_r;
ret_size = tmp_size;
}else{
//cout << "Find equal or bigger, Igore!! " << endl;
}
//cout << "Shrink Now from win l " << win_l << " value is " << s[win_l] << endl;
if(t_counter.find(s[win_l]) != t_counter.end()){
win[s[win_l]] -= 1;
if(win[s[win_l]] < t_counter[s[win_l]]){
//cout << "Key letter leave " << win_l << " value is " << s[win_l] << endl;
//print_helper(win, "Key letter leave, ");
win_match -= 1;
//cout << "Win match is " << win_match << endl;
}
}
win_l +=1;
}
}
if(ret_size == RAW_REF){
return "";
}
return s.substr(ret_l, ret_r - ret_l);
}
应对大数据量无力的版本
class Solution {
public:
string minWindow(string s, string t) {
if(s == t ) return s;
if(s.size() < t.size()) return "";
unordered_map<char, int> t_counter, s_counter;
for(auto c: t) t_counter[c] += 1;
//for(auto c: s) s_counter[c] += 1;
//if(t_counter.size() > s_counter.size()) return "";
//print_helper(t_counter, "Init t_couter ");
int win_l = 0, win_r = 1;
unordered_map<char, int> win;
win[s[0]] = 1;
string ret = "";
int ret_size = s.size() + 1; // 初始化的设计
int RAW_REF = ret_size;
while(win_l < win_r){
//cout << "Round start l " << win_l << ", r " << win_r << endl;
if(!compare_win_contains(win, t_counter)){
//cout << "Compare failed, win not contains" << endl;
//cout << "Compare failed Win Str is " << s.substr(win_l, win_r-win_l) << endl;
if(win_r < s.size()){
//cout << "Slide right, r is " << win_r+1 << endl;
win[s[win_r]] += 1;
win_r += 1;
}else{
//cout << "Shrink Now from win l " << win_l << ": " << s[win_l] << endl;
win[s[win_l]] -= 1;
win_l +=1;
}
}else{
//cout << "Compare success, win contains" << endl;
//print_helper(win, "Compare success, win contains Win is ");
int tmp_size = win_r - win_l;
//string tmp = s.substr(win_l, win_r-win_l);
//cout << "Got tmp str " << tmp << endl;
if(tmp_size < ret_size){
//cout << "Find Smaller, replace ret with tmp" << endl;
ret = s.substr(win_l, win_r-win_l);
ret_size = tmp_size;
}else{
//cout << "Find equal or bigger, Igore!! ";
}
//cout << "Shrink Now from win l " << win_l << ": " << s[win_l] << endl;
win[s[win_l]] -= 1;
win_l +=1;
}
//print_helper(win, "Round End, window is ");
//cout << "-------" << endl;
}
if(ret_size == RAW_REF){
return "";
}
return ret;
}
bool compare_win_contains(unordered_map<char, int>& map1, unordered_map<char, int>& map2){
if(map1.size() < map2.size()) return false;
for(auto &pir: map2){ // map2是参考
if(map1.find(pir.first) == map1.end()) return false;
if(map2[pir.first] > map1[pir.first]) return false;
}
return true;
}
void print_helper(unordered_map<char, int>& map, string tip=""){
cout << tip << " map " << endl;
for(auto &pir: map){
cout << pir.first << ", " << pir.second << endl;
}
cout << endl;
}
};
应对大数据量,leetcode给了一个测试用例。
s="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa..(非常多的a).aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ"
t="aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ"
果断超时了。
C++对运行时间要求很严格,因为语言效率比较高,因此上,要注意一些细节的优化。
我这个代码能跑 265/268 个例子,但是这个例子就是过不了,直接执行内存溢出了。
我总结了两个原因:
- 执行的逻辑太复杂了!这个一开始就要详细考虑, 否则大概率是调试不出来的。
- 设计算法的时候,应该考虑大数据量,应该考虑性能。
通过本次经验,可以总结的一个亮点是:
- 变量的命名可以通过前缀相似来保持紧密的关系说明。