手撕算法稳过技巧4-手撕算法的框架思维

30 阅读6分钟

应付面试的算法

道理是这么个道理,就是任何题目都有一个大致的框架,套路,多举一反三,熟练掌握框架即可,否则做再多的题,下次见到陌生的题还是不会做,或者做过的题做一阵子也会忘记。

欢迎大家看我写的题解 我的题解 - 力扣 (LeetCode)

C++跑的面试算法对运行时间要求很严格,因为语言效率比较高,因此上,要注意一些细节的优化

以本题为例,我这个代码能跑 265/268 个例子,但是这个例子就是过不了,直接执行内存溢出了

我总结了两个原因:

  1. 执行的逻辑太复杂了!这个一开始就要详细考虑, 否则大概率是调试不出来的。 思路的框架是一开始就要定下来的,应该遵循的逻辑是简单,健壮!

  2. 设计算法的时候,应该考虑大数据量,应该考虑性能。

通过本次经验,可以总结的一个亮点是:

  1. 变量的命名可以通过前缀相似来保持紧密的关系说明。

题目

[76] 最小覆盖子串

leetcode.cn/problems/mi…

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 个例子,但是这个例子就是过不了,直接执行内存溢出了。

我总结了两个原因:

  1. 执行的逻辑太复杂了!这个一开始就要详细考虑, 否则大概率是调试不出来的。
  2. 设计算法的时候,应该考虑大数据量,应该考虑性能。

通过本次经验,可以总结的一个亮点是:

  1. 变量的命名可以通过前缀相似来保持紧密的关系说明。