楼主: 大木虫
跳转到指定楼层
上一主题 下一主题
收起左侧

Elements of Programming Interviews 白班编程记录,求挑刺求反馈

 
🔗
 楼主| 大木虫 2018-10-7 05:13:17 | 只看该作者
全局:
LC 78 Subsets

Problem Metrics:
1. Understand problem at 1 min
2. Get core concept at 1 min
3. Algorithm draft at 2 min
4. Detailed example derivation at 7 min
5. Code draft at 18 min
6. AC solution at 20 min (one time AC, no compile/runtime error)

Remember, if you use a state vector (or whatever state tracker) to keep track of your state, make sure to do housekeeping (undo all your changes here)

标准backtracking,对于每一个branch的结束,要记得撤销这个branch对state vector所作出的更改,一遍下一个branch 有一个fresh start

还有另一种方法,既使用2^n的binary code来得出subsets

补充内容 (2018-10-7 05:13):
Class 1
回复

使用道具 举报

🔗
 楼主| 大木虫 2018-10-7 10:20:28 | 只看该作者
全局:
LC 126. Word Ladder II (class 3)

Problem Metrics:
1. Understand problem at 2 min
2. Get core concept at 12 min
3. Algorithm draft at 16 min
4. Detailed example derivation (part 1) at 25 min
5. Code draft (part 1) at 30 min
6. Detailed example derivation (part 2) at 46 min
7. Code draft (part 2) at 57 min
8. Detailed example derivation (part 3) at 63 min
9. Code draft (part 3) at 71 min
10. Code compile at 77 min
11. Debug part 1 done at 90 min
12. Debug part 2 done at 118 min
13. No bug in part 3
14. AC solution at 119 min (YEAH finally!!!!!!!!!!!!!!!)

This is so damn fun:
graph construction + level order traversal + Tree edge graph discovery + backtracking

这题写得太酸爽了,能在120分钟内从读题到AC,我已经挺满意了。(第一次写,没看答案)
这是一道综合题目,运用到的知识点有以下几个:
1. Graph Construction
    这是一道图题,那么首要第一步就是建图,根据题目要求把word graph建出来,每个word算作一个vertex,相差一个字母的word之间连接
2. BFS (变体level order traversal)
    既然要返回所有最短路径,那么BFS是最好的选择,使用BFS变体level order traversal的原因是因为要记录路程长度,一旦发现最终目标,就没必要往下一个level走了。Level order traversal 同时还可用于存path用于事后的return
3. Tree edge graph discovery
    这个概念的意思是把一个图走BFS,然后把在过程中发现新unvisited node的edge记录下来,整体将会形成一个tree(就像是从原图中抓出来的骨架一样),这里不完全是一个tree,因为记录的edge是指向存于新level中的node,主要目的是用于记录到达该level这个node的路径
4. Backtracking
    这里则运用上面建的骨架图来枚举所有路径,标准的backtracking

写法是一步一步的按照知识点写,一步一步的测试。知识点1和4是比较简单的,本题的难点核心在于知识点2和3(part 2),当然,我主要犯的错误是在第一步建图的过程中疏漏了一些corner case,导致接下来的步骤执行出错,所以一步步test是很重要的。

C++ 代码如下(还没有重构,如果上白板,则需要精简):
class Solution {
public:
    vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
        /* first, build a graph using the word list */
        /* Level f***ing order traversal !!!!!!!!!!!! */
        /* Use a hash table to memorize previous words */
        /* Like a permutation */
        /* Do things in layer (level) */
        /* I'm only going to push NEW nodes into NEXT layer (level) */        
        /* key: in the BFS constructed path-map, we only add tree edges */
        
        /* Algorithm Description :
            1. Use BFS to find the path level
            2. Use backtracking to enumerate all the paths */
        
        /* Overall, I will build two graphs here:
            1. The orignal graph built from the wordlist
            2. The BFS tree discovery graph extracted from the original graph */
        
        /* Step 0: pre-check */
        vector<vector<string> > emptyAnswer;
        bool endWordExist = false;
        for(auto word : wordList){
            if(word == endWord){
                endWordExist = true;
                break;
            }
        }
        if(!endWordExist){
            return emptyAnswer;
        }
              
        // cout << "start!" << endl;
        /* Step 1: build original graph: */
        unordered_map<string, vector<string> > graph;
        for(auto word1 : wordList){
            for(auto word2 : wordList){
                if(numDiff(word1, word2) == 1){
                    if(graph.find(word1) == graph.end()){
                        graph[word1] = {};
                    }                  
                    graph[word1].emplace_back(word2);
                }
            }
        }
        
        bool beginWordExist = false;
        for(auto word : wordList){
            if(word == beginWord){
                beginWordExist = true;
                break;
            }
        }
        if(!beginWordExist){
            for(auto word : wordList){
                if(numDiff(beginWord, word) == 1){
                    if(graph.find(beginWord) == graph.end()){
                        graph[beginWord] = {};
                    }
                    if(graph.find(word) == graph.end()){
                        graph[word] = {};
                    }
                    graph[beginWord].emplace_back(word);
                    graph[word].emplace_back(beginWord);
                }
            }         
        }

        
        // cout << "step 1 done!" << endl;
        
        /* Step 2: Use BFS (level order traversal (or we can call this ripple traversal) ) to build the tree discovery graph (path graph)
                    At the same time find the target word */
        /* level map */
        /* process layer (add into path graph) */
        /* generate next layer */
        unordered_map<string, vector<string> > pathGraph; /* our general big graph */
        unordered_map<string, vector<string> > levelPathStore;
        unordered_set<string> visited;
        queue<string> currentLevel, nextLevel;
        bool findTarget = false;
        
        /* Initialization */
        currentLevel.emplace(beginWord);
        pathGraph[beginWord] = {};
        visited.emplace(beginWord);
        
        while(!currentLevel.empty() && findTarget == false){
            
            while(!currentLevel.empty()){
                string word = currentLevel.front();
                currentLevel.pop();
                // cout << word << " " << graph[word].size() << endl;
                for(string child : graph[word]){
                    // cout << word << " --> " << child << endl;
                    if(visited.find(child) == visited.end()){
                        visited.emplace(child);
                        levelPathStore[child] = {};
                        nextLevel.emplace(child);
                    }
                    if(levelPathStore.find(child) != levelPathStore.end()){
                        levelPathStore[child].emplace_back(word);
                        // cout << "child: " << child << " parent: " << word << endl;
                    }
                    if(child == endWord){
                        findTarget = true;
                    }
                }               
            }
            currentLevel = nextLevel;
            nextLevel = {};
            for(auto itr = levelPathStore.begin(); itr != levelPathStore.end(); ++itr){
                pathGraph[itr->first] = itr->second;
            }
            levelPathStore.clear();
        }
        // cout << "findTarget: " << findTarget << endl;
        // cout << "step 2 done!" << endl;
        
        if(!findTarget){
            return emptyAnswer;
        }
        
        /* Step 3 backtracking the pathGraph to enumerate all possible paths */
        /* There shouldn't be back fanout */
        vector<vector<string> > answer;
        vector<string> path;
        enumeratePaths(pathGraph, &answer, &path, endWord, beginWord);
        
        // cout << "step 3 done!" << endl;
        
        return answer;        
    }
   
   
    void enumeratePaths(const unordered_map<string, vector<string> > &pathGraph,
                        vector<vector<string> > * answer,
                        vector<string> * path,
                        const string & currentWord,
                        const string & beginWord){
        path->emplace_back(currentWord);
        if(currentWord == beginWord){
            answer->emplace_back((*path).rbegin(), (*path).rend());
        }else{
            for(string parent : pathGraph.find(currentWord)->second){
                // cout << "parent: " << parent << endl;
                enumeratePaths(pathGraph, answer, path, parent, beginWord);
            }   
        }        
        path->pop_back();
    }
   
    int numDiff(const string & s1, const string & s2){
        int count = 0;
        for(int i = 0; i < s1.size(); ++i){
            if(s1[i] != s2[i])++count;
        }
        return count;
    }
};











补充内容 (2018-10-7 10:25):
以后再做优化。。。
回复

使用道具 举报

🔗
 楼主| 大木虫 2018-10-7 21:52:52 | 只看该作者
全局:
LC 39 Combination Sum

Problem Metrics: (class 2)
1. Understand problem at 2 min
2. Get core concept (backtracking + pruning) at 9 min
3. Algorithm draft at 12 min
4. Detailed example derivation at 19 min
5. Code draft at 27 min
6. Code compiles at 32 min
7. AC solution at 35 min

Take away:
This is a variation of coin exchange problem

The catch here is that I initially started at a fixed point. I should instead start at ALL possible starting points. This problem is caused by premature assignment.

Take away:
When backtracking, doing branching and housekeeping within the loop is a safer alternative. The only thing to do outside of the branching loop is base/ending case checking.

backtracking题目,这题让我修正了backtracking的模板,模板总结如下:

void Bracktracking(parentBranch){
    Basecase checking/return
    Endcase checking/return

    for(branches){
        parentBranch --update-->childBranch (according to problem's branching logic)
        Backtracking(childBranch)
        childBranch --undo---> parentBranch (get back to where you were, giving next child a fresh start)
    }
}
回复

使用道具 举报

🔗
 楼主| 大木虫 2018-10-7 23:24:15 | 只看该作者
全局:
LC 212 Word Search II (class 3)

Problem Metrics:
1. Understand problem at 2 min
2. Get core concept at 3 min
3. Algorithm draft at 13 min
4. Detailed example derivation at 21 min
5. Code draft at 43 min
6. Code compile at 52 min
7. pass 33/37 test cases, TLE (code should be correct, just need more pruning) at 57 min
8. AC solution at 62 min (added a prefix table to simulate a trie)

Compile error:
ISO C++ forbids declaration of 'parameter' with no type [-fpermissive]
Reason: brackets "<>" don't match
When there are a lot of parameters in a function, make sure to handle everyone of them in the function call

Some backtracking is two-leveled, like this one, where the levels are handled differently. For this one, the first level is to iterate over all board starting locations, and this is done in the main function. The second level is the four-direction branching search, which is done in the recursive call. The key here is to know exactly where to modify the parent branch (in the branching loop), and where to check for results (in the base case/ending case checks before the branching loop)

Another take away:
Write down the thoughts as documentation as you think; lay out code structure in comments before actual coding.

Another take away 2:
A prefix table (eg. a hash table storing all possible prefixes for all valid words in a given dictionary) can be a quick hack if you want to use a trie but don't have the luxury to build one.

标准backtracking题目,这题作为hard的立足点在我看来是其代码的复杂性以及稍微对pruning有了一些要求。

C++代码如下:
class Solution {
public:
    vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
        /* first, get all the words in a map for faster look up */
        /* you need a state vector to keep track of visited cells;
           the state vector will be modified along the way */
        /* Use backtracking template */
        /* Use a hashtable to handle duplicate valid words */
        /* A trie will be much more helpful */
        /* Naive ending case: search depth exceeds maximum word length */
        /* Advanced ending case: current prefix doesn't exist in trie */
        /* Note: finding a valid word is NOT an ending case here because this valid word
           could be the prefix of a longer valid word, so you need to keep searching */
        /* Each backtracking layer: branch and try next */
        /* The difference here is that this is a two layer backtrack so you need to apply changes beforehand */
        
        
        /* Step 1 preparation */
        
        /* dictionary */
        unordered_set<string> dictionary;
        
        /* max word length used for ending case */
        int maxLength = 0 ;
        
        for(string word : words){
            dictionary.emplace(word);
            maxLength = max(maxLength, (int)word.size());
        }
        
        /* visited tracker */
        vector<vector<bool> > visited(board.size(), vector<bool>(board[0].size(),false));
        
        /* answer store */
        unordered_set<string> answer;
        
        /* handle the case of empty word */
        if(dictionary.find("") != dictionary.end()){
            answer.emplace("");
        }
        
        /* build a prefix table (to simulate a trie)*/
        unordered_set<string> prefixes;
        for(string word : words){
            string prefix = "";
            for(char c : word){
                prefix += c;
                prefixes.emplace(prefix);
            }
        }
        
        string trackerStr = "";
        
        /* Step 2 backtracking (this is the key step)*/
        for(int i = 0; i < board.size(); ++i){
            for(int j = 0; j < board[0].size(); ++j){
                /* Apply changes */
                visited[i][j] = true;
                trackerStr += board[i][j];
               
                /* Branch */
                findWordsRecursion(board, dictionary, prefixes, &visited, maxLength, i, j, &trackerStr, &answer);
               
                /* Undo changes */
                trackerStr.pop_back();
                visited[i][j] = false;
            }
        }   
        
        /* Step 3 generate and return answer */        
        vector<string> answerVector;
        for(auto itr = answer.begin(); itr != answer.end(); ++itr){
            answerVector.emplace_back(*itr);
        }
        return answerVector;
    }
   
    void findWordsRecursion(const vector<vector<char> > & board,
                            const unordered_set<string> & dictionary,
                            const unordered_set<string> & prefixes,
                            vector<vector<bool> > * visited,
                            int maxLength, int i, int j,
                            string * trackerStr,
                            unordered_set<string> * answer){
        /* Ending case handling (do return) */
        if(trackerStr->size() > maxLength)return;
        if(prefixes.find(*trackerStr) == prefixes.end())return;
        
        // cout << trackerStr->size() << " " << *trackerStr << endl;
        
        /* Base case handling (don't return ) */
        if(dictionary.find(*trackerStr) != dictionary.end()){
            answer->emplace(*trackerStr);
        }
        
        /* branching, four directions */
        if(i > 0){
            if((*visited)[i-1][j] == false){
                (*visited)[i-1][j] = true;
                (*trackerStr) += board[i-1][j];
                findWordsRecursion(board, dictionary, prefixes, visited, maxLength, i - 1, j, trackerStr, answer);  
                (*visited)[i-1][j] = false;
                trackerStr->pop_back();
            }
        }
            
        if(i < board.size()-1){
            if((*visited)[i+1][j] == false){
                (*visited)[i+1][j] = true;
                (*trackerStr) += board[i+1][j];
                findWordsRecursion(board, dictionary, prefixes, visited, maxLength, i + 1, j, trackerStr, answer);  
                (*visited)[i+1][j] = false;
                trackerStr->pop_back();
            }
        }
        
        if(j > 0){
            if((*visited)[i][j-1] == false){
                (*visited)[i][j-1] = true;
                (*trackerStr) += board[i][j-1];
                findWordsRecursion(board, dictionary, prefixes, visited, maxLength, i, j - 1, trackerStr, answer);  
                (*visited)[i][j-1] = false;
                trackerStr->pop_back();
            }
        }
            
        if(j < board[0].size()-1){
            if((*visited)[i][j+1] == false){
                (*visited)[i][j+1] = true;
                (*trackerStr) += board[i][j+1];
                findWordsRecursion(board, dictionary, prefixes, visited, maxLength, i, j + 1, trackerStr, answer);  
                (*visited)[i][j+1] = false;
                trackerStr->pop_back();
            }
        }        
    }        
};
回复

使用道具 举报

🔗
 楼主| 大木虫 2018-10-8 01:51:04 | 只看该作者
全局:
LC 364 Nested List Weight Sum II (class 2)

Problem Metrics:
1. Understand problem at 12 min
2. Get core concept at 12 min
3. Algorithm draft at 14 min
4. Detailed example derivation at 19 min
5. Code draft at 29 min
6. Code compile at 30 min
7. AC solution at 31 min (I forgot to pop, causing memory exceeded)

Remember to pop nodes.
Do this next time: pop the node RIGHT AFTER you assign it to a temp node.

标准BFS(level order traversal),使用level order traversal 模板解决。此题难点在于理解题意并且正确地抽象其图模型,以便实施level order traversal的processing逻辑
回复

使用道具 举报

🔗
 楼主| 大木虫 2018-10-8 03:50:59 | 只看该作者
全局:
LC 140 Word Break II (class 3)

Problem Metrics:
1. Understand problem at 3 min
2. Get core concept at 5 min
3. Algorithm draft at 11 min
4. Detailed example derivation at 15 min
5. Code draft at 35 min
6. 31/39 pass at 37 min
7. Use word break I as a helper!!!!!!!!! at 63 min
8. AC solution at 87 min

This is a combination of DP and backtracking
First of all, we need to return all possible results, so there is no way around backtracking (DP won't get you any faster)
However, for unsolvable cases (extremely long and tons of branches), we can to a pre-check to determine whether it's worth it to continue. And in this case we can utilize DP to do a quick check. (use Word Break I)

Again, this is fun.

这题太TM有意思了。backtracking解在前40分钟已经AC,不过有TLE的case。稍微修改了了test case之后发现正确答案也TLE,那么问题看来不在我的算法速度,而在于一些trick。想了一会,发现其中一个trick是pre-check,既确定这个input是否值得使用backtracking进行搜索。这一步骤可以借用Word Break I 来完成,是一个典型的二维DP算法。
我基本上前40分钟完成了backtracking,花了25分钟琢磨出来要使用Word Break II,然后花了20分钟写出了Word Break I,代码AC。感觉十分爽。

总结,这道题是两个知识点的结合运用,也是我目前遇到的hard题的普遍模式。对于这类题目,主要训练是把各个考点熟练掌握,融汇贯通。不要拘泥于考点自身,更要思考这个考点能帮助我解决什么问题,就像使用工具拼装模型一样。
回复

使用道具 举报

🔗
 楼主| 大木虫 2018-10-8 05:44:17 | 只看该作者
全局:
LC 211 Add and Search Word - Data structure design
Problem Metrics: (class 3)
1. Understand Problem at 2 min
2. Get core concpet at 7 min
3. Algorithm draft at 14 min
4. Detailed example derivation at 30 min
5. Code draft at 50 min
6. AC solution at 70 min

Damn I got it right..

This is building a trie from scratch

感觉这题考验的是syntax (建trie需要使用linked map, C++实现起来十分蛋疼)
写这题的时候已经很累了(之前7小时搞了两个hard两个medium,都是新题)
syntax基本上是飘着写的,完全不确定对不对。好在没有出太多编译错误,bug也比较好解决

这题需要回顾,因为它非常典型

C++ 代码:(我的版本有memory leak,不过实在是写不动了。。。)

struct Trie{
    /* Note: has memory leak, remember to clean in destructor */
    unordered_map<char, Trie*> charList;
   
    void AddWord(string word, int index, unordered_map<char, Trie*> * charList){
        if(index == word.size()){
            cout << "added word: " << word << endl;
            (*charList)['*'] = NULL;
            return;
        }else{
            if(charList->find(word[index]) == charList->end()){
                (*charList)[word[index]] = new Trie();
            }
            AddWord(word, index + 1, &((*charList)[word[index]]->charList) );
        }
    }
        
    bool Search(const string & word, int index, unordered_map<char, Trie*> * charList){
        if(charList == NULL)return false;
        if(index == word.size()){
            if(charList->find('*') != charList->end()){
                return true;
            }else{
                return false;
            }
        }
            
        if(word[index] != '.'){
            if(charList->find(word[index]) != charList->end()){
                return Search(word, index + 1, &((*charList)[word[index]]->charList) );
            }else{
                return false;
            }  
        }else{
            bool found = false;
            for(auto itr = charList->begin(); itr != charList->end(); ++itr){
                if(found)break;
                found = Search(word, index + 1, &(itr->second->charList) );
            }
            return found;
        }
    }   
   
};

class WordDictionary {
public:
    /** Initialize your data structure here. */
    WordDictionary() {
        trie = Trie();
    }
   
    /** Adds a word into the data structure. */
    void addWord(string word) {
        trie.AddWord(word, 0, &(trie.charList));
    }
   
    /** Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter. */
    bool search(string word) {
        return trie.Search(word, 0, &(trie.charList));
    }
   
    struct Trie trie;   
};




/**
* Your WordDictionary object will be instantiated and called as such:
* WordDictionary obj = new WordDictionary();
* obj.addWord(word);
* bool param_2 = obj.search(word);
*/
回复

使用道具 举报

🔗
芥末黄 2018-10-8 11:55:42 | 只看该作者
本楼:
全局:
看不到啊?
回复

使用道具 举报

🔗
 楼主| 大木虫 2018-10-8 22:05:16 | 只看该作者
全局:
LC 94 Binary Tree Inorder Traversal (class 2)

Problem Metrics:
1. Understand Problem at 2 min
2. Get core concept at 5 min
3. Algorithm draft at 12 min
4. Detailed example derivation at 18 min
5. Code draft at 40 min
6. Code compile at 43 min
7. AC solution at 44 min

In order traversal is classic. You should do this problem multiple times

经典题目,迭代解法的逻辑比较复杂,一定要完全理解。为了熟练我需要多做几遍这道题
回复

使用道具 举报

🔗
 楼主| 大木虫 2018-10-8 23:10:07 | 只看该作者
全局:
LC 285 Inorder Successor in BST (class 2)

Problem Metrics:
1. Understand problem at 1 min
2. Algorithm draft (part 1) at 2 min
3. Code draft (part 2) at 3 min
4. Get core concept (part 2) at 6 min
5. Algorithm draft & detailed example derivation (part 2) at 12 min
6. AC solution at 14 min

与上一题同一个概念,都是inorder逻辑的运用,我把它归为class 2
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

>
快速回复 返回顶部 返回列表