diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..ccd81dff --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,7 @@ +{ + "name": "TeX Live", + "image": "soulmachine/texlive:latest", + "extensions": [ + "James-Yu.latex-workshop" + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..72ac5ac3 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,31 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "lettcode-C++", + "type": "shell", + "command": "xelatex", + "args": [ + "-synctex=1", + "-interaction=nonstopmode", + "leetcode-cpp.tex" + ], + "options": { + "cwd": "${workspaceFolder}/C++/" + } + }, + { + "label": "lettcode-Java", + "type": "shell", + "command": "xelatex", + "args": [ + "-synctex=1", + "-interaction=nonstopmode", + "leetcode-java.tex" + ], + "options": { + "cwd": "${workspaceFolder}/Java/" + } + } + ] +} \ No newline at end of file diff --git a/C++/.DS_Store b/C++/.DS_Store new file mode 100644 index 00000000..cd3a2cf8 Binary files /dev/null and b/C++/.DS_Store differ diff --git a/C++/README.md b/C++/README.md index 6894ad25..a856af82 100644 --- a/C++/README.md +++ b/C++/README.md @@ -1,5 +1,7 @@ -#C++版 ------------------ -**下载**:LeetCode题解(C++版).pdf +# C++版 -书的内容与Java版一摸一样,不过代码是用C++写的。本书的代码使用 C++ 11 标准。 +## 编译 + +```bash +docker run -it --rm -v $(pwd):/project -w /project soulmachine/texlive xelatex -interaction=nonstopmode leetcode-cpp.tex +```` diff --git a/C++/chapBFS.tex b/C++/chapBFS.tex index 9ad2297d..826d7c4a 100644 --- a/C++/chapBFS.tex +++ b/C++/chapBFS.tex @@ -36,9 +36,99 @@ \subsubsection{描述} \subsubsection{分析} +求最短路径,用广搜。 -\subsubsection{代码} +\subsubsection{单队列} +\begin{Code} +//LeetCode, Word Ladder +// 时间复杂度O(n),空间复杂度O(n) +struct state_t { + string word; + int level; + + state_t() { word = ""; level = 0; } + state_t(const string& word, int level) { + this->word = word; + this->level = level; + } + + bool operator==(const state_t &other) const { + return this->word == other.word; + } +}; + +namespace std { + template<> struct hash { + public: + size_t operator()(const state_t& s) const { + return str_hash(s.word); + } + private: + std::hash str_hash; + }; +} + + +class Solution { +public: + int ladderLength(const string& start, const string &end, + const unordered_set &dict) { + queue q; + unordered_set visited; // 判重 + + auto state_is_valid = [&](const state_t& s) { + return dict.find(s.word) != dict.end() || s.word == end; + }; + auto state_is_target = [&](const state_t &s) {return s.word == end; }; + auto state_extend = [&](const state_t &s) { + unordered_set result; + + for (size_t i = 0; i < s.word.size(); ++i) { + state_t new_state(s.word, s.level + 1); + for (char c = 'a'; c <= 'z'; c++) { + // 防止同字母替换 + if (c == new_state.word[i]) continue; + + swap(c, new_state.word[i]); + + if (state_is_valid(new_state) && + visited.find(new_state) == visited.end()) { + result.insert(new_state); + } + swap(c, new_state.word[i]); // 恢复该单词 + } + } + + return result; + }; + + state_t start_state(start, 0); + q.push(start_state); + visited.insert(start_state); + while (!q.empty()) { + // 千万不能用 const auto&,pop() 会删除元素, + // 引用就变成了悬空引用 + const auto state = q.front(); + q.pop(); + + if (state_is_target(state)) { + return state.level + 1; + } + + const auto& new_states = state_extend(state); + for (const auto& new_state : new_states) { + q.push(new_state); + visited.insert(new_state); + } + } + return 0; + } +}; +\end{Code} + + +\subsubsection{双队列} \begin{Code} //LeetCode, Word Ladder // 时间复杂度O(n),空间复杂度O(n) @@ -49,24 +139,26 @@ \subsubsection{代码} queue current, next; // 当前层,下一层 unordered_set visited; // 判重 - int level = 0; // 层次 - bool found = false; + int level = -1; // 层次 + auto state_is_valid = [&](const string& s) { + return dict.find(s) != dict.end() || s == end; + }; auto state_is_target = [&](const string &s) {return s == end;}; auto state_extend = [&](const string &s) { - vector result; + unordered_set result; for (size_t i = 0; i < s.size(); ++i) { string new_word(s); for (char c = 'a'; c <= 'z'; c++) { + // 防止同字母替换 if (c == new_word[i]) continue; swap(c, new_word[i]); - if ((dict.count(new_word) > 0 || new_word == end) && - !visited.count(new_word)) { - result.push_back(new_word); - visited.insert(new_word); + if (state_is_valid(new_word) && + visited.find(new_word) == visited.end()) { + result.insert(new_word); } swap(c, new_word[i]); // 恢复该单词 } @@ -76,25 +168,28 @@ \subsubsection{代码} }; current.push(start); - while (!current.empty() && !found) { + visited.insert(start); + while (!current.empty()) { ++level; - while (!current.empty() && !found) { - const string str = current.front(); + while (!current.empty()) { + // 千万不能用 const auto&,pop() 会删除元素, + // 引用就变成了悬空引用 + const auto state = current.front(); current.pop(); - const auto& new_states = state_extend(str); - for (const auto& state : new_states) { - next.push(state); - if (state_is_target(state)) { - found = true; //找到了 - break; - } + if (state_is_target(state)) { + return level + 1; + } + + const auto& new_states = state_extend(state); + for (const auto& new_state : new_states) { + next.push(new_state); + visited.insert(new_state); } } swap(next, current); } - if (found) return level + 1; - else return 0; + return 0; } }; \end{Code} @@ -142,24 +237,178 @@ \subsubsection{描述} \subsubsection{分析} 跟 Word Ladder比,这题是求路径本身,不是路径长度,也是BFS,略微麻烦点。 -这题跟普通的广搜有很大的不同,就是要输出所有路径,因此在记录前驱和判重地方与普通广搜略有不同。 +求一条路径和求所有路径有很大的不同,求一条路径,每个状态节点只需要记录一个前驱即可;求所有路径时,有的状态节点可能有多个父节点,即要记录多个前驱。 +如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的处理,因为我们要找的是最短路径。 -\subsubsection{代码} + +\subsubsection{单队列} \begin{Code} //LeetCode, Word Ladder II // 时间复杂度O(n),空间复杂度O(n) +struct state_t { + string word; + int level; + + state_t() { word = ""; level = 0; } + state_t(const string& word, int level) { + this->word = word; + this->level = level; + } + + bool operator==(const state_t &other) const { + return this->word == other.word; + } +}; + +namespace std { + template<> struct hash { + public: + size_t operator()(const state_t& s) const { + return str_hash(s.word); + } + private: + std::hash str_hash; + }; +} + + class Solution { public: - vector > findLadders(string start, string end, - const unordered_set &dict) { - unordered_set current, next; // 当前层,下一层,用集合是为了去重 + vector > findLadders(const string& start, + const string& end, const unordered_set &dict) { + queue q; + unordered_set visited; // 判重 + unordered_map > father; // DAG + + auto state_is_valid = [&](const state_t& s) { + return dict.find(s.word) != dict.end() || s.word == end; + }; + auto state_is_target = [&](const state_t &s) {return s.word == end; }; + auto state_extend = [&](const state_t &s) { + unordered_set result; + + for (size_t i = 0; i < s.word.size(); ++i) { + state_t new_state(s.word, s.level + 1); + for (char c = 'a'; c <= 'z'; c++) { + // 防止同字母替换 + if (c == new_state.word[i]) continue; + + swap(c, new_state.word[i]); + + if (state_is_valid(new_state)) { + auto visited_iter = visited.find(new_state); + + if (visited_iter != visited.end()) { + if (visited_iter->level < new_state.level) { + // do nothing + } else if (visited_iter->level == new_state.level) { + result.insert(new_state); + } else { // not possible + throw std::logic_error("not possible to get here"); + } + } else { + result.insert(new_state); + } + } + swap(c, new_state.word[i]); // 恢复该单词 + } + } + + return result; + }; + + vector> result; + state_t start_state(start, 0); + q.push(start_state); + visited.insert(start_state); + while (!q.empty()) { + // 千万不能用 const auto&,pop() 会删除元素, + // 引用就变成了悬空引用 + const auto state = q.front(); + q.pop(); + + // 如果当前路径长度已经超过当前最短路径长度, + // 可以中止对该路径的处理,因为我们要找的是最短路径 + if (!result.empty() && state.level + 1 > result[0].size()) break; + + if (state_is_target(state)) { + vector path; + gen_path(father, start_state, state, path, result); + continue; + } + // 必须挪到下面,比如同一层A和B两个节点均指向了目标节点, + // 那么目标节点就会在q中出现两次,输出路径就会翻倍 + // visited.insert(state); + + // 扩展节点 + const auto& new_states = state_extend(state); + for (const auto& new_state : new_states) { + if (visited.find(new_state) == visited.end()) { + q.push(new_state); + } + visited.insert(new_state); + father[new_state].push_back(state); + } + } + + return result; + } +private: + void gen_path(unordered_map > &father, + const state_t &start, const state_t &state, vector &path, + vector > &result) { + path.push_back(state.word); + if (state == start) { + if (!result.empty()) { + if (path.size() < result[0].size()) { + result.clear(); + result.push_back(path); + reverse(result.back().begin(), result.back().end()); + } else if (path.size() == result[0].size()) { + result.push_back(path); + reverse(result.back().begin(), result.back().end()); + } else { // not possible + throw std::logic_error("not possible to get here "); + } + } else { + result.push_back(path); + reverse(result.back().begin(), result.back().end()); + } + + } else { + for (const auto& f : father[state]) { + gen_path(father, start, f, path, result); + } + } + path.pop_back(); + } +}; +\end{Code} + + +\subsubsection{双队列} + +\begin{Code} +//LeetCode, Word Ladder II +// 时间复杂度O(n),空间复杂度O(n) +class Solution { +public: + vector > findLadders(const string& start, + const string& end, const unordered_set &dict) { + // 当前层,下一层,用unordered_set是为了去重,例如两个父节点指向 + // 同一个子节点,如果用vector, 子节点就会在next里出现两次,其实此 + // 时 father 已经记录了两个父节点,next里重复出现两次是没必要的 + unordered_set current, next; unordered_set visited; // 判重 - unordered_map > father; // 树 + unordered_map > father; // DAG - bool found = false; + int level = -1; // 层次 + auto state_is_valid = [&](const string& s) { + return dict.find(s) != dict.end() || s == end; + }; auto state_is_target = [&](const string &s) {return s == end;}; auto state_extend = [&](const string &s) { unordered_set result; @@ -167,12 +416,13 @@ \subsubsection{代码} for (size_t i = 0; i < s.size(); ++i) { string new_word(s); for (char c = 'a'; c <= 'z'; c++) { + // 防止同字母替换 if (c == new_word[i]) continue; swap(c, new_word[i]); - if ((dict.count(new_word) > 0 || new_word == end) && - !visited.count(new_word)) { + if (state_is_valid(new_word) && + visited.find(new_word) == visited.end()) { result.insert(new_word); } swap(c, new_word[i]); // 恢复该单词 @@ -182,29 +432,37 @@ \subsubsection{代码} return result; }; + vector > result; current.insert(start); - while (!current.empty() && !found) { - // 先将本层全部置为已访问,防止同层之间互相指向 - for (const auto& word : current) - visited.insert(word); - for (const auto& word : current) { - const auto new_states = state_extend(word); - for (const auto &state : new_states) { - if (state_is_target(state)) found = true; - next.insert(state); - father[state].push_back(word); - // visited.insert(state); // 移动到最上面了 + while (!current.empty()) { + ++ level; + // 如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的 + // 处理,因为我们要找的是最短路径 + if (!result.empty() && level+1 > result[0].size()) break; + + // 1. 延迟加入visited, 这样才能允许两个父节点指向同一个子节点 + // 2. 一股脑current 全部加入visited, 是防止本层前一个节点扩展 + // 节点时,指向了本层后面尚未处理的节点,这条路径必然不是最短的 + for (const auto& state : current) + visited.insert(state); + for (const auto& state : current) { + if (state_is_target(state)) { + vector path; + gen_path(father, path, start, state, result); + continue; + } + + const auto new_states = state_extend(state); + for (const auto& new_state : new_states) { + next.insert(new_state); + father[new_state].push_back(state); } } current.clear(); swap(current, next); } - vector > result; - if (found) { - vector path; - gen_path(father, path, start, end, result); - } + return result; } private: @@ -213,7 +471,19 @@ \subsubsection{代码} vector > &result) { path.push_back(word); if (word == start) { - result.push_back(path); + if (!result.empty()) { + if (path.size() < result[0].size()) { + result.clear(); + result.push_back(path); + } else if(path.size() == result[0].size()) { + result.push_back(path); + } else { + // not possible + throw std::logic_error("not possible to get here"); + } + } else { + result.push_back(path); + } reverse(result.back().begin(), result.back().end()); } else { for (const auto& f : father[word]) { @@ -226,6 +496,139 @@ \subsubsection{代码} \end{Code} +\subsubsection{图的广搜} + +本题还可以看做是图上的广搜。给定了字典 \fn{dict},可以基于它画出一个无向图,表示单词之间可以互相转换。本题的本质就是已知起点和终点,在图上找出所有最短路径。 + +\begin{Code} +//LeetCode, Word Ladder II +// 时间复杂度O(n),空间复杂度O(n) +class Solution { +public: + vector > findLadders(const string& start, + const string &end, const unordered_set &dict) { + const auto& g = build_graph(dict); + vector pool; + queue q; // 未处理的节点 + // value 是所在层次 + unordered_map visited; + + auto state_is_target = [&](const state_t *s) {return s->word == end; }; + + vector> result; + q.push(create_state(nullptr, start, 0, pool)); + while (!q.empty()) { + state_t* state = q.front(); + q.pop(); + + // 如果当前路径长度已经超过当前最短路径长度, + // 可以中止对该路径的处理,因为我们要找的是最短路径 + if (!result.empty() && state->level+1 > result[0].size()) break; + + if (state_is_target(state)) { + const auto& path = gen_path(state); + if (result.empty()) { + result.push_back(path); + } else { + if (path.size() < result[0].size()) { + result.clear(); + result.push_back(path); + } else if (path.size() == result[0].size()) { + result.push_back(path); + } else { + // not possible + throw std::logic_error("not possible to get here"); + } + } + continue; + } + visited[state->word] = state->level; + + // 扩展节点 + auto iter = g.find(state->word); + if (iter == g.end()) continue; + + for (const auto& neighbor : iter->second) { + auto visited_iter = visited.find(neighbor); + + if (visited_iter != visited.end() && + visited_iter->second < state->level + 1) { + continue; + } + + q.push(create_state(state, neighbor, state->level + 1, pool)); + } + } + + // release all states + for (auto state : pool) { + delete state; + } + return result; + } + +private: + struct state_t { + state_t* father; + string word; + int level; // 所在层次,从0开始编号 + + state_t(state_t* father_, const string& word_, int level_) : + father(father_), word(word_), level(level_) {} + }; + + state_t* create_state(state_t* parent, const string& value, + int length, vector& pool) { + state_t* node = new state_t(parent, value, length); + pool.push_back(node); + + return node; + } + vector gen_path(const state_t* node) { + vector path; + + while(node != nullptr) { + path.push_back(node->word); + node = node->father; + } + + reverse(path.begin(), path.end()); + return path; + } + + unordered_map > build_graph( + const unordered_set& dict) { + unordered_map > adjacency_list; + + for (const auto& word : dict) { + for (size_t i = 0; i < word.size(); ++i) { + string new_word(word); + for (char c = 'a'; c <= 'z'; c++) { + // 防止同字母替换 + if (c == new_word[i]) continue; + + swap(c, new_word[i]); + + if ((dict.find(new_word) != dict.end())) { + auto iter = adjacency_list.find(word); + if (iter != adjacency_list.end()) { + iter->second.insert(new_word); + } else { + adjacency_list.insert(pair>(word, unordered_set())); + adjacency_list[word].insert(new_word); + } + } + swap(c, new_word[i]); // 恢复该单词 + } + } + } + return adjacency_list; + } +}; +\end{Code} + + \subsubsection{相关题目} \begindot @@ -296,7 +699,7 @@ \subsubsection{代码} const int m = board.size(); const int n = board[0].size(); - auto is_valid = [&](const state_t &s) { + auto state_is_valid = [&](const state_t &s) { const int x = s.first; const int y = s.second; if (x < 0 || x >= m || y < 0 || y >= n || board[x][y] != 'O') @@ -312,7 +715,7 @@ \subsubsection{代码} const state_t new_states[4] = {{x-1,y}, {x+1,y}, {x,y-1}, {x,y+1}}; for (int k = 0; k < 4; ++k) { - if (is_valid(new_states[k])) { + if (state_is_valid(new_states[k])) { // 既有标记功能又有去重功能 board[new_states[k].first][new_states[k].second] = '+'; result.push_back(new_states[k]); @@ -323,7 +726,7 @@ \subsubsection{代码} }; state_t start = { i, j }; - if (is_valid(start)) { + if (state_is_valid(start)) { board[i][j] = '+'; q.push(start); } @@ -353,7 +756,7 @@ \subsection{适用场景} \textbf{输入数据}:没什么特征,不像深搜,需要有“递归”的性质。如果是树或者图,概率更大。 -\textbf{状态转换图}:树或者图。 +\textbf{状态转换图}:树或者DAG图。 \textbf{求解目标}:求最短。 @@ -374,10 +777,16 @@ \subsection{思考的步骤} \item 如何扩展状态?这一步跟第2步相关。状态里记录的数据不同,扩展方法就不同。对于固定不变的数据结构(一般题目直接给出,作为输入数据),如二叉树,图等,扩展方法很简单,直接往下一层走,对于隐式图,要先在第1步里想清楚状态所带的数据,想清楚了这点,那如何扩展就很简单了。 -\item 关于判重,状态是否存在完美哈希方案?即将状态一一映射到整数,互相之间不会冲突。 +\item 如何判断重复?如果状态转换图是一颗树,则永远不会出现回路,不需要判重;如果状态转换图是一个图(这时候是一个图上的BFS),则需要判重。 \begin{enumerate} - \item 如果不存在,则需要使用通用的哈希表(自己实现或用标准库,例如\fn{unordered_set})来判重;自己实现哈希表的话,如果能够预估状态个数的上限,则可以开两个数组,head和next,表示哈希表,参考第 \S \ref{subsec:eightDigits}节方案2。 - \item 如果存在,则可以开一个大布尔数组,作为哈希表来判重,且此时可以精确计算出状态总数,而不仅仅是预估上限。 + \item 如果是求最短路径长度或一条路径,则只需要让“点”(即状态)不重复出现,即可保证不出现回路 + \item 如果是求所有路径,注意此时,状态转换图是DAG,即允许两个父节点指向同一个子节点。具体实现时,每个节点要\textbf{“延迟”}加入到已访问集合\fn{visited},要等一层全部访问完后,再加入到\fn{visited}集合。 + \item 具体实现 + \begin{enumerate} + \item 状态是否存在完美哈希方案?即将状态一一映射到整数,互相之间不会冲突。 + \item 如果不存在,则需要使用通用的哈希表(自己实现或用标准库,例如\fn{unordered_set})来判重;自己实现哈希表的话,如果能够预估状态个数的上限,则可以开两个数组,head和next,表示哈希表,参考第 \S \ref{subsec:eightDigits}节方案2。 + \item 如果存在,则可以开一个大布尔数组,来判重,且此时可以精确计算出状态总数,而不仅仅是预估上限。 + \end{enumerate} \end{enumerate} \item 目标状态是否已知?如果题目已经给出了目标状态,可以带来很大便利,这时候可以从起始状态出发,正向广搜;也可以从目标状态出发,逆向广搜;也可以同时出发,双向广搜。 @@ -389,8 +798,8 @@ \subsection{代码模板} 对于队列,可以用\fn{queue},也可以把\fn{vector}当做队列使用。当求长度时,有两种做法: \begin{enumerate} -\item 只用一个队列,但在状态结构体\fn{state_t}里增加一个整数字段\fn{step},表示走到当前状态用了多少步,当碰到目标状态,直接输出\fn{step}即可。这个方案,可以很方便的变成A*算法,把队列换成优先队列即可。 -\item 用两个队列,\fn{current, next},分别表示当前层次和下一层,另设一个全局整数\fn{level},表示层数(也即路径长度),当碰到目标状态,输出\fn{level}即可。这个方案,状态可以少一个字段,节省内存。 +\item 只用一个队列,但在状态结构体\fn{state_t}里增加一个整数字段\fn{level},表示当前所在的层次,当碰到目标状态,直接输出\fn{level}即可。这个方案,可以很容易的变成A*算法,把\fn{queue}替换为\fn{priority_queue}即可。 +\item 用两个队列,\fn{current, next},分别表示当前层次和下一层,另设一个全局整数\fn{level},表示层数(也即路径长度),当碰到目标状态,输出\fn{level}即可。这个方案,状态里可以不存路径长度,只需全局设置一个整数\fn{level},比较节省内存; \end{enumerate} 对于hashset,如果有完美哈希方案,用布尔数组(\fn{bool visited[STATE_MAX]}或\fn{vector visited(STATE_MAX, false)})来表示;如果没有,可以用STL里的\fn{set}或\fn{unordered_set}。 @@ -398,15 +807,16 @@ \subsection{代码模板} 对于树,如果用STL,可以用\fn{unordered_map father}表示一颗树,代码非常简洁。如果能够预估状态总数的上限(设为STATE_MAX),可以用数组(\fn{state_t nodes[STATE_MAX]}),即树的双亲表示法来表示树,效率更高,当然,需要写更多代码。 -\subsubsection{双队列的写法} -\begin{Codex}[label=bfs_template1.cpp] +\subsubsection{如何表示状态} + +\begin{Codex}[label=bfs_common.h] /** 状态 */ struct state_t { int data1; /** 状态的数据,可以有多个字段. */ int data2; /** 状态的数据,可以有多个字段. */ // dataN; /** 其他字段 */ int action; /** 由父状态移动到本状态的动作,求动作序列时需要. */ - int count; /** 所花费的步骤数(也即路径长度-1),求路径长度时需要; + int level; /** 所在的层次(从0开始),也即路径长度-1,求路径长度时需要; 不过,采用双队列时不需要本字段,只需全局设一个整数 */ bool operator==(const state_t &other) const { return true; // 根据具体问题实现 @@ -435,14 +845,12 @@ \subsubsection{双队列的写法} int m; // 存放外面传入的数据 }; - /** - * @brief 反向生成路径. + * @brief 反向生成路径,求一条路径. * @param[in] father 树 * @param[in] target 目标节点 * @return 从起点到target的路径 */ -template vector gen_path(const unordered_map &father, const state_t &target) { vector path; @@ -458,132 +866,334 @@ \subsubsection{双队列的写法} } /** - * @brief 广搜. - * @param[in] state_t 状态,如整数,字符串,一维数组等 + * 反向生成路径,求所有路径. + * @param[in] father 存放了所有路径的树 + * @param[in] start 起点 + * @param[in] state 终点 + * @return 从起点到终点的所有路径 + */ +void gen_path(unordered_map > &father, + const string &start, const state_t& state, vector &path, + vector > &result) { + path.push_back(state); + if (state == start) { + if (!result.empty()) { + if (path.size() < result[0].size()) { + result.clear(); + result.push_back(path); + } else if(path.size() == result[0].size()) { + result.push_back(path); + } else { + // not possible + throw std::logic_error("not possible to get here"); + } + } else { + result.push_back(path); + } + reverse(result.back().begin(), result.back().end()); + } else { + for (const auto& f : father[state]) { + gen_path(father, start, f, path, result); + } + } + path.pop_back(); +} +\end{Codex} + + +\subsubsection{求最短路径长度或一条路径} + +\textbf{单队列的写法} + +\begin{Codex}[label=bfs_template.cpp] +#include "bfs_common.h" + +/** + * @brief 广搜,只用一个队列. * @param[in] start 起点 - * @param[in] grid 输入数据 + * @param[in] data 输入数据 * @return 从起点到目标状态的一条最短路径 */ -template -vector bfs(const state_t &start, const vector> &grid) { +vector bfs(state_t &start, const vector> &grid) { + queue q; // 队列 + unordered_set visited; // 判重 + unordered_map father; // 树,求路径本身时才需要 + + // 判断状态是否合法 + auto state_is_valid = [&](const state_t &s) { /*...*/ }; + + // 判断当前状态是否为所求目标 + auto state_is_target = [&](const state_t &s) { /*...*/ }; + + // 扩展当前状态 + auto state_extend = [&](const state_t &s) { + unordered_set result; + for (/*...*/) { + const state_t new_state = /*...*/; + if (state_is_valid(new_state) && + visited.find(new_state) != visited.end()) { + result.insert(new_state); + } + } + return result; + }; + + assert (start.level == 0); + q.push(start); + while (!q.empty()) { + // 千万不能用 const auto&,pop() 会删除元素, + // 引用就变成了悬空引用 + const state_t state = q.front(); + q.pop(); + visited.insert(state); + + // 访问节点 + if (state_is_target(state)) { + return return gen_path(father, target); // 求一条路径 + // return state.level + 1; // 求路径长度 + } + + // 扩展节点 + vector new_states = state_extend(state); + for (const auto& new_state : new_states) { + q.push(new_state); + father[new_state] = state; // 求一条路径 + // visited.insert(state); // 优化:可以提前加入 visited 集合, + // 从而缩小状态扩展。这时 q 的含义略有变化,里面存放的是处理了一半 + // 的节点:已经加入了visited,但还没有扩展。别忘记 while循环开始 + // 前,要加一行代码, visited.insert(start) + } + } + + return vector(); + //return 0; +} +\end{Codex} + + +\textbf{双队列的写法} +\begin{Codex}[label=bfs_template1.cpp] +#include "bfs_common.h" + +/** + * @brief 广搜,使用两个队列. + * @param[in] start 起点 + * @param[in] data 输入数据 + * @return 从起点到目标状态的一条最短路径 + */ +vector bfs(const state_t &start, const type& data) { queue next, current; // 当前层,下一层 unordered_set visited; // 判重 - unordered_map father; // 树 + unordered_map father; // 树,求路径本身时才需要 + + int level = -1; // 层次 - int level = 0; // 层次 - bool found = false; // 是否找到目标 - state_t target; // 符合条件的目标状态 + // 判断状态是否合法 + auto state_is_valid = [&](const state_t &s) { /*...*/ }; // 判断当前状态是否为所求目标 - auto state_is_target = [&](const state_t &s) {return true; }; + auto state_is_target = [&](const state_t &s) { /*...*/ }; + // 扩展当前状态 auto state_extend = [&](const state_t &s) { - vector result; - // ... + unordered_set result; + for (/*...*/) { + const state_t new_state = /*...*/; + if (state_is_valid(new_state) && + visited.find(new_state) != visited.end()) { + result.insert(new_state); + } + } return result; }; current.push(start); - visited.insert(start); - while (!current.empty() && !found) { + while (!current.empty()) { ++level; - while (!current.empty() && !found) { - const state_t state = current.front(); + while (!current.empty()) { + // 千万不能用 const auto&,pop() 会删除元素, + // 引用就变成了悬空引用 + const auto state = current.front(); current.pop(); - vector new_states = state_extend(state); - for (auto iter = new_states.cbegin(); - iter != new_states.cend() && ! found; ++iter) { - const state_t new_state(*iter); - - if (state_is_target(new_state)) { - found = true; //找到了 - target = new_state; - father[new_state] = state; - break; - } + visited.insert(state); + if (state_is_target(state)) { + return return gen_path(father, state); // 求一条路径 + // return state.level + 1; // 求路径长度 + } + + const auto& new_states = state_extend(state); + for (const auto& new_state : new_states) { next.push(new_state); - // visited.insert(new_state); 必须放到 state_extend()里 father[new_state] = state; + // visited.insert(state); // 优化:可以提前加入 visited 集合, + // 从而缩小状态扩展。这时 current 的含义略有变化,里面存放的是处 + // 理了一半的节点:已经加入了visited,但还没有扩展。别忘记 while + // 循环开始前,要加一行代码, visited.insert(start) } } swap(next, current); //!!! 交换两个队列 } - if (found) { - return gen_path(father, target); - //return level + 1; - } else { - return vector(); - //return 0; + return vector(); + // return 0; +} +\end{Codex} + + +\subsubsection{求所有路径} + +\textbf{单队列} + +\begin{Codex}[label=bfs_template.cpp] +/** + * @brief 广搜,使用一个队列. + * @param[in] start 起点 + * @param[in] data 输入数据 + * @return 从起点到目标状态的所有最短路径 + */ +vector > bfs(const state_t &start, const type& data) { + queue q; + unordered_set visited; // 判重 + unordered_map > father; // DAG + + auto state_is_valid = [&](const state_t& s) { /*...*/ }; + auto state_is_target = [&](const state_t &s) { /*...*/ }; + auto state_extend = [&](const state_t &s) { + unordered_set result; + for (/*...*/) { + const state_t new_state = /*...*/; + if (state_is_valid(new_state)) { + auto visited_iter = visited.find(new_state); + + if (visited_iter != visited.end()) { + if (visited_iter->level < new_state.level) { + // do nothing + } else if (visited_iter->level == new_state.level) { + result.insert(new_state); + } else { // not possible + throw std::logic_error("not possible to get here"); + } + } else { + result.insert(new_state); + } + } + } + + return result; + }; + + vector> result; + state_t start_state(start, 0); + q.push(start_state); + visited.insert(start_state); + while (!q.empty()) { + // 千万不能用 const auto&,pop() 会删除元素, + // 引用就变成了悬空引用 + const auto state = q.front(); + q.pop(); + + // 如果当前路径长度已经超过当前最短路径长度, + // 可以中止对该路径的处理,因为我们要找的是最短路径 + if (!result.empty() && state.level + 1 > result[0].size()) break; + + if (state_is_target(state)) { + vector path; + gen_path(father, start_state, state, path, result); + continue; + } + // 必须挪到下面,比如同一层A和B两个节点均指向了目标节点, + // 那么目标节点就会在q中出现两次,输出路径就会翻倍 + // visited.insert(state); + + // 扩展节点 + const auto& new_states = state_extend(state); + for (const auto& new_state : new_states) { + if (visited.find(new_state) == visited.end()) { + q.push(new_state); + } + visited.insert(new_state); + father[new_state].push_back(state); + } } + + return result; } \end{Codex} -\subsubsection{只用一个队列的写法} -双队列的写法,当求路径长度时,不需要在状态里设置一个\fn{count}字段记录路径长度,只需全局设置一个整数\fn{level},比较节省内存;只用一个队列的写法,当求路径长度时,需要在状态里设置一个\fn{count}字段,不过,这种写法有一个好处 —— 可以很容易的变为A*算法,把\fn{queue}替换为\fn{priority_queue}即可。 +\textbf{双队列的写法} -\begin{Codex}[label=bfs_template2.cpp] -// 与模板1相同的部分,不再重复 -// ... +\begin{Codex}[label=bfs_template.cpp] +#include "bfs_common.h" /** - * @brief 广搜. - * @param[in] state_t 状态,如整数,字符串,一维数组等 + * @brief 广搜,使用两个队列. * @param[in] start 起点 - * @param[in] grid 输入数据 - * @return 从起点到目标状态的一条最短路径 + * @param[in] data 输入数据 + * @return 从起点到目标状态的所有最短路径 */ -template -vector bfs(state_t &start, const vector> &grid) { - queue q; // 队列 +vector > bfs(const state_t &start, const type& data) { + // 当前层,下一层,用unordered_set是为了去重,例如两个父节点指向 + // 同一个子节点,如果用vector, 子节点就会在next里出现两次,其实此 + // 时 father 已经记录了两个父节点,next里重复出现两次是没必要的 + unordered_set current, next; unordered_set visited; // 判重 - unordered_map father; // 树 + unordered_map > father; // DAG + + int level = -1; // 层次 - int level = 0; // 层次 - bool found = false; // 是否找到目标 - state_t target; // 符合条件的目标状态 + // 判断状态是否合法 + auto state_is_valid = [&](const state_t &s) { /*...*/ }; // 判断当前状态是否为所求目标 - auto state_is_target = [&](const state_t &s) {return true; }; + auto state_is_target = [&](const state_t &s) { /*...*/ }; + // 扩展当前状态 auto state_extend = [&](const state_t &s) { - vector result; - // ... + unordered_set result; + for (/*...*/) { + const state_t new_state = /*...*/; + if (state_is_valid(new_state) && + visited.find(new_state) != visited.end()) { + result.insert(new_state); + } + } return result; }; - start.count = 0; - q.push(start); - visited.insert(start); - while (!q.empty() && !found) { - const state_t state = q.front(); - q.pop(); - vector new_states = state_extend(state); - for (auto iter = new_states.cbegin(); - iter != new_states.cend() && ! found; ++iter) { - const state_t new_state(*iter); - - if (state_is_target(new_state)) { - found = true; //找到了 - target = new_state; - father[new_state] = state; - break; + vector > result; + current.insert(start); + while (!current.empty()) { + ++ level; + // 如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的 + // 处理,因为我们要找的是最短路径 + if (!result.empty() && level+1 > result[0].size()) break; + + // 1. 延迟加入visited, 这样才能允许两个父节点指向同一个子节点 + // 2. 一股脑current 全部加入visited, 是防止本层前一个节点扩展 + // 节点时,指向了本层后面尚未处理的节点,这条路径必然不是最短的 + for (const auto& state : current) + visited.insert(state); + for (const auto& state : current) { + if (state_is_target(state)) { + vector path; + gen_path(father, path, start, state, result); + continue; } - q.push(new_state); - // visited.insert(new_state); 必须放到 state_extend()里 - father[new_state] = state; + const auto new_states = state_extend(state); + for (const auto& new_state : new_states) { + next.insert(new_state); + father[new_state].push_back(state); + } } - } - if (found) { - return gen_path(father, target); - //return level + 1; - } else { - return vector(); - //return 0; + current.clear(); + swap(current, next); } + + return result; } \end{Codex} + diff --git a/C++/chapBruteforce.tex b/C++/chapBruteforce.tex index 08014864..4c9e4037 100644 --- a/C++/chapBruteforce.tex +++ b/C++/chapBruteforce.tex @@ -439,16 +439,16 @@ \subsubsection{代码} // 时间复杂度O(n!),空间复杂度O(1) class Solution { public: - vector> permute(vector& num) { + vector > permute(vector &num) { + vector > result; sort(num.begin(), num.end()); - vector> permutations; - do { - permutations.push_back(num); - } while (next_permutation(num.begin(), num.end())); // 见第2.1节 - - return permutations; + result.push_back(num); + // 调用的是 2.1.12 节的 next_permutation() + // 而不是 std::next_permutation() + } while(next_permutation(num.begin(), num.end())); + return result; } }; \end{Code} @@ -733,6 +733,7 @@ \subsection{递归} vector letterCombinations (const string &digits) { vector result; + if (digits.empty()) return result; dfs(digits, 0, "", result); return result; } @@ -761,6 +762,7 @@ \subsection{迭代} "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" }; vector letterCombinations (const string &digits) { + if (digits.empty()) return vector(); vector result(1, ""); for (auto d : digits) { const size_t n = result.size(); diff --git a/C++/chapDFS.tex b/C++/chapDFS.tex index 87c4c7bf..f6c4c107 100644 --- a/C++/chapDFS.tex +++ b/C++/chapDFS.tex @@ -39,8 +39,7 @@ \subsubsection{深搜1} return result; } - // s[0, prev-1]之间已经处理,保证是回文串 - // prev 表示s[prev-1]与s[prev]之间的空隙位置,start同理 + // prev 表示前一个隔板, start 表示当前隔板 void dfs(string &s, vector& path, vector> &result, size_t prev, size_t start) { if (start == s.size()) { // 最后一个隔板 @@ -204,24 +203,24 @@ \subsubsection{代码} class Solution { public: int uniquePaths(int m, int n) { - // 0行和0列未使用 - this->f = vector >(m + 1, vector(n + 1, 0)); - return dfs(m, n); + // f[x][y] 表示 从(0,0)到(x,y)的路径条数 + f = vector >(m, vector(n, 0)); + f[0][0] = 1; + return dfs(m - 1, n - 1); } private: vector > f; // 缓存 int dfs(int x, int y) { - if (x < 1 || y < 1) return 0; // 数据非法,终止条件 + if (x < 0 || y < 0) return 0; // 数据非法,终止条件 - if (x == 1 && y == 1) return 1; // 回到起点,收敛条件 + if (x == 0 && y == 0) return f[0][0]; // 回到起点,收敛条件 - return getOrUpdate(x - 1, y) + getOrUpdate(x, y - 1); - } - - int getOrUpdate(int x, int y) { - if (f[x][y] > 0) return f[x][y]; - else return f[x][y] = dfs(x, y); + if (f[x][y] > 0) { + return f[x][y]; + } else { + return f[x][y] = dfs(x - 1, y) + dfs(x, y - 1); + } } }; \end{Code} @@ -248,9 +247,9 @@ \subsubsection{代码} f[0] = 1; for (int i = 0; i < m; i++) { for (int j = 1; j < n; j++) { - // 左边的f[j],表示更新后的f[j],与公式中的f[i[[j]对应 + // 左边的f[j],表示更新后的f[j],与公式中的f[i][j]对应 // 右边的f[j],表示老的f[j],与公式中的f[i-1][j]对应 - f[j] = f[j - 1] + f[j]; + f[j] = f[j] + f[j - 1]; } } return f[n - 1]; @@ -338,40 +337,41 @@ \subsubsection{代码} // 深搜 + 缓存,即备忘录法 class Solution { public: - int uniquePathsWithObstacles(vector > &obstacleGrid) { + int uniquePathsWithObstacles(const vector >& obstacleGrid) { const int m = obstacleGrid.size(); const int n = obstacleGrid[0].size(); - // 0行和0列未使用 - this->f = vector >(m + 1, vector(n + 1, 0)); - return dfs(obstacleGrid, m, n); + if (obstacleGrid[0][0] || obstacleGrid[m - 1][n - 1]) return 0; + + f = vector >(m, vector(n, 0)); + f[0][0] = obstacleGrid[0][0] ? 0 : 1; + return dfs(obstacleGrid, m - 1, n - 1); } private: vector > f; // 缓存 - int dfs(const vector > &obstacleGrid, + // @return 从 (0, 0) 到 (x, y) 的路径总数 + int dfs(const vector >& obstacleGrid, int x, int y) { - if (x < 1 || y < 1) return 0; // 数据非法,终止条件 + if (x < 0 || y < 0) return 0; // 数据非法,终止条件 // (x,y)是障碍 - if (obstacleGrid[x-1][y-1]) return 0; - - if (x == 1 and y == 1) return 1; // 回到起点,收敛条件 + if (obstacleGrid[x][y]) return 0; - return getOrUpdate(obstacleGrid, x - 1, y) + - getOrUpdate(obstacleGrid, x, y - 1); - } + if (x == 0 and y == 0) return f[0][0]; // 回到起点,收敛条件 - int getOrUpdate(const vector > &obstacleGrid, - int x, int y) { - if (f[x][y] > 0) return f[x][y]; - else return f[x][y] = dfs(obstacleGrid, x, y); + if (f[x][y] > 0) { + return f[x][y]; + } else { + return f[x][y] = dfs(obstacleGrid, x - 1, y) + + dfs(obstacleGrid, x, y - 1); + } } }; \end{Code} \subsection{动规} -与上一题类似,但要特别注意第一列的障碍。在上一题中,第一列全部是1,但是在这一题中不同,第一列如果某一行有障碍物,那么后面的行应该为0。 +与上一题类似,但要特别注意第一列的障碍。在上一题中,第一列全部是1,但是在这一题中不同,第一列如果某一行有障碍物,那么后面的行全为0。 \subsubsection{代码} @@ -389,9 +389,11 @@ \subsubsection{代码} vector f(n, 0); f[0] = obstacleGrid[0][0] ? 0 : 1; - for (int i = 0; i < m; i++) - for (int j = 0; j < n; j++) - f[j] = obstacleGrid[i][j] ? 0 : (j == 0 ? 0 : f[j - 1]) + f[j]; + for (int i = 0; i < m; i++) { + f[0] = f[0] == 0 ? 0 : (obstacleGrid[i][0] ? 0 : 1); + for (int j = 1; j < n; j++) + f[j] = obstacleGrid[i][j] ? 0 : (f[j] + f[j - 1]); + } return f[n - 1]; } @@ -440,9 +442,74 @@ \subsubsection{描述} \subsubsection{分析} + 经典的深搜题。 -\subsubsection{代码} +设置一个数组 \fn{vector C(n, 0)}, \fn{C[i]} 表示第i行皇后所在的列编号,即在位置 (i, C[i]) 上放了一个皇后,这样用一个一维数组,就能记录整个棋盘。 + + +\subsubsection{代码1} +\begin{Code} +// LeetCode, N-Queens +// 深搜+剪枝 +// 时间复杂度O(n!*n),空间复杂度O(n) +class Solution { +public: + vector > solveNQueens(int n) { + vector > result; + vector C(n, -1); // C[i]表示第i行皇后所在的列编号 + dfs(C, result, 0); + return result; + } +private: + void dfs(vector &C, vector > &result, int row) { + const int N = C.size(); + if (row == N) { // 终止条件,也是收敛条件,意味着找到了一个可行解 + vector solution; + for (int i = 0; i < N; ++i) { + string s(N, '.'); + for (int j = 0; j < N; ++j) { + if (j == C[i]) s[j] = 'Q'; + } + solution.push_back(s); + } + result.push_back(solution); + return; + } + + for (int j = 0; j < N; ++j) { // 扩展状态,一列一列的试 + const bool ok = isValid(C, row, j); + if (!ok) continue; // 剪枝,如果非法,继续尝试下一列 + // 执行扩展动作 + C[row] = j; + dfs(C, result, row + 1); + // 撤销动作 + // C[row] = -1; + } + } + + /** + * 能否在 (row, col) 位置放一个皇后. + * + * @param C 棋局 + * @param row 当前正在处理的行,前面的行都已经放了皇后了 + * @param col 当前列 + * @return 能否放一个皇后 + */ + bool isValid(const vector &C, int row, int col) { + for (int i = 0; i < row; ++i) { + // 在同一列 + if (C[i] == col) return false; + // 在同一对角线上 + if (abs(i - row) == abs(C[i] - col)) return false; + } + return true; + } +}; +\end{Code} + + +\subsubsection{代码2} \begin{Code} // LeetCode, N-Queens // 深搜+剪枝 @@ -450,20 +517,20 @@ \subsubsection{代码} class Solution { public: vector > solveNQueens(int n) { - this->columns = vector(n, 0); - this->main_diag = vector(2 * n, 0); - this->anti_diag = vector(2 * n, 0); + this->columns = vector(n, false); + this->main_diag = vector(2 * n - 1, false); + this->anti_diag = vector(2 * n - 1, false); vector > result; - vector C(n, 0); // C[i]表示第i行皇后所在的列编号 + vector C(n, -1); // C[i]表示第i行皇后所在的列编号 dfs(C, result, 0); return result; } private: // 这三个变量用于剪枝 - vector columns; // 表示已经放置的皇后占据了哪些列 - vector main_diag; // 占据了哪些主对角线 - vector anti_diag; // 占据了哪些副对角线 + vector columns; // 表示已经放置的皇后占据了哪些列 + vector main_diag; // 占据了哪些主对角线 + vector anti_diag; // 占据了哪些副对角线 void dfs(vector &C, vector > &result, int row) { const int N = C.size(); @@ -481,16 +548,16 @@ \subsubsection{代码} } for (int j = 0; j < N; ++j) { // 扩展状态,一列一列的试 - const bool ok = columns[j] == 0 && main_diag[row + j] == 0 && - anti_diag[row - j + N] == 0; - if (!ok) continue; // 剪枝:如果合法,继续递归 + const bool ok = !columns[j] && !main_diag[row - j + N - 1] && + !anti_diag[row + j]; + if (!ok) continue; // 剪枝,如果非法,继续尝试下一列 // 执行扩展动作 C[row] = j; - columns[j] = main_diag[row + j] = anti_diag[row - j + N] = 1; + columns[j] = main_diag[row - j + N - 1] = anti_diag[row + j] = true; dfs(C, result, row + 1); // 撤销动作 - // C[row] = 0; - columns[j] = main_diag[row + j] = anti_diag[row - j + N] = 0; + // C[row] = -1; + columns[j] = main_diag[row - j + N - 1] = anti_diag[row + j] = false; } } }; @@ -517,7 +584,62 @@ \subsubsection{分析} 只需要输出解的个数,不需要输出所有解,代码要比上一题简化很多。设一个全局计数器,每找到一个解就增1。 -\subsubsection{代码} +\subsubsection{代码1} +\begin{Code} +// LeetCode, N-Queens II +// 深搜+剪枝 +// 时间复杂度O(n!*n),空间复杂度O(n) +class Solution { +public: + int totalNQueens(int n) { + this->count = 0; + + vector C(n, 0); // C[i]表示第i行皇后所在的列编号 + dfs(C, 0); + return this->count; + } +private: + int count; // 解的个数 + + void dfs(vector &C, int row) { + const int N = C.size(); + if (row == N) { // 终止条件,也是收敛条件,意味着找到了一个可行解 + ++this->count; + return; + } + + for (int j = 0; j < N; ++j) { // 扩展状态,一列一列的试 + const bool ok = isValid(C, row, j); + if (!ok) continue; // 剪枝:如果合法,继续递归 + // 执行扩展动作 + C[row] = j; + dfs(C, row + 1); + // 撤销动作 + // C[row] = -1; + } + } + /** + * 能否在 (row, col) 位置放一个皇后. + * + * @param C 棋局 + * @param row 当前正在处理的行,前面的行都已经放了皇后了 + * @param col 当前列 + * @return 能否放一个皇后 + */ + bool isValid(const vector &C, int row, int col) { + for (int i = 0; i < row; ++i) { + // 在同一列 + if (C[i] == col) return false; + // 在同一对角线上 + if (abs(i - row) == abs(C[i] - col)) return false; + } + return true; + } +}; +\end{Code} + + +\subsubsection{代码2} \begin{Code} // LeetCode, N-Queens II // 深搜+剪枝 @@ -526,9 +648,9 @@ \subsubsection{代码} public: int totalNQueens(int n) { this->count = 0; - this->columns = vector(n, 0); - this->main_diag = vector(2 * n, 0); - this->anti_diag = vector(2 * n, 0); + this->columns = vector(n, false); + this->main_diag = vector(2 * n - 1, false); + this->anti_diag = vector(2 * n - 1, false); vector C(n, 0); // C[i]表示第i行皇后所在的列编号 dfs(C, 0); @@ -537,9 +659,9 @@ \subsubsection{代码} private: int count; // 解的个数 // 这三个变量用于剪枝 - vector columns; // 表示已经放置的皇后占据了哪些列 - vector main_diag; // 占据了哪些主对角线 - vector anti_diag; // 占据了哪些副对角线 + vector columns; // 表示已经放置的皇后占据了哪些列 + vector main_diag; // 占据了哪些主对角线 + vector anti_diag; // 占据了哪些副对角线 void dfs(vector &C, int row) { const int N = C.size(); @@ -549,19 +671,19 @@ \subsubsection{代码} } for (int j = 0; j < N; ++j) { // 扩展状态,一列一列的试 - const bool ok = columns[j] == 0 && - main_diag[row + j] == 0 && - anti_diag[row - j + N] == 0; + const bool ok = !columns[j] && + !main_diag[row - j + N] && + !anti_diag[row + j]; if (!ok) continue; // 剪枝:如果合法,继续递归 // 执行扩展动作 C[row] = j; - columns[j] = main_diag[row + j] = - anti_diag[row - j + N] = 1; + columns[j] = main_diag[row - j + N] = + anti_diag[row + j] = true; dfs(C, row + 1); // 撤销动作 - // C[row] = 0; - columns[j] = main_diag[row + j] = - anti_diag[row - j + N] = 0; + // C[row] = -1; + columns[j] = main_diag[row - j + N] = + anti_diag[row + j] = false; } } }; @@ -597,43 +719,43 @@ \subsubsection{代码} // 时间复杂度O(n^4),空间复杂度O(n) class Solution { public: - vector restoreIpAddresses(string s) { + vector restoreIpAddresses(const string& s) { vector result; - string ip; // 存放中间结果 - dfs(s, 0, 0, ip, result); + vector ip; // 存放中间结果 + dfs(s, ip, result, 0); return result; } /** * @brief 解析字符串 * @param[in] s 字符串,输入数据 - * @param[in] startIndex 从s的哪里开始 - * @param[in] step 当前步骤编号,从0开始编号,取值为0,1,2,3,4表示结束了 - * @param[out] intermediate 当前解析出来的中间结果 + * @param[out] ip 存放中间结果 * @param[out] result 存放所有可能的IP地址 + * @param[in] start 当前正在处理的 index * @return 无 */ - void dfs(string s, size_t start, size_t step, string ip, - vector &result) { - if (start == s.size() && step == 4) { // 找到一个合法解 - ip.resize(ip.size() - 1); - result.push_back(ip); + void dfs(string s, vector& ip, vector &result, + size_t start) { + if (ip.size() == 4 && start == s.size()) { // 找到一个合法解 + result.push_back(ip[0] + '.' + ip[1] + '.' + ip[2] + '.' + ip[3]); return; } - if (s.size() - start > (4 - step) * 3) + if (s.size() - start > (4 - ip.size()) * 3) return; // 剪枝 - if (s.size() - start < (4 - step)) + if (s.size() - start < (4 - ip.size())) return; // 剪枝 int num = 0; for (size_t i = start; i < start + 3; i++) { num = num * 10 + (s[i] - '0'); - if (num <= 255) { // 当前结点合法,则继续往下递归 - ip += s[i]; - dfs(s, i + 1, step + 1, ip + '.', result); - } + if (num < 0 || num > 255) continue; // 剪枝 + + ip.push_back(s.substr(start, i - start + 1)); + dfs(s, ip, result, i + 1); + ip.pop_back(); + if (num == 0) break; // 不允许前缀0,但允许单个0 } } @@ -684,24 +806,24 @@ \subsubsection{代码} vector > combinationSum(vector &nums, int target) { sort(nums.begin(), nums.end()); vector > result; // 最终结果 - vector intermediate; // 中间结果 - dfs(nums, target, 0, intermediate, result); + vector path; // 中间结果 + dfs(nums, path, result, target, 0); return result; } private: - void dfs(vector& nums, int gap, int start, vector& intermediate, - vector > &result) { + void dfs(vector& nums, vector& path, vector > &result, + int gap, int start) { if (gap == 0) { // 找到一个合法解 - result.push_back(intermediate); + result.push_back(path); return; } for (size_t i = start; i < nums.size(); i++) { // 扩展状态 if (gap < nums[i]) return; // 剪枝 - intermediate.push_back(nums[i]); // 执行扩展动作 - dfs(nums, gap - nums[i], i, intermediate, result); - intermediate.pop_back(); // 撤销动作 + path.push_back(nums[i]); // 执行扩展动作 + dfs(nums, path, result, gap - nums[i], i); + path.pop_back(); // 撤销动作 } } }; @@ -719,9 +841,9 @@ \section{Combination Sum II} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsubsection{描述} -Given a set of candidate numbers ($C$) and a target number ($T$), find all unique combinations in $C$ where the candidate numbers sums to $T$. +Given a collection of candidate numbers ($C$) and a target number ($T$), find all unique combinations in $C$ where the candidate numbers sums to $T$. -The same repeated number may be chosen from $C$ \emph{once} number of times. +Each number in $C$ may only be used \emph{once} in the combination. Note: \begindot @@ -752,24 +874,24 @@ \subsubsection{代码} public: vector > combinationSum2(vector &nums, int target) { sort(nums.begin(), nums.end()); // 跟第 50 行配合, - // 确保每个元素最多只用一次 + // 确保每个元素最多只用一次 vector > result; - vector intermediate; - dfs(nums, target, 0, intermediate, result); + vector path; + dfs(nums, path, result, target, 0); return result; } private: // 使用nums[start, nums.size())之间的元素,能找到的所有可行解 - static void dfs(vector &nums, int gap, int start, - vector &intermediate, vector > &result) { + static void dfs(const vector &nums, vector &path, + vector > &result, int gap, int start) { if (gap == 0) { // 找到一个合法解 - result.push_back(intermediate); + result.push_back(path); return; } int previous = -1; for (size_t i = start; i < nums.size(); i++) { - // 如果上一轮循环没有选nums[i],则本次循环就不能再选nums[i], + // 如果上一轮循环已经使用了nums[i],则本次循环就不能再选nums[i], // 确保nums[i]最多只用一次 if (previous == nums[i]) continue; @@ -777,9 +899,9 @@ \subsubsection{代码} previous = nums[i]; - intermediate.push_back(nums[i]); - dfs(nums, gap - nums[i], i + 1, intermediate, result); - intermediate.pop_back(); // 恢复环境 + path.push_back(nums[i]); + dfs(nums, path, result, gap - nums[i], i + 1); + path.pop_back(); // 恢复环境 } } }; @@ -818,17 +940,27 @@ \subsubsection{代码1} public: vector generateParenthesis(int n) { vector result; - if (n > 0) generate(n, "", 0, 0, result); + string path; + if (n > 0) generate(n, path, result, 0, 0); return result; } // l 表示 ( 出现的次数, r 表示 ) 出现的次数 - void generate(int n, string s, int l, int r, vector &result) { + void generate(int n, string& path, vector &result, int l, int r) { if (l == n) { + string s(path); result.push_back(s.append(n - r, ')')); return; } - generate(n, s + '(', l + 1, r, result); - if (l > r) generate(n, s + ")", l, r + 1, result); + + path.push_back('('); + generate(n, path, result, l + 1, r); + path.pop_back(); + + if (l > r) { + path.push_back(')'); + generate(n, path, result, l, r + 1); + path.pop_back(); + } } }; \end{Code} @@ -971,7 +1103,7 @@ \subsubsection{代码} // 时间复杂度O(n^2*m^2),空间复杂度O(n^2) class Solution { public: - bool exist(vector > &board, string word) { + bool exist(const vector > &board, const string& word) { const int m = board.size(); const int n = board[0].size(); vector > visited(m, vector(n, false)); @@ -1039,12 +1171,6 @@ \subsection{思考的步骤} \item 如何扩展状态?这一步跟上一步相关。状态里记录的数据不同,扩展方法就不同。对于固定不变的数据结构(一般题目直接给出,作为输入数据),如二叉树,图等,扩展方法很简单,直接往下一层走,对于隐式图,要先在第1步里想清楚状态所带的数据,想清楚了这点,那如何扩展就很简单了。 -\item 关于判重 - \begin{enumerate} - \item 是否需要判重?如果状态转换图是一棵树,则不需要判重,因为在遍历过程中不可能重复;如果状态转换图是一个DAG,则需要判重。这一点跟BFS不一样,BFS的状态转换图总是DAG,必须要判重。 - \item 怎样判重?跟广搜相同,见第 \S \ref{sec:bfs-template} 节。同时,DAG说明存在重叠子问题,此时可以用缓存加速,见第8步。 - \end{enumerate} - \item 终止条件是什么?终止条件是指到了不能扩展的末端节点。对于树,是叶子节点,对于图或隐式图,是出度为0的节点。 \item {收敛条件是什么?收敛条件是指找到了一个合法解的时刻。如果是正向深搜(父状态处理完了才进行递归,即父状态不依赖子状态,递归语句一定是在最后,尾递归),则是指是否达到目标状态;如果是逆向深搜(处理父状态时需要先知道子状态的结果,此时递归语句不在最后),则是指是否到达初始状态。 @@ -1053,6 +1179,12 @@ \subsection{思考的步骤} 为了判断是否到了收敛条件,要在函数接口里用一个参数记录当前的位置(或距离目标还有多远)。如果是求一个解,直接返回这个解;如果是求所有解,要在这里收集解,即把第一步中表示路径的数组\fn{path[]}复制到解集合里。} +\item 关于判重 + \begin{enumerate} + \item 是否需要判重?如果状态转换图是一棵树,则不需要判重,因为在遍历过程中不可能重复;如果状态转换图是一个DAG,则需要判重。这一点跟BFS不一样,BFS的状态转换图总是DAG,必须要判重。 + \item 怎样判重?跟广搜相同,见第 \S \ref{sec:bfs-template} 节。同时,DAG说明存在重叠子问题,此时可以用缓存加速,见第8步。 + \end{enumerate} + \item 如何加速? \begin{enumerate} \item 剪枝。深搜一定要好好考虑怎么剪枝,成本小收益大,加几行代码,就能大大加速。这里没有通用方法,只能具体问题具体分析,要充分观察,充分利用各种信息来剪枝,在中间节点提前返回。 @@ -1116,8 +1248,8 @@ \subsection{深搜与递归的区别} 递归有两种加速策略,一种是\textbf{剪枝(prunning)},对中间结果进行判断,提前返回;一种是\textbf{缓存},缓存中间结果,防止重复计算,用空间换时间。 -其实,递归+缓存,就是 memorization。所谓\textbf{memorization}(翻译为备忘录法,见第 \S \ref{sec:dp-vs-memorization}节),就是"top-down with cache"(自顶向下+缓存),它是Donald Michie 在1968年创造的术语,表示一种优化技术,在top-down 形式的程序中,使用缓存来避免重复计算,从而达到加速的目的。 +其实,递归+缓存,就是 memoization。所谓\textbf{memoization}(翻译为备忘录法,见第 \S \ref{sec:dp-vs-memoization}节),就是"top-down with cache"(自顶向下+缓存),它是Donald Michie 在1968年创造的术语,表示一种优化技术,在top-down 形式的程序中,使用缓存来避免重复计算,从而达到加速的目的。 -\textbf{memorization 不一定用递归},就像深搜不一定用递归一样,可以在迭代(iterative)中使用 memorization 。\textbf{递归也不一定用 memorization},可以用memorization来加速,但不是必须的。只有当递归使用了缓存,它才是 memorization 。 +\textbf{memoization 不一定用递归},就像深搜不一定用递归一样,可以在迭代(iterative)中使用 memoization 。\textbf{递归也不一定用 memoization},可以用memoization来加速,但不是必须的。只有当递归使用了缓存,它才是 memoization 。 既然递归一定是深搜,为什么很多书籍都同时使用这两个术语呢?在递归味道更浓的地方,一般用递归这个术语,在深搜更浓的场景下,用深搜这个术语,读者心里要弄清楚他俩大部分时候是一回事。在单链表、二叉树等递归数据结构上,递归的味道更浓,这时用递归这个术语;在图、隐式图等数据结构上,深搜的味道更浓,这时用深搜这个术语。 diff --git a/C++/chapDivideAndConquer.tex b/C++/chapDivideAndConquer.tex index 0876f43f..9cf03559 100644 --- a/C++/chapDivideAndConquer.tex +++ b/C++/chapDivideAndConquer.tex @@ -20,7 +20,7 @@ \subsubsection{代码} // 时间复杂度O(logn),空间复杂度O(1) class Solution { public: - double pow(double x, int n) { + double myPow(double x, int n) { if (n < 0) return 1.0 / power(x, -n); else return power(x, n); } @@ -62,7 +62,7 @@ \subsubsection{代码} // 时间复杂度O(logn),空间复杂度O(1) class Solution { public: - int sqrt(int x) { + int mySqrt(int x) { int left = 1, right = x / 2; int last_mid; // 记录最近一次mid diff --git a/C++/chapDynamicProgramming.tex b/C++/chapDynamicProgramming.tex index a6ec1d08..5327de51 100644 --- a/C++/chapDynamicProgramming.tex +++ b/C++/chapDynamicProgramming.tex @@ -25,7 +25,7 @@ \subsubsection{描述} \subsubsection{分析} 设状态为$f(i, j)$,表示从从位置$(i,j)$出发,路径的最小和,则状态转移方程为 $$ -f(i,j)=\min\left\{f(i,j+1),f(i+1,j+1)\right\}+(i,j) +f(i,j)=\min\left\{f(i+1,j),f(i+1,j+1)\right\}+(i,j) $$ @@ -101,10 +101,10 @@ \subsubsection{动规} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: - int maxSubArray(int A[], int n) { + int maxSubArray(vector& nums) { int result = INT_MIN, f = 0; - for (int i = 0; i < n; ++i) { - f = max(f + A[i], A[i]); + for (int i = 0; i < nums.size(); ++i) { + f = max(f + nums[i], nums[i]); result = max(result, f); } return result; @@ -119,22 +119,24 @@ \subsubsection{思路5} // 时间复杂度O(n),空间复杂度O(n) class Solution { public: - int maxSubArray(int A[], int n) { - return mcss(A, n); + int maxSubArray(vector& A) { + return mcss(A.begin(), A.end()); } private: // 思路5,求最大连续子序列和 - static int mcss(int A[], int n) { - int i, result, cur_min; + template + static int mcss(Iter begin, Iter end) { + int result, cur_min; + const int n = distance(begin, end); int *sum = new int[n + 1]; // 前n项和 sum[0] = 0; result = INT_MIN; cur_min = sum[0]; - for (i = 1; i <= n; i++) { - sum[i] = sum[i - 1] + A[i - 1]; + for (int i = 1; i <= n; i++) { + sum[i] = sum[i - 1] + *(begin + i - 1); } - for (i = 1; i <= n; i++) { + for (int i = 1; i <= n; i++) { result = max(result, sum[i] - cur_min); cur_min = min(cur_min, sum[i]); } @@ -191,7 +193,7 @@ \subsubsection{代码} // 时间复杂度O(n^2),空间复杂度O(n^2) class Solution { public: - int minCut(string s) { + int minCut(const string& s) { const int n = s.size(); int f[n+1]; bool p[n][n]; @@ -369,7 +371,7 @@ \subsubsection{递归} // 递归,会超时,仅用来帮助理解 class Solution { public: - bool isInterleave(string s1, string s2, string s3) { + bool isInterleave(const string& s1, const string& s2, const string& s3) { if (s3.length() != s1.length() + s2.length()) return false; @@ -400,7 +402,7 @@ \subsubsection{动规} // 二维动规,时间复杂度O(n^2),空间复杂度O(n^2) class Solution { public: - bool isInterleave(string s1, string s2, string s3) { + bool isInterleave(const string& s1, const string& s2, const string& s3) { if (s3.length() != s1.length() + s2.length()) return false; @@ -430,7 +432,7 @@ \subsubsection{动规+滚动数组} // 二维动规+滚动数组,时间复杂度O(n^2),空间复杂度O(n) class Solution { public: - bool isInterleave(string s1, string s2, string s3) { + bool isInterleave(const string& s1, const string& s2, const string& s3) { if (s1.length() + s2.length() != s3.length()) return false; @@ -512,7 +514,7 @@ \subsubsection{描述} \subsubsection{分析} -首先想到的是递归(即深搜),对两个string进行分割,然后比较四对字符串。代码虽然简单,但是复杂度比较高。有两种加速策略,一种是剪枝,提前返回;一种是加缓存,缓存中间结果,即memorization(翻译为记忆化搜索)。 +首先想到的是递归(即深搜),对两个string进行分割,然后比较四对字符串。代码虽然简单,但是复杂度比较高。有两种加速策略,一种是剪枝,提前返回;一种是加缓存,缓存中间结果,即memoization(翻译为记忆化搜索)。 剪枝可以五花八门,要充分观察,充分利用信息,找到能让节点提前返回的条件。例如,判断两个字符串是否互为scamble,至少要求每个字符在两个字符串中出现的次数要相等,如果不相等则返回false。 @@ -528,12 +530,12 @@ \subsubsection{分析} \subsubsection{递归} \begin{Code} -// LeetCode, Interleaving String +// LeetCode, Scramble String // 递归,会超时,仅用来帮助理解 // 时间复杂度O(n^6),空间复杂度O(1) class Solution { public: - bool isScramble(string s1, string s2) { + bool isScramble(const string& s1, const string& s2) { return isScramble(s1.begin(), s1.end(), s2.begin()); } private: @@ -559,11 +561,11 @@ \subsubsection{递归} \subsubsection{动规} \begin{Code} -// LeetCode, Interleaving String +// LeetCode, Scramble String // 动规,时间复杂度O(n^3),空间复杂度O(n^3) class Solution { public: - bool isScramble(string s1, string s2) { + bool isScramble(const string& s1, const string& s2) { const int N = s1.size(); if (N != s2.size()) return false; @@ -597,16 +599,16 @@ \subsubsection{动规} \subsubsection{递归+剪枝} \begin{Code} -// LeetCode, Interleaving String +// LeetCode, Scramble String // 递归+剪枝 // 时间复杂度O(n^6),空间复杂度O(1) class Solution { public: - bool isScramble(string s1, string s2) { + bool isScramble(const string& s1, const string& s2) { return isScramble(s1.begin(), s1.end(), s2.begin()); } private: - typedef string::iterator Iterator; + typedef string::const_iterator Iterator; bool isScramble(Iterator first1, Iterator last1, Iterator first2) { auto length = distance(first1, last1); auto last2 = next(first2, length); @@ -634,12 +636,12 @@ \subsubsection{递归+剪枝} \subsubsection{备忘录法} \begin{Code} -// LeetCode, Interleaving String +// LeetCode, Scramble String // 递归+map做cache -// 时间复杂度O(n^3),空间复杂度O(n^3) +// 时间复杂度O(n^3),空间复杂度O(n^3), TLE class Solution { public: - bool isScramble(string s1, string s2) { + bool isScramble(const string& s1, const string& s2) { cache.clear(); return isScramble(s1.begin(), s1.end(), s2.begin()); } @@ -694,14 +696,14 @@ \subsubsection{备忘录法} }; } -// LeetCode, Interleaving String +// LeetCode, Scramble String // 递归+unordered_map做cache,比map快 // 时间复杂度O(n^3),空间复杂度O(n^3) class Solution { public: unordered_map cache; - bool isScramble(string s1, string s2) { + bool isScramble(const string& s1, const string& s2) { cache.clear(); return isScramble(s1.begin(), s1.end(), s2.begin()); } diff --git a/C++/chapGreedy.tex b/C++/chapGreedy.tex index 86b67e3a..2e098bea 100644 --- a/C++/chapGreedy.tex +++ b/C++/chapGreedy.tex @@ -38,11 +38,11 @@ \subsubsection{代码1} // 思路1,时间复杂度O(n),空间复杂度O(1) class Solution { public: - bool canJump(int A[], int n) { + bool canJump(const vector& nums) { int reach = 1; // 最右能跳到哪里 - for (int i = 0; i < reach && reach < n; ++i) - reach = max(reach, i + 1 + A[i]); - return reach >= n; + for (int i = 0; i < reach && reach < nums.size(); ++i) + reach = max(reach, i + 1 + nums[i]); + return reach >= nums.size(); } }; \end{Code} @@ -54,13 +54,13 @@ \subsubsection{代码2} // 思路2,时间复杂度O(n),空间复杂度O(1) class Solution { public: - bool canJump (int A[], int n) { - if (n == 0) return true; + bool canJump (const vector& nums) { + if (nums.empty()) return true; // 逆向下楼梯,最左能下降到第几层 - int left_most = n - 1; + int left_most = nums.size() - 1; - for (int i = n - 2; i >= 0; --i) - if (i + A[i] >= left_most) + for (int i = nums.size() - 2; i >= 0; --i) + if (i + nums[i] >= left_most) left_most = i; return left_most == 0; @@ -75,14 +75,14 @@ \subsubsection{代码3} // 思路三,动规,时间复杂度O(n),空间复杂度O(n) class Solution { public: - bool canJump(int A[], int n) { - vector f(n, 0); + bool canJump(const vector& nums) { + vector f(nums.size(), 0); f[0] = 0; - for (int i = 1; i < n; i++) { - f[i] = max(f[i - 1], A[i - 1]) - 1; + for (int i = 1; i < nums.size(); i++) { + f[i] = max(f[i - 1], nums[i - 1]) - 1; if (f[i] < 0) return false;; } - return f[n - 1] >= 0; + return f[nums.size() - 1] >= 0; } }; \end{Code} @@ -121,18 +121,18 @@ \subsubsection{代码1} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: - int jump(int A[], int n) { + int jump(const vector& nums) { int step = 0; // 最小步数 int left = 0; int right = 0; // [left, right]是当前能覆盖的区间 - if (n == 1) return 0; + if (nums.size() == 1) return 0; while (left <= right) { // 尝试从每一层跳最远 ++step; const int old_right = right; for (int i = left; i <= old_right; ++i) { - int new_right = i + A[i]; - if (new_right >= n - 1) return step; + int new_right = i + nums[i]; + if (new_right >= nums.size() - 1) return step; if (new_right > right) right = new_right; } @@ -150,18 +150,18 @@ \subsubsection{代码2} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: - int jump(int A[], int n) { + int jump(const vector& nums) { int result = 0; // the maximum distance that has been reached int last = 0; // the maximum distance that can be reached by using "ret+1" steps int cur = 0; - for (int i = 0; i < n; ++i) { + for (int i = 0; i < nums.size(); ++i) { if (i > last) { last = cur; ++result; } - cur = max(cur, i + A[i]); + cur = max(cur, i + nums[i]); } return result; @@ -282,21 +282,22 @@ \subsubsection{代码} \begin{Code} // LeetCode, Longest Substring Without Repeating Characters // 时间复杂度O(n),空间复杂度O(1) +// 考虑非字母的情况 class Solution { public: int lengthOfLongestSubstring(string s) { - const int ASCII_MAX = 26; + const int ASCII_MAX = 255; int last[ASCII_MAX]; // 记录字符上次出现过的位置 int start = 0; // 记录当前子串的起始位置 fill(last, last + ASCII_MAX, -1); // 0也是有效位置,因此初始化为-1 int max_len = 0; for (int i = 0; i < s.size(); i++) { - if (last[s[i] - 'a'] >= start) { + if (last[s[i]] >= start) { max_len = max(i - start, max_len); - start = last[s[i] - 'a'] + 1; + start = last[s[i]] + 1; } - last[s[i] - 'a'] = i; + last[s[i]] = i; } return max((int)s.size() - start, max_len); // 别忘了最后一次,例如"abcd" } diff --git a/C++/chapImplement.tex b/C++/chapImplement.tex index 6c0aeab8..40b88a2b 100644 --- a/C++/chapImplement.tex +++ b/C++/chapImplement.tex @@ -33,15 +33,26 @@ \subsubsection{代码} \begin{Code} //LeetCode, Reverse Integer // 时间复杂度O(logn),空间复杂度O(1) +// 考虑 1.负数的情况 2. 溢出的情况(正溢出&&负溢出,比如 x = -2147483648(即-2^31) ) class Solution { public: int reverse (int x) { - int r = 0; - - for (; x; x /= 10) - r = r * 10 + x % 10; - - return r; + long long r = 0; + long long t = x; + t = t > 0 ? t : -t; + for (; t; t /= 10) + r = r * 10 + t % 10; + + bool sign = x > 0 ? false: true; + if (r > 2147483647 || (sign && r > 2147483648)) { + return 0; + } else { + if (sign) { + return -r; + } else { + return r; + } + } } }; \end{Code} @@ -545,7 +556,7 @@ \subsubsection{相关题目} \section{Pascal's Triangle} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:pascals-triangle} +\label{sec:pascal-s-triangle} \subsubsection{描述} @@ -627,7 +638,7 @@ \subsubsection{相关题目} \section{Pascal's Triangle II} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:pascals-triangle-ii} +\label{sec:pascal-s-triangle-ii} \subsubsection{描述} @@ -1087,7 +1098,7 @@ \subsubsection{相关题目} \section{Max Points on a Line} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:Max-Points-on-a-Line} +\label{sec:max-points-on-a-line} \subsubsection{描述} diff --git a/C++/chapLinearList.tex b/C++/chapLinearList.tex index c23c4538..bbc0ec1c 100644 --- a/C++/chapLinearList.tex +++ b/C++/chapLinearList.tex @@ -29,13 +29,13 @@ \subsubsection{代码1} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: - int removeDuplicates(int A[], int n) { - if (n == 0) return 0; + int removeDuplicates(vector& nums) { + if (nums.empty()) return 0; int index = 0; - for (int i = 1; i < n; i++) { - if (A[index] != A[i]) - A[++index] = A[i]; + for (int i = 1; i < nums.size(); i++) { + if (nums[index] != nums[i]) + nums[++index] = nums[i]; } return index + 1; } @@ -49,8 +49,8 @@ \subsubsection{代码2} // 使用STL,时间复杂度O(n),空间复杂度O(1) class Solution { public: - int removeDuplicates(int A[], int n) { - return distance(A, unique(A, A + n)); + int removeDuplicates(vector& nums) { + return distance(nums.begin(), unique(nums.begin(), nums.end())); } }; \end{Code} @@ -62,8 +62,8 @@ \subsubsection{代码3} // 使用STL,时间复杂度O(n),空间复杂度O(1) class Solution { public: - int removeDuplicates(int A[], int n) { - return removeDuplicates(A, A + n, A) - A; + int removeDuplicates(vector& nums) { + return distance(nums.begin(), removeDuplicates(nums.begin(), nums.end(), nums.begin())); } template @@ -111,13 +111,13 @@ \subsubsection{代码1} // @author hex108 (https://github.com/hex108) class Solution { public: - int removeDuplicates(int A[], int n) { - if (n <= 2) return n; + int removeDuplicates(vector& nums) { + if (nums.size() <= 2) return nums.size(); int index = 2; - for (int i = 2; i < n; i++){ - if (A[i] != A[index - 2]) - A[index++] = A[i]; + for (int i = 2; i < nums.size(); i++){ + if (nums[i] != nums[index - 2]) + nums[index++] = nums[i]; } return index; @@ -134,13 +134,14 @@ \subsubsection{代码2} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: - int removeDuplicates(int A[], int n) { + int removeDuplicates(vector& nums) { + const int n = nums.size(); int index = 0; for (int i = 0; i < n; ++i) { - if (i > 0 && i < n - 1 && A[i] == A[i - 1] && A[i] == A[i + 1]) + if (i > 0 && i < n - 1 && nums[i] == nums[i - 1] && nums[i] == nums[i + 1]) continue; - A[index++] = A[i]; + nums[index++] = nums[i]; } return index; } @@ -179,19 +180,19 @@ \subsubsection{代码} // 时间复杂度O(log n),空间复杂度O(1) class Solution { public: - int search(int A[], int n, int target) { - int first = 0, last = n; + int search(const vector& nums, int target) { + int first = 0, last = nums.size(); while (first != last) { const int mid = first + (last - first) / 2; - if (A[mid] == target) + if (nums[mid] == target) return mid; - if (A[first] <= A[mid]) { - if (A[first] <= target && target < A[mid]) + if (nums[first] <= nums[mid]) { + if (nums[first] <= target && target < nums[mid]) last = mid; else first = mid + 1; } else { - if (A[mid] < target && target <= A[last-1]) + if (nums[mid] < target && target <= nums[last-1]) first = mid + 1; else last = mid; @@ -237,19 +238,19 @@ \subsubsection{代码} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: - bool search(int A[], int n, int target) { - int first = 0, last = n; + bool search(const vector& nums, int target) { + int first = 0, last = nums.size(); while (first != last) { const int mid = first + (last - first) / 2; - if (A[mid] == target) + if (nums[mid] == target) return true; - if (A[first] < A[mid]) { - if (A[first] <= target && target < A[mid]) + if (nums[first] < nums[mid]) { + if (nums[first] <= target && target < nums[mid]) last = mid; else first = mid + 1; - } else if (A[first] > A[mid]) { - if (A[mid] < target && target <= A[last-1]) + } else if (nums[first] > nums[mid]) { + if (nums[mid] < target && target <= nums[last-1]) first = mid + 1; else last = mid; @@ -283,7 +284,7 @@ \subsubsection{分析} $O(m+n)$的解法比较直观,直接merge两个数组,然后求第$k$大的元素。 -不过我们仅仅需要第$k$大的元素,是不需要“排序”这么复杂的操作的。可以用一个计数器,记录当前已经找到第$m$大的元素了。同时我们使用两个指针\fn{pA}和\fn{pB},分别指向A和B数组的第一个元素,使用类似于merge sort的原理,如果数组A当前元素小,那么\fn{pA++},同时\fn{m++};如果数组B当前元素小,那么\fn{pB++},同时\fn{m++}。最终当$m$等于$k$的时候,就得到了我们的答案,$O(k)$时间,$O(1)$空间。但是,当$k$很接近$m+n$的时候,这个方法还是$O(m+n)$的。 +不过我们仅仅需要第$k$大的元素,是不需要“排序”这么昂贵的操作的。可以用一个计数器,记录当前已经找到第$m$大的元素了。同时我们使用两个指针\fn{pA}和\fn{pB},分别指向A和B数组的第一个元素,使用类似于merge sort的原理,如果数组A当前元素小,那么\fn{pA++},同时\fn{m++};如果数组B当前元素小,那么\fn{pB++},同时\fn{m++}。最终当$m$等于$k$的时候,就得到了我们的答案,$O(k)$时间,$O(1)$空间。但是,当$k$很接近$m+n$的时候,这个方法还是$O(m+n)$的。 有没有更好的方案呢?我们可以考虑从$k$入手。如果我们每次都能够删除一个一定在第$k$大元素之前的元素,那么我们需要进行$k$次。但是如果每次我们都删除一半呢?由于A和B都是有序的,我们应该充分利用这里面的信息,类似于二分查找,也是充分利用了“有序”。 @@ -294,7 +295,7 @@ \subsubsection{分析} \item \fn{A[k/2-1] < B[k/2-1]} \myenddot -如果\fn{A[k/2-1] < B[k/2-1]},意味着\fn{A[0]}到\fn{A[k/2-1}的肯定在$A \cup B$的top k元素的范围内,换句话说,\fn{A[k/2-1}不可能大于$A \cup B$的第$k$大元素。留给读者证明。 +如果\fn{A[k/2-1] < B[k/2-1]},意味着\fn{A[0]}到\fn{A[k/2-1]}的肯定在$A \cup B$的top k元素的范围内,换句话说,\fn{A[k/2-1]}不可能大于$A \cup B$的第$k$大元素。留给读者证明。 因此,我们可以放心的删除A数组的这$k/2$个元素。同理,当\fn{A[k/2-1] > B[k/2-1]}时,可以删除B数组的$k/2$个元素。 @@ -314,26 +315,29 @@ \subsubsection{代码} // 时间复杂度O(log(m+n)),空间复杂度O(log(m+n)) class Solution { public: - double findMedianSortedArrays(int A[], int m, int B[], int n) { + double findMedianSortedArrays(const vector& A, const vector& B) { + const int m = A.size(); + const int n = B.size(); int total = m + n; if (total & 0x1) - return find_kth(A, m, B, n, total / 2 + 1); + return find_kth(A.begin(), m, B.begin(), n, total / 2 + 1); else - return (find_kth(A, m, B, n, total / 2) - + find_kth(A, m, B, n, total / 2 + 1)) / 2.0; + return (find_kth(A.begin(), m, B.begin(), n, total / 2) + + find_kth(A.begin(), m, B.begin(), n, total / 2 + 1)) / 2.0; } private: - static int find_kth(int A[], int m, int B[], int n, int k) { + static int find_kth(std::vector::const_iterator A, int m, + std::vector::const_iterator B, int n, int k) { //always assume that m is equal or smaller than n if (m > n) return find_kth(B, n, A, m, k); - if (m == 0) return B[k - 1]; - if (k == 1) return min(A[0], B[0]); + if (m == 0) return *(B + k - 1); + if (k == 1) return min(*A, *B); //divide k into two parts int ia = min(k / 2, m), ib = k - ia; - if (A[ia - 1] < B[ib - 1]) + if (*(A + ia - 1) < *(B + ib - 1)) return find_kth(A + ia, m - ia, B, n, k - ia); - else if (A[ia - 1] > B[ib - 1]) + else if (*(A + ia - 1) > *(B + ib - 1)) return find_kth(A, m, B + ib, n - ib, k - ib); else return A[ia - 1]; @@ -377,14 +381,14 @@ \subsubsection{代码} // 时间复杂度O(n),空间复杂度O(n) class Solution { public: - int longestConsecutive(const vector &num) { + int longestConsecutive(const vector &nums) { unordered_map used; - for (auto i : num) used[i] = false; + for (auto i : nums) used[i] = false; int longest = 0; - for (auto i : num) { + for (auto i : nums) { if (used[i]) continue; int length = 1; @@ -422,18 +426,18 @@ \subsubsection{代码} // Author: @advancedxy class Solution { public: - int longestConsecutive(vector &num) { + int longestConsecutive(vector &nums) { unordered_map map; - int size = num.size(); + int size = nums.size(); int l = 1; for (int i = 0; i < size; i++) { - if (map.find(num[i]) != map.end()) continue; - map[num[i]] = 1; - if (map.find(num[i] - 1) != map.end()) { - l = max(l, mergeCluster(map, num[i] - 1, num[i])); + if (map.find(nums[i]) != map.end()) continue; + map[nums[i]] = 1; + if (map.find(nums[i] - 1) != map.end()) { + l = max(l, mergeCluster(map, nums[i] - 1, nums[i])); } - if (map.find(num[i] + 1) != map.end()) { - l = max(l, mergeCluster(map, num[i], num[i] + 1)); + if (map.find(nums[i] + 1) != map.end()) { + l = max(l, mergeCluster(map, nums[i], nums[i] + 1)); } } return size == 0 ? 0 : l; @@ -488,14 +492,14 @@ \subsubsection{代码} // 时间复杂度O(n),空间复杂度O(n) class Solution { public: - vector twoSum(vector &num, int target) { + vector twoSum(vector &nums, int target) { unordered_map mapping; vector result; - for (int i = 0; i < num.size(); i++) { - mapping[num[i]] = i; + for (int i = 0; i < nums.size(); i++) { + mapping[nums[i]] = i; } - for (int i = 0; i < num.size(); i++) { - const int gap = target - num[i]; + for (int i = 0; i < nums.size(); i++) { + const int gap = target - nums[i]; if (mapping.find(gap) != mapping.end() && mapping[gap] > i) { result.push_back(i + 1); result.push_back(mapping[gap] + 1); @@ -550,16 +554,16 @@ \subsubsection{代码} // 先排序,然后左右夹逼,注意跳过重复的数,时间复杂度O(n^2),空间复杂度O(1) class Solution { public: - vector> threeSum(vector& num) { + vector> threeSum(vector& nums) { vector> result; - if (num.size() < 3) return result; - sort(num.begin(), num.end()); + if (nums.size() < 3) return result; + sort(nums.begin(), nums.end()); const int target = 0; - auto last = num.end(); - for (auto i = num.begin(); i < last-2; ++i) { + auto last = nums.end(); + for (auto i = nums.begin(); i < last-2; ++i) { auto j = i+1; - if (i > num.begin() && *i == *(i-1)) continue; + if (i > nums.begin() && *i == *(i-1)) continue; auto k = last-1; while (j < k) { if (*i + *j + *k < target) { @@ -611,15 +615,15 @@ \subsubsection{代码} // 先排序,然后左右夹逼,时间复杂度O(n^2),空间复杂度O(1) class Solution { public: - int threeSumClosest(vector& num, int target) { + int threeSumClosest(vector& nums, int target) { int result = 0; int min_gap = INT_MAX; - sort(num.begin(), num.end()); + sort(nums.begin(), nums.end()); - for (auto a = num.begin(); a != prev(num.end(), 2); ++a) { + for (auto a = nums.begin(); a != prev(nums.end(), 2); ++a) { auto b = next(a); - auto c = prev(num.end()); + auto c = prev(nums.end()); while (b < c) { const int sum = *a + *b + *c; @@ -684,13 +688,13 @@ \subsubsection{左右夹逼} // 先排序,然后左右夹逼,时间复杂度O(n^3),空间复杂度O(1) class Solution { public: - vector> fourSum(vector& num, int target) { + vector> fourSum(vector& nums, int target) { vector> result; - if (num.size() < 4) return result; - sort(num.begin(), num.end()); + if (nums.size() < 4) return result; + sort(nums.begin(), nums.end()); - auto last = num.end(); - for (auto a = num.begin(); a < prev(last, 3); ++a) { + auto last = nums.end(); + for (auto a = nums.begin(); a < prev(last, 3); ++a) { for (auto b = next(a); b < prev(last, 2); ++b) { auto c = next(b); auto d = prev(last); @@ -722,21 +726,21 @@ \subsubsection{map做缓存} // 时间复杂度,平均O(n^2),最坏O(n^4),空间复杂度O(n^2) class Solution { public: - vector > fourSum(vector &num, int target) { + vector > fourSum(vector &nums, int target) { vector> result; - if (num.size() < 4) return result; - sort(num.begin(), num.end()); + if (nums.size() < 4) return result; + sort(nums.begin(), nums.end()); unordered_map > > cache; - for (size_t a = 0; a < num.size(); ++a) { - for (size_t b = a + 1; b < num.size(); ++b) { - cache[num[a] + num[b]].push_back(pair(a, b)); + for (size_t a = 0; a < nums.size(); ++a) { + for (size_t b = a + 1; b < nums.size(); ++b) { + cache[nums[a] + nums[b]].push_back(pair(a, b)); } } - for (int c = 0; c < num.size(); ++c) { - for (size_t d = c + 1; d < num.size(); ++d) { - const int key = target - num[c] - num[d]; + for (int c = 0; c < nums.size(); ++c) { + for (size_t d = c + 1; d < nums.size(); ++d) { + const int key = target - nums[c] - nums[d]; if (cache.find(key) == cache.end()) continue; const auto& vec = cache[key]; @@ -744,8 +748,8 @@ \subsubsection{map做缓存} if (c <= vec[k].second) continue; // 有重叠 - result.push_back( { num[vec[k].first], - num[vec[k].second], num[c], num[d] }); + result.push_back( { nums[vec[k].first], + nums[vec[k].second], nums[c], nums[d] }); } } } @@ -765,15 +769,15 @@ \subsubsection{multimap} // @author 龚陆安(http://weibo.com/luangong) class Solution { public: - vector> fourSum(vector& num, int target) { + vector> fourSum(vector& nums, int target) { vector> result; - if (num.size() < 4) return result; - sort(num.begin(), num.end()); + if (nums.size() < 4) return result; + sort(nums.begin(), nums.end()); unordered_multimap> cache; - for (int i = 0; i + 1 < num.size(); ++i) - for (int j = i + 1; j < num.size(); ++j) - cache.insert(make_pair(num[i] + num[j], make_pair(i, j))); + for (int i = 0; i + 1 < nums.size(); ++i) + for (int j = i + 1; j < nums.size(); ++j) + cache.insert(make_pair(nums[i] + nums[j], make_pair(i, j))); for (auto i = cache.begin(); i != cache.end(); ++i) { int x = target - i->first; @@ -784,7 +788,7 @@ \subsubsection{multimap} auto c = j->second.first; auto d = j->second.second; if (a != c && a != d && b != c && b != d) { - vector vec = { num[a], num[b], num[c], num[d] }; + vector vec = { nums[a], nums[b], nums[c], nums[d] }; sort(vec.begin(), vec.end()); result.push_back(vec); } @@ -805,13 +809,13 @@ \subsubsection{方法4} // 跟方法1相比,表面上优化了,实际上更慢了,切记! class Solution { public: - vector> fourSum(vector& num, int target) { + vector> fourSum(vector& nums, int target) { vector> result; - if (num.size() < 4) return result; - sort(num.begin(), num.end()); + if (nums.size() < 4) return result; + sort(nums.begin(), nums.end()); - auto last = num.end(); - for (auto a = num.begin(); a < prev(last, 3); + auto last = nums.end(); + for (auto a = nums.begin(); a < prev(last, 3); a = upper_bound(a, prev(last, 3), *a)) { for (auto b = next(a); b < prev(last, 2); b = upper_bound(b, prev(last, 2), *b)) { @@ -864,11 +868,11 @@ \subsubsection{代码1} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: - int removeElement(int A[], int n, int elem) { + int removeElement(vector& nums, int target) { int index = 0; - for (int i = 0; i < n; ++i) { - if (A[i] != elem) { - A[index++] = A[i]; + for (int i = 0; i < nums.size(); ++i) { + if (nums[i] != target) { + nums[index++] = nums[i]; } } return index; @@ -883,8 +887,8 @@ \subsubsection{代码2} // 使用remove(),时间复杂度O(n),空间复杂度O(1) class Solution { public: - int removeElement(int A[], int n, int elem) { - return distance(A, remove(A, A+n, elem)); + int removeElement(vector& nums, int target) { + return distance(nums.begin(), remove(nums.begin(), nums.end(), target)); } }; \end{Code} @@ -930,8 +934,8 @@ \subsubsection{代码} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: - void nextPermutation(vector &num) { - next_permutation(num.begin(), num.end()); + void nextPermutation(vector &nums) { + next_permutation(nums.begin(), nums.end()); } template @@ -1208,7 +1212,8 @@ \subsubsection{代码1} // 思路1,时间复杂度O(n),空间复杂度O(n) class Solution { public: - int trap(int A[], int n) { + int trap(const vector& A) { + const int n = A.size(); int *max_left = new int[n](); int *max_right = new int[n](); @@ -1240,7 +1245,8 @@ \subsubsection{代码2} // 思路2,时间复杂度O(n),空间复杂度O(1) class Solution { public: - int trap(int A[], int n) { + int trap(const vector& A) { + const int n = A.size(); int max = 0; // 最高的柱子,将数组分为两半 for (int i = 0; i < n; i++) if (A[i] > A[max]) max = i; @@ -1267,7 +1273,8 @@ \subsubsection{代码3} // 时间复杂度O(n),空间复杂度O(n) class Solution { public: - int trap(int a[], int n) { + int trap(const vector& A) { + const int n = A.size(); stack> s; int water = 0; @@ -1277,17 +1284,17 @@ \subsubsection{代码3} while (!s.empty()) { // 将栈里比当前元素矮或等高的元素全部处理掉 int bar = s.top().first; int pos = s.top().second; - // bar, height, a[i] 三者夹成的凹陷 - water += (min(bar, a[i]) - height) * (i - pos - 1); + // bar, height, A[i] 三者夹成的凹陷 + water += (min(bar, A[i]) - height) * (i - pos - 1); height = bar; - if (a[i] < bar) // 碰到了比当前元素高的,跳出循环 + if (A[i] < bar) // 碰到了比当前元素高的,跳出循环 break; else s.pop(); // 弹出栈顶,因为该元素处理完了,不再需要了 } - s.push(make_pair(a[i], i)); + s.push(make_pair(A[i], i)); } return water; @@ -1879,10 +1886,11 @@ \subsubsection{代码1} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: - int singleNumber(int A[], int n) { + int singleNumber(vector& nums) { int x = 0; - for (size_t i = 0; i < n; ++i) - x ^= A[i]; + for (auto i : nums) { + x ^= i; + } return x; } }; @@ -1895,8 +1903,8 @@ \subsubsection{代码2} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: - int singleNumber(int A[], int n) { - return accumulate(A, A + n, 0, bit_xor()); + int singleNumber(vector& nums) { + return accumulate(nums.begin(), nums.end(), 0, bit_xor()); } }; \end{Code} @@ -1932,13 +1940,13 @@ \subsubsection{代码1} // 方法1,时间复杂度O(n),空间复杂度O(1) class Solution { public: - int singleNumber(int A[], int n) { + int singleNumber(vector& nums) { const int W = sizeof(int) * 8; // 一个整数的bit数,即整数字长 int count[W]; // count[i]表示在在i位出现的1的次数 fill_n(&count[0], W, 0); - for (int i = 0; i < n; i++) { + for (int i = 0; i < nums.size(); i++) { for (int j = 0; j < W; j++) { - count[j] += (A[i] >> j) & 1; + count[j] += (nums[i] >> j) & 1; count[j] %= 3; } } @@ -1958,11 +1966,11 @@ \subsubsection{代码2} // 方法2,时间复杂度O(n),空间复杂度O(1) class Solution { public: - int singleNumber(int A[], int n) { + int singleNumber(vector& nums) { int one = 0, two = 0, three = 0; - for (int i = 0; i < n; ++i) { - two |= (one & A[i]); - one ^= A[i]; + for (auto i : nums) { + two |= (one & i); + one ^= i; three = ~(one & two); one &= three; two &= three; @@ -2217,7 +2225,7 @@ \subsubsection{迭代版} ListNode *deleteDuplicates(ListNode *head) { if (head == nullptr) return nullptr; - for (ListNode *prev = head, *cur = head->next; cur; cur = cur->next) { + for (ListNode *prev = head, *cur = head->next; cur; cur = prev->next) { if (prev->val == cur->val) { prev->next = cur->next; delete cur; @@ -2658,7 +2666,7 @@ \subsubsection{相关题目} \subsection{Linked List Cycle} -\label{sec:Linked-List-Cycle} +\label{sec:linked-list-cycle} \subsubsection{描述} @@ -2701,7 +2709,7 @@ \subsubsection{相关题目} \subsection{Linked List Cycle II} -\label{sec:Linked-List-Cycle-II} +\label{sec:linked-list-cycle-ii} \subsubsection{描述} @@ -2761,7 +2769,7 @@ \subsubsection{相关题目} \subsection{Reorder List} -\label{sec:Reorder-List} +\label{sec:reorder-list} \subsubsection{描述} @@ -2833,7 +2841,7 @@ \subsubsection{相关题目} \subsection{LRU Cache} -\label{sec:LRU-Cachet} +\label{sec:lru-cache} \subsubsection{描述} diff --git a/C++/chapSearching.tex b/C++/chapSearching.tex index e008d75b..e94d3c38 100644 --- a/C++/chapSearching.tex +++ b/C++/chapSearching.tex @@ -28,10 +28,10 @@ \subsubsection{使用STL} // 时间复杂度O(logn),空间复杂度O(1) class Solution { public: - vector searchRange(int A[], int n, int target) { - const int l = distance(A, lower_bound(A, A + n, target)); - const int u = distance(A, prev(upper_bound(A, A + n, target))); - if (A[l] != target) // not found + vector searchRange(vector& nums, int target) { + const int l = distance(nums.begin(), lower_bound(nums.begin(), nums.end(), target)); + const int u = distance(nums.begin(), prev(upper_bound(nums.begin(), nums.end(), target))); + if (nums[l] != target) // not found return vector { -1, -1 }; else return vector { l, u }; @@ -47,14 +47,14 @@ \subsubsection{重新实现 lower_bound 和 upper_bound} // 时间复杂度O(logn),空间复杂度O(1) class Solution { public: - vector searchRange (int A[], int n, int target) { - auto lower = lower_bound(A, A + n, target); - auto uppper = upper_bound(lower, A + n, target); + vector searchRange (vector& nums, int target) { + auto lower = lower_bound(nums.begin(), nums.end(), target); + auto uppper = upper_bound(lower, nums.end(), target); - if (lower == A + n || *lower != target) + if (lower == nums.end() || *lower != target) return vector { -1, -1 }; else - return vector {distance(A, lower), distance(A, prev(uppper))}; + return vector {distance(nums.begin(), lower), distance(nums.begin(), prev(uppper))}; } template @@ -120,8 +120,8 @@ \subsubsection{代码} // 时间复杂度O(logn),空间复杂度O(1) class Solution { public: - int searchInsert(int A[], int n, int target) { - return lower_bound(A, A + n, target) - A; + int searchInsert(vector& nums, int target) { + return distance(nums.begin(), lower_bound(nums.begin(), nums.end(), target)); } template diff --git a/C++/chapSorting.tex b/C++/chapSorting.tex index a1192eb5..f92c115a 100644 --- a/C++/chapSorting.tex +++ b/C++/chapSorting.tex @@ -1,7 +1,7 @@ \chapter{排序} -\section{Merge Sorted Array} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:merge-sorted-array} +\section{Merge Two Sorted Arrays} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:merge-two-sorted-arrays} \subsubsection{描述} @@ -21,7 +21,7 @@ \subsubsection{代码} // 时间复杂度O(m+n),空间复杂度O(1) class Solution { public: - void merge(int A[], int m, int B[], int n) { + void merge(vector& A, int m, vector& B, int n) { int ia = m - 1, ib = n - 1, icur = m + n - 1; while(ia >= 0 && ib >= 0) { A[icur--] = A[ia] >= B[ib] ? A[ia--] : B[ib--]; @@ -100,31 +100,36 @@ \subsubsection{代码} // 时间复杂度O(n1+n2+...),空间复杂度O(1) class Solution { public: - ListNode *mergeKLists(vector &lists) { - if (lists.size() == 0) return nullptr; - ListNode *p = lists[0]; - for (int i = 1; i < lists.size(); i++) { - p = mergeTwoLists(p, lists[i]); + ListNode * mergeTwo(ListNode * l1, ListNode * l2){ + if(!l1) return l2; + if(!l2) return l1; + ListNode dummy(-1); + ListNode * p = &dummy; + for(; l1 && l2; p = p->next){ + if(l1->val > l2->val){ + p->next = l2; l2 = l2->next; + } + else{ + p->next = l1; l1 = l1->next; + } } - return p; + p->next = l1 ? l1 : l2; + return dummy.next; } - // Merge Two Sorted Lists - ListNode *mergeTwoLists(ListNode *l1, ListNode *l2) { - ListNode head(-1); - for (ListNode* p = &head; l1 != nullptr || l2 != nullptr; p = p->next) { - int val1 = l1 == nullptr ? INT_MAX : l1->val; - int val2 = l2 == nullptr ? INT_MAX : l2->val; - if (val1 <= val2) { - p->next = l1; - l1 = l1->next; - } else { - p->next = l2; - l2 = l2->next; - } + ListNode* mergeKLists(vector& lists) { + if(lists.size() == 0) return nullptr; + + // multi pass + deque dq(lists.begin(), lists.end()); + while(dq.size() > 1){ + ListNode * first = dq.front(); dq.pop_front(); + ListNode * second = dq.front(); dq.pop_front(); + dq.push_back(mergeTwo(first,second)); } - return head.next; + + return dq.front(); } }; \end{Code} @@ -138,7 +143,7 @@ \subsubsection{相关题目} \section{Insertion Sort List} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:Insertion-Sort-List} +\label{sec:insertion-sort-list} \subsubsection{描述} @@ -187,7 +192,7 @@ \subsubsection{相关题目} \section{Sort List} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:Sort-List} +\label{sec:sort-list} \subsubsection{描述} @@ -273,16 +278,17 @@ \subsubsection{代码} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: - int firstMissingPositive(int A[], int n) { - bucket_sort(A, n); - - for (int i = 0; i < n; ++i) - if (A[i] != (i + 1)) + int firstMissingPositive(vector& nums) { + bucket_sort(nums); + + for (int i = 0; i < nums.size(); ++i) + if (nums[i] != (i + 1)) return i + 1; - return n + 1; + return nums.size() + 1; } private: - static void bucket_sort(int A[], int n) { + static void bucket_sort(vector& A) { + const int n = A.size(); for (int i = 0; i < n; i++) { while (A[i] != i + 1) { if (A[i] <= 0 || A[i] > n || A[i] == A[A[i] - 1]) @@ -337,10 +343,10 @@ \subsubsection{代码1} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: - void sortColors(int A[], int n) { + void sortColors(vector& A) { int counts[3] = { 0 }; // 记录每个颜色出现的次数 - for (int i = 0; i < n; i++) + for (int i = 0; i < A.size(); i++) counts[A[i]]++; for (int i = 0, index = 0; i < 3; i++) @@ -358,9 +364,9 @@ \subsubsection{代码2} // 双指针,时间复杂度O(n),空间复杂度O(1) class Solution { public: - void sortColors(int A[], int n) { + void sortColors(vector& A) { // 一个是red的index,一个是blue的index,两边往中间走 - int red = 0, blue = n - 1; + int red = 0, blue = A.size() - 1; for (int i = 0; i < blue + 1;) { if (A[i] == 0) @@ -382,9 +388,9 @@ \subsubsection{代码3} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: - void sortColors(int A[], int n) { - partition(partition(A, A + n, bind1st(equal_to(), 0)), A + n, - bind1st(equal_to(), 1)); + void sortColors(vector& nums) { + partition(partition(nums.begin(), nums.end(), bind1st(equal_to(), 0)), + nums.end(), bind1st(equal_to(), 1)); } }; \end{Code} @@ -397,9 +403,9 @@ \subsubsection{代码4} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: - void sortColors(int A[], int n) { - partition(partition(A, A + n, bind1st(equal_to(), 0)), A + n, - bind1st(equal_to(), 1)); + void sortColors(vector& nums) { + partition(partition(nums.begin(), nums.end(), bind1st(equal_to(), 0)), + nums.end(), bind1st(equal_to(), 1)); } private: template diff --git a/C++/chapStackAndQueue.tex b/C++/chapStackAndQueue.tex index db1c1a0b..9dacb601 100644 --- a/C++/chapStackAndQueue.tex +++ b/C++/chapStackAndQueue.tex @@ -74,7 +74,7 @@ \subsubsection{使用栈} // 使用栈,时间复杂度O(n),空间复杂度O(n) class Solution { public: - int longestValidParentheses(string s) { + int longestValidParentheses(const string& s) { int max_len = 0, last = -1; // the position of the last ')' stack lefts; // keep track of the positions of non-matching '('s @@ -108,7 +108,7 @@ \subsubsection{Dynamic Programming, One Pass} // @author 一只杰森(http://weibo.com/wjson) class Solution { public: - int longestValidParentheses(string s) { + int longestValidParentheses(const string& s) { vector f(s.size(), 0); int ret = 0; for (int i = s.size() - 2; i >= 0; --i) { @@ -134,7 +134,7 @@ \subsubsection{两遍扫描} // @author 曹鹏(http://weibo.com/cpcs) class Solution { public: - int longestValidParentheses(string s) { + int longestValidParentheses(const string& s) { int answer = 0, depth = 0, start = -1; for (int i = 0; i < s.size(); ++i) { if (s[i] == '(') { @@ -242,7 +242,7 @@ \subsubsection{相关题目} \subsection{Evaluate Reverse Polish Notation} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:Evaluate-Reverse-Polish-Notation} +\label{sec:evaluate-reverse-polish-notation} \subsubsection{描述} diff --git a/C++/chapString.tex b/C++/chapString.tex index f6cf67f5..eefbe620 100644 --- a/C++/chapString.tex +++ b/C++/chapString.tex @@ -35,7 +35,7 @@ \subsubsection{代码} if (!::isalnum(*left)) ++left; else if (!::isalnum(*right)) --right; else if (*left != *right) return false; - else{ left++, right--; } + else { left++, right--; } } return true; } @@ -69,29 +69,20 @@ \subsubsection{暴力匹配} // 暴力解法,时间复杂度O(N*M),空间复杂度O(1) class Solution { public: - char *strStr(const char *haystack, const char *needle) { - // if needle is empty return the full string - if (!*needle) return (char*) haystack; - - const char *p1; - const char *p2; - const char *p1_advance = haystack; - for (p2 = &needle[1]; *p2; ++p2) { - p1_advance++; // advance p1_advance M-1 times - } - - for (p1 = haystack; *p1_advance; p1_advance++) { - char *p1_old = (char*) p1; - p2 = needle; - while (*p1 && *p2 && *p1 == *p2) { - p1++; - p2++; + int strStr(const string& haystack, const string& needle) { + if (needle.empty()) return 0; + + const int N = haystack.size() - needle.size() + 1; + for (int i = 0; i < N; i++) { + int j = i; + int k = 0; + while (j < haystack.size() && k < needle.size() && haystack[j] == needle[k]) { + j++; + k++; } - if (!*p2) return p1_old; - - p1 = p1_old + 1; + if (k == needle.size()) return i; } - return nullptr; + return -1; } }; \end{Code} @@ -103,10 +94,8 @@ \subsubsection{KMP} // KMP,时间复杂度O(N+M),空间复杂度O(M) class Solution { public: - char *strStr(const char *haystack, const char *needle) { - int pos = kmp(haystack, needle); - if (pos == -1) return nullptr; - else return (char*)haystack + pos; + int strStr(const string& haystack, const string& needle) { + return kmp(haystack.c_str(), needle.c_str()); } private: /* @@ -206,10 +195,10 @@ \subsubsection{代码} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: - int atoi(const char *str) { + int myAtoi(const string &str) { int num = 0; int sign = 1; - const int n = strlen(str); + const int n = str.length(); int i = 0; while (str[i] == ' ' && i < n) i++; @@ -325,7 +314,7 @@ \subsubsection{分析} \end{cases} $$ -思路三:Manacher’s Algorithm, 复杂度$O(n)$。详细解释见 \myurl{http://leetcode.com/2011/11/longest-palindromic-substring-part-ii.html} 。 +思路四:Manacher’s Algorithm, 复杂度$O(n)$。详细解释见 \myurl{http://leetcode.com/2011/11/longest-palindromic-substring-part-ii.html} 。 \subsubsection{备忘录法} @@ -389,7 +378,7 @@ \subsubsection{动规} // 动规,时间复杂度O(n^2),空间复杂度O(n^2) class Solution { public: - string longestPalindrome(string s) { + string longestPalindrome(const string& s) { const int n = s.size(); bool f[n][n]; fill_n(&f[0][0], n * n, false); @@ -423,7 +412,7 @@ \subsubsection{Manacher’s Algorithm} // Transform S into T. // For example, S = "abba", T = "^#a#b#b#a#$". // ^ and $ signs are sentinels appended to each end to avoid bounds checking - string preProcess(string s) { + string preProcess(const string& s) { int n = s.length(); if (n == 0) return "^$"; @@ -520,6 +509,10 @@ \subsubsection{递归版} // 递归版,时间复杂度O(n),空间复杂度O(1) class Solution { public: + bool isMatch(const string& s, const string& p) { + return isMatch(s.c_str(), p.c_str()); + } +private: bool isMatch(const char *s, const char *p) { if (*p == '\0') return *s == '\0'; @@ -596,6 +589,10 @@ \subsubsection{递归版} // 时间复杂度O(n!*m!),空间复杂度O(n) class Solution { public: + bool isMatch(const string& s, const string& p) { + return isMatch(s.c_str(), p.c_str()); + } +private: bool isMatch(const char *s, const char *p) { if (*p == '*') { while (*p == '*') ++p; //skip continuous '*' @@ -618,6 +615,10 @@ \subsubsection{迭代版} // 迭代版,时间复杂度O(n*m),空间复杂度O(1) class Solution { public: + bool isMatch(const string& s, const string& p) { + return isMatch(s.c_str(), p.c_str()); + } +private: bool isMatch(const char *s, const char *p) { bool star = false; const char *str, *ptr; @@ -751,7 +752,7 @@ \subsubsection{有限自动机} // finite automata,时间复杂度O(n),空间复杂度O(n) class Solution { public: - bool isNumber(const char *s) { + bool isNumber(const string& s) { enum InputType { INVALID, // 0 SPACE, // 1 @@ -774,17 +775,17 @@ \subsubsection{有限自动机} }; int state = 0; - for (; *s != '\0'; ++s) { + for (auto ch : s) { InputType inputType = INVALID; - if (isspace(*s)) + if (isspace(ch)) inputType = SPACE; - else if (*s == '+' || *s == '-') + else if (ch == '+' || ch == '-') inputType = SIGN; - else if (isdigit(*s)) + else if (isdigit(ch)) inputType = DIGIT; - else if (*s == '.') + else if (ch == '.') inputType = DOT; - else if (*s == 'e' || *s == 'E') + else if (ch == 'e' || ch == 'E') inputType = EXPONENT; // Get next state from current state and input symbol @@ -809,6 +810,10 @@ \subsubsection{使用strtod()} // 偷懒,直接用 strtod(),时间复杂度O(n) class Solution { public: + bool isNumber (const string& s) { + return isNumber(s.c_str()); + } +private: bool isNumber (char const* s) { char* endptr; strtod (s, &endptr); @@ -909,7 +914,7 @@ \subsubsection{代码} } } - int romanToInt(string s) { + int romanToInt(const string& s) { int result = 0; for (size_t i = 0; i < s.size(); i++) { if (i > 0 && map(s[i]) > map(s[i - 1])) { @@ -1070,7 +1075,7 @@ \subsubsection{代码} // 时间复杂度O(n),空间复杂度O(n) class Solution { public: - string simplifyPath(string const& path) { + string simplifyPath(const string& path) { vector dirs; // 当做栈 for (auto i = path.begin(); i != path.end();) { @@ -1137,10 +1142,9 @@ \subsubsection{用 STL} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: - int lengthOfLastWord(const char *s) { - const string str(s); - auto first = find_if(str.rbegin(), str.rend(), ::isalpha); - auto last = find_if_not(first, str.rend(), ::isalpha); + int lengthOfLastWord(const string& s) { + auto first = find_if(s.rbegin(), s.rend(), ::isalpha); + auto last = find_if_not(first, s.rend(), ::isalpha); return distance(first, last); } }; @@ -1154,6 +1158,10 @@ \subsubsection{顺序扫描} // 时间复杂度O(n),空间复杂度O(1) class Solution { public: + int lengthOfLastWord(const string& s) { + return lengthOfLastWord(s.c_str()); + } +private: int lengthOfLastWord(const char *s) { int len = 0; while (*s) { diff --git a/C++/chapTree.tex b/C++/chapTree.tex index 056d8e02..7e70c7b5 100644 --- a/C++/chapTree.tex +++ b/C++/chapTree.tex @@ -56,14 +56,11 @@ \subsubsection{栈} public: vector preorderTraversal(TreeNode *root) { vector result; - const TreeNode *p; stack s; - - p = root; - if (p != nullptr) s.push(p); + if (root != nullptr) s.push(root); while (!s.empty()) { - p = s.top(); + const TreeNode *p = s.top(); s.pop(); result.push_back(p->val); @@ -84,9 +81,8 @@ \subsubsection{Morris先序遍历} public: vector preorderTraversal(TreeNode *root) { vector result; - TreeNode *cur, *prev; + TreeNode *cur = root, *prev = nullptr; - cur = root; while (cur != nullptr) { if (cur->left == nullptr) { result.push_back(cur->val); @@ -157,8 +153,8 @@ \subsubsection{栈} public: vector inorderTraversal(TreeNode *root) { vector result; - const TreeNode *p = root; stack s; + const TreeNode *p = root; while (!s.empty() || p != nullptr) { if (p != nullptr) { @@ -185,9 +181,8 @@ \subsubsection{Morris中序遍历} public: vector inorderTraversal(TreeNode *root) { vector result; - TreeNode *cur, *prev; + TreeNode *cur = root, *prev = nullptr; - cur = root; while (cur != nullptr) { if (cur->left == nullptr) { result.push_back(cur->val); @@ -258,11 +253,9 @@ \subsubsection{栈} public: vector postorderTraversal(TreeNode *root) { vector result; - /* p,正在访问的结点,q,刚刚访问过的结点*/ - const TreeNode *p, *q; stack s; - - p = root; + /* p,正在访问的结点,q,刚刚访问过的结点*/ + const TreeNode *p = root, *q = nullptr; do { while (p != nullptr) { /* 往左下走*/ @@ -375,7 +368,7 @@ \subsubsection{相关题目} \subsection{Binary Tree Level Order Traversal} -\label{sec:binary-tree-tevel-order-traversal} +\label{sec:binary-tree-level-order-traversal} \subsubsection{描述} @@ -438,13 +431,16 @@ \subsubsection{迭代版} public: vector > levelOrder(TreeNode *root) { vector > result; - if(root == nullptr) return result; - queue current, next; - vector level; // elments in level level + + if(root == nullptr) { + return result; + } else { + current.push(root); + } - current.push(root); while (!current.empty()) { + vector level; // elments in one level while (!current.empty()) { TreeNode* node = current.front(); current.pop(); @@ -453,7 +449,6 @@ \subsubsection{迭代版} if (node->right != nullptr) next.push(node->right); } result.push_back(level); - level.clear(); swap(next, current); } return result; @@ -628,42 +623,36 @@ \subsubsection{递归版} \subsubsection{迭代版} \begin{Code} -//LeetCode, Binary Tree Zigzag Level Order Traversal -//广度优先遍历,用一个bool记录是从左到右还是从右到左,每一层结束就翻转一下。 +// LeetCode, Binary Tree Zigzag Level Order Traversal +// 广度优先遍历,用一个bool记录是从左到右还是从右到左,每一层结束就翻转一下。 // 迭代版,时间复杂度O(n),空间复杂度O(n) class Solution { public: vector > zigzagLevelOrder(TreeNode *root) { vector > result; - if (nullptr == root) return result; - - queue q; - bool left_to_right = true; //left to right - vector level; // one level's elements - - q.push(root); - q.push(nullptr); // level separator - while (!q.empty()) { - TreeNode *cur = q.front(); - q.pop(); - if (cur) { - level.push_back(cur->val); - if (cur->left) q.push(cur->left); - if (cur->right) q.push(cur->right); - } else { - if (left_to_right) { - result.push_back(level); - } else { - reverse(level.begin(), level.end()); - result.push_back(level); - } - level.clear(); - left_to_right = !left_to_right; + queue current, next; + bool left_to_right = true; + + if(root == nullptr) { + return result; + } else { + current.push(root); + } - if (q.size() > 0) q.push(nullptr); + while (!current.empty()) { + vector level; // elments in one level + while (!current.empty()) { + TreeNode* node = current.front(); + current.pop(); + level.push_back(node->val); + if (node->left != nullptr) next.push(node->left); + if (node->right != nullptr) next.push(node->right); } + if (!left_to_right) reverse(level.begin(), level.end()); + result.push_back(level); + left_to_right = !left_to_right; + swap(next, current); } - return result; } }; @@ -844,14 +833,15 @@ \subsubsection{递归版} class Solution { public: bool isSymmetric(TreeNode *root) { - return root ? isSymmetric(root->left, root->right) : true; + if (root == nullptr) return true; + return isSymmetric(root->left, root->right); } - bool isSymmetric(TreeNode *left, TreeNode *right) { - if (!left && !right) return true; // 终止条件 - if (!left || !right) return false; // 终止条件 - return left->val == right->val // 三方合并 - && isSymmetric(left->left, right->right) - && isSymmetric(left->right, right->left); + bool isSymmetric(TreeNode *p, TreeNode *q) { + if (p == nullptr && q == nullptr) return true; // 终止条件 + if (p == nullptr || q == nullptr) return false; // 终止条件 + return p->val == q->val // 三方合并 + && isSymmetric(p->left, q->right) + && isSymmetric(p->right, q->left); } }; \end{Code} @@ -1457,15 +1447,15 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} -// LeetCode, Validate Binary Search Tree +// Validate Binary Search Tree // 时间复杂度O(n),空间复杂度O(\logn) class Solution { public: bool isValidBST(TreeNode* root) { - return isValidBST(root, INT_MIN, INT_MAX); + return isValidBST(root, LONG_MIN, LONG_MAX); } - bool isValidBST(TreeNode* root, int lower, int upper) { + bool isValidBST(TreeNode* root, long long lower, long long upper) { if (root == nullptr) return true; return root->val > lower && root->val < upper diff --git a/C++/images/.DS_Store b/C++/images/.DS_Store new file mode 100644 index 00000000..ef69838b Binary files /dev/null and b/C++/images/.DS_Store differ diff --git a/C++/leetcode-cpp.pdf b/C++/leetcode-cpp.pdf index 6e3a2430..72d3da55 100644 Binary files a/C++/leetcode-cpp.pdf and b/C++/leetcode-cpp.pdf differ diff --git a/C++/title.tex b/C++/title.tex index 57d01956..a0d042f8 100644 --- a/C++/title.tex +++ b/C++/title.tex @@ -3,7 +3,7 @@ {\LARGE\textbf{\BookTitle}} \vspace{1em} - {\large 戴方勤 (soulmachine@gmail.com)} + {\large 灵魂机器 (soulmachine@gmail.com)} \vspace{1ex} \myurl{https://github.com/soulmachine/leetcode} diff --git a/Java/README.md b/Java/README.md index 8886a0b4..73032b56 100644 --- a/Java/README.md +++ b/Java/README.md @@ -1,3 +1,7 @@ #Java版 ----------------- -书的内容与C++版一摸一样,不过代码是用Java写的。本书的代码要求 Java 6 以上。 + +## 编译 + + docker pull soulmachine/texlive + docker run -it --rm -v $(pwd):/data -w /data soulmachine/texlive-full xelatex -synctex=1 -interaction=nonstopmode leetcode-java.tex diff --git a/README.md b/README.md index fd9ff34a..9af9ce21 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,41 @@ -#LeetCode题解 ------------------ -##PDF下载 -LeetCode题解(C++版).pdf - -C++ 文件夹下是C++版,内容一摸一样,代码是用C++写的, - -Java 文件夹下是Java版,内容一摸一样,代码是用Java写的 - -##LaTeX模板 -本书使用的是陈硕开源的[模板](https://github.com/chenshuo/typeset)。这个模板制作精良,很有taste,感谢陈硕 :) - -##在Windows下编译 -1. 安装Tex Live 2013 。把bin目录例如`D:\texlive\2013\bin\win32`加入PATH环境变量。 -1. 安装字体。这个LaTex模板总共使用了9个字体,下载地址 ,有的字体Windows自带了,有的字体Ubuntu自带了,但都不全,还是一次性安装完所有字体比较方便。 -1. 安装TeXstudio -1. (可选)启动Tex Live Manager,更新所有已安装的软件包。 -1. 配置TeXstudio。 +# LeetCode题解 - 启动Texstudio,选择 `Options-->Configure Texstudio-->Commands`,XeLaTex 设置为 `xelatex -synctex=1 -interaction=nonstopmode %.tex`; +## 在线阅读 - 选择 `Options-->Configure Texstudio-->Build` + - Build & View 由默认的 PDF Chain 改为 Compile & View; +## PDF下载 - Default Compiler 由默认的PdfLaTex 修改为 XeLaTex ; - - PDF Viewer 改为 “Internal PDF Viewer(windowed)”,这样预览时会弹出一个独立的窗口,这样比较方便。 - -1. 编译。用TeXstudio打开`leetcode-cpp.tex`,点击界面上的绿色箭头就可以开始编译了。 - - 在下方的窗口可以看到TeXstudio正在使用的编译命令是`xelatex -synctex=1 -interaction=nonstopmode "leetcode-cpp".tex` +LeetCode题解(C++版).pdf -##在Ubuntu下编译 -1. 安装Tex Live 2013 - - 1.1. 下载TexLive 2013 的ISO 光盘,地址 +C++ 文件夹下是C++版,内容一模一样,代码是用C++写的。 - 1.2 mount 光盘,`sudo ./install-tl` 开始安装 +Java 文件夹下是Java版,目前正在编写中,由于拖延症,不知道猴年马月能完成。 - 1.3 加入环境变量 +## 如何编译PDF - sudo vi /etc/profile - export PATH=$PATH:/usr/local/texlive/2013/bin/x86_64-linux - export MANPATH=$MANPATH:/usr/local/texlive/2013/texmf-dist/doc/man - export INFPATH=$INFPATH:/usr/local/texlive/2013/texmf-dist/doc/info +### 命令行编译 -1. 安装字体。这个LaTex模板总共使用了9个字体,下载地址 ,有的字体Windows自带了,有的字体Ubuntu自带了,但都不全,还是一次性安装完所有字体比较方便。 -1. 安装TeXstudio -1. 配置TeXstudio。 +```bash +docker run -it --rm -v $(pwd)/C++:/project -w /project soulmachine/texlive xelatex -interaction=nonstopmode leetcode-cpp.tex +``` - 启动Texstudio,选择 `Options-->Configure Texstudio-->Commands`,XeLaTex 设置为 `xelatex -synctex=1 -interaction=nonstopmode %.tex`; +### vscode下编译 - 选择 `Options-->Configure Texstudio-->Build` +本项目已经配置好了vscode devcontainer, 可以在 Windows, Linux 和 macOS 三大平台上编译。 - Build & View 由默认的 PDF Chain 改为 Compile & View; +用 vscode 打开本项目,选择右下角弹出的 `"Reopen in Container"`,就会在容器中打开本项目,该容器安装了 Tex Live 2022 以及所需要的10个字体。 - Default Compiler 由默认的PdfLaTex 修改为 XeLaTex ; +点击vscode左下角的齿轮图标,选择 `Command Palette`,输入`tasks`, 选择 `Run Task`, 选择 `leetcode-C++`,即可启动编译。 - PDF Viewer 改为 “Internal PDF Viewer(windowed)”,这样预览时会弹出一个独立的窗口,这样比较方便。 +## LaTeX模板 -1. 编译。用TeXstudio打开`leetcode-cpp.tex`,点击界面上的绿色箭头就可以开始编译了。 +本书使用的是陈硕开源的[模板](https://github.com/chenshuo/typeset)。这个模板制作精良,感谢陈硕 :) - 在下方的窗口可以看到TeXstudio正在使用的编译命令是`xelatex -synctex=1 -interaction=nonstopmode "leetcode-cpp".tex` -1. 懒人版镜像。如果不想进行上面繁琐的安装过程,我做好了一个Ubuntu VMware虚拟机镜像,已经装好了TexLive 2013, TexStudio和字体(详细的安装日志见压缩包注释),开箱即用,下载地址 。 +这个LaTex模板总共使用了10个字体,下载地址 。有的字体Windows自带了,有的字体Ubuntu自带了,但都不全,还是一次性安装完所有字体比较方便。 -##如何贡献代码 -编译通过后,就具备了完整的LaTeX编译环境了。 +也可以参考 [Dockerfile](https://github.com/soulmachine/docker-images/blob/master/texlive/Dockerfile) 去学习如何安装所有字体。 -本书模板已经写好了,基本上不需要很多LaTeX知识就可以动手了。 +## 贡献代码 欢迎给本书添加内容或纠正错误,在自己本地编译成PDF,预览没问题后,就可以发pull request过来了。 - -##北美求职微博群 -我和我的小伙伴们在这里: diff --git "a/\345\217\202\350\200\203\350\265\204\346\226\231/silicon-job.jpeg" "b/\345\217\202\350\200\203\350\265\204\346\226\231/silicon-job.jpeg" new file mode 100644 index 00000000..dd349c0b Binary files /dev/null and "b/\345\217\202\350\200\203\350\265\204\346\226\231/silicon-job.jpeg" differ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy