diff --git a/C++/README.md b/C++/README.md index 78934f1f..d48c7653 100644 --- a/C++/README.md +++ b/C++/README.md @@ -1,7 +1,7 @@ #C++版 ----------------- -## 编译 +## 編譯 docker pull soulmachine/texlive docker run -it --rm -v $(pwd):/data -w /data soulmachine/texlive xelatex -interaction=nonstopmode leetcode-cpp.tex diff --git a/C++/abstract.tex b/C++/abstract.tex index 8747e624..3b695623 100644 --- a/C++/abstract.tex +++ b/C++/abstract.tex @@ -1,27 +1,27 @@ -\subsubsection{内容简介} -本书的目标读者是准备去北美找工作的码农,也适用于在国内找工作的码农,以及刚接触ACM算法竞赛的新手。 +\subsubsection{內容簡介} +本書的目標讀者是準備去北美找工作的碼農,也適用於在國內找工作的碼農,以及剛接觸ACM算法競賽的新手。 -本书包含了 LeetCode Online Judge(\myurl{http://leetcode.com/onlinejudge})所有题目的答案, -所有代码经过精心编写,编码规范良好,适合读者反复揣摩,模仿,甚至在纸上默写。 +本書包含了 LeetCode Online Judge(\myurl{http://leetcode.com/onlinejudge})所有題目的答案, +所有代碼經過精心編寫,編碼規範良好,適合讀者反覆揣摩,模仿,甚至在紙上默寫。 -全书的代码,使用C++ 11的编写,并在 LeetCode Online Judge 上测试通过。本书中的代码规范,跟在公司中的工程规范略有不同,为了使代码短(方便迅速实现): +全書的代碼,使用C++ 11的編寫,並在 LeetCode Online Judge 上測試通過。本書中的代碼規範,跟在公司中的工程規範略有不同,為了使代碼短(方便迅速實現): \begindot -\item 所有代码都是单一文件。这是因为一般OJ网站,提交代码的时候只有一个文本框,如果还是 -按照标准做法,比如分为头文件.h和源代码.cpp,无法在网站上提交; +\item 所有代碼都是單一文件。這是因為一般OJ網站,提交代碼的時候只有一個文本框,如果還是 +按照標準做法,比如分為頭文件.h和源代碼.cpp,無法在網站上提交; -\item Shorter is better。能递归则一定不用栈;能用STL则一定不自己实现。 +\item Shorter is better。能遞歸則一定不用棧;能用STL則一定不自己實現。 -\item 不提倡防御式编程。不需要检查malloc()/new 返回的指针是否为nullptr;不需要检查内部函数入口参数的有效性。 +\item 不提倡防禦式編程。不需要檢查malloc()/new 返回的指針是否為nullptr;不需要檢查內部函數入口參數的有效性。 \myenddot -本手册假定读者已经学过《数据结构》\footnote{《数据结构》,严蔚敏等著,清华大学出版社, +本手冊假定讀者已經學過《數據結構》\footnote{《數據結構》,嚴蔚敏等著,清華大學出版社, \myurl{http://book.douban.com/subject/2024655/}}, 《算法》\footnote{《Algorithms》,Robert Sedgewick, Addison-Wesley Professional, \myurl{http://book.douban.com/subject/4854123/}} -这两门课,熟练掌握C++或Java。 +這兩門課,熟練掌握C++或Java。 \subsubsection{GitHub地址} -本书是开源的,GitHub地址:\myurl{https://github.com/soulmachine/leetcode} +本書是開源的,GitHub地址:\myurl{https://github.com/soulmachine/leetcode} -\subsubsection{北美求职微博群} -我和我的小伙伴们在这里:\myurl{http://q.weibo.com/1312378} +\subsubsection{北美求職微博羣} +我和我的小夥伴們在這裏:\myurl{http://q.weibo.com/1312378} diff --git a/C++/chapBFS.tex b/C++/chapBFS.tex index 826d7c4a..5b068376 100644 --- a/C++/chapBFS.tex +++ b/C++/chapBFS.tex @@ -1,10 +1,11 @@ -\chapter{广度优先搜索} -当题目看不出任何规律,既不能用分治,贪心,也不能用动规时,这时候万能方法——搜索, -就派上用场了。搜索分为广搜和深搜,广搜里面又有普通广搜,双向广搜,A*搜索等。 -深搜里面又有普通深搜,回溯法等。 +\chapter{廣度優先搜索} +當題目看不出任何規律,既不能用分治,貪心,也不能用動規時,這時候萬能方法——搜索, +就派上用場了。搜索分為廣搜和深搜,廣搜裏面又有普通廣搜,雙向廣搜,A*搜索等。 +深搜裏面又有普通深搜,回溯法等。 -广搜和深搜非常类似(除了在扩展节点这部分不一样),二者有相同的框架,如何表示状态? -如何扩展状态?如何判重?尤其是判重,解决了这个问题,基本上整个问题就解决了。 +廣搜和深搜非常類似(除了在擴展節點這部分不一樣),二者有相同的框架,如何表示狀態? +如何擴展狀態?如何判重?尤其是判重,解決了這個問題,基本上整個問題就解決了。 +\newline \section{Word Ladder} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -23,7 +24,7 @@ \subsubsection{描述} \begin{Code} start = "hit" end = "cog" -dict = ["hot","dot","dog","lot","log"] +dict = ["hot","dot","dog","lot","log","cog"] \end{Code} As one shortest transformation is \code{"hit" -> "hot" -> "dot" -> "dog" -> "cog"}, return its length $5$. @@ -36,13 +37,77 @@ \subsubsection{描述} \subsubsection{分析} -求最短路径,用广搜。 - - -\subsubsection{单队列} +求最短路徑,用廣搜。 + +\subsubsection{Tackle} +\begin{itemize} + \item Input types + \begin{itemize} + \item categories + \begin{itemize} + \item valid, invalid + \item with answer, without answer + \end{itemize} + \begin{itemize} + \item valid, with answer + \begin{Code} +start = "hit" +end = "cog" +dict = ["hot","dot","dog","lot","log","cog"] + \end{Code} + \item valid, without answer + \begin{Code} +start = "hit" +end = "cog" +dict = ["hot","dot","dog","lot","log"] + \end{Code} + \item invalid + \begin{Code} +start = "hitjj" +end = "cogkkkhgg" +dict = ["hot","dot","dog","lot","log"] + \end{Code} + \end{itemize} + \item We have 3 cases finally, which can be handle by one implementation. This is doable and we can go to the next step. + \end{itemize} + \item Algo type? BFS, shortest path search + \item Test case + \begin{itemize} + \item input: start_word, end_word, additional words + \item output: dict, expected steps + \item Algo steps + \begin{itemize} + \item Generate a word ladder from start to end --- mob ~> cob ~> cab ~> cap + \item Generate additional words by random + \item Output both valid test input with both with and without answer + \end{itemize} + \end{itemize} + \item Algo steps + \begin{itemize} + \item Invalid input case + \begin{itemize} + \item Confirm start, end can be found from the dict + \end{itemize} + \item Valid with answer, valid without answer cases + \begin{itemize} + \item Use BFS template with 2 queue + \item Expand stage + \item Push to next level, only push when that is \textbf{the first time} we saw the word + \item Loop until we found answer or no more next level + \end{itemize} + \end{itemize} +\end{itemize} + +\begin{center} +\includegraphics[width=150pt]{word-ladder.png}\\ +\figcaption{Word Ladder - Expand stage}\label{fig:word-ladder} +\end{center} + + +\subsubsection{單隊列} \begin{Code} //LeetCode, Word Ladder -// 时间复杂度O(n),空间复杂度O(n) +// 時間複雜度O(n),空間複雜度O(n) struct state_t { string word; int level; @@ -78,7 +143,7 @@ \subsubsection{单队列} unordered_set visited; // 判重 auto state_is_valid = [&](const state_t& s) { - return dict.find(s.word) != dict.end() || s.word == end; + return dict.find(s.word) != dict.end(); }; auto state_is_target = [&](const state_t &s) {return s.word == end; }; auto state_extend = [&](const state_t &s) { @@ -87,7 +152,7 @@ \subsubsection{单队列} 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]); @@ -96,7 +161,7 @@ \subsubsection{单队列} visited.find(new_state) == visited.end()) { result.insert(new_state); } - swap(c, new_state.word[i]); // 恢复该单词 + swap(c, new_state.word[i]); // 恢復該單詞 } } @@ -107,8 +172,8 @@ \subsubsection{单队列} q.push(start_state); visited.insert(start_state); while (!q.empty()) { - // 千万不能用 const auto&,pop() 会删除元素, - // 引用就变成了悬空引用 + // 千萬不能用 const auto&,pop() 會刪除元素, + // 引用就變成了懸空引用 const auto state = q.front(); q.pop(); @@ -128,21 +193,21 @@ \subsubsection{单队列} \end{Code} -\subsubsection{双队列} +\subsubsection{雙隊列} \begin{Code} //LeetCode, Word Ladder -// 时间复杂度O(n),空间复杂度O(n) +// 時間複雜度O(n),空間複雜度O(n) class Solution { public: int ladderLength(const string& start, const string &end, const unordered_set &dict) { - queue current, next; // 当前层,下一层 + queue current, next; // 當前層,下一層 unordered_set visited; // 判重 - int level = -1; // 层次 + int level = -1; // 層次 auto state_is_valid = [&](const string& s) { - return dict.find(s) != dict.end() || s == end; + return dict.find(s) != dict.end(); }; auto state_is_target = [&](const string &s) {return s == end;}; auto state_extend = [&](const string &s) { @@ -151,7 +216,7 @@ \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]); @@ -160,7 +225,7 @@ \subsubsection{双队列} visited.find(new_word) == visited.end()) { result.insert(new_word); } - swap(c, new_word[i]); // 恢复该单词 + swap(c, new_word[i]); // 恢復該單詞 } } @@ -172,8 +237,8 @@ \subsubsection{双队列} while (!current.empty()) { ++level; while (!current.empty()) { - // 千万不能用 const auto&,pop() 会删除元素, - // 引用就变成了悬空引用 + // 千萬不能用 const auto&,pop() 會刪除元素, + // 引用就變成了懸空引用 const auto state = current.front(); current.pop(); @@ -195,10 +260,10 @@ \subsubsection{双队列} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Word Ladder II,见 \S \ref{sec:word-ladder-ii} +\item Word Ladder II,見 \S \ref{sec:word-ladder-ii} \myenddot @@ -235,18 +300,18 @@ \subsubsection{描述} \subsubsection{分析} -跟 Word Ladder比,这题是求路径本身,不是路径长度,也是BFS,略微麻烦点。 +跟 Word Ladder比,這題是求路徑本身,不是路徑長度,也是BFS,略微麻煩點。 -求一条路径和求所有路径有很大的不同,求一条路径,每个状态节点只需要记录一个前驱即可;求所有路径时,有的状态节点可能有多个父节点,即要记录多个前驱。 +求一條路徑和求所有路徑有很大的不同,求一條路徑,每個狀態節點只需要記錄一個前驅即可;求所有路徑時,有的狀態節點可能有多個父節點,即要記錄多個前驅。 -如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的处理,因为我们要找的是最短路径。 +如果當前路徑長度已經超過當前最短路徑長度,可以中止對該路徑的處理,因為我們要找的是最短路徑。 -\subsubsection{单队列} +\subsubsection{單隊列} \begin{Code} //LeetCode, Word Ladder II -// 时间复杂度O(n),空间复杂度O(n) +// 時間複雜度O(n),空間複雜度O(n) struct state_t { string word; int level; @@ -280,10 +345,10 @@ \subsubsection{单队列} const string& end, const unordered_set &dict) { queue q; unordered_set visited; // 判重 - unordered_map > father; // DAG + unordered_map > father; // DAG (樹) key: child value: father auto state_is_valid = [&](const state_t& s) { - return dict.find(s.word) != dict.end() || s.word == end; + return dict.find(s.word) != dict.end(); }; auto state_is_target = [&](const state_t &s) {return s.word == end; }; auto state_extend = [&](const state_t &s) { @@ -292,7 +357,7 @@ \subsubsection{单队列} 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]); @@ -312,7 +377,7 @@ \subsubsection{单队列} result.insert(new_state); } } - swap(c, new_state.word[i]); // 恢复该单词 + swap(c, new_state.word[i]); // 恢復該單詞 } } @@ -324,13 +389,13 @@ \subsubsection{单队列} q.push(start_state); visited.insert(start_state); while (!q.empty()) { - // 千万不能用 const auto&,pop() 会删除元素, - // 引用就变成了悬空引用 + // 千萬不能用 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)) { @@ -338,11 +403,11 @@ \subsubsection{单队列} gen_path(father, start_state, state, path, result); continue; } - // 必须挪到下面,比如同一层A和B两个节点均指向了目标节点, - // 那么目标节点就会在q中出现两次,输出路径就会翻倍 + // 必須挪到下面,比如同一層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()) { @@ -388,26 +453,26 @@ \subsubsection{单队列} \end{Code} -\subsubsection{双队列} +\subsubsection{雙隊列} \begin{Code} //LeetCode, Word Ladder II -// 时间复杂度O(n),空间复杂度O(n) +// 時間複雜度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是為了去重,例如兩個父節點指向 + // 同一個子節點,如果用vector, 子節點就會在next裏出現兩次,其實此 + // 時 father 已經記錄了兩個父節點,next裏重複出現兩次是沒必要的 unordered_set current, next; unordered_set visited; // 判重 unordered_map > father; // DAG - int level = -1; // 层次 + int level = -1; // 層次 auto state_is_valid = [&](const string& s) { - return dict.find(s) != dict.end() || s == end; + return dict.find(s) != dict.end(); }; auto state_is_target = [&](const string &s) {return s == end;}; auto state_extend = [&](const string &s) { @@ -416,7 +481,7 @@ \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]); @@ -425,7 +490,7 @@ \subsubsection{双队列} visited.find(new_word) == visited.end()) { result.insert(new_word); } - swap(c, new_word[i]); // 恢复该单词 + swap(c, new_word[i]); // 恢復該單詞 } } @@ -436,13 +501,13 @@ \subsubsection{双队列} current.insert(start); while (!current.empty()) { ++ level; - // 如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的 - // 处理,因为我们要找的是最短路径 + // 如果當前路徑長度已經超過當前最短路徑長度,可以中止對該路徑的 + // 處理,因為我們要找的是最短路徑 if (!result.empty() && level+1 > result[0].size()) break; - // 1. 延迟加入visited, 这样才能允许两个父节点指向同一个子节点 - // 2. 一股脑current 全部加入visited, 是防止本层前一个节点扩展 - // 节点时,指向了本层后面尚未处理的节点,这条路径必然不是最短的 + // 1. 延遲加入visited, 這樣才能允許兩個父節點指向同一個子節點 + // 2. 一股腦current 全部加入visited, 是防止本層前一個節點擴展 + // 節點時,指向了本層後面尚未處理的節點,這條路徑必然不是最短的 for (const auto& state : current) visited.insert(state); for (const auto& state : current) { @@ -496,21 +561,21 @@ \subsubsection{双队列} \end{Code} -\subsubsection{图的广搜} +\subsubsection{圖的廣搜} -本题还可以看做是图上的广搜。给定了字典 \fn{dict},可以基于它画出一个无向图,表示单词之间可以互相转换。本题的本质就是已知起点和终点,在图上找出所有最短路径。 +本題還可以看做是圖上的廣搜。給定了字典 \fn{dict},可以基於它畫出一個無向圖,表示單詞之間可以互相轉換。本題的本質就是已知起點和終點,在圖上找出所有最短路徑。 \begin{Code} //LeetCode, Word Ladder II -// 时间复杂度O(n),空间复杂度O(n) +// 時間複雜度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 是所在层次 + queue q; // 未處理的節點 + // value 是所在層次 unordered_map visited; auto state_is_target = [&](const state_t *s) {return s->word == end; }; @@ -521,8 +586,8 @@ \subsubsection{图的广搜} state_t* state = q.front(); q.pop(); - // 如果当前路径长度已经超过当前最短路径长度, - // 可以中止对该路径的处理,因为我们要找的是最短路径 + // 如果當前路徑長度已經超過當前最短路徑長度, + // 可以中止對該路徑的處理,因為我們要找的是最短路徑 if (!result.empty() && state->level+1 > result[0].size()) break; if (state_is_target(state)) { @@ -542,9 +607,8 @@ \subsubsection{图的广搜} } continue; } - visited[state->word] = state->level; - // 扩展节点 + // 擴展節點 auto iter = g.find(state->word); if (iter == g.end()) continue; @@ -556,6 +620,7 @@ \subsubsection{图的广搜} continue; } + visited[neighbor] = state->level; q.push(create_state(state, neighbor, state->level + 1, pool)); } } @@ -571,7 +636,7 @@ \subsubsection{图的广搜} struct state_t { state_t* father; string word; - int level; // 所在层次,从0开始编号 + int level; // 所在層次,從0開始編號 state_t(state_t* father_, const string& word_, int level_) : father(father_), word(word_), level(level_) {} @@ -604,7 +669,7 @@ \subsubsection{图的广搜} 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]); @@ -619,7 +684,7 @@ \subsubsection{图的广搜} adjacency_list[word].insert(new_word); } } - swap(c, new_word[i]); // 恢复该单词 + swap(c, new_word[i]); // 恢復該單詞 } } } @@ -629,10 +694,10 @@ \subsubsection{图的广搜} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Word Ladder,见 \S \ref{sec:word-ladder} +\item Word Ladder,見 \S \ref{sec:word-ladder} \myenddot @@ -663,13 +728,13 @@ \subsubsection{描述} \subsubsection{分析} -广搜。从上下左右四个边界往里走,凡是能碰到的\fn{'O'},都是跟边界接壤的,应该保留。 +廣搜。從上下左右四個邊界往裏走,凡是能碰到的\fn{'O'},都是跟邊界接壤的,應該保留。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Surrounded Regions -// BFS,时间复杂度O(n),空间复杂度O(n) +// BFS,時間複雜度O(n),空間複雜度O(n) class Solution { public: void solve(vector> &board) { @@ -716,7 +781,7 @@ \subsubsection{代码} {x,y-1}, {x,y+1}}; for (int k = 0; k < 4; ++k) { if (state_is_valid(new_states[k])) { - // 既有标记功能又有去重功能 + // 既有標記功能又有去重功能 board[new_states[k].first][new_states[k].second] = '+'; result.push_back(new_states[k]); } @@ -741,115 +806,801 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot +\section{Walls and Gates} +\label{sec:walls-and-gates} -\section{小结} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsubsection{描述} +You are given a m x n 2D grid initialized with these three possible values. + +\begindot +\item -1 - A wall or an obstacle. +\item 0 - A gate. +\item INF - Infinity means an empty room. We use the value 231 - 1 = 2147483647 to represent INF as you may assume that the distance to a gate is less than 2147483647. +\myenddot + +Fill each empty room with the distance to its nearest gate. If it is impossible to reach a gate, it should be filled with INF. + +Example: +Given the 2D grid: +\begin{Code} +INF -1 0 INF +INF INF INF -1 +INF -1 INF -1 + 0 -1 INF INF +\end{Code} + +After running your function, the 2D grid should be: +\begin{Code} + 3 -1 0 1 + 2 2 1 -1 + 1 -1 2 -1 + 0 -1 3 4 +\end{Code} + + +\subsubsection{BFS} +\begin{Code} +// LeetCode +// 時間複雜度O(n*m),空間複雜度O(1) +int rows[] = {0, 0, 1,-1}; +int cols[] = {1,-1, 0, 0}; +#define INF 2147483647 +class Solution { +public: + bool isSafe(vector>& rooms,int i, int j) + { + if( i <0 or i>=rooms.size() or j <0 or j >= rooms[0].size() or rooms[i][j] != INF ) + return false; + return true; + } + + void wallsAndGates(vector>& rooms) { + int r = rooms.size(); + if (r < 1) return; + int c = rooms[0].size(); + queue> q; + for (int i=0; i < r; i++) + for(int j=0; j "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202". +Note that a sequence like "0000" -> "0001" -> "0002" -> "0102" -> "0202" would be invalid, +because the wheels of the lock become stuck after the display becomes the dead end "0102". +\end{Code} + +Example 2: +\begin{Code} +Input: deadends = ["8888"], target = "0009" +Output: 1 +Explanation: +We can turn the last wheel in reverse to move from "0000" -> "0009". +\end{Code} + +Example 3: +\begin{Code} +Input: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888" +Output: -1 +Explanation: +We can't reach the target without getting stuck. +\end{Code} + +Example 4: +\begin{Code} +Input: deadends = ["0000"], target = "8888" +Output: -1 +\end{Code} + +\begindot +\item The length of deadends will be in the range [1, 500]. +\item target will not be in the list deadends. +\item Every string in deadends and the string target will be a string of 4 digits from the 10,000 possibilities '0000' to '9999'. +\myenddot + +\subsection{分析} +Nil + +\subsection{BFS} +\begin{Code} +// LeetCode +// 時間複雜度O(N^2 * A^N + D),空間複雜度O(A^N + D) +// D is the size of deadends, A is the number of digits in our alphabet +// N is the number of digits +class Solution { +public: + int openLock(vector& deadends, string target) { + unordered_set deadendsMap(deadends.begin(), deadends.end()); + unordered_set visited; + + queue> q; + q.push({"0000", 0}); + + int level = 0; + while (!q.empty()) + { + auto cur = q.front(); + q.pop(); + + // 到了 dead end 處理下一個 + if (deadendsMap.find(cur.first) != deadendsMap.end()) continue; + // 找到了答案 + if (cur.first == target) return cur.second; + + for (const auto& nextPW : GetNextPW(cur.first, visited)) + { + visited.insert(nextPW); + q.push({nextPW, cur.second + 1}); + } + } + + return -1; + } +private: + const vector steps{-1, 1}; + + vector GetNextPW(string curPW, const unordered_set& visited) + { + vector result; result.reserve(curPW.size() * 2); + for (char& c : curPW) + { + char prev = c; + for (const auto& n : steps) + { + c = ((10 + c - '0' + n) % 10) + '0'; + if (visited.find(curPW) == visited.end()) + result.push_back(curPW); + c = prev; + } + } + + return result; + } +}; +\end{Code} + +\subsection{Counting} +\begin{Code} +// LeetCode +// 時間複雜度O(),空間複雜度O() +class Solution { +public: + int openLock(vector& deadends, string target) { + vector bucket(10000, 0); + vector rotate = { 0, 1, 2, 3, 4, 5, 4, 3, 2, 1 }; + for (auto& s : deadends) { + bucket[stoi(s)] = 1; + } + + if (1 == bucket[0]) return -1; + + vector diffOne; + + for (int i = 0; i < 4; ++i) { + diffOne.push_back(add(target, i, -1)); + diffOne.push_back(add(target, i, 1)); + } + + int ans = numeric_limits::max(); + + for (auto& s : diffOne) { + if (0 == bucket[stoi(s)]) { + ans = min(ans, moves(s, rotate) + 1); + } + } + + if (ans == numeric_limits::max()) ans = -1; + + return ans; + } + +private: + string add(const string& num, int idx, int delta) + { + string ans = num; + char ch = num[idx]; + + if ('0' == ch && delta == -1) { + ans[idx] = '9'; + } + else if ('9' == ch && delta == 1) { + ans[idx] = '0'; + } + else { + ans[idx] += delta; + } + + return ans; + } + + int moves(const string& num, const vector& rotate) + { + int ans = 0; + for (auto c : num) { + ans += rotate[c - '0']; + } + + return ans; + } +}; +\end{Code} + +\section{Perfect Squares} +\label{sec:perfect-squares} + +\subsection{描述} +Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, ...) which sum to n. + +Example 1: +\begin{Code} +Input: n = 12 +Output: 3 +Explanation: 12 = 4 + 4 + 4. +\end{Code} + +Example 2: +\begin{Code} +Input: n = 13 +Output: 2 +Explanation: 13 = 4 + 9. +\end{Code} + + +\subsection{暴力 - DFS - 會超時} +\begin{Code} +// LeetCode +// 時間複雜度O(),空間複雜度O() +class Solution { +public: + int numSquares(int n) { + m_minStep = INT_MAX; + DFS(n, 0, 1); + return m_minStep; + } +private: + int m_minStep; + void DFS(int n, int step, int start) + { + if (n == 0) + { + m_minStep = min(m_minStep, step); + return; + } + else if (n < 0) + return; + else if (step > m_minStep) + return; + + for (int i = start; i*i <= n; i++) + { + DFS(n - (i*i), step + 1, i); + } + } +}; +\end{Code} + +\subsection{貪心法 - DFS - I} +\begin{Code} +// LeetCode +// 時間複雜度O(),空間複雜度O() +class Solution { +public: + int numSquares(int n) { + m_minStep = INT_MAX; + m_N = n; + DFS(n, 0, 1); + return m_minStep; + } +private: + int m_minStep; + int m_N; + void DFS(int n, int step) + { + if (n == 0) + { + m_minStep = min(m_minStep, step); + return; + } + else if (n < 0) + return; + else if (step > m_minStep || step > m_N) + return; + + + for (int i = (int)sqrt(n); i >= 1; i--) + { + DFS(n - (i*i), step + 1); + if (step + 1 >= m_minStep) return; // 求得最短,節枝 + } + } +}; +\end{Code} + +\subsection{貪心法 - DFS - II} +\begin{Code} +// LeetCode +// 時間複雜度O(n ^h/2),空間複雜度O(sqrt(n)) +class Solution { +public: + int numSquares(int n) { + for (int i = 1; i * i <= n; ++i) + m_squareNums.insert(i * i); + + int count = 1; + for (; count <= n; ++count) { + if (isDividedBy(n, count)) + return count; + } + return count; + } +private: + unordered_set m_squareNums; + + bool isDividedBy(int n, int count) + { + if (count == 1) + return m_squareNums.find(n) != m_squareNums.end(); + + for (const int& square : m_squareNums) { + if (isDividedBy(n - square, count - 1)) + return true; + } + return false; + } +}; +\end{Code} + +\subsection{貪心法 - BFS} +\begin{Code} +// LeetCode +// 時間複雜度O(n ^h/2),空間複雜度O(sqrt(n)) +class Solution { +public: + int numSquares(int n) { + unordered_set squareNums; + for (int i = 1; i * i <= n; i++) + squareNums.insert(i*i); + + int level = 1; + queue cur, next; + cur.push(n); + + while (!cur.empty()) + { + while (!cur.empty()) + { + auto curN = cur.front(); + cur.pop(); + + for (int i = sqrt(curN); i >= 1; i--) + { + int tmpSq = i*i; + if (curN == tmpSq) return level; // 找到答案 + else if (curN < tmpSq) continue; // 變成負數,節枝 + else + next.push(curN - tmpSq); + } + } + + swap(cur, next); + level++; + } + + return n; + } +}; +\end{Code} + +\subsection{動規} + +\begin{center} +\includegraphics[width=300pt]{perfect-squares-dp.png}\\ +\figcaption{Perfect Squares}\label{fig:perfect-squares-dp} +\end{center} + +\begin{Code} +// LeetCode +// 時間複雜度O(n*sqtr(n)),空間複雜度O(n) +class Solution { +public: + int numSquares(int n) { + vector dp(n+1, INT_MAX); + // bottom case + dp[0] = 0; + + // pre-calculate the square numbers. + int max_square_index = (int) sqrt(n) + 1; + vector square_nums(max_square_index); + + for (int i = 1; i < max_square_index; ++i) { + square_nums[i] = i * i; + } + + for (int i = 1; i <= n; ++i) { + for (int s = 1; s < max_square_index; ++s) { + if (i < square_nums[s]) + break; + dp[i] = min(dp[i], dp[i - square_nums[s]] + 1); + } + } + + return dp[n]; + } +}; +\end{Code} + +\section{01 Matrix} +\label{sec:01-matrix} + +\subsection{描述} +Given a matrix consists of 0 and 1, find the distance of the nearest 0 for each cell. + +The distance between two adjacent cells is 1. + +Example 1: +\begin{Code} +Input: +[[0,0,0], + [0,1,0], + [0,0,0]] + +Output: +[[0,0,0], + [0,1,0], + [0,0,0]] +\end{Code} + +Example 2: +\begin{Code} +Input: +[[0,0,0], + [0,1,0], + [1,1,1]] + +Output: +[[0,0,0], + [0,1,0], + [1,2,1]] +\end{Code} + + +\begindot +\item The number of elements of the given matrix will not exceed 10,000. +\item There are at least one 0 in the given matrix. +\item The cells are adjacent in only four directions: up, down, left and right. +\myenddot + +\subsection{分析} +Nil + +\subsection{代碼} +\begin{Code} +// LeetCode +// 時間複雜度O(),空間複雜度O() +class Solution { +public: + const vector xLoc{-1, 0, 1, 0}; + const vector yLoc{0, -1, 0, 1}; + vector> updateMatrix(vector>& matrix) { + if (matrix.empty()) return vector>(); + + // 準備 result structure + // 記低所有 0 的位置 + // 其餘位置為 MAX + int M = matrix.size(); + int N = matrix[0].size(); + vector> result(M, vector(N, INT_MAX)); + for (int i = 0; i < M; i++) + { + for (int j = 0; j < N; j++) + { + if (matrix[i][j] == 0) + result[i][j] = 0; + } + } + + // 開始 BFS + queue> q; + for (int i = 0; i < M; i++) + { + for (int j = 0; j < N; j++) + { + if (result[i][j] == 0) + q.push(make_pair(i,j)); + } + } + + while (!q.empty()) + { + auto loc = q.front(); + q.pop(); + + // 展開 + for (int i = 0; i < 4; i++) + { + int x = loc.first + xLoc[i]; + int y = loc.second + yLoc[i]; + + if (x < 0 || x >= M || y < 0 || y >= N || result[x][y] != INT_MAX) + continue; + result[x][y] = result[loc.first][loc.second] + 1; + q.push(make_pair(x,y)); + } + } + + return result; + } +}; +\end{Code} + + +\section{Keys and Rooms} +\label{sec:keys-and-rooms} + +\subsection{描述} +There are N rooms and you start in room 0. Each room has a distinct number in 0, 1, 2, ..., N-1, and each room may have some keys to access the next room. + +Formally, each room i has a list of keys rooms[i], and each key rooms[i][j] is an integer in [0, 1, ..., N-1] where N = rooms.length. A key rooms[i][j] = v opens the room with number v. + +Initially, all the rooms start locked (except for room 0). + +You can walk back and forth between rooms freely. + +Return true if and only if you can enter every room. + +Example 1: +\begin{Code} +Input: [[1],[2],[3],[]] +Output: true +Explanation: +We start in room 0, and pick up key 1. +We then go to room 1, and pick up key 2. +We then go to room 2, and pick up key 3. +We then go to room 3. Since we were able to go to every room, we return true. +\end{Code} + +Example 2: +\begin{Code} +Input: [[1,3],[3,0,1],[2],[0]] +Output: false +Explanation: We can't enter the room with number 2. +\end{Code} + +\begindot +\item 1 <= rooms.length <= 1000 +\item 0 <= rooms[i].length <= 1000 +\item The number of keys in all rooms combined is at most 3000. +\myenddot + +\subsection{分析} +Nil + +\subsection{BFS} +\begin{Code} +// LeetCode +// 時間複雜度O(N+E),空間複雜度O(N) +class Solution { +public: + bool canVisitAllRooms(vector>& rooms) { + if (rooms.empty()) return true; + + int M = rooms.size(); + vector visited(M, false); + + // BFS + queue q; + for (const auto& room : rooms.front()) + q.push(room); + visited.front() = true; + + int totalVisited = 1; + while (!q.empty()) + { + auto room = q.front(); + q.pop(); + + if (visited[room] == false) + { + totalVisited++; + if (totalVisited == M) return true; + visited[room] = true; + + // 擴展 + for (const auto& nei : rooms[room]) + q.push(nei); + } + } + + return find_if(visited.begin(), visited.end(), [&](const auto& e){ return !e; }) + == visited.end(); + } +}; +\end{Code} + +\subsection{DFS} +\begin{Code} +// LeetCode +// 時間複雜度O(N+E),空間複雜度O(N) +class Solution { +public: + bool canVisitAllRooms(vector>& rooms) { + if (rooms.empty()) return true; + + int M = rooms.size(); + vector visited(M, false); + + // DFS + stack q; // 使用 stack,不同於 BFS + for (auto it = rooms.front().rbegin(); it != rooms.front().rend(); it++) + q.push(*it); + visited.front() = true; + + int totalVisited = 1; + while (!q.empty()) + { + auto room = q.top(); + q.pop(); + + if (visited[room] == false) + { + totalVisited++; + if (totalVisited == M) return true; + visited[room] = true; + + // 擴展 + for (auto it = rooms[room].rbegin(); it != rooms[room].rend(); it++) + q.push(*it); + } + } + + return find_if(visited.begin(), visited.end(), [&](const auto& e){ return !e; }) + == visited.end(); + } +}; +\end{Code} +\section{小結} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \label{sec:bfs-template} -\subsection{适用场景} +\subsection{適用場景} -\textbf{输入数据}:没什么特征,不像深搜,需要有“递归”的性质。如果是树或者图,概率更大。 +\textbf{輸入數據}:沒什麼特徵,不像深搜,需要有“遞歸”的性質。如果是樹或者圖,概率更大。 -\textbf{状态转换图}:树或者DAG图。 +\textbf{狀態轉換圖}:樹或者DAG圖。 -\textbf{求解目标}:求最短。 +\textbf{求解目標}:求最短。 -\subsection{思考的步骤} +\subsection{思考的步驟} \begin{enumerate} -\item 是求路径长度,还是路径本身(或动作序列)? +\item 是求路徑長度,還是路徑本身(或動作序列)? \begin{enumerate} - \item 如果是求路径长度,则状态里面要存路径长度(或双队列+一个全局变量) - \item 如果是求路径本身或动作序列 + \item 如果是求路徑長度,則狀態裏面要存路徑長度(或雙隊列+一個全局變量) + \item 如果是求路徑本身或動作序列 \begin{enumerate} - \item 要用一棵树存储宽搜过程中的路径 - \item 是否可以预估状态个数的上限?能够预估状态总数,则开一个大数组,用树的双亲表示法;如果不能预估状态总数,则要使用一棵通用的树。这一步也是第4步的必要不充分条件。 + \item 要用一棵樹存儲寬搜過程中的路徑 + \item 是否可以預估狀態個數的上限?能夠預估狀態總數,則開一個大數組,用樹的雙親表示法;如果不能預估狀態總數,則要使用一棵通用的樹。這一步也是第4步的必要不充分條件。 \end{enumerate} \end{enumerate} -\item 如何表示状态?即一个状态需要存储哪些些必要的数据,才能够完整提供如何扩展到下一步状态的所有信息。一般记录当前位置或整体局面。 +\item 如何表示狀態?即一個狀態需要存儲哪些些必要的數據,才能夠完整提供如何擴展到下一步狀態的所有信息。一般記錄當前位置或整體局面。 -\item 如何扩展状态?这一步跟第2步相关。状态里记录的数据不同,扩展方法就不同。对于固定不变的数据结构(一般题目直接给出,作为输入数据),如二叉树,图等,扩展方法很简单,直接往下一层走,对于隐式图,要先在第1步里想清楚状态所带的数据,想清楚了这点,那如何扩展就很简单了。 +\item 如何擴展狀態?這一步跟第2步相關。狀態裏記錄的數據不同,擴展方法就不同。對於固定不變的數據結構(一般題目直接給出,作為輸入數據),如二叉樹,圖等,擴展方法很簡單,直接往下一層走,對於隱式圖,要先在第1步裏想清楚狀態所帶的數據,想清楚了這點,那如何擴展就很簡單了。 -\item 如何判断重复?如果状态转换图是一颗树,则永远不会出现回路,不需要判重;如果状态转换图是一个图(这时候是一个图上的BFS),则需要判重。 +\item 如何判斷重複?如果狀態轉換圖是一顆樹,則永遠不會出現迴路,不需要判重;如果狀態轉換圖是一個圖(這時候是一個圖上的BFS),則需要判重。 \begin{enumerate} - \item 如果是求最短路径长度或一条路径,则只需要让“点”(即状态)不重复出现,即可保证不出现回路 - \item 如果是求所有路径,注意此时,状态转换图是DAG,即允许两个父节点指向同一个子节点。具体实现时,每个节点要\textbf{“延迟”}加入到已访问集合\fn{visited},要等一层全部访问完后,再加入到\fn{visited}集合。 - \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 如果存在,则可以开一个大布尔数组,来判重,且此时可以精确计算出状态总数,而不仅仅是预估上限。 + \item 狀態是否存在完美哈希方案?即將狀態一一映射到整數,互相之間不會衝突。 + \item 如果不存在,則需要使用通用的哈希表(自己實現或用標準庫,例如\fn{unordered_set})來判重;自己實現哈希表的話,如果能夠預估狀態個數的上限,則可以開兩個數組,head和next,表示哈希表,參考第 \S \ref{subsec:eightDigits}節方案2。 + \item 如果存在,則可以開一個大布爾數組,來判重,且此時可以精確計算出狀態總數,而不僅僅是預估上限。 \end{enumerate} \end{enumerate} -\item 目标状态是否已知?如果题目已经给出了目标状态,可以带来很大便利,这时候可以从起始状态出发,正向广搜;也可以从目标状态出发,逆向广搜;也可以同时出发,双向广搜。 +\item 目標狀態是否已知?如果題目已經給出了目標狀態,可以帶來很大便利,這時候可以從起始狀態出發,正向廣搜;也可以從目標狀態出發,逆向廣搜;也可以同時出發,雙向廣搜。 \end{enumerate} -\subsection{代码模板} -广搜需要一个队列,用于一层一层扩展,一个hashset,用于判重,一棵树(只求长度时不需要),用于存储整棵树。 +\subsection{代碼模板} +廣搜需要一個隊列,用於一層一層擴展,一個hashset,用於判重,一棵樹(只求長度時不需要),用於存儲整棵樹。 -对于队列,可以用\fn{queue},也可以把\fn{vector}当做队列使用。当求长度时,有两种做法: +對於隊列,可以用\fn{queue},也可以把\fn{vector}當做隊列使用。當求長度時,有兩種做法: \begin{enumerate} -\item 只用一个队列,但在状态结构体\fn{state_t}里增加一个整数字段\fn{level},表示当前所在的层次,当碰到目标状态,直接输出\fn{level}即可。这个方案,可以很容易的变成A*算法,把\fn{queue}替换为\fn{priority_queue}即可。 -\item 用两个队列,\fn{current, next},分别表示当前层次和下一层,另设一个全局整数\fn{level},表示层数(也即路径长度),当碰到目标状态,输出\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}。 +對於hashset,如果有完美哈希方案,用布爾數組(\fn{bool visited[STATE_MAX]}或\fn{vector visited(STATE_MAX, false)})來表示;如果沒有,可以用STL裏的\fn{set}或\fn{unordered_set}。 -对于树,如果用STL,可以用\fn{unordered_map father}表示一颗树,代码非常简洁。如果能够预估状态总数的上限(设为STATE_MAX),可以用数组(\fn{state_t nodes[STATE_MAX]}),即树的双亲表示法来表示树,效率更高,当然,需要写更多代码。 +對於樹,如果用STL,可以用\fn{unordered_map father}表示一顆樹,代碼非常簡潔。如果能夠預估狀態總數的上限(設為STATE_MAX),可以用數組(\fn{state_t nodes[STATE_MAX]}),即樹的雙親表示法來表示樹,效率更高,當然,需要寫更多代碼。 -\subsubsection{如何表示状态} +\subsubsection{如何表示狀態} \begin{Codex}[label=bfs_common.h] -/** 状态 */ +/** 狀態 */ struct state_t { - int data1; /** 状态的数据,可以有多个字段. */ - int data2; /** 状态的数据,可以有多个字段. */ + int data1; /** 狀態的數據,可以有多個字段. */ + int data2; /** 狀態的數據,可以有多個字段. */ // dataN; /** 其他字段 */ - int action; /** 由父状态移动到本状态的动作,求动作序列时需要. */ - int level; /** 所在的层次(从0开始),也即路径长度-1,求路径长度时需要; - 不过,采用双队列时不需要本字段,只需全局设一个整数 */ + int action; /** 由父狀態移動到本狀態的動作,求動作序列時需要. */ + int level; /** 所在的層次(從0開始),也即路徑長度-1,求路徑長度時需要; + 不過,採用雙隊列時不需要本字段,只需全局設一個整數 */ bool operator==(const state_t &other) const { - return true; // 根据具体问题实现 + return true; // 根據具體問題實現 } }; -// 定义hash函数 +// 定義hash函數 -// 方法1:模板特化,当hash函数只需要状态本身,不需要其他数据时,用这个方法比较简洁 +// 方法1:模板特化,當hash函數只需要狀態本身,不需要其他數據時,用這個方法比較簡潔 namespace std { template<> struct hash { size_t operator()(const state_t & x) const { - return 0; // 根据具体问题实现 + return 0; // 根據具體問題實現 } }; } -// 方法2:函数对象,如果hash函数需要运行时数据,则用这种方法 +// 方法2:函數對象,如果hash函數需要運行時數據,則用這種方法 class Hasher { public: Hasher(int _m) : m(_m) {}; size_t operator()(const state_t &s) const { - return 0; // 根据具体问题实现 + return 0; // 根據具體問題實現 } private: - int m; // 存放外面传入的数据 + int m; // 存放外面傳入的數據 }; /** - * @brief 反向生成路径,求一条路径. - * @param[in] father 树 - * @param[in] target 目标节点 - * @return 从起点到target的路径 + * @brief 反向生成路徑,求一條路徑. + * @param[in] father 樹 + * @param[in] target 目標節點 + * @return 從起點到target的路徑 */ vector gen_path(const unordered_map &father, const state_t &target) { @@ -866,11 +1617,11 @@ \subsubsection{如何表示状态} } /** - * 反向生成路径,求所有路径. - * @param[in] father 存放了所有路径的树 - * @param[in] start 起点 - * @param[in] state 终点 - * @return 从起点到终点的所有路径 + * 反向生成路徑,求所有路徑. + * @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, @@ -901,31 +1652,31 @@ \subsubsection{如何表示状态} \end{Codex} -\subsubsection{求最短路径长度或一条路径} +\subsubsection{求最短路徑長度或一條路徑} -\textbf{单队列的写法} +\textbf{單隊列的寫法} \begin{Codex}[label=bfs_template.cpp] #include "bfs_common.h" /** - * @brief 广搜,只用一个队列. - * @param[in] start 起点 - * @param[in] data 输入数据 - * @return 从起点到目标状态的一条最短路径 + * @brief 廣搜,只用一個隊列. + * @param[in] start 起點 + * @param[in] data 輸入數據 + * @return 從起點到目標狀態的一條最短路徑 */ vector bfs(state_t &start, const vector> &grid) { - queue q; // 队列 + queue q; // 隊列 unordered_set visited; // 判重 - unordered_map father; // 树,求路径本身时才需要 + 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 (/*...*/) { @@ -941,27 +1692,27 @@ \subsubsection{求最短路径长度或一条路径} assert (start.level == 0); q.push(start); while (!q.empty()) { - // 千万不能用 const auto&,pop() 会删除元素, - // 引用就变成了悬空引用 + // 千萬不能用 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; // 求路径长度 + 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) + father[new_state] = state; // 求一條路徑 + // visited.insert(state); // 優化:可以提前加入 visited 集合, + // 從而縮小狀態擴展。這時 q 的含義略有變化,裏面存放的是處理了一半 + // 的節點:已經加入了visited,但還沒有擴展。別忘記 while循環開始 + // 前,要加一行代碼, visited.insert(start) } } @@ -971,30 +1722,30 @@ \subsubsection{求最短路径长度或一条路径} \end{Codex} -\textbf{双队列的写法} +\textbf{雙隊列的寫法} \begin{Codex}[label=bfs_template1.cpp] #include "bfs_common.h" /** - * @brief 广搜,使用两个队列. - * @param[in] start 起点 - * @param[in] data 输入数据 - * @return 从起点到目标状态的一条最短路径 + * @brief 廣搜,使用兩個隊列. + * @param[in] start 起點 + * @param[in] data 輸入數據 + * @return 從起點到目標狀態的一條最短路徑 */ vector bfs(const state_t &start, const type& data) { - queue next, current; // 当前层,下一层 + queue next, current; // 當前層,下一層 unordered_set visited; // 判重 - unordered_map father; // 树,求路径本身时才需要 + unordered_map father; // 樹,求路徑本身時才需要 - int level = -1; // 层次 + int level = -1; // 層次 - // 判断状态是否合法 + // 判斷狀態是否合法 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 (/*...*/) { @@ -1011,28 +1762,28 @@ \subsubsection{求最短路径长度或一条路径} while (!current.empty()) { ++level; while (!current.empty()) { - // 千万不能用 const auto&,pop() 会删除元素, - // 引用就变成了悬空引用 + // 千萬不能用 const auto&,pop() 會刪除元素, + // 引用就變成了懸空引用 const auto state = current.front(); current.pop(); visited.insert(state); if (state_is_target(state)) { - return return gen_path(father, state); // 求一条路径 - // return state.level + 1; // 求路径长度 + 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); father[new_state] = state; - // visited.insert(state); // 优化:可以提前加入 visited 集合, - // 从而缩小状态扩展。这时 current 的含义略有变化,里面存放的是处 - // 理了一半的节点:已经加入了visited,但还没有扩展。别忘记 while - // 循环开始前,要加一行代码, visited.insert(start) + // visited.insert(state); // 優化:可以提前加入 visited 集合, + // 從而縮小狀態擴展。這時 current 的含義略有變化,裏面存放的是處 + // 理了一半的節點:已經加入了visited,但還沒有擴展。別忘記 while + // 循環開始前,要加一行代碼, visited.insert(start) } } - swap(next, current); //!!! 交换两个队列 + swap(next, current); //!!! 交換兩個隊列 } return vector(); @@ -1041,16 +1792,16 @@ \subsubsection{求最短路径长度或一条路径} \end{Codex} -\subsubsection{求所有路径} +\subsubsection{求所有路徑} -\textbf{单队列} +\textbf{單隊列} \begin{Codex}[label=bfs_template.cpp] /** - * @brief 广搜,使用一个队列. - * @param[in] start 起点 - * @param[in] data 输入数据 - * @return 从起点到目标状态的所有最短路径 + * @brief 廣搜,使用一個隊列. + * @param[in] start 起點 + * @param[in] data 輸入數據 + * @return 從起點到目標狀態的所有最短路徑 */ vector > bfs(const state_t &start, const type& data) { queue q; @@ -1088,13 +1839,13 @@ \subsubsection{求所有路径} q.push(start_state); visited.insert(start_state); while (!q.empty()) { - // 千万不能用 const auto&,pop() 会删除元素, - // 引用就变成了悬空引用 + // 千萬不能用 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)) { @@ -1102,11 +1853,11 @@ \subsubsection{求所有路径} gen_path(father, start_state, state, path, result); continue; } - // 必须挪到下面,比如同一层A和B两个节点均指向了目标节点, - // 那么目标节点就会在q中出现两次,输出路径就会翻倍 + // 必須挪到下面,比如同一層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()) { @@ -1122,34 +1873,34 @@ \subsubsection{求所有路径} \end{Codex} -\textbf{双队列的写法} +\textbf{雙隊列的寫法} \begin{Codex}[label=bfs_template.cpp] #include "bfs_common.h" /** - * @brief 广搜,使用两个队列. - * @param[in] start 起点 - * @param[in] data 输入数据 - * @return 从起点到目标状态的所有最短路径 + * @brief 廣搜,使用兩個隊列. + * @param[in] start 起點 + * @param[in] data 輸入數據 + * @return 從起點到目標狀態的所有最短路徑 */ vector > bfs(const state_t &start, const type& data) { - // 当前层,下一层,用unordered_set是为了去重,例如两个父节点指向 - // 同一个子节点,如果用vector, 子节点就会在next里出现两次,其实此 - // 时 father 已经记录了两个父节点,next里重复出现两次是没必要的 + // 當前層,下一層,用unordered_set是為了去重,例如兩個父節點指向 + // 同一個子節點,如果用vector, 子節點就會在next裏出現兩次,其實此 + // 時 father 已經記錄了兩個父節點,next裏重複出現兩次是沒必要的 unordered_set current, next; unordered_set visited; // 判重 unordered_map > father; // DAG - int level = -1; // 层次 + int level = -1; // 層次 - // 判断状态是否合法 + // 判斷狀態是否合法 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 (/*...*/) { @@ -1166,13 +1917,13 @@ \subsubsection{求所有路径} current.insert(start); while (!current.empty()) { ++ level; - // 如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的 - // 处理,因为我们要找的是最短路径 + // 如果當前路徑長度已經超過當前最短路徑長度,可以中止對該路徑的 + // 處理,因為我們要找的是最短路徑 if (!result.empty() && level+1 > result[0].size()) break; - // 1. 延迟加入visited, 这样才能允许两个父节点指向同一个子节点 - // 2. 一股脑current 全部加入visited, 是防止本层前一个节点扩展 - // 节点时,指向了本层后面尚未处理的节点,这条路径必然不是最短的 + // 1. 延遲加入visited, 這樣才能允許兩個父節點指向同一個子節點 + // 2. 一股腦current 全部加入visited, 是防止本層前一個節點擴展 + // 節點時,指向了本層後面尚未處理的節點,這條路徑必然不是最短的 for (const auto& state : current) visited.insert(state); for (const auto& state : current) { diff --git a/C++/chapBruteforce.tex b/C++/chapBruteforce.tex index 4c9e4037..dfbf9c2b 100644 --- a/C++/chapBruteforce.tex +++ b/C++/chapBruteforce.tex @@ -1,4 +1,4 @@ -\chapter{暴力枚举法} +\chapter{暴力枚舉法} \section{Subsets} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -29,19 +29,19 @@ \subsubsection{描述} \end{Code} -\subsection{递归} +\subsection{遞歸} -\subsubsection{增量构造法} -每个元素,都有两种选择,选或者不选。 +\subsubsection{增量構造法1} +每個元素,都有兩種選擇,選或者不選。 \begin{Code} // LeetCode, Subsets -// 增量构造法,深搜,时间复杂度O(2^n),空间复杂度O(n) +// 增量構造法,深搜,時間複雜度O(2^n),空間複雜度O(n) class Solution { public: vector > subsets(vector &S) { - sort(S.begin(), S.end()); // 输出要求有序 + sort(S.begin(), S.end()); // 輸出要求有序 vector > result; vector path; subsets(S, path, 0, result); @@ -55,9 +55,9 @@ \subsubsection{增量构造法} result.push_back(path); return; } - // 不选S[step] + // 不選S[step] subsets(S, path, step + 1, result); - // 选S[step] + // 選S[step] path.push_back(S[step]); subsets(S, path, step + 1, result); path.pop_back(); @@ -65,17 +65,48 @@ \subsubsection{增量构造法} }; \end{Code} +\subsubsection{增量構造法2} +每個元素,都有兩種選擇,選或者不選。 + +\begin{Code} +// LeetCode, Subsets +// 增量構造法,深搜,時間複雜度O(2^n),空間複雜度O(n) +class Solution { +public: + vector > subsets(vector &S) { + sort(S.begin(), S.end()); // 輸出要求有序 + vector > result; + vector path; + subsets(S, path, 0, result); + return result; + } + +private: + static void subsets(const vector &S, vector &path, int step, + vector > &result) { + result.push_back(path); + + for (int i = step; i < S.size(); i++) { + // 選S[step] + path.push_back(S[i]); + subsets(S, path, i + 1, result); + // 不選S[step] + path.pop_back(); + } + } +}; +\end{Code} \subsubsection{位向量法} -开一个位向量\fn{bool selected[n]},每个元素可以选或者不选。 +開一個位向量\fn{bool selected[n]},每個元素可以選或者不選。 \begin{Code} // LeetCode, Subsets -// 位向量法,深搜,时间复杂度O(2^n),空间复杂度O(n) +// 位向量法,深搜,時間複雜度O(2^n),空間複雜度O(n) class Solution { public: vector > subsets(vector &S) { - sort(S.begin(), S.end()); // 输出要求有序 + sort(S.begin(), S.end()); // 輸出要求有序 vector > result; vector selected(S.size(), false); @@ -94,10 +125,10 @@ \subsubsection{位向量法} result.push_back(subset); return; } - // 不选S[step] + // 不選S[step] selected[step] = false; subsets(S, selected, step + 1, result); - // 选S[step] + // 選S[step] selected[step] = true; subsets(S, selected, step + 1, result); } @@ -108,14 +139,14 @@ \subsubsection{位向量法} \subsection{迭代} -\subsubsection{增量构造法} +\subsubsection{增量構造法} \begin{Code} // LeetCode, Subsets -// 迭代版,时间复杂度O(2^n),空间复杂度O(1) +// 迭代版,時間複雜度O(2^n),空間複雜度O(1) class Solution { public: vector > subsets(vector &S) { - sort(S.begin(), S.end()); // 输出要求有序 + sort(S.begin(), S.end()); // 輸出要求有序 vector > result(1); for (auto elem : S) { result.reserve(result.size() * 2); @@ -131,20 +162,20 @@ \subsubsection{增量构造法} \end{Code} -\subsubsection{二进制法} -本方法的前提是:集合的元素不超过int位数。用一个int整数表示位向量,第$i$位为1,则表示选择$S[i]$,为0则不选择。例如\fn{S=\{A,B,C,D\}},则\fn{0110=6}表示子集\fn{\{B,C\}}。 +\subsubsection{二進制法} +本方法的前提是:集合的元素不超過int位數。用一個int整數表示位向量,第$i$位為1,則表示選擇$S[i]$,為0則不選擇。例如\fn{S=\{A,B,C,D\}},則\fn{0110=6}表示子集\fn{\{B,C\}}。 -这种方法最巧妙。因为它不仅能生成子集,还能方便的表示集合的并、交、差等集合运算。设两个集合的位向量分别为$B_1$和$B_2$,则$B_1\cup B_2, B_1 \cap B_2, B_1 \triangle B_2$分别对应集合的并、交、对称差。 +這種方法最巧妙。因為它不僅能生成子集,還能方便的表示集合的並、交、差等集合運算。設兩個集合的位向量分別為$B_1$和$B_2$,則$B_1\cup B_2, B_1 \cap B_2, B_1 \triangle B_2$分別對應集合的並、交、對稱差。 -二进制法,也可以看做是位向量法,只不过更加优化。 +二進制法,也可以看做是位向量法,只不過更加優化。 \begin{Code} // LeetCode, Subsets -// 二进制法,时间复杂度O(2^n),空间复杂度O(1) +// 二進制法,時間複雜度O(2^n),空間複雜度O(1) class Solution { public: vector > subsets(vector &S) { - sort(S.begin(), S.end()); // 输出要求有序 + sort(S.begin(), S.end()); // 輸出要求有序 vector > result; const size_t n = S.size(); vector v; @@ -162,9 +193,9 @@ \subsubsection{二进制法} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Subsets II,见 \S \ref{sec:subsets-ii} +\item Subsets II,見 \S \ref{sec:subsets-ii} \myenddot @@ -194,20 +225,20 @@ \subsubsection{描述} \subsubsection{分析} -这题有重复元素,但本质上,跟上一题很类似,上一题中元素没有重复,相当于每个元素只能选0或1次,这里扩充到了每个元素可以选0到若干次而已。 +這題有重複元素,但本質上,跟上一題很類似,上一題中元素沒有重複,相當於每個元素只能選0或1次,這裏擴充到了每個元素可以選0到若干次而已。 -\subsection{递归} +\subsection{遞歸} -\subsubsection{增量构造法} +\subsubsection{增量構造法} \begin{Code} // LeetCode, Subsets II -// 增量构造法,版本1,时间复杂度O(2^n),空间复杂度O(n) +// 增量構造法,版本1,時間複雜度O(2^n),空間複雜度O(n) class Solution { public: vector > subsetsWithDup(vector &S) { - sort(S.begin(), S.end()); // 必须排序 + sort(S.begin(), S.end()); // 必須排序 vector > result; vector path; @@ -233,14 +264,14 @@ \subsubsection{增量构造法} \begin{Code} // LeetCode, Subsets II -// 增量构造法,版本2,时间复杂度O(2^n),空间复杂度O(n) +// 增量構造法,版本2,時間複雜度O(2^n),空間複雜度O(n) class Solution { public: vector > subsetsWithDup(vector &S) { vector > result; - sort(S.begin(), S.end()); // 必须排序 + sort(S.begin(), S.end()); // 必須排序 - unordered_map count_map; // 记录每个元素的出现次数 + unordered_map count_map; // 記錄每個元素的出現次數 for_each(S.begin(), S.end(), [&count_map](int e) { if (count_map.find(e) != count_map.end()) count_map[e]++; @@ -248,14 +279,14 @@ \subsubsection{增量构造法} count_map[e] = 1; }); - // 将map里的pair拷贝到一个vector里 + // 將map裏的pair拷貝到一個vector裏 vector > elems; for_each(count_map.begin(), count_map.end(), [&elems](const pair &e) { elems.push_back(e); }); sort(elems.begin(), elems.end()); - vector path; // 中间结果 + vector path; // 中間結果 subsets(elems, 0, path, result); return result; @@ -286,19 +317,19 @@ \subsubsection{增量构造法} \subsubsection{位向量法} \begin{Code} // LeetCode, Subsets II -// 位向量法,时间复杂度O(2^n),空间复杂度O(n) +// 位向量法,時間複雜度O(2^n),空間複雜度O(n) class Solution { public: vector > subsetsWithDup(vector &S) { - vector > result; // 必须排序 + vector > result; // 必須排序 sort(S.begin(), S.end()); vector count(S.back() - S.front() + 1, 0); - // 计算所有元素的个数 + // 計算所有元素的個數 for (auto i : S) { count[i - S[0]]++; } - // 每个元素选择了多少个 + // 每個元素選擇了多少個 vector selected(S.back() - S.front() + 1, -1); subsets(S, count, selected, 0, result); @@ -331,15 +362,15 @@ \subsubsection{位向量法} \subsection{迭代} -\subsubsection{增量构造法} +\subsubsection{增量構造法} \begin{Code} // LeetCode, Subsets II -// 增量构造法 -// 时间复杂度O(2^n),空间复杂度O(1) +// 增量構造法 +// 時間複雜度O(2^n),空間複雜度O(1) class Solution { public: vector > subsetsWithDup(vector &S) { - sort(S.begin(), S.end()); // 必须排序 + sort(S.begin(), S.end()); // 必須排序 vector > result(1); size_t previous_size = 0; @@ -359,15 +390,15 @@ \subsubsection{增量构造法} \end{Code} -\subsubsection{二进制法} +\subsubsection{二進制法} \begin{Code} // LeetCode, Subsets II -// 二进制法,时间复杂度O(2^n),空间复杂度O(1) +// 二進制法,時間複雜度O(2^n),空間複雜度O(1) class Solution { public: vector > subsetsWithDup(vector &S) { - sort(S.begin(), S.end()); // 必须排序 - // 用 set 去重,不能用 unordered_set,因为输出要求有序 + sort(S.begin(), S.end()); // 必須排序 + // 用 set 去重,不能用 unordered_set,因為輸出要求有序 set > result; const size_t n = S.size(); vector v; @@ -388,9 +419,9 @@ \subsubsection{二进制法} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Subsets,见 \S \ref{sec:subsets} +\item Subsets,見 \S \ref{sec:subsets} \myenddot @@ -407,12 +438,12 @@ \subsubsection{描述} \subsection{next_permutation()} -偷懒的做法,可以直接使用\fn{std::next_permutation()}。如果是在OJ网站上,可以用这个API偷个懒;如果是在面试中,面试官肯定会让你重新实现。 +偷懶的做法,可以直接使用\fn{std::next_permutation()}。如果是在OJ網站上,可以用這個API偷個懶;如果是在面試中,面試官肯定會讓你重新實現。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Permutations -// 时间复杂度O(n!),空间复杂度O(1) +// 時間複雜度O(n!),空間複雜度O(1) class Solution { public: vector > permute(vector &num) { @@ -428,15 +459,15 @@ \subsubsection{代码} \end{Code} -\subsection{重新实现next_permutation()} -见第 \S \ref{sec:next-permutation} 节。 +\subsection{重新實現next_permutation()} +見第 \S \ref{sec:next-permutation} 節。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Permutations -// 重新实现 next_permutation() -// 时间复杂度O(n!),空间复杂度O(1) +// 重新實現 next_permutation() +// 時間複雜度O(n!),空間複雜度O(1) class Solution { public: vector > permute(vector &num) { @@ -445,7 +476,7 @@ \subsubsection{代码} do { result.push_back(num); - // 调用的是 2.1.12 节的 next_permutation() + // 調用的是 2.1.12 節的 next_permutation() // 而不是 std::next_permutation() } while(next_permutation(num.begin(), num.end())); return result; @@ -454,25 +485,25 @@ \subsubsection{代码} \end{Code} -\subsection{递归} -本题是求路径本身,求所有解,函数参数需要标记当前走到了哪步,还需要中间结果的引用,最终结果的引用。 +\subsection{遞歸} +本題是求路徑本身,求所有解,函數參數需要標記當前走到了哪步,還需要中間結果的引用,最終結果的引用。 -扩展节点,每次从左到右,选一个没有出现过的元素。 +擴展節點,每次從左到右,選一個沒有出現過的元素。 -本题不需要判重,因为状态装换图是一颗有层次的树。收敛条件是当前走到了最后一个元素。 +本題不需要判重,因為狀態裝換圖是一顆有層次的樹。收斂條件是當前走到了最後一個元素。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Permutations -// 深搜,增量构造法 -// 时间复杂度O(n!),空间复杂度O(n) +// 深搜,增量構造法 +// 時間複雜度O(n!),空間複雜度O(n) class Solution { public: vector > permute(vector& num) { sort(num.begin(), num.end()); vector> result; - vector path; // 中间结果 + vector path; // 中間結果 dfs(num, path, result); return result; @@ -480,14 +511,14 @@ \subsubsection{代码} private: void dfs(const vector& num, vector &path, vector > &result) { - if (path.size() == num.size()) { // 收敛条件 + if (path.size() == num.size()) { // 收斂條件 result.push_back(path); return; } - // 扩展状态 + // 擴展狀態 for (auto i : num) { - // 查找 i 是否在path 中出现过 + // 查找 i 是否在path 中出現過 auto pos = find(path.begin(), path.end(), i); if (pos == path.end()) { @@ -501,12 +532,12 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Next Permutation, 见 \S \ref{sec:next-permutation} -\item Permutation Sequence, 见 \S \ref{sec:permutation-sequence} -\item Permutations II, 见 \S \ref{sec:permutations-ii} -\item Combinations, 见 \S \ref{sec:combinations} +\item Next Permutation, 見 \S \ref{sec:next-permutation} +\item Permutation Sequence, 見 \S \ref{sec:permutation-sequence} +\item Permutations II, 見 \S \ref{sec:permutations-ii} +\item Combinations, 見 \S \ref{sec:combinations} \myenddot @@ -523,31 +554,31 @@ \subsubsection{描述} \subsection{next_permutation()} -直接使用\fn{std::next_permutation()},代码与上一题相同。 +直接使用\fn{std::next_permutation()},代碼與上一題相同。 -\subsection{重新实现next_permutation()} -重新实现\fn{std::next_permutation()},代码与上一题相同。 +\subsection{重新實現next_permutation()} +重新實現\fn{std::next_permutation()},代碼與上一題相同。 -\subsection{递归} -递归函数\fn{permute()}的参数\fn{p},是中间结果,它的长度又能标记当前走到了哪一步,用于判断收敛条件。 +\subsection{遞歸} +遞歸函數\fn{permute()}的參數\fn{p},是中間結果,它的長度又能標記當前走到了哪一步,用於判斷收斂條件。 -扩展节点,每次从小到大,选一个没有被用光的元素,直到所有元素被用光。 +擴展節點,每次從小到大,選一個沒有被用光的元素,直到所有元素被用光。 -本题不需要判重,因为状态装换图是一颗有层次的树。 +本題不需要判重,因為狀態裝換圖是一顆有層次的樹。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Permutations II -// 深搜,时间复杂度O(n!),空间复杂度O(n) +// 深搜,時間複雜度O(n!),空間複雜度O(n) class Solution { public: vector > permuteUnique(vector& num) { sort(num.begin(), num.end()); - unordered_map count_map; // 记录每个元素的出现次数 + unordered_map count_map; // 記錄每個元素的出現次數 for_each(num.begin(), num.end(), [&count_map](int e) { if (count_map.find(e) != count_map.end()) count_map[e]++; @@ -555,15 +586,15 @@ \subsubsection{代码} count_map[e] = 1; }); - // 将map里的pair拷贝到一个vector里 + // 將map裏的pair拷貝到一個vector裏 vector > elems; for_each(count_map.begin(), count_map.end(), [&elems](const pair &e) { elems.push_back(e); }); - vector> result; // 最终结果 - vector p; // 中间结果 + vector> result; // 最終結果 + vector p; // 中間結果 n = num.size(); permute(elems.begin(), elems.end(), p, result); @@ -576,13 +607,13 @@ \subsubsection{代码} void permute(Iter first, Iter last, vector &p, vector > &result) { - if (n == p.size()) { // 收敛条件 + if (n == p.size()) { // 收斂條件 result.push_back(p); } - // 扩展状态 + // 擴展狀態 for (auto i = first; i != last; i++) { - int count = 0; // 统计 *i 在p中出现过多少次 + int count = 0; // 統計 *i 在p中出現過多少次 for (auto j = p.begin(); j != p.end(); j++) { if (i->first == *j) { count ++; @@ -591,7 +622,7 @@ \subsubsection{代码} if (count < i->second) { p.push_back(i->first); permute(first, last, p, result); - p.pop_back(); // 撤销动作,返回上一层 + p.pop_back(); // 撤銷動作,返回上一層 } } } @@ -599,12 +630,12 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Next Permutation, 见 \S \ref{sec:next-permutation} -\item Permutation Sequence, 见 \S \ref{sec:permutation-sequence} -\item Permutations, 见 \S \ref{sec:permutations} -\item Combinations, 见 \S \ref{sec:combinations} +\item Next Permutation, 見 \S \ref{sec:next-permutation} +\item Permutation Sequence, 見 \S \ref{sec:permutation-sequence} +\item Permutations, 見 \S \ref{sec:permutations} +\item Combinations, 見 \S \ref{sec:combinations} \myenddot @@ -629,11 +660,11 @@ \subsubsection{描述} \end{Code} -\subsection{递归} +\subsection{遞歸} \begin{Code} // LeetCode, Combinations -// 深搜,递归 -// 时间复杂度O(n!),空间复杂度O(n) +// 深搜,遞歸 +// 時間複雜度O(n!),空間複雜度O(n) class Solution { public: vector > combine(int n, int k) { @@ -643,7 +674,7 @@ \subsection{递归} return result; } private: - // start,开始的数, cur,已经选择的数目 + // start,開始的數, cur,已經選擇的數目 static void dfs(int n, int k, int start, int cur, vector &path, vector > &result) { if (cur == k) { @@ -663,7 +694,7 @@ \subsection{迭代} \begin{Code} // LeetCode, Combinations // use prev_permutation() -// 时间复杂度O((n-k)!),空间复杂度O(n) +// 時間複雜度O((n-k)!),空間複雜度O(n) class Solution { public: vector > combine(int n, int k) { @@ -687,12 +718,12 @@ \subsection{迭代} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Next Permutation, 见 \S \ref{sec:next-permutation} -\item Permutation Sequence, 见 \S \ref{sec:permutation-sequence} -\item Permutations, 见 \S \ref{sec:permutations} -\item Permutations II, 见 \S \ref{sec:permutations-ii} +\item Next Permutation, 見 \S \ref{sec:next-permutation} +\item Permutation Sequence, 見 \S \ref{sec:permutation-sequence} +\item Permutations, 見 \S \ref{sec:permutations} +\item Permutations II, 見 \S \ref{sec:permutations-ii} \myenddot @@ -712,20 +743,20 @@ \subsubsection{描述} \textbf{Input:}Digit string \code{"23"} -\textbf{Output:} \code{["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]}. +\textbf{Output:} \code{\["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"\]}. \textbf{Note:} Although the above answer is in lexicographical order, your answer could be in any order you want. \subsubsection{分析} -无 +無 -\subsection{递归} +\subsection{遞歸} \begin{Code} // LeetCode, Letter Combinations of a Phone Number -// 时间复杂度O(3^n),空间复杂度O(n) +// 時間複雜度O(3^n),空間複雜度O(n) class Solution { public: const vector keyboard { " ", "", "abc", "def", // '0','1','2',... @@ -755,7 +786,7 @@ \subsection{递归} \subsection{迭代} \begin{Code} // LeetCode, Letter Combinations of a Phone Number -// 时间复杂度O(3^n),空间复杂度O(1) +// 時間複雜度O(3^n),空間複雜度O(1) class Solution { public: const vector keyboard { " ", "", "abc", "def", // '0','1','2',... @@ -785,7 +816,7 @@ \subsection{迭代} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot diff --git a/C++/chapDFS.tex b/C++/chapDFS.tex index f6c4c107..96a80e1a 100644 --- a/C++/chapDFS.tex +++ b/C++/chapDFS.tex @@ -1,4 +1,4 @@ -\chapter{深度优先搜索} +\chapter{深度優先搜索} \section{Palindrome Partitioning} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -21,40 +21,40 @@ \subsubsection{描述} \subsubsection{分析} -在每一步都可以判断中间结果是否为合法结果,用回溯法。 +在每一步都可以判斷中間結果是否為合法結果,用回溯法。 -一个长度为n的字符串,有$n-1$个地方可以砍断,每个地方可断可不断,因此复杂度为$O(2^{n-1})$ +一個長度為n的字符串,有$n-1$個地方可以砍斷,每個地方可斷可不斷,因此複雜度為$O(2^{n-1})$ \subsubsection{深搜1} \begin{Code} //LeetCode, Palindrome Partitioning -// 时间复杂度O(2^n),空间复杂度O(n) +// 時間複雜度O(2^n),空間複雜度O(n) class Solution { public: vector> partition(string s) { vector> result; - vector path; // 一个partition方案 + vector path; // 一個partition方案 dfs(s, path, result, 0, 1); return result; } - // prev 表示前一个隔板, start 表示当前隔板 + // prev 表示前一個隔板, start 表示當前隔板 void dfs(string &s, vector& path, vector> &result, size_t prev, size_t start) { - if (start == s.size()) { // 最后一个隔板 - if (isPalindrome(s, prev, start - 1)) { // 必须使用 + if (start == s.size()) { // 最後一個隔板 + if (isPalindrome(s, prev, start - 1)) { // 必須使用 path.push_back(s.substr(prev, start - prev)); result.push_back(path); path.pop_back(); } return; } - // 不断开 + // 不斷開 dfs(s, path, result, prev, start + 1); - // 如果[prev, start-1] 是回文,则可以断开,也可以不断开(上一行已经做了) + // 如果[prev, start-1] 是迴文,則可以斷開,也可以不斷開(上一行已經做了) if (isPalindrome(s, prev, start - 1)) { - // 断开 + // 斷開 path.push_back(s.substr(prev, start - prev)); dfs(s, path, result, start, start + 1); path.pop_back(); @@ -72,19 +72,19 @@ \subsubsection{深搜1} \end{Code} \subsubsection{深搜2} -另一种写法,更加简洁。这种写法也在 Combination Sum, Combination Sum II 中出现过。 +另一種寫法,更加簡潔。這種寫法也在 Combination Sum, Combination Sum II 中出現過。 \begin{Code} //LeetCode, Palindrome Partitioning -// 时间复杂度O(2^n),空间复杂度O(n) +// 時間複雜度O(2^n),空間複雜度O(n) class Solution { public: vector> partition(string s) { vector> result; - vector path; // 一个partition方案 + vector path; // 一個partition方案 DFS(s, path, result, 0); return result; } - // 搜索必须以s[start]开头的partition方案 + // 搜索必須以s[start]開頭的partition方案 void DFS(string &s, vector& path, vector> &result, int start) { if (start == s.size()) { @@ -92,10 +92,10 @@ \subsubsection{深搜2} return; } for (int i = start; i < s.size(); i++) { - if (isPalindrome(s, start, i)) { // 从i位置砍一刀 + if (isPalindrome(s, start, i)) { // 從i位置砍一刀 path.push_back(s.substr(start, i - start + 1)); - DFS(s, path, result, i + 1); // 继续往下砍 - path.pop_back(); // 撤销上上行 + DFS(s, path, result, i + 1); // 繼續往下砍 + path.pop_back(); // 撤銷上上行 } } } @@ -109,11 +109,16 @@ \subsubsection{深搜2} }; \end{Code} +\begin{center} +\includegraphics[width=250pt]{palindrome-partitioning.png}\\ +\figcaption{example}\label{fig:palindrome-partitioning} +\end{center} + -\subsubsection{动规} +\subsubsection{動規} \begin{Code} // LeetCode, Palindrome Partitioning -// 动规,时间复杂度O(n^2),空间复杂度O(1) +// 動規,時間複雜度O(n^2),空間複雜度O(1) class Solution { public: vector > partition(string s) { @@ -124,7 +129,7 @@ \subsubsection{动规} for (int j = i; j < n; ++j) p[i][j] = s[i] == s[j] && ((j - i < 2) || p[i + 1][j - 1]); - vector > sub_palins[n]; // sub palindromes of s[0,i] + vector > sub_palins[n]; // sub palindromes of s[i,n] for (int i = n - 1; i >= 0; --i) { for (int j = i; j < n; ++j) if (p[i][j]) { @@ -145,10 +150,10 @@ \subsubsection{动规} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Palindrome Partitioning II,见 \S \ref{sec:palindrome-partitioning-ii} +\item Palindrome Partitioning II,見 \S \ref{sec:palindrome-partitioning-ii} \myenddot @@ -172,19 +177,19 @@ \subsubsection{描述} \subsection{深搜} -深搜,小集合可以过,大集合会超时 +深搜,小集合可以過,大集合會超時 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Unique Paths -// 深搜,小集合可以过,大集合会超时 -// 时间复杂度O(n^4),空间复杂度O(n) +// 深搜,小集合可以過,大集合會超時 +// 時間複雜度O(n^4),空間複雜度O(n) class Solution { public: int uniquePaths(int m, int n) { - if (m < 1 || n < 1) return 0; // 终止条件 + if (m < 1 || n < 1) return 0; // 終止條件 - if (m == 1 && n == 1) return 1; // 收敛条件 + if (m == 1 && n == 1) return 1; // 收斂條件 return uniquePaths(m - 1, n) + uniquePaths(m, n - 1); } @@ -192,29 +197,29 @@ \subsubsection{代码} \end{Code} -\subsection{备忘录法} -给前面的深搜,加个缓存,就可以过大集合了。即备忘录法。 +\subsection{備忘錄法} +給前面的深搜,加個緩存,就可以過大集合了。即備忘錄法。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Unique Paths -// 深搜 + 缓存,即备忘录法 -// 时间复杂度O(n^2),空间复杂度O(n^2) +// 深搜 + 緩存,即備忘錄法 +// 時間複雜度O(n^2),空間複雜度O(n^2) class Solution { public: int uniquePaths(int m, int n) { - // f[x][y] 表示 从(0,0)到(x,y)的路径条数 + // 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; // 缓存 + vector > f; // 緩存 int dfs(int x, int y) { - if (x < 0 || y < 0) return 0; // 数据非法,终止条件 + if (x < 0 || y < 0) return 0; // 數據非法,終止條件 - if (x == 0 && y == 0) return f[0][0]; // 回到起点,收敛条件 + if (x == 0 && y == 0) return f[0][0]; // 回到起點,收斂條件 if (f[x][y] > 0) { return f[x][y]; @@ -226,20 +231,20 @@ \subsubsection{代码} \end{Code} -\subsection{动规} -既然可以用备忘录法自顶向下解决,也一定可以用动规自底向上解决。 +\subsection{動規} +既然可以用備忘錄法自頂向下解決,也一定可以用動規自底向上解決。 -设状态为\fn{f[i][j]},表示从起点$(1,1)$到达$(i,j)$的路线条数,则状态转移方程为: +設狀態為\fn{f[i][j]},表示從起點$(1,1)$到達$(i,j)$的路線條數,則狀態轉移方程為: \begin{Code} f[i][j]=f[i-1][j]+f[i][j-1] \end{Code} -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Unique Paths -// 动规,滚动数组 -// 时间复杂度O(n^2),空间复杂度O(n) +// 動規,滾動數組 +// 時間複雜度O(n^2),空間複雜度O(n) class Solution { public: int uniquePaths(int m, int n) { @@ -247,8 +252,8 @@ \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-1][j]对应 + // 左邊的f[j],表示更新後的f[j],與公式中的f[i][j]對應 + // 右邊的f[j],表示老的f[j],與公式中的f[i-1][j]對應 f[j] = f[j] + f[j - 1]; } } @@ -258,26 +263,26 @@ \subsubsection{代码} \end{Code} -\subsection{数学公式} -一个$m$行,$n$列的矩阵,机器人从左上走到右下总共需要的步数是$m+n-2$,其中向下走的步数是$m-1$,因此问题变成了在$m+n-2$个操作中,选择$m–1$个时间点向下走,选择方式有多少种。即 $C_{m+n-2}^{m-1}$ 。 +\subsection{數學公式} +一個$m$行,$n$列的矩陣,機器人從左上走到右下總共需要的步數是$m+n-2$,其中向下走的步數是$m-1$,因此問題變成了在$m+n-2$個操作中,選擇$m–1$個時間點向下走,選擇方式有多少種。即 $C_{m+n-2}^{m-1}$ 。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Unique Paths -// 数学公式 +// 數學公式 class Solution { public: typedef long long int64_t; - // 求阶乘, n!/(start-1)!,即 n*(n-1)...start,要求 n >= 1 + // 求階乘, n!/(start-1)!,即 n*(n-1)...start,要求 n >= 1 static int64_t factor(int n, int start = 1) { int64_t ret = 1; for(int i = start; i <= n; ++i) ret *= i; return ret; } - // 求组合数 C_n^k + // 求組合數 C_n^k static int64_t combination(int n, int k) { - // 常数优化 + // 常數優化 if (k == 0) return 1; if (k == 1) return n; @@ -287,17 +292,17 @@ \subsubsection{代码} } int uniquePaths(int m, int n) { - // max 可以防止n和k差距过大,从而防止combination()溢出 + // max 可以防止n和k差距過大,從而防止combination()溢出 return combination(m+n-2, max(m-1, n-1)); } }; \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Unique Paths II,见 \S \ref{sec:unique-paths-ii} -\item Minimum Path Sum, 见 \S \ref{sec:minimum-path-sum} +\item Unique Paths II,見 \S \ref{sec:unique-paths-ii} +\item Minimum Path Sum, 見 \S \ref{sec:minimum-path-sum} \myenddot @@ -328,13 +333,13 @@ \subsubsection{描述} Note: $m$ and $n$ will be at most 100. -\subsection{备忘录法} -在上一题的基础上改一下即可。相比动规,简单得多。 +\subsection{備忘錄法} +在上一題的基礎上改一下即可。相比動規,簡單得多。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Unique Paths II -// 深搜 + 缓存,即备忘录法 +// 深搜 + 緩存,即備忘錄法 class Solution { public: int uniquePathsWithObstacles(const vector >& obstacleGrid) { @@ -347,17 +352,17 @@ \subsubsection{代码} return dfs(obstacleGrid, m - 1, n - 1); } private: - vector > f; // 缓存 + vector > f; // 緩存 - // @return 从 (0, 0) 到 (x, y) 的路径总数 + // @return 從 (0, 0) 到 (x, y) 的路徑總數 int dfs(const vector >& obstacleGrid, int x, int y) { - if (x < 0 || y < 0) return 0; // 数据非法,终止条件 + if (x < 0 || y < 0) return 0; // 數據非法,終止條件 - // (x,y)是障碍 + // (x,y)是障礙 if (obstacleGrid[x][y]) return 0; - if (x == 0 and y == 0) return f[0][0]; // 回到起点,收敛条件 + if (x == 0 and y == 0) return f[0][0]; // 回到起點,收斂條件 if (f[x][y] > 0) { return f[x][y]; @@ -370,15 +375,15 @@ \subsubsection{代码} \end{Code} -\subsection{动规} -与上一题类似,但要特别注意第一列的障碍。在上一题中,第一列全部是1,但是在这一题中不同,第一列如果某一行有障碍物,那么后面的行全为0。 +\subsection{動規} +與上一題類似,但要特別注意第一列的障礙。在上一題中,第一列全部是1,但是在這一題中不同,第一列如果某一行有障礙物,那麼後面的行全為0。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Unique Paths II -// 动规,滚动数组 -// 时间复杂度O(n^2),空间复杂度O(n) +// 動規,滾動數組 +// 時間複雜度O(n^2),空間複雜度O(n) class Solution { public: int uniquePathsWithObstacles(vector > &obstacleGrid) { @@ -401,10 +406,10 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Unique Paths,见 \S \ref{sec:unique-paths} -\item Minimum Path Sum, 见 \S \ref{sec:minimum-path-sum} +\item Unique Paths,見 \S \ref{sec:unique-paths} +\item Minimum Path Sum, 見 \S \ref{sec:minimum-path-sum} \myenddot @@ -443,28 +448,28 @@ \subsubsection{描述} \subsubsection{分析} -经典的深搜题。 +經典的深搜題。 -设置一个数组 \fn{vector C(n, 0)}, \fn{C[i]} 表示第i行皇后所在的列编号,即在位置 (i, C[i]) 上放了一个皇后,这样用一个一维数组,就能记录整个棋盘。 +設置一個數組 \fn{vector C(n, 0)}, \fn{C[i]} 表示第i行皇后所在的列編號,即在位置 (i, C[i]) 上放了一個皇后,這樣用一個一維數組,就能記錄整個棋盤。 -\subsubsection{代码1} +\subsubsection{代碼1} \begin{Code} // LeetCode, N-Queens // 深搜+剪枝 -// 时间复杂度O(n!*n),空间复杂度O(n) +// 時間複雜度O(n!*n),空間複雜度O(n) class Solution { public: vector > solveNQueens(int n) { vector > result; - vector C(n, -1); // C[i]表示第i行皇后所在的列编号 + 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) { // 终止条件,也是收敛条件,意味着找到了一个可行解 + if (row == N) { // 終止條件,也是收斂條件,意味着找到了一個可行解 vector solution; for (int i = 0; i < N; ++i) { string s(N, '.'); @@ -477,30 +482,30 @@ \subsubsection{代码1} return; } - for (int j = 0; j < N; ++j) { // 扩展状态,一列一列的试 + for (int j = 0; j < N; ++j) { // 擴展狀態,一列一列的試 const bool ok = isValid(C, row, j); - if (!ok) continue; // 剪枝,如果非法,继续尝试下一列 - // 执行扩展动作 + if (!ok) continue; // 剪枝,如果非法,繼續嘗試下一列 + // 執行擴展動作 C[row] = j; dfs(C, result, row + 1); - // 撤销动作 + // 撤銷動作 // C[row] = -1; } } /** - * 能否在 (row, col) 位置放一个皇后. + * 能否在 (row, col) 位置放一個皇后. * * @param C 棋局 - * @param row 当前正在处理的行,前面的行都已经放了皇后了 - * @param col 当前列 - * @return 能否放一个皇后 + * @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; @@ -509,11 +514,11 @@ \subsubsection{代码1} \end{Code} -\subsubsection{代码2} +\subsubsection{代碼2} \begin{Code} // LeetCode, N-Queens // 深搜+剪枝 -// 时间复杂度O(n!),空间复杂度O(n) +// 時間複雜度O(n!),空間複雜度O(n) class Solution { public: vector > solveNQueens(int n) { @@ -522,19 +527,19 @@ \subsubsection{代码2} this->anti_diag = vector(2 * n - 1, false); vector > result; - vector C(n, -1); // 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(); - if (row == N) { // 终止条件,也是收敛条件,意味着找到了一个可行解 + if (row == N) { // 終止條件,也是收斂條件,意味着找到了一個可行解 vector solution; for (int i = 0; i < N; ++i) { string s(N, '.'); @@ -547,15 +552,15 @@ \subsubsection{代码2} return; } - for (int j = 0; j < N; ++j) { // 扩展状态,一列一列的试 + for (int j = 0; j < N; ++j) { // 擴展狀態,一列一列的試 const bool ok = !columns[j] && !main_diag[row - j + N - 1] && !anti_diag[row + j]; - if (!ok) continue; // 剪枝,如果非法,继续尝试下一列 - // 执行扩展动作 + if (!ok) continue; // 剪枝,如果非法,繼續嘗試下一列 + // 執行擴展動作 C[row] = j; columns[j] = main_diag[row - j + N - 1] = anti_diag[row + j] = true; dfs(C, result, row + 1); - // 撤销动作 + // 撤銷動作 // C[row] = -1; columns[j] = main_diag[row - j + N - 1] = anti_diag[row + j] = false; } @@ -564,9 +569,9 @@ \subsubsection{代码2} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item N-Queens II,见 \S \ref{sec:n-queens-ii} +\item N-Queens II,見 \S \ref{sec:n-queens-ii} \myenddot @@ -581,56 +586,56 @@ \subsubsection{描述} \subsubsection{分析} -只需要输出解的个数,不需要输出所有解,代码要比上一题简化很多。设一个全局计数器,每找到一个解就增1。 +只需要輸出解的個數,不需要輸出所有解,代碼要比上一題簡化很多。設一個全局計數器,每找到一個解就增1。 -\subsubsection{代码1} +\subsubsection{代碼1} \begin{Code} // LeetCode, N-Queens II // 深搜+剪枝 -// 时间复杂度O(n!*n),空间复杂度O(n) +// 時間複雜度O(n!*n),空間複雜度O(n) class Solution { public: int totalNQueens(int n) { this->count = 0; - vector C(n, 0); // C[i]表示第i行皇后所在的列编号 + vector C(n, 0); // C[i]表示第i行皇后所在的列編號 dfs(C, 0); return this->count; } private: - int count; // 解的个数 + int count; // 解的個數 void dfs(vector &C, int row) { const int N = C.size(); - if (row == N) { // 终止条件,也是收敛条件,意味着找到了一个可行解 + if (row == N) { // 終止條件,也是收斂條件,意味着找到了一個可行解 ++this->count; return; } - for (int j = 0; j < N; ++j) { // 扩展状态,一列一列的试 + for (int j = 0; j < N; ++j) { // 擴展狀態,一列一列的試 const bool ok = isValid(C, row, j); - if (!ok) continue; // 剪枝:如果合法,继续递归 - // 执行扩展动作 + if (!ok) continue; // 剪枝:如果合法,繼續遞歸 + // 執行擴展動作 C[row] = j; dfs(C, row + 1); - // 撤销动作 + // 撤銷動作 // C[row] = -1; } } /** - * 能否在 (row, col) 位置放一个皇后. + * 能否在 (row, col) 位置放一個皇后. * * @param C 棋局 - * @param row 当前正在处理的行,前面的行都已经放了皇后了 - * @param col 当前列 - * @return 能否放一个皇后 + * @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; @@ -639,11 +644,11 @@ \subsubsection{代码1} \end{Code} -\subsubsection{代码2} +\subsubsection{代碼2} \begin{Code} // LeetCode, N-Queens II // 深搜+剪枝 -// 时间复杂度O(n!),空间复杂度O(n) +// 時間複雜度O(n!),空間複雜度O(n) class Solution { public: int totalNQueens(int n) { @@ -652,35 +657,35 @@ \subsubsection{代码2} this->main_diag = vector(2 * n - 1, false); this->anti_diag = vector(2 * n - 1, false); - vector C(n, 0); // C[i]表示第i行皇后所在的列编号 + vector C(n, 0); // C[i]表示第i行皇后所在的列編號 dfs(C, 0); return this->count; } private: - int count; // 解的个数 - // 这三个变量用于剪枝 - vector columns; // 表示已经放置的皇后占据了哪些列 - vector main_diag; // 占据了哪些主对角线 - vector anti_diag; // 占据了哪些副对角线 + int count; // 解的個數 + // 這三個變量用於剪枝 + vector columns; // 表示已經放置的皇后佔據了哪些列 + vector main_diag; // 佔據了哪些主對角線 + vector anti_diag; // 佔據了哪些副對角線 void dfs(vector &C, int row) { const int N = C.size(); - if (row == N) { // 终止条件,也是收敛条件,意味着找到了一个可行解 + if (row == N) { // 終止條件,也是收斂條件,意味着找到了一個可行解 ++this->count; return; } - for (int j = 0; j < N; ++j) { // 扩展状态,一列一列的试 + for (int j = 0; j < N; ++j) { // 擴展狀態,一列一列的試 const bool ok = !columns[j] && !main_diag[row - j + N] && !anti_diag[row + j]; - if (!ok) continue; // 剪枝:如果合法,继续递归 - // 执行扩展动作 + if (!ok) continue; // 剪枝:如果合法,繼續遞歸 + // 執行擴展動作 C[row] = j; columns[j] = main_diag[row - j + N] = anti_diag[row + j] = true; dfs(C, row + 1); - // 撤销动作 + // 撤銷動作 // C[row] = -1; columns[j] = main_diag[row - j + N] = anti_diag[row + j] = false; @@ -690,9 +695,9 @@ \subsubsection{代码2} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item N-Queens,见 \S \ref{sec:n-queens} +\item N-Queens,見 \S \ref{sec:n-queens} \myenddot @@ -706,37 +711,37 @@ \subsubsection{描述} For example: Given \code{"25525511135"}, -return \code{["255.255.11.135", "255.255.111.35"]}. (Order does not matter) +return \code{\["255.255.11.135", "255.255.111.35"\]}. (Order does not matter) \subsubsection{分析} -必须要走到底部才能判断解是否合法,深搜。 +必須要走到底部才能判斷解是否合法,深搜。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Restore IP Addresses -// 时间复杂度O(n^4),空间复杂度O(n) +// 時間複雜度O(n^4),空間複雜度O(n) class Solution { public: vector restoreIpAddresses(const string& s) { vector result; - vector ip; // 存放中间结果 + vector ip; // 存放中間結果 dfs(s, ip, result, 0); return result; } /** * @brief 解析字符串 - * @param[in] s 字符串,输入数据 - * @param[out] ip 存放中间结果 + * @param[in] s 字符串,輸入數據 + * @param[out] ip 存放中間結果 * @param[out] result 存放所有可能的IP地址 - * @param[in] start 当前正在处理的 index - * @return 无 + * @param[in] start 當前正在處理的 index + * @return 無 */ void dfs(string s, vector& ip, vector &result, size_t start) { - if (ip.size() == 4 && start == s.size()) { // 找到一个合法解 + if (ip.size() == 4 && start == s.size()) { // 找到一個合法解 result.push_back(ip[0] + '.' + ip[1] + '.' + ip[2] + '.' + ip[3]); return; } @@ -756,16 +761,16 @@ \subsubsection{代码} dfs(s, ip, result, i + 1); ip.pop_back(); - if (num == 0) break; // 不允许前缀0,但允许单个0 + if (num == 0) break; // 不允許前綴0,但允許單個0 } } }; \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -794,19 +799,19 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Combination Sum -// 时间复杂度O(n!),空间复杂度O(n) +// 時間複雜度O(n!),空間複雜度O(n) class Solution { public: vector > combinationSum(vector &nums, int target) { sort(nums.begin(), nums.end()); - vector > result; // 最终结果 - vector path; // 中间结果 + vector > result; // 最終結果 + vector path; // 中間結果 dfs(nums, path, result, target, 0); return result; } @@ -814,25 +819,25 @@ \subsubsection{代码} private: void dfs(vector& nums, vector& path, vector > &result, int gap, int start) { - if (gap == 0) { // 找到一个合法解 + if (gap == 0) { // 找到一個合法解 result.push_back(path); return; } - for (size_t i = start; i < nums.size(); i++) { // 扩展状态 + for (size_t i = start; i < nums.size(); i++) { // 擴展狀態 if (gap < nums[i]) return; // 剪枝 - path.push_back(nums[i]); // 执行扩展动作 + path.push_back(nums[i]); // 執行擴展動作 dfs(nums, path, result, gap - nums[i], i); - path.pop_back(); // 撤销动作 + path.pop_back(); // 撤銷動作 } } }; \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Combination Sum II ,见 \S \ref{sec:combination-sum-ii} +\item Combination Sum II ,見 \S \ref{sec:combination-sum-ii} \myenddot @@ -863,36 +868,36 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Combination Sum II -// 时间复杂度O(n!),空间复杂度O(n) +// 時間複雜度O(n!),空間複雜度O(n) class Solution { public: vector > combinationSum2(vector &nums, int target) { sort(nums.begin(), nums.end()); // 跟第 50 行配合, - // 确保每个元素最多只用一次 + // 確保每個元素最多隻用一次 vector > result; vector path; dfs(nums, path, result, target, 0); return result; } private: - // 使用nums[start, nums.size())之间的元素,能找到的所有可行解 + // 使用nums[start, nums.size())之間的元素,能找到的所有可行解 static void dfs(const vector &nums, vector &path, vector > &result, int gap, int start) { - if (gap == 0) { // 找到一个合法解 + if (gap == 0) { // 找到一個合法解 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], + // 確保nums[i]最多隻用一次 if (previous == nums[i]) continue; if (gap < nums[i]) return; // 剪枝 @@ -901,16 +906,16 @@ \subsubsection{代码} path.push_back(nums[i]); dfs(nums, path, result, gap - nums[i], i + 1); - path.pop_back(); // 恢复环境 + path.pop_back(); // 恢復環境 } } }; \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Combination Sum ,见 \S \ref{sec:combination-sum} +\item Combination Sum ,見 \S \ref{sec:combination-sum} \myenddot @@ -927,15 +932,15 @@ \subsubsection{描述} \end{Code} \subsubsection{分析} -小括号串是一个递归结构,跟单链表、二叉树等递归结构一样,首先想到用递归。 +小括號串是一個遞歸結構,跟單鏈表、二叉樹等遞歸結構一樣,首先想到用遞歸。 -一步步构造字符串。当左括号出现次数$ generateParenthesis(int n) { @@ -944,7 +949,7 @@ \subsubsection{代码1} if (n > 0) generate(n, path, result, 0, 0); return result; } - // l 表示 ( 出现的次数, r 表示 ) 出现的次数 + // l 表示 ( 出現的次數, r 表示 ) 出現的次數 void generate(int n, string& path, vector &result, int l, int r) { if (l == n) { string s(path); @@ -966,11 +971,11 @@ \subsubsection{代码1} \end{Code} -\subsubsection{代码2} -另一种递归写法,更加简洁。 +\subsubsection{代碼2} +另一種遞歸寫法,更加簡潔。 \begin{Code} // LeetCode, Generate Parentheses -// @author 连城 (http://weibo.com/lianchengzju) +// @author 連城 (http://weibo.com/lianchengzju) class Solution { public: vector generateParenthesis (int n) { @@ -989,10 +994,10 @@ \subsubsection{代码2} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Valid Parentheses, 见 \S \ref{sec:valid-parentheses} -\item Longest Valid Parentheses, 见 \S \ref{sec:longest-valid-parentheses} +\item Valid Parentheses, 見 \S \ref{sec:valid-parentheses} +\item Longest Valid Parentheses, 見 \S \ref{sec:longest-valid-parentheses} \myenddot @@ -1019,13 +1024,13 @@ \subsubsection{描述} \subsubsection{分析} -无。 +無。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Sudoku Solver -// 时间复杂度O(9^4),空间复杂度O(1) +// 時間複雜度O(9^4),空間複雜度O(1) class Solution { public: bool solveSudoku(vector > &board) { @@ -1044,13 +1049,13 @@ \subsubsection{代码} return true; } private: - // 检查 (x, y) 是否合法 + // 檢查 (x, y) 是否合法 bool isValid(const vector > &board, int x, int y) { int i, j; - for (i = 0; i < 9; i++) // 检查 y 列 + for (i = 0; i < 9; i++) // 檢查 y 列 if (i != x && board[i][y] == board[x][y]) return false; - for (j = 0; j < 9; j++) // 检查 x 行 + for (j = 0; j < 9; j++) // 檢查 x 行 if (j != y && board[x][j] == board[x][y]) return false; for (i = 3 * (x / 3); i < 3 * (x / 3 + 1); i++) @@ -1063,9 +1068,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Valid Sudoku, 见 \S \ref{sec:valid-sudoku} +\item Valid Sudoku, 見 \S \ref{sec:valid-sudoku} \myenddot @@ -1093,14 +1098,14 @@ \subsubsection{描述} \subsubsection{分析} -无。 +無。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Word Search -// 深搜,递归 -// 时间复杂度O(n^2*m^2),空间复杂度O(n^2) +// 深搜,遞歸 +// 時間複雜度O(n^2*m^2),空間複雜度O(n^2) class Solution { public: bool exist(const vector > &board, const string& word) { @@ -1117,12 +1122,12 @@ \subsubsection{代码} static bool dfs(const vector > &board, const string &word, int index, int x, int y, vector > &visited) { if (index == word.size()) - return true; // 收敛条件 + return true; // 收斂條件 if (x < 0 || y < 0 || x >= board.size() || y >= board[0].size()) - return false; // 越界,终止条件 + return false; // 越界,終止條件 - if (visited[x][y]) return false; // 已经访问过,剪枝 + if (visited[x][y]) return false; // 已經訪問過,剪枝 if (board[x][y] != word[index]) return false; // 不相等,剪枝 @@ -1138,118 +1143,714 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + +\section{Optimal Account Balancing} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:optimal-account-balancing} + + +\subsubsection{描述} +A group of friends went on holiday and sometimes lent each other money. +For example, Alice paid for Bill's lunch for \$10. +Then later Chris gave Alice \$5 for a taxi ride. +We can model each transaction as a tuple (x, y, z) which means person x gave person y \$z. +Assuming Alice, Bill, and Chris are person 0, 1, and 2 respectively (0, 1, 2 are the person's ID), +the transactions can be represented as [[0, 1, 10], [2, 0, 5]]. + +A transaction will be given as a tuple (x, y, z). Note that x ? y and z > 0. +Person's IDs may not be linear, e.g. we could have the persons 0, 1, 2 or we could also have the persons 0, 2, 6. + +Example 1: \newline +Input: +[[0,1,10], [2,0,5]] +Output: +2 +Explanation: +Person \#0 gave person \#1 \$10. +Person \#2 gave person \#0 \$5. +Two transactions are needed. One way to settle the debt is person \#1 pays person \#0 and \#2 \$5 each. + +Example 2: \newline +Input: +[[0,1,10], [1,0,1], [1,2,5], [2,0,5]] +Output: +1 + +Explanation: +\begin{enumerate} + \item Person \#0 gave person \#1 \$10. + \item Person \#1 gave person \#0 \$1. + \item Person \#1 gave person \#2 \$5. + \item Person \#2 gave person \#0 \$5. + \item Therefore, person \#1 only need to give person \#0 \$4, and all debt is settled. +\end{enumerate} + +\subsubsection{分析} +先計算每個人的 balance。然後嘗試所有的可能性,記低最細的交易次數。 +Reference \myurl{https://www.youtube.com/watch?v=I8lLGTgb9LU} + + +\subsubsection{代碼 - DFS} +\begin{Code} +// LeetCode, Optimal Account Balancing +// dfs,時間複雜度O(),空間複雜度O() +class Solution { +public: + int minTransfers(const vector>& transactions) + { + unordered_map map; // key: person value: balance + for (const auto& tran : transactions) + { + int personGive = tran[0]; + int personGet = tran[1]; + int balance = tran[2]; + + auto giveIT = map.find(personGive); + if (giveIT == map.end()) giveIT = map.insert(giveIT, make_pair(personGive, 0)); + auto getIT = map.find(personGet); + if (getIT == map.end()) getIT = map.insert(getIT, make_pair(personGet, 0)); + + giveIT->second += balance; + getIT->second -= balance; + } + + vector vec; vec.reserve(map.size()); + for (const auto& m : map) + { + vec.push_back(m.second); + } + + return dfs(0, vec); + } +private: + int dfs(size_t k, vector& vec) + { + if (k == vec.size()) return 0; + int cur = vec[k]; + if (cur == 0) + return dfs(k + 1, vec); + + int minTrans = INT_MAX; + for (size_t i = k + 1; i < vec.size(); i++) + { + int next = vec[i]; + if (cur * next < 0) + { + vec[i] = cur + next; + minTrans = min(minTrans, 1 + dfs(k + 1, vec)); + vec[i] = next; + } + + if (cur + next == 0) break; + } + + return minTrans; + } +}; +\end{Code} + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + +\section{Candy Crush II} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:candy-crush-ii} + + +\subsubsection{描述} +This question is about implementing a basic elimination algorithm for Candy Crush. This example for demo only. Greedy crushing the longest possible solution. + +\subsubsection{分析} +DFS to find the longest possible crush + +\subsubsection{代碼 - DFS} +\begin{Code} +// LeetCode, Candy Crush II +// dfs,時間複雜度O(),空間複雜度O() +class Solution { +public: + struct Point + { + int m_x; + int m_y; + + Point(int x, int y) : m_x(x), m_y(y) {} + }; + void printCandy(vector>& board) { + for (const auto& r : board) + { + for (const auto& c : r) + cout << c << "\t"; + cout << endl; + } + } + vector> candyCrush(vector>& board) { + m_M = board.size(); + m_N = board[0].size(); + + bool isNeedScan = true; + while (isNeedScan) + { + vector points = DoCrush(board); + if (points.size() >= 3) + { + DoDrop(board, points); + isNeedScan = true; + } + else + isNeedScan = false; + + } + return board; + } +private: + void FindLongestPath(vector>& visited + , vector>& board, int i, int j, int target, vector& points) + { + if (i < 0 || j < 0 || i >= m_M || j >= m_N || board[i][j] != target) return; + if (visited[i][j]) return; + + if (board[i][j] == target) + points.emplace_back(i, j); + + visited[i][j] = true; + FindLongestPath(visited, board, i + 1, j, target, points); + FindLongestPath(visited, board, i, j + 1, target, points); + FindLongestPath(visited, board, i - 1, j, target, points); + FindLongestPath(visited, board, i, j - 1, target, points); + visited[i][j] = false; + + return; + } + vector DoCrush(vector>& board) + { + for (int i = 0; i < m_M; i++) + { + for (int j = 0; j < m_N; j++) + { + if (board[i][j] == 0) continue; + + vector points; + vector> visited(m_M, vector(m_N, false)); + FindLongestPath(visited, board, i, j, board[i][j], points); + + if (points.size() >= 3) + { + // Clear the candy + for (const auto& p : points) + board[p.m_x][p.m_y] = 0; + return points; + } + } + } + return vector(); + } + void DoDrop(vector>& board, vector& points) + { + for (const auto& p : points) + { + DoDropCol(board, p.m_y); + } + } + void DoDropCol(vector>& board, int col) + { + // find the lowest zero + int lowestZ = m_M - 1; + for (; lowestZ >= 0; lowestZ--) + if (board[lowestZ][col] == 0) break; + // find the highest zero + int highestZ = lowestZ; + for (; highestZ >= 0; highestZ--) + if (board[highestZ][col] != 0) break; + // move numbers one by one + for (int i = highestZ; i >= 0; i--) + board[lowestZ--][col] = board[i][col]; + // padding zero if needed + for (; lowestZ >= 0; lowestZ--) + board[lowestZ][col] = 0; + } +private: + int m_M; + int m_N; +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + +\section{K-th Symbol in Grammar} +\label{sec:kth-symbol-in-grammar} + +\subsubsection{描述} +On the first row, we write a 0. Now in every subsequent row, we look at the previous row and replace each occurrence of 0 with 01, and each occurrence of 1 with 10. + +Given row N and index K, return the K-th indexed symbol in row N. (The values of K are 1-indexed.) (1 indexed). + +\begin{Code} +Examples: +Input: N = 1, K = 1 +Output: 0 + +Input: N = 2, K = 1 +Output: 0 + +Input: N = 2, K = 2 +Output: 1 + +Input: N = 4, K = 5 +Output: 1 + +Explanation: +row 1: 0 +row 2: 01 +row 3: 0110 +row 4: 01101001 +\end{Code} + +\subsubsection{分析} +Nil + +\subsubsection{代碼 - DFS} +\begin{Code} +// LeetCode +// 時間複雜度O(logn),空間複雜度O(1) +class Solution { +public: + int kthGrammar(int N, int K) { + if (N == 1) return 0; + if (K <= 1 << N-2) + return kthGrammar(N-1, K); + return kthGrammar(N-1, K - (1 << N-2)) ^ 1; + } +}; +\end{Code} + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + +\section{Robot Room Cleaner} +\label{sec:robot-room-cleaner} + +\subsubsection{描述} +Given a robot cleaner in a room modeled as a grid. + +Each cell in the grid can be empty or blocked. + +The robot cleaner with 4 given APIs can move forward, turn left or turn right. Each turn it made is 90 degrees. + +When it tries to move into a blocked cell, its bumper sensor detects the obstacle and it stays on the current cell. + +Design an algorithm to clean the entire room using only the 4 given APIs shown below. + +\begin{Code} +interface Robot { + // returns true if next cell is open and robot moves into the cell. + // returns false if next cell is obstacle and robot stays on the current cell. + boolean move(); + + // Robot will stay on the same cell after calling turnLeft/turnRight. + // Each turn will be 90 degrees. + void turnLeft(); + void turnRight(); + + // Clean the current cell. + void clean(); +} +\end{Code} + +Example: +\begin{Code} +Input: +room = [ + [1,1,1,1,1,0,1,1], + [1,1,1,1,1,0,1,1], + [1,0,1,1,1,1,1,1], + [0,0,0,1,0,0,0,0], + [1,1,1,1,1,1,1,1] +], +row = 1, +col = 3 + +Explanation: +All grids in the room are marked by either 0 or 1. +0 means the cell is blocked, while 1 means the cell is accessible. +The robot initially starts at the position of row=1, col=3. +From the top left corner, its position is one row below and three columns right. +\end{Code} + +Notes: + +\begin{enumerate} +\item The input is only given to initialize the room and the robot's position internally. You must solve this problem "blindfolded". In other words, you must control the robot using only the mentioned 4 APIs, without knowing the room layout and the initial robot's position. +\item The robot's initial position will always be in an accessible cell. +\item The initial direction of the robot will be facing up. +\item All accessible cells are connected, which means the all cells marked as 1 will be accessible by the robot. +\item Assume all four edges of the grid are all surrounded by wall. +\end{enumerate} + +\subsubsection{分析} +Nil + +\subsubsection{代碼 - DFS} +\begin{Code} +// LeetCode +// 時間複雜度O(todo),空間複雜度O(todo) +/** + * // This is the robot's control interface. + * // You should not implement it, or speculate about its implementation + * class Robot { + * public: + * // Returns true if the cell in front is open and robot moves into the cell. + * // Returns false if the cell in front is blocked and robot stays in the current cell. + * bool move(); + * + * // Robot will stay in the same cell after calling turnLeft/turnRight. + * // Each turn will be 90 degrees. + * void turnLeft(); + * void turnRight(); + * + * // Clean the current cell. + * void clean(); + * }; + */ +namespace std +{ + template <> + struct hash> + { + size_t operator()(pair const& p) const + { + return ((size_t)&(p.first) ^ (size_t)&(p.second)); + } + }; +} +class Solution { +public: + void cleanRoom(Robot& robot) { + + backtrack(robot, 0, 0, 0); + } +private: + const vector> m_directions{{-1,0},{0,1},{1,0},{0,-1}}; + unordered_set> m_visited; +private: + void backtrack(Robot& robot, int row, int col, int d) + { + m_visited.emplace(row, col); + robot.clean(); + + // 以順時針方向出法,0:上,1:右,2:下,3:左 + for (int i = 0; i < 4; i++) + { + int newD = (d + i) % 4; + int newRow = row + m_directions[newD].first; + int newCol = col + m_directions[newD].second; + + if (m_visited.find(make_pair(newRow, newCol)) == m_visited.end() && robot.move()) + { + backtrack(robot, newRow, newCol, newD); + GoBack(robot); + } + robot.turnRight(); + } + } + void GoBack(Robot& robot) + { + robot.turnRight(); + robot.turnRight(); + robot.move(); + robot.turnRight(); + robot.turnRight(); + } +}; +\end{Code} + + +\subsubsection{代碼 - DFS} +\begin{Code} +// LeetCode +// 時間複雜度O(todo),空間複雜度O(todo) +class Solution { +public: + #define ii pair + int getr(int x, int y) { + return max(abs(x), abs(y)); + } + int xy2n(int x, int y) { + int r = max(abs(x), abs(y)); + if(r==0) return 0; + int start = (2*r-1)*(2*r-1); + if(x==r) return start + (x+y); + if(y==r) return start + 2*r + (y-x); + if(x==-r) return start + 4*r - (x+y); + if(y==-r) return start + 6*r +(x-y); + return -1; + } + vector grid; + int x, y, dir, r; + #define VISITING 1 + #define VISITED 2 + #define BLOCK 3 + #define N 0 + #define W 1 + #define S 2 + #define E 3 + bool move(Robot &robot) { + int nx=x,ny=y; + switch(dir) { + case N: ny++; break; + case S: ny--; break; + case E: nx++; break; + case W: nx--; break; + } + if(r < getr(nx,ny)) { + grid.resize(grid.size() + 8*(r+1), 0); + r = getr(nx,ny); + } + int n = xy2n(nx,ny); + if(grid[n] != 0) return false; + if(robot.move()) { + grid[xy2n(nx,ny)] = 1; + x=nx, y=ny; + return true; + } else { + grid[xy2n(nx,ny)] = BLOCK; + return false; + } + return false; + } + void turnLeft(Robot &r) { dir = (dir+1)%4; r.turnLeft(); } + void turnRight(Robot &r) { dir =(dir+3)%4; r.turnRight(); } + bool dfs(Robot &r) { + //printf("(%c,%d) ", dir, xy2n(x,y)); + grid[xy2n(x,y)] = VISITING; + r.clean(); + int curx=x, cury=y; + for(int i=0; i<4; i++) { + if(move(r)) { + dfs(r); + // pull back the robot back to current cell + grid[xy2n(curx,cury)] = 0; + move(r); turnRight(r); turnRight(r); + grid[xy2n(curx,cury)] = VISITING; + } + turnLeft(r); + } + turnRight(r); turnRight(r); // point back to the path how we came in + grid[xy2n(x,y)] = VISITED; + return false; + } + void cleanRoom(Robot& robot) { + x = y = r = 0; + dir = N; + grid.resize(1,0); + grid[0]=1; + dfs(robot); + } +}; +\end{Code} + + +\subsubsection{代碼 - DFS} +\begin{Code} +// LeetCode +// 時間複雜度O(todo),空間複雜度O(todo) +class Solution { +public: + void dfs(Robot& robot, unordered_map>& visited, int x, int y) + { + //cout << x << " " << y << endl; + visited[x].insert(y); + robot.clean(); + + if ((visited.count(x-1) == 0 || visited[x-1].count(y) == 0) && + robot.move()) + { + dfs(robot, visited, x-1, y); + robot.turnRight(); + robot.turnRight(); + robot.move(); + } + else + { + robot.turnRight(); + robot.turnRight(); + } + + if ((visited.count(x+1) == 0 || visited[x+1].count(y) == 0) && + robot.move()) + { + robot.turnLeft(); + robot.turnLeft(); + dfs(robot, visited, x+1, y); + robot.move(); + robot.turnRight(); + } + else + { + robot.turnLeft(); + } + + if (visited[x].count(y+1) == 0 && + robot.move()) + { + robot.turnLeft(); + dfs(robot, visited, x, y+1); + robot.turnLeft(); + robot.move(); + } + else + { + robot.turnLeft(); + robot.turnLeft(); + } + + if (visited[x].count(y-1) == 0 && + robot.move()) + { + robot.turnRight(); + dfs(robot, visited, x, y-1); + robot.turnRight(); + robot.move(); + robot.turnLeft(); + } + else + { + robot.turnRight(); + } + } + + void cleanRoom(Robot& robot) { + ios_base::sync_with_stdio(false); + cin.tie(NULL); + cout.tie(NULL); + unordered_map> visited; + + dfs(robot, visited, 0, 0); + } +}; +\end{Code} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot -\section{小结} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{小結} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \label{sec:dfs-template} -\subsection{适用场景} +\subsection{適用場景} -\textbf{输入数据}:如果是递归数据结构,如单链表,二叉树,集合,则百分之百可以用深搜;如果是非递归数据结构,如一维数组,二维数组,字符串,图,则概率小一些。 +\textbf{輸入數據}:如果是遞歸數據結構,如單鏈表,二叉樹,集合,則百分之百可以用深搜;如果是非遞歸數據結構,如一維數組,二維數組,字符串,圖,則概率小一些。 -\textbf{状态转换图}:树或者图。 +\textbf{狀態轉換圖}:樹或者圖。 -\textbf{求解目标}:必须要走到最深(例如对于树,必须要走到叶子节点)才能得到一个解,这种情况适合用深搜。 +\textbf{求解目標}:必須要走到最深(例如對於樹,必須要走到葉子節點)才能得到一個解,這種情況適合用深搜。 -\subsection{思考的步骤} +\subsection{思考的步驟} \begin{enumerate} -\item 是求路径条数,还是路径本身(或动作序列)?深搜最常见的三个问题,求可行解的总数,求一个可行解,求所有可行解。 +\item 是求路徑條數,還是路徑本身(或動作序列)?深搜最常見的三個問題,求可行解的總數,求一個可行解,求所有可行解。 \begin{enumerate} - \item 如果是路径条数,则不需要存储路径。 - \item 如果是求路径本身,则要用一个数组\fn{path[]}存储路径。跟宽搜不同,宽搜虽然最终求的也是一条路径,但是需要存储扩展过程中的所有路径,在没找到答案之前所有路径都不能放弃;而深搜,在搜索过程中始终只有一条路径,因此用一个数组就足够了。 + \item 如果是路徑條數,則不需要存儲路徑。 + \item 如果是求路徑本身,則要用一個數組\fn{path[]}存儲路徑。跟寬搜不同,寬搜雖然最終求的也是一條路徑,但是需要存儲擴展過程中的所有路徑,在沒找到答案之前所有路徑都不能放棄;而深搜,在搜索過程中始終只有一條路徑,因此用一個數組就足夠了。 \end{enumerate} -\item 只要求一个解,还是要求所有解?如果只要求一个解,那找到一个就可以返回;如果要求所有解,找到了一个后,还要继续扩展,直到遍历完。广搜一般只要求一个解,因而不需要考虑这个问题(广搜当然也可以求所有解,这时需要扩展到所有叶子节点,相当于在内存中存储整个状态转换图,非常占内存,因此广搜不适合解这类问题)。 +\item 只要求一個解,還是要求所有解?如果只要求一個解,那找到一個就可以返回;如果要求所有解,找到了一個後,還要繼續擴展,直到遍歷完。廣搜一般只要求一個解,因而不需要考慮這個問題(廣搜當然也可以求所有解,這時需要擴展到所有葉子節點,相當於在內存中存儲整個狀態轉換圖,非常佔內存,因此廣搜不適合解這類問題)。 -\item 如何表示状态?即一个状态需要存储哪些些必要的数据,才能够完整提供如何扩展到下一步状态的所有信息。跟广搜不同,深搜的惯用写法,不是把数据记录在状态\fn{struct}里,而是添加函数参数(有时为了节省递归堆栈,用全局变量),\fn{struct}里的字段与函数参数一一对应。 +\item 如何表示狀態?即一個狀態需要存儲哪些些必要的數據,才能夠完整提供如何擴展到下一步狀態的所有信息。跟廣搜不同,深搜的慣用寫法,不是把數據記錄在狀態\fn{struct}裏,而是添加函數參數(有時為了節省遞歸堆棧,用全局變量),\fn{struct}裏的字段與函數參數一一對應。 -\item 如何扩展状态?这一步跟上一步相关。状态里记录的数据不同,扩展方法就不同。对于固定不变的数据结构(一般题目直接给出,作为输入数据),如二叉树,图等,扩展方法很简单,直接往下一层走,对于隐式图,要先在第1步里想清楚状态所带的数据,想清楚了这点,那如何扩展就很简单了。 +\item 如何擴展狀態?這一步跟上一步相關。狀態裏記錄的數據不同,擴展方法就不同。對於固定不變的數據結構(一般題目直接給出,作為輸入數據),如二叉樹,圖等,擴展方法很簡單,直接往下一層走,對於隱式圖,要先在第1步裏想清楚狀態所帶的數據,想清楚了這點,那如何擴展就很簡單了。 -\item 终止条件是什么?终止条件是指到了不能扩展的末端节点。对于树,是叶子节点,对于图或隐式图,是出度为0的节点。 +\item 終止條件是什麼?終止條件是指到了不能擴展的末端節點。對於樹,是葉子節點,對於圖或隱式圖,是出度為0的節點。 -\item {收敛条件是什么?收敛条件是指找到了一个合法解的时刻。如果是正向深搜(父状态处理完了才进行递归,即父状态不依赖子状态,递归语句一定是在最后,尾递归),则是指是否达到目标状态;如果是逆向深搜(处理父状态时需要先知道子状态的结果,此时递归语句不在最后),则是指是否到达初始状态。 +\item {收斂條件是什麼?收斂條件是指找到了一個合法解的時刻。如果是正向深搜(父狀態處理完了才進行遞歸,即父狀態不依賴子狀態,遞歸語句一定是在最後,尾遞歸),則是指是否達到目標狀態;如果是逆向深搜(處理父狀態時需要先知道子狀態的結果,此時遞歸語句不在最後),則是指是否到達初始狀態。 -由于很多时候终止条件和收敛条件是是合二为一的,因此很多人不区分这两种条件。仔细区分这两种条件,还是很有必要的。 +由於很多時候終止條件和收斂條件是是合二為一的,因此很多人不區分這兩種條件。仔細區分這兩種條件,還是很有必要的。 -为了判断是否到了收敛条件,要在函数接口里用一个参数记录当前的位置(或距离目标还有多远)。如果是求一个解,直接返回这个解;如果是求所有解,要在这里收集解,即把第一步中表示路径的数组\fn{path[]}复制到解集合里。} +為了判斷是否到了收斂條件,要在函數接口裏用一個參數記錄當前的位置(或距離目標還有多遠)。如果是求一個解,直接返回這個解;如果是求所有解,要在這裏收集解,即把第一步中表示路徑的數組\fn{path[]}複製到解集合裏。} -\item 关于判重 +\item 關於判重 \begin{enumerate} - \item 是否需要判重?如果状态转换图是一棵树,则不需要判重,因为在遍历过程中不可能重复;如果状态转换图是一个DAG,则需要判重。这一点跟BFS不一样,BFS的状态转换图总是DAG,必须要判重。 - \item 怎样判重?跟广搜相同,见第 \S \ref{sec:bfs-template} 节。同时,DAG说明存在重叠子问题,此时可以用缓存加速,见第8步。 + \item 是否需要判重?如果狀態轉換圖是一棵樹,則不需要判重,因為在遍歷過程中不可能重複;如果狀態轉換圖是一個DAG,則需要判重。這一點跟BFS不一樣,BFS的狀態轉換圖總是DAG,必須要判重。 + \item 怎樣判重?跟廣搜相同,見第 \S \ref{sec:bfs-template} 節。同時,DAG説明存在重疊子問題,此時可以用緩存加速,見第8步。 \end{enumerate} \item 如何加速? \begin{enumerate} - \item 剪枝。深搜一定要好好考虑怎么剪枝,成本小收益大,加几行代码,就能大大加速。这里没有通用方法,只能具体问题具体分析,要充分观察,充分利用各种信息来剪枝,在中间节点提前返回。 - \item 缓存。 + \item 剪枝。深搜一定要好好考慮怎麼剪枝,成本小收益大,加幾行代碼,就能大大加速。這裏沒有通用方法,只能具體問題具體分析,要充分觀察,充分利用各種信息來剪枝,在中間節點提前返回。 + \item 緩存。 \begin{enumerate} - \item 前提条件:状态转换图是一个DAG。DAG=>存在重叠子问题=>子问题的解会被重复利用,用缓存自然会有加速效果。如果依赖关系是树状的(例如树,单链表等),没必要加缓存,因为子问题只会一层层往下,用一次就再也不会用到,加了缓存也没什么加速效果。 - \item 具体实现:可以用数组或HashMap。维度简单的,用数组;维度复杂的,用HashMap,C++有\fn{map},C++ 11以后有\fn{unordered_map},比\fn{map}快。 + \item 前提條件:狀態轉換圖是一個DAG。DAG=>存在重疊子問題=>子問題的解會被重複利用,用緩存自然會有加速效果。如果依賴關係是樹狀的(例如樹,單鏈表等),沒必要加緩存,因為子問題只會一層層往下,用一次就再也不會用到,加了緩存也沒什麼加速效果。 + \item 具體實現:可以用數組或HashMap。維度簡單的,用數組;維度複雜的,用HashMap,C++有\fn{map},C++ 11以後有\fn{unordered_map},比\fn{map}快。 \end{enumerate} \end{enumerate} \end{enumerate} -拿到一个题目,当感觉它适合用深搜解决时,在心里面把上面8个问题默默回答一遍,代码基本上就能写出来了。对于树,不需要回答第5和第8个问题。如果读者对上面的经验总结看不懂或感觉“不实用”,很正常,因为这些经验总结是我做了很多题目后总结出来的,从思维的发展过程看,“经验总结”要晚于感性认识,所以这时候建议读者先做做前面的题目,积累一定的感性认识后,再回过头来看这一节的总结,一定会有共鸣。 +拿到一個題目,當感覺它適合用深搜解決時,在心裏面把上面8個問題默默回答一遍,代碼基本上就能寫出來了。對於樹,不需要回答第5和第8個問題。如果讀者對上面的經驗總結看不懂或感覺“不實用”,很正常,因為這些經驗總結是我做了很多題目後總結出來的,從思維的發展過程看,“經驗總結”要晚於感性認識,所以這時候建議讀者先做做前面的題目,積累一定的感性認識後,再回過頭來看這一節的總結,一定會有共鳴。 -\subsection{代码模板} +\subsection{代碼模板} \begin{Codex}[label=dfs_template.cpp] /** * dfs模板. - * @param[in] input 输入数据指针 - * @param[out] path 当前路径,也是中间结果 - * @param[out] result 存放最终结果 - * @param[inout] cur or gap 标记当前位置或距离目标的距离 - * @return 路径长度,如果是求路径本身,则不需要返回长度 + * @param[in] input 輸入數據指針 + * @param[out] path 當前路徑,也是中間結果 + * @param[out] result 存放最終結果 + * @param[inout] cur or gap 標記當前位置或距離目標的距離 + * @return 路徑長度,如果是求路徑本身,則不需要返回長度 */ void dfs(type &input, type &path, type &result, int cur or gap) { - if (数据非法) return 0; // 终止条件 - if (cur == input.size()) { // 收敛条件 + if (數據非法) return 0; // 終止條件 + if (cur == input.size()) { // 收斂條件 // if (gap == 0) { - 将path放入result + 將path放入result } if (可以剪枝) return; - for(...) { // 执行所有可能的扩展动作 - 执行动作,修改path + for(...) { // 執行所有可能的擴展動作 + 執行動作,修改path dfs(input, step + 1 or gap--, result); - 恢复path + 恢復path } } \end{Codex} -\subsection{深搜与回溯法的区别} -深搜(Depth-first search, DFS)的定义见\myurl{http://en.wikipedia.org/wiki/Depth_first_search},回溯法(backtracking)的定义见\myurl{http://en.wikipedia.org/wiki/Backtracking} +\subsection{深搜與回溯法的區別} +深搜(Depth-first search, DFS)的定義見\myurl{http://en.wikipedia.org/wiki/Depth_first_search},回溯法(backtracking)的定義見\myurl{http://en.wikipedia.org/wiki/Backtracking} -\textbf{回溯法 = 深搜 + 剪枝}。一般大家用深搜时,或多或少会剪枝,因此深搜与回溯法没有什么不同,可以在它们之间画上一个等号。本书同时使用深搜和回溯法两个术语,但读者可以认为二者等价。 +\textbf{回溯法 = 深搜 + 剪枝}。一般大家用深搜時,或多或少會剪枝,因此深搜與回溯法沒有什麼不同,可以在它們之間畫上一個等號。本書同時使用深搜和回溯法兩個術語,但讀者可以認為二者等價。 -深搜一般用递归(recursion)来实现,这样比较简洁。 +深搜一般用遞歸(recursion)來實現,這樣比較簡潔。 -深搜能够在候选答案生成到一半时,就进行判断,抛弃不满足要求的答案,所以深搜比暴力搜索法要快。 +深搜能夠在候選答案生成到一半時,就進行判斷,拋棄不滿足要求的答案,所以深搜比暴力搜索法要快。 -\subsection{深搜与递归的区别} +\subsection{深搜與遞歸的區別} \label{sec:dfs-vs-recursion} -深搜经常用递归(recursion)来实现,二者常常同时出现,导致很多人误以为他俩是一个东西。 +深搜經常用遞歸(recursion)來實現,二者常常同時出現,導致很多人誤以為他倆是一個東西。 -深搜,是逻辑意义上的算法,递归,是一种物理意义上的实现,它和迭代(iteration)是对应的。深搜,可以用递归来实现,也可以用栈来实现;而递归,一般总是用来实现深搜。可以说,\textbf{递归一定是深搜,深搜不一定用递归}。 +深搜,是邏輯意義上的算法,遞歸,是一種物理意義上的實現,它和迭代(iteration)是對應的。深搜,可以用遞歸來實現,也可以用棧來實現;而遞歸,一般總是用來實現深搜。可以説,\textbf{遞歸一定是深搜,深搜不一定用遞歸}。 -递归有两种加速策略,一种是\textbf{剪枝(prunning)},对中间结果进行判断,提前返回;一种是\textbf{缓存},缓存中间结果,防止重复计算,用空间换时间。 +遞歸有兩種加速策略,一種是\textbf{剪枝(prunning)},對中間結果進行判斷,提前返回;一種是\textbf{緩存},緩存中間結果,防止重複計算,用空間換時間。 -其实,递归+缓存,就是 memoization。所谓\textbf{memoization}(翻译为备忘录法,见第 \S \ref{sec:dp-vs-memoization}节),就是"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{memoization 不一定用递归},就像深搜不一定用递归一样,可以在迭代(iterative)中使用 memoization 。\textbf{递归也不一定用 memoization},可以用memoization来加速,但不是必须的。只有当递归使用了缓存,它才是 memoization 。 +\textbf{memoization 不一定用遞歸},就像深搜不一定用遞歸一樣,可以在迭代(iterative)中使用 memoization 。\textbf{遞歸也不一定用 memoization},可以用memoization來加速,但不是必須的。只有當遞歸使用了緩存,它才是 memoization 。 -既然递归一定是深搜,为什么很多书籍都同时使用这两个术语呢?在递归味道更浓的地方,一般用递归这个术语,在深搜更浓的场景下,用深搜这个术语,读者心里要弄清楚他俩大部分时候是一回事。在单链表、二叉树等递归数据结构上,递归的味道更浓,这时用递归这个术语;在图、隐式图等数据结构上,深搜的味道更浓,这时用深搜这个术语。 +既然遞歸一定是深搜,為什麼很多書籍都同時使用這兩個術語呢?在遞歸味道更濃的地方,一般用遞歸這個術語,在深搜更濃的場景下,用深搜這個術語,讀者心裏要弄清楚他倆大部分時候是一回事。在單鏈表、二叉樹等遞歸數據結構上,遞歸的味道更濃,這時用遞歸這個術語;在圖、隱式圖等數據結構上,深搜的味道更濃,這時用深搜這個術語。 diff --git a/C++/chapDatabase.tex b/C++/chapDatabase.tex new file mode 100644 index 00000000..a3e60659 --- /dev/null +++ b/C++/chapDatabase.tex @@ -0,0 +1,1288 @@ +\chapter{Database} +Database questions +\newline + +\section{Combine Two Tables} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:combine-two-tables} + + +\subsubsection{描述} +SQL Schema + +Table: Person +\begin{Code} ++-------------+---------+ +| Column Name | Type | ++-------------+---------+ +| PersonId | int | +| FirstName | varchar | +| LastName | varchar | ++-------------+---------+ +PersonId is the primary key column for this table. +\end{Code} + +Table: Address +\begin{Code} ++-------------+---------+ +| Column Name | Type | ++-------------+---------+ +| AddressId | int | +| PersonId | int | +| City | varchar | +| State | varchar | ++-------------+---------+ +AddressId is the primary key column for this table. +\end{Code} + +Write a SQL query for a report that provides the following information for each person in the Person table, regardless if there is an address for each of those people: + +\subsubsection{outer join} +\begin{Code} + // Note: Using where clause to filter the records will fail if there is + // no address information for a person because it will not display the name information. +SELECT + FirstName, LastName, City, State +FROM + Person LEFT JOIN Address +ON + Person.PersonID = Address.PersonID +; +\end{Code} + +\section{Second Highest Salary} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:second-highest-salary} + + +\subsubsection{描述} +SQL Schema + +Table: Employee +\begin{Code} ++----+--------+ +| Id | Salary | ++----+--------+ +| 1 | 100 | +| 2 | 200 | +| 3 | 300 | ++----+--------+ +\end{Code} + +For example, given the above Employee table, the query should return 200 as the second highest salary. If there is no second highest salary, then the query should return null. +\begin{Code} ++---------------------+ +| SecondHighestSalary | ++---------------------+ +| 200 | ++---------------------+ +\end{Code} + +Write a SQL query to get the second highest salary from the Employee table. + +\subsubsection{sub-query and LIMIT clause} +\begin{Code} + // Note: Use a sub-query to handle only one record in this table +SELECT + (SELECT DISTINCT + Salary + FROM + Employee + ORDER BY Salary DESC + LIMIT 1 OFFSET 1) AS SecondHighestSalary +; +\end{Code} + +\subsubsection{IFNULL and LIMIT} +\begin{Code} + // Note: Use a sub-query to handle only one record in this table +SELECT + IFNULL( + (SELECT DISTINCT + Salary + FROM + Employee + ORDER BY Salary DESC + LIMIT 1 OFFSET 1), + NULL) AS SecondHighestSalary +; +\end{Code} + +\section{Nth Highest Salary} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:nth-highest-salary} + + +\subsubsection{描述} +SQL Schema + +Table: Employee +\begin{Code} ++----+--------+ +| Id | Salary | ++----+--------+ +| 1 | 100 | +| 2 | 200 | +| 3 | 300 | ++----+--------+ +\end{Code} + +For example, given the above Employee table, the nth highest salary where n = 2 is 200. If there is no nth highest salary, then the query should return null. +\begin{Code} ++------------------------+ +| getNthHighestSalary(2) | ++------------------------+ +| 200 | ++------------------------+ +\end{Code} + +Write a SQL query to get the nth highest salary from the Employee table. + +\subsubsection{IFNULL and LIMIT} +\begin{Code} +CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT +BEGIN + +SET N=N-1; + RETURN ( + # Write your MySQL query statement below. + select + ifnull( + (select distinct + salary + from + employee + order by salary desc + limit N,1), + null) + ); +END +\end{Code} + +\section{Rank Scores} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:rank-scores} + + +\subsubsection{描述} +SQL Schema + +\begin{Code} +Create table If Not Exists Scores (Id int, Score DECIMAL(3,2)) +Truncate table Scores +insert into Scores (Id, Score) values ('1', '3.5') +insert into Scores (Id, Score) values ('2', '3.65') +insert into Scores (Id, Score) values ('3', '4.0') +insert into Scores (Id, Score) values ('4', '3.85') +insert into Scores (Id, Score) values ('5', '4.0') +insert into Scores (Id, Score) values ('6', '3.65') +\end{Code} + +Write a SQL query to rank scores. If there is a tie between two scores, both should have the same ranking. Note that after a tie, the next ranking number should be the next consecutive integer value. In other words, there should be no "holes" between ranks. + +Table: Scores +\begin{Code} ++----+-------+ +| Id | Score | ++----+-------+ +| 1 | 3.50 | +| 2 | 3.65 | +| 3 | 4.00 | +| 4 | 3.85 | +| 5 | 4.00 | +| 6 | 3.65 | ++----+-------+ +\end{Code} + +For example, given the above Scores table, your query should generate the following report (order by highest score): +\begin{Code} ++-------+---------+ +| score | Rank | ++-------+---------+ +| 4.00 | 1 | +| 4.00 | 1 | +| 3.85 | 2 | +| 3.65 | 3 | +| 3.65 | 3 | +| 3.50 | 4 | ++-------+---------+ +\end{Code} + +\textbf{Important Note}: For MySQL solutions, to escape reserved words used as column names, you can use an apostrophe before and after the keyword. For example `Rank`. + +\subsubsection{Dense_Rank} +\begin{Code} +SELECT Score, dense_rank() OVER(ORDER BY Score DESC) AS `Rank` FROM Scores; +\end{Code} + +\section{Consecutive Numbers} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:consecutive-numbers} + + +\subsubsection{描述} +SQL Schema + +\begin{Code} +Create table If Not Exists Logs (Id int, Num int) +Truncate table Logs +insert into Logs (Id, Num) values ('1', '1') +insert into Logs (Id, Num) values ('2', '1') +insert into Logs (Id, Num) values ('3', '1') +insert into Logs (Id, Num) values ('4', '2') +insert into Logs (Id, Num) values ('5', '1') +insert into Logs (Id, Num) values ('6', '2') +insert into Logs (Id, Num) values ('7', '2') +\end{Code} + +Write an SQL query to find all numbers that appear at least three times consecutively. +Return the result table in any order. +The query result format is in the following example: + +Table: Logs +\begin{Code} ++-------------+---------+ +| Column Name | Type | ++-------------+---------+ +| id | int | +| num | varchar | ++-------------+---------+ +id is the primary key for this table. +\end{Code} + + +\begin{Code} +Logs table: ++----+-----+ +| Id | Num | ++----+-----+ +| 1 | 1 | +| 2 | 1 | +| 3 | 1 | +| 4 | 2 | +| 5 | 1 | +| 6 | 2 | +| 7 | 2 | ++----+-----+ + +Result table: ++-----------------+ +| ConsecutiveNums | ++-----------------+ +| 1 | ++-----------------+ +1 is the only number that appears consecutively for at least three times. +\end{Code} + + +\subsubsection{multiple table} +\begin{Code} +SELECT DISTINCT + l1.Num AS ConsecutiveNums +FROM + Logs l1, + Logs l2, + Logs l3 +WHERE + l1.Id = l2.Id - 1 + AND l2.Id = l3.Id - 1 + AND l1.Num = l2.Num + AND l2.Num = l3.Num +; +\end{Code} + +\section{Employees Earning More Than Their Managers} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:employees-earning-more-than-their-managers} + + +\subsubsection{描述} +SQL Schema + +\begin{Code} +Create table If Not Exists Employee (Id int, Name varchar(255), Salary int, ManagerId int) +Truncate table Employee +insert into Employee (Id, Name, Salary, ManagerId) values ('1', 'Joe', '70000', '3') +insert into Employee (Id, Name, Salary, ManagerId) values ('2', 'Henry', '80000', '4') +insert into Employee (Id, Name, Salary, ManagerId) values ('3', 'Sam', '60000', 'None') +insert into Employee (Id, Name, Salary, ManagerId) values ('4', 'Max', '90000', 'None') +\end{Code} + +The Employee table holds all employees including their managers. Every employee has an Id, and there is also a column for the manager Id. + +Table: Employee +\begin{Code} ++----+-------+--------+-----------+ +| Id | Name | Salary | ManagerId | ++----+-------+--------+-----------+ +| 1 | Joe | 70000 | 3 | +| 2 | Henry | 80000 | 4 | +| 3 | Sam | 60000 | NULL | +| 4 | Max | 90000 | NULL | ++----+-------+--------+-----------+ +\end{Code} + +Given the Employee table, write a SQL query that finds out employees who earn more than their managers. For the above table, Joe is the only employee who earns more than his manager. + +\begin{Code} ++----------+ +| Employee | ++----------+ +| Joe | ++----------+ +\end{Code} + + +\subsubsection{multiple table} +\begin{Code} +SELECT + a.Name AS 'Employee' +FROM + Employee AS a, + Employee AS b +WHERE + a.ManagerId = b.Id + AND a.Salary > b.Salary +; +\end{Code} + +\subsubsection{join} +\begin{Code} +SELECT + a.NAME AS Employee +FROM Employee AS a JOIN Employee AS b + ON a.ManagerId = b.Id + AND a.Salary > b.Salary +; +\end{Code} + +\section{Duplicate Emails} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:duplicate-emails} + + +\subsubsection{描述} +SQL Schema + +\begin{Code} +Create table If Not Exists Person (Id int, Email varchar(255)) +Truncate table Person +insert into Person (Id, Email) values ('1', 'a@b.com') +insert into Person (Id, Email) values ('2', 'c@d.com') +insert into Person (Id, Email) values ('3', 'a@b.com') +\end{Code} + +Write a SQL query to find all duplicate emails in a table named Person. + +Table: Person +\begin{Code} ++----+---------+ +| Id | Email | ++----+---------+ +| 1 | a@b.com | +| 2 | c@d.com | +| 3 | a@b.com | ++----+---------+ +\end{Code} + +For example, your query should return the following for the above table: + +\begin{Code} ++---------+ +| Email | ++---------+ +| a@b.com | ++---------+ +\end{Code} + + +\subsubsection{Using GROUP BY and a temporary table} +\begin{Code} +select Email from +( + select Email, count(Email) as num + from Person + group by Email +) as statistic +where num > 1 +; +\end{Code} + +\subsubsection{Using GROUP BY and HAVING condition} +\begin{Code} +select Email +from Person +group by Email +having count(Email) > 1; +\end{Code} + +\section{Customers Who Never Order} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:customers-who-never-order} + + +\subsubsection{描述} +SQL Schema + +\begin{Code} +Create table If Not Exists Customers (Id int, Name varchar(255)) +Create table If Not Exists Orders (Id int, CustomerId int) +Truncate table Customers +insert into Customers (Id, Name) values ('1', 'Joe') +insert into Customers (Id, Name) values ('2', 'Henry') +insert into Customers (Id, Name) values ('3', 'Sam') +insert into Customers (Id, Name) values ('4', 'Max') +Truncate table Orders +insert into Orders (Id, CustomerId) values ('1', '3') +insert into Orders (Id, CustomerId) values ('2', '1') +\end{Code} + +Suppose that a website contains two tables, the Customers table and the Orders table. Write a SQL query to find all customers who never order anything. + +Table: Customers. +\begin{Code} ++----+-------+ +| Id | Name | ++----+-------+ +| 1 | Joe | +| 2 | Henry | +| 3 | Sam | +| 4 | Max | ++----+-------+ +\end{Code} + +Table: Orders. + +\begin{Code} ++----+------------+ +| Id | CustomerId | ++----+------------+ +| 1 | 3 | +| 2 | 1 | ++----+------------+ +\end{Code} + +Using the above tables as example, return the following: + +\begin{Code} ++-----------+ +| Customers | ++-----------+ +| Henry | +| Max | ++-----------+ +\end{Code} + +\subsubsection{Not In} +\begin{Code} +select customers.name as 'Customers' +from customers +where customers.id not in +( + select customerid from orders +); +\end{Code} + +\subsubsection{Left Join} +\begin{Code} +SELECT Name AS 'Customers' +FROM Customers c +LEFT JOIN Orders o +ON c.Id = o.CustomerId +WHERE o.CustomerId IS NULL; +\end{Code} + +\section{Department Highest Salary} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:department-highest-salary} + + +\subsubsection{描述} +SQL Schema + +\begin{Code} +Create table If Not Exists Employee (Id int, Name varchar(255), Salary int, DepartmentId int) +Create table If Not Exists Department (Id int, Name varchar(255)) +Truncate table Employee +insert into Employee (Id, Name, Salary, DepartmentId) values ('1', 'Joe', '70000', '1') +insert into Employee (Id, Name, Salary, DepartmentId) values ('2', 'Jim', '90000', '1') +insert into Employee (Id, Name, Salary, DepartmentId) values ('3', 'Henry', '80000', '2') +insert into Employee (Id, Name, Salary, DepartmentId) values ('4', 'Sam', '60000', '2') +insert into Employee (Id, Name, Salary, DepartmentId) values ('5', 'Max', '90000', '1') +Truncate table Department +insert into Department (Id, Name) values ('1', 'IT') +insert into Department (Id, Name) values ('2', 'Sales') +\end{Code} + +The Employee table holds all employees. Every employee has an Id, a salary, and there is also a column for the department Id. + +\begin{Code} ++----+-------+--------+--------------+ +| Id | Name | Salary | DepartmentId | ++----+-------+--------+--------------+ +| 1 | Joe | 70000 | 1 | +| 2 | Jim | 90000 | 1 | +| 3 | Henry | 80000 | 2 | +| 4 | Sam | 60000 | 2 | +| 5 | Max | 90000 | 1 | ++----+-------+--------+--------------+ +\end{Code} + +The Department table holds all departments of the company. +\begin{Code} ++----+----------+ +| Id | Name | ++----+----------+ +| 1 | IT | +| 2 | Sales | ++----+----------+ +\end{Code} + +Write a SQL query to find employees who have the highest salary in each of the departments. For the above tables, your SQL query should return the following rows (order of rows does not matter). +\begin{Code} ++------------+----------+--------+ +| Department | Employee | Salary | ++------------+----------+--------+ +| IT | Max | 90000 | +| IT | Jim | 90000 | +| Sales | Henry | 80000 | ++------------+----------+--------+ +\end{Code} + +\subsubsection{Join, In} +\begin{Code} +# This example shows that IN can match multiple fields together +SELECT + Department.name AS 'Department', + Employee.name AS 'Employee', + Salary +FROM + Employee + JOIN + Department ON Employee.DepartmentId = Department.Id +WHERE + (Employee.DepartmentId , Salary) IN + ( SELECT + DepartmentId, MAX(Salary) + FROM + Employee + GROUP BY DepartmentId + ) +; +\end{Code} + +\section{Department Top Three Salaries} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:department-top-three-salaries} + + +\subsubsection{描述} +SQL Schema + +\begin{Code} +Create table If Not Exists Employee (Id int, Name varchar(255), Salary int, DepartmentId int) +Create table If Not Exists Department (Id int, Name varchar(255)) +Truncate table Employee +insert into Employee (Id, Name, Salary, DepartmentId) values ('1', 'Joe', '85000', '1') +insert into Employee (Id, Name, Salary, DepartmentId) values ('2', 'Henry', '80000', '2') +insert into Employee (Id, Name, Salary, DepartmentId) values ('3', 'Sam', '60000', '2') +insert into Employee (Id, Name, Salary, DepartmentId) values ('4', 'Max', '90000', '1') +insert into Employee (Id, Name, Salary, DepartmentId) values ('5', 'Janet', '69000', '1') +insert into Employee (Id, Name, Salary, DepartmentId) values ('6', 'Randy', '85000', '1') +insert into Employee (Id, Name, Salary, DepartmentId) values ('7', 'Will', '70000', '1') +Truncate table Department +insert into Department (Id, Name) values ('1', 'IT') +insert into Department (Id, Name) values ('2', 'Sales') +\end{Code} + +Table: Employee + +\begin{Code} ++--------------+---------+ +| Column Name | Type | ++--------------+---------+ +| Id | int | +| Name | varchar | +| Salary | int | +| DepartmentId | int | ++--------------+---------+ +Id is the primary key for this table. +Each row contains the ID, name, salary, and department of one employee. +\end{Code} + +Table: Department +\begin{Code} ++-------------+---------+ +| Column Name | Type | ++-------------+---------+ +| Id | int | +| Name | varchar | ++-------------+---------+ +Id is the primary key for this table. +Each row contains the ID and the name of one department. +\end{Code} + +A company's executives are interested in seeing who earns the most money in each of the company's departments. A high earner in a department is an employee who has a salary in the top three unique salaries for that department. + +Write an SQL query to find the employees who are high earners in each of the departments. + +Return the result table in any order. + +The query result format is in the following example: +\begin{Code} +Employee table: ++----+-------+--------+--------------+ +| Id | Name | Salary | DepartmentId | ++----+-------+--------+--------------+ +| 1 | Joe | 85000 | 1 | +| 2 | Henry | 80000 | 2 | +| 3 | Sam | 60000 | 2 | +| 4 | Max | 90000 | 1 | +| 5 | Janet | 69000 | 1 | +| 6 | Randy | 85000 | 1 | +| 7 | Will | 70000 | 1 | ++----+-------+--------+--------------+ + +Department table: ++----+-------+ +| Id | Name | ++----+-------+ +| 1 | IT | +| 2 | Sales | ++----+-------+ + +Result table: ++------------+----------+--------+ +| Department | Employee | Salary | ++------------+----------+--------+ +| IT | Max | 90000 | +| IT | Joe | 85000 | +| IT | Randy | 85000 | +| IT | Will | 70000 | +| Sales | Henry | 80000 | +| Sales | Sam | 60000 | ++------------+----------+--------+ + +In the IT department: +- Max earns the highest unique salary +- Both Randy and Joe earn the second-highest unique salary +- Will earns the third-highest unique salary + +In the Sales department: +- Henry earns the highest salary +- Sam earns the second-highest salary +- There is no third-highest salary as there are only two employees +\end{Code} + +\subsubsection{Join, sub-query} +\begin{Code} +SELECT + d.Name AS 'Department', e1.Name AS 'Employee', e1.Salary +FROM + Employee e1 + JOIN + Department d ON e1.DepartmentId = d.Id +WHERE + 3 > (SELECT + COUNT(DISTINCT e2.Salary) + FROM + Employee e2 + WHERE + e2.Salary > e1.Salary + AND e1.DepartmentId = e2.DepartmentId + ) +; +\end{Code} + +\subsubsection{Join, dense_rank} +\begin{Code} +SELECT d.NAME AS Department, + a. NAME AS Employee, + a. salary +FROM (SELECT e.*, + Dense_rank() + OVER ( + partition BY departmentid + ORDER BY salary DESC) AS DeptPayRank + FROM employee e) a + JOIN department d + ON a. departmentid = d. id +WHERE deptpayrank <= 3; +\end{Code} + +\section{Delete Duplicate Emails} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:delete-duplicate-emails} + + +\subsubsection{描述} +SQL Schema + +\begin{Code} +Truncate table Person +insert into Person (Id, Email) values ('1', 'john@example.com') +insert into Person (Id, Email) values ('2', 'bob@example.com') +insert into Person (Id, Email) values ('3', 'john@example.com') +\end{Code} + +Write a SQL query to delete all duplicate email entries in a table named Person, keeping only unique emails based on its smallest Id. + +\begin{Code} ++----+------------------+ +| Id | Email | ++----+------------------+ +| 1 | john@example.com | +| 2 | bob@example.com | +| 3 | john@example.com | ++----+------------------+ +Id is the primary key column for this table. +\end{Code} + +For example, after running your query, the above Person table should have the following rows: +\begin{Code} ++----+------------------+ +| Id | Email | ++----+------------------+ +| 1 | john@example.com | +| 2 | bob@example.com | ++----+------------------+ +\end{Code} + +Note: + +Your output is the whole Person table after executing your sql. Use delete statement. + +\subsubsection{Delete, Where} +\begin{Code} +DELETE p1 FROM Person p1, + Person p2 +WHERE + p1.Email = p2.Email AND p1.Id > p2.Id; +\end{Code} + +\section{Rising Temperature} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:rising-temperature} + + +\subsubsection{描述} +SQL Schema + +\begin{Code} +Create table If Not Exists Weather (Id int, RecordDate date, Temperature int) +Truncate table Weather +insert into Weather (Id, RecordDate, Temperature) values ('1', '2015-01-01', '10') +insert into Weather (Id, RecordDate, Temperature) values ('2', '2015-01-02', '25') +insert into Weather (Id, RecordDate, Temperature) values ('3', '2015-01-03', '20') +insert into Weather (Id, RecordDate, Temperature) values ('4', '2015-01-04', '30') +\end{Code} + +Table: Weather + +\begin{Code} ++---------------+---------+ +| Column Name | Type | ++---------------+---------+ +| id | int | +| recordDate | date | +| temperature | int | ++---------------+---------+ +id is the primary key for this table. +This table contains information about the temperature in a certain day. +\end{Code} + +Write an SQL query to find all dates' id with higher temperature compared to its previous dates (yesterday). + +Return the result table in any order. + +The query result format is in the following example: +\begin{Code} +Weather ++----+------------+-------------+ +| id | recordDate | Temperature | ++----+------------+-------------+ +| 1 | 2015-01-01 | 10 | +| 2 | 2015-01-02 | 25 | +| 3 | 2015-01-03 | 20 | +| 4 | 2015-01-04 | 30 | ++----+------------+-------------+ + +Result table: ++----+ +| id | ++----+ +| 2 | +| 4 | ++----+ +In 2015-01-02, temperature was higher than the previous day (10 -> 25). +In 2015-01-04, temperature was higher than the previous day (20 -> 30). +\end{Code} + + +\subsubsection{Join, Datediff} +\begin{Code} +SELECT + weather.id AS 'Id' +FROM + weather + JOIN + weather w ON DATEDIFF(weather.recordDate, w.recordDate) = 1 + AND weather.Temperature > w.Temperature +; +\end{Code} + +\subsubsection{Join, Subdate} +\begin{Code} +select + w1.id +from + Weather w1 +join + Weather w2 +on + subdate(w1.recordDate, 1) = w2.recordDate + and w1.Temperature > w2.Temperature; +\end{Code} + +\section{Trips and Users} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:trips-and-users} + + +\subsubsection{描述} +SQL Schema + +\begin{Code} +Create table If Not Exists Trips (Id int, Client_Id int, Driver_Id int, City_Id int, Status ENUM('completed', 'cancelled_by_driver', 'cancelled_by_client'), Request_at varchar(50)) +Create table If Not Exists Users (Users_Id int, Banned varchar(50), Role ENUM('client', 'driver', 'partner')) +Truncate table Trips +insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('1', '1', '10', '1', 'completed', '2013-10-01') +insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('2', '2', '11', '1', 'cancelled_by_driver', '2013-10-01') +insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('3', '3', '12', '6', 'completed', '2013-10-01') +insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('4', '4', '13', '6', 'cancelled_by_client', '2013-10-01') +insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('5', '1', '10', '1', 'completed', '2013-10-02') +insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('6', '2', '11', '6', 'completed', '2013-10-02') +insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('7', '3', '12', '6', 'completed', '2013-10-02') +insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('8', '2', '12', '12', 'completed', '2013-10-03') +insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('9', '3', '10', '12', 'completed', '2013-10-03') +insert into Trips (Id, Client_Id, Driver_Id, City_Id, Status, Request_at) values ('10', '4', '13', '12', 'cancelled_by_driver', '2013-10-03') +Truncate table Users +insert into Users (Users_Id, Banned, Role) values ('1', 'No', 'client') +insert into Users (Users_Id, Banned, Role) values ('2', 'Yes', 'client') +insert into Users (Users_Id, Banned, Role) values ('3', 'No', 'client') +insert into Users (Users_Id, Banned, Role) values ('4', 'No', 'client') +insert into Users (Users_Id, Banned, Role) values ('10', 'No', 'driver') +insert into Users (Users_Id, Banned, Role) values ('11', 'No', 'driver') +insert into Users (Users_Id, Banned, Role) values ('12', 'No', 'driver') +insert into Users (Users_Id, Banned, Role) values ('13', 'No +\end{Code} + +Table: Trips + +\begin{Code} ++-------------+----------+ +| Column Name | Type | ++-------------+----------+ +| Id | int | +| Client_Id | int | +| Driver_Id | int | +| City_Id | int | +| Status | enum | +| Request_at | date | ++-------------+----------+ +Id is the primary key for this table. +The table holds all taxi trips. Each trip has a unique Id, while Client_Id and Driver_Id are foreign keys to the Users_Id at the Users table. +Status is an ENUM type of (‘completed’, ‘cancelled_by_driver’, ‘cancelled_by_client’). +\end{Code} + +Table: Users + +\begin{Code} ++-------------+----------+ +| Column Name | Type | ++-------------+----------+ +| Users_Id | int | +| Banned | enum | +| Role | enum | ++-------------+----------+ +Users_Id is the primary key for this table. +The table holds all users. Each user has a unique Users_Id, and Role is an ENUM type of (‘client’, ‘driver’, ‘partner’). +Status is an ENUM type of (‘Yes’, ‘No’). +\end{Code} + +Write a SQL query to find the cancellation rate of requests with unbanned users (both client and driver must not be banned) each day between "2013-10-01" and "2013-10-03". + +The cancellation rate is computed by dividing the number of canceled (by client or driver) requests with unbanned users by the total number of requests with unbanned users on that day. + +Return the result table in any order. Round Cancellation Rate to two decimal points. + +The query result format is in the following example: + +\begin{Code} +Trips table: ++----+-----------+-----------+---------+---------------------+------------+ +| Id | Client_Id | Driver_Id | City_Id | Status | Request_at | ++----+-----------+-----------+---------+---------------------+------------+ +| 1 | 1 | 10 | 1 | completed | 2013-10-01 | +| 2 | 2 | 11 | 1 | cancelled_by_driver | 2013-10-01 | +| 3 | 3 | 12 | 6 | completed | 2013-10-01 | +| 4 | 4 | 13 | 6 | cancelled_by_client | 2013-10-01 | +| 5 | 1 | 10 | 1 | completed | 2013-10-02 | +| 6 | 2 | 11 | 6 | completed | 2013-10-02 | +| 7 | 3 | 12 | 6 | completed | 2013-10-02 | +| 8 | 2 | 12 | 12 | completed | 2013-10-03 | +| 9 | 3 | 10 | 12 | completed | 2013-10-03 | +| 10 | 4 | 13 | 12 | cancelled_by_driver | 2013-10-03 | ++----+-----------+-----------+---------+---------------------+------------+ + +Users table: ++----------+--------+--------+ +| Users_Id | Banned | Role | ++----------+--------+--------+ +| 1 | No | client | +| 2 | Yes | client | +| 3 | No | client | +| 4 | No | client | +| 10 | No | driver | +| 11 | No | driver | +| 12 | No | driver | +| 13 | No | driver | ++----------+--------+--------+ + +Result table: ++------------+-------------------+ +| Day | Cancellation Rate | ++------------+-------------------+ +| 2013-10-01 | 0.33 | +| 2013-10-02 | 0.00 | +| 2013-10-03 | 0.50 | ++------------+-------------------+ + +On 2013-10-01: + - There were 4 requests in total, 2 of which were canceled. + - However, the request with Id=2 was made by a banned client (User_Id=2), so it is ignored in the calculation. + - Hence there are 3 unbanned requests in total, 1 of which was canceled. + - The Cancellation Rate is (1 / 3) = 0.33 +On 2013-10-02: + - There were 3 requests in total, 0 of which were canceled. + - The request with Id=6 was made by a banned client, so it is ignored. + - Hence there are 2 unbanned requests in total, 0 of which were canceled. + - The Cancellation Rate is (0 / 2) = 0.00 +On 2013-10-03: + - There were 3 requests in total, 1 of which was canceled. + - The request with Id=8 was made by a banned client, so it is ignored. + - Hence there are 2 unbanned request in total, 1 of which were canceled. + - The Cancellation Rate is (1 / 2) = 0.50 +\end{Code} + +\subsubsection{CASE WHEN, Extra Column} +\begin{Code} +SELECT + t.Request_at AS Day + , ROUND(SUM(t.flag) / COUNT(t.Request_at), 2) AS "Cancellation Rate" +FROM + ( + SELECT + * + , CASE WHEN Status!='completed' THEN 1 ELSE 0 END AS flag + FROM + Trips + WHERE + Request_at >= '2013-10-01' AND Request_at <= '2013-10-03' + AND Client_Id NOT IN (SELECT Users_Id FROM Users WHERE Banned='Yes') + AND Driver_Id NOT IN (SELECT Users_Id FROM Users WHERE Banned='Yes') + ) AS t +GROUP BY + t.Request_at +; +\end{Code} + +\subsubsection{Extra Table} +\begin{Code} +WITH cte AS +( +SELECT + Client_Id + , Driver_Id + , Status + , Request_at + , CASE WHEN Status!='completed' THEN 1 ELSE 0 END AS flag +FROM + Trips +WHERE + Request_at >= '2013-10-01' AND Request_at <= '2013-10-03' + AND Client_Id NOT IN (SELECT Users_Id FROM Users WHERE Banned='Yes') + AND Driver_Id NOT IN (SELECT Users_Id FROM Users WHERE Banned='Yes') +) + +SELECT + Request_at AS Day + , ROUND(SUM(flag) / COUNT(*), 2) AS 'Cancellation Rate' +FROM + cte +GROUP BY + Request_at +; +\end{Code} + +\subsubsection{Extra Table, Faster} +\begin{Code} +WITH cte AS +( +SELECT + * + , CASE WHEN Status!='completed' THEN 1 ELSE 0 END AS flag +FROM + Trips +WHERE + Request_at >= '2013-10-01' AND Request_at <= '2013-10-03' + AND Client_Id NOT IN (SELECT Users_Id FROM Users WHERE Banned='Yes') + AND Driver_Id NOT IN (SELECT Users_Id FROM Users WHERE Banned='Yes') +) + +SELECT + Request_at AS Day + , ROUND(SUM(flag) / COUNT(*), 2) AS 'Cancellation Rate' +FROM + cte +GROUP BY + Request_at +; +\end{Code} + +\section{Game Play Analysis I} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:game-play-analysis-i} + + +\subsubsection{描述} +SQL Schema + +\begin{Code} +Create table If Not Exists Activity (player_id int, device_id int, event_date date, games_played int) +Truncate table Activity +insert into Activity (player_id, device_id, event_date, games_played) values ('1', '2', '2016-03-01', '5') +insert into Activity (player_id, device_id, event_date, games_played) values ('1', '2', '2016-05-02', '6') +insert into Activity (player_id, device_id, event_date, games_played) values ('2', '3', '2017-06-25', '1') +insert into Activity (player_id, device_id, event_date, games_played) values ('3', '1', '2016-03-02', '0') +insert into Activity (player_id, device_id, event_date, games_played) values ('3', '4', '2018-07-03', '5') +\end{Code} + +Table: Activity + +\begin{Code} ++--------------+---------+ +| Column Name | Type | ++--------------+---------+ +| player_id | int | +| device_id | int | +| event_date | date | +| games_played | int | ++--------------+---------+ +(player_id, event_date) is the primary key of this table. +This table shows the activity of players of some game. +Each row is a record of a player who logged in and played a number of games (possibly 0) before logging out on some day using some device. +\end{Code} + +Write an SQL query that reports the first login date for each player. + +The query result format is in the following example: + +\begin{Code} +Activity table: ++-----------+-----------+------------+--------------+ +| player_id | device_id | event_date | games_played | ++-----------+-----------+------------+--------------+ +| 1 | 2 | 2016-03-01 | 5 | +| 1 | 2 | 2016-05-02 | 6 | +| 2 | 3 | 2017-06-25 | 1 | +| 3 | 1 | 2016-03-02 | 0 | +| 3 | 4 | 2018-07-03 | 5 | ++-----------+-----------+------------+--------------+ + +Result table: ++-----------+-------------+ +| player_id | first_login | ++-----------+-------------+ +| 1 | 2016-03-01 | +| 2 | 2017-06-25 | +| 3 | 2016-03-02 | ++-----------+-------------+ +\end{Code} + + +\subsubsection{GROUP BY, MIN} +\begin{Code} +SELECT + player_id, + MIN(event_date) AS first_login +FROM + activity +GROUP BY + player_id +; +\end{Code} + +\section{Game Play Analysis II} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:game-play-analysis-ii} + + +\subsubsection{描述} +SQL Schema + +\begin{Code} +Create table If Not Exists Activity (player_id int, device_id int, event_date date, games_played int) +Truncate table Activity +insert into Activity (player_id, device_id, event_date, games_played) values ('1', '2', '2016-03-01', '5') +insert into Activity (player_id, device_id, event_date, games_played) values ('1', '2', '2016-05-02', '6') +insert into Activity (player_id, device_id, event_date, games_played) values ('2', '3', '2017-06-25', '1') +insert into Activity (player_id, device_id, event_date, games_played) values ('3', '1', '2016-03-02', '0') +insert into Activity (player_id, device_id, event_date, games_played) values ('3', '4', '2018-07-03', '5') +\end{Code} + +Table: Activity + +\begin{Code} ++--------------+---------+ +| Column Name | Type | ++--------------+---------+ +| player_id | int | +| device_id | int | +| event_date | date | +| games_played | int | ++--------------+---------+ +(player_id, event_date) is the primary key of this table. +This table shows the activity of players of some game. +Each row is a record of a player who logged in and played a number of games (possibly 0) before logging out on some day using some device. +\end{Code} + +Write an SQL query that reports the first login date for each player. + +The query result format is in the following example: + +\begin{Code} +Activity table: ++-----------+-----------+------------+--------------+ +| player_id | device_id | event_date | games_played | ++-----------+-----------+------------+--------------+ +| 1 | 2 | 2016-03-01 | 5 | +| 1 | 2 | 2016-05-02 | 6 | +| 2 | 3 | 2017-06-25 | 1 | +| 3 | 1 | 2016-03-02 | 0 | +| 3 | 4 | 2018-07-03 | 5 | ++-----------+-----------+------------+--------------+ + +Result table: ++-----------+-----------+ +| player_id | device_id | ++-----------+-----------+ +| 1 | 2 | +| 2 | 3 | +| 3 | 1 | ++-----------+-----------+ +\end{Code} + + +\subsubsection{GROUP BY, MIN} +\begin{Code} +SELECT + player_id + , device_id +FROM + activity +WHERE + (player_id + , event_date) +IN + (SELECT + player_id + , MIN(event_date) AS event_date + FROM + activity + GROUP BY player_id) +; +\end{Code} + +\subsubsection{GROUP BY, Dense_Rank} +\begin{Code} +SELECT + player_id + , device_id +FROM + (SELECT + player_id + , device_id + , DENSE_RANK() OVER (PARTITION BY player_id ORDER BY event_date) AS num + FROM + activity) AS a + WHERE + num = 1 +; +\end{Code} + + +\section{Game Play Analysis III} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:game-play-analysis-iii} + + +\subsubsection{描述} +SQL Schema + +\begin{Code} +Create table If Not Exists Activity (player_id int, device_id int, event_date date, games_played int) +Truncate table Activity +insert into Activity (player_id, device_id, event_date, games_played) values ('1', '2', '2016-03-01', '5') +insert into Activity (player_id, device_id, event_date, games_played) values ('1', '2', '2016-05-02', '6') +insert into Activity (player_id, device_id, event_date, games_played) values ('2', '3', '2017-06-25', '1') +insert into Activity (player_id, device_id, event_date, games_played) values ('3', '1', '2016-03-02', '0') +insert into Activity (player_id, device_id, event_date, games_played) values ('3', '4', '2018-07-03', '5') +\end{Code} + +Table: Activity + +\begin{Code} ++--------------+---------+ +| Column Name | Type | ++--------------+---------+ +| player_id | int | +| device_id | int | +| event_date | date | +| games_played | int | ++--------------+---------+ +(player_id, event_date) is the primary key of this table. +This table shows the activity of players of some game. +Each row is a record of a player who logged in and played a number of games (possibly 0) +before logging out on some day using some device. +\end{Code} + +Write an SQL query that reports for each player and date, how many games played so far by the player. That is, the total number of games played by the player until that date. Check the example for clarity. + +The query result format is in the following example: + +\begin{Code} +Activity table: ++-----------+-----------+------------+--------------+ +| player_id | device_id | event_date | games_played | ++-----------+-----------+------------+--------------+ +| 1 | 2 | 2016-03-01 | 5 | +| 1 | 2 | 2016-05-02 | 6 | +| 1 | 3 | 2017-06-25 | 1 | +| 3 | 1 | 2016-03-02 | 0 | +| 3 | 4 | 2018-07-03 | 5 | ++-----------+-----------+------------+--------------+ + +Result table: ++-----------+------------+---------------------+ +| player_id | event_date | games_played_so_far | ++-----------+------------+---------------------+ +| 1 | 2016-03-01 | 5 | +| 1 | 2016-05-02 | 11 | +| 1 | 2017-06-25 | 12 | +| 3 | 2016-03-02 | 0 | +| 3 | 2018-07-03 | 5 | ++-----------+------------+---------------------+ +For the player with id 1, 5 + 6 = 11 games played by 2016-05-02, +and 5 + 6 + 1 = 12 games played by 2017-06-25. +For the player with id 3, 0 + 5 = 5 games played by 2018-07-03. +Note that for each player we only care about the days when the player logged in. +\end{Code} + + +\subsubsection{SUM, Window Function} +\begin{Code} +SELECT + player_id + , event_date + , SUM(games_played) OVER + (PARTITION BY player_id ORDER BY event_date) AS games_played_so_far +FROM + Activity; +\end{Code} + + diff --git a/C++/chapDivideAndConquer.tex b/C++/chapDivideAndConquer.tex index 9cf03559..4636b264 100644 --- a/C++/chapDivideAndConquer.tex +++ b/C++/chapDivideAndConquer.tex @@ -13,32 +13,33 @@ \subsubsection{分析} 二分法,$x^n = x^{n/2} \times x^{n/2} \times x^{n\%2}$ -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} //LeetCode, Pow(x, n) // 二分法,$x^n = x^{n/2} * x^{n/2} * x^{n\%2}$ -// 时间复杂度O(logn),空间复杂度O(1) +// 時間複雜度O(logn),空間複雜度O(1) class Solution { public: double myPow(double x, int n) { - if (n < 0) return 1.0 / power(x, -n); - else return power(x, n); + long long nn = n // 使用 64 bit 來處理 INT_MIN 由負變正 case + if (nn < 0) return 1.0 / power(x, -nn); + else return power(x, nn); } private: - double power(double x, int n) { - if (n == 0) return 1; - double v = power(x, n / 2); - if (n % 2 == 0) return v * v; + double power(double x, long long nn) { + if (nn == 0) return 1; + double v = power(x, nn / 2); + if (nn % 2 == 0) return v * v; else return v * v * x; } }; \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Sqrt(x),见 \S \ref{sec:sqrt} +\item Sqrt(x),見 \S \ref{sec:sqrt} \myenddot @@ -55,22 +56,22 @@ \subsubsection{分析} 二分查找 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Sqrt(x) // 二分查找 -// 时间复杂度O(logn),空间复杂度O(1) +// 時間複雜度O(logn),空間複雜度O(1) class Solution { public: int mySqrt(int x) { int left = 1, right = x / 2; - int last_mid; // 记录最近一次mid + int last_mid; // 記錄最近一次mid if (x < 2) return x; while(left <= right) { const int mid = left + (right - left) / 2; - if(x / mid > mid) { // 不要用 x > mid * mid,会溢出 + if(x / mid > mid) { // 不要用 x > mid * mid,會溢出 left = mid + 1; last_mid = mid; } else if(x / mid < mid) { @@ -85,7 +86,209 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Pow(x),见 \S \ref{sec:pow} +\item Pow(x),見 \S \ref{sec:pow} \myenddot + +\section{The Skyline Problem} +\label{sec:the-skyline-problem} + +\subsubsection{描述} +A city's skyline is the outer contour of the silhouette formed by all the buildings in that city when viewed from a distance. Now suppose you are given the locations and height of all the buildings as shown on a cityscape photo (Figure A), write a program to output the skyline formed by these buildings collectively (Figure B). + +\begin{center} +\includegraphics[width=250pt]{the-skyline-problem.png}\\ +\figcaption{Skyline Problem}\label{fig:the-skyline-problem} +\end{center} + +The geometric information of each building is represented by a triplet of integers [Li, Ri, Hi], where Li and Ri are the x coordinates of the left and right edge of the ith building, respectively, and Hi is its height. It is guaranteed that 0 ≤ Li, Ri ≤ INT_MAX, 0 < Hi ≤ INT_MAX, and Ri - Li > 0. You may assume all buildings are perfect rectangles grounded on an absolutely flat surface at height 0. + +For instance, the dimensions of all buildings in Figure A are recorded as: [ [2 9 10], [3 7 15], [5 12 12], [15 20 10], [19 24 8] ] . + +The output is a list of "key points" (red dots in Figure B) in the format of [ [x1,y1], [x2, y2], [x3, y3], ... ] that uniquely defines a skyline. A key point is the left endpoint of a horizontal line segment. Note that the last key point, where the rightmost building ends, is merely used to mark the termination of the skyline, and always has zero height. Also, the ground in between any two adjacent buildings should be considered part of the skyline contour. + +For instance, the skyline in Figure B should be represented as:[ [2 10], [3 15], [7 12], [12 0], [15 10], [20 8], [24, 0] ]. + + + +Notes: + +\begin{enumerate} +\item The number of buildings in any input list is guaranteed to be in the range [0, 10000]. +\item The input list is already sorted in ascending order by the left x position Li. +\item The output list must be sorted by the x position. +\item There must be no consecutive horizontal lines of equal height in the output skyline. For instance, [...[2 3], [4 5], [7 5], [11 5], [12 7]...] is not acceptable; the three lines of height 5 should be merged into one in the final output as such: [...[2 3], [4 5], [12 7], ...] +\end{enumerate} + +\subsubsection{分析} +Nil + +\subsubsection{分治法} +\begin{Code} +// 時間複雜度O(nlogn),空間複雜度O(n) +class Solution { +public: + vector> getSkyline(vector>& buildings) { + return DFS(buildings, 0, buildings.size()); + } +private: + vector> DFS(vector>& buildings, int start, int end) + { + if (start == end) + { + return vector>(); + } + else if (end - start == 1) + { + vector> result; + int& startX = buildings[start][0]; + int& startY = buildings[start][2]; + int& endX = buildings[start][1]; + result.push_back({startX, startY}); + result.push_back({endX, 0}); + + return result; + } + + int mid = start + (end - start) / 2; + auto left = DFS(buildings, start, mid); + auto right = DFS(buildings, mid, end); + + return mergeSkylines(left, right); + } + vector> mergeSkylines(const vector>& left + , const vector>& right) + { + int lN = left.size(); + int rN = right.size(); + int lp, rp, curP; + int ly, ry, curY; + int x, maxY; + vector> output; output.reserve(lN + rN); + lp = rp = curP = 0; + ly = ry = curY = 0; + x = maxY = 0; + + while (lp < lN && rp < rN) + { + if (left[lp][0] < right[rp][0]) + { + x = left[lp][0]; + ly = left[lp][1]; + lp++; + } + else + { + x = right[rp][0]; + ry = right[rp][1]; + rp++; + } + + maxY = max(ly, ry); + + if (curY != maxY) + { + UpdateOutput(output, x, maxY); + curY = maxY; + } + } + + if (lp < lN) + AppendSkylines(output, left, lp, lN, curY); + else + AppendSkylines(output, right, rp, rN, curY); + + + return output; + } + void UpdateOutput(vector>& output, int x, int y) + { + if (output.empty() || output.back()[0] != x) + output.push_back({x, y}); + else + output.back()[1] = y; + } + void AppendSkylines(vector>& output + , const vector>& skyline, int p, int N, int curY) + { + while (p < N) + { + const int& x = skyline[p][0]; + const int& y = skyline[p][1]; + p++; + + if (curY != y) + { + UpdateOutput(output, x, y); + curY = y; + } + } + } +}; +\end{Code} + + +\subsubsection{hashmap} +\begin{Code} + // 時間複雜度O(timeout),空間複雜度O(n) + // time will base on the width of buildings where O(n) n is the number of buildings +class Solution +{ +public: + vector> getSkyline(vector>& buildings) + { + // get last x position + int last_x = Get_Last_X(buildings); + // create hash map wiht an array + vector skyline_outline(last_x + 1, 0); + + // get skyline outline structure + for (const vector& building: buildings) + { + for (int j = building[0]; j <= building[1]; j++) + { + int y = building[2]; + if (y > skyline_outline[j]) + // get the highest y + skyline_outline[j] = y; + } + } + + // get result + vector> skyline_result; + int cur_y = 0; + for (size_t x = 0; x < skyline_outline.size(); x++) + { + if (skyline_outline[x] > cur_y) + { + cur_y = skyline_outline[x]; + skyline_result.push_back({(int)x, cur_y}); + } + else if (skyline_outline[x] < cur_y) + { + cur_y = skyline_outline[x]; + skyline_result.push_back({(int)(x - 1), cur_y}); + } + } + if (cur_y != 0) + { + skyline_result.push_back({(int)(skyline_outline.size() - 1), 0}); + } + + return skyline_result; + } +private: + int Get_Last_X(const vector>& buildings) + { + int last_x = INT_MIN; + for (const vector& building : buildings) + { + if (building[1] > last_x) + last_x = building[1]; + } + + return last_x; + } +}; +\end{Code} diff --git a/C++/chapDynamicProgramming.tex b/C++/chapDynamicProgramming.tex index 5327de51..4157dec4 100644 --- a/C++/chapDynamicProgramming.tex +++ b/C++/chapDynamicProgramming.tex @@ -1,4 +1,4 @@ -\chapter{动态规划} +\chapter{動態規劃} \section{Triangle} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -23,16 +23,16 @@ \subsubsection{描述} \subsubsection{分析} -设状态为$f(i, j)$,表示从从位置$(i,j)$出发,路径的最小和,则状态转移方程为 +設狀態為$f(i, j)$,表示從從位置$(i,j)$出發,路徑的最小和,則狀態轉移方程為 $$ f(i,j)=\min\left\{f(i+1,j),f(i+1,j+1)\right\}+(i,j) $$ -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Triangle -// 时间复杂度O(n^2),空间复杂度O(1) +// 時間複雜度O(n^2),空間複雜度O(1) class Solution { public: int minimumTotal (vector>& triangle) { @@ -47,11 +47,117 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot +\section{House Robber} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:house-robber} + + +\subsubsection{描述} +You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night. + +Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police. + +Example 1: +\begin{Code} +Input: nums = [1,2,3,1] +Output: 4 +Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3). + Total amount you can rob = 1 + 3 = 4. +\end{Code} + +Example 2: +\begin{Code} +Input: nums = [2,7,9,3,1] +Output: 12 +Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1). + Total amount you can rob = 2 + 9 + 1 = 12. +\end{Code} + +\begindot +\item 0 <= nums.length <= 100 +\item 0 <= nums[i] <= 400 +\myenddot + +\subsubsection{動規} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(1) +// +class Solution { +public: + int rob(vector& nums) { + int prevMax = 0; + int curMax = 0; + for (const int& n : nums) + { + int temp = curMax; + curMax = max(prevMax + n, curMax); + prevMax = temp; + } + + return curMax; + } +}; +\end{Code} + + +\section{Maximum Product Subarray} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:maximum-product-subarray} + + +\subsubsection{描述} +Given an integer array nums, find the contiguous subarray within an array (containing at least one number) which has the largest product. + +Example 1: +\begin{Code} +Input: [2,3,-2,4] +Output: 6 +Explanation: [2,3] has the largest product 6. +\end{Code} + +Example 2: +\begin{Code} +Input: [-2,0,-1] +Output: 0 +Explanation: The result cannot be 2, because [-2,-1] is not a subarray. +\end{Code} + +\subsubsection{動規} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(1) +// +// 先由左至右累計一次,再由右至左累計一次,取最大值 +// 若果負數出現的次數為雙數,必定可以用盡全個數組。所以必定可以得到最大數 +// 若果負數出現的次數為單數,由左至右或由右至左必定可以得到答案,答案會包括其中的子數組,其中負數出現次數為雙數。 +class Solution { +public: + int maxProduct(vector& nums) { + int ans = INT_MIN, curProd = 1; + // 由左至右 + for (int i = 0; i < nums.size(); i++) + { + curProd *= nums[i]; + if (curProd > ans) ans = curProd; + if (curProd == 0) curProd = 1; + } + + curProd = 1; + // 由右至左 + for (int i = nums.size()-1; i > 0; i--) + { + curProd *= nums[i]; + if(curProd>ans) ans = curProd; + if(curProd==0) curProd=1; + } + + return ans; + } +}; +\end{Code} + \section{Maximum Subarray} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \label{sec:maximum-subarray} @@ -60,45 +166,45 @@ \section{Maximum Subarray} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsubsection{描述} Find the contiguous subarray within an array (containing at least one number) which has the largest sum. -For example, given the array \code{[−2,1,−3,4,−1,2,1,−5,4]}, -the contiguous subarray \code{[4,−1,2,1]} has the largest \code{sum = 6}. +For example, given the array \code{\[−2,1,−3,4,−1,2,1,−5,4\]}, +the contiguous subarray \code{\[4,−1,2,1\]} has the largest \code{sum = 6}. \subsubsection{分析} -最大连续子序列和,非常经典的题。 +最大連續子序列和,非常經典的題。 -当我们从头到尾遍历这个数组的时候,对于数组里的一个整数,它有几种选择呢?它只有两种选择: 1、加入之前的SubArray;2. 自己另起一个SubArray。那什么时候会出现这两种情况呢? +當我們從頭到尾遍歷這個數組的時候,對於數組裏的一個整數,它有幾種選擇呢?它只有兩種選擇: 1、加入之前的SubArray;2. 自己另起一個SubArray。那什麼時候會出現這兩種情況呢? -如果之前SubArray的总体和大于0的话,我们认为其对后续结果是有贡献的。这种情况下我们选择加入之前的SubArray +如果之前SubArray的總體和大於0的話,我們認為其對後續結果是有貢獻的。這種情況下我們選擇加入之前的SubArray -如果之前SubArray的总体和为0或者小于0的话,我们认为其对后续结果是没有贡献,甚至是有害的(小于0时)。这种情况下我们选择以这个数字开始,另起一个SubArray。 +如果之前SubArray的總體和為0或者小於0的話,我們認為其對後續結果是沒有貢獻,甚至是有害的(小於0時)。這種情況下我們選擇以這個數字開始,另起一個SubArray。 -设状态为\fn{f[j]},表示以\fn{S[j]}结尾的最大连续子序列和,则状态转移方程如下: +設狀態為\fn{f[j]},表示以\fn{S[j]}結尾的最大連續子序列和,則狀態轉移方程如下: \begin{eqnarray} f[j] &=& \max\left\{f[j-1]+S[j],S[j]\right\}, \text{ 其中 }1 \leq j \leq n \nonumber \\ target &=& \max\left\{f[j]\right\}, \text{ 其中 }1 \leq j \leq n \nonumber \end{eqnarray} -解释如下: +解釋如下: \begindot -\item 情况一,S[j]不独立,与前面的某些数组成一个连续子序列,则最大连续子序列和为$f[j-1]+S[j]$。 -\item 情况二,S[j]独立划分成为一段,即连续子序列仅包含一个数S[j],则最大连续子序列和为$S[j]$。 +\item 情況一,S[j]不獨立,與前面的某些數組成一個連續子序列,則最大連續子序列和為$f[j-1]+S[j]$。 +\item 情況二,S[j]獨立劃分成為一段,即連續子序列僅包含一個數S[j],則最大連續子序列和為$S[j]$。 \myenddot 其他思路: \begindot -\item 思路2:直接在i到j之间暴力枚举,复杂度是$O(n^3)$ -\item 思路3:处理后枚举,连续子序列的和等于两个前缀和之差,复杂度$O(n^2)$。 -\item 思路4:分治法,把序列分为两段,分别求最大连续子序列和,然后归并,复杂度$O(n\log n)$ -\item 思路5:把思路2$O(n^2)$的代码稍作处理,得到$O(n)$的算法 -\item 思路6:当成M=1的最大M子段和 +\item 思路2:直接在i到j之間暴力枚舉,複雜度是$O(n^3)$ +\item 思路3:處理後枚舉,連續子序列的和等於兩個前綴和之差,複雜度$O(n^2)$。 +\item 思路4:分治法,把序列分為兩段,分別求最大連續子序列和,然後歸併,複雜度$O(n\log n)$ +\item 思路5:把思路2$O(n^2)$的代碼稍作處理,得到$O(n)$的算法 +\item 思路6:當成M=1的最大M子段和 \myenddot -\subsubsection{动规} +\subsubsection{動規} \begin{Code} // LeetCode, Maximum Subarray -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: int maxSubArray(vector& nums) { @@ -116,19 +222,19 @@ \subsubsection{动规} \subsubsection{思路5} \begin{Code} // LeetCode, Maximum Subarray -// 时间复杂度O(n),空间复杂度O(n) +// 時間複雜度O(n),空間複雜度O(n) class Solution { public: int maxSubArray(vector& A) { return mcss(A.begin(), A.end()); } private: - // 思路5,求最大连续子序列和 + // 思路5,求最大連續子序列和 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项和 + int *sum = new int[n + 1]; // 前n項和 sum[0] = 0; result = INT_MIN; @@ -147,9 +253,9 @@ \subsubsection{思路5} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Binary Tree Maximum Path Sum,见 \S \ref{sec:binary-tree-maximum-path-sum} +\item Binary Tree Maximum Path Sum,見 \S \ref{sec:binary-tree-maximum-path-sum} \myenddot @@ -164,33 +270,35 @@ \subsubsection{描述} For example, given \code{s = "aab"}, -Return 1 since the palindrome partitioning \code{["aa","b"]} could be produced using 1 cut. +Return 1 since the palindrome partitioning \code{\["aa","b"\]} could be produced using 1 cut. + +Reference \myurl{https://www.youtube.com/watch?v=lDYIvtBVmgo} \subsubsection{分析} -定义状态\fn{f(i,j)}表示区间\fn{[i,j]}之间最小的cut数,则状态转移方程为 +定義狀態\fn{f(i,j)}表示區間\fn{[i,j]}之間最小的cut數,則狀態轉移方程為 $$ f(i,j)=\min\left\{f(i,k)+f(k+1,j)\right\}, i \leq k \leq j, 0 \leq i \leq j= 0; i--) { for (int j = i; j < n; j++) { if (s[i] == s[j] && (j - i < 2 || p[i + 1][j - 1])) { @@ -215,9 +323,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Palindrome Partitioning,见 \S \ref{sec:palindrome-partitioning} +\item Palindrome Partitioning,見 \S \ref{sec:palindrome-partitioning} \myenddot @@ -230,13 +338,17 @@ \subsubsection{描述} \subsubsection{分析} -无 +\begin{center} +\includegraphics{maximal-rectangle.png}\\ +\figcaption{Maximal Rectangle}\label{fig:maximal-rectangle} +\end{center} -\subsubsection{代码} + +\subsubsection{代碼} \begin{Code} // LeetCode, Maximal Rectangle -// 时间复杂度O(n^2),空间复杂度O(n) +// 時間複雜度O(n^2),空間複雜度O(n) class Solution { public: int maximalRectangle(vector > &matrix) { @@ -277,10 +389,10 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -297,16 +409,16 @@ \subsubsection{描述} \subsubsection{分析} -设状态$f(i)$,表示区间$[0,i](0 \leq i \leq n-1)$的最大利润,状态$g(i)$,表示区间$[i, n-1](0 \leq i \leq n-1)$的最大利润,则最终答案为$\max\left\{f(i)+g(i)\right\},0 \leq i \leq n-1$。 +設狀態$f(i)$,表示區間$[0,i](0 \leq i \leq n-1)$的最大利潤,狀態$g(i)$,表示區間$[i, n-1](0 \leq i \leq n-1)$的最大利潤,則最終答案為$\max\left\{f(i)+g(i)\right\},0 \leq i \leq n-1$。 -允许在一天内买进又卖出,相当于不交易,因为题目的规定是最多两次,而不是一定要两次。 +允許在一天內買進又賣出,相當於不交易,因為題目的規定是最多兩次,而不是一定要兩次。 -将原数组变成差分数组,本题也可以看做是最大$m$子段和,$m=2$,参考代码:\myurl{https://gist.github.com/soulmachine/5906637} +將原數組變成差分數組,本題也可以看做是最大$m$子段和,$m=2$,參考代碼:\myurl{https://gist.github.com/soulmachine/5906637} -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Best Time to Buy and Sell Stock III -// 时间复杂度O(n),空间复杂度O(n) +// 時間複雜度O(n),空間複雜度O(n) class Solution { public: int maxProfit(vector& prices) { @@ -336,10 +448,10 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Best Time to Buy and Sell Stock,见 \S \ref{sec:best-time-to-buy-and-sell-stock} -\item Best Time to Buy and Sell Stock II,见 \S \ref{sec:best-time-to-buy-and-sell-stock-ii} +\item Best Time to Buy and Sell Stock,見 \S \ref{sec:best-time-to-buy-and-sell-stock} +\item Best Time to Buy and Sell Stock II,見 \S \ref{sec:best-time-to-buy-and-sell-stock-ii} \myenddot @@ -358,17 +470,22 @@ \subsubsection{描述} \subsubsection{分析} -设状态\fn{f[i][j]},表示\fn{s1[0,i]}和\fn{s2[0,j]},匹配\fn{s3[0, i+j]}。如果s1的最后一个字符等于s3的最后一个字符,则\fn{f[i][j]=f[i-1][j]};如果s2的最后一个字符等于s3的最后一个字符,则\fn{f[i][j]=f[i][j-1]}。因此状态转移方程如下: +設狀態\fn{f[i][j]},表示\fn{s1[0,i]}和\fn{s2[0,j]},匹配\fn{s3[0, i+j]}。如果s1的最後一個字符等於s3的最後一個字符,則\fn{f[i][j]=f[i-1][j]};如果s2的最後一個字符等於s3的最後一個字符,則\fn{f[i][j]=f[i][j-1]}。因此狀態轉移方程如下: \begin{Code} f[i][j] = (s1[i - 1] == s3 [i + j - 1] && f[i - 1][j]) || (s2[j - 1] == s3 [i + j - 1] && f[i][j - 1]); \end{Code} +Reference \myurl{https://www.youtube.com/watch?v=ih2OZ9-M3OM} +\begin{center} +\includegraphics{interleaving-string.png}\\ +\figcaption{Interleaving String Example}\label{fig:interleaving-string} +\end{center} -\subsubsection{递归} +\subsubsection{遞歸} \begin{Code} // LeetCode, Interleaving String -// 递归,会超时,仅用来帮助理解 +// 遞歸,會超時,僅用來幫助理解 class Solution { public: bool isInterleave(const string& s1, const string& s2, const string& s3) { @@ -396,10 +513,10 @@ \subsubsection{递归} \end{Code} -\subsubsection{动规} +\subsubsection{動規} \begin{Code} // LeetCode, Interleaving String -// 二维动规,时间复杂度O(n^2),空间复杂度O(n^2) +// 二維動規,時間複雜度O(n^2),空間複雜度O(n^2) class Solution { public: bool isInterleave(const string& s1, const string& s2, const string& s3) { @@ -426,10 +543,10 @@ \subsubsection{动规} \end{Code} -\subsubsection{动规+滚动数组} +\subsubsection{動規+滾動數組} \begin{Code} // LeetCode, Interleaving String -// 二维动规+滚动数组,时间复杂度O(n^2),空间复杂度O(n) +// 二維動規+滾動數組,時間複雜度O(n^2),空間複雜度O(n) class Solution { public: bool isInterleave(const string& s1, const string& s2, const string& s3) { @@ -458,9 +575,9 @@ \subsubsection{动规+滚动数组} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -514,25 +631,25 @@ \subsubsection{描述} \subsubsection{分析} -首先想到的是递归(即深搜),对两个string进行分割,然后比较四对字符串。代码虽然简单,但是复杂度比较高。有两种加速策略,一种是剪枝,提前返回;一种是加缓存,缓存中间结果,即memoization(翻译为记忆化搜索)。 +首先想到的是遞歸(即深搜),對兩個string進行分割,然後比較四對字符串。代碼雖然簡單,但是複雜度比較高。有兩種加速策略,一種是剪枝,提前返回;一種是加緩存,緩存中間結果,即memoization(翻譯為記憶化搜索)。 -剪枝可以五花八门,要充分观察,充分利用信息,找到能让节点提前返回的条件。例如,判断两个字符串是否互为scamble,至少要求每个字符在两个字符串中出现的次数要相等,如果不相等则返回false。 +剪枝可以五花八門,要充分觀察,充分利用信息,找到能讓節點提前返回的條件。例如,判斷兩個字符串是否互為scamble,至少要求每個字符在兩個字符串中出現的次數要相等,如果不相等則返回false。 -加缓存,可以用数组或HashMap。本题维数较高,用HashMap,\fn{map}和\fn{unordered_map}均可。 +加緩存,可以用數組或HashMap。本題維數較高,用HashMap,\fn{map}和\fn{unordered_map}均可。 -既然可以用记忆化搜索,这题也一定可以用动规。设状态为\fn{f[n][i][j]},表示长度为$n$,起点为\fn{s1[i]}和起点为\fn{s2[j]}两个字符串是否互为scramble,则状态转移方程为 +既然可以用記憶化搜索,這題也一定可以用動規。設狀態為\fn{f[n][i][j]},表示長度為$n$,起點為\fn{s1[i]}和起點為\fn{s2[j]}兩個字符串是否互為scramble,則狀態轉移方程為 \begin{Code} f[n][i][j]} = (f[k][i][j] && f[n-k][i+k][j+k]) || (f[k][i][j+n-k] && f[n-k][i+k][j]) \end{Code} -\subsubsection{递归} +\subsubsection{遞歸} \begin{Code} // LeetCode, Scramble String -// 递归,会超时,仅用来帮助理解 -// 时间复杂度O(n^6),空间复杂度O(1) +// 遞歸,會超時,僅用來幫助理解 +// 時間複雜度O(n^6),空間複雜度O(1) class Solution { public: bool isScramble(const string& s1, const string& s2) { @@ -559,18 +676,18 @@ \subsubsection{递归} \end{Code} -\subsubsection{动规} +\subsubsection{動規} \begin{Code} // LeetCode, Scramble String -// 动规,时间复杂度O(n^3),空间复杂度O(n^3) +// 動規,時間複雜度O(n^3),空間複雜度O(n^3) class Solution { public: bool isScramble(const string& s1, const string& s2) { const int N = s1.size(); if (N != s2.size()) return false; - // f[n][i][j],表示长度为n,起点为s1[i]和 - // 起点为s2[j]两个字符串是否互为scramble + // f[n][i][j],表示長度為n,起點為s1[i]和 + // 起點為s2[j]兩個字符串是否互為scramble bool f[N + 1][N][N]; fill_n(&f[0][0][0], (N + 1) * N * N, false); @@ -597,11 +714,11 @@ \subsubsection{动规} \end{Code} -\subsubsection{递归+剪枝} +\subsubsection{遞歸+剪枝} \begin{Code} // LeetCode, Scramble String -// 递归+剪枝 -// 时间复杂度O(n^6),空间复杂度O(1) +// 遞歸+剪枝 +// 時間複雜度O(n^6),空間複雜度O(1) class Solution { public: bool isScramble(const string& s1, const string& s2) { @@ -615,7 +732,7 @@ \subsubsection{递归+剪枝} if (length == 1) return *first1 == *first2; // 剪枝,提前返回 - int A[26]; // 每个字符的计数器 + int A[26]; // 每個字符的計數器 fill(A, A + 26, 0); for(int i = 0; i < length; i++) A[*(first1+i)-'a']++; for(int i = 0; i < length; i++) A[*(first2+i)-'a']--; @@ -634,11 +751,11 @@ \subsubsection{递归+剪枝} \end{Code} -\subsubsection{备忘录法} +\subsubsection{備忘錄法} \begin{Code} // LeetCode, Scramble String -// 递归+map做cache -// 时间复杂度O(n^3),空间复杂度O(n^3), TLE +// 遞歸+map做cache +// 時間複雜度O(n^3),空間複雜度O(n^3), TLE class Solution { public: bool isScramble(const string& s1, const string& s2) { @@ -676,11 +793,11 @@ \subsubsection{备忘录法} \end{Code} -\subsubsection{备忘录法} +\subsubsection{備忘錄法} \begin{Code} typedef string::const_iterator Iterator; typedef tuple Key; -// 定制一个哈希函数 +// 定製一個哈希函數 namespace std { template<> struct hash { size_t operator()(const Key & x) const { @@ -697,8 +814,8 @@ \subsubsection{备忘录法} } // LeetCode, Scramble String -// 递归+unordered_map做cache,比map快 -// 时间复杂度O(n^3),空间复杂度O(n^3) +// 遞歸+unordered_map做cache,比map快 +// 時間複雜度O(n^3),空間複雜度O(n^3) class Solution { public: unordered_map cache; @@ -736,9 +853,9 @@ \subsubsection{备忘录法} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -753,18 +870,23 @@ \subsubsection{描述} \subsubsection{分析} -跟 Unique Paths (见 \S \ref{sec:unique-paths}) 很类似。 +跟 Unique Paths (見 \S \ref{sec:unique-paths}) 很類似。 -设状态为\fn{f[i][j]},表示从起点$(0,0)$到达$(i,j)$的最小路径和,则状态转移方程为: +設狀態為\fn{f[i][j]},表示從起點$(0,0)$到達$(i,j)$的最小路徑和,則狀態轉移方程為: \begin{Code} f[i][j]=min(f[i-1][j], f[i][j-1])+grid[i][j] \end{Code} +\begin{center} +\includegraphics{minimum-path-sum.png}\\ +\figcaption{Minimum Path Sum}\label{fig:minimum-path-sum} +\end{center} + -\subsubsection{备忘录法} +\subsubsection{備忘錄法} \begin{Code} // LeetCode, Minimum Path Sum -// 备忘录法 +// 備忘錄法 class Solution { public: int minPathSum(vector > &grid) { @@ -774,12 +896,12 @@ \subsubsection{备忘录法} return dfs(grid, m-1, n-1); } private: - vector > f; // 缓存 + vector > f; // 緩存 int dfs(const vector > &grid, int x, int y) { - if (x < 0 || y < 0) return INT_MAX; // 越界,终止条件,注意,不是0 + if (x < 0 || y < 0) return INT_MAX; // 越界,終止條件,注意,不是0 - if (x == 0 && y == 0) return grid[0][0]; // 回到起点,收敛条件 + if (x == 0 && y == 0) return grid[0][0]; // 回到起點,收斂條件 return min(getOrUpdate(grid, x - 1, y), getOrUpdate(grid, x, y - 1)) + grid[x][y]; @@ -794,10 +916,10 @@ \subsubsection{备忘录法} \end{Code} -\subsubsection{动规} +\subsubsection{動規} \begin{Code} // LeetCode, Minimum Path Sum -// 二维动规 +// 二維動規 class Solution { public: int minPathSum(vector > &grid) { @@ -825,10 +947,10 @@ \subsubsection{动规} \end{Code} -\subsubsection{动规+滚动数组} +\subsubsection{動規+滾動數組} \begin{Code} // LeetCode, Minimum Path Sum -// 二维动规+滚动数组 +// 二維動規+滾動數組 class Solution { public: int minPathSum(vector > &grid) { @@ -836,14 +958,14 @@ \subsubsection{动规+滚动数组} const int n = grid[0].size(); int f[n]; - fill(f, f+n, INT_MAX); // 初始值是 INT_MAX,因为后面用了min函数。 + fill(f, f+n, INT_MAX); // 初始值是 INT_MAX,因為後面用了min函數。 f[0] = 0; for (int i = 0; i < m; i++) { f[0] += grid[i][0]; for (int j = 1; j < n; j++) { - // 左边的f[j],表示更新后的f[j],与公式中的f[i[[j]对应 - // 右边的f[j],表示老的f[j],与公式中的f[i-1][j]对应 + // 左邊的f[j],表示更新後的f[j],與公式中的f[i[[j]對應 + // 右邊的f[j],表示老的f[j],與公式中的f[i-1][j]對應 f[j] = min(f[j - 1], f[j]) + grid[i][j]; } } @@ -852,10 +974,10 @@ \subsubsection{动规+滚动数组} }; \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Unique Paths, 见 \S \ref{sec:unique-paths} -\item Unique Paths II, 见 \S \ref{sec:unique-paths-ii} +\item Unique Paths, 見 \S \ref{sec:unique-paths} +\item Unique Paths II, 見 \S \ref{sec:unique-paths-ii} \myenddot @@ -875,28 +997,33 @@ \subsubsection{描述} \subsubsection{分析} -设状态为\fn{f[i][j]},表示\fn{A[0,i]}和\fn{B[0,j]}之间的最小编辑距离。设\fn{A[0,i]}的形式是\fn{str1c},\fn{B[0,j]}的形式是\fn{str2d}, +設狀態為\fn{f[i][j]},表示\fn{A[0,i]}和\fn{B[0,j]}之間的最小編輯距離。設\fn{A[0,i]}的形式是\fn{str1c},\fn{B[0,j]}的形式是\fn{str2d}, \begin{enumerate} -\item 如果\fn{c==d},则\fn{f[i][j]=f[i-1][j-1]}; +\item 如果\fn{c==d},則\fn{f[i][j]=f[i-1][j-1]}; \item 如果\fn{c!=d}, \begin{enumerate} - \item 如果将c替换成d,则\fn{f[i][j]=f[i-1][j-1]+1}; - \item 如果在c后面添加一个d,则\fn{f[i][j]=f[i][j-1]+1}; - \item 如果将c删除,则\fn{f[i][j]=f[i-1][j]+1}; + \item 如果將c替換成d,則\fn{f[i][j]=f[i-1][j-1]+1}; + \item 如果在c後面添加一個d,則\fn{f[i][j]=f[i][j-1]+1}; + \item 如果將c刪除,則\fn{f[i][j]=f[i-1][j]+1}; \end{enumerate} \end{enumerate} -\subsubsection{动规} +\begin{center} +\includegraphics{edit-distance.png}\\ +\figcaption{Edit Distance}\label{fig:edit-distance} +\end{center} + +\subsubsection{動規} \begin{Code} // LeetCode, Edit Distance -// 二维动规,时间复杂度O(n*m),空间复杂度O(n*m) +// 二維動規,時間複雜度O(n*m),空間複雜度O(n*m) class Solution { public: int minDistance(const string &word1, const string &word2) { const size_t n = word1.size(); const size_t m = word2.size(); - // 长度为n的字符串,有n+1个隔板 + // 長度為n的字符串,有n+1個隔板 int f[n + 1][m + 1]; for (size_t i = 0; i <= n; i++) f[i][0] = i; @@ -919,11 +1046,11 @@ \subsubsection{动规} \end{Code} -\subsubsection{动规+滚动数组} +\subsubsection{動規+滾動數組} \begin{Code} // LeetCode, Edit Distance -// 二维动规+滚动数组 -// 时间复杂度O(n*m),空间复杂度O(n) +// 二維動規+滾動數組 +// 時間複雜度O(n*m),空間複雜度O(n) class Solution { public: int minDistance(const string &word1, const string &word2) { @@ -931,7 +1058,7 @@ \subsubsection{动规+滚动数组} return minDistance(word2, word1); int f[word2.length() + 1]; - int upper_left = 0; // 额外用一个变量记录f[i-1][j-1] + int upper_left = 0; // 額外用一個變量記錄f[i-1][j-1] for (size_t i = 0; i <= word2.size(); ++i) f[i] = i; @@ -958,9 +1085,9 @@ \subsubsection{动规+滚动数组} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -986,13 +1113,13 @@ \subsubsection{描述} \subsubsection{分析} -跟 Climbing Stairs (见 \S \ref{sec:climbing-stairs})很类似,不过多加一些判断逻辑。 +跟 Climbing Stairs (見 \S \ref{sec:climbing-stairs})很類似,不過多加一些判斷邏輯。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Decode Ways -// 动规,时间复杂度O(n),空间复杂度O(1) +// 動規,時間複雜度O(n),空間複雜度O(1) class Solution { public: int numDecodings(const string &s) { @@ -1000,7 +1127,7 @@ \subsubsection{代码} int prev = 0; int cur = 1; - // 长度为n的字符串,有 n+1个阶梯 + // 長度為n的字符串,有 n+1個階梯 for (size_t i = 1; i <= s.size(); ++i) { if (s[i-1] == '0') cur = 0; @@ -1018,9 +1145,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Climbing Stairs, 见 \S \ref{sec:climbing-stairs} +\item Climbing Stairs, 見 \S \ref{sec:climbing-stairs} \myenddot @@ -1040,14 +1167,18 @@ \subsubsection{描述} \subsubsection{分析} -设状态为$f(i,j)$,表示\fn{T[0,j]}在\fn{S[0,i]}里出现的次数。首先,无论\fn{S[i]}和\fn{T[j]}是否相等,若不使用\fn{S[i]},则$f(i,j)=f(i-1,j)$;若\fn{S[i]==T[j]},则可以使用\fn{S[i]},此时$f(i,j)=f(i-1,j)+f(i-1, j-1)$。 +設狀態為$f(i,j)$,表示\fn{T[0,j]}在\fn{S[0,i]}裏出現的次數。首先,無論\fn{S[i]}和\fn{T[j]}是否相等,若不使用\fn{S[i]},則$f(i,j)=f(i-1,j)$;若\fn{S[i]==T[j]},則可以使用\fn{S[i]},此時$f(i,j)=f(i-1,j)+f(i-1, j-1)$。 +\begin{center} +\includegraphics{distinct-subsequences.png}\\ +\figcaption{Distinct Subsequences}\label{fig:distinct-subsequences} +\end{center} -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Distinct Subsequences -// 二维动规+滚动数组 -// 时间复杂度O(m*n),空间复杂度O(n) +// 二維動規+滾動數組 +// 時間複雜度O(m*n),空間複雜度O(n) class Solution { public: int numDistinct(const string &S, const string &T) { @@ -1065,9 +1196,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -1086,7 +1217,7 @@ \subsubsection{描述} \subsubsection{分析} -设状态为$f(i)$,表示\fn{s[0,i]}是否可以分词,则状态转移方程为 +設狀態為$f(i)$,表示\fn{s[0,i]}是否可以分詞,則狀態轉移方程為 $$ f(i) = any\_of(f(j) \&\& s[j+1,i] \in dict), 0 \leq j < i $$ @@ -1095,8 +1226,8 @@ \subsubsection{分析} \subsubsection{深搜} \begin{Code} // LeetCode, Word Break -// 深搜,超时 -// 时间复杂度O(2^n),空间复杂度O(n) +// 深搜,超時 +// 時間複雜度O(2^n),空間複雜度O(n) class Solution { public: bool wordBreak(string s, unordered_set &dict) { @@ -1117,14 +1248,14 @@ \subsubsection{深搜} \end{Code} -\subsubsection{动规} +\subsubsection{動規} \begin{Code} // LeetCode, Word Break -// 动规,时间复杂度O(n^2),空间复杂度O(n) +// 動規,時間複雜度O(n^2),空間複雜度O(n) class Solution { public: bool wordBreak(string s, unordered_set &dict) { - // 长度为n的字符串有n+1个隔板 + // 長度為n的字符串有n+1個隔板 vector f(s.size() + 1, false); f[0] = true; // 空字符串 for (int i = 1; i <= s.size(); ++i) { @@ -1141,9 +1272,9 @@ \subsubsection{动规} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Word Break II, 见 \S \ref{sec:word-break-ii} +\item Word Break II, 見 \S \ref{sec:word-break-ii} \myenddot @@ -1164,19 +1295,24 @@ \subsubsection{描述} \subsubsection{分析} -在上一题的基础上,要返回解本身。 +在上一題的基礎上,要返回解本身。 +\begin{center} +\includegraphics{word-break-ii.png}\\ +\figcaption{Word Break Example iamace}\label{fig:word-break-ii} +\end{center} -\subsubsection{代码} + +\subsubsection{代碼} \begin{Code} // LeetCode, Word Break II -// 动规,时间复杂度O(n^2),空间复杂度O(n^2) +// 動規,時間複雜度O(n^2),空間複雜度O(n^2) class Solution { public: vector wordBreak(string s, unordered_set &dict) { - // 长度为n的字符串有n+1个隔板 + // 長度為n的字符串有n+1個隔板 vector f(s.length() + 1, false); - // prev[i][j]为true,表示s[j, i)是一个合法单词,可以从j处切开 + // prev[i][j]為true,表示s[j, i)是一個合法單詞,可以從j處切開 // 第一行未用 vector > prev(s.length() + 1, vector(s.length())); f[0] = true; @@ -1195,7 +1331,7 @@ \subsubsection{代码} } private: - // DFS遍历树,生成路径 + // DFS遍歷樹,生成路徑 void gen_path(const string &s, const vector > &prev, int cur, vector &path, vector &result) { if (cur == 0) { @@ -1217,7 +1353,159 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Word Break, 见 \S \ref{sec:word-break} +\item Word Break, 見 \S \ref{sec:word-break} \myenddot + +\section{Target Sum} +\label{sec:target-sum} + +\subsection{描述} +You are given a list of non-negative integers, a1, a2, ..., an, and a target, S. Now you have 2 symbols + and -. For each integer, you should choose one from + and - as its new symbol. + +Find out how many ways to assign symbols to make sum of integers equal to target S. + +Example 1: +\begin{Code} +Input: nums is [1, 1, 1, 1, 1], S is 3. +Output: 5 +Explanation: + +-1+1+1+1+1 = 3 ++1-1+1+1+1 = 3 ++1+1-1+1+1 = 3 ++1+1+1-1+1 = 3 ++1+1+1+1-1 = 3 + +There are 5 ways to assign symbols to make the sum of nums be target 3. +\end{Code} + + +\begindot +\item The length of the given array is positive and will not exceed 20. +\item The sum of elements in the given array will not exceed 1000. +\item Your output answer is guaranteed to be fitted in a 32-bit integer. +\myenddot + +\subsection{深搜} +\begin{Code} +// LeetCode +// 時間複雜度O(2^n),空間複雜度O(n) +class Solution { +public: + int findTargetSumWays(vector& nums, int S) { + m_ways = 0; + + DFS(nums, 0, 0, S); + + return m_ways; + } +private: + int m_ways; + void DFS(vector& nums, int step, int curS, int S) + { + if (step == nums.size() && S == curS) + { + m_ways++; + return; + } + else if (step >= nums.size()) + return; + + DFS(nums, step + 1, curS + nums[step], S); + DFS(nums, step + 1, curS - nums[step], S); + } +}; +\end{Code} + + +\subsection{備忘錄法} +\begin{Code} +// LeetCode +// 時間複雜度O(l*n),空間複雜度O(l*n) +class Solution { +public: + int findTargetSumWays(vector& nums, int S) { + vector> cache(nums.size(), vector(2001, INT_MIN)); + + return DFS(nums, 0, 0, S, cache); + } +private: + int DFS(vector& nums, int step, int curS, int S, vector>& cache) + { + if (step == nums.size()) + { + if (S == curS) + return 1; + else + return 0; + } + else + { + if (cache[step][curS + 1000] != INT_MIN) + return cache[step][curS + 1000]; + + int add = DFS(nums, step + 1, curS + nums[step], S, cache); + int subtract = DFS(nums, step + 1, curS - nums[step], S, cache); + cache[step][curS + 1000] = add + subtract; + return cache[step][curS + 1000]; + } + } +}; +\end{Code} + + +\subsection{動規} +\begin{Code} +// LeetCode +// 時間複雜度O(l*n),空間複雜度O(l*n) +class Solution { +public: + int findTargetSumWays(vector& nums, int S) { + vector> dp(nums.size(), vector(2001, 0)); + dp[0][nums[0] + 1000] = 1; + dp[0][-nums[0] + 1000] += 1; + for (int step = 1; step < nums.size(); step++) + { + for (int sum = -1000; sum <= 1000; sum++) + { + if (dp[step - 1][sum + 1000] > 0) + { + dp[step][sum + nums[step] + 1000] += dp[step - 1][sum + 1000]; + dp[step][sum - nums[step] + 1000] += dp[step - 1][sum + 1000]; + } + } + } + return S > 1000 ? 0 : dp[nums.size() - 1][S + 1000]; + } +}; +\end{Code} + +\subsection{動規 + 滾動數組} +\begin{Code} +// LeetCode +// 時間複雜度O(l*n),空間複雜度O(n) +class Solution { +public: + int findTargetSumWays(vector& nums, int S) { + vector dp(2001, 0); + dp[nums[0] + 1000] = 1; + dp[-nums[0] + 1000] += 1; + for (int step = 1; step < nums.size(); step++) + { + vector next(2001, 0); + for (int sum = -1000; sum <= 1000; sum++) + { + if (dp[sum + 1000] > 0) + { + next[sum + nums[step] + 1000] += dp[sum + 1000]; + next[sum - nums[step] + 1000] += dp[sum + 1000]; + } + } + dp = next; + } + return S > 1000 ? 0 : dp[S + 1000]; + } +}; +\end{Code} diff --git a/C++/chapGoogle.tex b/C++/chapGoogle.tex new file mode 100644 index 00000000..04d06bec --- /dev/null +++ b/C++/chapGoogle.tex @@ -0,0 +1,2341 @@ +\chapter{Google} +Google interview questions +\newline + +\section{Odd Even Jump} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:odd-even-jump} + + +\subsubsection{描述} +You are given an integer array A. From some starting index, you can make a series of jumps. The (1st, 3rd, 5th, ...) jumps in the series are called odd numbered jumps, and the (2nd, 4th, 6th, ...) jumps in the series are called even numbered jumps. + +You may from index i jump forward to index j (with i < j) in the following way: + +During odd numbered jumps (ie. jumps 1, 3, 5, ...), you jump to the index j such that A[i] <= A[j] and A[j] is the smallest possible value. If there are multiple such indexes j, you can only jump to the smallest such index j. +During even numbered jumps (ie. jumps 2, 4, 6, ...), you jump to the index j such that A[i] >= A[j] and A[j] is the largest possible value. If there are multiple such indexes j, you can only jump to the smallest such index j. +(It may be the case that for some index i, there are no legal jumps.) +A starting index is good if, starting from that index, you can reach the end of the array (index A.length - 1) by jumping some number of times (possibly 0 or more than once.) + +Return the number of good starting indexes. + +Example 1: +\begin{Code} +Input: [10,13,12,14,15] +Output: 2 +Explanation: +From starting index i = 0, we can jump to i = 2 (since A[2] is the smallest among A[1], A[2], A[3], A[4] that is greater or equal to A[0]), then we can't jump any more. +From starting index i = 1 and i = 2, we can jump to i = 3, then we can't jump any more. +From starting index i = 3, we can jump to i = 4, so we've reached the end. +From starting index i = 4, we've reached the end already. +In total, there are 2 different starting indexes (i = 3, i = 4) where we can reach the end with some number of jumps. +\end{Code} + +Example 2: +\begin{Code} +Input: [2,3,1,1,4] +Output: 3 +Explanation: +From starting index i = 0, we make jumps to i = 1, i = 2, i = 3: + +During our 1st jump (odd numbered), we first jump to i = 1 because A[1] is the smallest value in (A[1], A[2], A[3], A[4]) that is greater than or equal to A[0]. + +During our 2nd jump (even numbered), we jump from i = 1 to i = 2 because A[2] is the largest value in (A[2], A[3], A[4]) that is less than or equal to A[1]. A[3] is also the largest value, but 2 is a smaller index, so we can only jump to i = 2 and not i = 3. + +During our 3rd jump (odd numbered), we jump from i = 2 to i = 3 because A[3] is the smallest value in (A[3], A[4]) that is greater than or equal to A[2]. + +We can't jump from i = 3 to i = 4, so the starting index i = 0 is not good. + +In a similar manner, we can deduce that: +From starting index i = 1, we jump to i = 4, so we reach the end. +From starting index i = 2, we jump to i = 3, and then we can't jump anymore. +From starting index i = 3, we jump to i = 4, so we reach the end. +From starting index i = 4, we are already at the end. +In total, there are 3 different starting indexes (i = 1, i = 3, i = 4) where we can reach the end with some number of jumps. +\end{Code} + +Example 3: +\begin{Code} +Input: [5,1,3,4,2] +Output: 3 +Explanation: +We can reach the end from starting indexes 1, 2, and 4. +\end{Code} + + +\subsubsection{動規 - Monotonic Stack} +\begin{Code} +// 思路: 先求得單和雙數往後跳的目的地,再用動規,得知由某點能否跳往目的地 +// 時間複雜度O(nlogn),空間複雜度O(n) +class Solution { +public: + int oddEvenJumps(vector& A) { + vector> cache; cache.reserve(A.size()); + for (int i = 0; i < A.size(); i++) + cache.emplace_back(A[i], i); + // 找尋增大順序的下標 + sort(cache.begin(), cache.end() + , [&](const auto& left, const auto& right) + { + if (left.first == right.first) + return left.second < right.second; + else + return left.first < right.first; + }); + vector sortedIndex; + sortedIndex.reserve(cache.size()); + for_each(cache.begin(), cache.end() + , [&](const auto& ele) { sortedIndex.push_back(ele.second); } ); + // 求得單數跳躍的下標 + vector oddNext = GetNext(sortedIndex); + + // 找尋減少順序的下標 + sort(cache.begin(), cache.end() + , [&](const auto& left, const auto& right) + { + if (left.first == right.first) + return left.second < right.second; + else + return left.first > right.first; + }); + sortedIndex.clear(); + sortedIndex.reserve(cache.size()); + for_each(cache.begin(), cache.end() + , [&](const auto& ele) { sortedIndex.push_back(ele.second); } ); + // 求得雙數跳躍的下標 + vector evenNext = GetNext(sortedIndex); + + // 設 odd[i]: 單數跳躍時可以由 i 到 N + vector odd(A.size(), false); + // 設 even[i]: 雙數跳躍時可以由 i 到 N + vector even(A.size(), false); + odd.back() = even.back() = true; + + // odd 的總數便是答案 + for (int i = A.size() - 1; i >= 0; i--) + { + if (oddNext[i] != -1) + odd[i] = even[oddNext[i]]; + if (evenNext[i] != -1) + even[i] = odd[evenNext[i]]; + } + + int result = 0; + for (const auto& o : odd) + if (o) result++; + + return result; + } +private: + vector GetNext(const vector& inputIndex) + { + // 使用了 Monotonic Stack + stack cache; + vector result(inputIndex.size(), -1); // -1 代表沒法再往後跳躍 + + for (const auto& index : inputIndex) + { + while (!cache.empty() && cache.top() < index) + { + result[cache.top()] = index; + cache.pop(); + } + cache.push(index); + } + + return result; + } +}; +\end{Code} + +\subsubsection{動規 - Map} +\begin{Code} +// 思路: 利用 BST 由尾至頭歷遍,找出每一個對應的跳躍目的地 +// 時間複雜度O(nlogn),空間複雜度O(n) +class Solution { +public: + int oddEvenJumps(vector& A) { + int N = A.size(); + // 設 odd[i]: 單數跳躍時可以由 i 到 N + vector odd(A.size(), false); + // 設 even[i]: 雙數跳躍時可以由 i 到 N + vector even(A.size(), false); + odd.back() = even.back() = true; + + map cache; // key: A[i] value: i + cache.emplace(A[N-1], N-1); + + for (int i = N - 2; i >= 0; i--) + { + auto it = cache.find(A[i]); + if (it != cache.end()) + { + odd[i] = even[it->second]; + even[i] = odd[it->second]; + } + else + { + auto bigger = GetBigger(cache, A[i]); + auto smaller = GetSmaller(cache, A[i]); + + if (bigger != cache.end()) + odd[i] = even[bigger->second]; + if (smaller != cache.end()) + even[i] = odd[smaller->second]; + } + cache[A[i]] = i; + } + + // odd 的總數便是答案 + int result = 0; + for (const auto& o : odd) + if (o) result++; + + return result; + } +private: + template + typename MyMap::iterator GetBigger(MyMap& cache, T val) + { + auto bigger = cache.lower_bound(val); + if (bigger == cache.end()) + return bigger; + else + return bigger; + } + template + typename MyMap::iterator GetSmaller(MyMap& cache, T val) + { + auto smaller = cache.lower_bound(val); + if (smaller == cache.begin()) + return cache.end(); + else + return prev(smaller); + } +}; +\end{Code} + +\subsubsection{動規 - Multimap} +\begin{Code} +// 思路: 利用 BST 由尾至頭歷遍,找出每一個對應的跳躍目的地 +// 時間複雜度O(nlogn),空間複雜度O(n) +class Solution { +public: + int oddEvenJumps(vector& A) { + int N = A.size(); + // 設 odd[i]: 單數跳躍時可以由 i 到 N + vector odd(A.size(), false); + // 設 even[i]: 雙數跳躍時可以由 i 到 N + vector even(A.size(), false); + odd.back() = even.back() = true; + + multimap cache; // key: A[i] value: i + cache.emplace(A[N-1], N-1); + + for (int i = N - 2; i >= 0; i--) + { + auto it = cache.find(A[i]); + if (it != cache.end()) + { + // 找尋同值最細下標 + it = GetSmallestIndex(cache, it); + odd[i] = even[it->second]; + even[i] = odd[it->second]; + } + else + { + auto bigger = GetBigger(cache, A[i]); + auto smaller = GetSmaller(cache, A[i]); + + if (smaller != cache.end()) + even[i] = odd[smaller->second]; + if (bigger != cache.end()) + odd[i] = even[bigger->second]; + } + cache.emplace(A[i], i); + } + + // odd 的總數便是答案 + int result = 0; + for (const auto& o : odd) + if (o) result++; + + return result; + } +private: + template + typename MyMap::iterator GetBigger(MyMap& cache, T val) + { + auto bigger = cache.lower_bound(val); + if (bigger == cache.end()) + return bigger; + else + return GetSmallestIndex(cache, bigger); // 找尋同值最細下標 + } + template + typename MyMap::iterator GetSmaller(MyMap& cache, T val) + { + auto smaller = cache.lower_bound(val); + if (smaller == cache.begin()) + return cache.end(); + else + return GetSmallestIndex(cache, prev(smaller)); // 找尋同值最細下標 + } + template + MapIT GetSmallestIndex(MyMap& cache, MapIT target) + { + auto range = cache.equal_range(target->first); + // 找尋同值最細下標 + int minIndex = INT_MAX; + for (auto j = range.first; j != range.second; j++) + { + if (minIndex > j->second) + { + target = j; + minIndex = j->second; + } + } + + return target; + } +}; +\end{Code} + +\section{Course Schedule II} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:course-schedule-ii} + + +\subsubsection{描述} +There are a total of n courses you have to take, labeled from 0 to n-1. + +Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1] + +Given the total number of courses and a list of prerequisite pairs, return the ordering of courses you should take to finish all courses. + +There may be multiple correct orders, you just need to return one of them. If it is impossible to finish all courses, return an empty array. + +Example 1: +\begin{Code} +Input: 2, [[1,0]] +Output: [0,1] +Explanation: There are a total of 2 courses to take. + To take course 1 you should have finished + course 0. So the correct course order is [0,1] . +\end{Code} + +Example 2: +\begin{Code} +Input: 4, [[1,0],[2,0],[3,1],[3,2]] +Output: [0,1,2,3] or [0,2,1,3] +Explanation: There are a total of 4 courses to take. + To take course 3 you should have finished both courses 1 and 2. + Both courses 1 and 2 should be taken after you finished course 0. + So one correct course order is [0,1,2,3]. + Another correct ordering is [0,2,1,3] . +\end{Code} + +Note: +\begindot +\item The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented. +\item You may assume that there are no duplicate edges in the input prerequisites. +\myenddot + +\subsubsection{DFS - Topological Sort} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + vector findOrder(int numCourses, vector>& prerequisites) { + vector result; result.reserve(numCourses); + + // Topological Sort + // 先造出 dependency graph + // 也記低有什麼 course 出現過 + unordered_map> dGraph; + unordered_set seenCourse; + for (const auto& p : prerequisites) + { + dGraph[p[0]].push_back(p[1]); + seenCourse.insert(p[0]); + seenCourse.insert(p[1]); + } + + // 先補上在 graph 中沒有出現的 course + for (int i = 0; i < numCourses; i++) + if (seenCourse.count(i) == 0) result.push_back(i); + + // 利用 DFS 造出答案 + unordered_map visited; + + for (const auto& [k, v] : dGraph) + { + if (!DFS(dGraph, k, result, visited)) return vector(); + } + + return result; + } +private: + bool DFS(const unordered_map>& dGraph, int k + , vector& result, unordered_map& visited) + { + if (visited.find(k) != visited.end()) return visited[k]; + + visited[k] = false; + + auto it = dGraph.find(k); + if (it != dGraph.end()) + { + for (const auto& nei : it->second) + { + if (!DFS(dGraph, nei, result, visited)) return false; + } + } + + visited[k] = true; + result.push_back(k); + + return true; + } +}; +\end{Code} + +\section{Longest Increasing Path in a Matrix} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:longest-increasing-path-in-a-matrix} + + +\subsubsection{描述} +Given an integer matrix, find the length of the longest increasing path. + +From each cell, you can either move to four directions: left, right, up or down. You may NOT move diagonally or move outside of the boundary (i.e. wrap-around is not allowed). + +Example 1: +\begin{Code} +Input: nums = +[ + [9,9,4], + [6,6,8], + [2,1,1] +] +Output: 4 +Explanation: The longest increasing path is [1, 2, 6, 9]. +\end{Code} + +Example 2: +\begin{Code} +Input: nums = +[ + [3,4,5], + [3,2,6], + [2,2,1] +] +Output: 4 +Explanation: The longest increasing path is [3, 4, 5, 6]. Moving diagonally is not allowed. +\end{Code} + + +\subsubsection{備忘錄法} +\begin{Code} +// 時間複雜度O(m*n),空間複雜度O(m*n) +class Solution { +public: + int longestIncreasingPath(vector>& matrix) { + m_M = matrix.size(); + if (m_M == 0) return 0; + m_N = matrix[0].size(); + if (m_N == 0) return 0; + + vector> visited(m_M, vector(m_N, false)); + // 設 cache[i][j] 為 i,j 的最長答案 + // 這個答案是由 0 開始總加的,所以 DFS 也要由 0 開始總加 + vector> cache(m_M, vector(m_N, -1)); + int maxLen = INT_MIN; + for (int i = 0; i < m_M; i++) + { + for (int j = 0; j < m_N; j++) + { + maxLen = max(maxLen, DFS(i, j, matrix, cache, visited)); + } + } + + + return maxLen; + } +private: + const vector> directions{{1,0}, {0,1}, {-1,0}, {0,-1}}; + int DFS(int i, int j, const vector>& matrix + , vector>& cache, vector>& visited) + { + if (cache[i][j] != -1) return cache[i][j]; + + visited[i][j] = true; + int maxLen = 0; + for (int d = 0; d < 4; d++) + { + int newI = i + directions[d].first; + int newJ = j + directions[d].second; + if (newI >= 0 && newI < m_M && newJ >= 0 && newJ < m_N + && !visited[newI][newJ] + && matrix[i][j] > matrix[newI][newJ])// 由大至細地找尋答案 (由細至大也可以) + maxLen = max(maxLen, DFS(newI, newJ, matrix, cache, visited)); + } + visited[i][j] = false; + + return cache[i][j] = maxLen + 1; // 注意: 由細至大保存中途答案 + } +private: + int m_M; + int m_N; +}; +\end{Code} + +\section{Count Complete Tree Nodes} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:count-complete-tree-nodes} + + +\subsubsection{描述} +Given a complete binary tree, count the number of nodes. + +Note: + +Definition of a complete binary tree from Wikipedia: +In a complete binary tree every level, except possibly the last, is completely filled, and all nodes in the last level are as far left as possible. It can have between 1 and 2h nodes inclusive at the last level h. + +Example: +\begin{Code} +Input: + 1 + / \ + 2 3 + / \ / +4 5 6 + +Output: 6 +\end{Code} + + +\subsubsection{暴力} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + int countNodes(TreeNode* root) { + if (root == nullptr) return 0; + + return 1 + countNodes(root->left) + countNodes(root->right); + } +}; +\end{Code} + +\subsubsection{Binary Search} +\begin{Code} +// 時間複雜度O(d^2),空間複雜度O(n) +class Solution { +public: + int countNodes(TreeNode* root) { + // 若果樹沒有元素 + if (root == nullptr) return 0; + + int d = ComputeDepth(root); + // 若果樹只有一個元素 + if (d == 0) return 1; + + // left: 為最低層的最左的 node index + // right: 為最低層的最右的 node index + int left = 1; int right = pow(2, d) - 1; + while (left <= right) + { + int pivot = (left + right) / 2; + if (IsExist(pivot, d, root)) + left = pivot + 1; + else + right = pivot - 1; + } + + return pow(2, d) - 1 + left; + } +private: + int ComputeDepth(TreeNode *root) + { + int d = 0; + while (root->left) + { + root = root->left; + d++; + } + return d; + } + bool IsExist(int idx, int d, TreeNode* node) + { + int left = 0; int right = pow(2, d) - 1; + for (int i = 0; i < d; i++) + { + int pivot = (left + right) / 2; + if (idx <= pivot) + { + node = node->left; + right = pivot; + } + else + { + node = node->right; + left = ++pivot; + } + } + return node != nullptr; + } +}; +\end{Code} + +\section{Flip Equivalent Binary Trees} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:flip-equivalent-binary-trees} + + +\subsubsection{描述} +For a binary tree T, we can define a flip operation as follows: choose any node, and swap the left and right child subtrees. + +A binary tree X is flip equivalent to a binary tree Y if and only if we can make X equal to Y after some number of flip operations. + +Write a function that determines whether two binary trees are flip equivalent. The trees are given by root nodes root1 and root2. + +Example 1: +\begin{Code} +Input: + 1 1 + / \ / \ + 3 2 2 3 + \ / \ / \ / + 6 4 5 4 5 6 + / \ / \ + 8 7 7 8 + +Input: root1 = [1,2,3,4,5,6,null,null,null,7,8], root2 = [1,3,2,null,6,4,5,null,null,null,null,8,7] +Output: true +Explanation: We flipped at nodes with values 1, 3, and 5. +\end{Code} + +Note: +\begindot +\item Each tree will have at most 100 nodes. +\item Each value in each tree will be a unique integer in the range [0, 99] +\myenddot + + +\subsubsection{遞歸} +\begin{Code} +// 時間複雜度O(min(N1, N2)),空間複雜度O(min(H1, H2)) +class Solution { +public: + bool flipEquiv(TreeNode* root1, TreeNode* root2) { + if (root1 == nullptr) return root2 == nullptr; + if (root2 == nullptr) return root1 == nullptr; + + return root1->val == root2->val + && (flipEquiv(root1->right, root2->left) && flipEquiv(root1->left, root2->right) + || flipEquiv(root1->right, root2->right) && flipEquiv(root1->left, root2->left)); + } +}; +\end{Code} + +\subsubsection{Canonical Traversal} +\begin{Code} +// 時間複雜度O(min(N1, N2)),空間複雜度O(min(H1, H2)) +class Solution { +public: + bool flipEquiv(TreeNode* root1, TreeNode* root2) { + list vals1; + list vals2; + + // 由細至大把二叉樹放到鏈表中 + DFS(root1, vals1); + DFS(root2, vals2); + + // 當兩個表是一樣,回 true + return vals1.size() == vals2.size() + && equal(vals1.begin(), vals1.end(), vals2.begin() + , [](const auto& left, const auto& right) + { + if (left == nullptr && right == nullptr) return true; + else if (left == nullptr || right == nullptr) return false; + else return left->val == right->val; + }); + } +private: + void DFS(TreeNode *root, list& vals) + { + if (root == nullptr) return; + + vals.push_back(root); + + int L = root->left == nullptr ? -1 : root->left->val; + int R = root->right == nullptr ? -1 : root->right->val; + + if (L < R) + { + DFS(root->left, vals); + DFS(root->right, vals); + } + else + { + DFS(root->right, vals); + DFS(root->left, vals); + } + + vals.push_back(nullptr); + } +}; +\end{Code} + +\section{Diameter of Binary Tree} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:diameter-of-binary-tree} + + +\subsubsection{描述} +Given a binary tree, you need to compute the length of the diameter of the tree. The diameter of a binary tree is the length of the longest path between any two nodes in a tree. This path may or may not pass through the root. + +Example: +Given a binary tree +\begin{Code} + 1 + / \ + 2 3 + / \ + 4 5 + +\end{Code} +Return 3, which is the length of the path [4,2,1,3] or [5,2,1,3].\\ +Note: The length of path between two nodes is represented by the number of edges between them. + + +\begin{center} + \includegraphics[width=300pt]{diameter-of-binary-tree.png}\\ + \figcaption{Diameter Of Binary Tree}\label{fig:diameter-of-binary-tree} +\end{center} + +Example of binary tree that no root node included.\\ +Return 10, which is length of the path [24, 21, 15, 10, 6, 3, 7, 13, 18, 23, 26] or [25, 21, 15, 10, 6, 3, 7, 13, 18, 23, 26] + +\subsubsection{遞歸} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + int diameterOfBinaryTree(TreeNode* root) { + m_result = 1; + DFS(root); + return m_result - 1; + } +private: + int DFS(TreeNode *root) + { + if (root == nullptr) return 0; + + int L = DFS(root->left); + int R = DFS(root->right); + + m_result = max(m_result, L+R+1); // 由左至右總加起來 + return max(L, R) + 1; // 注意: 只取最長的子樹 + } +private: + int m_result; +}; +\end{Code} + +\section{Evaluate Division} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:evaluate-division} + + +\subsubsection{描述} +Equations are given in the format A / B = k, where A and B are variables represented as strings, and k is a real number (floating point number). Given some queries, return the answers. If the answer does not exist, return -1.0. + +Example: +Given a / b = 2.0, b / c = 3.0. +queries are: a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ? . +return [6.0, 0.5, -1.0, 1.0, -1.0 ]. + +\begin{Code} + The input is: vector> equations, vector& values + , vector> queries , where equations.size() == values.size() + , and the values are positive. This represents the equations. Return vector. +\end{Code} + +According to the example above: + +\begin{Code} +equations = [ ["a", "b"], ["b", "c"] ], +values = [2.0, 3.0], +queries = [ ["a", "c"], ["b", "a"], ["a", "e"], ["a", "a"], ["x", "x"] ]. +\end{Code} + +The input is always valid. You may assume that evaluating the queries will result in no division by zero and there is no contradiction. + +\subsubsection{遞歸 - DFS} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + vector calcEquation(vector>& equations + , vector& values + , vector>& queries){ + // 造圖 + unordered_map>> g; + for (int i = 0; i < equations.size(); i++) + { + string& strA = equations[i][0]; + string& strB = equations[i][1]; + // 記錄 A / B + g[strA].emplace_back(values[i], strB); + // 記錄 B / A + g[strB].emplace_back(1 / values[i], strA); + } + + vector result; + for (int i = 0; i < queries.size(); i++) + { + const string& qStart = queries[i][0]; + const string& qEnd = queries[i][1]; + // 若整個除數關係沒有記錄 + if (g.find(qStart) == g.end() || g.find(qEnd) == g.end()) + result.push_back(-1.0); + else + { + // 若有記錄 + set visited; + int ansLen = result.size(); + DFS(qStart, qEnd, 1, visited, result, g); + if (result.size() == ansLen) + result.push_back(-1); // 若找不到答案 + } + } + return result ; + } +private: + void DFS(string start, string end, double cost + , set& visited + , vector& result + , unordered_map>>& g) + { + visited.insert(start); + + if (start == end) + { + result.push_back(cost); + return; + } + + for (const auto& [num, neigbor] : g[start]) + { + if (visited.find(neigbor) != visited.end()) continue; + + int ansLen = result.size(); + DFS(neigbor, end, cost * num, visited, result, g); + if (ansLen != result.size()) return; // 已找到答案,剪枝 + } + } +}; +\end{Code} + +\section{Cracking the Safe} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:cracking-the-safe} + + +\subsubsection{描述} +There is a box protected by a password. The password is a sequence of n digits where each digit can be one of the first k digits 0, 1, ..., k-1. + +While entering a password, the last n digits entered will automatically be matched against the correct password. + +For example, assuming the correct password is "345", if you type "012345", the box will open because the correct password matches the suffix of the entered password. + +Return any password of minimum length that is guaranteed to open the box at some point of entering it. + +Example 1: +\begin{Code} +Input: n = 1, k = 2 +Output: "01" +Note: "10" will be accepted too. +\end{Code} + +Example 2: +\begin{Code} +Input: n = 2, k = 2 +Output: "00110" +Note: "01100", "10011", "11001" will be accepted too. +\end{Code} + +Note: +\begindot +\item n will be in the range (1, 4). +\item k will be in the range (1, 10). +\item pow(k, n) will be at most 4096. +\myenddot + +\subsubsection{遞歸 - DFS - Hierholzer's Algorithm} +\begin{Code} +// 時間複雜度O(n * k^n),空間複雜度O(n * k^n) +// 利用 Hierholzer's Algorithm 歷遍所有 edges 並記錄所有的 nodes +// Hierholzer's Algorithm 用來找尋 Eulerian Path +class Solution { +public: + string crackSafe(int n, int k) { + if (n == 1 && k == 1) return "0"; + // seen 用來存放見過的 node + unordered_set seen; + string result; + + // 準備開始點 + string start; start.append(n-1, '0'); + + DFS(start, k, seen, result); + result += start; + + return result; + } +private: + void DFS(const string& node, int k, unordered_set& seen, string& result) + { + // 這是 post order travel 的做法 + for (int x = 0; x < k; x++) + { + // nei 是下一個歷遍的 node + edge + // x 為 edge, 0, 1, 2 ... k-1 + string nei = node + to_string(x); + if (seen.find(nei) == seen.end()) + { + seen.insert(nei); + // nei.substr(1) 可以提取下一個 node + // 例子: 01(上一個 node) -> 010(edge) -> 10(下一個 node) + DFS(nei.substr(1), k, seen, result); + result += to_string(x); // 完成後,記低 edge + } + } + } +}; +\end{Code} + +\section{Most Stones Removed with Same Row or Column} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:most-stones-removed-with-same-row-or-column} + + +\subsubsection{描述} +On a 2D plane, we place stones at some integer coordinate points. Each coordinate point may have at most one stone. + +Now, a move consists of removing a stone that shares a column or row with another stone on the grid. + +What is the largest possible number of moves we can make? + +Example 1: +\begin{Code} +Input: stones = [[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]] +Output: 5 +\end{Code} + +Example 2: +\begin{Code} +Input: stones = [[0,0],[0,2],[1,1],[2,0],[2,2]] +Output: 3 +\end{Code} + +Example 3: +\begin{Code} +Input: stones = [[0,0]] +Output: 0 +\end{Code} + +Note: +\begindot +\item 1 <= stones.length <= 1000 +\item 0 <= stones[i][j] < 10000 +\myenddot + +\subsubsection{迭代 - DFS - stack} +\begin{Code} +// 時間複雜度O(n^2),空間複雜度O(n^2) +class Solution { +public: + int removeStones(vector>& stones) { + int N = stones.size(); + + // graph[i][0]: 代表總共有多少個石頭和第 i 個石頭位於同列或欄 + // graph[i][x]: 1 <= x <= N-1, x 存了 stones 的 index + // 例子 graph[0][2] == 5: stones[0] 的第二個同列或欄石頭 是 stones[5] + vector> graph(N, vector(N, 0)); + for (int i = 0; i < N; i++) + { + for (int j = i+1; j < N; j++) + { + // 若找到同列或同欄 + if (stones[i][0] == stones[j][0] + || stones[i][1] == stones[j][1]) + { + // 交叉記錄 + graph[i][++graph[i][0]] = j; + graph[j][++graph[j][0]] = i; + } + } + } + + int ans = 0; + vector seen(N, false); + stack cache; + for (int i = 0; i < N; i++) + { + if (!seen[i]) + { + cache.push(i); + seen[i] = true; + ans--; + while (!cache.empty()) + { + int node = cache.top(); + cache.pop(); + ans++; + for (int k = 1; k <= graph[node][0]; k++) + { + int nei = graph[node][k]; + if (!seen[nei]) + { + cache.push(nei); + seen[nei] = true; + } + } + } + } + } + + return ans; + } +}; +\end{Code} + +\subsubsection{Union} +\begin{Code} +// 時間複雜度O(nlogn),空間複雜度O(n) +class DSU // Disjoint Set Union +{ +public: + DSU(int N) + { + m_parent.resize(N); + for (int i = 0; i < N; i++) m_parent[i] = i; + } + ~DSU() {} + + int Find(const int& x) + { + if (m_parent[x] == x) return x; + return m_parent[x] = Find(m_parent[x]); + } + + void Union(const int& x, const int& y) + { + const int& xpar = Find(x); + const int& ypar = Find(y); + + if (xpar == ypar) return; + + m_parent[xpar] = ypar; + } +private: + vector m_parent; +}; +class Solution { +public: + int removeStones(vector>& stones) { + int M = 10000; // Number of Row + int N = 10000; // Number of Col + + DSU dsu(M + N); // 利用一個數組去存放二維的數據 + + for (const auto& stone : stones) + dsu.Union(stone[0], stone[1] + M); + + unordered_set groups; + for (const auto& stone : stones) + groups.insert(dsu.Find(stone[0])); + + return stones.size() - groups.size(); + } +}; +\end{Code} + +\section{Strobogrammatic Number II} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:strobogrammatic-number-ii} + + +\subsubsection{描述} +A strobogrammatic number is a number that looks the same when rotated 180 degrees (looked at upside down). + +Find all strobogrammatic numbers that are of length = n. + +Example: +\begin{Code} +Input: n = 2 +Output: ["11","69","88","96"] +\end{Code} + +\subsubsection{遞歸 - DFS} +\begin{Code} +// 時間複雜度O(),空間複雜度O() +class Solution { +public: + vector findStrobogrammatic(int n) + { + return helper(n, n); + } + + vectorhelper(int n, int m) + { + if (n == 0) return vector {""}; + if (n == 1) return vector {"0","1","8"}; + + vector list = helper(n-2, m); + vector result; + + for (int i = 0;i < list.size(); i++) + { + string s = list[i]; + if (n != m) result.push_back("0" + s + "0"); + + result.push_back("1" + s + "1"); + result.push_back("6" + s + "9"); + result.push_back("8" + s + "8"); + result.push_back("9" + s + "6"); + } + return result; + } +}; +\end{Code} + +\section{Word Search II} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:word-search-ii} + + +\subsubsection{描述} +Given a 2D board and a list of words from the dictionary, find all words in the board. + +Each word must be constructed from letters of sequentially adjacent cell, where "adjacent" cells are those horizontally or vertically neighboring. The same letter cell may not be used more than once in a word. + +Example: +\begin{Code} +Input: +board = [ + ['o','a','a','n'], + ['e','t','a','e'], + ['i','h','k','r'], + ['i','f','l','v'] +] +words = ["oath","pea","eat","rain"] + +Output: ["eat","oath"] +\end{Code} + +\begindot +\item All inputs are consist of lowercase letters a-z. +\item The values of words are distinct. +\myenddot + +\subsubsection{遞歸 - DFS - Trie} +\begin{Code} +// 時間複雜度O(M(4*3^L),空間複雜度O(N) +// 利用 Trie 存放目標,DFS 去找尋答案 +struct TrieNode +{ + unordered_map next; + string word; +}; + +class Solution { +public: + vector findWords(vector>& board, vector& words) { + TrieNode root; + // 製造 Trie + for (const string& w : words) + { + TrieNode *p = &root; + for (const char& c : w) + { + if (p->next.find(c) == p->next.end()) + p->next[c] = new TrieNode(); + p = p->next[c]; + } + if (p->next.find(END_KEY) == p->next.end()) + p->next[END_KEY] = new TrieNode(); + p->next[END_KEY]->word = w; + } + + // DFS 找尋答案 + vector result; + for (int i = 0; i < board.size(); i++) + { + for (int j = 0; j < board[0].size(); j++) + { + if (root.next.find(board[i][j]) != root.next.end()) + DFS(i, j, board, &root, result); + } + } + return result; + } +private: + const vector dirX{-1,0,1,0}; + const vector dirY{0,-1,0,1}; + const char END_KEY = '$'; + const char VISITED_KEY = '#'; + void DFS(int i, int j, vector>& board + , TrieNode *node, vector& result) + { + const char letter = board[i][j]; + TrieNode *nextNode = node->next[letter]; + + // 若找到答案 + if (nextNode->next.find(END_KEY) != nextNode->next.end()) + { + result.push_back(nextNode->next[END_KEY]->word); + // 剛除已找到的文字 + nextNode->next.erase(END_KEY); + } + + // 標為 visited + board[i][j] = VISITED_KEY; + + // 擴展 + for (int x = 0; x < 4; x++) + { + int newI = i + dirX[x]; + int newJ = j + dirY[x]; + + // 若出了界限 + if (newI < 0 || newI >= board.size() || newJ < 0 || newJ >= board[0].size()) + continue; + // 若新的字母是不需要的,包括了已經訪問的檢查 + if (nextNode->next.find(board[newI][newJ]) == nextNode->next.end()) + continue; + DFS(newI, newJ, board, nextNode, result); + } + + // 復原 + board[i][j] = letter; + + // 刪除空了的 TrieNode 加快找尋 + if (nextNode->next.size() == 0) + node->next.erase(letter); + } +}; +\end{Code} + +\subsubsection{遞歸 - DFS - Trie - Array} +\begin{Code} +// 時間複雜度O(M(4*3^L),空間複雜度O(N) +// 利用 Trie 存放目標,DFS 去找尋答案 +struct TierNode +{ + vector next; // 這裏不同,不是用 hash + string word; + + TierNode() + { + next.resize(26, nullptr); + } +}; + +class Solution { +public: + vector findWords(vector>& board, vector& words) { + TierNode root; + // 製造 Tier + for (const string& w : words) + { + TierNode *p = &root; + for (const char& c : w) + { + if (p->next[c-'a'] == nullptr) + p->next[c-'a'] = new TierNode(); + p = p->next[c-'a']; + } + p->word = w; + } + + // DFS 找尋答案 + vector result; + for (int i = 0; i < board.size(); i++) + { + for (int j = 0; j < board[0].size(); j++) + { + if (root.next[board[i][j]-'a'] != nullptr) + DFS(i, j, board, &root, result); + } + } + return result; + } +private: + const vector dirX{-1,0,1,0}; + const vector dirY{0,-1,0,1}; + const char VISITED_KEY = '#'; + void DFS(int i, int j, vector>& board + , TierNode *node, vector& result) + { + const char letter = board[i][j]; + TierNode *nextNode = node->next[letter-'a']; + + // 若找到答案 + if (nextNode->word.size() != 0) + { + result.push_back(nextNode->word); + // 剛除已找到的文字 + nextNode->word = ""; + } + + // 標為 visited + board[i][j] = VISITED_KEY; + + // 擴展 + for (int x = 0; x < 4; x++) + { + int newI = i + dirX[x]; + int newJ = j + dirY[x]; + + // 若出了界限 + if (newI < 0 || newI >= board.size() || newJ < 0 || newJ >= board[0].size()) + continue; + // 若已經訪問過 + if (board[newI][newJ] == VISITED_KEY) // 這裏不同,分開了出來檢查 + continue; + // 若新的字母是不需要的 + if (nextNode->next[board[newI][newJ]-'a'] == nullptr) + continue; + DFS(newI, newJ, board, nextNode, result); + } + + // 復原 + board[i][j] = letter; + } +}; +\end{Code} + +\section{Word Squares} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:word-squares} + + +\subsubsection{描述} +Given a set of words (without duplicates), find all word squares you can build from them. + +A sequence of words forms a valid word square if the kth row and column read the exact same string, where 0 ≤ k < max(numRows, numColumns). + +For example, the word sequence ["ball","area","lead","lady"] forms a word square because each word reads the same both horizontally and vertically. + + +\begin{Code} +b a l l +a r e a +l e a d +l a d y +\end{Code} + +\begindot +\item There are at least 1 and at most 1000 words. +\item All words will have the exact same length. +\item Word length is at least 1 and at most 5. +\item Each word contains only lowercase English alphabet a-z. +\myenddot + +Example 1: +\begin{Code} +Input: +["area","lead","wall","lady","ball"] + +Output: +[ + [ "wall", + "area", + "lead", + "lady" + ], + [ "ball", + "area", + "lead", + "lady" + ] +] + +Explanation: +The output consists of two word squares. +The order of output does not matter (just the order of words in each word square matters). +\end{Code} + +Example 2: +\begin{Code} +Input: +["abat","baba","atan","atal"] + +Output: +[ + [ "baba", + "abat", + "baba", + "atan" + ], + [ "baba", + "abat", + "baba", + "atal" + ] +] + +Explanation: +The output consists of two word squares. +The order of output does not matter (just the order of words in each word square matters). +\end{Code} +\subsubsection{遞歸 - DFS} +\begin{Code} +// 時間複雜度O(),空間複雜度O(N) +// 會超時 +class Solution { +public: + vector> wordSquares(vector& words) { + vector> result; + if (words.size() == 0) return result; + + for (const string& w : words) + { + vector wordSquare{w}; + DFS(words, 1, wordSquare, result); + } + + return result; + } +private: + void DFS(const vector& words, int step + , vector& wordSquare, vector>& result) + { + if (step == wordSquare[0].size()) + { + // 求得答案 + result.push_back(wordSquare); + } + else if (step > wordSquare[0].size()) + return; + else + { + // 擴展 + string prefix; + for (const string& w : wordSquare) + prefix += w.substr(step, 1); + for (const auto& nei : GetNeiByPrefix(prefix, words)) + { + wordSquare.push_back(nei); + DFS(words, step+1, wordSquare, result); + wordSquare.pop_back(); + } + } + } + vector GetNeiByPrefix(const string& prefix, const vector& words) + { + vector result; result.reserve(words.size()); + for (const string& w : words) + { + if (w.find(prefix) == 0) result.push_back(w); + } + return result; + } +}; +\end{Code} + +\subsubsection{遞歸 - DFS - Hash} +\begin{Code} +// 時間複雜度O(N*26^L),空間複雜度O(N*L) L is the length of a single word +class Solution { +public: + vector> wordSquares(vector& words) { + vector> result; + if (words.size() == 0) return result; + CreatePrefixMap(words); // 利用 hash table 加快 + + + for (const string& w : words) + { + vector wordSquare{w}; + DFS(words, 1, wordSquare, result); + } + + return result; + } +private: + unordered_map> m_prefixMap; +private: + void CreatePrefixMap(const vector& words) + { + for (const string& w : words) + { + string prefix; + int count = 0; + for (int i = 0; i < w.size()-1; i++) + { + prefix += w[i]; + m_prefixMap[prefix].insert(w); + } + } + } + void DFS(const vector& words, int step + , vector& wordSquare, vector>& result) + { + if (step == wordSquare[0].size()) + { + // 求得答案 + result.push_back(wordSquare); + } + else if (step > wordSquare[0].size()) + return; + else + { + // 擴展 + string prefix; + for (const string& w : wordSquare) + prefix += w.substr(step, 1); + for (const auto& nei : GetNeiByPrefix(prefix, words)) + { + wordSquare.push_back(nei); + DFS(words, step+1, wordSquare, result); + wordSquare.pop_back(); + } + } + } + set GetNeiByPrefix(const string& prefix, const vector& words) + { + auto it = m_prefixMap.find(prefix); + if (it == m_prefixMap.end()) return set(); // 若找不到 + + return it->second; + } +}; +\end{Code} + +\subsubsection{遞歸 - DFS - Trie} +\begin{Code} +// 時間複雜度O(N*26^L),空間複雜度O(N*L) L is the length of a single word +struct Trie +{ + vector m_next; + vector m_wordIndex; + + Trie() { m_next.resize(26, nullptr); } +}; + +class Solution { +public: + vector> wordSquares(vector& words) { + vector> result; + if (words.size() == 0) return result; + CreateTrie(words); // 利用 hash table 加快 + + + vector wordSquare; + for (const string& w : words) + { + wordSquare.push_back(w); + DFS(words, 1, wordSquare, result); + wordSquare.pop_back(); + } + + return result; + } +private: + Trie m_root; +private: + void CreateTrie(const vector& words) + { + int wordIndex = 0; + for (const string& w : words) + { + Trie *cur = &m_root; + for (const char& c : w) + { + const char cc = c - 'a'; + if (cur->m_next[cc] == nullptr) + cur->m_next[cc] = new Trie(); + cur = cur->m_next[cc]; + cur->m_wordIndex.push_back(wordIndex); + } + wordIndex++; + } + } + void DFS(const vector& words, int step + , vector& wordSquare, vector>& result) + { + if (step == wordSquare[0].size()) + { + // 求得答案 + result.push_back(wordSquare); + } + else + { + Trie *cur = &m_root; + // 利用 Trie 來找到岩的 prefix + for (const string& s : wordSquare) + { + const char cc = s[step] - 'a'; + if (cur->m_next[cc] == nullptr) return; + cur = cur->m_next[cc]; + } + // 擴展 + for (const auto& nei : cur->m_wordIndex) + { + // 由 index 找回文字 + wordSquare.push_back(words[nei]); + DFS(words, step+1, wordSquare, result); + wordSquare.pop_back(); + } + } + } +}; +\end{Code} + +\section{Android Unlock Patterns} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:android-unlock-patterns} + + +\subsubsection{描述} +Given an Android 3x3 key lock screen and two integers m and n, where 1 ≤ m ≤ n ≤ 9, count the total number of unlock patterns of the Android lock screen, which consist of minimum of m keys and maximum n keys. + +Rules for a valid pattern: +\begindot +\item Each pattern must connect at least m keys and at most n keys. +\item All the keys must be distinct. +\item If the line connecting two consecutive keys in the pattern passes through any other keys, the other keys must have previously selected in the pattern. No jumps through non selected key is allowed. +\item The order of keys used matters. +\myenddot + +\begin{center} +\includegraphics[width=300pt]{android-unlock-patterns.png}\\ +\figcaption{Android Unlock Patterns}\label{fig:android-unlock-patterns} +\end{center} + +\subsubsection{遞歸 - DFS - vector 版} +\begin{Code} +// 時間複雜度O(N!),空間複雜度O(N) +class Solution { +public: + int numberOfPatterns(int m, int n) { + int result = 0; + + // 使用了vector,此方法最慢,對比使用 integer + vector used(9, false); + for (int len = m; len <= n; len++) + result += DFS(-1, len, used); + + return result; + } +private: + // index: 為是次選中的下標 last: 為上次選中的下標 + // IsValid: 是在檢驗,由 last index 走到 index 是否合法 + bool IsValid(int index, int last, const vector& used) + { + // 若果此下標已被使用 + if (used[index]) return false; + // 若果此下標是第一次被選用 + if (last == -1) return true; + // 若果是鄰近的移動,或日字移動法 + if ((index + last) % 2 == 1) return true; + // 若果是大斜角走,要先檢查居中下標 + int mid = (index + last) / 2; + if (mid == 4) return used[mid]; + // 處理鄰近斜角移動 --- (not same col) and (not same row) + if ((index % 3 != last % 3) && (index / 3 != last / 3)) + return true; + // 剩下的移動選擇都不是相鄰的 + return used[mid]; + } + int DFS(int last, int len, vector& used) + { + if (len == 0) + return 1; + int sum = 0; + for (int i = 0; i < 9; i++) + { + if (IsValid(i, last, used)) + { + used[i] = true; + sum += DFS(i, len - 1, used); + used[i] = false; + } + } + return sum; + } +}; +\end{Code} + +\subsubsection{遞歸 - DFS - int 版} +\begin{Code} +// 時間複雜度O(N!),空間複雜度O(N) +// 快很多的版本,因為用了 int 來代替 vector,用來記錄使用情況 +class Solution { +public: + int numberOfPatterns(int m, int n) { + // 使用 int 來記錄已使用的情況 + return count(m, n, 0, 1, 1); + } +private: + int count(int m, int n, int used, int i2, int j2) { + int number = m <= 0; + if (!n) return 1; + for (int i=0; i<3; i++) { + for (int j=0; j<3; j++) { + // 求得下一個下標,和標示使用 + int I = i2 + i, J = j2 + j, used2 = used | (1 << (i*3 + j)); + // 當 used2 == used,代表重覆使用下標 + // 所以只有當 used2 變大了才會繼續 + // I % 2 和 J % 2 的檢查包括了所有小斜步和日字步 + // 那後的 (I*3 + J)/2 會檢查大斜步的居中下標和餘下的所有步法 + if (used2 > used && (I % 2 || J % 2 || used2 & (1 << (I/2*3 + J/2)))) + number += count(m-1, n-1, used2, i, j); + } + } + return number; + } +}; +\end{Code} + +\subsubsection{遞歸 - DFS - int 版} +\begin{Code} +// 時間複雜度O(N!),空間複雜度O(N) +// 快很多的版本,因為用了 int 來代替 vector,用來記錄使用情況 +class Solution { +public: + int numberOfPatterns(int m, int n) { + int result = 0; + + int used = 0; + for (int len = m; len <= n; len++) + result += DFS(-1, len, used); + + return result; + } +private: + // index: 為是次選中的下標 last: 為上次選中的下標 + // IsValid: 是在檢驗,由 last index 走到 index 是否合法 + bool IsValid(int index, int last, const int& used) + { + // 若果此下標已被使用 + if (used & 1 << index) return false; + // 若果此下標是第一次被選用 + if (last == -1) return true; + // 若果是鄰近的移動,或日字移動法 + if ((index + last) % 2 == 1) return true; + // 若果是大斜角走,要先檢查居中下標 + int mid = (index + last) / 2; + if (mid == 4) return used & 1 << mid; + // 處理鄰近斜角移動 --- (not same col) and (not same row) + if ((index % 3 != last % 3) && (index / 3 != last / 3)) + return true; + // 剩下的移動選擇都不是相鄰的 + return used & 1 << mid; + } + int DFS(int last, int len, int& used) + { + if (len == 0) + return 1; + int sum = 0; + for (int i = 0; i < 9; i++) + { + if (IsValid(i, last, used)) + { + used |= 1 << i; + sum += DFS(i, len - 1, used); + used &= ~(1 << i); + } + } + return sum; + } +}; +\end{Code} + +\section{Fenwick Tree or Binary Indexed Tree} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:fenwick-tree-or-binary-indexed-tree} + + +\subsubsection{描述} +We may use this tree to calculate the sum of the prefix of a given array. Mean while we can update the value of the array and the tree in logN time. + +\subsubsection{Code} +\begin{Code} +// 時間複雜度O(NLogN),空間複雜度O(N) +// Learn from https://www.youtube.com/watch?v=CWDQJGaN1gY +class FenwickTree +{ +public: + FenwickTree() {} + ~FenwickTree() {} + + vector CreateTree(const vector& input) + { + vector binaryIndexedTree(input.size()+1); + + for (size_t i = 1; i <= input.size(); i++) + { + UpdateBinaryIndexedTree(binaryIndexedTree, input[i-1], i); + } + return binaryIndexedTree; + } + int GetSum(const vector& binaryIndexedTree, int index) + { + index++; + int sum = 0; + while (index > 0) + { + sum += binaryIndexedTree[index]; + index = GetParent(index); + } + return sum; + } +private: + void UpdateBinaryIndexedTree(vector& binaryIndexedTree, int val, int index) + { + while (index < binaryIndexedTree.size()) + { + binaryIndexedTree[index] += val; + index = GetNext(index); + } + } + /** + * To get parent + * 1) 2's complement to get minus of index + * 2) AND this with index + * 3) Subtract that from index + */ + int GetParent(int index) + { + return index - (index & -index); + } + /** + * To get next + * 1) 2's complement of get minus of index + * 2) AND this with index + * 3) Add it to index + */ + int GetNext(int index) + { + return index + (index & -index); + } +}; +int main(int argc, char *argv[]) +{ + vector input{1,2,3,4,5,6,7}; + FenwickTree ft; + vector binaryIndexedTree = ft.CreateTree(input); + assert(1 == ft.GetSum(binaryIndexedTree, 0)); + assert(3 == ft.GetSum(binaryIndexedTree, 1)); + assert(6 == ft.GetSum(binaryIndexedTree, 2)); + assert(10 == ft.GetSum(binaryIndexedTree, 3)); + assert(15 == ft.GetSum(binaryIndexedTree, 4)); + assert(21 == ft.GetSum(binaryIndexedTree, 5)); + assert(28 == ft.GetSum(binaryIndexedTree, 6)); + + return 0; +} +\end{Code} + +\section{Count of Smaller Numbers After Self} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:count-of-smaller-numbers-after-self} + +\subsubsection{描述} +You are given an integer array nums and you have to return a new counts array. The counts array has the property where counts[i] is the number of smaller elements to the right of nums[i]. + +Example: +\begin{Code} +Input: [5,2,6,1] +Output: [2,1,1,0] +Explanation: +To the right of 5 there are 2 smaller elements (2 and 1). +To the right of 2 there is only 1 smaller element (1). +To the right of 6 there is 1 smaller element (1). +To the right of 1 there is 0 smaller element. +\end{Code} + +Note: +Please read more about FenwickTree from example FenwickTree. + +\subsubsection{Code} +\begin{Code} +// 時間複雜度O(NLogN),空間複雜度O(N) +// The idea of this solution is similar to the monotonic stack +class Solution { +public: + vector countSmaller(vector& nums) + { + // Relative indexing. + // This is the key to achieve true O(n(log(n))). + set unique_elements; + for (const int& i : nums) unique_elements.insert(i); + + // 由細至大配上 id + unordered_map index; + int id = 0; + for (const auto& e : unique_elements) index[e] = id++; + + // Fenwick Tree + vector ft(id+1); + + vector result; + // 由尾至頭歷遍 + for (int i = nums.size()-1; i >= 0; --i) + { + // 記低出現過的下標 + ftIncrement(ft, index[nums[i]]); + // 數一數較細的下標出現過的次數 + result.push_back(ftPrefixSum(ft, index[nums[i]])); + } + reverse(result.begin(),result.end()); + return result; + } +private: + // Insert into Fenwick tree + // O(log(n)) + void ftIncrement(vector &ft, int index) + { + // 0 index -> 1 index + ++index; + while (index < ft.size()) + { + ft[index] += 1; + index += (index&-index); + } + } + // Get sum from [0,index) in a Fenwick tree. + // O(log(n)) + int ftPrefixSum(vector &ft, int index) + { + // 0 index -> 1 index but excluding index + int sum = 0; + while (index > 0) + { + sum += ft[index]; + index -= (index&-index); + } + return sum; + } +}; +\end{Code} + +\section{Coin Change} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:coin-change} + +\subsubsection{描述} +You are given coins of different denominations and a total amount of money amount. Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1. + +Example 1: +\begin{Code} +Input: coins = [1, 2, 5], amount = 11 +Output: 3 +Explanation: 11 = 5 + 5 + 1 +\end{Code} + +Example 2: +\begin{Code} +Input: coins = [2], amount = 3 +Output: -1 +\end{Code} + +Note: +You may assume that you have an infinite number of each kind of coin. + +\subsubsection{DP} +設狀態為$f(i)$,表示\fn{i}最少的 coins 數目 +$$ +f(i) = min(f(i-coins[j]) + 1) \&\& coins[j] < i +$$ + +\begin{Code} +// 時間複雜度O(n*k),空間複雜度O(n) +class Solution { +public: + int coinChange(vector& coins, int amount) { + if (coins.size() == 0) return -1; + sort(coins.begin(), coins.end()); + + vector f(amount+1, INT_MAX); + f[0] = 0; + + for (int i = coins[0]; i < f.size(); i++) + { + for (int j = 0; j < coins.size(); j++) + { + if (coins[j] > i) break; + if (f[i-coins[j]] == INT_MAX) continue; + f[i] = min(f[i], f[i-coins[j]] + 1); + } + } + + return f[amount] == INT_MAX ? -1 : f[amount]; + } +}; +\end{Code} + +\subsubsection{DFS} +\begin{Code} +// 時間複雜度O(n*k),空間複雜度O(n) +class Solution { +public: + int coinChange(vector& coins, int amount) { + if (coins.size() == 0) return -1; + sort(coins.begin(), coins.end()); + + vector counts(amount+1, 0); + counts[0] = 0; + DFS(coins, amount, counts); + + return counts[amount] == INT_MAX ? -1 : counts[amount]; + } +private: + int DFS(vector& coins, int amount, vector& counts) + { + if (amount < 0) return -1; + if (amount == 0) return 0; + if (counts[amount] != 0) return counts[amount]; + else + { + int minCount = INT_MAX; + for (int i = coins.size() - 1; i >= 0; i--) + { + if (coins[i] > amount) continue; + int ret = DFS(coins, amount-coins[i], counts); + if (ret >= 0 && ret < minCount) + minCount = 1 + ret; + } + return counts[amount] = minCount == INT_MAX ? -1 : minCount; + } + } +}; +\end{Code} + +\section{Min Stack} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:min-stack} + +\subsubsection{描述} +Design a stack that supports push, pop, top, and retrieving the minimum element in constant time. + +\begindot +\item push(x) -- Push element x onto stack. +\item pop() -- Removes the element on top of the stack. +\item top() -- Get the top element. +\item getMin() -- Retrieve the minimum element in the stack. +\myenddot + +Example 1: +\begin{Code} +Input +["MinStack","push","push","push","getMin","pop","top","getMin"] +[[],[-2],[0],[-3],[],[],[],[]] + +Output +[null,null,null,null,-3,null,0,-2] + +Explanation +MinStack minStack = new MinStack(); +minStack.push(-2); +minStack.push(0); +minStack.push(-3); +minStack.getMin(); // return -3 +minStack.pop(); +minStack.top(); // return 0 +minStack.getMin(); // return -2 +\end{Code} + + +Note: +You may assume that you have an infinite number of each kind of coin. + +\subsubsection{Stack and Min Stack} +\begin{Code} +// 時間複雜度O(1),空間複雜度O(n) +class MinStack { +public: + MinStack(){ + + } + void push(int x) + { + if(s1.empty()) + { + s1.push(x); + minStack.push(x); + } + else + { + if(x < minStack.top()) + { + s1.push(x); + minStack.push(x); + } + else + { + s1.push(x); + minStack.push(minStack.top()); + } + } + } + void pop() + { + s1.pop(); + minStack.pop(); + } + int top() + { + return s1.top(); + } + int getMin() + { + return minStack.top(); + } +private: + stack s1; // 一個正常 stack + stack minStack; // 一個 min stack +}; +\end{Code} + +\subsubsection{Stack and Pair} +\begin{Code} +// 時間複雜度O(1),空間複雜度O(n) +class MinStack { +public: + /** initialize your data structure here. */ + MinStack() { + + } + + void push(int x) { + if (m_cache.empty()) + m_cache.emplace(x, x); + else + m_cache.emplace(x, min(m_cache.top().second, x)); + } + + void pop() { + m_cache.pop(); + } + + int top() { + return m_cache.top().first; + } + + int getMin() { + return m_cache.top().second; + } +private: + stack> m_cache; +}; +\end{Code} + +\section{Guess the Word} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:guess-the-word} + +\subsubsection{描述} +This problem is an interactive problem new to the LeetCode platform. + +We are given a word list of unique words, each word is 6 letters long, and one word in this list is chosen as secret. + +You may call master.guess(word) to guess a word. The guessed word should have type string and must be from the original list with 6 lowercase letters. + +This function returns an integer type, representing the number of exact matches (value and position) of your guess to the secret word. Also, if your guess is not in the given wordlist, it will return -1 instead. + +For each test case, you have 10 guesses to guess the word. At the end of any number of calls, if you have made 10 or less calls to master.guess and at least one of these guesses was the secret, you pass the testcase. + +Besides the example test case below, there will be 5 additional test cases, each with 100 words in the word list. The letters of each word in those testcases were chosen independently at random from 'a' to 'z', such that every word in the given word lists is unique. + +Example 1: +\begin{Code} +Input: secret = "acckzz", wordlist = ["acckzz","ccbazz","eiowzz","abcczz"] + +Explanation: + +master.guess("aaaaaa") returns -1, because "aaaaaa" is not in wordlist. +master.guess("acckzz") returns 6, because "acckzz" is secret and has all 6 matches. +master.guess("ccbazz") returns 3, because "ccbazz" has 3 matches. +master.guess("eiowzz") returns 2, because "eiowzz" has 2 matches. +master.guess("abcczz") returns 4, because "abcczz" has 4 matches. + +We made 5 calls to master.guess and one of them was the secret, so we pass the test case. +\end{Code} + + +Note: +Any solutions that attempt to circumvent the judge will result in disqualification. + +\subsubsection{Minimax Strategy} +\begin{Code} +// 時間複雜度O(N^2),空間複雜度O(N^2) +// 使用了 minimax algorithm 來最大化地減少下一個需要嘗試的搜尋範圍 +class Solution { +public: + void findSecretWord(vector& wordlist, Master& master) { + int N = wordlist.size(); + m_H.resize(N, vector(N)); + for (int i = 0; i < N; i++) + { + for (int j = i; j < N; j++) + { + int match = 0; + for (int k = 0; k < 6; k++) + { + if (wordlist[i][k] == wordlist[j][k]) + match++; + } + m_H[i][j] = m_H[j][i] = match; + } + } + + vector possible; + vector path(N, false); + for (int i = 0; i < N; i++) possible.push_back(i); + + while (!possible.empty()) + { + int guess = solve(possible, path); + int matches = master.guess(wordlist[guess]); + if (matches == wordlist[0].length()) return; // 找到答案 + + // 找不到答案,便需要找出下一個搜尋範圍 + vector possible2; + for (const int& j : possible) + if (m_H[guess][j] == matches) possible2.push_back(j); + possible = possible2; + path[guess] = true; + } + } +private: + vector> m_H; + + int solve(const vector& possible, const vector& path) + { + if (possible.size() <= 2) return possible.front(); + int ansgrpSize = possible.size(); + int ansguess = -1; + + for (int guess = 0; guess < m_H.size(); guess++) + { + if (!path[guess]) + { + vector> groups(7, vector()); + for (const int& j : possible) + { + if (j != guess) + groups[m_H[guess][j]].push_back(j); + } + + int maxGroupSize = 0; + for (int i = 0; i < 7; i++) + { + if (groups[i].size() > maxGroupSize) + maxGroupSize = groups[i].size(); + } + + if (maxGroupSize < ansgrpSize) + { + ansgrpSize = maxGroupSize; + ansguess = guess; + } + } + } + + return ansguess; + } +}; +\end{Code} + +\subsubsection{Random Pick} +\begin{Code} +// 時間複雜度O(N^2),空間複雜度O(N^2) +class Solution { +public: + void findSecretWord(vector& wordlist, Master& master) { + + string target = wordlist[rand() % wordlist.size()]; + int count = 10; + while(count--) { + int numMatch = master.guess(target); + + shrinkList(wordlist, target, numMatch); + + target = wordlist[rand() % wordlist.size()]; + } + } + +private: + void shrinkList(vector & wordlist, const string & target, int numMatch) { + vector newList; + for (auto & w : wordlist) { + if (computeComm(w, target) == numMatch) { + newList.push_back(w); + } + } + wordlist = newList; + } + + int computeComm(const string & s1, const string & s2) { + int comm = 0; + for (int i = 0; i < 6; ++i) { + if (s1[i] == s2[i]) comm++; + } + return comm; + } + +}; +\end{Code} + +\section{Bulls and Cows} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:bulls-and-cows} + +\subsubsection{描述} +You are playing the following Bulls and Cows game with your friend: You write down a number and ask your friend to guess what the number is. Each time your friend makes a guess, you provide a hint that indicates how many digits in said guess match your secret number exactly in both digit and position (called "bulls") and how many digits match the secret number but locate in the wrong position (called "cows"). Your friend will use successive guesses and hints to eventually derive the secret number. + +Write a function to return a hint according to the secret number and friend's guess, use A to indicate the bulls and B to indicate the cows. + +Please note that both secret number and friend's guess may contain duplicate digits. + +Example 1: +\begin{Code} +Input: secret = "1807", guess = "7810" + +Output: "1A3B" + +Explanation: 1 bull and 3 cows. The bull is 8, the cows are 0, 1 and 7. +\end{Code} + +Example 2: +\begin{Code} +Input: secret = "1123", guess = "0111" + +Output: "1A1B" + +Explanation: The 1st 1 in friend's guess is a bull, the 2nd or 3rd 1 is a cow. +\end{Code} + + +Note: +You may assume that the secret number and your friend's guess only contain digits, and their lengths are always equal. + +\subsubsection{One pass One Hash map} +\begin{Code} +// 時間複雜度O(N),空間複雜度O(1) +class Solution { +public: + string getHint(string secret, string guess) { + // 利用正負數去代表 secret 和 guess 的出現來減少一個 hash map + vector counter(10, 0); + int bulls = 0; + int cows = 0; + + for (size_t i = 0; i < secret.size(); i++) + { + const char& s = secret[i]; + const char& g = guess[i]; + + if (s == g) + bulls++; + else + { + cows += (counter[s-'0'] < 0 ? 1 : 0) + (counter[g-'0'] > 0 ? 1 : 0); + counter[s-'0']++; // 用正數代表 secret 出現 + counter[g-'0']--; // 用負數代表 guess 出現 + } + } + + return to_string(bulls) + "A" + to_string(cows) + "B"; + } +}; +\end{Code} diff --git a/C++/chapGraph.tex b/C++/chapGraph.tex index c3f30918..b40be0e0 100644 --- a/C++/chapGraph.tex +++ b/C++/chapGraph.tex @@ -1,8 +1,8 @@ -\chapter{图} +\chapter{圖} -无向图的节点定义如下: +無向圖的節點定義如下: \begin{Code} -// 无向图的节点 +// 無向圖的節點 struct UndirectedGraphNode { int label; vector neighbors; @@ -44,13 +44,13 @@ \subsubsection{描述} \subsubsection{分析} -广度优先遍历或深度优先遍历都可以。 +廣度優先遍歷或深度優先遍歷都可以。 \subsubsection{DFS} \begin{Code} // LeetCode, Clone Graph -// DFS,时间复杂度O(n),空间复杂度O(n) +// DFS,時間複雜度O(n),空間複雜度O(n) class Solution { public: UndirectedGraphNode *cloneGraph(const UndirectedGraphNode *node) { @@ -82,7 +82,7 @@ \subsubsection{DFS} \subsubsection{BFS} \begin{Code} // LeetCode, Clone Graph -// BFS,时间复杂度O(n),空间复杂度O(n) +// BFS,時間複雜度O(n),空間複雜度O(n) class Solution { public: UndirectedGraphNode *cloneGraph(const UndirectedGraphNode *node) { @@ -117,7 +117,571 @@ \subsubsection{BFS} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot + +\section{Network Delay Time} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:network-delay-time} + + +\subsubsection{描述} +There are N network nodes, labelled 1 to N. + +Given times, a list of travel times as directed edges times[i] = (u, v, w), where u is the source node, v is the target node, and w is the time it takes for a signal to travel from source to target. + +Now, we send a signal from a certain node K. How long will it take for all nodes to receive the signal? If it is impossible, return -1. + +\begin{center} +\includegraphics[width=100pt]{network-delay-time.png}\\ +\figcaption{Rotate Image}\label{fig:network-delay-time} +\end{center} + +\begin{Code} +Input: times = [[2,1,1],[2,3,1],[3,4,1]], N = 4, K = 2 +Output: 2 +\end{Code} + +\begin{enumerate} +\item N will be in the range [l, 100] +\item K will be in the range [l, N] +\item The length of times will be in the range [l, 6000]. +\item All edges times[i] = (u, v, w) will have l <= u, v <= N and 0 <= w <= 100. +\end{enumerate} +\subsubsection{分析} +廣度優先遍歷 +Use Djikstra Algorithm. Find a minium path from one source to one end. + + +\subsubsection{Djikstra} +\begin{Code} +// LeetCode, Clone Graph +// DFS with graph,時間複雜度O(ELogV),空間複雜度O(V^2) +class Solution { +public: + struct Node { + int m_node; + int m_dist; + + Node (int node, int dist) + : m_node(node), m_dist(dist) {} + }; + int networkDelayTime(vector>& times, int N, int K) { + // create graph + unordered_map> graph; // key: nodeNumber value: nodes + for (const auto& t : times) // startNode: t[0], endNode: t[1], dist: t[2] + graph[t[0]].push_back(Node(t[1], t[2])); + + unordered_map dist; // key: nodeNumber value: dist + multimap dq; // djikstra queue, key: dist value: nodeNumber + + // do until no more node in queue + dq.insert(make_pair(0, K)); + while (!dq.empty()) { + pair cur = *dq.begin(); + dq.erase(dq.begin()); + + // filter out if shortest path is found + if (dist.find(cur.second) != dist.end()) continue; + // record the shortest parth + dist.insert(make_pair(cur.second, cur.first)); + // push the neighbour to dq + auto it = graph.find(cur.second); + if (it == graph.end()) continue; + for (const auto& nei : it->second) { + if (dist.find(nei.m_node) != dist.end()) continue; + dq.insert(make_pair(nei.m_dist + cur.first, nei.m_node)); + } + } + + // pick the max dist, return -1 if not all node is visited + if ((int)dist.size() == N) + return max_element(dist.begin(), dist.end() + , [&](const pair& first, const pair& second) + { + return first.second < second.second; + })->second; + else + return -1; + } +}; + +\end{Code} +\subsubsection{Djikstra} +\begin{Code} +// LeetCode, Clone Graph +// DFS with adjacency list,時間複雜度O(ELogV),空間複雜度O(V^2) +class Solution { +public: + int networkDelayTime(vector>& times, int N, int K) + { + // Build adjacency list + // index 0 is dummy for easy implementation + vector>> adjList(N + 1); + for (const auto& v : times) + adjList[v[0]].emplace_back(v[1], v[2]); + + vector dist(N + 1, INT_MAX); + // initialize pq and source vertex distance + dist[K] = 0; + set> pq {{dist[K], K}}; + while (!pq.empty()) { + const auto u = pq.begin()->second; + pq.erase(pq.begin()); // pop the top, i.e., first element in set + for (const auto& [v, w]: adjList[u]) { + if (dist[u] + w < dist[v]) { // if edge can be relaxed + pq.erase({dist[v], v}); // remove old info + dist[v] = dist[u] + w; // update distance + pq.emplace(dist[v], v); // insert new info + } + } + } + + const int longestTime = *max_element(dist.begin() + 1, dist.end()); + return longestTime == INT_MAX ? -1 : longestTime; + } +}; +\end{Code} + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + +\section{Alien Dictionary} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:alien-dictionary} + +\subsubsection{描述} +There is a new alien language which uses the latin alphabet. However, the order among letters are unknown to you. You receive a list of non-empty words from the dictionary, where words are sorted lexicographically by the rules of this new language. Derive the order of letters in this language. + +Example 1: +\begin{Code} +Input: +[ + "wrt", + "wrf", + "er", + "ett", + "rftt" +] + +Output: "wertf" +\end{Code} + +Example 2: +\begin{Code} +Input: +[ + "z", + "x" +] + +Output: "zx" +\end{Code} + +Example 3: +\begin{Code} +Input: +[ + "z", + "x", + "z" +] + +Output: "" +\end{Code} + +\subsubsection{分析} +Make DAG from sorted strings, do topological sort by BFS or DFS + + +\subsubsection{BFS with List} +\begin{Code} +// LeetCode, Alien Dictionary +// BFS Topological sort,時間複雜度O(N),空間複雜度O(N) +class Solution { +public: + string alienOrder(vector& words) { + unordered_map> adjList; // 用來記低依賴關係 + unordered_map depenCount; // 用來儲存每個字母的依賴數目 + + for (const auto& w : words) + for (const auto& c : w) + { + depenCount[c] = 0; + adjList.insert(make_pair(c, list())); + } + // 製造 adjList DAG + for (size_t i = 0; i < words.size() - 1; i++) + { + const string& w1 = words[i]; + const string& w2 = words[i+1]; + // 剪枝,若 prefix 一樣 + if (w1.size() > w2.size() && w1.find(w2) == 0) return ""; + // 找尋第一個不同的字母 + for (size_t j = 0; j < w1.size() && j < w2.size(); j++) + { + const char& c1 = w1[j]; + const char& c2 = w2[j]; + if (c1 != c2) + { + adjList[c1].push_back(c2); + depenCount[c2]++; + break; + } + } + } + // 利用 Topological Sort 來解決相關的依賴關係 + queue cur; + // 先處理沒有依賴的字母 + for (const auto& [k, v] : depenCount) + if (v == 0) + cur.push(k); + + string result; + // BFS + while (!cur.empty()) + { + char c = cur.front(); + cur.pop(); + result.push_back(c); + auto it = adjList.find(c); + if (it == adjList.end()) continue; + for (const auto& nei : it->second) + { + if (--(depenCount[nei]) == 0) + cur.push(nei); + } + } + + if (result.size() < depenCount.size()) + return ""; + else + return result; + } +}; +\end{Code} + +\subsubsection{BFS with Set} +\begin{Code} +// LeetCode, Alien Dictionary +// BFS Topological sort,時間複雜度O(N),空間複雜度O(N) +class Solution { +public: + string alienOrder(vector& words) { + unordered_map> adjList; // 用來記低依賴關係 + unordered_map depenCount; // 用來儲存每個字母的依賴數目 + + for (const auto& w : words) + for (const auto& c : w) + { + depenCount[c] = 0; + adjList.insert(make_pair(c, set())); + } + // 製造 adjList DAG + for (size_t i = 0; i < words.size() - 1; i++) + { + const string& w1 = words[i]; + const string& w2 = words[i+1]; + // 剪枝,若 prefix 一樣 + if (w1.size() > w2.size() && w1.find(w2) == 0) return ""; + // 找尋第一個不同的字母 + for (size_t j = 0; j < w1.size() && j < w2.size(); j++) + { + const char& c1 = w1[j]; + const char& c2 = w2[j]; + if (c1 != c2) + { + if (adjList[c1].find(c2) == adjList[c1].end()) // 這裏不同 + { + adjList[c1].insert(c2); + depenCount[c2]++; + } + break; + } + } + } + // 之後的動作和上一個例子一樣,略去 。。。 // + } +}; + +\end{Code} + +\subsubsection{DFS with List} +\begin{Code} +// LeetCode, Alien Dictionary +// DFS Topological sort,時間複雜度O(N),空間複雜度O(N) +class Solution { +public: + string alienOrder(vector& words) { + unordered_map> adjList; // 用來記低依賴關係 + + // 製造 DAG,令到所有字母也有被歷篇的機會 + for (const auto& w : words) + for (const auto& c : w) + adjList.insert(make_pair(c, list())); + // 製造剩下的 DAG + for (size_t i = 0; i < words.size() - 1; i++) + { + const string& w1 = words[i]; + const string& w2 = words[i+1]; + // 找到 prefix case + if (w1.size() > w2.size() && w1.find(w2) == 0) return ""; + + for (size_t j = 0; j < w1.size() && j < w2.size(); j++) + { + const char& c1 = w1[j]; + const char& c2 = w2[j]; + if (c1 != c2) + { + adjList[c1].push_back(c2); + break; + } + } + } + + string result; + unordered_map visited; + for (const auto& [k, v] : adjList) + { + if (!DFS(adjList, visited, k, result)) + return ""; + } + + if (result.size() < adjList.size()) + return ""; + else + return result; + } +private: + bool DFS(unordered_map>& adjList, unordered_map& visited, char k, string& result) + { + if (visited.find(k) != visited.end()) + return visited[k]; // If this node was grey (false), a cycle was detected. + + visited[k] = false; + auto it = adjList.find(k); + for (const auto& nei : adjList[k]) + { + if (!DFS(adjList, visited, *nei, result)) return false; + } + visited[k] = true; + result.insert(result.begin(), k); + + return true; + } +}; + +\end{Code} + +\subsubsection{相關題目} +\begindot +\item Topological sort,見 \S \ref{sec:topological-sort} +\myenddot + +\section{Redundant Connection} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:redundant-connection} + +\subsubsection{描述} +In this problem, a tree is an undirected graph that is connected and has no cycles. + +The given input is a graph that started as a tree with N nodes (with distinct values 1, 2, ..., N), with one additional edge added. The added edge has two different vertices chosen from 1 to N, and was not an edge that already existed. + +The resulting graph is given as a 2D-array of edges. Each element of edges is a pair [u, v] with u < v, that represents an undirected edge connecting nodes u and v. + +Return an edge that can be removed so that the resulting graph is a tree of N nodes. If there are multiple answers, return the answer that occurs last in the given 2D-array. The answer edge [u, v] should be in the same format, with u < v. + + +Example 1: +\begin{Code} +Input: [[1,2], [1,3], [2,3]] +Output: [2,3] +Explanation: The given undirected graph will be like this: + 1 + / \ +2 - 3 +\end{Code} + +Example 2: +\begin{Code} +Input: [[1,2], [2,3], [3,4], [1,4], [1,5]] +Output: [1,4] +Explanation: The given undirected graph will be like this: +5 - 1 - 2 + | | + 4 - 3 +\end{Code} + + +Note: +\begindot +\item The size of the input 2D-array will be between 3 and 1000. +\item Every integer represented in the 2D-array will be between 1 and N, where N is the size of the input array. +\myenddot + + +\subsubsection{DFS} +\begin{Code} +// 時間複雜度O(N^2),空間複雜度O(N) +// 重點:當發現有多過一個路徑可由 u 走到 v,便發現答案 +class Solution { +public: + vector findRedundantConnection(vector>& edges) { + // 記低見過的 edges, 由 u -> v + unordered_map> cache; + + for (const auto& edge : edges) + { + const int& u = edge[0]; + const int& v = edge[1]; + // 利用 DFS 來驗證有沒有另一個方法可以由 u 跑到 v + unordered_set visited; + if (cache.count(u) && cache.count(v) && DFS(cache, visited, u, v)) + return vector{u, v}; + + cache[u].insert(v); + cache[v].insert(u); + } + + return vector(); + } +private: + template + bool DFS(eMap& cache, Vis& visited, const int& start, const int& target) + { + if (start == target) return true; + + if (cache.find(start) == cache.end()) return false; + + visited.insert(start); + for (const int& nei : cache[start]) + if (!visited.count(nei) && DFS(cache, visited, nei, target)) + return true; + + return false; + } +}; +\end{Code} + +\subsubsection{DSU - Without Rank} +\begin{Code} +// 時間複雜度O(N),空間複雜度O(N) +// 一開始每個點都以自己為一個集合(set) +// 歷遍所有 edge,把點放於同一個集合。 +// 若果兩點早已是同一集合,那個 edge 便是多餘。 +// https://www.youtube.com/watch?time_continue=1124&v=wU6udHRIkcc&feature=emb_title +class DSU // Disjoint Set Union (DSU) +{ +public: + DSU(int N) + { + m_parent.reserve(N); + for (int i = 0; i < N; i++) m_parent.push_back(i); + } + ~DSU() {} + + int FindParent(const int& x) + { + if (m_parent[x] == x) // 當 x 為集合的父親時 + return x; + return m_parent[x] = FindParent(m_parent[x]); // x 的集合等如 x 的父親的集合 + } + bool Union(const int& x, const int& y) + { + const int xpar = FindParent(x); + const int ypar = FindParent(y); + if (xpar == ypar) return false; + + m_parent[xpar] = ypar; // perform union + + return true; + } +private: + vector m_parent; // 利用數列來記低點的所在集合 +}; +class Solution +{ +public: + vector findRedundantConnection(vector>& edges) { + DSU dsu(1001); + + for (const auto& edge : edges) + { + const int& u = edge[0]; + const int& v = edge[1]; + + if (!dsu.Union(u, v)) + return vector{u, v}; + } + + return vector(); + } +}; +\end{Code} + +\subsubsection{DSU - Rank} +\begin{Code} +// 時間複雜度O(N),空間複雜度O(N) +// 一開始每個點都以自己為一個集合(set) +// 歷遍所有 edge,把點放於同一個集合。 +// 若果兩點早已是同一集合,那個 edge 便是多餘。 +class DSU // Disjoint Set Union (DSU) +{ +public: + DSU(int N) + { + // 負數意指該合集的父親 + // 所以一開始,每個點都是某個合集的父親 + // 負數的值代表 rank, 也是該合集的點的總數 + // 正數的值代表父的 index + m_parent.resize(N, -1); + } + ~DSU() {} + + int FindParent(const int& x) + { + if (m_parent[x] < 0) + return x; + return m_parent[x] = FindParent(m_parent[x]); + } + bool Union(const int& x, const int& y) + { + const int xpar = FindParent(x); + const int ypar = FindParent(y); + if (xpar == ypar) return false; + + if (abs(m_parent[xpar]) < abs(m_parent[ypar])) + { + m_parent[ypar] += m_parent[xpar]; + m_parent[xpar] = ypar; + } + else + { + m_parent[xpar] += m_parent[ypar]; + m_parent[ypar] = xpar; + } + + return true; + } +private: + vector m_parent; +}; +class Solution { +public: + vector findRedundantConnection(vector>& edges) { + DSU dsu(1001); + + for (const auto& edge : edges) + { + const int& u = edge[0]; + const int& v = edge[1]; + + if (!dsu.Union(u, v)) + return vector{u, v}; + } + + return vector(); + } +}; +\end{Code} diff --git a/C++/chapGreedy.tex b/C++/chapGreedy.tex index 2e098bea..573322ed 100644 --- a/C++/chapGreedy.tex +++ b/C++/chapGreedy.tex @@ -1,4 +1,4 @@ -\chapter{贪心法} +\chapter{貪心法} \section{Jump Game} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -20,26 +20,26 @@ \subsubsection{描述} \subsubsection{分析} -由于每层最多可以跳\fn{A[i]}步,也可以跳0或1步,因此如果能到达最高层,则说明每一层都可以到达。有了这个条件,说明可以用贪心法。 +由於每層最多可以跳\fn{A[i]}步,也可以跳0或1步,因此如果能到達最高層,則説明每一層都可以到達。有了這個條件,説明可以用貪心法。 -思路一:正向,从0出发,一层一层网上跳,看最后能不能超过最高层,能超过,说明能到达,否则不能到达。 +思路一:正向,從0出發,一層一層網上跳,看最後能不能超過最高層,能超過,説明能到達,否則不能到達。 -思路二:逆向,从最高层下楼梯,一层一层下降,看最后能不能下降到第0层。 +思路二:逆向,從最高層下樓梯,一層一層下降,看最後能不能下降到第0層。 -思路三:如果不敢用贪心,可以用动规,设状态为\fn{f[i]},表示从第0层出发,走到\fn{A[i]}时剩余的最大步数,则状态转移方程为: +思路三:如果不敢用貪心,可以用動規,設狀態為\fn{f[i]},表示從第0層出發,走到\fn{A[i]}時剩餘的最大步數,則狀態轉移方程為: $$ f[i] = max(f[i-1], A[i-1])-1, i > 0 $$ -\subsubsection{代码1} +\subsubsection{代碼1} \begin{Code} // LeetCode, Jump Game -// 思路1,时间复杂度O(n),空间复杂度O(1) +// 思路1,時間複雜度O(n),空間複雜度O(1) class Solution { public: bool canJump(const vector& nums) { - int reach = 1; // 最右能跳到哪里 + int reach = 1; // 最右能跳到哪裏 for (int i = 0; i < reach && reach < nums.size(); ++i) reach = max(reach, i + 1 + nums[i]); return reach >= nums.size(); @@ -48,15 +48,15 @@ \subsubsection{代码1} \end{Code} -\subsubsection{代码2} +\subsubsection{代碼2} \begin{Code} // LeetCode, Jump Game -// 思路2,时间复杂度O(n),空间复杂度O(1) +// 思路2,時間複雜度O(n),空間複雜度O(1) class Solution { public: bool canJump (const vector& nums) { if (nums.empty()) return true; - // 逆向下楼梯,最左能下降到第几层 + // 逆向下樓梯,最左能下降到第幾層 int left_most = nums.size() - 1; for (int i = nums.size() - 2; i >= 0; --i) @@ -69,10 +69,10 @@ \subsubsection{代码2} \end{Code} -\subsubsection{代码3} +\subsubsection{代碼3} \begin{Code} // LeetCode, Jump Game -// 思路三,动规,时间复杂度O(n),空间复杂度O(n) +// 思路三,動規,時間複雜度O(n),空間複雜度O(n) class Solution { public: bool canJump(const vector& nums) { @@ -88,9 +88,9 @@ \subsubsection{代码3} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Jump Game II ,见 \S \ref{sec:jump-game-ii} +\item Jump Game II ,見 \S \ref{sec:jump-game-ii} \myenddot @@ -112,22 +112,22 @@ \subsubsection{描述} \subsubsection{分析} -贪心法。 +貪心法。 -\subsubsection{代码1} +\subsubsection{代碼1} \begin{Code} // LeetCode, Jump Game II -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: int jump(const vector& nums) { - int step = 0; // 最小步数 + int step = 0; // 最小步數 int left = 0; - int right = 0; // [left, right]是当前能覆盖的区间 + int right = 0; // [left, right]是當前能覆蓋的區間 if (nums.size() == 1) return 0; - while (left <= right) { // 尝试从每一层跳最远 + while (left <= right) { // 嘗試從每一層跳最遠 ++step; const int old_right = right; for (int i = left; i <= old_right; ++i) { @@ -144,35 +144,35 @@ \subsubsection{代码1} \end{Code} -\subsubsection{代码2} +\subsubsection{代碼2} \begin{Code} // LeetCode, Jump Game II -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: - int jump(const vector& nums) { - int result = 0; + int jump(vector& nums) { // the maximum distance that has been reached - int last = 0; + int last = 1; // the maximum distance that can be reached by using "ret+1" steps - int cur = 0; - for (int i = 0; i < nums.size(); ++i) { - if (i > last) { + int cur = 1; + int step = 0; + for (int i = 0; i < last && last < (int)nums.size(); i++) { + cur = max(cur, i + 1 + nums[i]); + if (i + 1 >= last) { last = cur; - ++result; + step++; } - cur = max(cur, i + nums[i]); } - return result; + return last >= (int)nums.size() ? step : 0; } }; \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Jump Game ,见 \S \ref{sec:jump-game} +\item Jump Game ,見 \S \ref{sec:jump-game} \myenddot @@ -187,20 +187,20 @@ \subsubsection{描述} \subsubsection{分析} -贪心法,分别找到价格最低和最高的一天,低进高出,注意最低的一天要在最高的一天之前。 +貪心法,分別找到價格最低和最高的一天,低進高出,注意最低的一天要在最高的一天之前。 -把原始价格序列变成差分序列,本题也可以做是最大$m$子段和,$m=1$。 +把原始價格序列變成差分序列,本題也可以做是最大$m$子段和,$m=1$。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Best Time to Buy and Sell Stock -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: int maxProfit(vector &prices) { if (prices.size() < 2) return 0; - int profit = 0; // 差价,也就是利润 - int cur_min = prices[0]; // 当前最小 + int profit = 0; // 差價,也就是利潤 + int cur_min = prices[0]; // 當前最小 for (int i = 1; i < prices.size(); i++) { profit = max(profit, prices[i] - cur_min); @@ -212,10 +212,10 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Best Time to Buy and Sell Stock II,见 \S \ref{sec:best-time-to-buy-and-sell-stock-ii} -\item Best Time to Buy and Sell Stock III,见 \S \ref{sec:best-time-to-buy-and-sell-stock-iii} +\item Best Time to Buy and Sell Stock II,見 \S \ref{sec:best-time-to-buy-and-sell-stock-ii} +\item Best Time to Buy and Sell Stock III,見 \S \ref{sec:best-time-to-buy-and-sell-stock-iii} \myenddot @@ -230,14 +230,14 @@ \subsubsection{描述} \subsubsection{分析} -贪心法,低进高出,把所有正的价格差价相加起来。 +貪心法,低進高出,把所有正的價格差價相加起來。 -把原始价格序列变成差分序列,本题也可以做是最大$m$子段和,$m=$数组长度。 +把原始價格序列變成差分序列,本題也可以做是最大$m$子段和,$m=$數組長度。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Best Time to Buy and Sell Stock II -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: int maxProfit(vector &prices) { @@ -252,10 +252,10 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Best Time to Buy and Sell Stock,见 \S \ref{sec:best-time-to-buy-and-sell-stock} -\item Best Time to Buy and Sell Stock III,见 \S \ref{sec:best-time-to-buy-and-sell-stock-iii} +\item Best Time to Buy and Sell Stock,見 \S \ref{sec:best-time-to-buy-and-sell-stock} +\item Best Time to Buy and Sell Stock III,見 \S \ref{sec:best-time-to-buy-and-sell-stock-iii} \myenddot @@ -268,29 +268,29 @@ \subsubsection{描述} \subsubsection{分析} -假设子串里含有重复字符,则父串一定含有重复字符,单个子问题就可以决定父问题,因此可以用贪心法。跟动规不同,动规里,单个子问题只能影响父问题,不足以决定父问题。 +假設子串裏含有重複字符,則父串一定含有重複字符,單個子問題就可以決定父問題,因此可以用貪心法。跟動規不同,動規裏,單個子問題只能影響父問題,不足以決定父問題。 -从左往右扫描,当遇到重复字母时,以上一个重复字母的\fn{index+1},作为新的搜索起始位置,直到最后一个字母,复杂度是$O(n)$。如图~\ref{fig:longest-substring-without-repeating-characters}所示。 +從左往右掃描,當遇到重複字母時,以上一個重複字母的\fn{index+1},作為新的搜索起始位置,直到最後一個字母,複雜度是$O(n)$。如圖~\ref{fig:longest-substring-without-repeating-characters}所示。 \begin{center} \includegraphics[width=300pt]{longest-substring-without-repeating-characters.png}\\ -\figcaption{不含重复字符的最长子串}\label{fig:longest-substring-without-repeating-characters} +\figcaption{不含重複字符的最長子串}\label{fig:longest-substring-without-repeating-characters} \end{center} -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Longest Substring Without Repeating Characters -// 时间复杂度O(n),空间复杂度O(1) -// 考虑非字母的情况 +// 時間複雜度O(n),空間複雜度O(1) +// 考慮非字母的情況 class Solution { public: int lengthOfLongestSubstring(string s) { const int ASCII_MAX = 255; - int last[ASCII_MAX]; // 记录字符上次出现过的位置 - int start = 0; // 记录当前子串的起始位置 + int last[ASCII_MAX]; // 記錄字符上次出現過的位置 + int start = 0; // 記錄當前子串的起始位置 - fill(last, last + ASCII_MAX, -1); // 0也是有效位置,因此初始化为-1 + fill(last, last + ASCII_MAX, -1); // 0也是有效位置,因此初始化為-1 int max_len = 0; for (int i = 0; i < s.size(); i++) { if (last[s[i]] >= start) { @@ -299,15 +299,15 @@ \subsubsection{代码} } last[s[i]] = i; } - return max((int)s.size() - start, max_len); // 别忘了最后一次,例如"abcd" + return max((int)s.size() - start, max_len); // 別忘了最後一次,例如"abcd" } }; \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -322,13 +322,13 @@ \subsubsection{描述} \subsubsection{分析} -每个容器的面积,取决于最短的木板。 +每個容器的面積,取決於最短的木板。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Container With Most Water -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: int maxArea(vector &height) { @@ -350,8 +350,8 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Trapping Rain Water, 见 \S \ref{sec:trapping-rain-water} -\item Largest Rectangle in Histogram, 见 \S \ref{sec:largest-rectangle-in-histogram} +\item Trapping Rain Water, 見 \S \ref{sec:trapping-rain-water} +\item Largest Rectangle in Histogram, 見 \S \ref{sec:largest-rectangle-in-histogram} \myenddot diff --git a/C++/chapImplement.tex b/C++/chapImplement.tex index 40b88a2b..b11133cb 100644 --- a/C++/chapImplement.tex +++ b/C++/chapImplement.tex @@ -1,5 +1,29 @@ -\chapter{细节实现题} -这类题目不考特定的算法,纯粹考察写代码的熟练度。 +\chapter{細節實現題} +這類題目不考特定的算法,純粹考察寫代碼的熟練度。 +\newline + +Gerneral approach for software implementation. Please refer to the example \textbf{Interval Map} +\begin{itemize} + \item List out all the inputs with categories + \begin{itemize} + \item How to list out the categories? Usually that will be a mix of bottom to top and top to bottom approach, list out some inputs that are easy to observe and then pick their common factors. Then use set theory to explore all the possibilities of that factor. + \begin{itemize} + \item an input that is longer than 10 + \item an input that is short than 10 + \item an input that is equal to 10 + \end{itemize} + \item if there are too many categories, try to change the input format so that you can merge different categories into the same one in order to reduce the number of types + \item it is important to reduce the number of categories. you can resolve one category by one implementation so the number of categories will increase your time of coding + \end{itemize} + \item List out the output of the corresponding input category + \item Think about the implementation --- language independently + \begin{itemize} + \item There could be multiple steps within the implementation, try to seperate them if they are related to each other. + \item The aim is to reduce the number of entanglement. + \end{itemize} + \item Prepare auto test cases --- random generation is preferred + \item Implement and test your code. It will be very helpful if you can implement a random test case especially for questions that are related to boundaries like the one \textbf{Interval Map} +\end{itemize} \section{Reverse Integer} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -26,14 +50,14 @@ \subsubsection{描述} \subsubsection{分析} -短小精悍的题,代码也可以写的很短小。 +短小精悍的題,代碼也可以寫的很短小。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} //LeetCode, Reverse Integer -// 时间复杂度O(logn),空间复杂度O(1) -// 考虑 1.负数的情况 2. 溢出的情况(正溢出&&负溢出,比如 x = -2147483648(即-2^31) ) +// 時間複雜度O(logn),空間複雜度O(1) +// 考慮 1.負數的情況 2. 溢出的情況(正溢出&&負溢出,比如 x = -2147483648(即-2^31) ) class Solution { public: int reverse (int x) { @@ -58,9 +82,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Palindrome Number, 见 \S \ref{sec:palindrome-number} +\item Palindrome Number, 見 \S \ref{sec:palindrome-number} \myenddot @@ -83,15 +107,15 @@ \subsubsection{描述} \subsubsection{分析} -首先想到,可以利用上一题,将整数反转,然后与原来的整数比较,是否相等,相等则为 Palindrome 的。可是 reverse()会溢出。 +首先想到,可以利用上一題,將整數反轉,然後與原來的整數比較,是否相等,相等則為 Palindrome 的。可是 reverse()會溢出。 -正确的解法是,不断地取第一位和最后一位(10进制下)进行比较,相等则取第二位和倒数第二位,直到完成比较或者中途找到了不一致的位。 +正確的解法是,不斷地取第一位和最後一位(10進制下)進行比較,相等則取第二位和倒數第二位,直到完成比較或者中途找到了不一致的位。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} //LeetCode, Palindrome Number -// 时间复杂度O(1),空间复杂度O(1) +// 時間複雜度O(1),空間複雜度O(1) class Solution { public: bool isPalindrome(int x) { @@ -112,10 +136,10 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Reverse Integer, 见 \S \ref{sec:reverse-integer} -\item Valid Palindrome, 见 \S \ref{sec:valid-palindrome} +\item Reverse Integer, 見 \S \ref{sec:reverse-integer} +\item Valid Palindrome, 見 \S \ref{sec:valid-palindrome} \myenddot @@ -129,19 +153,18 @@ \subsubsection{描述} You may assume that the intervals were initially sorted according to their start times. Example 1: -Given intervals \code{[1,3],[6,9]}, insert and merge \code{[2,5]} in as \code{[1,5],[6,9]}. +Given intervals \code\{[1,3],[6,9]\}, insert and merge \code\{[2,5]\} in as \code\{[1,5],[6,9]\}. Example 2: -Given \code{[1,2],[3,5],[6,7],[8,10],[12,16]}, insert and merge \code{[4,9]} in as \code{[1,2],[3,10],[12,16]}. +Given \code\{[1,2],[3,5],[6,7],[8,10],[12,16]\}, insert and merge \code\{[4,9]\} in as \code\{[1,2],[3,10],[12,16]\}. -This is because the new interval \code{[4,9]} overlaps with \code{[3,5],[6,7],[8,10]}. +This is because the new interval \code\{[4,9]\} overlaps with \code\{[3,5],[6,7],[8,10]\}. \subsubsection{分析} -无 +無 - -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} struct Interval { int start; @@ -149,9 +172,9 @@ \subsubsection{代码} Interval() : start(0), end(0) { } Interval(int s, int e) : start(s), end(e) { } }; - + //LeetCode, Insert Interval -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: vector insert(vector &intervals, Interval newInterval) { @@ -175,11 +198,51 @@ \subsubsection{代码} }; \end{Code} +\subsubsection{代碼} +\begin{Code} +//LeetCode, Insert Interval +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + vector> insert(vector>& intervals, vector& newInterval) { + vector> result; + bool isDone = false; + for (const auto& interval : intervals) + { + if (!isDone && newInterval[0] <= interval[0]) + { + MyMerge(result, newInterval); + isDone = true; + } + MyMerge(result, interval); + } + if (!isDone) + MyMerge(result, newInterval); + + return result; + } +private: + void MyMerge(vector>& result, const vector& interval) + { + if (result.empty()) + result.push_back(interval); + else + { + int& end = result.back()[1]; + if (end < interval[0]) + result.push_back(interval); + else + end = max(end, interval[1]); + } + } +}; +\end{Code} + -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Merge Intervals,见 \S \ref{sec:merge-intervals} +\item Merge Intervals,見 \S \ref{sec:merge-intervals} \myenddot @@ -191,15 +254,15 @@ \subsubsection{描述} Given a collection of intervals, merge all overlapping intervals. For example, -Given \code{[1,3],[2,6],[8,10],[15,18]}, -return \code{[1,6],[8,10],[15,18]} +Given \code\{[1,3],[2,6],[8,10],[15,18]\}, +return \code\{[1,6],[8,10],[15,18]\} \subsubsection{分析} -复用一下Insert Intervals的解法即可,创建一个新的interval集合,然后每次从旧的里面取一个interval出来,然后插入到新的集合中。 +複用一下Insert Intervals的解法即可,創建一個新的interval集合,然後每次從舊的裏面取一個interval出來,然後插入到新的集合中。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} struct Interval { int start; @@ -207,10 +270,10 @@ \subsubsection{代码} Interval() : start(0), end(0) { } Interval(int s, int e) : start(s), end(e) { } }; - + //LeetCode, Merge Interval -//复用一下Insert Intervals的解法即可 -// 时间复杂度O(n1+n2+...),空间复杂度O(1) +//複用一下Insert Intervals的解法即可 +// 時間複雜度O(n1+n2+...),空間複雜度O(1) class Solution { public: vector merge(vector &intervals) { @@ -241,12 +304,47 @@ \subsubsection{代码} } }; \end{Code} +\subsubsection{代碼} +\begin{Code} +//LeetCode, Merge Interval +// Sort it and always push back into a reserved vector +// 時間複雜度O(nlogn),空間複雜度O(n) +class Solution { +public: + vector> merge(vector>& intervals) { + // sort it first + sort(intervals.begin(), intervals.end(), [&](const auto& first, const auto& second) + { + return first[0] < second[0]; + }); + vector> result; + result.reserve(intervals.size()); + + for (const auto& interval : intervals) + mergeBack(result, interval); + + return result; + } +private: + void mergeBack(vector>& result, const vector& interval) { + if (result.size() == 0) result.push_back(interval); + + int& start = result.back()[0]; + int& end = result.back()[1]; + + if (interval[0] >= start && interval[0] <= end) + end = max(end, interval[1]); + else + result.push_back(interval); + } +}; +\end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Insert Interval,见 \S \ref{sec:insert-interval} +\item Insert Interval,見 \S \ref{sec:insert-interval} \myenddot @@ -269,13 +367,13 @@ \subsubsection{描述} \subsubsection{分析} -双指针,动态维护一个区间。尾指针不断往后扫,当扫到有一个窗口包含了所有$T$的字符后,然后再收缩头指针,直到不能再收缩为止。最后记录所有可能的情况中窗口最小的 +雙指針,動態維護一個區間。尾指針不斷往後掃,當掃到有一個窗口包含了所有$T$的字符後,然後再收縮頭指針,直到不能再收縮為止。最後記錄所有可能的情況中窗口最小的 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Minimum Window Substring -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: string minWindow(string S, string T) { @@ -290,18 +388,18 @@ \subsubsection{代码} for (size_t i = 0; i < T.size(); i++) expected_count[T[i]]++; - int minWidth = INT_MAX, min_start = 0; // 窗口大小,起点 + int minWidth = INT_MAX, min_start = 0; // 窗口大小,起點 int wnd_start = 0; - int appeared = 0; // 完整包含了一个T - //尾指针不断往后扫 + int appeared = 0; // 完整包含了一個T + //尾指針不斷往後掃 for (size_t wnd_end = 0; wnd_end < S.size(); wnd_end++) { if (expected_count[S[wnd_end]] > 0) { // this char is a part of T appeared_count[S[wnd_end]]++; if (appeared_count[S[wnd_end]] <= expected_count[S[wnd_end]]) appeared++; } - if (appeared == T.size()) { // 完整包含了一个T - // 收缩头指针 + if (appeared == T.size()) { // 完整包含了一個T + // 收縮頭指針 while (appeared_count[S[wnd_start]] > expected_count[S[wnd_start]] || expected_count[S[wnd_start]] == 0) { appeared_count[S[wnd_start]]--; @@ -321,10 +419,10 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -341,15 +439,15 @@ \subsubsection{描述} \subsubsection{分析} 高精度乘法。 -常见的做法是将字符转化为一个int,一一对应,形成一个int数组。但是这样很浪费空间,一个int32的最大值是$2^{31}-1=2147483647$,可以与9个字符对应,由于有乘法,减半,则至少可以与4个字符一一对应。一个int64可以与9个字符对应。 +常見的做法是將字符轉化為一個int,一一對應,形成一個int數組。但是這樣很浪費空間,一個int32的最大值是$2^{31}-1=2147483647$,可以與9個字符對應,由於有乘法,減半,則至少可以與4個字符一一對應。一個int64可以與9個字符對應。 -\subsubsection{代码1} +\subsubsection{代碼1} \begin{Code} // LeetCode, Multiply Strings -// @author 连城 (http://weibo.com/lianchengzju) -// 一个字符对应一个int -// 时间复杂度O(n*m),空间复杂度O(n+m) +// @author 連城 (http://weibo.com/lianchengzju) +// 一個字符對應一個int +// 時間複雜度O(n*m),空間複雜度O(n+m) typedef vector bigint; bigint make_bigint(string const& repr) { @@ -389,18 +487,18 @@ \subsubsection{代码1} \end{Code} -\subsubsection{代码2} +\subsubsection{代碼2} \begin{Code} // LeetCode, Multiply Strings -// 9个字符对应一个int64_t -// 时间复杂度O(n*m/81),空间复杂度O((n+m)/9) -/** 大整数类. */ +// 9個字符對應一個int64_t +// 時間複雜度O(n*m/81),空間複雜度O((n+m)/9) +/** 大整數類. */ class BigInt { public: /** - * @brief 构造函数,将字符串转化为大整数. - * @param[in] s 输入的字符串 - * @return 无 + * @brief 構造函數,將字符串轉化為大整數. + * @param[in] s 輸入的字符串 + * @return 無 */ BigInt(string s) { vector result; @@ -417,57 +515,57 @@ \subsubsection{代码2} elems = result; } /** - * @brief 将整数转化为字符串. + * @brief 將整數轉化為字符串. * @return 字符串 */ string toString() { stringstream result; - bool started = false; // 用于跳过前导0 + bool started = false; // 用於跳過前導0 for (auto i = elems.rbegin(); i != elems.rend(); i++) { - if (started) { // 如果多余的0已经都跳过,则输出 + if (started) { // 如果多餘的0已經都跳過,則輸出 result << setw(RADIX_LEN) << setfill('0') << *i; } else { result << *i; - started = true; // 碰到第一个非0的值,就说明多余的0已经都跳过 + started = true; // 碰到第一個非0的值,就説明多餘的0已經都跳過 } } - if (!started) return "0"; // 当x全为0时 + if (!started) return "0"; // 當x全為0時 else return result.str(); } /** - * @brief 大整数乘法. + * @brief 大整數乘法. * @param[in] x x * @param[in] y y - * @return 大整数 + * @return 大整數 */ static BigInt multiply(const BigInt &x, const BigInt &y) { vector z(x.elems.size() + y.elems.size(), 0); for (size_t i = 0; i < y.elems.size(); i++) { for (size_t j = 0; j < x.elems.size(); j++) { // 用y[i]去乘以x的各位 - // 两数第i, j位相乘,累加到结果的第i+j位 + // 兩數第i, j位相乘,累加到結果的第i+j位 z[i + j] += y.elems[i] * x.elems[j]; - if (z[i + j] >= BIGINT_RADIX) { // 看是否要进位 - z[i + j + 1] += z[i + j] / BIGINT_RADIX; // 进位 + if (z[i + j] >= BIGINT_RADIX) { // 看是否要進位 + z[i + j + 1] += z[i + j] / BIGINT_RADIX; // 進位 z[i + j] %= BIGINT_RADIX; } } } - while (z.back() == 0) z.pop_back(); // 没有进位,去掉最高位的0 + while (z.back() == 0) z.pop_back(); // 沒有進位,去掉最高位的0 return BigInt(z); } private: typedef long long int64_t; - /** 一个数组元素对应9个十进制位,即数组是亿进制的 - * 因为 1000000000 * 1000000000 没有超过 2^63-1 + /** 一個數組元素對應9個十進制位,即數組是億進制的 + * 因為 1000000000 * 1000000000 沒有超過 2^63-1 */ const static int BIGINT_RADIX = 1000000000; const static int RADIX_LEN = 9; - /** 万进制整数. */ + /** 萬進制整數. */ vector elems; BigInt(const vector num) : elems(num) {} }; @@ -484,10 +582,10 @@ \subsubsection{代码2} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -498,23 +596,23 @@ \section{Substring with Concatenation of All Words} %%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsubsection{描述} You are given a string, $S$, and a list of words, $L$, that are all of the same length. Find all starting indices of substring(s) in $S$ that is a concatenation of each word in $L$ exactly once and without any intervening characters. -For example, given: +For example, given: \begin{Code} S: "barfoothefoobarman" L: ["foo", "bar"] \end{Code} -You should return the indices: \code{[0,9]}.(order does not matter). +You should return the indices: \code\{[0,9]\}.(order does not matter). \subsubsection{分析} -无 +無 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Substring with Concatenation of All Words -// 时间复杂度O(n*m),空间复杂度O(m) +// 時間複雜度O(n*m),空間複雜度O(m) class Solution { public: vector findSubstring(string s, vector& dict) { @@ -548,10 +646,10 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -577,15 +675,15 @@ \subsubsection{描述} \subsubsection{分析} -本题可以用队列,计算下一行时,给上一行左右各加一个0,然后下一行的每个元素,就等于左上角和右上角之和。 +本題可以用隊列,計算下一行時,給上一行左右各加一個0,然後下一行的每個元素,就等於左上角和右上角之和。 -另一种思路,下一行第一个元素和最后一个元素赋值为1,中间的每个元素,等于上一行的左上角和右上角元素之和。 +另一種思路,下一行第一個元素和最後一個元素賦值為1,中間的每個元素,等於上一行的左上角和右上角元素之和。 -\subsubsection{从左到右} +\subsubsection{從左到右} \begin{Code} // LeetCode, Pascal's Triangle -// 时间复杂度O(n^2),空间复杂度O(n) +// 時間複雜度O(n^2),空間複雜度O(n) class Solution { public: vector > generate(int numRows) { @@ -609,10 +707,10 @@ \subsubsection{从左到右} \end{Code} -\subsubsection{从右到左} +\subsubsection{從右到左} \begin{Code} // LeetCode, Pascal's Triangle -// 时间复杂度O(n^2),空间复杂度O(n) +// 時間複雜度O(n^2),空間複雜度O(n) class Solution { public: vector > generate(int numRows) { @@ -631,9 +729,9 @@ \subsubsection{从右到左} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Pascal's Triangle II,见 \S \ref{sec:pascals-triangle-ii} +\item Pascal's Triangle II,見 \S \ref{sec:pascals-triangle-ii} \myenddot @@ -646,39 +744,62 @@ \subsubsection{描述} For example, given $k = 3$, -Return \code{[1,3,3,1]}. +Return \code\{[1,3,3,1]\}. Note: Could you optimize your algorithm to use only $O(k)$ extra space? \subsubsection{分析} -滚动数组。 +滾動數組。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Pascal's Triangle II -// 滚动数组,时间复杂度O(n^2),空间复杂度O(n) +// 滾動數組,時間複雜度O(n^2),空間複雜度O(n) class Solution { public: - vector getRow(int rowIndex) { - vector array; - for (int i = 0; i <= rowIndex; i++) { - for (int j = i - 1; j > 0; j--){ - array[j] = array[j - 1] + array[j]; - } - array.push_back(1); - } - return array; - } + vector getRow(int rowIndex) { + vector array; + for (int i = 0; i <= rowIndex; i++) { + for (int j = i - 1; j > 0; j--){ + array[j] = array[j - 1] + array[j]; + } + array.push_back(1); + } + return array; + } }; \end{Code} +\subsubsection{遞歸} +\begin{Code} +// LeetCode, Pascal's Triangle II +// 滾動數組,時間複雜度O(n^2),空間複雜度O(n) +class Solution { +public: + vector getRow(int rowIndex) { + vector result; + getRow(result, rowIndex, 0); + return result; + } +private: + void getRow(vector& result, int rowIndex, int step) { + if (step > rowIndex) return; -\subsubsection{相关题目} + for (int i = step - 1; i > 0; i--) + result[i] = result[i-1] + result[i]; + result.push_back(1); + + getRow(result, rowIndex, step+1); + } +}; +\end{Code} + +\subsubsection{相關題目} \begindot -\item Pascal's Triangle,见 \S \ref{sec:pascals-triangle} +\item Pascal's Triangle,見 \S \ref{sec:pascal-s-triangle} \myenddot @@ -702,13 +823,13 @@ \subsubsection{描述} \subsubsection{分析} -模拟。 +模擬。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Spiral Matrix -// @author 龚陆安 (http://weibo.com/luangong) -// 时间复杂度O(n^2),空间复杂度O(1) +// @author 龔陸安 (http://weibo.com/luangong) +// 時間複雜度O(n^2),空間複雜度O(1) class Solution { public: vector spiralOrder(vector >& matrix) { @@ -736,9 +857,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Spiral Matrix II ,见 \S \ref{sec:spiral-matrix-ii} +\item Spiral Matrix II ,見 \S \ref{sec:spiral-matrix-ii} \myenddot @@ -763,13 +884,13 @@ \subsubsection{描述} \subsubsection{分析} -这题比上一题要简单。 +這題比上一題要簡單。 -\subsubsection{代码1} +\subsubsection{代碼1} \begin{Code} // LeetCode, Spiral Matrix II -// 时间复杂度O(n^2),空间复杂度O(n^2) +// 時間複雜度O(n^2),空間複雜度O(n^2) class Solution { public: vector > generateMatrix(int n) { @@ -794,11 +915,11 @@ \subsubsection{代码1} \end{Code} -\subsubsection{代码2} +\subsubsection{代碼2} \begin{Code} // LeetCode, Spiral Matrix II -// @author 龚陆安 (http://weibo.com/luangong) -// 时间复杂度O(n^2),空间复杂度O(n^2) +// @author 龔陸安 (http://weibo.com/luangong) +// 時間複雜度O(n^2),空間複雜度O(n^2) class Solution { public: vector > generateMatrix(int n) { @@ -826,9 +947,9 @@ \subsubsection{代码2} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Spiral Matrix, 见 \S \ref{sec:spiral-matrix} +\item Spiral Matrix, 見 \S \ref{sec:spiral-matrix} \myenddot @@ -855,7 +976,7 @@ \subsubsection{描述} \subsubsection{分析} -要找到数学规律。真正面试中,不大可能出这种问题。 +要找到數學規律。真正面試中,不大可能出這種問題。 n=4: \begin{Code} @@ -874,13 +995,13 @@ \subsubsection{分析} A N \end{Code} -所以,对于每一层垂直元素的坐标 $(i,j)= (j+1 )*n +i$;对于每两层垂直元素之间的插入元素(斜对角元素),$(i,j)= (j+1)*n -i$ +所以,對於每一層垂直元素的座標 $(i,j)= (j+1 )*n +i$;對於每兩層垂直元素之間的插入元素(斜對角元素),$(i,j)= (j+1)*n -i$ -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, ZigZag Conversion -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: string convert(string s, int nRows) { @@ -890,7 +1011,7 @@ \subsubsection{代码} for (int j = 0, index = i; index < s.size(); j++, index = (2 * nRows - 2) * j + i) { result.append(1, s[index]); // 垂直元素 - if (i == 0 || i == nRows - 1) continue; // 斜对角元素 + if (i == 0 || i == nRows - 1) continue; // 斜對角元素 if (index + (nRows - i - 1) * 2 < s.size()) result.append(1, s[index + (nRows - i - 1) * 2]); } @@ -901,9 +1022,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -916,23 +1037,23 @@ \subsubsection{描述} \subsubsection{分析} -不能用乘、除和取模,那剩下的,还有加、减和位运算。 +不能用乘、除和取模,那剩下的,還有加、減和位運算。 -最简单的方法,是不断减去被除数。在这个基础上,可以做一点优化,每次把被除数翻倍,从而加速。 +最簡單的方法,是不斷減去被除數。在這個基礎上,可以做一點優化,每次把被除數翻倍,從而加速。 -\subsubsection{代码1} +\subsubsection{代碼1} \begin{Code} // LeetCode, Divide Two Integers -// 时间复杂度O(logn),空间复杂度O(1) +// 時間複雜度O(logn),空間複雜度O(1) class Solution { public: int divide(int dividend, int divisor) { - // 当 dividend = INT_MIN时,-dividend会溢出,所以用 long long + // 當 dividend = INT_MIN時,-dividend會溢出,所以用 long long long long a = dividend >= 0 ? dividend : -(long long)dividend; long long b = divisor >= 0 ? divisor : -(long long)divisor; - // 当 dividend = INT_MIN时,divisor = -1时,结果会溢出,所以用 long long + // 當 dividend = INT_MIN時,divisor = -1時,結果會溢出,所以用 long long long long result = 0; while (a >= b) { long long c = b; @@ -948,18 +1069,18 @@ \subsubsection{代码1} \end{Code} -\subsubsection{代码2} +\subsubsection{代碼2} \begin{Code} // LeetCode, Divide Two Integers -// 时间复杂度O(logn),空间复杂度O(1) +// 時間複雜度O(logn),空間複雜度O(1) class Solution { public: int divide(int dividend, int divisor) { - int result = 0; // 当 dividend = INT_MIN时,divisor = -1时,结果会溢出 + int result = 0; // 當 dividend = INT_MIN時,divisor = -1時,結果會溢出 const bool sign = (dividend > 0 && divisor < 0) || - (dividend < 0 && divisor > 0); // 异号 + (dividend < 0 && divisor > 0); // 異號 - // 当 dividend = INT_MIN时,-dividend会溢出,所以用 unsigned int + // 當 dividend = INT_MIN時,-dividend會溢出,所以用 unsigned int unsigned int a = dividend >= 0 ? dividend : -dividend; unsigned int b = divisor >= 0 ? divisor : -divisor; @@ -983,9 +1104,9 @@ \subsubsection{代码2} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -1003,7 +1124,7 @@ \subsubsection{描述} For the last line of text, it should be left justified and no extra space is inserted between words. For example, \\ -words: \code{["This", "is", "an", "example", "of", "text", "justification."]} \\ +words: \code{\["This", "is", "an", "example", "of", "text", "justification."\]} \\ L: 16. Return the formatted lines as: @@ -1025,19 +1146,19 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Text Justification -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: vector fullJustify(vector &words, int L) { vector result; const int n = words.size(); - int begin = 0, len = 0; // 当前行的起点,当前长度 + int begin = 0, len = 0; // 當前行的起點,當前長度 for (int i = 0; i < n; ++i) { if (len + words[i].size() + (i - begin) > L) { result.push_back(connect(words, begin, i - 1, len, L, false)); @@ -1046,19 +1167,19 @@ \subsubsection{代码} } len += words[i].size(); } - // 最后一行不足L + // 最後一行不足L result.push_back(connect(words, begin, n - 1, len, L, true)); return result; } /** - * @brief 将 words[begin, end] 连成一行 - * @param[in] words 单词列表 - * @param[in] begin 开始 - * @param[in] end 结束 - * @param[in] len words[begin, end]所有单词加起来的长度 - * @param[in] L 题目规定的一行长度 - * @param[in] is_last 是否是最后一行 - * @return 对齐后的当前行 + * @brief 將 words[begin, end] 連成一行 + * @param[in] words 單詞列表 + * @param[in] begin 開始 + * @param[in] end 結束 + * @param[in] len words[begin, end]所有單詞加起來的長度 + * @param[in] L 題目規定的一行長度 + * @param[in] is_last 是否是最後一行 + * @return 對齊後的當前行 */ string connect(vector &words, int begin, int end, int len, int L, bool is_last) { @@ -1076,11 +1197,11 @@ \subsubsection{代码} /** * @brief 添加空格. * @param[inout]s 一行 - * @param[in] i 当前空隙的序号 - * @param[in] n 空隙总数 - * @param[in] L 总共需要添加的空额数 - * @param[in] is_last 是否是最后一行 - * @return 无 + * @param[in] i 當前空隙的序號 + * @param[in] n 空隙總數 + * @param[in] L 總共需要添加的空額數 + * @param[in] is_last 是否是最後一行 + * @return 無 */ void addSpaces(string &s, int i, int n, int L, bool is_last) { if (n < 1 || i > n - 1) return; @@ -1091,9 +1212,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -1106,15 +1227,15 @@ \subsubsection{描述} \subsubsection{分析} -暴力枚举法。两点决定一条直线,$n$个点两两组合,可以得到$\dfrac{1}{2}n(n+1)$条直线,对每一条直线,判断$n$个点是否在该直线上,从而可以得到这条直线上的点的个数,选择最大的那条直线返回。复杂度$O(n^3)$。 +暴力枚舉法。兩點決定一條直線,$n$個點兩兩組合,可以得到$\dfrac{1}{2}n(n+1)$條直線,對每一條直線,判斷$n$個點是否在該直線上,從而可以得到這條直線上的點的個數,選擇最大的那條直線返回。複雜度$O(n^3)$。 -上面的暴力枚举法以“边”为中心,再看另一种暴力枚举法,以每个“点”为中心,然后遍历剩余点,找到所有的斜率,如果斜率相同,那么一定共线对每个点,用一个哈希表,key为斜率,value为该直线上的点数,计算出哈希表后,取最大值,并更新全局最大值,最后就是结果。时间复杂度$O(n^2)$,空间复杂度$O(n)$。 +上面的暴力枚舉法以“邊”為中心,再看另一種暴力枚舉法,以每個“點”為中心,然後遍歷剩餘點,找到所有的斜率,如果斜率相同,那麼一定共線對每個點,用一個哈希表,key為斜率,value為該直線上的點數,計算出哈希表後,取最大值,並更新全局最大值,最後就是結果。時間複雜度$O(n^2)$,空間複雜度$O(n)$。 -\subsubsection{以边为中心} +\subsubsection{以邊為中心} \begin{Code} // LeetCode, Max Points on a Line -// 暴力枚举法,以边为中心,时间复杂度O(n^3),空间复杂度O(1) +// 暴力枚舉法,以邊為中心,時間複雜度O(n^3),空間複雜度O(1) class Solution { public: int maxPoints(vector &points) { @@ -1133,8 +1254,8 @@ \subsubsection{以边为中心} } int count = 0; for (int k = 0; k < points.size(); k++) { - if ((0 == sign && a * points[k].y == c + b * points[k].x) || - (1 == sign&&points[k].x == points[j].x)) + if ((0 == sign && a * points[k].y == c + b * points[k].x) || + (1 == sign&&points[k].x == points[j].x)) count++; } if (count > result) result = count; @@ -1146,10 +1267,10 @@ \subsubsection{以边为中心} \end{Code} -\subsubsection{以点为中心} +\subsubsection{以點為中心} \begin{Code} // LeetCode, Max Points on a Line -// 暴力枚举,以点为中心,时间复杂度O(n^2),空间复杂度O(n) +// 暴力枚舉,以點為中心,時間複雜度O(n^2),空間複雜度O(n) class Solution { public: int maxPoints(vector &points) { @@ -1159,8 +1280,8 @@ \subsubsection{以点为中心} unordered_map slope_count; for (int i = 0; i < points.size()-1; i++) { slope_count.clear(); - int samePointNum = 0; // 与i重合的点 - int point_max = 1; // 和i共线的最大点数 + int samePointNum = 0; // 與i重合的點 + int point_max = 1; // 和i共線的最大點數 for (int j = i + 1; j < points.size(); j++) { double slope; // 斜率 @@ -1171,7 +1292,7 @@ \subsubsection{以点为中心} continue; } } else { - slope = 1.0 * (points[i].y - points[j].y) / + slope = 1.0 * (points[i].y - points[j].y) / (points[i].x - points[j].x); } @@ -1193,7 +1314,770 @@ \subsubsection{以点为中心} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot + +\section{Calculate Number of Weeks} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:calculate-number-of-weeks} + +\subsubsection{描述} +Gvien a year, start month, end month and the first week day of that year. Calculate the maximum number of weeks that is included. +A week must start from Monday to Sunday. There has 29 day in February when the year can be multiple by 4. + +\subsubsection{分析} +Nil + + +\subsubsection{代碼} +\begin{Code} +class Solution +{ +public: + Solution() { PrepareDB(); } + ~Solution() {} + + int numOfWeeks(int Y, const string& A, const string& B, const string& W) + { + int startMon = m_strMon2Int[A]; + int endMon = m_strMon2Int[B]; + int yearStartWeekDay = m_strWeek2Int[W]; + // Get the date of first Monday + int startDay = GetFirstMonday(Y, startMon, yearStartWeekDay); + // Get the date of last Sunday + int endDay = GetLastSunday(Y, endMon, yearStartWeekDay); + + // Get the total of days + int dayDiff = GetDayDiff(Y, startMon, startDay, endMon, endDay); + // return answer + return (dayDiff + 1) / 7; + } +private: + int GetFirstMonday(int Y, int Mon, int yearStartWeekDay) + { + int dayDiff = GetDayDiff(Y, 1, 1, Mon, 1); + dayDiff %= 7; + int wday = (yearStartWeekDay + dayDiff) % 7; + int curDay = 1; + while (wday != 1) + { + curDay++; + wday++; + wday %= 7; + } + + return curDay; + } + int GetLastSunday(int Y, int Mon, int yearStartWeekDay) + { + int dayDiff = GetDayDiff(Y, 1, 1, Mon, GetMonLength(Y, Mon)); + dayDiff %= 7; + int wday = (yearStartWeekDay + dayDiff) % 7; + int curDay = GetMonLength(Y, Mon); + while (wday != 0) + { + curDay--; + wday--; + if (wday == -1) wday = 6; + } + + return curDay; + } + int GetDayDiff(int Y, int firstMon, int firstDate, int secondMon, int secondDate) + { + int result = 0; + for (int i = firstMon; i < secondMon; i++) + { + if (i == firstMon) + result += GetMonLength(Y, i) - firstDate + 1; + else + result += GetMonLength(Y, i); + } + result += secondDate; + + return result - 1; + } + int GetMonLength(int Y, int Mon) + { + if (Mon == 2) + { + if (Y % 4 == 0) + return 29; + else + return 28; + } + else + return m_Mon2Length[Mon]; + } + void PrepareDB() + { + m_strMon2Int["January"] = 1; + m_strMon2Int["February"] = 2; + m_strMon2Int["March"] = 3; + m_strMon2Int["April"] = 4; + m_strMon2Int["May"] = 5; + m_strMon2Int["June"] = 6; + m_strMon2Int["July"] = 7; + m_strMon2Int["August"] = 8; + m_strMon2Int["September"] = 9; + m_strMon2Int["October"] = 10; + m_strMon2Int["November"] = 11; + m_strMon2Int["December"] = 12; + + m_Mon2Length[1] = 31; + m_Mon2Length[3] = 31; + m_Mon2Length[4] = 30; + m_Mon2Length[5] = 31; + m_Mon2Length[6] = 30; + m_Mon2Length[7] = 31; + m_Mon2Length[8] = 31; + m_Mon2Length[9] = 30; + m_Mon2Length[10] = 31; + m_Mon2Length[11] = 30; + m_Mon2Length[12] = 31; + + m_strWeek2Int["Monday"] = 1; + m_strWeek2Int["Tuesday"] = 2; + m_strWeek2Int["Wednesday"] = 3; + m_strWeek2Int["Thursday"] = 4; + m_strWeek2Int["Friday"] = 5; + m_strWeek2Int["Saturday"] = 6; + m_strWeek2Int["Sunday"] = 0; + } +private: + unordered_map m_strMon2Int; + unordered_map m_Mon2Length; + unordered_map m_strWeek2Int; +}; +\end{Code} + + +\subsubsection{相關題目} +Nil + +\section{Integer to English Words} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:integer-to-english-words} + +\subsubsection{描述} +Bloomberg questions +\newline +Input: 123\newline +Output: One Hundred Twenty Three\newline + +\subsubsection{分析} +Nil + + +\subsubsection{代碼} +\begin{Code} +class Solution +{ +public: + string numberToWords(int num) { + if (num == 0) return "Zero"; + + const vector bigUnit{"","Thousand","Million","Billion"}; + const vector strNum{"","One","Two","Three","Four","Five" + ,"Six","Seven","Eight","Nine" + ,"Ten","Eleven","Twelve","Thirteen" + ,"Fourteen","Fifteen","Sixteen","Seventeen","Eighteen","Nineteen"}; + const vector str10Num{"","","Twenty","Thirty","Forty" + ,"Fifty","Sixty","Seventy","Eighty","Ninety"}; + + string result; + + while (num > 0) { + int t = num; + int step = 0; + int d = 1; // one unit + while (t >= 1000) { + t /= 1000; + d *= 1000; + step++; + } + + int u3 = t / 100; t %= 100; + if (t > 19) { + int u2 = t / 10; t %= 10; + int u1 = t; + + if (u3 > 0) + result += " " + strNum[u3] + " " + "Hundred"; + + result += " " + str10Num[u2]; + + // prevent edge case, example 20, u1 == 0 + if (u1 > 0) result += " " + strNum[u1]; + } + else { + if (u3 > 0) + result += " " + strNum[u3] + " " + "Hundred"; + if (t != 0) + result += " " + strNum[t]; + } + // handle big unit + if (step > 0) result += " " + bigUnit[step]; + + if (num < 1000) break; + num %= d; + } + + return result.substr(1, result.size() - 1); + } +}; +\end{Code} + + +\subsubsection{相關題目} +Nil + +\section{String Compression} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:string-compression} + +\subsubsection{描述} +Bloomberg questions + +\begin{Code} +Example 1: +Input: + ["a","a","b","b","c","c","c"] +Output: + Return 6, and the first 6 characters of the + input array should be: ["a","2","b","2","c","3"] +Explanation: + "aa" is replaced by "a2". "bb" is replaced by "b2". "ccc" is replaced by "c3". + +Example 2: +Input: + ["a"] +Output: + Return 1, and the first 1 characters of the input array should be: ["a"] +Explanation: + Nothing is replaced. +\end{Code} + +\subsubsection{分析} +Nil + + +\subsubsection{代碼} +\begin{Code} +class Solution +{ +public: + int compress(vector& chars) { + auto PushNum = [&](int& count, int& index) + { + int d = 1; + while (count / d >= 10) // find the largest decimal + d *= 10; + + while (d > 0) + { + chars[index++] = (char)(count / d + '0'); + count %= d; + d /= 10; + } + }; + // use 2 pointers + int count = 1; + int index = 0; + for (int i = 1; i < (int)chars.size(); i++) + { + if (chars[i] == chars[i-1]) + { + count++; + } + else + { + chars[index++] = chars[i-1]; + if (count != 1) + { + // get num to char + PushNum(count, index); + count = 1; + } + } + } + chars[index++] = chars.back(); + if (count > 1) + PushNum(count, index); + + return index; + } +}; +\end{Code} + + +\subsubsection{相關題目} +Nil + +\section{Diagonal Traverse} +\label{sec:diagonal-traverse} + +\subsection{描述} +Given a matrix of M x N elements (M rows, N columns), return all elements of the matrix in diagonal order as shown in the below image. + +\begin{center} +\includegraphics[width=150pt]{diagonal-traverse.png}\\ +\figcaption{Diagonal Traverse}\label{fig:diagonal-traverse} +\end{center} + +Note: +The total number of elements of the given matrix will not exceed 10,000. + + +\subsection{分析} +Nil + +\subsection{代碼} +\begin{Code} +// LeetCode +// 時間複雜度O(),空間複雜度O() +class Solution { +public: + vector findDiagonalOrder(vector>& matrix) { + int M = matrix.size(); + if (M == 0) return vector(); + int N = matrix[0].size(); + if (N == 0) return vector(); + + vector result; result.reserve(M + N); + + for (int d = 0; d < M + N - 1; d++) { + int r = (d < N) ? 0 : d - N + 1; + int c = (d < N) ? d : N - 1; + + vector row; + while (r < M && c > -1) + { + row.push_back(matrix[r][c]); + r++; + c--; + } + + if (d % 2 == 0) reverse(row.begin(), row.end()); + result.insert(result.end(), row.begin(), row.end()); + } + + return result; + } +}; +\end{Code} + +\subsection{代碼} +\begin{Code} +// LeetCode +// 時間複雜度O(),空間複雜度O() +class Solution { +public: + vector findDiagonalOrder(vector>& matrix) { + if(matrix.empty()) return {}; + int m = matrix.size(), n = matrix[0].size(); + vector res(m * n); + for(int i = 0, r = 0, c = 0;i < m * n;i++) { + res[i] = matrix[r][c]; + if((r + c) % 2 == 0) { + if(c == n - 1) r++; + else if(r == 0) c++; + else r--, c++; + } else { + if(r == m - 1) c++; + else if(c == 0) r++; + else r++, c--; + } + } + + return res; + } +}; +\end{Code} + +\section{Interval Map} +\label{sec:interval-map} + +\subsection{描述} +This question comes from \href{https://stackoverflow.com/q/54068482/2358836}{stack overflow} + +\textbf{interval_map} is a data structure that efficiently associates intervals of keys of type K with values of type V. Your task is to implement the assign member function of this data structure, which is outlined below. + +\textbf{interval_map} is implemented on top of \textbf{std::map}. In case you are not entirely sure which functions \textbf{std::map} provides, what they do and which guarantees they provide, we provide an excerpt of the C++ standard here. (at the end) + +Each key-value-pair (k,v) in the \textbf{std::map} means that the value v is associated with the interval from k (including) to the next key (excluding) in the \textbf{std::map}. + +Example: the \textbf{std::map (0,'A'), (3,'B'), (5,'A')} represents the mapping + +\begin{Code} + 0 -> 'A' + 1 -> 'A' + 2 -> 'A' + 3 -> 'B' + 4 -> 'B' + 5 -> 'A' + 6 -> 'A' + 7 -> 'A' +\end{Code} + +... all the way to \textbf{numeric_limits::max()} + +The representation in the \textbf{std::map} must be canonical, that is, consecutive map entries must not have the same value: \textbf{..., (0,'A'), (3,'A'), ...} is not allowed. Initially, the whole range of K is associated with a given initial value, passed to the constructor of the interval_map data structure. + + +Key type --- K + + besides being copyable and assignable, is less-than comparable via operator< + is bounded below, with the lowest value being std::numeric_limits::lowest() + does not implement any other operations, in particular no equality comparison or arithmetic operators + +Value type --- V + + besides being copyable and assignable, is equality-comparable via operator== + does not implement any other operations + + +\subsection{分析} +The first attempt I did was list out all the possible cases for the interval insertion. This is a top to bottom approach. The cases are included but not limited to: + +\begin{Code} +for c_1 in ["new intervale is bigger / equal\ + / smaller than the entire group of intervals in the map"]: + for c_2 in ["c_1 is before / one leg overlap\ + / two legs overlap / after the group of intervals in the map"]: + for c_3 in ["is the new interval touch the boundary?"]: +\end{Code} + +The cases are already more than 60. This approach is not workable. The following graph shows some cases. + +\begin{center} +\includegraphics[width=150pt]{interval-map-cases.png}\\ +\figcaption{Interval map cases}\label{fig:interval-map-cases} +\end{center} + +From the experience of the first attempt. I am looking for an implementation that can handle all the cases. The cases from the above diagram \textbf{interval full overlap} we can handle all the cases by one implementation. With the observation, I create a dummy interval for the leading and tailing interval and trun the entire problem into 1 case only and that is the \textbf{interval full overlap} case. + +\begin{center} +\includegraphics[width=150pt]{interval-map-one-case.png}\\ +\figcaption{Interval map cases}\label{fig:interval-map-one-case} +\end{center} + +Then I separate the handling of the interval begin and end, so that I can reduce the complexity of the status checking including the key value overlaping, and the begin value checking. + + +\subsection{代碼} +\begin{Code} +// stack overflow +// 時間複雜度O(),空間複雜度O() +class Solution { +public: + +}; + +#include +#include +#include +#include +#include +#include +#include + +template +class block_map { + friend void BlockMapTest(); + V m_valBegin; + std::map m_map; +public: + block_map(V const& val) + : m_valBegin(val) + {} + + void print() const + { + for (const auto &[k, v] : m_map) + { + std::cout << k << " " << v << std::endl; + } + } + + // Assign value val to block [keyBegin, keyEnd) + void assign( K const& keyBegin, K const& keyEnd, V const& val ) { + if (!(keyBegin < keyEnd)) return; // return if empty block + + // initial for empty map + if (m_map.size() == 0) + { + const auto it = m_map.insert(m_map.begin(), {keyBegin, val}); + m_map.insert(it, {keyEnd, m_valBegin}); + return; + } + + // handle all interval related information for keyBegin + // keyBegin use upper_bound - 1 to get current interval val + // due to the definition of the interval val [start, end) + auto next_begin_it = m_map.upper_bound(keyBegin); + auto cur_begin_it = next_begin_it == m_map.begin()\ + ? m_map.begin() : std::prev(next_begin_it); + bool is_begin_key_equal = next_begin_it != m_map.begin()\ + && !(cur_begin_it->first < keyBegin)\ + ? true : false; + V cur_begin_val = next_begin_it == m_map.begin()\ + ? m_valBegin : cur_begin_it->second; + auto prev_begin_it = cur_begin_it == m_map.begin()\ + ? m_map.begin() : std::prev(cur_begin_it); + V prev_begin_val = cur_begin_it == m_map.begin()\ + ? m_valBegin : prev_begin_it->second; + + // handle all interval related information for keyEnd + // keyBegin use lower_bound - 1 to get current interval val + // due to the definition of the interval val [start, end) + auto next_end_it = m_map.lower_bound(keyEnd); + bool is_end_key_equal = next_end_it == m_map.end()\ + ? false : !(keyEnd < next_end_it->first); + V next_end_val = next_end_it == m_map.end()\ + ? m_valBegin : next_end_it->second; + V cur_end_val = next_end_it == m_map.begin()\ + ? m_valBegin : std::prev(next_end_it)-> second; + + // erase [begin current block, next end block) + auto erase_end_it = is_end_key_equal && val == next_end_val\ + ? std::next(next_end_it) : next_end_it; + auto erase_begin_it = is_begin_key_equal\ + ? cur_begin_it : next_begin_it; + auto erase_it = m_map.erase(erase_begin_it, erase_end_it); + + // insert back + auto insert_it = erase_it; + if ((!is_begin_key_equal && val != cur_begin_val)\ + || (is_begin_key_equal && prev_begin_val != val)) + { + insert_it = m_map.insert(insert_it, {keyBegin, val}); + insert_it++; + } + if (val != cur_end_val && !is_end_key_equal) + { + insert_it = m_map.insert(insert_it, {keyEnd, cur_end_val}); + } + } + + V const& operator[]( K const& key ) const { + auto it=m_map.upper_bound(key); + if(it==m_map.begin()) { + return m_valBegin; + } else { + return (--it)->second; + } + } +}; + +struct TestKeyType +{ + unsigned int val; + constexpr TestKeyType(unsigned int val) : val(val) {} + constexpr bool operator<(const TestKeyType& other) const { return val < other.val; } +}; + +namespace std { + template<> class numeric_limits { + public: + static constexpr TestKeyType lowest()\ + { return TestKeyType(numeric_limits::lowest()); } + //static constexpr TestKeyType lowest() { return TestKeyType(-250); } + }; +} + +using TestValueType = char; + +struct TestFloatKeyType +{ + float val; + + TestFloatKeyType() = default; + + TestFloatKeyType(float val) : val(val) {} + bool operator< (TestFloatKeyType other) const + { + return other.val - val > 1.e-4f; + } +}; + +namespace std { + template<> class numeric_limits { + public: + static TestFloatKeyType lowest()\ + { return TestFloatKeyType(numeric_limits::lowest()); } + }; +} +\end{Code} + + +\section{Interval Map Random Test Case} +\label{sec:interval-map-random-test-case} + +\subsection{描述} +Please create a random test case generator for the interval map. + + +\subsection{分析} +There are 2 parts. We will have the first random funtion to generate an expected output. Then base on that output, we will generate a valid input. + +For the second random function, we will treat the block as a layer. Due to the assignment sequence, the later the block we assign, the range requirement will be tighter. + +\begin{center} +\includegraphics[width=150pt]{interval-map-test-cases.png}\\ +\figcaption{Interval map test cases}\label{fig:interval-map-test-cases} +\end{center} + +The first random function will generate the expected output in the left handside of the above diagram. In the middle, the second random function will generate a valid input based on a valid output. Finally, we should get a result similar to the right hand side diagram. + +\subsection{代碼} +\begin{Code} +// stack overflow +// 時間複雜度O(),空間複雜度O() +class Solution { +public: + +}; + +#include +#include +#include +#include +#include +#include +#include + +std::vector> +Generate_Random_Outputs(int num_of_inputs + , int begin_key + , int end_key + , const char &begin_val + , const char &end_val) +{ + std::random_device r; + std::default_random_engine el(r()); + std::uniform_int_distribution ran_key(begin_key, end_key); + std::uniform_int_distribution ran_val(begin_val, end_val); + + // first input + std::set first_input; + while (first_input.size() <= (size_t)num_of_inputs) // extra number for block ending + { + first_input.insert(ran_key(el)); + } + // final_output + std::vector> final_output; + char last_val = begin_val; + for (const auto &key : first_input) + { + char cur_val = ran_val(el); + while (cur_val == last_val) + cur_val = ran_val(el); + + // confirm a different value generated + final_output.push_back({key, cur_val}); + last_val = cur_val; + } + final_output.back().second = begin_val; + return final_output; +} + +std::vector> +Generate_Random_Input(const std::vector>& expected_output) +{ + std::set seen; + std::vector> random_input; + + std::random_device r; + std::default_random_engine el(r()); + std::uniform_int_distribution ran_idx(0, expected_output.size()-2); + while (seen.size() < expected_output.size() - 1) + { + size_t cur_idx = ran_idx(el); + while (seen.count(cur_idx)) + cur_idx = ran_idx(el); + + auto next_level_it = seen.lower_bound(cur_idx); + auto prev_level_it = next_level_it == seen.begin()\ + ? seen.begin() : std::prev(next_level_it); + // handle begin + int rand_begin_start = next_level_it == seen.begin()\ + ? expected_output[cur_idx].first\ + : expected_output[(*prev_level_it) + 1].first; + int rand_begin_end = expected_output[cur_idx].first; + + // handle end + int rand_end_start = expected_output[cur_idx + 1].first; + int rand_end_end = next_level_it == seen.end()\ + ? expected_output.back().first\ + : expected_output[*next_level_it].first; + + // generate random + std::uniform_int_distribution ran_begin(rand_begin_start, rand_begin_end); + std::uniform_int_distribution ran_end(rand_end_start, rand_end_end); + random_input.push_back({ran_begin(el)\ + , ran_end(el)\ + , expected_output[cur_idx].second}); + + // update seen + seen.insert(cur_idx); + } + + return random_input; +} + +void Demo_Block_Map() +{ + std::cout << "Start Demo_Block_Map" << std::endl; + + bool is_found_error = false; + std::vector> random_output; + while (!is_found_error) + { + random_output = Generate_Random_Outputs(500, 1, 300000, 'A', 'Z'); + + std::cout << "Expected output:" << std::endl; + for (const auto &[begin_key, val] : random_output) + { + std::cout << begin_key << " " << val << std::endl; + } + std::cout << std::endl; + std::cout << "Random input:" << std::endl; + auto random_input = Generate_Random_Input(random_output); + // insert into map + block_map bm('A'); + for (const auto &[begin_key, end_key, val] : random_input) + { + bm.assign(begin_key, end_key, val); + std::cout << begin_key << " " << end_key << " " << val << std::endl; + } + + // check + for (size_t i = 1; i < random_output.size(); i++) + { + const auto &[prev_key, prev_val] = random_output[i-1]; + const auto &[cur_key, cur_val] = random_output[i]; + + for (int j = prev_key; j < cur_key; j++) + { + if (bm[j] != prev_val) + { + std::cout << "Error expected: " << prev_val + << " in index: " << j + << " but get: " << bm[j] << std::endl; + is_found_error = true; + break; + } + } + if (is_found_error) + break; + } + if (is_found_error) + { + std::cout << std::endl; + std::cout << "Final we get:" << std::endl; + bm.print(); + } + } +} + +\end{Code} \ No newline at end of file diff --git a/C++/chapLinear.tex b/C++/chapLinear.tex new file mode 100644 index 00000000..2ace70dc --- /dev/null +++ b/C++/chapLinear.tex @@ -0,0 +1,3522 @@ +\chapter{線性表} +這類題目考察線性表的操作,例如,數組,單鏈表,雙向鏈表等。 +\newline + + +\section{數組} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +\subsection{Remove Duplicates from Sorted Array} +\label{sec:remove-duplicates-from-sorted-array} + + +\subsubsection{描述} +Given a sorted array, remove the duplicates in place such that each element appear only once and return the new length. + +Do not allocate extra space for another array, you must do this in place with constant memory. + +For example, Given input array \code{A = [1,1,2]}, + +Your function should return length = 2, and A is now \code{\[1,2\]}. + + +\subsubsection{分析} +無 + + +\subsubsection{代碼1} +\begin{Code} +// LeetCode, Remove Duplicates from Sorted Array +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + int removeDuplicates(vector& nums) { + if (nums.empty()) return 0; + + int index = 0; + for (int i = 1; i < nums.size(); i++) { + if (nums[index] != nums[i]) + nums[++index] = nums[i]; + } + return index + 1; + } +}; +\end{Code} + + +\subsubsection{代碼2} +\begin{Code} +// LeetCode, Remove Duplicates from Sorted Array +// 使用STL,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + int removeDuplicates(vector& nums) { + return distance(nums.begin(), unique(nums.begin(), nums.end())); + } +}; +\end{Code} + + +\subsubsection{代碼3} +\begin{Code} +// LeetCode, Remove Duplicates from Sorted Array +// 使用STL,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + int removeDuplicates(vector& nums) { + return distance(nums.begin(), removeDuplicates(nums.begin(), nums.end(), nums.begin())); + } + + template + OutIt removeDuplicates(InIt first, InIt last, OutIt output) { + while (first != last) { + *output++ = *first; + first = upper_bound(first, last, *first); + } + + return output; + } +}; +\end{Code} + + +\subsubsection{相關題目} + +\begindot +\item Remove Duplicates from Sorted Array II,見 \S \ref{sec:remove-duplicates-from-sorted-array-ii} +\myenddot + + +\subsection{Remove Duplicates from Sorted Array II} +\label{sec:remove-duplicates-from-sorted-array-ii} + + +\subsubsection{描述} +Follow up for "Remove Duplicates": +What if duplicates are allowed at most twice? + +For example, +Given sorted array \code{A = [1,1,1,2,2,3]}, + +Your function should return length = 5, and A is now \code{\[1,1,2,2,3\]} + + +\subsubsection{分析} +加一個變量記錄一下元素出現的次數即可。這題因為是已經排序的數組,所以一個變量即可解決。如果是沒有排序的數組,則需要引入一個hashmap來記錄出現次數。 + + +\subsubsection{代碼1} +\begin{Code} +// LeetCode, Remove Duplicates from Sorted Array II +// 時間複雜度O(n),空間複雜度O(1) +// @author hex108 (https://github.com/hex108) +class Solution { +public: + int removeDuplicates(vector& nums) { + if (nums.size() <= 2) return nums.size(); + + int index = 2; + for (int i = 2; i < nums.size(); i++){ + if (nums[i] != nums[index - 2]) + nums[index++] = nums[i]; + } + + return index; + } +}; +\end{Code} + + +\subsubsection{代碼2} +下面是一個更簡潔的版本。上面的代碼略長,不過擴展性好一些,例如將\fn{occur < 2}改為\fn{occur < 3},就變成了允許重複最多3次。 +\begin{Code} +// LeetCode, Remove Duplicates from Sorted Array II +// @author 虞航仲 (http://weibo.com/u/1666779725) +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + 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 && nums[i] == nums[i - 1] && nums[i] == nums[i + 1]) + continue; + + nums[index++] = nums[i]; + } + return index; + } +}; +\end{Code} + + +\subsubsection{代碼3} +Variable of duplication +\begin{Code} +// LeetCode, Remove Duplicates from Sorted Array II +// @author +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + int removeDuplicates(vector& nums, int du) { + int index = du; + for (int i = du; i < (int)nums.size(); i++) { + if (nums[index - du] != nums[i]) + nums[index++] = nums[i]; + } + + return index; + } +}; +\end{Code} +\subsubsection{相關題目} + +\begindot +\item Remove Duplicates from Sorted Array,見 \S \ref{sec:remove-duplicates-from-sorted-array} +\myenddot + + +\subsection{Search in Rotated Sorted Array} +\label{sec:search-in-rotated-sorted-array} + + +\subsubsection{描述} +Suppose a sorted array is rotated at some pivot unknown to you beforehand. + +(i.e., \code{0 1 2 4 5 6 7} might become \code{4 5 6 7 0 1 2}). + +You are given a target value to search. If found in the array return its index, otherwise return -1. + +You may assume no duplicate exists in the array. + + +\subsubsection{分析} +二分查找,難度主要在於左右邊界的確定。 + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Search in Rotated Sorted Array +// 時間複雜度O(log n),空間複雜度O(1) +class Solution { +public: + int search(const vector& nums, int target) { + int first = 0, last = nums.size(); + while (first != last) { + const int mid = first + (last - first) / 2; + if (nums[mid] == target) + return mid; + if (nums[first] <= nums[mid]) { + if (nums[first] <= target && target < nums[mid]) + last = mid; + else + first = mid + 1; + } else { + if (nums[mid] < target && target <= nums[last-1]) + first = mid + 1; + else + last = mid; + } + } + return -1; + } +}; +\end{Code} + + +\subsubsection{相關題目} + +\begindot +\item Search in Rotated Sorted Array II,見 \S \ref{sec:search-in-rotated-sorted-array-ii} +\myenddot + + +\subsection{Search in Rotated Sorted Array II} +\label{sec:search-in-rotated-sorted-array-ii} + + +\subsubsection{描述} +Follow up for "Search in Rotated Sorted Array": What if \emph{duplicates} are allowed? + +Would this affect the run-time complexity? How and why? + +Write a function to determine if a given target is in the array. + + +\subsubsection{分析} +允許重複元素,則上一題中如果\fn{A[m]>=A[l]},那麼\fn{[l,m]}為遞增序列的假設就不能成立了,比如\code{\[1,3,1,1,1\]}。 + +如果\fn{A[m]>=A[l]}不能確定遞增,那就把它拆分成兩個條件: +\begindot +\item 若\fn{A[m]>A[l]},則區間\fn{[l,m]}一定遞增 +\item 若\fn{A[m]==A[l]} 確定不了,那就\fn{l++},往下看一步即可。 +\myenddot + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Search in Rotated Sorted Array II +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + bool search(const vector& nums, int target) { + int first = 0, last = nums.size(); + while (first != last) { + const int mid = first + (last - first) / 2; + if (nums[mid] == target) + return true; + if (nums[first] < nums[mid]) { + if (nums[first] <= target && target < nums[mid]) + last = mid; + else + first = mid + 1; + } else if (nums[first] > nums[mid]) { + if (nums[mid] < target && target <= nums[last-1]) + first = mid + 1; + else + last = mid; + } else + //skip duplicate one + first++; + } + return false; + } +}; +\end{Code} + + +\subsubsection{相關題目} + +\begindot +\item Search in Rotated Sorted Array,見 \S \ref{sec:search-in-rotated-sorted-array} +\myenddot + + +\subsection{Median of Two Sorted Arrays} +\label{sec:median-of-two-sorted-arrays} + + +\subsubsection{描述} +There are two sorted arrays A and B of size m and n respectively. Find the median of the two sorted arrays. The overall run time complexity should be $O(\log (m+n))$. + + +\subsubsection{分析} +這是一道非常經典的題。這題更通用的形式是,給定兩個已經排序好的數組,找到兩者所有元素中第$k$大的元素。 + +$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$入手。如果我們每次都能夠刪除一個一定在第$k$大元素之前的元素,那麼我們需要進行$k$次。但是如果每次我們都刪除一半呢?由於A和B都是有序的,我們應該充分利用這裏面的信息,類似於二分查找,也是充分利用了“有序”。 + +假設A和B的元素個數都大於$k/2$,我們將A的第$k/2$個元素(即\fn{A[k/2-1]})和B的第$k/2$個元素(即\fn{B[k/2-1]})進行比較,有以下三種情況(為了簡化這裏先假設$k$為偶數,所得到的結論對於$k$是奇數也是成立的): +\begindot +\item \fn{A[k/2-1] == B[k/2-1]} +\item \fn{A[k/2-1] > B[k/2-1]} +\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$大元素。留給讀者證明。 + +因此,我們可以放心的刪除A數組的這$k/2$個元素。同理,當\fn{A[k/2-1] > B[k/2-1]}時,可以刪除B數組的$k/2$個元素。 + +當\fn{A[k/2-1] == B[k/2-1]}時,説明找到了第$k$大的元素,直接返回\fn{A[k/2-1]}或\fn{B[k/2-1]}即可。 + +因此,我們可以寫一個遞歸函數。那麼函數什麼時候應該終止呢? +\begindot +\item 當A或B是空時,直接返回\fn{B[k-1]}或\fn{A[k-1]}; +\item 當\fn{k=1}是,返回\fn{min(A[0], B[0])}; +\item 當\fn{A[k/2-1] == B[k/2-1]}時,返回\fn{A[k/2-1]}或\fn{B[k/2-1]} +\myenddot + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Median of Two Sorted Arrays +// 時間複雜度O(log(m+n)),空間複雜度O(log(m+n)) +class Solution { +public: + 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.begin(), m, B.begin(), n, total / 2 + 1); + else + 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(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, *B); + + //divide k into two parts + int ia = min(k / 2, m), ib = k - ia; + 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)) + return find_kth(A, m, B + ib, n - ib, k - ib); + else + return A[ia - 1]; + } +}; +\end{Code} + + +\subsubsection{相關題目} + +\begindot +\item 無 +\myenddot + + +\subsection{Longest Consecutive Sequence} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:longest-consecutive-sequence} + + +\subsubsection{描述} +Given an unsorted array of integers, find the length of the longest consecutive elements sequence. + +For example, +Given \code{\[100, 4, 200, 1, 3, 2\]}, +The longest consecutive elements sequence is \code{\[1, 2, 3, 4\]}. Return its length: 4. + +Your algorithm should run in $O(n)$ complexity. + + +\subsubsection{分析} +如果允許$O(n \log n)$的複雜度,那麼可以先排序,可是本題要求$O(n)$。 + +由於序列裏的元素是無序的,又要求$O(n)$,首先要想到用哈希表。 + +用一個哈希表 \fn{unordered_map used}記錄每個元素是否使用,對每個元素,以該元素為中心,往左右擴張,直到不連續為止,記錄下最長的長度。 + + +\subsubsection{代碼} +\begin{Code} +// Leet Code, Longest Consecutive Sequence +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + int longestConsecutive(const vector &nums) { + unordered_map used; + + for (auto i : nums) used[i] = false; + + int longest = 0; + + for (auto i : nums) { + if (used[i]) continue; + + int length = 1; + + used[i] = true; + + for (int j = i + 1; used.find(j) != used.end(); ++j) { + used[j] = true; + ++length; + } + + for (int j = i - 1; used.find(j) != used.end(); --j) { + used[j] = true; + ++length; + } + + longest = max(longest, length); + } + + return longest; + } +}; +\end{Code} + +\subsubsection{分析2} +第一直覺是個聚類的操作,應該有union,find的操作.連續序列可以用兩端和長度來表示. +本來用兩端就可以表示,但考慮到查詢的需求,將兩端分別暴露出來.用\fn{unordered_map map}來 +存儲.原始思路來自於\url{http://discuss.leetcode.com/questions/1070/longest-consecutive-sequence} + +\subsubsection{代碼} + +\begin{Code} +// Leet Code, Longest Consecutive Sequence +// 時間複雜度O(n),空間複雜度O(n) +// Author: @advancedxy +class Solution { +public: + int longestConsecutive(vector &nums) { + unordered_map map; + int size = nums.size(); + int l = 1; + for (int i = 0; i < size; 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(nums[i] + 1) != map.end()) { + l = max(l, mergeCluster(map, nums[i], nums[i] + 1)); + } + } + return size == 0 ? 0 : l; + } + +private: + int mergeCluster(unordered_map &map, int left, int right) { + int upper = right + map[right] - 1; + int lower = left - map[left] + 1; + int length = upper - lower + 1; + map[upper] = length; + map[lower] = length; + return length; + } +}; +\end{Code} + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + + +\subsection{Two Sum} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:Two-sum} + + +\subsubsection{描述} +Given an array of integers, find two numbers such that they add up to a specific target number. + +The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based. + +You may assume that each input would have exactly one solution. + +Input: \code{numbers=\{2, 7, 11, 15\}, target=9} + +Output: \code{index1=1, index2=2} + + +\subsubsection{分析} +方法1:暴力,複雜度$O(n^2)$,會超時 + +方法2:hash。用一個哈希表,存儲每個數對應的下標,複雜度$O(n)$. + +方法3:先排序,然後左右夾逼,排序$O(n\log n)$,左右夾逼$O(n)$,最終$O(n\log n)$。但是注意,這題需要返回的是下標,而不是數字本身,因此這個方法行不通。 + + +\subsubsection{代碼} +\begin{Code} +//LeetCode, Two Sum +// 方法2:hash。用一個哈希表,存儲每個數對應的下標 +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + vector twoSum(vector &nums, int target) { + unordered_map mapping; + vector result; + for (int i = 0; i < nums.size(); i++) { + mapping[nums[i]] = 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); + break; + } + } + return result; + } +}; +\end{Code} + +\subsubsection{代碼} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + vector twoSum(vector& nums, int target) { + unordered_map cache; + for (size_t i = 0; i < nums.size(); i++) + { + auto it = cache.find(target - nums[i]); + if (it != cache.end()) + { + return vector{it->second, (int)i}; + } + cache.emplace(nums[i], i); + } + + return vector(); // should not go here + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item 3Sum, 見 \S \ref{sec:3sum} +\item 3Sum Closest, 見 \S \ref{sec:3sum-closest} +\item 4Sum, 見 \S \ref{sec:4sum} +\myenddot + +\subsection{Two Sum III} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:Two-sum-iii} + + +\subsubsection{描述} +Design and implement a TwoSum class. It should support the following operations: add and find. + +add - Add the number to an internal data structure. +find - Find if there exists any pair of numbers which sum is equal to the value. + +Example 1: +\begin{Code} +add(1); add(3); add(5); +find(4) -> true +find(7) -> false +\end{Code} + +Example 2: +\begin{Code} +add(3); add(1); add(2); +find(3) -> true +find(6) -> false +\end{Code} + +\subsubsection{代碼} +\begin{Code} +// 用一個 hash map,存儲每個數對應的 count +// 時間複雜度O(n),空間複雜度O(n) +class TwoSum { +public: + /** Initialize your data structure here. */ + TwoSum() { + + } + + /** Add the number to an internal data structure.. */ + void add(int number) { + m_cache[number]++; + } + + /** Find if there exists any pair of numbers which sum is equal to the value. */ + bool find(int value) { + for (const auto& [k, v] : m_cache) + { + auto it = m_cache.find(value - k); + if (it != m_cache.end()) + { + // 例子: value: 4, k: 2. 當 k == 2 在數組中只出現一次,不能當是答案 + if (value - k == k) + { + if (v > 1) + return true; + else + continue; + } + else + return true; + } + } + return false; + } +private: + unordered_map m_cache; +}; + +/** + * Your TwoSum object will be instantiated and called as such: + * TwoSum* obj = new TwoSum(); + * obj->add(number); + * bool param_2 = obj->find(value); + */ +\end{Code} + + + +\subsection{3Sum} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:3sum} + + +\subsubsection{描述} +Given an array $S$ of $n$ integers, are there elements $a, b, c$ in $S$ such that $a + b + c = 0$? Find all unique triplets in the array which gives the sum of zero. + +Note: +\begindot +\item Elements in a triplet $(a,b,c)$ must be in non-descending order. (ie, $a \leq b \leq c$) +\item The solution set must not contain duplicate triplets. +\myenddot + +For example, given array \code{S = \{-1 0 1 2 -1 -4\}}. + +A solution set is: +\begin{Code} +(-1, 0, 1) +(-1, -1, 2) +\end{Code} + + +\subsubsection{分析} +先排序,然後左右夾逼,複雜度 $O(n^2)$。 + +這個方法可以推廣到$k$-sum,先排序,然後做$k-2$次循環,在最內層循環左右夾逼,時間複雜度是 $O(\max\{n \log n, n^{k-1}\})$。 + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, 3Sum +// 先排序,然後左右夾逼,注意跳過重複的數,時間複雜度O(n^2),空間複雜度O(1) +class Solution { + public: + vector> threeSum(vector& nums) { + vector> result; + if (nums.size() < 3) return result; + sort(nums.begin(), nums.end()); + const int target = 0; + + auto last = nums.end(); + for (auto i = nums.begin(); i < last-2; ++i) { + auto j = i+1; + if (i > nums.begin() && *i == *(i-1)) continue; + auto k = last-1; + while (j < k) { + if (*i + *j + *k < target) { + ++j; + while(*j == *(j - 1) && j < k) ++j; + } else if (*i + *j + *k > target) { + --k; + while(*k == *(k + 1) && j < k) --k; + } else { + result.push_back({ *i, *j, *k }); + ++j; + --k; + while(*j == *(j - 1) && *k == *(k + 1) && j < k) ++j; + } + } + } + return result; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Two sum, 見 \S \ref{sec:Two-sum} +\item 3Sum Closest, 見 \S \ref{sec:3sum-closest} +\item 4Sum, 見 \S \ref{sec:4sum} +\myenddot + +\subsection{3Sum Closest} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:3sum-closest} + + +\subsubsection{描述} +Given an array $S$ of $n$ integers, find three integers in $S$ such that the sum is closest to a given number, target. Return the sum of the three integers. You may assume that each input would have exactly one solution. + +For example, given array \code{S = \{-1 2 1 -4\}}, and \code{target = 1}. + +The sum that is closest to the target is 2. (\code{-1 + 2 + 1 = 2}). + + +\subsubsection{分析} +先排序,然後左右夾逼,複雜度 $O(n^2)$。 + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, 3Sum Closest +// 先排序,然後左右夾逼,時間複雜度O(n^2),空間複雜度O(1) +class Solution { +public: + int threeSumClosest(vector& nums, int target) { + int result = 0; + int min_gap = INT_MAX; + + sort(nums.begin(), nums.end()); + + for (auto a = nums.begin(); a != prev(nums.end(), 2); ++a) { + auto b = next(a); + auto c = prev(nums.end()); + + while (b < c) { + const int sum = *a + *b + *c; + const int gap = abs(sum - target); + + if (gap < min_gap) { + result = sum; + min_gap = gap; + } + + if (sum < target) ++b; + else --c; + } + } + + return result; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Two sum, 見 \S \ref{sec:Two-sum} +\item 3Sum, 見 \S \ref{sec:3sum} +\item 4Sum, 見 \S \ref{sec:4sum} +\myenddot + + +\subsection{4Sum} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:4sum} + + +\subsubsection{描述} +Given an array $S$ of $n$ integers, are there elements $a, b, c$, and $d$ in $S$ such that $a + b + c + d = target$? Find all unique quadruplets in the array which gives the sum of target. + +Note: +\begindot +\item Elements in a quadruplet $(a,b,c,d)$ must be in non-descending order. (ie, $a \leq b \leq c \leq d$) +\item The solution set must not contain duplicate quadruplets. +\myenddot + +For example, given array \code{S = \{1 0 -1 0 -2 2\}}, and \code{target = 0}. + +A solution set is: +\begin{Code} +(-1, 0, 0, 1) +(-2, -1, 1, 2) +(-2, 0, 0, 2) +\end{Code} + + +\subsubsection{分析} +先排序,然後左右夾逼,複雜度 $O(n^3)$,會超時。 + +可以用一個hashmap先緩存兩個數的和,最終複雜度$O(n^3)$。這個策略也適用於 3Sum 。 + + +\subsubsection{左右夾逼} +\begin{Code} +// LeetCode, 4Sum +// 先排序,然後左右夾逼,時間複雜度O(n^3),空間複雜度O(1) +class Solution { +public: + vector> fourSum(vector& nums, int target) { + vector> result; + if (nums.size() < 4) return result; + sort(nums.begin(), nums.end()); + + 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); + while (c < d) { + if (*a + *b + *c + *d < target) { + ++c; + } else if (*a + *b + *c + *d > target) { + --d; + } else { + result.push_back({ *a, *b, *c, *d }); + ++c; + --d; + } + } + } + } + sort(result.begin(), result.end()); + result.erase(unique(result.begin(), result.end()), result.end()); + return result; + } +}; +\end{Code} + + +\subsubsection{map做緩存} +\begin{Code} +// LeetCode, 4Sum +// 用一個hashmap先緩存兩個數的和 +// 時間複雜度,平均O(n^2),最壞O(n^4),空間複雜度O(n^2) +class Solution { +public: + vector > fourSum(vector &nums, int target) { + vector> result; + if (nums.size() < 4) return result; + sort(nums.begin(), nums.end()); + + unordered_map > > cache; + 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 < 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]; + for (size_t k = 0; k < vec.size(); ++k) { + if (c <= vec[k].second) + continue; // 有重疊 + + result.push_back( { nums[vec[k].first], + nums[vec[k].second], nums[c], nums[d] }); + } + } + } + sort(result.begin(), result.end()); + result.erase(unique(result.begin(), result.end()), result.end()); + return result; + } +}; +\end{Code} + + +\subsubsection{multimap} +\begin{Code} +// LeetCode, 4Sum +// 用一個 hashmap 先緩存兩個數的和 +// 時間複雜度O(n^2),空間複雜度O(n^2) +// @author 龔陸安(http://weibo.com/luangong) +class Solution { +public: + vector> fourSum(vector& nums, int target) { + vector> result; + if (nums.size() < 4) return result; + sort(nums.begin(), nums.end()); + + unordered_multimap> cache; + 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; + auto range = cache.equal_range(x); + for (auto j = range.first; j != range.second; ++j) { + auto a = i->second.first; + auto b = i->second.second; + auto c = j->second.first; + auto d = j->second.second; + if (a != c && a != d && b != c && b != d) { + vector vec = { nums[a], nums[b], nums[c], nums[d] }; + sort(vec.begin(), vec.end()); + result.push_back(vec); + } + } + } + sort(result.begin(), result.end()); + result.erase(unique(result.begin(), result.end()), result.end()); + return result; + } +}; +\end{Code} + + +\subsubsection{方法4} +\begin{Code} +// LeetCode, 4Sum +// 先排序,然後左右夾逼,時間複雜度O(n^3logn),空間複雜度O(1),會超時 +// 跟方法1相比,表面上優化了,實際上更慢了,切記! +class Solution { +public: + vector> fourSum(vector& nums, int target) { + vector> result; + if (nums.size() < 4) return result; + sort(nums.begin(), nums.end()); + + 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)) { + auto c = next(b); + auto d = prev(last); + while (c < d) { + if (*a + *b + *c + *d < target) { + c = upper_bound(c, d, *c); + } else if (*a + *b + *c + *d > target) { + d = prev(lower_bound(c, d, *d)); + } else { + result.push_back({ *a, *b, *c, *d }); + c = upper_bound(c, d, *c); + d = prev(lower_bound(c, d, *d)); + } + } + } + } + return result; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Two sum, 見 \S \ref{sec:Two-sum} +\item 3Sum, 見 \S \ref{sec:3sum} +\item 3Sum Closest, 見 \S \ref{sec:3sum-closest} +\myenddot + +\subsection{4Sum II} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:4sum-ii} + + +\subsubsection{描述} +Given four lists A, B, C, D of integer values, compute how many tuples (i, j, k, l) there are such that A[i] + B[j] + C[k] + D[l] is zero. + +To make problem a bit easier, all A, B, C, D have same length of N where 0 ≤ N ≤ 500. All integers are in the range of -228 to 228 - 1 and the result is guaranteed to be at most 231 - 1. + +Example: +\begin{Code} +Input: +A = [ 1, 2] +B = [-2,-1] +C = [-1, 2] +D = [ 0, 2] + +Output: +2 + +Explanation: +The two tuples are: +1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0 +2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0 +\end{Code} + + +\subsubsection{Hash map} +\begin{Code} +class Solution { +public: + int fourSumCount(vector& A, vector& B, vector& C, vector& D) { + int cnt = 0; + // 製造 hash map 來記低數組 A, B + unordered_map m; + for (int a : A) + for (int b : B) + ++m[a + b]; + + // 對比數組 C, D + for (int c : C) + for (int d : D) { + auto it = m.find(-(c + d)); + if (it != end(m)) + cnt += it->second; + } + + return cnt; + } +}; +\end{Code} + +\subsection{Remove Element} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:remove-element } + + +\subsubsection{描述} +Given an array and a value, remove all instances of that value in place and return the new length. + +The order of elements can be changed. It doesn't matter what you leave beyond the new length. + + +\subsubsection{分析} +無 + + +\subsubsection{代碼1} +\begin{Code} +// LeetCode, Remove Element +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + int removeElement(vector& nums, int target) { + int index = 0; + for (int i = 0; i < nums.size(); ++i) { + if (nums[i] != target) { + nums[index++] = nums[i]; + } + } + return index; + } +}; +\end{Code} + + +\subsubsection{代碼2} +\begin{Code} +// LeetCode, Remove Element +// 使用remove(),時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + int removeElement(vector& nums, int target) { + return distance(nums.begin(), remove(nums.begin(), nums.end(), target)); + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + + +\subsection{Next Permutation} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:next-permutation} + + +\subsubsection{描述} +Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers. + +If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order). + +The replacement must be in-place, do not allocate extra memory. + +Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column. +\begin{Code} +1,2,3 → 1,3,2 +3,2,1 → 1,2,3 +1,1,5 → 1,5,1 +\end{Code} + + +\subsubsection{分析} +算法過程如圖~\ref{fig:permutation}所示(來自\myurl{http://fisherlei.blogspot.com/2012/12/leetcode-next-permutation.html})。 + +\begin{center} +\includegraphics[width=360pt]{next-permutation.png}\\ +\figcaption{下一個排列算法流程}\label{fig:permutation} +\end{center} + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Next Permutation +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + void nextPermutation(vector &nums) { + next_permutation(nums.begin(), nums.end()); + } + + template + bool next_permutation(BidiIt first, BidiIt last) { + // Get a reversed range to simplify reversed traversal. + const auto rfirst = reverse_iterator(last); + const auto rlast = reverse_iterator(first); + + // Begin from the second last element to the first element. + auto pivot = next(rfirst); + + // Find `pivot`, which is the first element that is no less than its + // successor. `Prev` is used since `pivort` is a `reversed_iterator`. + while (pivot != rlast && *pivot >= *prev(pivot)) + ++pivot; + + // No such elemenet found, current sequence is already the largest + // permutation, then rearrange to the first permutation and return false. + if (pivot == rlast) { + reverse(rfirst, rlast); + return false; + } + + // Scan from right to left, find the first element that is greater than + // `pivot`. + auto change = find_if(rfirst, pivot, bind1st(less(), *pivot)); + + swap(*change, *pivot); + reverse(rfirst, pivot); + + return true; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Permutation Sequence, 見 \S \ref{sec:permutation-sequence} +\item Permutations, 見 \S \ref{sec:permutations} +\item Permutations II, 見 \S \ref{sec:permutations-ii} +\item Combinations, 見 \S \ref{sec:combinations} +\myenddot + +\subsection{Prev Permutation} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:prev-permutation} + + +\subsubsection{描述} +Implement previous permutation + +\begin{Code} +1,2,3 → 3,2,1 +3,2,1 → 3,1,2 +1,1,5 → 5,1,1 +\end{Code} + + +\subsubsection{分析} +無 + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Previous Permutation +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + void prevPermutation(vector &nums) { + prev_permutation(nums.begin(), nums.end()); + } + + template + bool prev_permutation(BidiIt first, BidiIt last) { + const auto rfirst = reverse_iterator(last); + const auto rlast = reverse_iterator(first); + + auto pivot = next(rfirst); + + while (pivot != rlast && *(pivot) <= *prev(pivot)) + pivot++; + + if (pivot == rlast) { + reverse(rfirst, rlast); + return false; + } + + auto change = find_if(rfirst, pivot, bind1st(greater(), *pivot)); + + swap(*change, *pivot); + reverse(rfirst, pivot); + + return true; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Permutation Sequence, 見 \S \ref{sec:permutation-sequence} +\item Permutations, 見 \S \ref{sec:permutations} +\item Permutations II, 見 \S \ref{sec:permutations-ii} +\item Combinations, 見 \S \ref{sec:combinations} +\myenddot + +\subsection{Permutation Sequence} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:permutation-sequence} + + +\subsubsection{描述} +The set \fn{[1,2,3,…,n]} contains a total of $n!$ unique permutations. + +By listing and labeling all of the permutations in order, +We get the following sequence (ie, for $n = 3$): +\begin{Code} +"123" +"132" +"213" +"231" +"312" +"321" +\end{Code} + +Given $n$ and $k$, return the kth permutation sequence. + +Note: Given $n$ will be between 1 and 9 inclusive. + + +\subsubsection{分析} +簡單的,可以用暴力枚舉法,調用 $k-1$ 次 \fn{next_permutation()}。 + +暴力枚舉法把前 $k$個排列都求出來了,比較浪費,而我們只需要第$k$個排列。 + +利用康託編碼的思路,假設有$n$個不重複的元素,第$k$個排列是$a_1, a_2, a_3, ..., a_n$,那麼$a_1$是哪一個位置呢? + +我們把$a_1$去掉,那麼剩下的排列為 +$a_2, a_3, ..., a_n$, 共計$n-1$個元素,$n-1$個元素共有$(n-1)!$個排列,於是就可以知道 $a_1 = k / (n-1)!$。 + +同理,$a_2, a_3, ..., a_n$的值推導如下: + +\begin{eqnarray} +k_2 &=& k\%(n-1)! \nonumber \\ +a_2 &=& k_2/(n-2)! \nonumber \\ +\quad & \cdots \nonumber \\ +k_{n-1} &=& k_{n-2}\%2! \nonumber \\ +a_{n-1} &=& k_{n-1}/1! \nonumber \\ +a_n &=& 0 \nonumber +\end{eqnarray} + + +\subsubsection{使用next_permutation()} +\begin{Code} +// LeetCode, Permutation Sequence +// 使用next_permutation(),TLE +class Solution { +public: + string getPermutation(int n, int k) { + string s(n, '0'); + for (int i = 0; i < n; ++i) + s[i] += i+1; + for (int i = 0; i < k-1; ++i) + next_permutation(s.begin(), s.end()); + return s; + } + + template + bool next_permutation(BidiIt first, BidiIt last) { + // 代碼見上一題 Next Permutation + } +}; +\end{Code} + + +\subsubsection{康託編碼} +\begin{Code} +// LeetCode, Permutation Sequence +// 康託編碼,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + string getPermutation(int n, int k) { + string s(n, '0'); + string result; + for (int i = 0; i < n; ++i) + s[i] += i + 1; + + return kth_permutation(s, k); + } +private: + int factorial(int n) { + int result = 1; + for (int i = 1; i <= n; ++i) + result *= i; + return result; + } + + // seq 已排好序,是第一個排列 + template + Sequence kth_permutation(const Sequence &seq, int k) { + const int n = seq.size(); + Sequence S(seq); + Sequence result; + + int base = factorial(n - 1); + --k; // 康託編碼從0開始 + + for (int i = n - 1; i > 0; k %= base, base /= i, --i) { + auto a = next(S.begin(), k / base); + result.push_back(*a); + S.erase(a); + } + + result.push_back(S[0]); // 最後一個 + return result; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Next Permutation, 見 \S \ref{sec:next-permutation} +\item Permutations, 見 \S \ref{sec:permutations} +\item Permutations II, 見 \S \ref{sec:permutations-ii} +\item Combinations, 見 \S \ref{sec:combinations} +\myenddot + + +\subsection{Valid Sudoku} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:valid-sudoku} + + +\subsubsection{描述} +Determine if a Sudoku is valid, according to: Sudoku Puzzles - The Rules \myurl{http://sudoku.com.au/TheRules.aspx} . + +The Sudoku board could be partially filled, where empty cells are filled with the character \fn{'.'}. + +\begin{center} +\includegraphics[width=150pt]{sudoku.png}\\ +\figcaption{A partially filled sudoku which is valid}\label{fig:sudoku} +\end{center} + +\subsubsection{分析} +細節實現題。 + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Valid Sudoku +// 時間複雜度O(n^2),空間複雜度O(1) +class Solution { +public: + bool isValidSudoku(const vector>& board) { + bool used[9]; + + for (int i = 0; i < 9; ++i) { + fill(used, used + 9, false); + + for (int j = 0; j < 9; ++j) // 檢查行 + if (!check(board[i][j], used)) + return false; + + fill(used, used + 9, false); + + for (int j = 0; j < 9; ++j) // 檢查列 + if (!check(board[j][i], used)) + return false; + } + + for (int r = 0; r < 3; ++r) // 檢查 9 個子格子 + for (int c = 0; c < 3; ++c) { + fill(used, used + 9, false); + + for (int i = r * 3; i < r * 3 + 3; ++i) + for (int j = c * 3; j < c * 3 + 3; ++j) + if (!check(board[i][j], used)) + return false; + } + + return true; + } + + bool check(char ch, bool used[9]) { + if (ch == '.') return true; + + if (used[ch - '1']) return false; + + return used[ch - '1'] = true; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Sudoku Solver, 見 \S \ref{sec:sudoku-solver} +\myenddot + + +\subsection{Trapping Rain Water} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:trapping-rain-water} + + +\subsubsection{描述} +Given $n$ non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining. + +For example, +Given \code{\[0,1,0,2,1,0,1,3,2,1,2,1\]}, return 6. + +\begin{center} +\includegraphics{trapping-rain-water.png}\\ +\figcaption{Trapping Rain Water}\label{fig:trapping-rain-water} +\end{center} + + +\subsubsection{分析} +對於每個柱子,找到其左右兩邊最高的柱子,該柱子能容納的面積就是\code{min(max_left, max_right) - height}。所以, +\begin{enumerate} +\item 從左往右掃描一遍,對於每個柱子,求取左邊最大值; +\item 從右往左掃描一遍,對於每個柱子,求最大右值; +\item 再掃描一遍,把每個柱子的面積並累加。 +\end{enumerate} + +也可以, +\begin{enumerate} +\item 掃描一遍,找到最高的柱子,這個柱子將數組分為兩半; +\item 處理左邊一半; +\item 處理右邊一半。 +\end{enumerate} + + +\subsubsection{代碼1} +\begin{Code} +// LeetCode, Trapping Rain Water +// 思路1,時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + int trap(const vector& A) { + const int n = A.size(); + int *max_left = new int[n](); + int *max_right = new int[n](); + + for (int i = 1; i < n; i++) { + max_left[i] = max(max_left[i - 1], A[i - 1]); + max_right[n - 1 - i] = max(max_right[n - i], A[n - i]); + + } + + int sum = 0; + for (int i = 0; i < n; i++) { + int height = min(max_left[i], max_right[i]); + if (height > A[i]) { + sum += height - A[i]; + } + } + + delete[] max_left; + delete[] max_right; + return sum; + } +}; +\end{Code} + + +\subsubsection{代碼2} +\begin{Code} +// LeetCode, Trapping Rain Water +// 思路2,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + 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; + + int water = 0; + for (int i = 0, peak = 0; i < max; i++) + if (A[i] > peak) peak = A[i]; + else water += peak - A[i]; + for (int i = n - 1, top = 0; i > max; i--) + if (A[i] > top) top = A[i]; + else water += top - A[i]; + return water; + } +}; +\end{Code} + + +\subsubsection{代碼3} +第三種解法,用一個棧輔助,小於棧頂的元素壓入,大於等於棧頂就把棧裏所有小於或等於當前值的元素全部出棧處理掉。 +\begin{Code} +// LeetCode, Trapping Rain Water +// 用一個棧輔助,小於棧頂的元素壓入,大於等於棧頂就把棧裏所有小於或 +// 等於當前值的元素全部出棧處理掉,計算面積,最後把當前元素入棧 +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + int trap(const vector& A) { + const int n = A.size(); + stack> s; + int water = 0; + + for (int i = 0; i < n; ++i) { + int height = 0; + + 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); + height = bar; + + if (A[i] < bar) // 碰到了比當前元素高的,跳出循環 + break; + else + s.pop(); // 彈出棧頂,因為該元素處理完了,不再需要了 + } + + s.push(make_pair(A[i], i)); + } + + return water; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Container With Most Water, 見 \S \ref{sec:container-with-most-water} +\item Largest Rectangle in Histogram, 見 \S \ref{sec:largest-rectangle-in-histogram} +\myenddot + + +\subsection{Rotate Image} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:rotate-image} + + +\subsubsection{描述} +You are given an $n \times n$ 2D matrix representing an image. + +Rotate the image by 90 degrees (clockwise). + +Follow up: +Could you do this in-place? + + +\subsubsection{分析} +首先想到,純模擬,從外到內一圈一圈的轉,但這個方法太慢。 + +如下圖,首先沿着副對角線翻轉一次,然後沿着水平中線翻轉一次。 + +\begin{center} +\includegraphics[width=200pt]{rotate-image.png}\\ +\figcaption{Rotate Image}\label{fig:rotate-image} +\end{center} + +或者,首先沿着水平中線翻轉一次,然後沿着主對角線翻轉一次。 + + +\subsubsection{代碼1} +\begin{Code} +// LeetCode, Rotate Image +// 思路 1,時間複雜度O(n^2),空間複雜度O(1) +class Solution { +public: + void rotate(vector>& matrix) { + const int n = matrix.size(); + + for (int i = 0; i < n; ++i) // 沿着副對角線反轉 + for (int j = 0; j < n - i; ++j) + swap(matrix[i][j], matrix[n - 1 - j][n - 1 - i]); + + for (int i = 0; i < n / 2; ++i) // 沿着水平中線反轉 + for (int j = 0; j < n; ++j) + swap(matrix[i][j], matrix[n - 1 - i][j]); + } +}; +\end{Code} + +\subsubsection{代碼2} +\begin{Code} +// LeetCode, Rotate Image +// 思路 2,時間複雜度O(n^2),空間複雜度O(1) +class Solution { +public: + void rotate(vector>& matrix) { + const int n = matrix.size(); + + for (int i = 0; i < n / 2; ++i) // 沿着水平中線反轉 + for (int j = 0; j < n; ++j) + swap(matrix[i][j], matrix[n - 1 - i][j]); + + for (int i = 0; i < n; ++i) // 沿着主對角線反轉 + for (int j = i + 1; j < n; ++j) + swap(matrix[i][j], matrix[j][i]); + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + + +\subsection{Plus One} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:plus-one} + + +\subsubsection{描述} +Given a number represented as an array of digits, plus one to the number. + + +\subsubsection{分析} +高精度加法。 + + +\subsubsection{代碼1} +\begin{Code} +// LeetCode, Plus One +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + vector plusOne(vector &digits) { + add(digits, 1); + return digits; + } +private: + // 0 <= digit <= 9 + void add(vector &digits, int digit) { + int c = digit; // carry, 進位 + + for (auto it = digits.rbegin(); it != digits.rend(); ++it) { + *it += c; + c = *it / 10; + *it %= 10; + } + + if (c > 0) digits.insert(digits.begin(), 1); + } +}; +\end{Code} + + +\subsubsection{代碼2} +\begin{Code} +// LeetCode, Plus One +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + vector plusOne(vector &digits) { + add(digits, 1); + return digits; + } +private: + // 0 <= digit <= 9 + void add(vector &digits, int digit) { + int c = digit; // carry, 進位 + + for_each(digits.rbegin(), digits.rend(), [&c](int &d){ + d += c; + c = d / 10; + d %= 10; + }); + + if (c > 0) digits.insert(digits.begin(), 1); + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + + +\subsection{Climbing Stairs} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:climbing-stairs} + + +\subsubsection{描述} +You are climbing a stair case. It takes $n$ steps to reach to the top. + +Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top? + + +\subsubsection{分析} +設$f(n)$表示爬$n$階樓梯的不同方法數,為了爬到第$n$階樓梯,有兩個選擇: +\begindot +\item 從第$n-1$階前進1步; +\item 從第$n-1$階前進2步; +\myenddot +因此,有$f(n)=f(n-1)+f(n-2)$。 + +這是一個斐波那契數列。 + +方法1,遞歸,太慢;方法2,迭代。 + +方法3,數學公式。斐波那契數列的通項公式為 $a_n=\dfrac{1}{\sqrt{5}}\left[\left(\dfrac{1+\sqrt{5}}{2}\right)^n-\left(\dfrac{1-\sqrt{5}}{2}\right)^n\right]$。 + + +\subsubsection{迭代} +\begin{Code} +// LeetCode, Climbing Stairs +// 迭代,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + int climbStairs(int n) { + int prev = 0; + int cur = 1; + for(int i = 1; i <= n ; ++i){ + int tmp = cur; + cur += prev; + prev = tmp; + } + return cur; + } +}; +\end{Code} + + +\subsubsection{數學公式} +\begin{Code} +// LeetCode, Climbing Stairs +// 數學公式,時間複雜度O(1),空間複雜度O(1) +class Solution { + public: + int climbStairs(int n) { + const double s = sqrt(5); + return floor((pow((1+s)/2, n+1) + pow((1-s)/2, n+1))/s + 0.5); + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Decode Ways, 見 \S \ref{sec:decode-ways} +\myenddot + + +\subsection{Gray Code} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:gray-code} + + +\subsubsection{描述} +The gray code is a binary numeral system where two successive values differ in only one bit. + +Given a non-negative integer $n$ representing the total number of bits in the code, print the sequence of gray code. A gray code sequence must begin with 0. + +For example, given $n = 2$, return \fn{[0,1,3,2]}. Its gray code sequence is: +\begin{Code} +00 - 0 +01 - 1 +11 - 3 +10 - 2 +\end{Code} + +Note: +\begindot +\item For a given $n$, a gray code sequence is not uniquely defined. +\item For example, \fn{[0,2,3,1]} is also a valid gray code sequence according to the above definition. +\item For now, the judge is able to judge based on one instance of gray code sequence. Sorry about that. +\myenddot + + +\subsubsection{分析} +格雷碼(Gray Code)的定義請參考 \myurl{http://en.wikipedia.org/wiki/Gray_code} + +\textbf{自然二進制碼轉換為格雷碼:$g_0=b_0, g_i=b_i \oplus b_{i-1}$} + +保留自然二進制碼的最高位作為格雷碼的最高位,格雷碼次高位為二進制碼的高位與次高位異或,其餘各位與次高位的求法類似。例如,將自然二進制碼1001,轉換為格雷碼的過程是:保留最高位;然後將第1位的1和第2位的0異或,得到1,作為格雷碼的第2位;將第2位的0和第3位的0異或,得到0,作為格雷碼的第3位;將第3位的0和第4位的1異或,得到1,作為格雷碼的第4位,最終,格雷碼為1101。 + +\textbf{格雷碼轉換為自然二進制碼:$b_0=g_0, b_i=g_i \oplus b_{i-1}$} + +保留格雷碼的最高位作為自然二進制碼的最高位,次高位為自然二進制高位與格雷碼次高位異或,其餘各位與次高位的求法類似。例如,將格雷碼1000轉換為自然二進制碼的過程是:保留最高位1,作為自然二進制碼的最高位;然後將自然二進制碼的第1位1和格雷碼的第2位0異或,得到1,作為自然二進制碼的第2位;將自然二進制碼的第2位1和格雷碼的第3位0異或,得到1,作為自然二進制碼的第3位;將自然二進制碼的第3位1和格雷碼的第4位0異或,得到1,作為自然二進制碼的第4位,最終,自然二進制碼為1111。 + +格雷碼有\textbf{數學公式},整數$n$的格雷碼是$n \oplus (n/2)$。 + +這題要求生成$n$比特的所有格雷碼。 + +方法1,最簡單的方法,利用數學公式,對從 $0\sim2^n-1$的所有整數,轉化為格雷碼。 + +方法2,$n$比特的格雷碼,可以遞歸地從$n-1$比特的格雷碼生成。如圖\S \ref{fig:gray-code-construction}所示。 + +\begin{center} +\includegraphics[width=160pt]{gray-code-construction.png}\\ +\figcaption{The first few steps of the reflect-and-prefix method.}\label{fig:gray-code-construction} +\end{center} + + +\subsubsection{數學公式} +\begin{Code} +// LeetCode, Gray Code +// 數學公式,時間複雜度O(2^n),空間複雜度O(1) +class Solution { +public: + vector grayCode(int n) { + vector result; + const size_t size = 1 << n; // 2^n + result.reserve(size); + for (size_t i = 0; i < size; ++i) + result.push_back(binary_to_gray(i)); + return result; + } +private: + static unsigned int binary_to_gray(unsigned int n) { + return n ^ (n >> 1); + } +}; +\end{Code} + + +\subsubsection{Reflect-and-prefix method} +\begin{Code} +// LeetCode, Gray Code +// reflect-and-prefix method +// 時間複雜度O(2^n),空間複雜度O(1) +class Solution { +public: + vector grayCode(int n) { + vector result; + result.reserve(1<= 0; j--) // 要反着遍歷,才能對稱 + result.push_back(highest_bit | result[j]); + } + return result; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + + +\subsection{Set Matrix Zeroes} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:set-matrix-zeroes} + + +\subsubsection{描述} +Given a $m \times n$ matrix, if an element is 0, set its entire row and column to 0. Do it in place. + +\textbf{Follow up:} +Did you use extra space? + +A straight forward solution using $O(mn)$ space is probably a bad idea. + +A simple improvement uses $O(m + n)$ space, but still not the best solution. + +Could you devise a constant space solution? + + +\subsubsection{分析} +$O(m+n)$空間的方法很簡單,設置兩個bool數組,記錄每行和每列是否存在0。 + +想要常數空間,可以複用第一行和第一列。 + + +\subsubsection{代碼1} +\begin{Code} +// LeetCode, Set Matrix Zeroes +// 時間複雜度O(m*n),空間複雜度O(m+n) +class Solution { +public: + void setZeroes(vector > &matrix) { + const size_t m = matrix.size(); + const size_t n = matrix[0].size(); + vector row(m, false); // 標記該行是否存在0 + vector col(n, false); // 標記該列是否存在0 + + for (size_t i = 0; i < m; ++i) { + for (size_t j = 0; j < n; ++j) { + if (matrix[i][j] == 0) { + row[i] = col[j] = true; + } + } + } + + for (size_t i = 0; i < m; ++i) { + if (row[i]) + fill(&matrix[i][0], &matrix[i][0] + n, 0); + } + for (size_t j = 0; j < n; ++j) { + if (col[j]) { + for (size_t i = 0; i < m; ++i) { + matrix[i][j] = 0; + } + } + } + } +}; +\end{Code} + + +\subsubsection{代碼2} +\begin{Code} +// LeetCode, Set Matrix Zeroes +// 時間複雜度O(m*n),空間複雜度O(1) +class Solution { +public: + void setZeroes(vector > &matrix) { + const size_t m = matrix.size(); + const size_t n = matrix[0].size(); + bool row_has_zero = false; // 第一行是否存在 0 + bool col_has_zero = false; // 第一列是否存在 0 + + for (size_t i = 0; i < n; i++) + if (matrix[0][i] == 0) { + row_has_zero = true; + break; + } + + for (size_t i = 0; i < m; i++) + if (matrix[i][0] == 0) { + col_has_zero = true; + break; + } + + for (size_t i = 1; i < m; i++) + for (size_t j = 1; j < n; j++) + if (matrix[i][j] == 0) { + matrix[0][j] = 0; + matrix[i][0] = 0; + } + for (size_t i = 1; i < m; i++) + for (size_t j = 1; j < n; j++) + if (matrix[i][0] == 0 || matrix[0][j] == 0) + matrix[i][j] = 0; + if (row_has_zero) + for (size_t i = 0; i < n; i++) + matrix[0][i] = 0; + if (col_has_zero) + for (size_t i = 0; i < m; i++) + matrix[i][0] = 0; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + + +\subsection{Gas Station} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:gas-station} + + +\subsubsection{描述} +There are $N$ gas stations along a circular route, where the amount of gas at station $i$ is \fn{gas[i]}. + +You have a car with an unlimited gas tank and it costs \fn{cost[i]} of gas to travel from station $i$ to its next station ($i$+1). You begin the journey with an empty tank at one of the gas stations. + +Return the starting gas station's index if you can travel around the circuit once, otherwise return -1. + +Note: +The solution is guaranteed to be unique. + + +\subsubsection{分析} +首先想到的是$O(N^2)$的解法,對每個點進行模擬。 + +$O(N)$的解法是,設置兩個變量,\fn{sum}判斷當前的指針的有效性;\fn{total}則判斷整個數組是否有解,有就返回通過\fn{sum}得到的下標,沒有則返回-1。 + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Gas Station +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + int canCompleteCircuit(vector &gas, vector &cost) { + int total = 0; + int j = -1; + for (int i = 0, sum = 0; i < gas.size(); ++i) { + sum += gas[i] - cost[i]; + total += gas[i] - cost[i]; + if (sum < 0) { + j = i; + sum = 0; + } + } + return total >= 0 ? j + 1 : -1; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + + +\subsection{Candy} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:candy} + + +\subsubsection{描述} +There are $N$ children standing in a line. Each child is assigned a rating value. + +You are giving candies to these children subjected to the following requirements: +\begindot +\item Each child must have at least one candy. +\item Children with a higher rating get more candies than their neighbors. +\myenddot + +What is the minimum candies you must give? + + +\subsubsection{分析} +無 + + +\subsubsection{迭代版} +\begin{Code} +// LeetCode, Candy +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + int candy(vector &ratings) { + const int n = ratings.size(); + vector increment(n); + + // 左右各掃描一遍 + for (int i = 1, inc = 1; i < n; i++) { + if (ratings[i] > ratings[i - 1]) + increment[i] = max(inc++, increment[i]); + else + inc = 1; + } + + for (int i = n - 2, inc = 1; i >= 0; i--) { + if (ratings[i] > ratings[i + 1]) + increment[i] = max(inc++, increment[i]); + else + inc = 1; + } + // 初始值為n,因為每個小朋友至少一顆糖 + return accumulate(&increment[0], &increment[0]+n, n); + } +}; +\end{Code} + + +\subsubsection{遞歸版} +\begin{Code} +// LeetCode, Candy +// 備忘錄法,時間複雜度O(n),空間複雜度O(n) +// @author fancymouse (http://weibo.com/u/1928162822) +class Solution { +public: + int candy(const vector& ratings) { + vector f(ratings.size()); + int sum = 0; + for (int i = 0; i < ratings.size(); ++i) + sum += solve(ratings, f, i); + return sum; + } + int solve(const vector& ratings, vector& f, int i) { + if (f[i] == 0) { + f[i] = 1; + if (i > 0 && ratings[i] > ratings[i - 1]) + f[i] = max(f[i], solve(ratings, f, i - 1) + 1); + if (i < ratings.size() - 1 && ratings[i] > ratings[i + 1]) + f[i] = max(f[i], solve(ratings, f, i + 1) + 1); + } + return f[i]; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + +\subsection{Counting Bits} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:counting-bits} + + +\subsubsection{描述} +Given a non negative integer number num. For every numbers i in the range 0 ≤ i ≤ num calculate the number of 1's in their binary representation and return them as an array. + +Example 1: +\begin{Code} +Input: 2 +Output: [0,1,1] +\end{Code} + +Example 2: +\begin{Code} +Input: 5 +Output: [0,1,1,2,1,2] +\end{Code} + +Follow up: +\begindot +\item It is very easy to come up with a solution with run time O(n*sizeof(integer)). But can you do it in linear time O(n) /possibly in a single pass? +\item Space complexity should be O(n). +\item Can you do it like a boss? Do it without using any builtin function like __builtin_popcount in c++ or in any other language. +\myenddot + +\subsubsection{Pop Count} +\begin{Code} +// 時間複雜度O(nk),空間複雜度O(n) +// 思路:利用 PopCount 暴力地數每一個數字的 bit 1 的總數 +class Solution { +public: + vector countBits(int num) { + vector result(num+1, 0); + for (int i = 0; i <= num; i++) + result[i] = PopCount(i); + return result; + } +private: + int PopCount(int x) + { + int count; + for (count = 0; x != 0; ++count) + x &= x - 1; // 消除最後的的 bit 1 + return count; + } +}; +\end{Code} + +\begin{center} +\includegraphics[width=300pt]{counting-bits.png}\\ +\figcaption{counting bits}\label{fig:counting-bits} +\end{center} + +\subsubsection{動劃 + Most Significant Bit} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(n) +// 思路:當前的數字的 bit 總數,跟前一個的 2 次方差數字的 bit 總數有關係 +class Solution { +public: + vector countBits(int num) { + vector result(num+1, 0); + int i = 0; + int b = 1; // 這是 2 次方總數 + + while (b <= num) + { + while (i < b && i + b <= num) + { + // 跟上一個 2 次方差數只有 1 bit 的差距 + result[i+b] = result[i] + 1; + ++i; + } + i = 0; + b <<= 1; + } + + return result; + } +}; +\end{Code} + +\begin{center} +\includegraphics[width=300pt]{counting-bits-002.png}\\ +\figcaption{counting bits 2}\label{fig:counting-bits-002} +\end{center} + +\subsubsection{動劃 + Least Significant Bit} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + vector countBits(int num) { + vector result(num+1, 0); + + for (int i = 1; i <= num; i++) + result[i] = result[i >> 1] + (i & 1); + + return result; + } +}; +\end{Code} + +\subsubsection{動劃 + Last Set Bit} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + vector countBits(int num) { + vector result(num+1, 0); + + // i & (i - 1) 會把最右 (Least Significant Bit) 除去 + for (int i = 1; i <= num; i++) + result[i] = result[i & (i - 1)] + 1; + + return result; + } +}; +\end{Code} + + +\subsection{Single Number} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:single-number} + + +\subsubsection{描述} +Given an array of integers, every element appears twice except for one. Find that single one. + +Note: +Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory? + + +\subsubsection{分析} +異或(XOR),不僅能處理兩次的情況,只要出現偶數次,都可以清零。 + + +\subsubsection{代碼1} +\begin{Code} +// LeetCode, Single Number +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + int singleNumber(vector& nums) { + int x = 0; + for (auto i : nums) { + x ^= i; + } + return x; + } +}; +\end{Code} + + +\subsubsection{代碼2} +\begin{Code} +// LeetCode, Single Number +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + int singleNumber(vector& nums) { + return accumulate(nums.begin(), nums.end(), 0, bit_xor()); + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Single Number II, 見 \S \ref{sec:single-number-ii} +\myenddot + + +\subsection{Single Number II} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:single-number-ii} + + +\subsubsection{描述} +Given an array of integers, every element appears three times except for one. Find that single one. + +Note: +Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory? + + +\subsubsection{分析} +本題和上一題 Single Number,考察的是位運算。 + +方法1:創建一個長度為\fn{sizeof(int)}的數組\fn{count[sizeof(int)]},\fn{count[i]}表示在在$i$位出現的1的次數。如果\fn{count[i]}是3的整數倍,則忽略;否則就把該位取出來組成答案。 + +方法2:用\fn{one}記錄到當前處理的元素為止,二進制1出現“1次”(mod 3 之後的 1)的有哪些二進制位;用\fn{two}記錄到當前計算的變量為止,二進制1出現“2次”(mod 3 之後的 2)的有哪些二進制位。當\fn{one}和\fn{two}中的某一位同時為1時表示該二進制位上1出現了3次,此時需要清零。即\textbf{用二進制模擬三進制運算}。最終\fn{one}記錄的是最終結果。 + +\subsubsection{代碼1} +\begin{Code} +// LeetCode, Single Number II +// 方法1,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + 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 < nums.size(); i++) { + for (int j = 0; j < W; j++) { + count[j] += (nums[i] >> j) & 1; + count[j] %= 3; + } + } + int result = 0; + for (int i = 0; i < W; i++) { + result += (count[i] << i); + } + return result; + } +}; +\end{Code} + + +\subsubsection{代碼2} +\begin{Code} +// LeetCode, Single Number II +// 方法2,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + int singleNumber(vector& nums) { + int one = 0, two = 0, three = 0; + for (auto i : nums) { + two |= (one & i); + one ^= i; + three = ~(one & two); + one &= three; + two &= three; + } + + return one; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Single Number, 見 \S \ref{sec:single-number} +\myenddot + +\subsection{Duplicate Zeros} +\label{sec:duplicate-zeros} + + +\subsubsection{描述} +Given a fixed length array arr of integers, duplicate each occurrence of zero, shifting the remaining elements to the right. + +Note that elements beyond the length of the original array are not written. + +Do the above modifications to the input array in place, do not return anything from your function. + +Example 1: +\begin{Code} +Input: [1,0,2,3,0,4,5,0] +Output: null +Explanation: After calling your function, the input array is modified to: [1,0,0,2,3,0,0,4] +\end{Code} + +Example 2: +\begin{Code} +Input: [1,2,3] +Output: null +Explanation: After calling your function, the input array is modified to: [1,2,3] +\end{Code} + +\subsubsection{分析} +Space Complexity O(1). Find the new last, loop from back and handle the edge case of the last zero without duplicate. + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Duplicate Zeros +// 時間複雜度O(n),空間複雜度O(1) +class solution{ +public: + void duplicateZeros(vector& arr) { + int N = arr.size(); + if (N < 2) return; + + // count for new last + int last = 0; + for (int i = 0; i < N && last < N; i++, last++) { + if (arr[last] == 0) + i++; + if (i == N) { // handle if the last zero need to duplicate + arr[i-1] = arr[last]; + last--; + N--; + } + } + + for (int i = last - 1, right = N - 1; i >= 0 && right >= 0; i--, right--) { + if (arr[i] == 0) { + arr[right--] = 0; + arr[right] = 0; + } + else + arr[right] = arr[i]; + } + } +}; +\end{Code} + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Duplicate Zeros +// 時間複雜度O(n),空間複雜度O(n) +class solution{ +public: + void duplicateZeros(vector& arr) { + int N = arr.size(); + if (N < 2) return; + + // count for zeros + int count = 0; + for (const auto& a : arr) if (a == 0) count++; + + arr.resize(N + count); + + for (int left = N - 1, right = N + count - 1; + left >= 0 && right >= 0; left-- , right--) { + if (arr[left] == 0) { + arr[right--] = 0; + arr[right] = 0; + } + else + arr[right] = arr[left]; + } + + arr.resize(N); + } +}; +\end{Code} + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + +\subsection{Subarray Sum Equals K} +\label{sec:subarray-sum-equals-k} + + +\subsubsection{描述} +Given an array of integers and an integer \textbf{k}, you need to find the total number of continuous subarrays whose sum equals to \textbf{k}. + +\begin{Code} + Example 1: + Input: nums = [1,1,1], k = 2 + Output: 2 +\end{Code} + +\subsubsection{分析} +The idea behind this approach is as follows: If the cumulative sum(repreesnted by sum[i]sum[i] for sum upto i-th index) upto two indices is the same, the sum of the elements lying in between those indices is zero. Extending the same thought further, if the cumulative sum upto two indices, say ii and jj is at a difference of kk i.e. if sum[i] - sum[j] = ksum[i]−sum[j]=k, the sum of elements lying between indices ii and jj is kk. + +Based on these thoughts, we make use of a hashmap mapmap which is used to store the cumulative sum upto all the indices possible along with the number of times the same sum occurs. We store the data in the form: (sum_i, no. of occurences of sum_i)(sum i ,no.ofoccurencesofsum i). We traverse over the array numsnums and keep on finding the cumulative sum. Every time we encounter a new sum, we make a new entry in the hashmap corresponding to that sum. If the same sum occurs again, we increment the count corresponding to that sum in the hashmap. Further, for every sum encountered, we also determine the number of times the sum sum-ksum−k has occured already, since it will determine the number of times a subarray with sum kk has occured upto the current index. We increment the countcount by the same amount. + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Duplicate Zeros +// 時間複雜度O(n),空間複雜度O(n) +class solution{ +public: + int subarraySum(vector& nums, int k) { + // 用 hash map 去存放累加總數和出現的次數 + // 設 sum[i] 為累加總數由 0 到 i + // 若 sum[j] - sum[i] = k, 那麼由 i 到 j 之間便會有一個 k 總的子列 + + unordered_map cache; // key: cumulative sum, value: count + cache[0] = 1; // 一定有 sum = 0 + + int sum = 0; + int count = 0; + for (const auto& n : nums) + { + sum += n; + + auto it = cache.find(sum - k); + if (it != cache.end()) + count += it->second; + + cache[sum]++; // 加到 hash map + } + + return count; + } +}; +\end{Code} + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + +\subsection{Candy Crush} +\label{sec:candy-crush} + +\subsubsection{描述} +This question is about implementing a basic elimination algorithm for Candy Crush. + +Given a 2D integer array board representing the grid of candy, different positive integers board[i][j] represent different types of candies. A value of board[i][j] = 0 represents that the cell at position (i, j) is empty. The given board represents the state of the game following the player's move. Now, you need to restore the board to a stable state by crushing candies according to the following rules: + +\begindot +\item If three or more candies of the same type are adjacent vertically or horizontally, "crush" them all at the same time - these positions become empty. +\item After crushing all candies simultaneously, if an empty space on the board has candies on top of itself, then these candies will drop until they hit a candy or bottom at the same time. (No new candies will drop outside the top boundary.) +\item After the above steps, there may exist more candies that can be crushed. If so, you need to repeat the above steps. +\item If there does not exist more candies that can be crushed (ie. the board is stable), then return the current board. +\myenddot + +You need to perform the above rules until the board becomes stable, then return the current board. + + +Example: + +\textbf{Input}: + [[110,5,112,113,114],[210,211,5,213,214],[310,311,3,313,314],[410,411,412,5,414] + ,[5,1,512,3,3],[610,4,1,613,614],[710,1,2,713,714],[810,1,2,1,1],[1,1,2,2,2],[4,1,4,4,1014]] + +\textbf{Output}: + [[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[110,0,0,0,114],[210,0,0,0,214],[310,0,0,113,314] + ,[410,0,0,213,414],[610,211,112,313,614],[710,311,412,613,714],[810,411,512,713,1014]] + + + +\begin{center} +\includegraphics[width=300pt]{candy-crush-example-2.png}\\ +\figcaption{candy crush}\label{fig:candy-crush-example} +\end{center} + +\subsubsection{分析} + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Candy Crush +// 時間複雜度O(n),空間複雜度O(n) +class solution{ +public: + vector> candyCrush(vector>& board) { + int M = board.size(); + int N = board[0].size(); + + bool isNeedScan = true; + while (isNeedScan) + { + // check row by row + HandleRowByRow(board, M, N); + // check col by col + HandleColByCol(board, M, N); + // dropping action + isNeedScan = DoDropAction(board, M, N); + } + + return board; + } +private: + void PrintBoard(vector>& board) + { + for (const auto& r : board) + { + for (const auto& c : r) + cout << c << "\t"; + cout << endl; + } + } + bool DoDropAction(vector>& board, int M, int N) + { + bool isDoDrop = false; + // do dropping col by col + for (int j = 0; j < N; j++) + { + int lowestNeg = M-1; + while (lowestNeg >= 0 && board[lowestNeg][j] >= 0) lowestNeg--; + if (lowestNeg >= 0) isDoDrop = true; + + // update next positive position + int nextPositive = lowestNeg; + while (nextPositive >= 0) + { + while (nextPositive >= 0 && board[nextPositive][j] < 0) nextPositive--; + + // move the data + for (; nextPositive >= 0 && board[nextPositive][j] >= 0; nextPositive--) + board[lowestNeg--][j] = board[nextPositive][j]; + } + for (; lowestNeg >= 0; lowestNeg--) + board[lowestNeg][j] = 0; + } + + return isDoDrop; + } + void HandleRowByRow(vector>& board, int M, int N) + { + for (int i = 0; i < M; i++) + { + for (int j = 0; j + 2 < N; j++) + { + int cur = abs(board[i][j]); + bool found = false; + while (j + 2 < N && cur == abs(board[i][j+1]) && cur == abs(board[i][j+2])) + { + found = true; + board[i][j] = -cur; + j++; + } + if (found) + { + board[i][j] = -cur; + board[i][++j] = -cur; + } + } + } + } + void HandleColByCol(vector>& board, int M, int N) + { + for (int j = 0; j < N; j++) + { + for (int i = 0; i + 2 < M; i++) + { + int cur = abs(board[i][j]); + bool found = false; + while (i + 2 < M && cur == abs(board[i+1][j]) && cur == abs(board[i+2][j])) + { + found = true; + board[i][j] = -cur; + i++; + } + if (found) + { + board[i][j] = -cur; + board[++i][j] = -cur; + } + } + } + } +}; +\end{Code} + +\subsubsection{相關題目} +\begindot +\item Candy Crush II, 見 \S \ref{sec:candy-crush-ii} +\myenddot + +\subsection{Intersection of Two Arrays} +\label{sec:intersection-of-two-arrays} + + +\subsubsection{描述} +Given two arrays, write a function to compute their intersection. + +Example 1: +\begin{Code} +Input: nums1 = [1,2,2,1], nums2 = [2,2] +Output: [2] +\end{Code} + +Example 2: +\begin{Code} +Input: nums1 = [4,9,5], nums2 = [9,4,9,8,4] +Output: [9,4] +\end{Code} + +Note: +\begindot +\item Each element in the result must be unique. +\item The result can be in any order. +\myenddot + +\subsubsection{分析} +1. Use std method\newline + +2. Use hash map\newline + +3. Sort then merge\newline + +\subsubsection{代碼 - STD method} +\begin{Code} +// LeetCode +// 時間複雜度O(nlogn),空間複雜度O(n) +class solution{ +public: + vector intersection(vector& nums1, vector& nums2) { + // 先順序 + sort(nums1.begin(), nums1.end()); + sort(nums2.begin(), nums2.end()); + vector result(nums1.size() + nums2.size()); + + // 利用 std + vector::iterator it = set_intersection(nums1.begin(), nums1.end() + , nums2.begin(), nums2.end() + , result.begin()); + // 刪去多餘的空間 + result.resize(it - result.begin()); + // 刪去重覆 + result.erase(unique(result.begin(), result.end()), result.end()); + + return result; + } +}; +\end{Code} + +\subsubsection{代碼 - Hash map} +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(n) +class solution{ +public: + vector intersection(vector& nums1, vector& nums2) { + // 使用 hash map + unordered_set map1(nums1.begin(), nums1.end()); + unordered_set map2(nums2.begin(), nums2.end()); + vector result; result.reserve(map1.size() + map2.size()); + + for (const auto& e1 : map1) { + // 在兩個 hash map 中出現,便是答案 + auto it = map2.find(e1); + if (it != map2.end()) result.push_back(e1); + } + + return result; + } +}; +\end{Code} + +\subsubsection{代碼 - Merge vector} +\begin{Code} +// LeetCode +// 時間複雜度O(nlogn),空間複雜度O(n) +class solution{ +public: + vector intersection(vector& nums1, vector& nums2) { + // 先順序 + sort(nums1.begin(), nums1.end()); + sort(nums2.begin(), nums2.end()); + vector result; result.reserve(nums1.size() + nums2.size()); + + // merge 兩支 vector + for (int p1 = 0, p2 = 0; p1 < nums1.size() && p2 < nums2.size()) { + if (nums1[p1] < nums2[p2]) + p1++; + else if (nums1[p1] > nums2[p2]) + p2++; + else { + result.push_back(nums1[p1]); + p1++; + p2++; + } + } + // 除去重覆 + result.erase(unique(result.begin(), result.end()), result.end()); + + return result; + } +}; +\end{Code} + + +\subsection{Find the Duplicate Number} +\label{sec:find-the-duplicate-number} + + +\subsubsection{描述} +Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one. + + +Example 1: +\begin{Code} +Input: [1,3,4,2,2] +Output: 2 +\end{Code} + +Example 2: +\begin{Code} +Input: [3,1,3,4,2] +Output: 3 +\end{Code} + + +\subsubsection{分析} +Nil + +\subsubsection{代碼 - Cycle - bucket sort} +\begin{Code} +// LeetCode +// 時間複雜度O(nlogn),空間複雜度O(n) +class solution{ +public: + int findDuplicate(vector& nums) { + // 這個跟 cycle linked list 差不多 + // 重覆的 element 會形成 cycle + int fast = nums[0]; + int slow = nums[0]; + + do { + fast = nums[nums[fast]]; + slow = nums[slow]; + } while (fast != slow); + + int slow2 = nums[0]; + while (slow2 != slow) { + slow2 = nums[slow2]; + slow = nums[slow]; + } + + return slow2; + } +}; +\end{Code} + + +\subsection{Find K-th Smallest Pair Distance} +\label{sec:kth-smallest-pair-distance} + + +\subsubsection{描述} +Given an integer array, return the k-th smallest distance among all the pairs. The distance of a pair (A, B) is defined as the absolute difference between A and B. + +Example 1: +\begin{Code} +Input: +nums = [1,3,1] +k = 1 +Output: 0 +Explanation: +Here are all the pairs: +(1,3) -> 2 +(1,1) -> 0 +(3,1) -> 2 +Then the 1st smallest distance pair is (1,1), and its distance is 0. +\end{Code} + + +\begindot +\item 2 <= len(nums) <= 10000. +\item 0 <= nums[i] < 1000000. +\item 1 <= k <= len(nums) * (len(nums) - 1) / 2. +\myenddot + +\subsubsection{分析} +估一個 distance 值,數一數有多少個 distance 值係細過此一估值。然後以二分法原則去調整估值的大細。 + +\subsubsection{代碼} +\begin{Code} +// LeetCode +// 時間複雜度O(nlogn),空間複雜度O(n) +class Solution { +public: + int smallestDistancePair(vector& nums, int k) { + if (nums.size() == 0) return 0; + sort(nums.begin(), nums.end()); + + int lo = 0; + int hi = nums[nums.size() - 1] - nums[0]; + while (lo < hi) { + int mi = lo + (hi - lo) / 2; // 得出估值 + int count = 0, left = 0; + for (int right = 0; right < nums.size(); ++right) { + while (nums[right] - nums[left] > mi) left++; + count += right - left; + } + //count = number of pairs with distance <= mi + if (count >= k) hi = mi; // 二分法來調整估值大細 + else lo = mi + 1; + } + return lo; + } +}; +\end{Code} + +\subsection{Find Pivot Index} +\label{sec:find-pivot-index} + +\subsubsection{描述} +Given an array of integers nums, write a method that returns the "pivot" index of this array. + +We define the pivot index as the index where the sum of all the numbers to the left of the index is equal to the sum of all the numbers to the right of the index. + +If no such index exists, we should return -1. If there are multiple pivot indexes, you should return the left-most pivot index. + +Example 1: +\begin{Code} +Input: nums = [1,7,3,6,5,6] +Output: 3 +Explanation: +The sum of the numbers to the left of index 3 (nums[3] = 6) is equal to the sum of numbers to the right of index 3. +Also, 3 is the first index where this occurs. +\end{Code} + +Example 2: +\begin{Code} +Input: nums = [1,2,3] +Output: -1 +Explanation: +There is no index that satisfies the conditions in the problem statement. +\end{Code} + +\subsubsection{分析} +先計算總數,往右走,每走一步都會跟左邊總加值對比。 + +\subsubsection{代碼} +\begin{Code} +// LeetCode +// 時間複雜度O(),空間複雜度O() +class Solution { +public: + int pivotIndex(vector& nums) { + int sum = 0, leftsum = 0; + for (int x: nums) sum += x; + for (int i = 0; i < nums.size(); ++i) { + if (leftsum == sum - leftsum - nums[i]) return i; + leftsum += nums[i]; + } + return -1; + } +}; +\end{Code} + + +\subsection{Largest Number At Least Twice of Others} +\label{sec:largest-number-at-least-twice-of-others} + +\subsubsection{描述} +In a given integer array nums, there is always exactly one largest element. + +Find whether the largest element in the array is at least twice as much as every other number in the array. + +If it is, return the index of the largest element, otherwise return -1. + +Example 1: +\begin{Code} +Input: nums = [3, 6, 1, 0] +Output: 1 +Explanation: 6 is the largest integer, and for every other number in the array x, +6 is more than twice as big as x. The index of value 6 is 1, so we return 1. +\end{Code} + +Example 2: +\begin{Code} +Input: nums = [1, 2, 3, 4] +Output: -1 +Explanation: 4 isn't at least as big as twice the value of 3, so we return -1. +\end{Code} + +Note: +\begindot +\item nums will have a length in the range [1, 50]. +\item Every nums[i] will be an integer in the range [0, 99]. +\myenddot + +\subsubsection{分析} +first pass 先求得最大值,second pass 找尋第二大的一倍數值,若過大,回值 -1 + +\subsubsection{代碼} +\begin{Code} +// LeetCode +// 時間複雜度O(),空間複雜度O() +class Solution { +public: + int dominantIndex(vector& nums) { + int N = nums.size(); + if (N == 0) return -1; + + int firstMax = INT_MIN; + int maxIndex = -1; + + for (int i = 0; i < N; i++) { + if (nums[i] > firstMax) { + firstMax = nums[i]; + maxIndex = i; + } + } + for (int i = 0; i < N; i++) { + if (firstMax > nums[i]) + if (firstMax < 2 * nums[i]) return -1; + } + + return maxIndex; + } +}; +\end{Code} + +\subsection{Array Partition I} +\label{sec:array-partition-i} + +\subsubsection{描述} +Given an array of 2n integers, your task is to group these integers into n pairs of integer, say (a1, b1), (a2, b2), ..., (an, bn) which makes sum of min(ai, bi) for all i from 1 to n as large as possible. + +Example 1: +\begin{Code} +Input: [1,4,3,2] + +Output: 4 +Explanation: n is 2, and the maximum sum of pairs is 4 = min(1, 2) + min(3, 4). +\end{Code} + +Note: +\begindot +\item n is a positive integer, which is in the range of [1, 10000]. +\item All the integers in the array will be in the range of [-10000, 10000]. +\myenddot + +\subsubsection{分析} +Nil + +\subsubsection{代碼} +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + int arrayPairSum(vector& nums) { + vector arr(20001, 0); + int shift = 10000; + for (const int& num : nums) + arr[num + shift]++; + int d = 0, sum = 0; + for (int i = -10000; i <= 10000; i++) + { + sum += (arr[i + shift] + 1 - d) / 2 * i; + d = (2 + arr[i + shift] - d) % 2; // 在前邊加 2,用來防止負數的出現。 + } + return sum; + } +}; +\end{Code} + +\subsubsection{代碼} +\begin{Code} +// LeetCode +// 時間複雜度O(nlogn),空間複雜度O(1) +class Solution { +public: + int arrayPairSum(vector& nums) { + sort(nums.begin(), nums.end()); + int sum = 0; + for (int i = 0; i < nums.size(); i += 2) + sum += nums[i]; + + return sum; + } +}; +\end{Code} + + +\subsection{Rotate Array} +\label{sec:rotate-array} + +\subsubsection{描述} +Given an array, rotate the array to the right by k steps, where k is non-negative. + +Example 1: +\begin{Code} +Input: nums = [1,2,3,4,5,6,7], k = 3 +Output: [5,6,7,1,2,3,4] +Explanation: +rotate 1 steps to the right: [7,1,2,3,4,5,6] +rotate 2 steps to the right: [6,7,1,2,3,4,5] +rotate 3 steps to the right: [5,6,7,1,2,3,4] +\end{Code} + +Example 2: +\begin{Code} +Input: nums = [-1,-100,3,99], k = 2 +Output: [3,99,-1,-100] +Explanation: +rotate 1 steps to the right: [99,-1,-100,3] +rotate 2 steps to the right: [3,99,-1,-100] +\end{Code} + +\subsubsection{額外的空間} +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + void rotate(vector& nums, int k) { + vector a(nums.size()); + + for (int i = 0; i < nums.size(); i++) + a[(i + k) % nums.size()] = nums[i]; + + nums = a; + } +}; +\end{Code} + +\subsubsection{循環取代法} +\begin{center} +\includegraphics[width=300pt]{cyclic-replacements.png}\\ +\figcaption{cyclic replacements}\label{fig:cyclic-replacements} +\end{center} + +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + void rotate(vector& nums, int k) { + k = k % nums.size(); + int count = 0; + for (int start = 0; count < nums.size(); start++) { + int current = start; + int prev = nums[start]; + do { + int next = (current + k) % nums.size(); + int temp = nums[next]; + nums[next] = prev; + prev = temp; + current = next; + count++; + } while (start != current); + } + } +}; +\end{Code} + +\subsubsection{reverse} +\begin{Code} +Original List : 1 2 3 4 5 6 7 +After reversing all numbers : 7 6 5 4 3 2 1 +After reversing first k numbers : 5 6 7 4 3 2 1 +After revering last n-k numbers : 5 6 7 1 2 3 4 --> Result +\end{Code} + +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + void rotate(vector& nums, int k) { + k %= nums.size(); + reverse(nums, 0, nums.size() - 1); + reverse(nums, 0, k - 1); + reverse(nums, k, nums.size() - 1); + } +private: + void reverse(vector& nums, int start, int end) { + while (start < end) { + int temp = nums[start]; + nums[start] = nums[end]; + nums[end] = temp; + start++; + end--; + } + } +}; +\end{Code} + + +\subsection{Reverse Words in a String} +\label{sec:reverse-words-in-a-string} + +\subsubsection{描述} +Given an input string, reverse the string word by word. + +Example 1: +\begin{Code} +Input: "the sky is blue" +Output: "blue is sky the" +\end{Code} + +Example 2: +\begin{Code} +Input: " hello world! " +Output: "world! hello" +Explanation: Your reversed string should not contain leading or trailing spaces. +\end{Code} + +Example 3: +\begin{Code} +Input: "a good example" +Output: "example good a" +Explanation: You need to reduce multiple spaces between two words to a single space in the reversed string. +\end{Code} + +Note: +\begindot +\item A word is defined as a sequence of non-space characters. +\item Input string may contain leading or trailing spaces. However, + your reversed string should not contain leading or trailing spaces. +\item You need to reduce multiple spaces between two words to a single space in the reversed string. +\myenddot + +\subsubsection{分析} +Reverse + +\subsubsection{代碼} +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + string reverseWords(string s) { + if (s.size() == 0) return s; + // 整段 reverse + MyReverse(s.begin(), s.end()); + // 每個字 reverse + auto cur = s.begin(); + auto next = cur; + + while (next < s.end()) + { + // 尋找字的開端 + cur = find_if(cur, s.end(), [&](const char& c) { return (c != ' '); }); + // 尋找空白的開端 + next = find_if(cur, s.end(), [&](const char& c) { return (c == ' '); }); + + MyReverse(cur, next); + + cur = next; + } + + // 對應的 std 方法 + // s.erase(s.find_last_not_of(' ')+1); // 移除領先空格 + // s.erase(0, s.find_first_not_of(' ')); // 移除尾隨空格 + s.erase(InPlaceRemoveSpace(s.begin(), s.end()), s.end()); + return s; + } +private: + template + RandIT InPlaceRemoveSpace(RandIT first, RandIT last) + { + bool isFirst = true; + // 移除領先空格,尾隨空格,中間多餘空格 + auto cur = first; + while (first < last) + { + // 找尋下一個字 + first = find_if(first, last, [&](const char& c) { return (c != ' '); }); + if (first >= last) break; + + if (!isFirst) *cur++ = ' '; // 只有當不是第一個文字,會加入空格 + while (first < last && *first != ' ') *cur++ = *first++; // 補上文字,直至空格出現 + if (first >= last) break; + isFirst = false; + } + + return cur; + } + template + void MyReverse(RandIT first, RandIT last) + { + last--; + while (first < last) + { + // 進行 swap + auto tmp = *last; + *last-- = *first; + *first++ = tmp; + } + } +}; +\end{Code} + +\subsection{Happy Number} +\label{sec:happy-number} + +\subsubsection{描述} +Write an algorithm to determine if a number n is "happy". + +A happy number is a number defined by the following process: Starting with any positive integer, replace the number by the sum of the squares of its digits, and repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle which does not include 1. Those numbers for which this process ends in 1 are happy numbers. + +Return True if n is a happy number, and False if not. + +Example: +\begin{Code} +Input: 19 +Output: true +Explanation: +1^2 + 9^2 = 82 +8^2 + 2^2 = 68 +6^2 + 8^2 = 100 +1^2 + 0^2 + 0^2 = 1 +\end{Code} + +\subsubsection{利用循環特性} +\begin{center} +\includegraphics[width=300pt]{happy-number-001.png}\\ +\figcaption{Why have loop}\label{fig:happy-number-001} +\end{center} + +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + bool isHappy(int n) { + unordered_set seen; + + // 由例子中可以看到,若果計不到 1 ,便會重覆 + // 並且不會愈計愈大,所以可以用這個方法 + while (n != 1 && seen.find(n) == seen.end()) + { + seen.insert(n); + n = GetNext(n); + } + + return n == 1; + } +private: + int GetNext(int n) + { + int result = 0; + while (n) + { + int d = n % 10; + result += d * d; + n /= 10; + } + + return result; + } +}; +\end{Code} + +\subsubsection{快慢雙指針法} +\begin{Code} +// LeetCode +// 時間複雜度O(logN),空間複雜度O(1) +class Solution { +public: + bool isHappy(int n) { + int slow = n; + int fast = GetNext(slow); + + while (fast != 1 && fast != slow) + { + slow = GetNext(slow); + fast = GetNext(GetNext(fast)); + } + + return fast == 1; + } +private: + int GetNext(int n) + { + int result = 0; + while (n) + { + int d = n % 10; + result += d * d; + n /= 10; + } + + return result; + } +}; +\end{Code} + +\subsubsection{數學規則} +\begin{Code} +// LeetCode +// 時間複雜度O(logN),空間複雜度O(1) +class Solution { +public: + bool isHappy(int n) { + // 通過觀察,只有以下路徑會循環 + // 4 -> 16 -> 37 -> 58 -> 89 -> 145 -> 42 -> 20 -> 4 + while (n != 1 && n != 4) + n = GetNext(n); + + return n == 1; + } +private: + int GetNext(int n) + { + int result = 0; + while (n) + { + int d = n % 10; + result += d * d; + n /= 10; + } + + return result; + } +}; +\end{Code} + +\subsection{Isomorphic Strings} +\label{sec:isomorphic-strings} + +\subsubsection{描述} +Given two strings s and t, determine if they are isomorphic. + +Two strings are isomorphic if the characters in s can be replaced to get t. + +All occurrences of a character must be replaced with another character while preserving the order of characters. No two characters may map to the same character but a character may map to itself. + +Example 1: +\begin{Code} +Input: s = "egg", t = "add" +Output: true +\end{Code} + +Example 2: +\begin{Code} +Input: s = "foo", t = "bar" +Output: false +\end{Code} + +Example 3: +\begin{Code} +Input: s = "paper", t = "title" +Output: true +\end{Code} + +\subsubsection{字母 hash} +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + bool isIsomorphic(string s, string t) { + vector v1(256, 0); + vector v2(256, 0); + + for (size_t i = 0; i < s.size(); i++) + { + if (v1[s[i]] != 0 && v1[s[i]] != t[i]) return false; + if (v2[t[i]] != 0 && v2[t[i]] != s[i]) return false; + + v1[s[i]] = t[i]; + v2[t[i]] = s[i]; + } + + return true; + } +}; +\end{Code} + +\subsection{Group Shifted Strings} +\label{sec:group-shifted-strings} + +\subsubsection{描述} +Given a string, we can "shift" each of its letter to its successive letter, for example: "abc" -> "bcd". We can keep "shifting" which forms the sequence: + +\begin{Code} +"abc" -> "bcd" -> ... -> "xyz" +\end{Code} + +Given a list of non-empty strings which contains only lowercase alphabets, group all strings that belong to the same shifting sequence. + +Example: +\begin{Code} +Input: ["abc", "bcd", "acef", "xyz", "az", "ba", "a", "z"], +Output: +[ + ["abc","bcd","xyz"], + ["az","ba"], + ["acef"], + ["a","z"] +] +\end{Code} + +\subsubsection{暴力 shift} +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + vector> groupStrings(vector& strings) { + unordered_map> hashMap; + for(auto &str : strings) { + string temp = str; + convertStringToKey(temp); + hashMap[temp].push_back(str); + } + + vector> answer; + for(auto it = hashMap.begin(); it != hashMap.end(); ++it) { + answer.push_back(it->second); + } + return answer; + } +private: + void convertStringToKey(string &str) { + while(str[0] != 'a') { + for(int i = 0; i < str.size(); ++i) { + str[i] += 1; + if(str[i] > 'z') { + str[i] = 'a'; + } + } + } + } +}; +\end{Code} + +\subsubsection{Counting shift} +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + vector> groupStrings(vector& strings) { + unordered_map> hashMap; + for(auto &str : strings) { + string temp = str; + convertStringToKey(temp); + hashMap[temp].push_back(str); + } + + vector> answer; + for(auto it = hashMap.begin(); it != hashMap.end(); ++it) { + answer.push_back(it->second); + } + return answer; + } +private: + void convertStringToKey(string &str) { + if (str.size() == 0) return; + + if (str[0] != 'a') + { + int diffFromA = str[0] - 'a'; + + for (size_t i = 0; i < str.size(); ++i) + { + if ((str[i] - 'a') < diffFromA) + str[i] = (26 + str[i] - diffFromA); + else + str[i] = str[i] - diffFromA; + } + } + } +}; +\end{Code} diff --git a/C++/chapLinearList.tex b/C++/chapLinearList.tex deleted file mode 100644 index bbc0ec1c..00000000 --- a/C++/chapLinearList.tex +++ /dev/null @@ -1,2923 +0,0 @@ -\chapter{线性表} -这类题目考察线性表的操作,例如,数组,单链表,双向链表等。 - - -\section{数组} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -\subsection{Remove Duplicates from Sorted Array} -\label{sec:remove-duplicates-from-sorted-array} - - -\subsubsection{描述} -Given a sorted array, remove the duplicates in place such that each element appear only once and return the new length. - -Do not allocate extra space for another array, you must do this in place with constant memory. - -For example, Given input array \code{A = [1,1,2]}, - -Your function should return length = 2, and A is now \code{[1,2]}. - - -\subsubsection{分析} -无 - - -\subsubsection{代码1} -\begin{Code} -// LeetCode, Remove Duplicates from Sorted Array -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - int removeDuplicates(vector& nums) { - if (nums.empty()) return 0; - - int index = 0; - for (int i = 1; i < nums.size(); i++) { - if (nums[index] != nums[i]) - nums[++index] = nums[i]; - } - return index + 1; - } -}; -\end{Code} - - -\subsubsection{代码2} -\begin{Code} -// LeetCode, Remove Duplicates from Sorted Array -// 使用STL,时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - int removeDuplicates(vector& nums) { - return distance(nums.begin(), unique(nums.begin(), nums.end())); - } -}; -\end{Code} - - -\subsubsection{代码3} -\begin{Code} -// LeetCode, Remove Duplicates from Sorted Array -// 使用STL,时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - int removeDuplicates(vector& nums) { - return distance(nums.begin(), removeDuplicates(nums.begin(), nums.end(), nums.begin())); - } - - template - OutIt removeDuplicates(InIt first, InIt last, OutIt output) { - while (first != last) { - *output++ = *first; - first = upper_bound(first, last, *first); - } - - return output; - } -}; -\end{Code} - - -\subsubsection{相关题目} - -\begindot -\item Remove Duplicates from Sorted Array II,见 \S \ref{sec:remove-duplicates-from-sorted-array-ii} -\myenddot - - -\subsection{Remove Duplicates from Sorted Array II} -\label{sec:remove-duplicates-from-sorted-array-ii} - - -\subsubsection{描述} -Follow up for "Remove Duplicates": -What if duplicates are allowed at most twice? - -For example, -Given sorted array \code{A = [1,1,1,2,2,3]}, - -Your function should return length = 5, and A is now \code{[1,1,2,2,3]} - - -\subsubsection{分析} -加一个变量记录一下元素出现的次数即可。这题因为是已经排序的数组,所以一个变量即可解决。如果是没有排序的数组,则需要引入一个hashmap来记录出现次数。 - - -\subsubsection{代码1} -\begin{Code} -// LeetCode, Remove Duplicates from Sorted Array II -// 时间复杂度O(n),空间复杂度O(1) -// @author hex108 (https://github.com/hex108) -class Solution { -public: - int removeDuplicates(vector& nums) { - if (nums.size() <= 2) return nums.size(); - - int index = 2; - for (int i = 2; i < nums.size(); i++){ - if (nums[i] != nums[index - 2]) - nums[index++] = nums[i]; - } - - return index; - } -}; -\end{Code} - - -\subsubsection{代码2} -下面是一个更简洁的版本。上面的代码略长,不过扩展性好一些,例如将\fn{occur < 2}改为\fn{occur < 3},就变成了允许重复最多3次。 -\begin{Code} -// LeetCode, Remove Duplicates from Sorted Array II -// @author 虞航仲 (http://weibo.com/u/1666779725) -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - 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 && nums[i] == nums[i - 1] && nums[i] == nums[i + 1]) - continue; - - nums[index++] = nums[i]; - } - return index; - } -}; -\end{Code} - - -\subsubsection{相关题目} - -\begindot -\item Remove Duplicates from Sorted Array,见 \S \ref{sec:remove-duplicates-from-sorted-array} -\myenddot - - -\subsection{Search in Rotated Sorted Array} -\label{sec:search-in-rotated-sorted-array} - - -\subsubsection{描述} -Suppose a sorted array is rotated at some pivot unknown to you beforehand. - -(i.e., \code{0 1 2 4 5 6 7} might become \code{4 5 6 7 0 1 2}). - -You are given a target value to search. If found in the array return its index, otherwise return -1. - -You may assume no duplicate exists in the array. - - -\subsubsection{分析} -二分查找,难度主要在于左右边界的确定。 - - -\subsubsection{代码} -\begin{Code} -// LeetCode, Search in Rotated Sorted Array -// 时间复杂度O(log n),空间复杂度O(1) -class Solution { -public: - int search(const vector& nums, int target) { - int first = 0, last = nums.size(); - while (first != last) { - const int mid = first + (last - first) / 2; - if (nums[mid] == target) - return mid; - if (nums[first] <= nums[mid]) { - if (nums[first] <= target && target < nums[mid]) - last = mid; - else - first = mid + 1; - } else { - if (nums[mid] < target && target <= nums[last-1]) - first = mid + 1; - else - last = mid; - } - } - return -1; - } -}; -\end{Code} - - -\subsubsection{相关题目} - -\begindot -\item Search in Rotated Sorted Array II,见 \S \ref{sec:search-in-rotated-sorted-array-ii} -\myenddot - - -\subsection{Search in Rotated Sorted Array II} -\label{sec:search-in-rotated-sorted-array-ii} - - -\subsubsection{描述} -Follow up for "Search in Rotated Sorted Array": What if \emph{duplicates} are allowed? - -Would this affect the run-time complexity? How and why? - -Write a function to determine if a given target is in the array. - - -\subsubsection{分析} -允许重复元素,则上一题中如果\fn{A[m]>=A[l]},那么\fn{[l,m]}为递增序列的假设就不能成立了,比如\code{[1,3,1,1,1]}。 - -如果\fn{A[m]>=A[l]}不能确定递增,那就把它拆分成两个条件: -\begindot -\item 若\fn{A[m]>A[l]},则区间\fn{[l,m]}一定递增 -\item 若\fn{A[m]==A[l]} 确定不了,那就\fn{l++},往下看一步即可。 -\myenddot - -\subsubsection{代码} -\begin{Code} -// LeetCode, Search in Rotated Sorted Array II -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - bool search(const vector& nums, int target) { - int first = 0, last = nums.size(); - while (first != last) { - const int mid = first + (last - first) / 2; - if (nums[mid] == target) - return true; - if (nums[first] < nums[mid]) { - if (nums[first] <= target && target < nums[mid]) - last = mid; - else - first = mid + 1; - } else if (nums[first] > nums[mid]) { - if (nums[mid] < target && target <= nums[last-1]) - first = mid + 1; - else - last = mid; - } else - //skip duplicate one - first++; - } - return false; - } -}; -\end{Code} - - -\subsubsection{相关题目} - -\begindot -\item Search in Rotated Sorted Array,见 \S \ref{sec:search-in-rotated-sorted-array} -\myenddot - - -\subsection{Median of Two Sorted Arrays} -\label{sec:median-of-two-sorted-arrays} - - -\subsubsection{描述} -There are two sorted arrays A and B of size m and n respectively. Find the median of the two sorted arrays. The overall run time complexity should be $O(\log (m+n))$. - - -\subsubsection{分析} -这是一道非常经典的题。这题更通用的形式是,给定两个已经排序好的数组,找到两者所有元素中第$k$大的元素。 - -$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$入手。如果我们每次都能够删除一个一定在第$k$大元素之前的元素,那么我们需要进行$k$次。但是如果每次我们都删除一半呢?由于A和B都是有序的,我们应该充分利用这里面的信息,类似于二分查找,也是充分利用了“有序”。 - -假设A和B的元素个数都大于$k/2$,我们将A的第$k/2$个元素(即\fn{A[k/2-1]})和B的第$k/2$个元素(即\fn{B[k/2-1]})进行比较,有以下三种情况(为了简化这里先假设$k$为偶数,所得到的结论对于$k$是奇数也是成立的): -\begindot -\item \fn{A[k/2-1] == B[k/2-1]} -\item \fn{A[k/2-1] > B[k/2-1]} -\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$大元素。留给读者证明。 - -因此,我们可以放心的删除A数组的这$k/2$个元素。同理,当\fn{A[k/2-1] > B[k/2-1]}时,可以删除B数组的$k/2$个元素。 - -当\fn{A[k/2-1] == B[k/2-1]}时,说明找到了第$k$大的元素,直接返回\fn{A[k/2-1]}或\fn{B[k/2-1]}即可。 - -因此,我们可以写一个递归函数。那么函数什么时候应该终止呢? -\begindot -\item 当A或B是空时,直接返回\fn{B[k-1]}或\fn{A[k-1]}; -\item 当\fn{k=1}是,返回\fn{min(A[0], B[0])}; -\item 当\fn{A[k/2-1] == B[k/2-1]}时,返回\fn{A[k/2-1]}或\fn{B[k/2-1]} -\myenddot - - -\subsubsection{代码} -\begin{Code} -// LeetCode, Median of Two Sorted Arrays -// 时间复杂度O(log(m+n)),空间复杂度O(log(m+n)) -class Solution { -public: - 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.begin(), m, B.begin(), n, total / 2 + 1); - else - 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(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, *B); - - //divide k into two parts - int ia = min(k / 2, m), ib = k - ia; - 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)) - return find_kth(A, m, B + ib, n - ib, k - ib); - else - return A[ia - 1]; - } -}; -\end{Code} - - -\subsubsection{相关题目} - -\begindot -\item 无 -\myenddot - - -\subsection{Longest Consecutive Sequence} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:longest-consecutive-sequence} - - -\subsubsection{描述} -Given an unsorted array of integers, find the length of the longest consecutive elements sequence. - -For example, -Given \code{[100, 4, 200, 1, 3, 2]}, -The longest consecutive elements sequence is \code{[1, 2, 3, 4]}. Return its length: 4. - -Your algorithm should run in $O(n)$ complexity. - - -\subsubsection{分析} -如果允许$O(n \log n)$的复杂度,那么可以先排序,可是本题要求$O(n)$。 - -由于序列里的元素是无序的,又要求$O(n)$,首先要想到用哈希表。 - -用一个哈希表 \fn{unordered_map used}记录每个元素是否使用,对每个元素,以该元素为中心,往左右扩张,直到不连续为止,记录下最长的长度。 - - -\subsubsection{代码} -\begin{Code} -// Leet Code, Longest Consecutive Sequence -// 时间复杂度O(n),空间复杂度O(n) -class Solution { -public: - int longestConsecutive(const vector &nums) { - unordered_map used; - - for (auto i : nums) used[i] = false; - - int longest = 0; - - for (auto i : nums) { - if (used[i]) continue; - - int length = 1; - - used[i] = true; - - for (int j = i + 1; used.find(j) != used.end(); ++j) { - used[j] = true; - ++length; - } - - for (int j = i - 1; used.find(j) != used.end(); --j) { - used[j] = true; - ++length; - } - - longest = max(longest, length); - } - - return longest; - } -}; -\end{Code} - -\subsubsection{分析2} -第一直觉是个聚类的操作,应该有union,find的操作.连续序列可以用两端和长度来表示. -本来用两端就可以表示,但考虑到查询的需求,将两端分别暴露出来.用\fn{unordered_map map}来 -存储.原始思路来自于\url{http://discuss.leetcode.com/questions/1070/longest-consecutive-sequence} - -\subsubsection{代码} - -\begin{Code} -// Leet Code, Longest Consecutive Sequence -// 时间复杂度O(n),空间复杂度O(n) -// Author: @advancedxy -class Solution { -public: - int longestConsecutive(vector &nums) { - unordered_map map; - int size = nums.size(); - int l = 1; - for (int i = 0; i < size; 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(nums[i] + 1) != map.end()) { - l = max(l, mergeCluster(map, nums[i], nums[i] + 1)); - } - } - return size == 0 ? 0 : l; - } - -private: - int mergeCluster(unordered_map &map, int left, int right) { - int upper = right + map[right] - 1; - int lower = left - map[left] + 1; - int length = upper - lower + 1; - map[upper] = length; - map[lower] = length; - return length; - } -}; -\end{Code} - -\subsubsection{相关题目} -\begindot -\item 无 -\myenddot - - -\subsection{Two Sum} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:Two-sum} - - -\subsubsection{描述} -Given an array of integers, find two numbers such that they add up to a specific target number. - -The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based. - -You may assume that each input would have exactly one solution. - -Input: \code{numbers=\{2, 7, 11, 15\}, target=9} - -Output: \code{index1=1, index2=2} - - -\subsubsection{分析} -方法1:暴力,复杂度$O(n^2)$,会超时 - -方法2:hash。用一个哈希表,存储每个数对应的下标,复杂度$O(n)$. - -方法3:先排序,然后左右夹逼,排序$O(n\log n)$,左右夹逼$O(n)$,最终$O(n\log n)$。但是注意,这题需要返回的是下标,而不是数字本身,因此这个方法行不通。 - - -\subsubsection{代码} -\begin{Code} -//LeetCode, Two Sum -// 方法2:hash。用一个哈希表,存储每个数对应的下标 -// 时间复杂度O(n),空间复杂度O(n) -class Solution { -public: - vector twoSum(vector &nums, int target) { - unordered_map mapping; - vector result; - for (int i = 0; i < nums.size(); i++) { - mapping[nums[i]] = 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); - break; - } - } - return result; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item 3Sum, 见 \S \ref{sec:3sum} -\item 3Sum Closest, 见 \S \ref{sec:3sum-closest} -\item 4Sum, 见 \S \ref{sec:4sum} -\myenddot - - -\subsection{3Sum} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:3sum} - - -\subsubsection{描述} -Given an array $S$ of $n$ integers, are there elements $a, b, c$ in $S$ such that $a + b + c = 0$? Find all unique triplets in the array which gives the sum of zero. - -Note: -\begindot -\item Elements in a triplet $(a,b,c)$ must be in non-descending order. (ie, $a \leq b \leq c$) -\item The solution set must not contain duplicate triplets. -\myenddot - -For example, given array \code{S = \{-1 0 1 2 -1 -4\}}. - -A solution set is: -\begin{Code} -(-1, 0, 1) -(-1, -1, 2) -\end{Code} - - -\subsubsection{分析} -先排序,然后左右夹逼,复杂度 $O(n^2)$。 - -这个方法可以推广到$k$-sum,先排序,然后做$k-2$次循环,在最内层循环左右夹逼,时间复杂度是 $O(\max\{n \log n, n^{k-1}\})$。 - - -\subsubsection{代码} -\begin{Code} -// LeetCode, 3Sum -// 先排序,然后左右夹逼,注意跳过重复的数,时间复杂度O(n^2),空间复杂度O(1) -class Solution { - public: - vector> threeSum(vector& nums) { - vector> result; - if (nums.size() < 3) return result; - sort(nums.begin(), nums.end()); - const int target = 0; - - auto last = nums.end(); - for (auto i = nums.begin(); i < last-2; ++i) { - auto j = i+1; - if (i > nums.begin() && *i == *(i-1)) continue; - auto k = last-1; - while (j < k) { - if (*i + *j + *k < target) { - ++j; - while(*j == *(j - 1) && j < k) ++j; - } else if (*i + *j + *k > target) { - --k; - while(*k == *(k + 1) && j < k) --k; - } else { - result.push_back({ *i, *j, *k }); - ++j; - --k; - while(*j == *(j - 1) && *k == *(k + 1) && j < k) ++j; - } - } - } - return result; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item Two sum, 见 \S \ref{sec:Two-sum} -\item 3Sum Closest, 见 \S \ref{sec:3sum-closest} -\item 4Sum, 见 \S \ref{sec:4sum} -\myenddot - -\subsection{3Sum Closest} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:3sum-closest} - - -\subsubsection{描述} -Given an array $S$ of $n$ integers, find three integers in $S$ such that the sum is closest to a given number, target. Return the sum of the three integers. You may assume that each input would have exactly one solution. - -For example, given array \code{S = \{-1 2 1 -4\}}, and \code{target = 1}. - -The sum that is closest to the target is 2. (\code{-1 + 2 + 1 = 2}). - - -\subsubsection{分析} -先排序,然后左右夹逼,复杂度 $O(n^2)$。 - - -\subsubsection{代码} -\begin{Code} -// LeetCode, 3Sum Closest -// 先排序,然后左右夹逼,时间复杂度O(n^2),空间复杂度O(1) -class Solution { -public: - int threeSumClosest(vector& nums, int target) { - int result = 0; - int min_gap = INT_MAX; - - sort(nums.begin(), nums.end()); - - for (auto a = nums.begin(); a != prev(nums.end(), 2); ++a) { - auto b = next(a); - auto c = prev(nums.end()); - - while (b < c) { - const int sum = *a + *b + *c; - const int gap = abs(sum - target); - - if (gap < min_gap) { - result = sum; - min_gap = gap; - } - - if (sum < target) ++b; - else --c; - } - } - - return result; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item Two sum, 见 \S \ref{sec:Two-sum} -\item 3Sum, 见 \S \ref{sec:3sum} -\item 4Sum, 见 \S \ref{sec:4sum} -\myenddot - - -\subsection{4Sum} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:4sum} - - -\subsubsection{描述} -Given an array $S$ of $n$ integers, are there elements $a, b, c$, and $d$ in $S$ such that $a + b + c + d = target$? Find all unique quadruplets in the array which gives the sum of target. - -Note: -\begindot -\item Elements in a quadruplet $(a,b,c,d)$ must be in non-descending order. (ie, $a \leq b \leq c \leq d$) -\item The solution set must not contain duplicate quadruplets. -\myenddot - -For example, given array \code{S = \{1 0 -1 0 -2 2\}}, and \code{target = 0}. - -A solution set is: -\begin{Code} -(-1, 0, 0, 1) -(-2, -1, 1, 2) -(-2, 0, 0, 2) -\end{Code} - - -\subsubsection{分析} -先排序,然后左右夹逼,复杂度 $O(n^3)$,会超时。 - -可以用一个hashmap先缓存两个数的和,最终复杂度$O(n^3)$。这个策略也适用于 3Sum 。 - - -\subsubsection{左右夹逼} -\begin{Code} -// LeetCode, 4Sum -// 先排序,然后左右夹逼,时间复杂度O(n^3),空间复杂度O(1) -class Solution { -public: - vector> fourSum(vector& nums, int target) { - vector> result; - if (nums.size() < 4) return result; - sort(nums.begin(), nums.end()); - - 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); - while (c < d) { - if (*a + *b + *c + *d < target) { - ++c; - } else if (*a + *b + *c + *d > target) { - --d; - } else { - result.push_back({ *a, *b, *c, *d }); - ++c; - --d; - } - } - } - } - sort(result.begin(), result.end()); - result.erase(unique(result.begin(), result.end()), result.end()); - return result; - } -}; -\end{Code} - - -\subsubsection{map做缓存} -\begin{Code} -// LeetCode, 4Sum -// 用一个hashmap先缓存两个数的和 -// 时间复杂度,平均O(n^2),最坏O(n^4),空间复杂度O(n^2) -class Solution { -public: - vector > fourSum(vector &nums, int target) { - vector> result; - if (nums.size() < 4) return result; - sort(nums.begin(), nums.end()); - - unordered_map > > cache; - 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 < 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]; - for (size_t k = 0; k < vec.size(); ++k) { - if (c <= vec[k].second) - continue; // 有重叠 - - result.push_back( { nums[vec[k].first], - nums[vec[k].second], nums[c], nums[d] }); - } - } - } - sort(result.begin(), result.end()); - result.erase(unique(result.begin(), result.end()), result.end()); - return result; - } -}; -\end{Code} - - -\subsubsection{multimap} -\begin{Code} -// LeetCode, 4Sum -// 用一个 hashmap 先缓存两个数的和 -// 时间复杂度O(n^2),空间复杂度O(n^2) -// @author 龚陆安(http://weibo.com/luangong) -class Solution { -public: - vector> fourSum(vector& nums, int target) { - vector> result; - if (nums.size() < 4) return result; - sort(nums.begin(), nums.end()); - - unordered_multimap> cache; - 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; - auto range = cache.equal_range(x); - for (auto j = range.first; j != range.second; ++j) { - auto a = i->second.first; - auto b = i->second.second; - auto c = j->second.first; - auto d = j->second.second; - if (a != c && a != d && b != c && b != d) { - vector vec = { nums[a], nums[b], nums[c], nums[d] }; - sort(vec.begin(), vec.end()); - result.push_back(vec); - } - } - } - sort(result.begin(), result.end()); - result.erase(unique(result.begin(), result.end()), result.end()); - return result; - } -}; -\end{Code} - - -\subsubsection{方法4} -\begin{Code} -// LeetCode, 4Sum -// 先排序,然后左右夹逼,时间复杂度O(n^3logn),空间复杂度O(1),会超时 -// 跟方法1相比,表面上优化了,实际上更慢了,切记! -class Solution { -public: - vector> fourSum(vector& nums, int target) { - vector> result; - if (nums.size() < 4) return result; - sort(nums.begin(), nums.end()); - - 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)) { - auto c = next(b); - auto d = prev(last); - while (c < d) { - if (*a + *b + *c + *d < target) { - c = upper_bound(c, d, *c); - } else if (*a + *b + *c + *d > target) { - d = prev(lower_bound(c, d, *d)); - } else { - result.push_back({ *a, *b, *c, *d }); - c = upper_bound(c, d, *c); - d = prev(lower_bound(c, d, *d)); - } - } - } - } - return result; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item Two sum, 见 \S \ref{sec:Two-sum} -\item 3Sum, 见 \S \ref{sec:3sum} -\item 3Sum Closest, 见 \S \ref{sec:3sum-closest} -\myenddot - - -\subsection{Remove Element} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:remove-element } - - -\subsubsection{描述} -Given an array and a value, remove all instances of that value in place and return the new length. - -The order of elements can be changed. It doesn't matter what you leave beyond the new length. - - -\subsubsection{分析} -无 - - -\subsubsection{代码1} -\begin{Code} -// LeetCode, Remove Element -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - int removeElement(vector& nums, int target) { - int index = 0; - for (int i = 0; i < nums.size(); ++i) { - if (nums[i] != target) { - nums[index++] = nums[i]; - } - } - return index; - } -}; -\end{Code} - - -\subsubsection{代码2} -\begin{Code} -// LeetCode, Remove Element -// 使用remove(),时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - int removeElement(vector& nums, int target) { - return distance(nums.begin(), remove(nums.begin(), nums.end(), target)); - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item 无 -\myenddot - - -\subsection{Next Permutation} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:next-permutation} - - -\subsubsection{描述} -Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers. - -If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order). - -The replacement must be in-place, do not allocate extra memory. - -Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column. -\begin{Code} -1,2,3 → 1,3,2 -3,2,1 → 1,2,3 -1,1,5 → 1,5,1 -\end{Code} - - -\subsubsection{分析} -算法过程如图~\ref{fig:permutation}所示(来自\myurl{http://fisherlei.blogspot.com/2012/12/leetcode-next-permutation.html})。 - -\begin{center} -\includegraphics[width=360pt]{next-permutation.png}\\ -\figcaption{下一个排列算法流程}\label{fig:permutation} -\end{center} - - -\subsubsection{代码} -\begin{Code} -// LeetCode, Next Permutation -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - void nextPermutation(vector &nums) { - next_permutation(nums.begin(), nums.end()); - } - - template - bool next_permutation(BidiIt first, BidiIt last) { - // Get a reversed range to simplify reversed traversal. - const auto rfirst = reverse_iterator(last); - const auto rlast = reverse_iterator(first); - - // Begin from the second last element to the first element. - auto pivot = next(rfirst); - - // Find `pivot`, which is the first element that is no less than its - // successor. `Prev` is used since `pivort` is a `reversed_iterator`. - while (pivot != rlast && *pivot >= *prev(pivot)) - ++pivot; - - // No such elemenet found, current sequence is already the largest - // permutation, then rearrange to the first permutation and return false. - if (pivot == rlast) { - reverse(rfirst, rlast); - return false; - } - - // Scan from right to left, find the first element that is greater than - // `pivot`. - auto change = find_if(rfirst, pivot, bind1st(less(), *pivot)); - - swap(*change, *pivot); - reverse(rfirst, pivot); - - return true; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item Permutation Sequence, 见 \S \ref{sec:permutation-sequence} -\item Permutations, 见 \S \ref{sec:permutations} -\item Permutations II, 见 \S \ref{sec:permutations-ii} -\item Combinations, 见 \S \ref{sec:combinations} -\myenddot - - -\subsection{Permutation Sequence} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:permutation-sequence} - - -\subsubsection{描述} -The set \fn{[1,2,3,…,n]} contains a total of $n!$ unique permutations. - -By listing and labeling all of the permutations in order, -We get the following sequence (ie, for $n = 3$): -\begin{Code} -"123" -"132" -"213" -"231" -"312" -"321" -\end{Code} - -Given $n$ and $k$, return the kth permutation sequence. - -Note: Given $n$ will be between 1 and 9 inclusive. - - -\subsubsection{分析} -简单的,可以用暴力枚举法,调用 $k-1$ 次 \fn{next_permutation()}。 - -暴力枚举法把前 $k$个排列都求出来了,比较浪费,而我们只需要第$k$个排列。 - -利用康托编码的思路,假设有$n$个不重复的元素,第$k$个排列是$a_1, a_2, a_3, ..., a_n$,那么$a_1$是哪一个位置呢? - -我们把$a_1$去掉,那么剩下的排列为 -$a_2, a_3, ..., a_n$, 共计$n-1$个元素,$n-1$个元素共有$(n-1)!$个排列,于是就可以知道 $a_1 = k / (n-1)!$。 - -同理,$a_2, a_3, ..., a_n$的值推导如下: - -\begin{eqnarray} -k_2 &=& k\%(n-1)! \nonumber \\ -a_2 &=& k_2/(n-2)! \nonumber \\ -\quad & \cdots \nonumber \\ -k_{n-1} &=& k_{n-2}\%2! \nonumber \\ -a_{n-1} &=& k_{n-1}/1! \nonumber \\ -a_n &=& 0 \nonumber -\end{eqnarray} - - -\subsubsection{使用next_permutation()} -\begin{Code} -// LeetCode, Permutation Sequence -// 使用next_permutation(),TLE -class Solution { -public: - string getPermutation(int n, int k) { - string s(n, '0'); - for (int i = 0; i < n; ++i) - s[i] += i+1; - for (int i = 0; i < k-1; ++i) - next_permutation(s.begin(), s.end()); - return s; - } - - template - bool next_permutation(BidiIt first, BidiIt last) { - // 代码见上一题 Next Permutation - } -}; -\end{Code} - - -\subsubsection{康托编码} -\begin{Code} -// LeetCode, Permutation Sequence -// 康托编码,时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - string getPermutation(int n, int k) { - string s(n, '0'); - string result; - for (int i = 0; i < n; ++i) - s[i] += i + 1; - - return kth_permutation(s, k); - } -private: - int factorial(int n) { - int result = 1; - for (int i = 1; i <= n; ++i) - result *= i; - return result; - } - - // seq 已排好序,是第一个排列 - template - Sequence kth_permutation(const Sequence &seq, int k) { - const int n = seq.size(); - Sequence S(seq); - Sequence result; - - int base = factorial(n - 1); - --k; // 康托编码从0开始 - - for (int i = n - 1; i > 0; k %= base, base /= i, --i) { - auto a = next(S.begin(), k / base); - result.push_back(*a); - S.erase(a); - } - - result.push_back(S[0]); // 最后一个 - return result; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item Next Permutation, 见 \S \ref{sec:next-permutation} -\item Permutations, 见 \S \ref{sec:permutations} -\item Permutations II, 见 \S \ref{sec:permutations-ii} -\item Combinations, 见 \S \ref{sec:combinations} -\myenddot - - -\subsection{Valid Sudoku} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:valid-sudoku} - - -\subsubsection{描述} -Determine if a Sudoku is valid, according to: Sudoku Puzzles - The Rules \myurl{http://sudoku.com.au/TheRules.aspx} . - -The Sudoku board could be partially filled, where empty cells are filled with the character \fn{'.'}. - -\begin{center} -\includegraphics[width=150pt]{sudoku.png}\\ -\figcaption{A partially filled sudoku which is valid}\label{fig:sudoku} -\end{center} - -\subsubsection{分析} -细节实现题。 - - -\subsubsection{代码} -\begin{Code} -// LeetCode, Valid Sudoku -// 时间复杂度O(n^2),空间复杂度O(1) -class Solution { -public: - bool isValidSudoku(const vector>& board) { - bool used[9]; - - for (int i = 0; i < 9; ++i) { - fill(used, used + 9, false); - - for (int j = 0; j < 9; ++j) // 检查行 - if (!check(board[i][j], used)) - return false; - - fill(used, used + 9, false); - - for (int j = 0; j < 9; ++j) // 检查列 - if (!check(board[j][i], used)) - return false; - } - - for (int r = 0; r < 3; ++r) // 检查 9 个子格子 - for (int c = 0; c < 3; ++c) { - fill(used, used + 9, false); - - for (int i = r * 3; i < r * 3 + 3; ++i) - for (int j = c * 3; j < c * 3 + 3; ++j) - if (!check(board[i][j], used)) - return false; - } - - return true; - } - - bool check(char ch, bool used[9]) { - if (ch == '.') return true; - - if (used[ch - '1']) return false; - - return used[ch - '1'] = true; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item Sudoku Solver, 见 \S \ref{sec:sudoku-solver} -\myenddot - - -\subsection{Trapping Rain Water} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:trapping-rain-water} - - -\subsubsection{描述} -Given $n$ non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining. - -For example, -Given \code{[0,1,0,2,1,0,1,3,2,1,2,1]}, return 6. - -\begin{center} -\includegraphics{trapping-rain-water.png}\\ -\figcaption{Trapping Rain Water}\label{fig:trapping-rain-water} -\end{center} - - -\subsubsection{分析} -对于每个柱子,找到其左右两边最高的柱子,该柱子能容纳的面积就是\code{min(max_left, max_right) - height}。所以, -\begin{enumerate} -\item 从左往右扫描一遍,对于每个柱子,求取左边最大值; -\item 从右往左扫描一遍,对于每个柱子,求最大右值; -\item 再扫描一遍,把每个柱子的面积并累加。 -\end{enumerate} - -也可以, -\begin{enumerate} -\item 扫描一遍,找到最高的柱子,这个柱子将数组分为两半; -\item 处理左边一半; -\item 处理右边一半。 -\end{enumerate} - - -\subsubsection{代码1} -\begin{Code} -// LeetCode, Trapping Rain Water -// 思路1,时间复杂度O(n),空间复杂度O(n) -class Solution { -public: - int trap(const vector& A) { - const int n = A.size(); - int *max_left = new int[n](); - int *max_right = new int[n](); - - for (int i = 1; i < n; i++) { - max_left[i] = max(max_left[i - 1], A[i - 1]); - max_right[n - 1 - i] = max(max_right[n - i], A[n - i]); - - } - - int sum = 0; - for (int i = 0; i < n; i++) { - int height = min(max_left[i], max_right[i]); - if (height > A[i]) { - sum += height - A[i]; - } - } - - delete[] max_left; - delete[] max_right; - return sum; - } -}; -\end{Code} - - -\subsubsection{代码2} -\begin{Code} -// LeetCode, Trapping Rain Water -// 思路2,时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - 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; - - int water = 0; - for (int i = 0, peak = 0; i < max; i++) - if (A[i] > peak) peak = A[i]; - else water += peak - A[i]; - for (int i = n - 1, top = 0; i > max; i--) - if (A[i] > top) top = A[i]; - else water += top - A[i]; - return water; - } -}; -\end{Code} - - -\subsubsection{代码3} -第三种解法,用一个栈辅助,小于栈顶的元素压入,大于等于栈顶就把栈里所有小于或等于当前值的元素全部出栈处理掉。 -\begin{Code} -// LeetCode, Trapping Rain Water -// 用一个栈辅助,小于栈顶的元素压入,大于等于栈顶就把栈里所有小于或 -// 等于当前值的元素全部出栈处理掉,计算面积,最后把当前元素入栈 -// 时间复杂度O(n),空间复杂度O(n) -class Solution { -public: - int trap(const vector& A) { - const int n = A.size(); - stack> s; - int water = 0; - - for (int i = 0; i < n; ++i) { - int height = 0; - - 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); - height = bar; - - if (A[i] < bar) // 碰到了比当前元素高的,跳出循环 - break; - else - s.pop(); // 弹出栈顶,因为该元素处理完了,不再需要了 - } - - s.push(make_pair(A[i], i)); - } - - return water; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item Container With Most Water, 见 \S \ref{sec:container-with-most-water} -\item Largest Rectangle in Histogram, 见 \S \ref{sec:largest-rectangle-in-histogram} -\myenddot - - -\subsection{Rotate Image} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:rotate-image} - - -\subsubsection{描述} -You are given an $n \times n$ 2D matrix representing an image. - -Rotate the image by 90 degrees (clockwise). - -Follow up: -Could you do this in-place? - - -\subsubsection{分析} -首先想到,纯模拟,从外到内一圈一圈的转,但这个方法太慢。 - -如下图,首先沿着副对角线翻转一次,然后沿着水平中线翻转一次。 - -\begin{center} -\includegraphics[width=200pt]{rotate-image.png}\\ -\figcaption{Rotate Image}\label{fig:rotate-image} -\end{center} - -或者,首先沿着水平中线翻转一次,然后沿着主对角线翻转一次。 - - -\subsubsection{代码1} -\begin{Code} -// LeetCode, Rotate Image -// 思路 1,时间复杂度O(n^2),空间复杂度O(1) -class Solution { -public: - void rotate(vector>& matrix) { - const int n = matrix.size(); - - for (int i = 0; i < n; ++i) // 沿着副对角线反转 - for (int j = 0; j < n - i; ++j) - swap(matrix[i][j], matrix[n - 1 - j][n - 1 - i]); - - for (int i = 0; i < n / 2; ++i) // 沿着水平中线反转 - for (int j = 0; j < n; ++j) - swap(matrix[i][j], matrix[n - 1 - i][j]); - } -}; -\end{Code} - -\subsubsection{代码2} -\begin{Code} -// LeetCode, Rotate Image -// 思路 2,时间复杂度O(n^2),空间复杂度O(1) -class Solution { -public: - void rotate(vector>& matrix) { - const int n = matrix.size(); - - for (int i = 0; i < n / 2; ++i) // 沿着水平中线反转 - for (int j = 0; j < n; ++j) - swap(matrix[i][j], matrix[n - 1 - i][j]); - - for (int i = 0; i < n; ++i) // 沿着主对角线反转 - for (int j = i + 1; j < n; ++j) - swap(matrix[i][j], matrix[j][i]); - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item 无 -\myenddot - - -\subsection{Plus One} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:plus-one} - - -\subsubsection{描述} -Given a number represented as an array of digits, plus one to the number. - - -\subsubsection{分析} -高精度加法。 - - -\subsubsection{代码1} -\begin{Code} -// LeetCode, Plus One -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - vector plusOne(vector &digits) { - add(digits, 1); - return digits; - } -private: - // 0 <= digit <= 9 - void add(vector &digits, int digit) { - int c = digit; // carry, 进位 - - for (auto it = digits.rbegin(); it != digits.rend(); ++it) { - *it += c; - c = *it / 10; - *it %= 10; - } - - if (c > 0) digits.insert(digits.begin(), 1); - } -}; -\end{Code} - - -\subsubsection{代码2} -\begin{Code} -// LeetCode, Plus One -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - vector plusOne(vector &digits) { - add(digits, 1); - return digits; - } -private: - // 0 <= digit <= 9 - void add(vector &digits, int digit) { - int c = digit; // carry, 进位 - - for_each(digits.rbegin(), digits.rend(), [&c](int &d){ - d += c; - c = d / 10; - d %= 10; - }); - - if (c > 0) digits.insert(digits.begin(), 1); - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item 无 -\myenddot - - -\subsection{Climbing Stairs} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:climbing-stairs} - - -\subsubsection{描述} -You are climbing a stair case. It takes $n$ steps to reach to the top. - -Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top? - - -\subsubsection{分析} -设$f(n)$表示爬$n$阶楼梯的不同方法数,为了爬到第$n$阶楼梯,有两个选择: -\begindot -\item 从第$n-1$阶前进1步; -\item 从第$n-1$阶前进2步; -\myenddot -因此,有$f(n)=f(n-1)+f(n-2)$。 - -这是一个斐波那契数列。 - -方法1,递归,太慢;方法2,迭代。 - -方法3,数学公式。斐波那契数列的通项公式为 $a_n=\dfrac{1}{\sqrt{5}}\left[\left(\dfrac{1+\sqrt{5}}{2}\right)^n-\left(\dfrac{1-\sqrt{5}}{2}\right)^n\right]$。 - - -\subsubsection{迭代} -\begin{Code} -// LeetCode, Climbing Stairs -// 迭代,时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - int climbStairs(int n) { - int prev = 0; - int cur = 1; - for(int i = 1; i <= n ; ++i){ - int tmp = cur; - cur += prev; - prev = tmp; - } - return cur; - } -}; -\end{Code} - - -\subsubsection{数学公式} -\begin{Code} -// LeetCode, Climbing Stairs -// 数学公式,时间复杂度O(1),空间复杂度O(1) -class Solution { - public: - int climbStairs(int n) { - const double s = sqrt(5); - return floor((pow((1+s)/2, n+1) + pow((1-s)/2, n+1))/s + 0.5); - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item Decode Ways, 见 \S \ref{sec:decode-ways} -\myenddot - - -\subsection{Gray Code} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:gray-code} - - -\subsubsection{描述} -The gray code is a binary numeral system where two successive values differ in only one bit. - -Given a non-negative integer $n$ representing the total number of bits in the code, print the sequence of gray code. A gray code sequence must begin with 0. - -For example, given $n = 2$, return \fn{[0,1,3,2]}. Its gray code sequence is: -\begin{Code} -00 - 0 -01 - 1 -11 - 3 -10 - 2 -\end{Code} - -Note: -\begindot -\item For a given $n$, a gray code sequence is not uniquely defined. -\item For example, \fn{[0,2,3,1]} is also a valid gray code sequence according to the above definition. -\item For now, the judge is able to judge based on one instance of gray code sequence. Sorry about that. -\myenddot - - -\subsubsection{分析} -格雷码(Gray Code)的定义请参考 \myurl{http://en.wikipedia.org/wiki/Gray_code} - -\textbf{自然二进制码转换为格雷码:$g_0=b_0, g_i=b_i \oplus b_{i-1}$} - -保留自然二进制码的最高位作为格雷码的最高位,格雷码次高位为二进制码的高位与次高位异或,其余各位与次高位的求法类似。例如,将自然二进制码1001,转换为格雷码的过程是:保留最高位;然后将第1位的1和第2位的0异或,得到1,作为格雷码的第2位;将第2位的0和第3位的0异或,得到0,作为格雷码的第3位;将第3位的0和第4位的1异或,得到1,作为格雷码的第4位,最终,格雷码为1101。 - -\textbf{格雷码转换为自然二进制码:$b_0=g_0, b_i=g_i \oplus b_{i-1}$} - -保留格雷码的最高位作为自然二进制码的最高位,次高位为自然二进制高位与格雷码次高位异或,其余各位与次高位的求法类似。例如,将格雷码1000转换为自然二进制码的过程是:保留最高位1,作为自然二进制码的最高位;然后将自然二进制码的第1位1和格雷码的第2位0异或,得到1,作为自然二进制码的第2位;将自然二进制码的第2位1和格雷码的第3位0异或,得到1,作为自然二进制码的第3位;将自然二进制码的第3位1和格雷码的第4位0异或,得到1,作为自然二进制码的第4位,最终,自然二进制码为1111。 - -格雷码有\textbf{数学公式},整数$n$的格雷码是$n \oplus (n/2)$。 - -这题要求生成$n$比特的所有格雷码。 - -方法1,最简单的方法,利用数学公式,对从 $0\sim2^n-1$的所有整数,转化为格雷码。 - -方法2,$n$比特的格雷码,可以递归地从$n-1$比特的格雷码生成。如图\S \ref{fig:gray-code-construction}所示。 - -\begin{center} -\includegraphics[width=160pt]{gray-code-construction.png}\\ -\figcaption{The first few steps of the reflect-and-prefix method.}\label{fig:gray-code-construction} -\end{center} - - -\subsubsection{数学公式} -\begin{Code} -// LeetCode, Gray Code -// 数学公式,时间复杂度O(2^n),空间复杂度O(1) -class Solution { -public: - vector grayCode(int n) { - vector result; - const size_t size = 1 << n; // 2^n - result.reserve(size); - for (size_t i = 0; i < size; ++i) - result.push_back(binary_to_gray(i)); - return result; - } -private: - static unsigned int binary_to_gray(unsigned int n) { - return n ^ (n >> 1); - } -}; -\end{Code} - - -\subsubsection{Reflect-and-prefix method} -\begin{Code} -// LeetCode, Gray Code -// reflect-and-prefix method -// 时间复杂度O(2^n),空间复杂度O(1) -class Solution { -public: - vector grayCode(int n) { - vector result; - result.reserve(1<= 0; j--) // 要反着遍历,才能对称 - result.push_back(highest_bit | result[j]); - } - return result; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item 无 -\myenddot - - -\subsection{Set Matrix Zeroes} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:set-matrix-zeroes} - - -\subsubsection{描述} -Given a $m \times n$ matrix, if an element is 0, set its entire row and column to 0. Do it in place. - -\textbf{Follow up:} -Did you use extra space? - -A straight forward solution using $O(mn)$ space is probably a bad idea. - -A simple improvement uses $O(m + n)$ space, but still not the best solution. - -Could you devise a constant space solution? - - -\subsubsection{分析} -$O(m+n)$空间的方法很简单,设置两个bool数组,记录每行和每列是否存在0。 - -想要常数空间,可以复用第一行和第一列。 - - -\subsubsection{代码1} -\begin{Code} -// LeetCode, Set Matrix Zeroes -// 时间复杂度O(m*n),空间复杂度O(m+n) -class Solution { -public: - void setZeroes(vector > &matrix) { - const size_t m = matrix.size(); - const size_t n = matrix[0].size(); - vector row(m, false); // 标记该行是否存在0 - vector col(n, false); // 标记该列是否存在0 - - for (size_t i = 0; i < m; ++i) { - for (size_t j = 0; j < n; ++j) { - if (matrix[i][j] == 0) { - row[i] = col[j] = true; - } - } - } - - for (size_t i = 0; i < m; ++i) { - if (row[i]) - fill(&matrix[i][0], &matrix[i][0] + n, 0); - } - for (size_t j = 0; j < n; ++j) { - if (col[j]) { - for (size_t i = 0; i < m; ++i) { - matrix[i][j] = 0; - } - } - } - } -}; -\end{Code} - - -\subsubsection{代码2} -\begin{Code} -// LeetCode, Set Matrix Zeroes -// 时间复杂度O(m*n),空间复杂度O(1) -class Solution { -public: - void setZeroes(vector > &matrix) { - const size_t m = matrix.size(); - const size_t n = matrix[0].size(); - bool row_has_zero = false; // 第一行是否存在 0 - bool col_has_zero = false; // 第一列是否存在 0 - - for (size_t i = 0; i < n; i++) - if (matrix[0][i] == 0) { - row_has_zero = true; - break; - } - - for (size_t i = 0; i < m; i++) - if (matrix[i][0] == 0) { - col_has_zero = true; - break; - } - - for (size_t i = 1; i < m; i++) - for (size_t j = 1; j < n; j++) - if (matrix[i][j] == 0) { - matrix[0][j] = 0; - matrix[i][0] = 0; - } - for (size_t i = 1; i < m; i++) - for (size_t j = 1; j < n; j++) - if (matrix[i][0] == 0 || matrix[0][j] == 0) - matrix[i][j] = 0; - if (row_has_zero) - for (size_t i = 0; i < n; i++) - matrix[0][i] = 0; - if (col_has_zero) - for (size_t i = 0; i < m; i++) - matrix[i][0] = 0; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item 无 -\myenddot - - -\subsection{Gas Station} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:gas-station} - - -\subsubsection{描述} -There are $N$ gas stations along a circular route, where the amount of gas at station $i$ is \fn{gas[i]}. - -You have a car with an unlimited gas tank and it costs \fn{cost[i]} of gas to travel from station $i$ to its next station ($i$+1). You begin the journey with an empty tank at one of the gas stations. - -Return the starting gas station's index if you can travel around the circuit once, otherwise return -1. - -Note: -The solution is guaranteed to be unique. - - -\subsubsection{分析} -首先想到的是$O(N^2)$的解法,对每个点进行模拟。 - -$O(N)$的解法是,设置两个变量,\fn{sum}判断当前的指针的有效性;\fn{total}则判断整个数组是否有解,有就返回通过\fn{sum}得到的下标,没有则返回-1。 - - -\subsubsection{代码} -\begin{Code} -// LeetCode, Gas Station -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - int canCompleteCircuit(vector &gas, vector &cost) { - int total = 0; - int j = -1; - for (int i = 0, sum = 0; i < gas.size(); ++i) { - sum += gas[i] - cost[i]; - total += gas[i] - cost[i]; - if (sum < 0) { - j = i; - sum = 0; - } - } - return total >= 0 ? j + 1 : -1; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item 无 -\myenddot - - -\subsection{Candy} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:candy} - - -\subsubsection{描述} -There are $N$ children standing in a line. Each child is assigned a rating value. - -You are giving candies to these children subjected to the following requirements: -\begindot -\item Each child must have at least one candy. -\item Children with a higher rating get more candies than their neighbors. -\myenddot - -What is the minimum candies you must give? - - -\subsubsection{分析} -无 - - -\subsubsection{迭代版} -\begin{Code} -// LeetCode, Candy -// 时间复杂度O(n),空间复杂度O(n) -class Solution { -public: - int candy(vector &ratings) { - const int n = ratings.size(); - vector increment(n); - - // 左右各扫描一遍 - for (int i = 1, inc = 1; i < n; i++) { - if (ratings[i] > ratings[i - 1]) - increment[i] = max(inc++, increment[i]); - else - inc = 1; - } - - for (int i = n - 2, inc = 1; i >= 0; i--) { - if (ratings[i] > ratings[i + 1]) - increment[i] = max(inc++, increment[i]); - else - inc = 1; - } - // 初始值为n,因为每个小朋友至少一颗糖 - return accumulate(&increment[0], &increment[0]+n, n); - } -}; -\end{Code} - - -\subsubsection{递归版} -\begin{Code} -// LeetCode, Candy -// 备忘录法,时间复杂度O(n),空间复杂度O(n) -// @author fancymouse (http://weibo.com/u/1928162822) -class Solution { -public: - int candy(const vector& ratings) { - vector f(ratings.size()); - int sum = 0; - for (int i = 0; i < ratings.size(); ++i) - sum += solve(ratings, f, i); - return sum; - } - int solve(const vector& ratings, vector& f, int i) { - if (f[i] == 0) { - f[i] = 1; - if (i > 0 && ratings[i] > ratings[i - 1]) - f[i] = max(f[i], solve(ratings, f, i - 1) + 1); - if (i < ratings.size() - 1 && ratings[i] > ratings[i + 1]) - f[i] = max(f[i], solve(ratings, f, i + 1) + 1); - } - return f[i]; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item 无 -\myenddot - - -\subsection{Single Number} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:single-number} - - -\subsubsection{描述} -Given an array of integers, every element appears twice except for one. Find that single one. - -Note: -Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory? - - -\subsubsection{分析} -异或,不仅能处理两次的情况,只要出现偶数次,都可以清零。 - - -\subsubsection{代码1} -\begin{Code} -// LeetCode, Single Number -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - int singleNumber(vector& nums) { - int x = 0; - for (auto i : nums) { - x ^= i; - } - return x; - } -}; -\end{Code} - - -\subsubsection{代码2} -\begin{Code} -// LeetCode, Single Number -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - int singleNumber(vector& nums) { - return accumulate(nums.begin(), nums.end(), 0, bit_xor()); - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item Single Number II, 见 \S \ref{sec:single-number-ii} -\myenddot - - -\subsection{Single Number II} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:single-number-ii} - - -\subsubsection{描述} -Given an array of integers, every element appears three times except for one. Find that single one. - -Note: -Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory? - - -\subsubsection{分析} -本题和上一题 Single Number,考察的是位运算。 - -方法1:创建一个长度为\fn{sizeof(int)}的数组\fn{count[sizeof(int)]},\fn{count[i]}表示在在$i$位出现的1的次数。如果\fn{count[i]}是3的整数倍,则忽略;否则就把该位取出来组成答案。 - -方法2:用\fn{one}记录到当前处理的元素为止,二进制1出现“1次”(mod 3 之后的 1)的有哪些二进制位;用\fn{two}记录到当前计算的变量为止,二进制1出现“2次”(mod 3 之后的 2)的有哪些二进制位。当\fn{one}和\fn{two}中的某一位同时为1时表示该二进制位上1出现了3次,此时需要清零。即\textbf{用二进制模拟三进制运算}。最终\fn{one}记录的是最终结果。 - -\subsubsection{代码1} -\begin{Code} -// LeetCode, Single Number II -// 方法1,时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - 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 < nums.size(); i++) { - for (int j = 0; j < W; j++) { - count[j] += (nums[i] >> j) & 1; - count[j] %= 3; - } - } - int result = 0; - for (int i = 0; i < W; i++) { - result += (count[i] << i); - } - return result; - } -}; -\end{Code} - - -\subsubsection{代码2} -\begin{Code} -// LeetCode, Single Number II -// 方法2,时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - int singleNumber(vector& nums) { - int one = 0, two = 0, three = 0; - for (auto i : nums) { - two |= (one & i); - one ^= i; - three = ~(one & two); - one &= three; - two &= three; - } - - return one; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item Single Number, 见 \S \ref{sec:single-number} -\myenddot - - -\section{单链表} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -单链表节点的定义如下: -\begin{Code} -// 单链表节点 -struct ListNode { - int val; - ListNode *next; - ListNode(int x) : val(x), next(nullptr) { } -}; -\end{Code} - - -\subsection{Add Two Numbers} -\label{sec:add-two-numbers} - - -\subsubsection{描述} -You are given two linked lists representing two non-negative numbers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list. - -Input: {\small \fontspec{Latin Modern Mono} (2 -> 4 -> 3) + (5 -> 6 -> 4)} - -Output: {\small \fontspec{Latin Modern Mono} 7 -> 0 -> 8} - - -\subsubsection{分析} -跟Add Binary(见 \S \ref{sec:add-binary})很类似 - - -\subsubsection{代码} -\begin{Code} -// LeetCode, Add Two Numbers -// 跟Add Binary 很类似 -// 时间复杂度O(m+n),空间复杂度O(1) -class Solution { -public: - ListNode *addTwoNumbers(ListNode *l1, ListNode *l2) { - ListNode dummy(-1); // 头节点 - int carry = 0; - ListNode *prev = &dummy; - for (ListNode *pa = l1, *pb = l2; - pa != nullptr || pb != nullptr; - pa = pa == nullptr ? nullptr : pa->next, - pb = pb == nullptr ? nullptr : pb->next, - prev = prev->next) { - const int ai = pa == nullptr ? 0 : pa->val; - const int bi = pb == nullptr ? 0 : pb->val; - const int value = (ai + bi + carry) % 10; - carry = (ai + bi + carry) / 10; - prev->next = new ListNode(value); // 尾插法 - } - if (carry > 0) - prev->next = new ListNode(carry); - return dummy.next; - } -}; -\end{Code} - - -\subsubsection{相关题目} - -\begindot -\item Add Binary, 见 \S \ref{sec:add-binary} -\myenddot - - -\subsection{Reverse Linked List II} -\label{sec:reverse-linked-list-ii} - - -\subsubsection{描述} -Reverse a linked list from position $m$ to $n$. Do it in-place and in one-pass. - -For example: -Given \code{1->2->3->4->5->nullptr}, $m$ = 2 and $n$ = 4, - -return \code{1->4->3->2->5->nullptr}. - -Note: -Given m, n satisfy the following condition: -$1 \leq m \leq n \leq $ length of list. - - -\subsubsection{分析} -这题非常繁琐,有很多边界检查,15分钟内做到bug free很有难度! - - -\subsubsection{代码} -\begin{Code} -// LeetCode, Reverse Linked List II -// 迭代版,时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - ListNode *reverseBetween(ListNode *head, int m, int n) { - ListNode dummy(-1); - dummy.next = head; - - ListNode *prev = &dummy; - for (int i = 0; i < m-1; ++i) - prev = prev->next; - ListNode* const head2 = prev; - - prev = head2->next; - ListNode *cur = prev->next; - for (int i = m; i < n; ++i) { - prev->next = cur->next; - cur->next = head2->next; - head2->next = cur; // 头插法 - cur = prev->next; - } - - return dummy.next; - } -}; -\end{Code} - - -\subsubsection{相关题目} - -\begindot -\item 无 -\myenddot - - -\subsection{Partition List} -\label{sec:partition-list} - - -\subsubsection{描述} -Given a linked list and a value $x$, partition it such that all nodes less than $x$ come before nodes greater than or equal to $x$. - -You should preserve the original relative order of the nodes in each of the two partitions. - -For example, -Given \code{1->4->3->2->5->2} and \code{x = 3}, return \code{1->2->2->4->3->5}. - - -\subsubsection{分析} -无 - - -\subsubsection{代码} -\begin{Code} -// LeetCode, Partition List -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - ListNode* partition(ListNode* head, int x) { - ListNode left_dummy(-1); // 头结点 - ListNode right_dummy(-1); // 头结点 - - auto left_cur = &left_dummy; - auto right_cur = &right_dummy; - - for (ListNode *cur = head; cur; cur = cur->next) { - if (cur->val < x) { - left_cur->next = cur; - left_cur = cur; - } else { - right_cur->next = cur; - right_cur = cur; - } - } - - left_cur->next = right_dummy.next; - right_cur->next = nullptr; - - return left_dummy.next; - } -}; -\end{Code} - - -\subsubsection{相关题目} - -\begindot -\item 无 -\myenddot - - -\subsection{Remove Duplicates from Sorted List} -\label{sec:remove-duplicates-from-sorted-list} - - -\subsubsection{描述} -Given a sorted linked list, delete all duplicates such that each element appear only once. - -For example, - -Given \code{1->1->2}, return \code{1->2}. - -Given \code{1->1->2->3->3}, return \code{1->2->3}. - - -\subsubsection{分析} -无 - - -\subsubsection{递归版} -\begin{Code} -// LeetCode, Remove Duplicates from Sorted List -// 递归版,时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - ListNode *deleteDuplicates(ListNode *head) { - if (!head) return head; - ListNode dummy(head->val + 1); // 值只要跟head不同即可 - dummy.next = head; - - recur(&dummy, head); - return dummy.next; - } -private: - static void recur(ListNode *prev, ListNode *cur) { - if (cur == nullptr) return; - - if (prev->val == cur->val) { // 删除head - prev->next = cur->next; - delete cur; - recur(prev, prev->next); - } else { - recur(prev->next, cur->next); - } - } -}; -\end{Code} - - -\subsubsection{迭代版} -\begin{Code} -// LeetCode, Remove Duplicates from Sorted List -// 迭代版,时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - ListNode *deleteDuplicates(ListNode *head) { - if (head == nullptr) return nullptr; - - for (ListNode *prev = head, *cur = head->next; cur; cur = prev->next) { - if (prev->val == cur->val) { - prev->next = cur->next; - delete cur; - } else { - prev = cur; - } - } - return head; - } -}; -\end{Code} - - -\subsubsection{相关题目} - -\begindot -\item Remove Duplicates from Sorted List II,见 \S \ref{sec:remove-duplicates-from-sorted-list-ii} -\myenddot - - -\subsection{Remove Duplicates from Sorted List II} -\label{sec:remove-duplicates-from-sorted-list-ii} - - -\subsubsection{描述} -Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numbers from the original list. - -For example, - -Given \code{1->2->3->3->4->4->5}, return \code{1->2->5}. - -Given \code{1->1->1->2->3}, return \code{2->3}. - - -\subsubsection{分析} -无 - - -\subsubsection{递归版} -\begin{Code} -// LeetCode, Remove Duplicates from Sorted List II -// 递归版,时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - ListNode *deleteDuplicates(ListNode *head) { - if (!head || !head->next) return head; - - ListNode *p = head->next; - if (head->val == p->val) { - while (p && head->val == p->val) { - ListNode *tmp = p; - p = p->next; - delete tmp; - } - delete head; - return deleteDuplicates(p); - } else { - head->next = deleteDuplicates(head->next); - return head; - } - } -}; -\end{Code} - - -\subsubsection{迭代版} -\begin{Code} -// LeetCode, Remove Duplicates from Sorted List II -// 迭代版,时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - ListNode *deleteDuplicates(ListNode *head) { - if (head == nullptr) return head; - - ListNode dummy(INT_MIN); // 头结点 - dummy.next = head; - ListNode *prev = &dummy, *cur = head; - while (cur != nullptr) { - bool duplicated = false; - while (cur->next != nullptr && cur->val == cur->next->val) { - duplicated = true; - ListNode *temp = cur; - cur = cur->next; - delete temp; - } - if (duplicated) { // 删除重复的最后一个元素 - ListNode *temp = cur; - cur = cur->next; - delete temp; - continue; - } - prev->next = cur; - prev = prev->next; - cur = cur->next; - } - prev->next = cur; - return dummy.next; - } -}; -\end{Code} - - -\subsubsection{相关题目} - -\begindot -\item Remove Duplicates from Sorted List,见 \S \ref{sec:remove-duplicates-from-sorted-list} -\myenddot - - -\subsection{Rotate List} -\label{sec:rotate-list} - - -\subsubsection{描述} -Given a list, rotate the list to the right by $k$ places, where $k$ is non-negative. - -For example: -Given \code{1->2->3->4->5->nullptr} and \code{k = 2}, return \code{4->5->1->2->3->nullptr}. - - -\subsubsection{分析} -先遍历一遍,得出链表长度$len$,注意$k$可能大于$len$,因此令$k \%= len$。将尾节点next指针指向首节点,形成一个环,接着往后跑$len-k$步,从这里断开,就是要求的结果了。 - - -\subsubsection{代码} -\begin{Code} -// LeetCode, Remove Rotate List -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - ListNode *rotateRight(ListNode *head, int k) { - if (head == nullptr || k == 0) return head; - - int len = 1; - ListNode* p = head; - while (p->next) { // 求长度 - len++; - p = p->next; - } - k = len - k % len; - - p->next = head; // 首尾相连 - for(int step = 0; step < k; step++) { - p = p->next; //接着往后跑 - } - head = p->next; // 新的首节点 - p->next = nullptr; // 断开环 - return head; - } -}; -\end{Code} - - -\subsubsection{相关题目} - -\begindot -\item 无 -\myenddot - - -\subsection{Remove Nth Node From End of List} -\label{sec:remove-nth-node-from-end-of-list} - - -\subsubsection{描述} -Given a linked list, remove the $n^{th}$ node from the end of list and return its head. - -For example, Given linked list: \code{1->2->3->4->5}, and $n$ = 2. - -After removing the second node from the end, the linked list becomes \code{1->2->3->5}. - -Note: -\begindot -\item Given $n$ will always be valid. -\item Try to do this in one pass. -\myenddot - - -\subsubsection{分析} -设两个指针$p,q$,让$q$先走$n$步,然后$p$和$q$一起走,直到$q$走到尾节点,删除\fn{p->next}即可。 - - -\subsubsection{代码} -\begin{Code} -// LeetCode, Remove Nth Node From End of List -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - ListNode *removeNthFromEnd(ListNode *head, int n) { - ListNode dummy{-1, head}; - ListNode *p = &dummy, *q = &dummy; - - for (int i = 0; i < n; i++) // q先走n步 - q = q->next; - - while(q->next) { // 一起走 - p = p->next; - q = q->next; - } - ListNode *tmp = p->next; - p->next = p->next->next; - delete tmp; - return dummy.next; - } -}; -\end{Code} - - -\subsubsection{相关题目} - -\begindot -\item 无 -\myenddot - - -\subsection{Swap Nodes in Pairs} -\label{sec:swap-nodes-in-pairs} - - -\subsubsection{描述} -Given a linked list, swap every two adjacent nodes and return its head. - -For example, -Given \code{1->2->3->4}, you should return the list as \code{2->1->4->3}. - -Your algorithm should use only constant space. You may \emph{not} modify the values in the list, only nodes itself can be changed. - - -\subsubsection{分析} -无 - - -\subsubsection{代码} -\begin{Code} -// LeetCode, Swap Nodes in Pairs -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - ListNode *swapPairs(ListNode *head) { - if (head == nullptr || head->next == nullptr) return head; - ListNode dummy(-1); - dummy.next = head; - - for(ListNode *prev = &dummy, *cur = prev->next, *next = cur->next; - next; - prev = cur, cur = cur->next, next = cur ? cur->next: nullptr) { - prev->next = next; - cur->next = next->next; - next->next = cur; - } - return dummy.next; - } -}; -\end{Code} - -下面这种写法更简洁,但题目规定了不准这样做。 -\begin{Code} -// LeetCode, Swap Nodes in Pairs -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - ListNode* swapPairs(ListNode* head) { - ListNode* p = head; - - while (p && p->next) { - swap(p->val, p->next->val); - p = p->next->next; - } - - return head; - } -}; -\end{Code} - -\subsubsection{相关题目} - -\begindot -\item Reverse Nodes in k-Group, 见 \S \ref{sec:reverse-nodes-in-k-group} -\myenddot - - -\subsection{Reverse Nodes in k-Group} -\label{sec:reverse-nodes-in-k-group} - - -\subsubsection{描述} -Given a linked list, reverse the nodes of a linked list k at a time and return its modified list. - -If the number of nodes is not a multiple of $k$ then left-out nodes in the end should remain as it is. - -You may not alter the values in the nodes, only nodes itself may be changed. - -Only constant memory is allowed. - -For example, -Given this linked list: \code{1->2->3->4->5} - -For $k = 2$, you should return: \code{2->1->4->3->5} - -For $k = 3$, you should return: \code{3->2->1->4->5} - - -\subsubsection{分析} -无 - - -\subsubsection{递归版} -\begin{Code} -// LeetCode, Reverse Nodes in k-Group -// 递归版,时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - ListNode *reverseKGroup(ListNode *head, int k) { - if (head == nullptr || head->next == nullptr || k < 2) - return head; - - ListNode *next_group = head; - for (int i = 0; i < k; ++i) { - if (next_group) - next_group = next_group->next; - else - return head; - } - // next_group is the head of next group - // new_next_group is the new head of next group after reversion - ListNode *new_next_group = reverseKGroup(next_group, k); - ListNode *prev = NULL, *cur = head; - while (cur != next_group) { - ListNode *next = cur->next; - cur->next = prev ? prev : new_next_group; - prev = cur; - cur = next; - } - return prev; // prev will be the new head of this group - } -}; -\end{Code} - - -\subsubsection{迭代版} -\begin{Code} -// LeetCode, Reverse Nodes in k-Group -// 迭代版,时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - ListNode *reverseKGroup(ListNode *head, int k) { - if (head == nullptr || head->next == nullptr || k < 2) return head; - ListNode dummy(-1); - dummy.next = head; - - for(ListNode *prev = &dummy, *end = head; end; end = prev->next) { - for (int i = 1; i < k && end; i++) - end = end->next; - if (end == nullptr) break; // 不足 k 个 - - prev = reverse(prev, prev->next, end); - } - - return dummy.next; - } - - // prev 是 first 前一个元素, [begin, end] 闭区间,保证三者都不为 null - // 返回反转后的倒数第1个元素 - ListNode* reverse(ListNode *prev, ListNode *begin, ListNode *end) { - ListNode *end_next = end->next; - for (ListNode *p = begin, *cur = p->next, *next = cur->next; - cur != end_next; - p = cur, cur = next, next = next ? next->next : nullptr) { - cur->next = p; - } - begin->next = end_next; - prev->next = end; - return begin; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item Swap Nodes in Pairs, 见 \S \ref{sec:swap-nodes-in-pairs} -\myenddot - - -\subsection{Copy List with Random Pointer} -\label{sec:copy-list-with-random-pointer} - - -\subsubsection{描述} -A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null. - -Return a deep copy of the list. - - -\subsubsection{分析} -无 - - -\subsubsection{代码} -\begin{Code} -// LeetCode, Copy List with Random Pointer -// 两遍扫描,时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - RandomListNode *copyRandomList(RandomListNode *head) { - for (RandomListNode* cur = head; cur != nullptr; ) { - RandomListNode* node = new RandomListNode(cur->label); - node->next = cur->next; - cur->next = node; - cur = node->next; - } - - for (RandomListNode* cur = head; cur != nullptr; ) { - if (cur->random != NULL) - cur->next->random = cur->random->next; - cur = cur->next->next; - } - - // 分拆两个单链表 - RandomListNode dummy(-1); - for (RandomListNode* cur = head, *new_cur = &dummy; - cur != nullptr; ) { - new_cur->next = cur->next; - new_cur = new_cur->next; - cur->next = cur->next->next; - cur = cur->next; - } - return dummy.next; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item 无 -\myenddot - - -\subsection{Linked List Cycle} -\label{sec:linked-list-cycle} - - -\subsubsection{描述} -Given a linked list, determine if it has a cycle in it. - -Follow up: -Can you solve it without using extra space? - - -\subsubsection{分析} -最容易想到的方法是,用一个哈希表\fn{unordered_map visited},记录每个元素是否被访问过,一旦出现某个元素被重复访问,说明存在环。空间复杂度$O(n)$,时间复杂度$O(N)$。 - -最好的方法是时间复杂度$O(n)$,空间复杂度$O(1)$的。设置两个指针,一个快一个慢,快的指针每次走两步,慢的指针每次走一步,如果快指针和慢指针相遇,则说明有环。参考\myurl{ http://leetcode.com/2010/09/detecting-loop-in-singly-linked-list.html} - - -\subsubsection{代码} -\begin{Code} -//LeetCode, Linked List Cycle -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - bool hasCycle(ListNode *head) { - // 设置两个指针,一个快一个慢 - ListNode *slow = head, *fast = head; - while (fast && fast->next) { - slow = slow->next; - fast = fast->next->next; - if (slow == fast) return true; - } - return false; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item Linked List Cycle II, 见 \S \ref{sec:Linked-List-Cycle-II} -\myenddot - - -\subsection{Linked List Cycle II} -\label{sec:linked-list-cycle-ii} - - -\subsubsection{描述} -Given a linked list, return the node where the cycle begins. If there is no cycle, return \fn{null}. - -Follow up: -Can you solve it without using extra space? - - -\subsubsection{分析} -当fast与slow相遇时,slow肯定没有遍历完链表,而fast已经在环内循环了$n$圈($1 \leq n$)。假设slow走了$s$步,则fast走了$2s$步(fast步数还等于$s$加上在环上多转的$n$圈),设环长为$r$,则: -\begin{eqnarray} -2s &=& s + nr \nonumber \\ -s &=& nr \nonumber -\end{eqnarray} - -设整个链表长$L$,环入口点与相遇点距离为$a$,起点到环入口点的距离为$x$,则 -\begin{eqnarray} -x + a &=& nr = (n – 1)r +r = (n-1)r + L - x \nonumber \\ -x &=& (n-1)r + (L – x – a) \nonumber -\end{eqnarray} - -$L – x – a$为相遇点到环入口点的距离,由此可知,从链表头到环入口点等于$n-1$圈内环+相遇点到环入口点,于是我们可以从\fn{head}开始另设一个指针\fn{slow2},两个慢指针每次前进一步,它俩一定会在环入口点相遇。 - - -\subsubsection{代码} -\begin{Code} -//LeetCode, Linked List Cycle II -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - ListNode *detectCycle(ListNode *head) { - ListNode *slow = head, *fast = head; - while (fast && fast->next) { - slow = slow->next; - fast = fast->next->next; - if (slow == fast) { - ListNode *slow2 = head; - - while (slow2 != slow) { - slow2 = slow2->next; - slow = slow->next; - } - return slow2; - } - } - return nullptr; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item Linked List Cycle, 见 \S \ref{sec:Linked-List-Cycle} -\myenddot - - -\subsection{Reorder List} -\label{sec:reorder-list} - - -\subsubsection{描述} -Given a singly linked list $L: L_0 \rightarrow L_1 \rightarrow \cdots \rightarrow L_{n-1} \rightarrow L_n$, -reorder it to: $L_0 \rightarrow L_n \rightarrow L_1 \rightarrow L_{n-1} \rightarrow L_2 \rightarrow L_{n-2} \rightarrow \cdots$ - -You must do this in-place without altering the nodes' values. - -For example, -Given \fn{\{1,2,3,4\}}, reorder it to \fn{\{1,4,2,3\}}. - - -\subsubsection{分析} -题目规定要in-place,也就是说只能使用$O(1)$的空间。 - -可以找到中间节点,断开,把后半截单链表reverse一下,再合并两个单链表。 - - -\subsubsection{代码} -\begin{Code} -// LeetCode, Reorder List -// 时间复杂度O(n),空间复杂度O(1) -class Solution { -public: - void reorderList(ListNode *head) { - if (head == nullptr || head->next == nullptr) return; - - ListNode *slow = head, *fast = head, *prev = nullptr; - while (fast && fast->next) { - prev = slow; - slow = slow->next; - fast = fast->next->next; - } - prev->next = nullptr; // cut at middle - - slow = reverse(slow); - - // merge two lists - ListNode *curr = head; - while (curr->next) { - ListNode *tmp = curr->next; - curr->next = slow; - slow = slow->next; - curr->next->next = tmp; - curr = tmp; - } - curr->next = slow; - } - - ListNode* reverse(ListNode *head) { - if (head == nullptr || head->next == nullptr) return head; - - ListNode *prev = head; - for (ListNode *curr = head->next, *next = curr->next; curr; - prev = curr, curr = next, next = next ? next->next : nullptr) { - curr->next = prev; - } - head->next = nullptr; - return prev; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item 无 -\myenddot - - -\subsection{LRU Cache} -\label{sec:lru-cache} - - -\subsubsection{描述} -Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set. - -\fn{get(key)} - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1. - -\fn{set(key, value)} - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item. - - -\subsubsection{分析} -为了使查找、插入和删除都有较高的性能,我们使用一个双向链表(\fn{std::list})和一个哈希表(\fn{std::unordered_map}),因为: -\begin{itemize} -\item{哈希表保存每个节点的地址,可以基本保证在$O(1)$时间内查找节点} -\item{双向链表插入和删除效率高,单向链表插入和删除时,还要查找节点的前驱节点} -\end{itemize} - -具体实现细节: -\begin{itemize} -\item{越靠近链表头部,表示节点上次访问距离现在时间最短,尾部的节点表示最近访问最少} -\item{访问节点时,如果节点存在,把该节点交换到链表头部,同时更新hash表中该节点的地址} -\item{插入节点时,如果cache的size达到了上限capacity,则删除尾部节点,同时要在hash表中删除对应的项;新节点插入链表头部} -\end{itemize} - - -\subsubsection{代码} -\begin{Code} -// LeetCode, LRU Cache -// 时间复杂度O(logn),空间复杂度O(n) -class LRUCache{ -private: - struct CacheNode { - int key; - int value; - CacheNode(int k, int v) :key(k), value(v){} - }; -public: - LRUCache(int capacity) { - this->capacity = capacity; - } - - int get(int key) { - if (cacheMap.find(key) == cacheMap.end()) return -1; - - // 把当前访问的节点移到链表头部,并且更新map中该节点的地址 - cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]); - cacheMap[key] = cacheList.begin(); - return cacheMap[key]->value; - } - - void set(int key, int value) { - if (cacheMap.find(key) == cacheMap.end()) { - if (cacheList.size() == capacity) { //删除链表尾部节点(最少访问的节点) - cacheMap.erase(cacheList.back().key); - cacheList.pop_back(); - } - // 插入新节点到链表头部, 并且在map中增加该节点 - cacheList.push_front(CacheNode(key, value)); - cacheMap[key] = cacheList.begin(); - } else { - //更新节点的值,把当前访问的节点移到链表头部,并且更新map中该节点的地址 - cacheMap[key]->value = value; - cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]); - cacheMap[key] = cacheList.begin(); - } - } -private: - list cacheList; - unordered_map::iterator> cacheMap; - int capacity; -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item 无 -\myenddot - diff --git a/C++/chapLinkedList.tex b/C++/chapLinkedList.tex new file mode 100644 index 00000000..2b16e638 --- /dev/null +++ b/C++/chapLinkedList.tex @@ -0,0 +1,1355 @@ +\chapter{鏈表} + +\section{單鏈表} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +單鏈表節點的定義如下: +\begin{Code} +// 單鏈表節點 +struct ListNode { + int val; + ListNode *next; + ListNode(int x) : val(x), next(nullptr) { } +}; +\end{Code} + + +\subsection{Add Two Numbers} +\label{sec:add-two-numbers} + + +\subsubsection{描述} +You are given two linked lists representing two non-negative numbers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list. + +Input: {\small \fontspec{Latin Modern Mono} (2 -> 4 -> 3) + (5 -> 6 -> 4)} + +Output: {\small \fontspec{Latin Modern Mono} 7 -> 0 -> 8} + + +\subsubsection{分析} +跟Add Binary(見 \S \ref{sec:add-binary})很類似 + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Add Two Numbers +// 跟Add Binary 很類似 +// 時間複雜度O(m+n),空間複雜度O(1) +class Solution { +public: + ListNode *addTwoNumbers(ListNode *l1, ListNode *l2) { + ListNode dummy(-1); // 頭節點 + int carry = 0; + ListNode *prev = &dummy; + for (ListNode *pa = l1, *pb = l2; + pa != nullptr || pb != nullptr; + pa = pa == nullptr ? nullptr : pa->next, + pb = pb == nullptr ? nullptr : pb->next, + prev = prev->next) { + const int ai = pa == nullptr ? 0 : pa->val; + const int bi = pb == nullptr ? 0 : pb->val; + const int value = (ai + bi + carry) % 10; + carry = (ai + bi + carry) / 10; + prev->next = new ListNode(value); // 尾插法 + } + if (carry > 0) + prev->next = new ListNode(carry); + return dummy.next; + } +}; +\end{Code} + + +\subsubsection{相關題目} + +\begindot +\item Add Binary, 見 \S \ref{sec:add-binary} +\myenddot + + +\subsection{Reverse Linked List II} +\label{sec:reverse-linked-list-ii} + + +\subsubsection{描述} +Reverse a linked list from position $m$ to $n$. Do it in-place and in one-pass. + +For example: +Given \code{1->2->3->4->5->nullptr}, $m$ = 2 and $n$ = 4, + +return \code{1->4->3->2->5->nullptr}. + +Note: +Given m, n satisfy the following condition: +$1 \leq m \leq n \leq $ length of list. + + +\subsubsection{分析} +這題非常繁瑣,有很多邊界檢查,15分鐘內做到bug free很有難度! + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Reverse Linked List II +// 迭代版,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + ListNode *reverseBetween(ListNode *head, int m, int n) { + ListNode dummy(-1); + dummy.next = head; + + ListNode *prev = &dummy; + for (int i = 0; i < m-1; ++i) + prev = prev->next; + ListNode* const head2 = prev; + + prev = head2->next; + ListNode *cur = prev->next; + for (int i = m; i < n; ++i) { + prev->next = cur->next; + cur->next = head2->next; + head2->next = cur; // 頭插法 + cur = prev->next; + } + + return dummy.next; + } +}; +\end{Code} + + +\subsubsection{相關題目} + +\begindot +\item 無 +\myenddot + + +\subsection{Partition List} +\label{sec:partition-list} + + +\subsubsection{描述} +Given a linked list and a value $x$, partition it such that all nodes less than $x$ come before nodes greater than or equal to $x$. + +You should preserve the original relative order of the nodes in each of the two partitions. + +For example, +Given \code{1->4->3->2->5->2} and \code{x = 3}, return \code{1->2->2->3->4->5}. + + +\subsubsection{分析} +無 + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Partition List +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + ListNode* partition(ListNode* head, int x) { + ListNode left_dummy(-1); // 頭結點 + ListNode right_dummy(-1); // 頭結點 + + auto left_cur = &left_dummy; + auto right_cur = &right_dummy; + + for (ListNode *cur = head; cur; cur = cur->next) { + if (cur->val < x) { + left_cur->next = cur; + left_cur = cur; + } else { + right_cur->next = cur; + right_cur = cur; + } + } + + left_cur->next = right_dummy.next; + right_cur->next = nullptr; + + return left_dummy.next; + } +}; +\end{Code} + + +\subsubsection{相關題目} + +\begindot +\item 無 +\myenddot + + +\subsection{Remove Duplicates from Sorted List} +\label{sec:remove-duplicates-from-sorted-list} + + +\subsubsection{描述} +Given a sorted linked list, delete all duplicates such that each element appear only once. + +For example, + +Given \code{1->1->2}, return \code{1->2}. + +Given \code{1->1->2->3->3}, return \code{1->2->3}. + + +\subsubsection{分析} +無 + + +\subsubsection{遞歸版} +\begin{Code} +// LeetCode, Remove Duplicates from Sorted List +// 遞歸版,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + ListNode *deleteDuplicates(ListNode *head) { + if (!head) return head; + ListNode dummy(head->val + 1); // 值只要跟head不同即可 + dummy.next = head; + + recur(&dummy, head); + return dummy.next; + } +private: + static void recur(ListNode *prev, ListNode *cur) { + if (cur == nullptr) return; + + if (prev->val == cur->val) { // 刪除head + prev->next = cur->next; + delete cur; + recur(prev, prev->next); + } else { + recur(prev->next, cur->next); + } + } +}; +\end{Code} + + +\subsubsection{迭代版} +\begin{Code} +// LeetCode, Remove Duplicates from Sorted List +// 迭代版,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + ListNode *deleteDuplicates(ListNode *head) { + if (head == nullptr) return nullptr; + + for (ListNode *prev = head, *cur = head->next; cur; cur = prev->next) { + if (prev->val == cur->val) { + prev->next = cur->next; + delete cur; + } else { + prev = cur; + } + } + return head; + } +}; +\end{Code} + + +\subsubsection{相關題目} + +\begindot +\item Remove Duplicates from Sorted List II,見 \S \ref{sec:remove-duplicates-from-sorted-list-ii} +\myenddot + + +\subsection{Remove Duplicates from Sorted List II} +\label{sec:remove-duplicates-from-sorted-list-ii} + + +\subsubsection{描述} +Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numbers from the original list. + +For example, + +Given \code{1->2->3->3->4->4->5}, return \code{1->2->5}. + +Given \code{1->1->1->2->3}, return \code{2->3}. + + +\subsubsection{分析} +無 + + +\subsubsection{遞歸版} +\begin{Code} +// LeetCode, Remove Duplicates from Sorted List II +// 遞歸版,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + ListNode *deleteDuplicates(ListNode *head) { + if (!head || !head->next) return head; + + ListNode *p = head->next; + if (head->val == p->val) { + while (p && head->val == p->val) { + ListNode *tmp = p; + p = p->next; + delete tmp; + } + delete head; + return deleteDuplicates(p); + } else { + head->next = deleteDuplicates(head->next); + return head; + } + } +}; +\end{Code} + + +\subsubsection{迭代版} +\begin{Code} +// LeetCode, Remove Duplicates from Sorted List II +// 迭代版,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + ListNode *deleteDuplicates(ListNode *head) { + if (head == nullptr) return head; + + ListNode dummy(INT_MIN); // 頭結點 + dummy.next = head; + ListNode *prev = &dummy, *cur = head; + while (cur != nullptr) { + bool duplicated = false; + while (cur->next != nullptr && cur->val == cur->next->val) { + duplicated = true; + ListNode *temp = cur; + cur = cur->next; + delete temp; + } + if (duplicated) { // 刪除重複的最後一個元素 + ListNode *temp = cur; + cur = cur->next; + delete temp; + continue; + } + prev->next = cur; + prev = prev->next; + cur = cur->next; + } + prev->next = cur; + return dummy.next; + } +}; +\end{Code} + + +\subsubsection{迭代版} +\begin{Code} +// LeetCode, Remove Duplicates from Sorted List II +// 迭代版,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + ListNode *deleteDuplicates(ListNode *head) { + if (head == nullptr) return head; + + ListNode dummy(INT_MIN); dummy.next = head; + ListNode *prev = &dummy; + ListNode *cur = head; + ListNode *next = cur != nullptr ? cur->next : nullptr; + + while (next != nullptr) { + bool duplicated = false; + while (next && cur->val == next->val) { + duplicated = true; + prev->next = next; + delete cur; + cur = next; + next = cur != nullptr ? cur->next : nullptr; + } + if (duplicated) { + prev->next = next; + delete cur; + } + else + prev = cur; + cur = next; + next = cur != nullptr ? cur->next : nullptr; + } + return dummy.next; + } +}; + + +\end{Code} +\subsubsection{相關題目} + +\begindot +\item Remove Duplicates from Sorted List,見 \S \ref{sec:remove-duplicates-from-sorted-list} +\myenddot + + +\subsection{Rotate List} +\label{sec:rotate-list} + + +\subsubsection{描述} +Given a list, rotate the list to the right by $k$ places, where $k$ is non-negative. + +For example: +Given \code{1->2->3->4->5->nullptr} and \code{k = 2}, return \code{4->5->1->2->3->nullptr}. + + +\subsubsection{分析} +先遍歷一遍,得出鏈表長度$len$,注意$k$可能大於$len$,因此令$k \%= len$。將尾節點next指針指向首節點,形成一個環,接着往後跑$len-k$步,從這裏斷開,就是要求的結果了。 + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Remove Rotate List +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + ListNode *rotateRight(ListNode *head, int k) { + if (head == nullptr || k == 0) return head; + + int len = 1; + ListNode* p = head; + while (p->next) { // 求長度 + len++; + p = p->next; + } + k = len - k % len; + + p->next = head; // 首尾相連 + for(int step = 0; step < k; step++) { + p = p->next; //接着往後跑 + } + head = p->next; // 新的首節點 + p->next = nullptr; // 斷開環 + return head; + } +}; +\end{Code} + + +\subsubsection{相關題目} + +\begindot +\item 無 +\myenddot + +\subsection{Rotate List II} +\label{sec:rotate-list-ii} + + +\subsubsection{描述} +Given a list, rotate the list to the left by $k$ places, where $k$ is non-negative. + +For example: +Given \code{1->2->3->4->5->nullptr} and \code{k = 2}, return \code{4->5->1->2->3->nullptr}. + + +\subsubsection{分析} +先遍歷一遍,得出鏈表長度$len$,注意$k$可能大於$len$,因此令$k \%= len$。將尾節點next指針指向首節點,形成一個環,接着往後跑$len-k$步,從這裏斷開,就是要求的結果了。 + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Remove Rotate List +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + ListNode *rotateRight(ListNode *head, int k) { + if (head == nullptr || k == 0) return head; + + int len = 1; + ListNode* p = head; + while (p->next) { // 求長度 + len++; + p = p->next; + } + k = k % len; // This is the only difference + + p->next = head; // 首尾相連 + for(int step = 0; step < k; step++) { + p = p->next; //接着往後跑 + } + head = p->next; // 新的首節點 + p->next = nullptr; // 斷開環 + return head; + } +}; +\end{Code} + + +\subsubsection{相關題目} + +\begindot +\item 無 +\myenddot + + +\subsection{Remove Nth Node From End of List} +\label{sec:remove-nth-node-from-end-of-list} + + +\subsubsection{描述} +Given a linked list, remove the $n^{th}$ node from the end of list and return its head. + +For example, Given linked list: \code{1->2->3->4->5}, and $n$ = 2. + +After removing the second node from the end, the linked list becomes \code{1->2->3->5}. + +Note: +\begindot +\item Given $n$ will always be valid. +\item Try to do this in one pass. +\myenddot + + +\subsubsection{分析} +設兩個指針$p,q$,讓$q$先走$n$步,然後$p$和$q$一起走,直到$q$走到尾節點,刪除\fn{p->next}即可。 + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Remove Nth Node From End of List +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + ListNode *removeNthFromEnd(ListNode *head, int n) { + ListNode dummy{-1, head}; + ListNode *p = &dummy, *q = &dummy; + + for (int i = 0; i < n; i++) // q先走n步 + q = q->next; + + while(q->next) { // 一起走 + p = p->next; + q = q->next; + } + ListNode *tmp = p->next; + p->next = p->next->next; + delete tmp; + return dummy.next; + } +}; +\end{Code} + + +\subsubsection{相關題目} + +\begindot +\item 無 +\myenddot + + +\subsection{Swap Nodes in Pairs} +\label{sec:swap-nodes-in-pairs} + + +\subsubsection{描述} +Given a linked list, swap every two adjacent nodes and return its head. + +For example, +Given \code{1->2->3->4}, you should return the list as \code{2->1->4->3}. + +Your algorithm should use only constant space. You may \emph{not} modify the values in the list, only nodes itself can be changed. + + +\subsubsection{分析} +無 + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Swap Nodes in Pairs +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + ListNode *swapPairs(ListNode *head) { + if (head == nullptr || head->next == nullptr) return head; + ListNode dummy(-1); + dummy.next = head; + + for(ListNode *prev = &dummy, *cur = prev->next, *next = cur->next; + next; + prev = cur, cur = cur->next, next = cur ? cur->next: nullptr) { + prev->next = next; + cur->next = next->next; + next->next = cur; + } + return dummy.next; + } +}; +\end{Code} + +下面這種寫法更簡潔,但題目規定了不允許這樣做。 +\begin{Code} +// LeetCode, Swap Nodes in Pairs +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + ListNode* swapPairs(ListNode* head) { + ListNode* p = head; + + while (p && p->next) { + swap(p->val, p->next->val); + p = p->next->next; + } + + return head; + } +}; +\end{Code} + +\subsubsection{相關題目} + +\begindot +\item Reverse Nodes in k-Group, 見 \S \ref{sec:reverse-nodes-in-k-group} +\myenddot + + +\subsection{Reverse Nodes in k-Group} +\label{sec:reverse-nodes-in-k-group} + + +\subsubsection{描述} +Given a linked list, reverse the nodes of a linked list k at a time and return its modified list. + +If the number of nodes is not a multiple of $k$ then left-out nodes in the end should remain as it is. + +You may not alter the values in the nodes, only nodes itself may be changed. + +Only constant memory is allowed. + +For example, +Given this linked list: \code{1->2->3->4->5} + +For $k = 2$, you should return: \code{2->1->4->3->5} + +For $k = 3$, you should return: \code{3->2->1->4->5} + + +\subsubsection{分析} +無 + + +\subsubsection{遞歸版} +\begin{Code} +// LeetCode, Reverse Nodes in k-Group +// 遞歸版,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + ListNode *reverseKGroup(ListNode *head, int k) { + if (head == nullptr || head->next == nullptr || k < 2) + return head; + + ListNode *next_group = head; + for (int i = 0; i < k; ++i) { + if (next_group) + next_group = next_group->next; + else + return head; + } + // next_group is the head of next group + // new_next_group is the new head of next group after reversion + ListNode *new_next_group = reverseKGroup(next_group, k); + ListNode *prev = new_next_group, *cur = head; + while (cur != next_group) { + ListNode *next = cur->next; + cur->next = prev; + prev = cur; + cur = next; + } + return prev; // prev will be the new head of this group + } +}; +\end{Code} + + +\subsubsection{迭代版} +\begin{Code} +// LeetCode, Reverse Nodes in k-Group +// 迭代版,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + ListNode *reverseKGroup(ListNode *head, int k) { + if (head == nullptr || head->next == nullptr || k < 2) return head; + ListNode dummy(-1); + dummy.next = head; + + for(ListNode *prev = &dummy, *end = head; end; end = prev->next) { + for (int i = 1; i < k && end; i++) + end = end->next; + if (end == nullptr) break; // 不足 k 個 + + prev = reverse(prev, prev->next, end); + } + + return dummy.next; + } + + // prev 是 first 前一個元素, [begin, end] 閉區間,保證三者都不為 null + // 返回反轉後的倒數第1個元素 + ListNode* reverse(ListNode *prev, ListNode *begin, ListNode *end) { + ListNode *end_next = end->next; + for (ListNode *p = begin, *cur = p->next, *next = cur->next; + cur != end_next; + p = cur, cur = next, next = next ? next->next : nullptr) { + cur->next = p; + } + begin->next = end_next; + prev->next = end; + return begin; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Swap Nodes in Pairs, 見 \S \ref{sec:swap-nodes-in-pairs} +\myenddot + + +\subsection{Copy List with Random Pointer} +\label{sec:copy-list-with-random-pointer} + + +\subsubsection{描述} +A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null. + +Return a deep copy of the list. + + +\subsubsection{分析} +無 + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Copy List with Random Pointer +// 兩遍掃描,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + RandomListNode *copyRandomList(RandomListNode *head) { + for (RandomListNode* cur = head; cur != nullptr; ) { + RandomListNode* node = new RandomListNode(cur->label); + node->next = cur->next; + cur->next = node; + cur = node->next; + } + + for (RandomListNode* cur = head; cur != nullptr; ) { + if (cur->random != NULL) + cur->next->random = cur->random->next; + cur = cur->next->next; + } + + // 分拆兩個單鏈表 + RandomListNode dummy(-1); + for (RandomListNode* cur = head, *new_cur = &dummy; + cur != nullptr; ) { + new_cur->next = cur->next; + new_cur = new_cur->next; + cur->next = cur->next->next; + cur = cur->next; + } + return dummy.next; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + + +\subsection{Linked List Cycle} +\label{sec:linked-list-cycle} + + +\subsubsection{描述} +Given a linked list, determine if it has a cycle in it. + +Follow up: +Can you solve it without using extra space? + + +\subsubsection{分析} +最容易想到的方法是,用一個哈希表\fn{unordered_map visited},記錄每個元素是否被訪問過,一旦出現某個元素被重複訪問,説明存在環。空間複雜度$O(n)$,時間複雜度$O(N)$。 + +最好的方法是時間複雜度$O(n)$,空間複雜度$O(1)$的。設置兩個指針,一個快一個慢,快的指針每次走兩步,慢的指針每次走一步,如果快指針和慢指針相遇,則説明有環。參考\myurl{ http://leetcode.com/2010/09/detecting-loop-in-singly-linked-list.html} + + +\subsubsection{代碼} +\begin{Code} +//LeetCode, Linked List Cycle +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + bool hasCycle(ListNode *head) { + // 設置兩個指針,一個快一個慢 + ListNode *slow = head, *fast = head; + while (fast && fast->next) { + slow = slow->next; + fast = fast->next->next; + if (slow == fast) return true; + } + return false; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Linked List Cycle II, 見 \S \ref{sec:linked-list-cycle-ii} +\myenddot + + +\subsection{Linked List Cycle II} +\label{sec:linked-list-cycle-ii} + + +\subsubsection{描述} +Given a linked list, return the node where the cycle begins. If there is no cycle, return \fn{null}. + +Follow up: +Can you solve it without using extra space? + + +\subsubsection{分析} +當fast與slow相遇時,slow肯定沒有遍歷完鏈表,而fast已經在環內循環了$n$圈($1 \leq n$)。假設slow走了$s$步,則fast走了$2s$步(fast步數還等於$s$加上在環上多轉的$n$圈),設環長為$r$,則: +\begin{eqnarray} +2s &=& s + nr \nonumber \\ +s &=& nr \nonumber +\end{eqnarray} + +設整個鏈表長$L$,環入口點與相遇點距離為$a$,起點到環入口點的距離為$x$,則 +\begin{eqnarray} +x + a &=& nr = (n – 1)r +r = (n-1)r + L - x \nonumber \\ +x &=& (n-1)r + (L – x – a) \nonumber +\end{eqnarray} + +$L – x – a$為相遇點到環入口點的距離,由此可知,從鏈表頭到環入口點等於$n-1$圈內環+相遇點到環入口點,於是我們可以從\fn{head}開始另設一個指針\fn{slow2},兩個慢指針每次前進一步,它倆一定會在環入口點相遇。 + + +\subsubsection{代碼} +\begin{Code} +//LeetCode, Linked List Cycle II +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + ListNode *detectCycle(ListNode *head) { + ListNode *slow = head, *fast = head; + while (fast && fast->next) { + slow = slow->next; + fast = fast->next->next; + if (slow == fast) { + ListNode *slow2 = head; + + while (slow2 != slow) { + slow2 = slow2->next; + slow = slow->next; + } + return slow2; + } + } + return nullptr; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Linked List Cycle, 見 \S \ref{sec:linked-list-cycle} +\myenddot + + +\subsection{Reorder List} +\label{sec:reorder-list} + + +\subsubsection{描述} +Given a singly linked list $L: L_0 \rightarrow L_1 \rightarrow \cdots \rightarrow L_{n-1} \rightarrow L_n$, +reorder it to: $L_0 \rightarrow L_n \rightarrow L_1 \rightarrow L_{n-1} \rightarrow L_2 \rightarrow L_{n-2} \rightarrow \cdots$ + +You must do this in-place without altering the nodes' values. + +For example, +Given \fn{\{1,2,3,4\}}, reorder it to \fn{\{1,4,2,3\}}. + + +\subsubsection{分析} +題目規定要in-place,也就是説只能使用$O(1)$的空間。 + +可以找到中間節點,斷開,把後半截單鏈表reverse一下,再合併兩個單鏈表。 + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Reorder List +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + void reorderList(ListNode *head) { + if (head == nullptr || head->next == nullptr) return; + + ListNode *slow = head, *fast = head, *prev = nullptr; + while (fast && fast->next) { + prev = slow; + slow = slow->next; + fast = fast->next->next; + } + prev->next = nullptr; // cut at middle + + slow = reverse(slow); + + // merge two lists + ListNode *curr = head; + while (curr->next) { + ListNode *tmp = curr->next; + curr->next = slow; + slow = slow->next; + curr->next->next = tmp; + curr = tmp; + } + curr->next = slow; + } + + ListNode* reverse(ListNode *head) { + if (head == nullptr || head->next == nullptr) return head; + + ListNode *prev = head; + for (ListNode *curr = head->next, *next = curr->next; curr; + prev = curr, curr = next, next = next ? next->next : nullptr) { + curr->next = prev; + } + head->next = nullptr; + return prev; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + +\subsection{Palindrome Linked List} +\label{sec:palindrome-list} + + +\subsubsection{描述} +Given a singly linked list, determine if it is a palindrome. + +For example, +Given \code{1->2}, return false. +Given \code{1->2->2->1}, return true; +Given \code{1->0->1}, return true; + + +\subsubsection{分析} +Cut in middle, reverse, compare for difference, recover, return result + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Palindrome List +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + bool isPalindrome(ListNode* head) { + if (!head || head->next == nullptr) return true; + + // go to half + ListNode *slow, *fast; slow = fast = head; + while (fast->next && fast->next->next) { + slow = slow->next; + fast = fast->next->next; + } + fast = slow->next; + slow->next = nullptr; // cut + + // reverse half + ListNode *head2 = reverseList(fast); + + // check if not equal + bool result = true; + for (ListNode *l1 = head, *l2 = head2; + l1 && l2; + l1 = l1->next, l2 = l2->next) { + if (l1->val != l2->val) { + result = false; + break; + } + } + + // recover + slow->next = reverseList(head2); + + // return result + return result; + } +private: + ListNode* reverseList(ListNode* head) { + if (!head || head->next == nullptr) return head; + + ListNode *prev, *cur; + prev = head; cur = prev->next; + while (cur) { + ListNode *next = cur->next; + cur->next = prev; + prev = cur; + cur = next; + } + head->next = nullptr; + + return prev; + } +}; +\end{Code} + +\subsection{Add Two Numbers II} +\label{sec:add-two-numbers-ii} + + +\subsubsection{描述} +You are given two \textbf{non-empty} linked lists representing two non-negative integers. The most significant digit comes first and each of their nodes contain a single digit. Add the two numbers and return it as a linked list. + +You may assume the two numbers do not contain any leading zero, except the number 0 itself. + + +Input: {\small \fontspec{Latin Modern Mono} (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)} + +Output: {\small \fontspec{Latin Modern Mono} 7 -> 8 -> 0 -> 7} + + +\subsubsection{分析} +跟Add Binary(見 \S \ref{sec:add-binary})很類似 + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Add Two Numbers II +// 跟Add Binary 很類似 +// 時間複雜度O(m+n),空間複雜度O(1) +class Solution { +public: + ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { + // 換成雙向容器 (string) + // 由尾至頭推進,使用插頭法 + // convert to string + string s1 = ListToStr(l1); + string s2 = ListToStr(l2); + + int carry = 0; + ListNode dummy(-1, nullptr); + // add stirng and create list + for (auto r1 = s1.rbegin(), r2 = s2.rbegin(); + r1 != s1.rend() || r2 != s2.rend(); + r1 = r1 != s1.rend() ? next(r1) : r1, r2 = r2 != s2.rend() ? next(r2) : r2) + { + int v1 = r1 != s1.rend() ? *r1 - '0' : 0; + int v2 = r2 != s2.rend() ? *r2 - '0' : 0; + int sum = v1 + v2 + carry; + + dummy.next = new ListNode(sum % 10, dummy.next); // 插頭法 + carry = sum / 10; + } + if (carry > 0) + dummy.next = new ListNode(carry, dummy.next); + + // reverse the list + return dummy.next; + } +private: + string ListToStr(ListNode *head) + { + string result; + + while (head) + { + result += '0' + head->val; + head = head->next; + } + return result; + } +}; +\end{Code} + + +\subsubsection{相關題目} + +\begindot +\item Add Two Numbers, 見 \S \ref{sec:add-two-numbers} +\myenddot + +\subsection{Insert into a Cyclic Sorted List} +\label{sec:insert-into-a-cyclic-sorted-list} + + +\subsubsection{描述} +Given a node from a Circular Linked List which is sorted in ascending order, write a function to insert a value insertVal into the list such that it remains a sorted circular list. The given node can be a reference to any single node in the list, and may not be necessarily the smallest value in the circular list. + +If there are multiple suitable places for insertion, you may choose any place to insert the new value. After the insertion, the circular list should remain sorted. + +If the list is empty (i.e., given node is null), you should create a new single circular list and return the reference to that single node. Otherwise, you should return the original given node. + +Example 1: +\begin{Code} +Input: head = [3,4,1], insertVal = 2 +Output: [3,4,1,2] +Explanation: In the figure above, there is a sorted circular list of three elements. You are given a reference to the node with value 3, and we need to insert 2 into the list. The new node should be inserted between node 1 and node 3. After the insertion, the list should look like this, and we should still return node 3. +\end{Code} + +\begin{center} +\includegraphics[width=150pt]{insert-into-a-cyclic-sorted-list-001.jpg}\\ +\figcaption{before insert}\label{fig:insert-into-a-cyclic-sorted-list-001} +\end{center} + +\begin{center} +\includegraphics[width=150pt]{insert-into-a-cyclic-sorted-list-002.jpg}\\ +\figcaption{before insert}\label{fig:insert-into-a-cyclic-sorted-list-002} +\end{center} + +\subsubsection{分析} +Nil + +\subsubsection{代碼} +\begin{Code} +// LeetCode +// 時間複雜度O(logn),空間複雜度O(n) +class Solution { +public: + Node* insert(Node* head, int insertVal) { + // edge case [1,1,1], target: 0 + // edge case [2,3,5,1], target: 0 + if (head == nullptr) { + head = new Node(insertVal); + head->next = head; + } + else { + Node *cur = head->next; + Node *prev = head; + Node *pivot = nullptr; + while (!(cur->val >= insertVal && prev->val < insertVal)) { + if (prev->val > cur->val) + pivot = prev; // 找出邊界, case [2,3,5,1], target: 0 + + prev = cur; + cur = cur->next; + + if (cur == head->next) break; // 走了一個循環也沒有結果 + } + if (cur == head->next && pivot) + pivot->next = new Node(insertVal, pivot->next); + else + prev->next = new Node(insertVal, cur); + } + return head; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + + +\subsection{LRU Cache} +\label{sec:lru-cache} + + +\subsubsection{描述} +Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set. + +\fn{get(key)} - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1. + +\fn{set(key, value)} - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item. + + +\subsubsection{分析} +為了使查找、插入和刪除都有較高的性能,我們使用一個雙向鏈表(\fn{std::list})和一個哈希表(\fn{std::unordered_map}),因為: +\begin{itemize} +\item{哈希表保存每個節點的地址,可以基本保證在$O(1)$時間內查找節點} +\item{雙向鏈表插入和刪除效率高,單向鏈表插入和刪除時,還要查找節點的前驅節點} +\end{itemize} + +具體實現細節: +\begin{itemize} +\item{越靠近鏈表頭部,表示節點上次訪問距離現在時間最短,尾部的節點表示最近訪問最少} +\item{訪問節點時,如果節點存在,把該節點交換到鏈表頭部,同時更新hash表中該節點的地址} +\item{插入節點時,如果cache的size達到了上限capacity,則刪除尾部節點,同時要在hash表中刪除對應的項;新節點插入鏈表頭部} +\end{itemize} + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, LRU Cache +// 時間複雜度O(logn),空間複雜度O(n) +class LRUCache{ +private: + struct CacheNode { + int key; + int value; + CacheNode(int k, int v) :key(k), value(v){} + }; +public: + LRUCache(int capacity) { + this->capacity = capacity; + } + + int get(int key) { + if (cacheMap.find(key) == cacheMap.end()) return -1; + + // 把當前訪問的節點移到鏈表頭部,並且更新map中該節點的地址 + cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]); + cacheMap[key] = cacheList.begin(); + return cacheMap[key]->value; + } + + void set(int key, int value) { + if (this->capacity == 0) return; + if (cacheMap.find(key) == cacheMap.end()) { + if (cacheList.size() == capacity) { //刪除鏈表尾部節點(最少訪問的節點) + cacheMap.erase(cacheList.back().key); + cacheList.pop_back(); + } + // 插入新節點到鏈表頭部, 並且在map中增加該節點 + cacheList.push_front(CacheNode(key, value)); + cacheMap[key] = cacheList.begin(); + } else { + //更新節點的值,把當前訪問的節點移到鏈表頭部,並且更新map中該節點的地址 + cacheMap[key]->value = value; + cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]); + cacheMap[key] = cacheList.begin(); + } + } +private: + list cacheList; + unordered_map::iterator> cacheMap; + int capacity; +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + +\section{雙鏈表} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +雙鏈表節點的定義如下: +\begin{Code} +// 雙鏈表節點 +class Node { +public: + int val; + Node* prev; + Node* next; + Node* child; +}; +\end{Code} + +\subsection{Flatten a Multilevel Doubly Linked List} +\label{sec:flatten-a-multilevel-doubly-linked-list} + + +\subsubsection{描述} +You are given a doubly linked list which in addition to the next and previous pointers, it could have a child pointer, which may or may not point to a separate doubly linked list. These child lists may have one or more children of their own, and so on, to produce a multilevel data structure, as shown in the example below. + +Flatten the list so that all the nodes appear in a single-level, doubly linked list. You are given the head of the first level of the list. + +\begin{center} +\includegraphics[width=300pt]{flatten-a-multilevel-doubly-list.png}\\ +\figcaption{candy crush}\label{fig:flatten-a-multilevel-double-list} +\end{center} + +\subsubsection{分析} +Nil + +\subsubsection{遞歸版} +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + Node* flatten(Node* head) { + for (Node *cur = head; cur; ) { + if (cur->child == nullptr) + cur = cur->next; + else { + cur->next = flatten_(cur->child, cur->next); + cur->next->prev = cur; + cur->child = nullptr; + } + } + return head; + } +private: + Node *flatten_(Node* head, Node *tail) { + Node *cur = head; + while (cur->next) { + if (cur->child == nullptr) + cur = cur->next; + else { + Node *next = cur->next; + cur->next = flatten_(cur->child, next); + cur->next->prev = cur; + cur->child = nullptr; + cur = next; + } + } + cur->next = tail; + if (tail) + tail->prev = cur; + + return head; + } +}; +\end{Code} + +\subsubsection{Stack} +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + Node* flatten(Node* head) { + if (head == nullptr) return head; + + stack cache; + + Node *prev = nullptr; + cache.push(head); + while (!cache.empty()) { + Node *cur = cache.top(); + cache.pop(); + + if (prev) + prev->next = cur; + cur->prev = prev; + + if (cur->next) cache.push(cur->next); + if (cur->child) cache.push(cur->child); + cur->child = nullptr; + + prev = cur; + } + + return head; + } +}; +\end{Code} + +\subsubsection{相關題目} + +\begindot +\item Nil +\myenddot diff --git a/C++/chapRemake.tex b/C++/chapRemake.tex new file mode 100644 index 00000000..402d56c5 --- /dev/null +++ b/C++/chapRemake.tex @@ -0,0 +1,211 @@ +\chapter{Remake Data Structure} +There are examples to try to remake those data strucutre in standard libraries for a practice purpose. +\newline + +\section{Smart Pointer} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:smart-pointer} + + +\subsubsection{描述} +Know more about smart pointer and reference counting. + +\subsubsection{分析} +Nil + + +\subsubsection{代碼} +\begin{Code} +#include +#include "DefaultMutex.h" + +class ReferenceCount +{ + public: + ReferenceCount(); + ~ReferenceCount(); + + void AddRef(); + int Release(); + private: + int m_count; + DefaultMutex m_mutex; +}; + +ReferenceCount::ReferenceCount() +{ + m_count = 0; +} + +ReferenceCount::~ReferenceCount() +{ +} + +void ReferenceCount::AddRef() +{ + DefaultLock lock(m_mutex); + m_count++; +} + +int ReferenceCount::Release() +{ + DefaultLock lock(m_mutex); + if (m_count > 0) + { + return --m_count; + } + else + { + m_count = 0; + return m_count; + } +} + +template +class SmartPointer +{ + public: + SmartPointer(); + SmartPointer(T* pData); + SmartPointer(const SmartPointer& smartPointer); + template + SmartPointer(const SmartPointer& smartPointer, T* pData); + ~SmartPointer(); + + T& operator* (); + T* operator-> (); + SmartPointer& operator = (const SmartPointer& smartPointer); + + T* Get(); + T* Get() const; + + template friend class SmartPointer; // with this friend, we can define the following constructor, SmartPointer(const SmartPointer& smartPointer, T* pData) + private: + T* m_pData; + ReferenceCount* m_pRC; +}; + +template +SmartPointer MakeSmartPointer(); + +template + SmartPointer StaticCast(const SmartPointer& smartPointer); + +template + SmartPointer DynamicCast(const SmartPointer& smartPointer); + +////////////////////////////////////////////////////////////////////////////////// +// Start implementation +////////////////////////////////////////////////////////////////////////////////// + +template +SmartPointer::SmartPointer() + : m_pData(NULL), m_pRC(NULL) +{ + m_pRC = new ReferenceCount(); +} + +template +SmartPointer::SmartPointer(T* pData) + : m_pData(pData), m_pRC(NULL) +{ + m_pRC = new ReferenceCount(); + m_pRC->AddRef(); +} + +template +SmartPointer::SmartPointer(const SmartPointer& smartPointer) + : m_pData(smartPointer.m_pData), m_pRC(smartPointer.m_pRC) +{ + m_pRC->AddRef(); +} + +template +template +SmartPointer::SmartPointer(const SmartPointer& smartPointer, T* pData) + : m_pData(pData), m_pRC(smartPointer.m_pRC) +{ + m_pRC->AddRef(); +} + +template +SmartPointer::~SmartPointer() +{ + if (m_pRC->Release() == 0) + { + if (m_pData) + delete m_pData; + delete m_pRC; + } +} + +template +T& SmartPointer::operator* () +{ + return *m_pData; +} + +template +T* SmartPointer::operator-> () +{ + return m_pData; +} + +template +SmartPointer& SmartPointer::operator = (const SmartPointer& smartPointer) +{ + if (this != &smartPointer) + { + if (m_pRC->Release() == 0) + { + if (m_pData) + delete m_pData; + delete m_pRC; + } + + m_pData = smartPointer.m_pData; + m_pRC = smartPointer.m_pRC; + m_pRC->AddRef(); + } + return *this; +} + +template +T* SmartPointer::Get() +{ + return m_pData; +} + +template +T* SmartPointer::Get() const +{ + return m_pData; +} + +template +SmartPointer MakeSmartPointer() +{ + SmartPointer smartPointer(new T()); + return smartPointer; +} + +template +SmartPointer StaticCast(const SmartPointer& smartPointer) +{ + T* p = static_cast(smartPointer.Get()); + return SmartPointer(smartPointer, p); +} + +template +SmartPointer DynamicCast(const SmartPointer& smartPointer) +{ + T* p = dynamic_cast(smartPointer.Get()); + if (p) + return SmartPointer(smartPointer, p); + else + return SmartPointer(); +} +\end{Code} + + +\subsubsection{相關題目} +Nil diff --git a/C++/chapSearching.tex b/C++/chapSearching.tex index e94d3c38..291e5f61 100644 --- a/C++/chapSearching.tex +++ b/C++/chapSearching.tex @@ -10,27 +10,28 @@ \subsubsection{描述} Your algorithm's runtime complexity must be in the order of $O(\log n)$. -If the target is not found in the array, return \code{[-1, -1]}. +If the target is not found in the array, return \code{\[-1, -1\]}. For example, -Given \code{[5, 7, 7, 8, 8, 10]} and target value 8, -return \code{[3, 4]}. +Given \code{\[5, 7, 7, 8, 8, 10\]} and target value 8, +return \code{\[3, 4\]}. \subsubsection{分析} -已经排好了序,用二分查找。 +已經排好了序,用二分查找。 \subsubsection{使用STL} \begin{Code} // LeetCode, Search for a Range -// 偷懒的做法,使用STL -// 时间复杂度O(logn),空间复杂度O(1) +// 偷懶的做法,使用STL +// 時間複雜度O(logn),空間複雜度O(1) class Solution { public: 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))); + 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 @@ -40,11 +41,11 @@ \subsubsection{使用STL} \end{Code} -\subsubsection{重新实现 lower_bound 和 upper_bound} +\subsubsection{重新實現 lower_bound 和 upper_bound} \begin{Code} // LeetCode, Search for a Range -// 重新实现 lower_bound 和 upper_bound -// 时间复杂度O(logn),空间复杂度O(1) +// 重新實現 lower_bound 和 upper_bound +// 時間複雜度O(logn),空間複雜度O(1) class Solution { public: vector searchRange (vector& nums, int target) { @@ -54,7 +55,8 @@ \subsubsection{重新实现 lower_bound 和 upper_bound} if (lower == nums.end() || *lower != target) return vector { -1, -1 }; else - return vector {distance(nums.begin(), lower), distance(nums.begin(), prev(uppper))}; + return vector {distance(nums.begin(), lower), + distance(nums.begin(), prev(uppper))}; } template @@ -76,7 +78,7 @@ \subsubsection{重新实现 lower_bound 和 upper_bound} while (first != last) { auto mid = next(first, distance (first, last) / 2); - if (value >= *mid) first = ++mid; // 与 lower_bound 仅此不同 + if (value >= *mid) first = ++mid; // 與 lower_bound 僅此不同 else last = mid; } @@ -85,9 +87,9 @@ \subsubsection{重新实现 lower_bound 和 upper_bound} }; \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Search Insert Position, 见 \S \ref{sec:search-insert-position} +\item Search Insert Position, 見 \S \ref{sec:search-insert-position} \myenddot @@ -113,11 +115,11 @@ \subsubsection{分析} 即\fn{std::lower_bound()}。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Search Insert Position -// 重新实现 lower_bound -// 时间复杂度O(logn),空间复杂度O(1) +// 重新實現 lower_bound +// 時間複雜度O(logn),空間複雜度O(1) class Solution { public: int searchInsert(vector& nums, int target) { @@ -140,9 +142,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Search for a Range, 见 \S \ref{sec:search-for-a-range} +\item Search for a Range, 見 \S \ref{sec:search-for-a-range} \myenddot @@ -172,10 +174,10 @@ \subsubsection{分析} 二分查找。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Search a 2D Matrix -// 时间复杂度O(logn),空间复杂度O(1) +// 時間複雜度O(logn),空間複雜度O(1) class Solution { public: bool searchMatrix(const vector>& matrix, int target) { @@ -204,7 +206,935 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot + +\section{Search a 2D Matrix II} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:search-a-2d-matrix-ii} + + +\subsubsection{描述} +Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties: +\begindot +\item Integers in each row are sorted in ascending from left to right. +\item Integers in each column are sorted in ascending from top to bottom. +\myenddot + +For example, Consider the following matrix: +\begin{Code} +[ + [1, 4, 7, 11, 15], + [2, 5, 8, 12, 19], + [3, 6, 9, 16, 22], + [10, 13, 14, 17, 24], + [18, 21, 23, 26, 30] +] +\end{Code} +Given \fn{target = 3}, return true. + + +\subsubsection{分析} +二分查找。 + + +\subsubsection{代碼} +\begin{Code} +// 時間複雜度O(m+n),空間複雜度O(1) +class Solution { +public: + bool searchMatrix(vector>& matrix, int target) { + int row = matrix.size() - 1; + if (row < 0) return false; + int col = 0; + + while (row >= 0 && col < matrix[0].size()) + { + if (matrix[row][col] > target) + row--; + else if (matrix[row][col] < target) + col++; + else + return true; + } + + return false; + } +}; +\end{Code} + +\subsubsection{分治} +\begin{Code} +// 時間複雜度O(nlogn),空間複雜度O(logn) +class Solution { +public: + bool searchMatrix(vector>& matrix, int target) { + int M = matrix.size(); + if (M == 0) return false; + int N = matrix[0].size(); + if (N == 0) return false; + + return searchRec(matrix, target, 0, 0, N-1, M-1); + } +private: + bool searchRec(vector>& matrix, const int& target + , int left, int up, int right, int down) + { + // 若果 matrix 已經不成形 + if (left > right || up > down) + return false; + else if (target < matrix[up][left] || target > matrix[down][right]) + return false; // target 不在 matrix 之中 + + int mid = left + (right - left) / 2; // 使用 index 中 + int row = up; + while (row <= down && matrix[row][mid] <= target) + { + if (matrix[row][mid] == target) + return true; + row++; + } + return searchRec(matrix, target, left, row, mid-1, down) + || searchRec(matrix, target, mid+1, up, right, row-1); + } +}; +\end{Code} + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + +\section{Find Peak Element} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:find-peadk-element} + + +\subsubsection{描述} +A peak element is an element that is greater than its neighbors. + +Given an input array nums, where nums[i] ≠ nums[i+1], find a peak element and return its index. + +The array may contain multiple peaks, in that case return the index to any one of the peaks is fine. + +You may imagine that nums[-1] = nums[n] = -∞. + +Example 1: +\begin{Code} +Input: nums = [1,2,3,1] +Output: 2 +Explanation: 3 is a peak element and your function should return the index number 2. +\end{Code} + +Example 2: +\begin{Code} +Input: nums = [1,2,1,3,5,6,4] +Output: 1 or 5 +Explanation: Your function can return either index number 1 where the peak element is 2, + or index number 5 where the peak element is 6. +\end{Code} + +\subsubsection{分析} +二分查找。 + + +\subsection{迭代} +\begin{Code} +// 時間複雜度O(logn),空間複雜度O(logn) +class Solution { +public: + int findPeakElement(vector& nums) { + return findPeak(nums, 0, nums.size()-1); + } +private: + int findPeak(const vector& nums, int left, int right) + { + while (left <= right) + { + int mid = left + (right - left) / 2; + + if (left == right) + return left; + if (nums[mid] > nums[mid + 1]) + right = mid; + else + left = ++mid; + } + return left; + } +}; +\end{Code} + +\subsection{遞歸} +\begin{Code} +// 時間複雜度O(logn),空間複雜度O(logn) +class Solution { +public: + int findPeakElement(vector& nums) { + return findPeak(nums, 0, nums.size()-1); + } +private: + int findPeak(const vector& nums, int left, int right) + { + if (left == right) + return left; + int mid = left + (right - left) / 2; + if (nums[mid] > nums[mid+1]) + return findPeak(nums, left, mid); + else + return findPeak(nums, ++mid, right); + } +}; +\end{Code} + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + +\section{Find Minimum in Rotated Sorted Array} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:find-minium-in-rotated-sorted-array} + + +\subsubsection{描述} +Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. + +(i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]). + +Find the minimum element. + +You may assume no duplicate exists in the array. + +Example 1: +\begin{Code} +Input: [3,4,5,1,2] +Output: 1 +\end{Code} + +Example 2: +\begin{Code} +Input: [4,5,6,7,0,1,2] +Output: 0 +\end{Code} + +\subsubsection{分析} +二分查找。 + + +\subsection{迭代} +\begin{Code} +// 時間複雜度O(logn),空間複雜度O(logn) +class Solution { +public: + int findMin(vector& nums) { + int left = 0; + int right = nums.size(); + + int minVal = INT_MAX; + while (left < right) { + int mid = left + (right - left) / 2; + + int index = 0; + + if (mid == left) { + minVal = min(minVal, nums[mid]); + break; + } + if (nums[left] < nums[mid]) { + index = left; + left = ++mid; + } + else if (nums[left] > nums[mid]) { + index = mid; + right = mid; + } + else + left++; + + minVal = min(minVal, nums[index]); + } + + return minVal; + } +}; +\end{Code} +\section{Find K Closest Elements} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:find-k-closest-elements} + + +\subsubsection{描述} +Given a sorted array arr, two integers k and x, find the k closest elements to x in the array. The result should also be sorted in ascending order. If there is a tie, the smaller elements are always preferred. + +Example 1: +\begin{Code} +Input: arr = [1,2,3,4,5], k = 4, x = 3 +Output: [1,2,3,4] +\end{Code} + +Example 2: +\begin{Code} +Input: arr = [1,2,3,4,5], k = 4, x = -1 +Output: [1,2,3,4] +\end{Code} + +\subsubsection{分析} +二分查找。 + + +\subsection{迭代} +\begin{Code} +// 時間複雜度O(logn),空間複雜度O(logn) +class Solution { +public: + vector findClosestElements(vector& arr, int k, int x) { + if ((int)arr.size() <= k) return arr; + + auto pos = ClosestSearch(arr.begin(), arr.end(), x); + // 使用兩個指針,往左右走開,若差相同,先取左(細),後取右(大) + if (pos == arr.end()) { + return vector(prev(arr.end(), k), arr.end()); + } + else { + auto left = pos; + auto right = next(pos); + cout << *pos << endl; + --k; + + while (k && left != arr.begin() && right != arr.end()) { + // 若差相同,先取左(細),後取右(大) + if (abs(*prev(left) - x) <= abs(*right - x)) + left = prev(left); + else + right = next(right); + --k; + } + if (k) { + if (left == arr.begin()) { + while (k--) right = next(right); + } + else { + while (k--) left = prev(left); + } + } + + return vector(left, right); + } + } +private: + template + RandIT ClosestSearch(RandIT left, RandIT right, const T& target) { + RandIT first = left; + RandIT last = right; + while (left != right) { + auto mid = next(left, distance(left, right) / 2); + + if (*mid == target) return mid; + if (*mid < target) + left = next(mid); + else + right = mid; + } + + if (left == first) return left; + if (left != last) { + if (abs(*prev(left) - target) <= abs(*left - target)) + return prev(left); + else + return left; + } + + return left; + } +}; +\end{Code} + + +\section{Closest Binary Search Tree Value} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:closest-binary-search-tree-value} + + +\subsubsection{描述} +Given a non-empty binary search tree and a target value, find the value in the BST that is closest to the target. + +Note: +\begindot +\item Given target value is a floating point. +\item You are guaranteed to have only one unique value in the BST that is closest to the target. +\myenddot + + +Example 1: +\begin{Code} +Input: root = [4,2,5,1,3], target = 3.714286 + + 4 + / \ + 2 5 + / \ +1 3 + +Output: 4 +\end{Code} + +\subsubsection{分析} +二分查找。 + + +\subsection{迭代} +\begin{Code} +// 時間複雜度O(logn),空間複雜度O(1) +class Solution { +public: + int closestValue(TreeNode* root, double target) { + if (root == nullptr) return target; + + int curMin = root->val; + + queue q; + q.push(root); + while (!q.empty()) { + TreeNode *cur = q.front(); + q.pop(); + + if (abs(cur->val - target) < abs(curMin - target)) { + curMin = cur->val; + if (abs(curMin - target) == 0) return curMin; + } + + if (cur->val > target && cur->left) + q.push(cur->left); + else if (cur->val < target && cur->right) + q.push(cur->right); + } + + return curMin; + } +}; +\end{Code} + +\subsection{遞歸} +\begin{Code} +// 時間複雜度O(logn),空間複雜度O(1) +class Solution { +public: + int closestValue(TreeNode* root, double target) { + double closest = root->val; + PreOrder(root, target, closest); + + return closest; + } +private: + void PreOrder(TreeNode *root, const double& target, double& closest) { + if (closest == target) return; + if (root == nullptr) return; + + if (abs(root->val - target) < abs(closest - target)) { + closest = root->val; + + if (closest == target) return; + } + + if (root->val > target) + PreOrder(root->left, target, closest); + else + PreOrder(root->right, target, closest); + } +}; +\end{Code} + +\section{Search in a Sorted Array of Unknown Size} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:search-in-a-sorted-array-of-unknown-size} + + +\subsubsection{描述} +Given an integer array sorted in ascending order, write a function to search target in nums. If target exists, then return its index, otherwise return -1. However, the array size is unknown to you. You may only access the array using an ArrayReader interface, where ArrayReader.get(k) returns the element of the array at index k (0-indexed). + +You may assume all integers in the array are less than 10000, and if you access the array out of bounds, ArrayReader.get will return 2147483647. + + +Example 1: +\begin{Code} +Input: array = [-1,0,3,5,9,12], target = 9 +Output: 4 +Explanation: 9 exists in nums and its index is 4 +\end{Code} + +Example 2: +\begin{Code} +Input: array = [-1,0,3,5,9,12], target = 2 +Output: -1 +Explanation: 2 does not exist in nums so return -1 +\end{Code} + +Note: +\begindot +\item You may assume that all elements in the array are unique. +\item The value of each element in the array will be in the range [-9999, 9999]. +\myenddot + + +\subsubsection{分析} +二分查找。 + + +\subsection{迭代} +\begin{Code} +// 時間複雜度O(logn),空間複雜度O(1) +class Solution { +public: + int search(const ArrayReader& reader, int target) { + // 由 0 開始的 binary search + int left = 0; + int right = 1; + int OutBound = 2147483647; + + while (left < right) { + int mid = left + (right - left) / 2; + + if (reader.get(mid) == target) return mid; + if (reader.get(mid) == OutBound) + right = mid; + else if (reader.get(mid) < target) { + left = ++mid; + right <<= 1; // times 2 + } + else + right = mid; + } + + return -1; + } +}; +\end{Code} + + +\section{Find Smallest Letter Greater Than Target} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:find-smallest-letter-greater-than-target} + + +\subsubsection{描述} +Given a list of sorted characters letters containing only lowercase letters, and given a target letter target, find the smallest element in the list that is larger than the given target. + +Letters also wrap around. For example, if the target is target = 'z' and letters = ['a', 'b'], the answer is 'a'. + + +Example: +\begin{Code} +Input: +letters = ["c", "f", "j"] +target = "a" +Output: "c" + +Input: +letters = ["c", "f", "j"] +target = "c" +Output: "f" + +Input: +letters = ["c", "f", "j"] +target = "d" +Output: "f" + +Input: +letters = ["c", "f", "j"] +target = "g" +Output: "j" + +Input: +letters = ["c", "f", "j"] +target = "j" +Output: "c" + +Input: +letters = ["c", "f", "j"] +target = "k" +Output: "c" +\end{Code} + +Note: +\begindot +\item letters has a length in range [2, 10000]. +\item letters consists of lowercase letters, and contains at least 2 unique letters. +\item target is a lowercase letter. +\myenddot + + +\subsubsection{分析} +Use upper bound. + + +\subsection{迭代} +\begin{Code} +// 時間複雜度O(logn),空間複雜度O(1) +class Solution { +public: + char nextGreatestLetter(vector& letters, char target) { + if (letters.size() == 0) return 'a'; + if (letters.size() == 1) return letters[0]; + + int left = 0; + int right = letters.size(); + + while (left < right) { + int mid = left + (right - left) / 2; + + if (letters[mid] <= target) + left = ++mid; + else + right = mid; + } + + if (left >= (int)letters.size() || letters[left] < target) + return letters[0]; + else + return letters[left]; + } +}; +\end{Code} + +\section{Split Array Largest Sum} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:split-array-largest-sum} + + +\subsubsection{描述} +Given an array which consists of non-negative integers and an integer m, you can split the array into m non-empty continuous subarrays. Write an algorithm to minimize the largest sum among these m subarrays. + +Example: +\begin{Code} +Input: +nums = [7,2,5,10,8] +m = 2 + +Output: +18 + +Explanation: +There are four ways to split nums into two subarrays. +The best way is to split it into [7,2,5] and [10,8], +where the largest sum among the two subarrays is only 18. +\end{Code} + +Note: +If n is the length of array, assume the following constraints are satisfied: +\begindot +\item 1 <= n <= 1000 +\item 1 <= m <= min(50, n) +\myenddot + + + +\subsection{暴力 - Brute Force} +\begin{Code} +// 時間複雜度O(n^m),空間複雜度O(n) +class Solution { +public: + int ans; + int n, m; + void dfs(vector& nums, int i, int cntSubarrays, int curSum, int curMax) { + if (i == n && cntSubarrays == m) { + ans = min(ans, curMax); + return; + } + if (i == n) { + return; + } + if (i > 0) { + dfs(nums, i + 1, cntSubarrays, curSum + nums[i], max(curMax, curSum + nums[i])); + } + if (cntSubarrays < m) { + dfs(nums, i + 1, cntSubarrays + 1, nums[i], max(curMax, nums[i])); + } + } + int splitArray(vector& nums, int M) { + ans = INT_MAX; + n = nums.size(); + m = M; + dfs(nums, 0, 0, 0, 0); + return ans; + } +}; +\end{Code} + +\subsubsection{分析} +Let's define f[i][j] to be the minimum largest subarray sum for splitting nums[0..i] into j parts. + +Consider the jth subarray. We can split the array from a smaller index k to i to form it. Thus f[i][j] can be derived from max(f[k][j - 1], nums[k + 1] + ... + nums[i]). For all valid index k, f[i][j] should choose the minimum value of the above formula. + +The final answer should be f[n][m], where n is the size of the array. + +For corner situations, all the invalid f[i][j] should be assigned with INFINITY, and f[0][0] should be initialized with 0. + +\subsection{動規} +\begin{Code} +// 時間複雜度O(n^2 * m),空間複雜度O(n*m) +class Solution { +public: + int splitArray(vector& nums, int m) { + int n = nums.size(); + vector> f(n + 1, vector(m + 1, INT_MAX)); + vector sub(n + 1, 0); + for (int i = 0; i < n; i++) { + sub[i + 1] = sub[i] + nums[i]; + } + f[0][0] = 0; + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + for (int k = 0; k < i; k++) { + f[i][j] = min(f[i][j], max(f[k][j - 1], sub[i] - sub[k])); + } + } + } + return f[n][m]; + } +}; +\end{Code} + +\subsection{二分尋找} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(n) +#define LL long long +class Solution { +public: + int splitArray(vector& nums, int m) { + LL l = 0, r = 0; + int n = nums.size(); + for (int i = 0; i < n; i++) { + r += nums[i]; + if (l < nums[i]) { + l = nums[i]; + } + } + LL ans = r; + while (l <= r) { + LL mid = (l + r) >> 1; // mid 是 sub-vector 的平均總加 + LL sum = 0; // 當下的 sub-vector 的總加 + int cnt = 1; // sub-vector 的總數 + for (int i = 0; i < n; i++) { + if (sum + nums[i] > mid) { + cnt ++; + sum = nums[i]; + } else { + sum += nums[i]; + } + } + if (cnt <= m) { + ans = min(ans, mid); + r = mid - 1; + } else { + l = mid + 1; + } + } + return ans; + } +}; +\end{Code} + +\section{Minimum Size Subarray Sum} +\label{sec:minimum-size-subarray-sum} + +\subsection{描述} +Given an array of n positive integers and a positive integer s, find the minimal length of a contiguous subarray of which the sum ≥ s. If there isn't one, return 0 instead. + +Example: +\begin{Code} +Input: s = 7, nums = [2,3,1,2,4,3] +Output: 2 +Explanation: the subarray [4,3] has the minimal length under the problem constraint. +\end{Code} + +\textbf{Follow up:}\newline +If you have figured out the O(n) solution, try coding another solution of which the time complexity is O(n log n). + +\subsection{分析} +使用兩個指針,當累積總和大過目標數時,左指針往右推進,否則右指針往右推進。 + +\subsection{雙指針} +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + int minSubArrayLen(int s, vector& nums) + { + int n = nums.size(); + int ans = INT_MAX; + int left = 0; + int sum = 0; + for (int i = 0; i < n; i++) { + sum += nums[i]; + while (sum >= s) { + ans = min(ans, i + 1 - left); + sum -= nums[left++]; + } + } + return (ans != INT_MAX) ? ans : 0; + } +}; +\end{Code} + + +\subsection{分析} +在順序的 array 當中計算得出移動總和。然後順序找㝷合條件的 subarray。並計算其長短。可以利用當前數值為 shifting value,加上目標數值和 lower_bound 找到合條件的 subarray。 + +\subsection{移動總加} +\begin{Code} +// LeetCode +// 時間複雜度O(nlogn),空間複雜度O(n) +class Solution { +public: + int minSubArrayLen(int s, vector& nums) + { + int n = nums.size(); + if (n == 0) + return 0; + int ans = INT_MAX; + vector sums(n + 1, 0); //size = n+1 for easier calculations + //sums[0]=0 : Meaning that it is the sum of first 0 elements + //sums[1]=A[0] : Sum of first 1 elements + //ans so on... + for (int i = 1; i <= n; i++) + sums[i] = sums[i - 1] + nums[i - 1]; + for (int i = 1; i <= n; i++) { + int to_find = s + sums[i - 1]; + auto bound = lower_bound(sums.begin(), sums.end(), to_find); + if (bound != sums.end()) { + ans = min(ans, static_cast(bound - (sums.begin() + i - 1))); + } + } + return (ans != INT_MAX) ? ans : 0; + } +}; +\end{Code} + +\section{Find Median from Data Stream} +\label{sec:find-median-from-data-stream} + +\subsection{描述} +Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value. + +For example, +\begin{Code} +[2,3,4], the median is 3 + +[2,3], the median is (2 + 3) / 2 = 2.5 +\end{Code} + +Design a data structure that supports the following two operations: + +\begindot +\item void addNum(int num) - Add a integer number from the data stream to the data structure. +\item double findMedian() - Return the median of all elements so far. +\myenddot + +Example: +\begin{Code} +addNum(1) +addNum(2) +findMedian() -> 1.5 +addNum(3) +findMedian() -> 2 +\end{Code} + +\subsection{保持順序} +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(1) +class MedianFinder { +public: + /** initialize your data structure here. */ + MedianFinder() { + + } + + void addNum(int num) { + if (m_cache.empty()) + m_cache.push_back(num); + else + m_cache.insert(lower_bound(m_cache.begin(), m_cache.end(), num), num); + } + + double findMedian() { + int N = m_cache.size(); + if (N & 1) + return m_cache[N/2]; + else + return (m_cache[N/2 - 1] + m_cache[N/2]) / 2.0; + } +private: + vector m_cache; +}; + +/** + * Your MedianFinder object will be instantiated and called as such: + * MedianFinder* obj = new MedianFinder(); + * obj->addNum(num); + * double param_2 = obj->findMedian(); + */ +\end{Code} + +\subsection{兩個 heap} +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(1) +class MedianFinder { + // lo 會維持住數值細過一半的數字 + priority_queue lo; // max heap + // hi 會維持住數值大過一半的數字 + priority_queue, greater> hi; // min heap + +public: + // Adds a number into the data structure. + void addNum(int num) + { + lo.push(num); // Add to max heap + + hi.push(lo.top()); // balancing step + lo.pop(); + + if (lo.size() < hi.size()) { // maintain size property + lo.push(hi.top()); + hi.pop(); + } + } + + // Returns the median of current data stream + double findMedian() + { + return lo.size() > hi.size() ? lo.top() : ((double) lo.top() + hi.top()) * 0.5; + } +}; +\end{Code} + +\subsection{STD method - AVL tree} +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(1) +class MedianFinder { +public: + /** initialize your data structure here. */ + MedianFinder() + : m_mid(m_data.end()) { + + } + + void addNum(int num) { + const int n = m_data.size(); + m_data.insert(num); + + if (!n) // first element inserted + m_mid = m_data.begin(); + else if (num < *m_mid) // median is decreased + m_mid = (n & 1 ? m_mid : prev(m_mid)); + else // median is increased + m_mid = (n & 1 ? next(m_mid) : m_mid); + } + + double findMedian() { + const int n = m_data.size(); + return ((double) *m_mid + *next(m_mid, n % 2 - 1)) * 0.5; + } +private: + multiset m_data; + multiset::iterator m_mid; +}; +\end{Code} diff --git a/C++/chapSorting.tex b/C++/chapSorting.tex index f92c115a..5b1bd84d 100644 --- a/C++/chapSorting.tex +++ b/C++/chapSorting.tex @@ -12,13 +12,13 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} //LeetCode, Merge Sorted Array -// 时间复杂度O(m+n),空间复杂度O(1) +// 時間複雜度O(m+n),空間複雜度O(1) class Solution { public: void merge(vector& A, int m, vector& B, int n) { @@ -34,10 +34,10 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Merge Two Sorted Lists,见 \S \ref{sec:merge-two-sorted-lists} -\item Merge k Sorted Lists,见 \S \ref{sec:merge-k-sorted-lists} +\item Merge Two Sorted Lists,見 \S \ref{sec:merge-two-sorted-arrays} +\item Merge k Sorted Lists,見 \S \ref{sec:merge-k-sorted-lists} \myenddot @@ -50,13 +50,13 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} //LeetCode, Merge Two Sorted Lists -// 时间复杂度O(min(m,n)),空间复杂度O(1) +// 時間複雜度O(min(m,n)),空間複雜度O(1) class Solution { public: ListNode *mergeTwoLists(ListNode *l1, ListNode *l2) { @@ -75,10 +75,10 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Merge Sorted Array \S \ref{sec:merge-sorted-array} -\item Merge k Sorted Lists,见 \S \ref{sec:merge-k-sorted-lists} +\item Merge Sorted Array \S \ref{sec:merge-two-sorted-arrays} +\item Merge k Sorted Lists,見 \S \ref{sec:merge-k-sorted-lists} \myenddot @@ -91,13 +91,13 @@ \subsubsection{描述} \subsubsection{分析} -可以复用Merge Two Sorted Lists(见 \S \ref{sec:merge-two-sorted-lists})的函数 +可以複用Merge Two Sorted Lists(見 \S \ref{sec:merge-two-sorted-lists})的函數 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} //LeetCode, Merge k Sorted Lists -// 时间复杂度O(n1+n2+...),空间复杂度O(1) +// 時間複雜度O(n1+n2+...),空間複雜度O(1) class Solution { public: @@ -135,10 +135,10 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Merge Sorted Array \S \ref{sec:merge-sorted-array} -\item Merge Two Sorted Lists,见 \S \ref{sec:merge-two-sorted-lists} +\item Merge Sorted Array \S \ref{sec:merge-two-sorted-arrays} +\item Merge Two Sorted Lists,見 \S \ref{sec:merge-two-sorted-lists} \myenddot @@ -151,13 +151,13 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Insertion Sort List -// 时间复杂度O(n^2),空间复杂度O(1) +// 時間複雜度O(n^2),空間複雜度O(1) class Solution { public: ListNode *insertionSortList(ListNode *head) { @@ -185,9 +185,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Sort List, 见 \S \ref{sec:Sort-List} +\item Sort List, 見 \S \ref{sec:sort-list} \myenddot @@ -200,31 +200,31 @@ \subsubsection{描述} \subsubsection{分析} -常数空间且$O(nlogn)$,单链表适合用归并排序,双向链表适合用快速排序。本题可以复用 "Merge Two Sorted Lists" 的代码。 +常數空間且$O(nlogn)$,單鏈表適合用歸併排序,雙向鏈表適合用快速排序。本題可以複用 "Merge Two Sorted Lists" 的代碼。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Sort List -// 归并排序,时间复杂度O(nlogn),空间复杂度O(1) +// 歸併排序,時間複雜度O(nlogn),空間複雜度O(1) class Solution { public: ListNode *sortList(ListNode *head) { if (head == NULL || head->next == NULL)return head; - // 快慢指针找到中间节点 + // 快慢指針找到中間節點 ListNode *fast = head, *slow = head; while (fast->next != NULL && fast->next->next != NULL) { fast = fast->next->next; slow = slow->next; } - // 断开 + // 斷開 fast = slow; slow = slow->next; fast->next = NULL; ListNode *l1 = sortList(head); // 前半段排序 - ListNode *l2 = sortList(slow); // 后半段排序 + ListNode *l2 = sortList(slow); // 後半段排序 return mergeTwoLists(l1, l2); } @@ -248,11 +248,253 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Insertion Sort List,见 \S \ref{sec:Insertion-Sort-List} +\item Insertion Sort List,見 \S \ref{sec:insertion-sort-list} \myenddot +\section{Merge sort Array} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:merge-sort-array} + + +\subsubsection{描述} + + +\subsubsection{分析} +無 + + +\subsubsection{Code} +\begin{Code} +// 時間複雜度O(nlogn),空間複雜度O(1) +class Solution { +public: + vector sortArray(vector& nums) { + mergeSort(nums.begin(), nums.end()); + return nums; + } +private: + template + void mergeSort(RandIT first, RandIT last) + { + int len = distance(first, last); + if (len < 2) return; + auto mid = next(first, len / 2); + + mergeSort(first, mid); + mergeSort(mid, last); + + merge(first, mid, last); + } + template + void merge(RandIT first, RandIT mid, RandIT last) + { + vector tmpVec(mid, last); + + auto ai = prev(mid); + auto ci = prev(last); + auto bi = prev(tmpVec.end()); + while (bi >= tmpVec.begin() && ai >= first) + { + if (*ai <= *bi) + { + *ci = *bi; + bi--; + } + else + { + *ci = *ai; + ai--; + } + ci--; + } + while (bi >= tmpVec.begin()) + { + *ci = *bi; + bi--; + ci--; + } + } +\end{Code} + + +\section{Quick sort Array} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:quick-sort-array} + + +\subsubsection{描述} +Quick Sort and STD iterator + + +\subsubsection{分析} +無 + +\subsubsection{代碼1} +\begin{Code} +// 時間複雜度O(nlogn),空間複雜度O(1) +class Solution { +public: + vector sortArray(vector& nums) { + quickSort(nums.begin(), nums.end()); + return nums; + } +private: + template + void quickSort(RandIT first, RandIT last) + { + if (first < last) + { + // template overload 的例子 + typename std::iterator_traits::iterator_category t; + auto pivot = partition(first, last, t); + quickSort(first, pivot); + quickSort(next(pivot), last); + } + } + template + RandIT partition(RandIT first, RandIT last, std::bidirectional_iterator_tag) + { + RandIT pivot = prev(last); + RandIT cur = first; + + while (first < last) + { + if (*first < *pivot) + { + mySwap(first, cur); + cur++; + } + first++; + } + mySwap(pivot, cur); + + return cur; + } + template + void mySwap(RandIT first, RandIT second) + { + auto tmp = *first; + *first = *second; + *second = tmp; + } +} +\end{Code} + +\subsubsection{代碼2} +\begin{Code} +// 時間複雜度O(nlogn),空間複雜度O(1) +class Solution { +public: + vector sortArray(vector& nums) { + if (nums.size() > 0) + quickSort(nums, 0, nums.size() - 1); + + return nums; + } +private: + void quickSort(vector& nums, int first, int last) + { + if (first < last) + { + int pivot = partition(nums, first, last); + quickSort(nums, first, pivot - 1); + quickSort(nums, pivot + 1, last); + } + } + int partition(vector& nums, int first, int last) + { + int cur = first; + while (first < last) + { + if (nums[first] < nums[last]) + { + mySwap(&nums[cur], &nums[first]); + cur++; + } + first++; + } + mySwap(&nums[cur], &nums[last]); + return cur; + } + void mySwap(int *a, int *b) + { + int tmp = *a; + *a = *b; + *b = tmp; + } +} +\end{Code} + + +\subsubsection{相關題目} +No + +\subsubsection{相關題目} +No +\newline + +\section{Quick sort List} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:quick-sort-list} + + +\subsubsection{描述} +Quick Sort with singal linked list + + +\subsubsection{分析} +無 + + +\subsubsection{代碼} +\begin{Code} +// Not from LeetCode +// 時間複雜度O(nlogn),空間複雜度O(1) +class Solution { +public: + void QuickSortList(ListNode *&head, ListNode *last = nullptr) + { + // return when not enough nodes + if (head == nullptr || head->next == nullptr) return; + if (head == last || head->next == last) return; + + ListNode *pivot = Q_PartitionList(head, last); + QuickSortList(head, pivot); + if (pivot != nullptr) + QuickSortList(pivot->next, last); + } +private: + ListNode *Q_PartitionList(ListNode *&head, ListNode* last) + { + ListNode *pivot = head; + ListNode left(-1), right(-1); + right.next = last; + ListNode *cur = head; + while (cur != last) + { + ListNode *next = cur->next; + if (pivot->val >= cur->val) + { + cur->next = left.next; + left.next = cur; + } + else + { + cur->next = right.next; + right.next = cur; + } + cur = next; + } + pivot->next = right.next; + head = left.next; + + return pivot; + } +\end{Code} + + +\subsubsection{相關題目} +No +\newline \section{First Missing Positive} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \label{sec:first-missing-positive} @@ -269,13 +511,13 @@ \subsubsection{描述} \subsubsection{分析} -本质上是桶排序(bucket sort),每当\fn{A[i]!= i+1}的时候,将A[i]与A[A[i]-1]交换,直到无法交换为止,终止条件是 \fn{A[i]== A[A[i]-1]}。 +本質上是桶排序(bucket sort),每當\fn{A[i]!= i+1}的時候,將A[i]與A[A[i]-1]交換,直到無法交換為止,終止條件是 \fn{A[i]== A[A[i]-1]}。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, First Missing Positive -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: int firstMissingPositive(vector& nums) { @@ -301,9 +543,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Sort Colors, 见 \S \ref{sec:sort-colors} +\item Sort Colors, 見 \S \ref{sec:sort-colors} \myenddot @@ -329,22 +571,22 @@ \subsubsection{描述} \subsubsection{分析} -由于0, 1, 2 非常紧凑,首先想到计数排序(counting sort),但需要扫描两遍,不符合题目要求。 +由於0, 1, 2 非常緊湊,首先想到計數排序(counting sort),但需要掃描兩遍,不符合題目要求。 -由于只有三种颜色,可以设置两个index,一个是red的index,一个是blue的index,两边往中间走。时间复杂度$O(n)$,空间复杂度$O(1)$。 +由於只有三種顏色,可以設置兩個index,一個是red的index,一個是blue的index,兩邊往中間走。時間複雜度$O(n)$,空間複雜度$O(1)$。 -第3种思路,利用快速排序里 partition 的思想,第一次将数组按0分割,第二次按1分割,排序完毕,可以推广到$n$种颜色,每种颜色有重复元素的情况。 +第3種思路,利用快速排序裏 partition 的思想,第一次將數組按0分割,第二次按1分割,排序完畢,可以推廣到$n$種顏色,每種顏色有重複元素的情況。 -\subsubsection{代码1} +\subsubsection{代碼1} \begin{Code} // LeetCode, Sort Colors // Counting Sort -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: void sortColors(vector& A) { - int counts[3] = { 0 }; // 记录每个颜色出现的次数 + int counts[3] = { 0 }; // 記錄每個顏色出現的次數 for (int i = 0; i < A.size(); i++) counts[A[i]]++; @@ -358,14 +600,14 @@ \subsubsection{代码1} \end{Code} -\subsubsection{代码2} +\subsubsection{代碼2} \begin{Code} // LeetCode, Sort Colors -// 双指针,时间复杂度O(n),空间复杂度O(1) +// 雙指針,時間複雜度O(n),空間複雜度O(1) class Solution { public: void sortColors(vector& A) { - // 一个是red的index,一个是blue的index,两边往中间走 + // 一個是red的index,一個是blue的index,兩邊往中間走 int red = 0, blue = A.size() - 1; for (int i = 0; i < blue + 1;) { @@ -381,11 +623,11 @@ \subsubsection{代码2} \end{Code} -\subsubsection{代码3} +\subsubsection{代碼3} \begin{Code} // LeetCode, Sort Colors // use partition() -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: void sortColors(vector& nums) { @@ -396,11 +638,11 @@ \subsubsection{代码3} \end{Code} -\subsubsection{代码4} +\subsubsection{代碼4} \begin{Code} // LeetCode, Sort Colors -// 重新实现 partition() -// 时间复杂度O(n),空间复杂度O(1) +// 重新實現 partition() +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: void sortColors(vector& nums) { @@ -423,7 +665,486 @@ \subsubsection{代码4} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item First Missing Positive, 见 \S \ref{sec:first-missing-positive} +\item First Missing Positive, 見 \S \ref{sec:first-missing-positive} \myenddot + + +\section{Heap Sort} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:heap-sort} + + +\subsubsection{描述} +Remake std heap_sort heap_make heap_pop heap_push, also using iterator type + + +\subsubsection{分析} +Learn from STL源碼剖析 侯捷 + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Heap Sort +// Heap Sort +// 時間複雜度O(nlogn),空間複雜度O(1) +class Solution { +public: + template + void PushHeap(T first, T last) + { + // Assume the value is pushed to the vector + typename std::iterator_traits::iterator_category t; + __PushHeapAux(first, last, t); + } + template + void PopHeap(T first, T last) + { + // will move the value to the last element + typename std::iterator_traits::iterator_category t; + __PopHeapAux(first, last, t); + } + template + void SortHeap (RandomAccessIterator first, RandomAccessIterator last) + { + while (last - first > 1) + PopHeap(first, last--); + } + template + inline void MakeHeap (T first, T last) + { + typename std::iterator_traits::iterator_category t; + __MakeHeap(first, last, t); + } +private: + template + void __PushHeap (RandomAccessIterator first + , Distance holeIndex, Distance topIndex, T value) + { + // percolate up + Distance parent = (holeIndex - 1) / 2; // find father node + while (holeIndex > topIndex && *next(first, parent) < value) + { + *(first + holeIndex) = *(first + parent); + holeIndex = parent; + parent = (holeIndex - 1) / 2; + } + *(first + holeIndex) = value; + } + template + void __PushHeapAux(T first, T last, std::random_access_iterator_tag) + { + __PushHeap(first, distance(first, prev(last)), distance(first, first), *prev(last)); + } + template + void __AdjustHeap (RandomAccessIterator first + , Distance holeIndex, Distance len, T value) + { + // percolate down + Distance topIndex = holeIndex; + Distance secondChild = 2 * (holeIndex + 1); + while (secondChild < len) + { + if (*(first + secondChild) < *(first + (secondChild - 1))) + secondChild--; + *(first + holeIndex) = *(first + secondChild); + holeIndex = secondChild; + secondChild = 2 * (secondChild + 1); + } + if (secondChild == len) // only left child case + { + *(first + holeIndex) = *(first + (secondChild - 1)); + holeIndex = secondChild - 1; + } + __PushHeap (first, holeIndex, topIndex, value); + } + template + void __PopHeap (RandomAccessIterator first + , RandomAccessIterator last, RandomAccessIterator result, T value) + { + *result = *first; + __AdjustHeap(first, distance(first, first), distance(first, last), value); + } + template + void __PopHeapAux(T first, T last, std::random_access_iterator_tag) + { + __PopHeap(first, prev(last), prev(last), *prev(last)); + } + template + void __MakeHeap (T first, T last, std::random_access_iterator_tag) + { + if (last - first < 2) return; + auto len = distance(first, last); + auto holeIndex = len / 2 - 1; + + while (true) + { + __AdjustHeap(first, holeIndex, len, *next(first, holeIndex)); + if (holeIndex == 0) return; + holeIndex--; + } + } +}; + +void TryHeap() +{ + using namespace std; + vector ivec = {0,1,2,3,4,8,9,3,5}; + + RM::MakeHeap(ivec.begin(), ivec.end()); + for (size_t i = 0; i < ivec.size(); ++i) + cout << ivec[i] << " "; + cout << endl; + + ivec.push_back(7); + cout << distance(ivec.begin(), ivec.end()) + << " " << distance(ivec.begin(), prev(ivec.end())) << endl; + RM::PushHeap(ivec.begin(), ivec.end()); + for (size_t i = 0; i < ivec.size(); ++i) + cout << ivec[i] << " "; + cout << endl; + + RM::PopHeap(ivec.begin(), ivec.end()); + cout << ivec.back() << endl; + ivec.pop_back(); + + for (size_t i = 0; i < ivec.size(); ++i) + cout << ivec[i] << " "; + cout << endl; + + RM::SortHeap(ivec.begin(), ivec.end()); + for (size_t i = 0; i < ivec.size(); ++i) + cout << ivec[i] << " "; + cout << endl; +} +\end{Code} + +\subsubsection{相關題目} +No + +\section{Partial Sort} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:partial-sort} + + +\subsubsection{描述} +Partial Sort + + +\subsubsection{分析} +Learn from STL源碼剖析 侯捷 + + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Partial Sort +// Partial Sort +// 時間複雜度O(mlogk),空間複雜度O(1) +class Solution { +public: + template + inline void PartialSort(T first, T middle, T last) + { + typename std::iterator_traits::iterator_category t; + __PartialSort(first, middle, last, t); + } + +private: + template + void __PartialSort(T first, T middle, T last, std::random_access_iterator_tag) + { + MakeHeap(first, middle); + for (T i = middle; i < last; ++i) + { + if (*i < *first) + __PopHeap(first, middle, i, *i); + } + SortHeap(first, middle); + } +} +\end{Code} + +\subsubsection{相關題目} +No + +\section{Topological Sort} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:topological-sort} + + +\subsubsection{描述} +Topological Sort +\begin{Code} + A B + \ /| + \ / | + C | + / | + D E + \ / + \ + F + | + G +\end{Code} + +Result: B E A C D F G + +\myurl{https://www.youtube.com/watch?v=ddTC4Zovtbc} + + +\subsubsection{分析} +This is useful for software compile, project sequence when having dependency + +\subsubsection{代碼} +\begin{Code} +// LeetCode, Topological Sort +// Topological Sort +// 時間複雜度O(),空間複雜度O() +class Solution { +public: + vector TopologicalSort(const vector>& adjList) { + unordered_map visited; + vector result; result.reserve(adjList.size()); + + // random pick a point to start + // DFS to leaf + // Put to when all child is visited + for (int i = 0; i < (int)adjList.size(); i++) { + if (!DFS(adjList, i, visited, result)) return ""; + } + + return result; + } +private: + void DFS(const vector>& adjList, int step, unordered_map& visited, vector& result) { + if (visited.find(step) != visited.end()) + return visited[step]; // If ths node was grey (false), a cycle was detected. + visited[step] = false; + + for (const auto& nei : adjList[step]) { + if (!DFS(adjList, nei, visited, result)) return false; + } + visited[step] = true; + result.insert(result.begin(), step); + } +}; +int main(int argc, char *argv[]) { + Solution solution; + vector> adjList(7, vector()); // DAG + adjList[0] = {2}; // A -> C + adjList[1] = {2, 4}; // B -> C, B -> E + adjList[2] = {3}; // C -> D + adjList[3] = {5}; // D -> F + adjList[4] = {5}; // E -> F + adjList[5] = {6}; // F -> G + adjList[6] = {}; // G -> + for (const auto& val : solution.TopologicalSort(adjList)) + cout << (char)(val + 'A') << " "; + cout << endl; + + return 0; +} +\end{Code} + +\subsubsection{相關題目} +No + +\section{Sort Characters By Frequency} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:sort-character-by-frequency} + + +\subsubsection{描述} +Given a string, sort it in decreasing order based on the frequency of characters. + +Example 1: +\begin{Code} +Input: +"tree" + +Output: +"eert" + +Explanation: +'e' appears twice while 'r' and 't' both appear once. +So 'e' must appear before both 'r' and 't'. Therefore "eetr" is also a valid answer. +\end{Code} + +Example 2: +\begin{Code} +Input: +"cccaaa" + +Output: +"cccaaa" + +Explanation: +Both 'c' and 'a' appear three times, so "aaaccc" is also a valid answer. +Note that "cacaca" is incorrect, as the same characters must be together. +\end{Code} + +\subsubsection{分析} +method 1: 初始化 vec 來儲存出現次數,計算出現次數,由大至細依出現次數排序 + +method 2: Bucket sort, 計算每個字母出現的次數, 以 bucket 來儲存每個出現次數的字每, 製造答案 + +\subsubsection{代碼} +\begin{Code} +// LeetCode, +// 時間複雜度O(nlogn),空間複雜度O(n) +class Solution { +public: + string frequencySort(string s) { + // 初始化 vec 來儲存出現次數 + vector> countVec(256); + for (size_t i = 0; i < countVec.size(); i++) + { + countVec[i].first = i; + countVec[i].second = 0; + } + + // 計算出現次數 + for (const char& c : s) + countVec[c].second++; + + // ,由大至細依出現次數排序 + sort(countVec.begin(), countVec.end() + , [&](const auto& first, const auto& second) + { + return first.second > second.second; + }); + + string result; + for (const auto& count : countVec) + { + if (count.second > 0) + result.append(count.second, count.first); + } + return result; + } +}; +\end{Code} + +\subsubsection{代碼} +\begin{Code} +// LeetCode, +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + string frequencySort(string s) { + if (s.size() == 0) return ""; + // Bucket sort + vector counter(256, 0); + + // 計算每個字母出現的次數 + int maxCount = INT_MIN; + for (const char& c : s) + { + counter[c]++; + if (counter[c] > maxCount) + maxCount = counter[c]; + } + + // 以 bucket 來儲存每個出現次數的字每 + vector> buckets(maxCount); + for (size_t i = 0; i < counter.size(); i++) + { + if (counter[i] > 0) + buckets[counter[i]-1].push_back((char)i); + } + + // 製造答案 + string result; + for (int i = (int)buckets.size()-1; i >= 0; i--) + { + for (const char& c : buckets[i]) + result.append(i+1, c); + } + return result; + } +}; +\end{Code} + +\subsubsection{相關題目} +No + +\section{Top K Frequent Words} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:top-k-frequent-words} + + +\subsubsection{描述} +Given a non-empty list of words, return the k most frequent elements. + +Your answer should be sorted by frequency from highest to lowest. If two words have the same frequency, then the word with the lower alphabetical order comes first. + +Example 1: +\begin{Code} +Input: ["i", "love", "leetcode", "i", "love", "coding"], k = 2 +Output: ["i", "love"] +Explanation: "i" and "love" are the two most frequent words. + Note that "i" comes before "love" due to a lower alphabetical order. +\end{Code} + +Example 2: +\begin{Code} +Input: ["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"], k = 4 +Output: ["the", "is", "sunny", "day"] +Explanation: "the", "is", "sunny" and "day" are the four most frequent words, + with the number of occurrence being 4, 3, 2 and 1 respectively. +\end{Code} + +\textbf{Note}: +\begin{itemize} +\item{You may assume k is always valid, 1 ≤ k ≤ number of unique elements.} +\item{Input words contain only lowercase letters.} +\end{itemize} + +\textbf{Follow up}: +\begin{itemize} +\item{Try to solve it in O(n log k) time and O(n) extra space.} +\end{itemize} + + + +\subsubsection{分析} +用 hash map 來統計每個字的出現次數, 以 vector 來保存 pair, 以 partial heap sort 得到答案 + +\subsubsection{代碼} +\begin{Code} +// LeetCode, +// 時間複雜度O(nlogk),空間複雜度O(n) +class Solution { +public: + vector topKFrequent(vector& words, int k) { + // 用 hash map 來統計每個字的出現次數 + unordered_map cache; + for (const auto& w : words) cache[w]++; + + // 以 vector 來保存 pair + vector> vecCache(cache.begin(), cache.end()); + + // 以 partial heap sort 得到答案 + partial_sort(vecCache.begin(), next(vecCache.begin(), k), vecCache.end() + , [&](const auto& first, const auto& second) + { + if (first.second > second.second) + return true; + else if (first.second < second.second) + return false; + else + { + return (first.first < second.first); + } + }); + + // 取得答案 + vector result; result.reserve(k); + for_each(vecCache.begin(), next(vecCache.begin(), k) + , [&](const auto& e) + { + result.push_back(e.first); + }); + return result; + } +}; +\end{Code} +\subsubsection{相關題目} +No diff --git a/C++/chapStackAndQueue.tex b/C++/chapStackAndQueue.tex index 9dacb601..03e1bd2d 100644 --- a/C++/chapStackAndQueue.tex +++ b/C++/chapStackAndQueue.tex @@ -1,7 +1,7 @@ -\chapter{栈和队列} +\chapter{棧和隊列} -\section{栈} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{棧} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Valid Parentheses} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -15,13 +15,13 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Valid Parentheses -// 时间复杂度O(n),空间复杂度O(n) +// 時間複雜度O(n),空間複雜度O(n) class Solution { public: bool isValid (string const& s) { @@ -45,10 +45,10 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Generate Parentheses, 见 \S \ref{sec:generate-parentheses} -\item Longest Valid Parentheses, 见 \S \ref{sec:longest-valid-parentheses} +\item Generate Parentheses, 見 \S \ref{sec:generate-parentheses} +\item Longest Valid Parentheses, 見 \S \ref{sec:longest-valid-parentheses} \myenddot @@ -65,13 +65,13 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{使用栈} +\subsubsection{使用棧} \begin{Code} // LeetCode, Longest Valid Parenthese -// 使用栈,时间复杂度O(n),空间复杂度O(n) +// 使用棧,時間複雜度O(n),空間複雜度O(n) class Solution { public: int longestValidParentheses(const string& s) { @@ -104,8 +104,8 @@ \subsubsection{使用栈} \subsubsection{Dynamic Programming, One Pass} \begin{Code} // LeetCode, Longest Valid Parenthese -// 时间复杂度O(n),空间复杂度O(n) -// @author 一只杰森(http://weibo.com/wjson) +// 時間複雜度O(n),空間複雜度O(n) +// @author 一隻傑森(http://weibo.com/wjson) class Solution { public: int longestValidParentheses(const string& s) { @@ -127,11 +127,11 @@ \subsubsection{Dynamic Programming, One Pass} \end{Code} -\subsubsection{两遍扫描} +\subsubsection{兩遍掃描} \begin{Code} // LeetCode, Longest Valid Parenthese -// 两遍扫描,时间复杂度O(n),空间复杂度O(1) -// @author 曹鹏(http://weibo.com/cpcs) +// 兩遍掃描,時間複雜度O(n),空間複雜度O(1) +// @author 曹鵬(http://weibo.com/cpcs) class Solution { public: int longestValidParentheses(const string& s) { @@ -147,7 +147,7 @@ \subsubsection{两遍扫描} } else if (depth == 0) { answer = max(answer, i - start); } - } + } } depth = 0; @@ -163,7 +163,7 @@ \subsubsection{两遍扫描} } else if (depth == 0) { answer = max(answer, start - i); } - } + } } return answer; } @@ -171,10 +171,10 @@ \subsubsection{两遍扫描} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Valid Parentheses, 见 \S \ref{sec:valid-parentheses} -\item Generate Parentheses, 见 \S \ref{sec:generate-parentheses} +\item Valid Parentheses, 見 \S \ref{sec:valid-parentheses} +\item Generate Parentheses, 見 \S \ref{sec:generate-parentheses} \myenddot @@ -201,17 +201,17 @@ \subsubsection{描述} \subsubsection{分析} -简单的,类似于 Container With Most Water(\S \ref{sec:container-with-most-water}),对每个柱子,左右扩展,直到碰到比自己矮的,计算这个矩形的面积,用一个变量记录最大的面积,复杂度$O(n^2)$,会超时。 +簡單的,類似於 Container With Most Water(\S \ref{sec:container-with-most-water}),對每個柱子,左右擴展,直到碰到比自己矮的,計算這個矩形的面積,用一個變量記錄最大的面積,複雜度$O(n^2)$,會超時。 -如图\S \ref{fig:histogram-area}所示,从左到右处理直方,当$i=4$时,小于当前栈顶(即直方3),对于直方3,无论后面还是前面的直方,都不可能得到比目前栈顶元素更高的高度了,处理掉直方3(计算从直方3到直方4之间的矩形的面积,然后从栈里弹出);对于直方2也是如此;直到碰到比直方4更矮的直方1。 +如圖\S \ref{fig:histogram-area}所示,從左到右處理直方,當$i=4$時,小於當前棧頂(即直方3),對於直方3,無論後面還是前面的直方,都不可能得到比目前棧頂元素更高的高度了,處理掉直方3(計算從直方3到直方4之間的矩形的面積,然後從棧裏彈出);對於直方2也是如此;直到碰到比直方4更矮的直方1。 -这就意味着,可以维护一个递增的栈,每次比较栈顶与当前元素。如果当前元素大于栈顶元素,则入栈,否则合并现有栈,直至栈顶元素小于当前元素。结尾时入栈元素0,重复合并一次。 +這就意味着,可以維護一個遞增的棧,每次比較棧頂與當前元素。如果當前元素大於棧頂元素,則入棧,否則合併現有棧,直至棧頂元素小於當前元素。結尾時入棧元素0,重複合併一次。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Largest Rectangle in Histogram -// 时间复杂度O(n),空间复杂度O(n) +// 時間複雜度O(n),空間複雜度O(n) class Solution { public: int largestRectangleArea(vector &height) { @@ -233,11 +233,112 @@ \subsubsection{代码} }; \end{Code} +\subsubsection{遞歸版} +\begin{Code} +// LeetCode, Largest Rectangle in Histogram +// 時間複雜度O(nlogn ~ n^2),空間複雜度O(n) +// 會超時 +class Solution { +public: + int largestRectangleArea(vector& heights) { + if (heights.size() == 0) return 0; + + return DFS(heights, 0, heights.size()-1); + } +private: + int DFS(vector& heights, int first, int last) { + if (first > last) return 0; + + int minIndex = first; + for (int i = first; i <= last; i++) { + if (heights[minIndex] > heights[i]) + minIndex = i; + } + + return max(heights[minIndex] * (last - first + 1) + , max(DFS(heights, first, minIndex-1) + , DFS(heights, minIndex+1, last))); + } +}; +\end{Code} + +\subsubsection{遞歸版 - segment tree} +\begin{Code} +// LeetCode, Largest Rectangle in Histogram +// 時間複雜度O(nlogn),空間複雜度O(n) +// 把尋找 min index 的時間減少為 log n +class SegTreeNode { +public: + int start; + int end; + int min; + SegTreeNode *left; + SegTreeNode *right; + SegTreeNode(int start, int end) { + this->start = start; + this->end = end; + left = right = NULL; + } +}; + +class Solution { +public: + int largestRectangleArea(vector& heights) { + if (heights.size() == 0) return 0; + // first build a segment tree + SegTreeNode *root = buildSegmentTree(heights, 0, heights.size() - 1); + // next calculate the maximum area recursively + return calculateMax(heights, root, 0, heights.size() - 1); + } + + int calculateMax(vector& heights, SegTreeNode* root, int start, int end) { + if (start > end) { + return -1; + } + if (start == end) { + return heights[start]; + } + int minIndex = query(root, heights, start, end); + int leftMax = calculateMax(heights, root, start, minIndex - 1); + int rightMax = calculateMax(heights, root, minIndex + 1, end); + int minMax = heights[minIndex] * (end - start + 1); + return max( max(leftMax, rightMax), minMax ); + } + + SegTreeNode *buildSegmentTree(vector& heights, int start, int end) { + if (start > end) return NULL; + SegTreeNode *root = new SegTreeNode(start, end); + if (start == end) { + root->min = start; + return root; + } else { + int middle = (start + end) / 2; + root->left = buildSegmentTree(heights, start, middle); + root->right = buildSegmentTree(heights, middle + 1, end); + root->min = heights[root->left->min] < heights[root->right->min] + ? root->left->min : root->right->min; + return root; + } + } -\subsubsection{相关题目} + int query(SegTreeNode *root, vector& heights, int start, int end) { + if (root == NULL || end < root->start || start > root->end) return -1; + if (start <= root->start && end >= root->end) { + return root->min; + } + int leftMin = query(root->left, heights, start, end); + int rightMin = query(root->right, heights, start, end); + if (leftMin == -1) return rightMin; + if (rightMin == -1) return leftMin; + return heights[leftMin] < heights[rightMin] ? leftMin : rightMin; + } +}; +\end{Code} + +\subsubsection{相關題目} \begindot -\item Trapping Rain Water, 见 \S \ref{sec:trapping-rain-water} -\item Container With Most Water, 见 \S \ref{sec:container-with-most-water} +\item Trapping Rain Water, 見 \S \ref{sec:trapping-rain-water} +\item Container With Most Water, 見 \S \ref{sec:container-with-most-water} \myenddot @@ -258,13 +359,13 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{递归版} +\subsubsection{遞歸版} \begin{Code} // LeetCode, Evaluate Reverse Polish Notation -// 递归,时间复杂度O(n),空间复杂度O(logn) +// 遞歸,時間複雜度O(n),空間複雜度O(logn) class Solution { public: int evalRPN(vector &tokens) { @@ -294,7 +395,7 @@ \subsubsection{递归版} \subsubsection{迭代版} \begin{Code} // LeetCode, Max Points on a Line -// 迭代,时间复杂度O(n),空间复杂度O(logn) +// 迭代,時間複雜度O(n),空間複雜度O(logn) class Solution { public: int evalRPN(vector &tokens) { @@ -324,11 +425,371 @@ \subsubsection{迭代版} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot -\section{队列} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{隊列} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\subsection{Sliding Window Maximum} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:sliding-window-maximum} + + +\subsubsection{描述} +Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position. Return the max sliding window. + +\textbf{Follow up:} +Could you solve it in linear time? + +\textbf{Example:} +\begin{Code} +Input: nums = [1,3,-1,-3,5,3,6,7], and k = 3 +Output: [3,3,5,5,6,7] +Explanation: + +Window position Max +--------------- ----- +[1 3 -1] -3 5 3 6 7 3 + 1 [3 -1 -3] 5 3 6 7 3 + 1 3 [-1 -3 5] 3 6 7 5 + 1 3 -1 [-3 5 3] 6 7 5 + 1 3 -1 -3 [5 3 6] 7 6 + 1 3 -1 -3 5 [3 6 7] 7 +\end{Code} + + +\subsubsection{分析} +以 deque 來追踪視窗內的指標, 移除老去的指標, 移除數值太細的元素指標 + + +\subsubsection{Code} +\begin{Code} +// LeetCode +// 迭代,時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + vector maxSlidingWindow(vector& nums, int k) { + int N = nums.size(); + if (N < k) return vector(); + + vector result; result.reserve(N-k+1); + deque indexList; + + // 預準首 K 項 + for (size_t i = 0; i < k; i++) + { + while (!indexList.empty() && nums[indexList.back()] < nums[i]) + indexList.pop_back(); + indexList.push_back(i); + } + result.push_back(nums[indexList.front()]); + + // 完成剩下的元素 + for (size_t i = k; i < nums.size(); i++) + { + // 移除老去的元素 + while (!indexList.empty() && indexList.front() < i-k+1) + indexList.pop_front(); + + // 移除數值太細的元素 + while (!indexList.empty() && nums[indexList.back()] < nums[i]) + indexList.pop_back(); + indexList.push_back(i); + + result.push_back(nums[indexList.front()]); + } + + return result; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + +\subsection{Meeting Rooms II} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:meeting-rooms-ii} + + +\subsubsection{描述} +Given an array of meeting time intervals consisting of start and end times [[s1,e1],[s2,e2],...] (si < ei), find the minimum number of conference rooms required. + +\textbf{Example 1} +\begin{Code} +Input: [[0, 30],[5, 10],[15, 20]] +Output: 2 +\end{Code} + +\textbf{Example 2} +\begin{Code} +Input: [[7,10],[2,4]] +Output: 1 +\end{Code} + + +\subsubsection{分析} +Nil + +\subsubsection{Priority Queue} +\begin{Code} +// LeetCode +// 迭代,時間複雜度O(nlogn),空間複雜度O(n) +class Solution { +public: + int minMeetingRooms(vector>& intervals) { + // 順序所以時間段落 + sort(intervals.begin(), intervals.end() + , [&](const auto& first, const auto& second) + { + return first[0] < second[0]; + }); + // 利用 priority queue 找出最快完結的時間段落 + auto Compare = [](const auto& first, const auto& second) + { return first.second > second.second; }; + priority_queue, vector>, decltype(Compare)> + pq(Compare); + + int maxUsed = 0; + for (const auto& interval : intervals) + { + // 移除用完的房間 + while (!pq.empty() && interval[0] >= pq.top().second) + pq.pop(); + // 加入新的時間段落 + pq.push(make_pair(interval[0], interval[1])); + // 更新同一時間的最大房間用量 + maxUsed = max(maxUsed, (int)pq.size()); + } + + return maxUsed; + } +}; +\end{Code} + +\subsubsection{Start End Vector} +\begin{Code} +// LeetCode +// 迭代,時間複雜度O(nlogn),空間複雜度O(n) +class Solution { +public: + int minMeetingRooms(vector>& intervals) { + // 使用兩支 vector 一支為 startTime, 一支為 endTime + vector startTime, endTime; + startTime.reserve(intervals.size()); + endTime.reserve(intervals.size()); + for (const auto& interval : intervals) + { + startTime.push_back(interval[0]); + endTime.push_back(interval[1]); + } + // 由細至大 + sort(startTime.begin(), startTime.end()); + sort(endTime.begin(), endTime.end()); + // 計算房間多少 + int curRm = 0; + int maxRm = 0; + int ei = 0; + for (int si = 0; si < (int)startTime.size(); si++) + { + while (startTime[si] >= endTime[ei]) + { + ei++; + curRm--; + } + curRm++; + maxRm = max(maxRm, curRm); + } + + return maxRm; + } +}; +\end{Code} + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + +\section{Implement Queue using Stack} +\label{sec:implement-queue-using-stack} + +\subsection{描述} +Implement the following operations of a queue using stacks. + +\begindot +\item push(x) -- Push element x to the back of queue. +\item pop() -- Removes the element from in front of queue. +\item peek() -- Get the front element. +\item empty() -- Return whether the queue is empty. +\myenddot + +Example: +\begin{Code} +MyQueue queue = new MyQueue(); + +queue.push(1); +queue.push(2); +queue.peek(); // returns 1 +queue.pop(); // returns 1 +queue.empty(); // returns false +\end{Code} + +Note: +\begindot +\item You must use only standard operations of a stack -- which means only push to top, peek/pop from top, size, and is empty operations are valid. +\item Depending on your language, stack may not be supported natively. You may simulate a stack by using a list or deque (double-ended queue), as long as you use only standard operations of a stack. +\item You may assume that all operations are valid (for example, no pop or peek operations will be called on an empty queue). +\myenddot + +\subsection{分析} +push: O(1), pop: O(1) + +\subsection{代碼} +\begin{Code} +// LeetCode +// 時間複雜度O(),空間複雜度O() +class MyQueue { +public: + /** Initialize your data structure here. */ + MyQueue() { + + } + + /** Push element x to the back of queue. */ + void push(int x) { + if (m_data_001.empty()) + m_front = x; + m_data_001.push(x); + } + + /** Removes the element from in front of queue and returns that element. */ + int pop() { + if (m_data_002.empty()) + while (!m_data_001.empty()) + { + m_data_002.push(m_data_001.top()); + m_data_001.pop(); + } + int tmp = m_data_002.top(); + m_data_002.pop(); + return tmp; + } + + /** Get the front element. */ + int peek() { + if (!m_data_002.empty()) + return m_data_002.top(); + return m_front; + } + + /** Returns whether the queue is empty. */ + bool empty() { + return m_data_001.empty() && m_data_002.empty(); + } +private: + stack m_data_001; + stack m_data_002; + int m_front; +}; + +/** + * Your MyQueue object will be instantiated and called as such: + * MyQueue* obj = new MyQueue(); + * obj->push(x); + * int param_2 = obj->pop(); + * int param_3 = obj->peek(); + * bool param_4 = obj->empty(); + */ +\end{Code} + +\section{Implement Stack using Queues} +\label{sec:implement-stack-using-queues} + +\subsection{描述} +Implement the following operations of a stack using queues. + +\begindot +\item push(x) -- Push element x onto stack. +\item pop() -- Removes the element on top of the stack. +\item top() -- Get the top element. +\item empty() -- Return whether the stack is empty. +\myenddot + +Example: +\begin{Code} +MyStack stack = new MyStack(); + +stack.push(1); +stack.push(2); +stack.top(); // returns 2 +stack.pop(); // returns 2 +stack.empty(); // returns false +\end{Code} + +\begindot +\item You must use only standard operations of a queue -- which means only push to back, peek/pop from front, size, and is empty operations are valid. +\item Depending on your language, queue may not be supported natively. You may simulate a queue by using a list or deque (double-ended queue), as long as you use only standard operations of a queue. +\item You may assume that all operations are valid (for example, no pop or top operations will be called on an empty stack). +\myenddot + +\subsection{分析} +push: O(n), pop: O(1) + +\subsection{代碼} +\begin{Code} +// LeetCode +// 時間複雜度O(),空間複雜度O() +class MyStack { +public: + /** Initialize your data structure here. */ + MyStack() { + + } + + /** Push element x onto stack. */ + void push(int x) { + m_data.push(x); + size_t sz = m_data.size(); + while (sz > 1) + { + m_data.push(m_data.front()); + m_data.pop(); + sz--; + } + } + + /** Removes the element on top of the stack and returns that element. */ + int pop() { + int tmp = m_data.front(); + m_data.pop(); + return tmp; + } + + /** Get the top element. */ + int top() { + return m_data.front(); + } + + /** Returns whether the stack is empty. */ + bool empty() { + return m_data.empty(); + } +private: + queue m_data; +}; + +/** + * Your MyStack object will be instantiated and called as such: + * MyStack* obj = new MyStack(); + * obj->push(x); + * int param_2 = obj->pop(); + * int param_3 = obj->top(); + * bool param_4 = obj->empty(); + */ +\end{Code} diff --git a/C++/chapString.tex b/C++/chapString.tex index eefbe620..98eb3c65 100644 --- a/C++/chapString.tex +++ b/C++/chapString.tex @@ -19,13 +19,13 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // Leet Code, Valid Palindrome -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: bool isPalindrome(string s) { @@ -43,12 +43,113 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Palindrome Number, 见 \S \ref{sec:palindrome-number} +\item Palindrome Number, 見 \S \ref{sec:palindrome-number} \myenddot +\section{Valid Palindrome II} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:valid-palindrome-ii} + + +\subsubsection{描述} +Given a non-empty string s, you may delete at most one character. Judge whether you can make it a palindrome. + + +Example 1: +\begin{Code} +Input: "aba" +Output: True +\end{Code} + +Example 2: +\begin{Code} +Input: "abca" +Output: True +Explanation: You could delete the character 'c'. +\end{Code} + +Note: +The string will only contain lowercase characters a-z. The maximum length of the string is 50000. + +\subsubsection{DFS} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + bool validPalindrome(string s) { + // errorSkip 為最大可容忍的錯誤次數 + return validP(s, 0, s.size() - 1, 1); + } +private: + bool validP(const string& s, int l, int r, int errorSkip) + { + while (l < r) + { + if (s[l] == s[r]) + { + l++; + r--; + } + else if (errorSkip > 0) + { + errorSkip--; + // status extend 把左右可以容忍的錯誤可能性都試一試 + return validP(s, l, r-1, errorSkip) + || validP(s, l+1, r, errorSkip); + } + else + return false; + } + return true; + } +}; +\end{Code} + +\subsubsection{左右探步} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + bool validPalindrome(string s) { + int m = s.size(); + if(m<=2) return true; + + int L,R; + L = 0; + R = m-1; + + while (L <= R) + { + if (s[L] == s[R]) + { + ++L;--R; + } + else + { + // 移除左邊 + int LL = L+1, LR = R; + while (LL <= LR && s[LL] == s[LR]) {++LL;--LR;} + if (LL >= LR) return true; + + // 移除右邊 + int RL = L, RR = R-1; + while (RL <= RR && s[RL] == s[RR]) {++RL;--RR;} + if (RL >= RR) return true; + + // 若兩個方向都不是 Palindrome + return false; + + } + + } + + return true; + } +}; +\end{Code} + \section{Implement strStr()} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \label{sec:strstr} @@ -60,13 +161,13 @@ \subsubsection{描述} \subsubsection{分析} -暴力算法的复杂度是 $O(m*n)$,代码如下。更高效的的算法有KMP算法、Boyer-Mooer算法和Rabin-Karp算法。面试中暴力算法足够了,一定要写得没有BUG。 +暴力算法的複雜度是 $O(m*n)$,代碼如下。更高效的的算法有KMP算法、Boyer-Mooer算法和Rabin-Karp算法。面試中暴力算法足夠了,一定要寫得沒有BUG。 \subsubsection{暴力匹配} \begin{Code} // LeetCode, Implement strStr() -// 暴力解法,时间复杂度O(N*M),空间复杂度O(1) +// 暴力解法,時間複雜度O(N*M),空間複雜度O(1) class Solution { public: int strStr(const string& haystack, const string& needle) { @@ -91,7 +192,7 @@ \subsubsection{暴力匹配} \subsubsection{KMP} \begin{Code} // LeetCode, Implement strStr() -// KMP,时间复杂度O(N+M),空间复杂度O(M) +// KMP,時間複雜度O(N+M),空間複雜度O(M) class Solution { public: int strStr(const string& haystack, const string& needle) { @@ -99,11 +200,11 @@ \subsubsection{KMP} } private: /* - * @brief 计算部分匹配表,即next数组. + * @brief 計算部分匹配表,即next數組. * * @param[in] pattern 模式串 - * @param[out] next next数组 - * @return 无 + * @param[out] next next數組 + * @return 無 */ static void compute_prefix(const char *pattern, int next[]) { int i; @@ -124,7 +225,7 @@ \subsubsection{KMP} * * @param[in] text 文本 * @param[in] pattern 模式串 - * @return 成功则返回第一次匹配的位置,失败则返回-1 + * @return 成功則返回第一次匹配的位置,失敗則返回-1 */ static int kmp(const char *text, const char *pattern) { int i; @@ -154,9 +255,9 @@ \subsubsection{KMP} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item String to Integer (atoi) ,见 \S \ref{sec:string-to-integer} +\item String to Integer (atoi) ,見 \S \ref{sec:string-to-integer} \myenddot @@ -182,17 +283,17 @@ \subsubsection{描述} If no valid conversion could be performed, a zero value is returned. If the correct value is out of the range of representable values, \code{INT_MAX (2147483647)} or \code{INT_MIN (-2147483648)} is returned. \subsubsection{分析} -细节题。注意几个测试用例: +細節題。注意幾個測試用例: \begin{enumerate} -\item 不规则输入,但是有效,"-3924x8fc", " + 413", -\item 无效格式," ++c", " ++1" -\item 溢出数据,"2147483648" +\item 不規則輸入,但是有效,"-3924x8fc", " + 413", +\item 無效格式," ++c", " ++1" +\item 溢出數據,"2147483648" \end{enumerate} -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, String to Integer (atoi) -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: int myAtoi(const string &str) { @@ -202,6 +303,7 @@ \subsubsection{代码} int i = 0; while (str[i] == ' ' && i < n) i++; + if (i == n) return 0; if (str[i] == '+') { i++; @@ -226,9 +328,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Implement strStr() ,见 \S \ref{sec:strstr} +\item Implement strStr() ,見 \S \ref{sec:strstr} \myenddot @@ -248,13 +350,13 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} //LeetCode, Add Binary -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: string addBinary(string a, string b) { @@ -279,9 +381,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Add Two Numbers, 见 \S \ref{sec:add-two-numbers} +\item Add Two Numbers, 見 \S \ref{sec:add-two-numbers} \myenddot @@ -294,18 +396,18 @@ \subsubsection{描述} \subsubsection{分析} -最长回文子串,非常经典的题。 +最長迴文子串,非常經典的題。 -思路一:暴力枚举,以每个元素为中间元素,同时从左右出发,复杂度$O(n^2)$。 +思路一:暴力枚舉,以每個元素為中間元素,同時從左右出發,複雜度$O(n^2)$。 -思路二:记忆化搜索,复杂度$O(n^2)$。设\fn{f[i][j]} 表示[i,j]之间的最长回文子串,递推方程如下: +思路二:記憶化搜索,複雜度$O(n^2)$。設\fn{f[i][j]} 表示[i,j]之間的最長迴文子串,遞推方程如下: \begin{Code} f[i][j] = if (i == j) S[i] if (S[i] == S[j] && f[i+1][j-1] == S[i+1][j-1]) S[i][j] else max(f[i+1][j-1], f[i][j-1], f[i+1][j]) \end{Code} -思路三:动规,复杂度$O(n^2)$。设状态为\fn{f(i,j)},表示区间[i,j]是否为回文串,则状态转移方程为 +思路三:動規,複雜度$O(n^2)$。設狀態為\fn{f(i,j)},表示區間[i,j]是否為迴文串,則狀態轉移方程為 $$ f(i,j)=\begin{cases} true & ,i=j\\ @@ -314,14 +416,14 @@ \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{备忘录法} +\subsubsection{備忘錄法} \begin{Code} // LeetCode, Longest Palindromic Substring -// 备忘录法,会超时 -// 时间复杂度O(n^2),空间复杂度O(n^2) +// 備忘錄法,會超時 +// 時間複雜度O(n^2),空間複雜度O(n^2) typedef string::const_iterator Iterator; namespace std { @@ -372,19 +474,19 @@ \subsubsection{备忘录法} \end{Code} -\subsubsection{动规} +\subsubsection{動規} \begin{Code} // LeetCode, Longest Palindromic Substring -// 动规,时间复杂度O(n^2),空间复杂度O(n^2) +// 動規,時間複雜度O(n^2),空間複雜度O(n^2) class Solution { public: string longestPalindrome(const string& s) { const int n = s.size(); bool f[n][n]; fill_n(&f[0][0], n * n, false); - // 用 vector 会超时 + // 用 vector 會超時 //vector > f(n, vector(n, false)); - size_t max_len = 1, start = 0; // 最长回文子串的长度,起点 + size_t max_len = 1, start = 0; // 最長迴文子串的長度,起點 for (size_t i = 0; i < s.size(); i++) { f[i][i] = true; @@ -406,7 +508,7 @@ \subsubsection{Manacher’s Algorithm} \begin{Code} // LeetCode, Longest Palindromic Substring // Manacher’s Algorithm -// 时间复杂度O(n),空间复杂度O(n) +// 時間複雜度O(n),空間複雜度O(n) class Solution { public: // Transform S into T. @@ -426,8 +528,8 @@ \subsubsection{Manacher’s Algorithm} string longestPalindrome(string s) { string T = preProcess(s); const int n = T.length(); - // 以T[i]为中心,向左/右扩张的长度,不包含T[i]自己, - // 因此 P[i]是源字符串中回文串的长度 + // 以T[i]為中心,向左/右擴張的長度,不包含T[i]自己, + // 因此 P[i]是源字符串中迴文串的長度 int P[n]; int C = 0, R = 0; @@ -464,9 +566,9 @@ \subsubsection{Manacher’s Algorithm} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -500,13 +602,13 @@ \subsubsection{描述} \subsubsection{分析} -这是一道很有挑战的题。 +這是一道很有挑戰的題。 -\subsubsection{递归版} +\subsubsection{遞歸版} \begin{Code} // LeetCode, Regular Expression Matching -// 递归版,时间复杂度O(n),空间复杂度O(1) +// 遞歸版,時間複雜度O(n),空間複雜度O(1) class Solution { public: bool isMatch(const string& s, const string& p) { @@ -541,9 +643,9 @@ \subsubsection{迭代版} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Wildcard Matching, 见 \S \ref{sec:wildcard-matching} +\item Wildcard Matching, 見 \S \ref{sec:wildcard-matching} \myenddot @@ -577,16 +679,16 @@ \subsubsection{描述} \subsubsection{分析} -跟上一题很类似。 +跟上一題很類似。 -主要是\fn{'*'}的匹配问题。\fn{p}每遇到一个\fn{'*'},就保留住当前\fn{'*'}的坐标和\fn{s}的坐标,然后\fn{s}从前往后扫描,如果不成功,则\fn{s++},重新扫描。 +主要是\fn{'*'}的匹配問題。\fn{p}每遇到一個\fn{'*'},就保留住當前\fn{'*'}的座標和\fn{s}的座標,然後\fn{s}從前往後掃描,如果不成功,則\fn{s++},重新掃描。 -\subsubsection{递归版} +\subsubsection{遞歸版} \begin{Code} // LeetCode, Wildcard Matching -// 递归版,会超时,用于帮助理解题意 -// 时间复杂度O(n!*m!),空间复杂度O(n) +// 遞歸版,會超時,用於幫助理解題意 +// 時間複雜度O(n!*m!),空間複雜度O(n) class Solution { public: bool isMatch(const string& s, const string& p) { @@ -612,7 +714,7 @@ \subsubsection{递归版} \subsubsection{迭代版} \begin{Code} // LeetCode, Wildcard Matching -// 迭代版,时间复杂度O(n*m),空间复杂度O(1) +// 迭代版,時間複雜度O(n*m),空間複雜度O(1) class Solution { public: bool isMatch(const string& s, const string& p) { @@ -636,7 +738,7 @@ \subsubsection{迭代版} break; default: if (*str != *ptr) { - // 如果前面没有'*',则匹配不成功 + // 如果前面沒有'*',則匹配不成功 if (!star) return false; s++; str = s - 1; @@ -651,9 +753,9 @@ \subsubsection{迭代版} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Regular Expression Matching, 见 \S \ref{sec:regular-expression-matching} +\item Regular Expression Matching, 見 \S \ref{sec:regular-expression-matching} \myenddot @@ -666,21 +768,21 @@ \subsubsection{描述} \subsubsection{分析} -从位置0开始,对每一个位置比较所有字符串,直到遇到一个不匹配。 +從位置0開始,對每一個位置比較所有字符串,直到遇到一個不匹配。 -\subsubsection{纵向扫描} +\subsubsection{縱向掃描} \begin{Code} // LeetCode, Longest Common Prefix -// 纵向扫描,从位置0开始,对每一个位置比较所有字符串,直到遇到一个不匹配 -// 时间复杂度O(n1+n2+...) +// 縱向掃描,從位置0開始,對每一個位置比較所有字符串,直到遇到一個不匹配 +// 時間複雜度O(n1+n2+...) // @author 周倩 (http://weibo.com/zhouditty) class Solution { public: string longestCommonPrefix(vector &strs) { if (strs.empty()) return ""; - for (int idx = 0; idx < strs[0].size(); ++idx) { // 纵向扫描 + for (int idx = 0; idx < strs[0].size(); ++idx) { // 縱向掃描 for (int i = 1; i < strs.size(); ++i) { if (strs[i][idx] != strs[0][idx]) return strs[0].substr(0,idx); } @@ -691,12 +793,12 @@ \subsubsection{纵向扫描} \end{Code} -\subsubsection{横向扫描} +\subsubsection{橫向掃描} \begin{Code} // LeetCode, Longest Common Prefix -// 横向扫描,每个字符串与第0个字符串,从左到右比较,直到遇到一个不匹配, -// 然后继续下一个字符串 -// 时间复杂度O(n1+n2+...) +// 橫向掃描,每個字符串與第0個字符串,從左到右比較,直到遇到一個不匹配, +// 然後繼續下一個字符串 +// 時間複雜度O(n1+n2+...) class Solution { public: string longestCommonPrefix(vector &strs) { @@ -705,7 +807,7 @@ \subsubsection{横向扫描} int right_most = strs[0].size() - 1; for (size_t i = 1; i < strs.size(); i++) for (int j = 0; j <= right_most; j++) - if (strs[i][j] != strs[0][j]) // 不会越界,请参考string::[]的文档 + if (strs[i][j] != strs[0][j]) // 不會越界,請參考string::[]的文檔 right_most = j - 1; return strs[0].substr(0, right_most + 1); @@ -714,9 +816,9 @@ \subsubsection{横向扫描} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -740,16 +842,16 @@ \subsubsection{描述} \subsubsection{分析} -细节实现题。 +細節實現題。 -本题的功能与标准库中的\fn{strtod()}功能类似。 +本題的功能與標準庫中的\fn{strtod()}功能類似。 -\subsubsection{有限自动机} +\subsubsection{有限自動機} \begin{Code} // LeetCode, Valid Number -// @author 龚陆安 (http://weibo.com/luangong) -// finite automata,时间复杂度O(n),空间复杂度O(n) +// @author 龔陸安 (http://weibo.com/luangong) +// finite automata,時間複雜度O(n),空間複雜度O(n) class Solution { public: bool isNumber(const string& s) { @@ -806,8 +908,8 @@ \subsubsection{有限自动机} \subsubsection{使用strtod()} \begin{Code} // LeetCode, Valid Number -// @author 连城 (http://weibo.com/lianchengzju) -// 偷懒,直接用 strtod(),时间复杂度O(n) +// @author 連城 (http://weibo.com/lianchengzju) +// 偷懶,直接用 strtod(),時間複雜度O(n) class Solution { public: bool isNumber (const string& s) { @@ -829,9 +931,9 @@ \subsubsection{使用strtod()} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -846,13 +948,13 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Integer to Roman -// 时间复杂度O(num),空间复杂度O(1) +// 時間複雜度O(num),空間複雜度O(1) class Solution { public: string intToRoman(int num) { @@ -873,9 +975,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Roman to Integer, 见 \S \ref{sec:roman-to-integer} +\item Roman to Integer, 見 \S \ref{sec:roman-to-integer} \myenddot @@ -890,15 +992,15 @@ \subsubsection{描述} \subsubsection{分析} -从前往后扫描,用一个临时变量记录分段数字。 +從前往後掃描,用一個臨時變量記錄分段數字。 -如果当前比前一个大,说明这一段的值应该是当前这个值减去上一个值。比如\fn{IV = 5 – 1};否则,将当前值加入到结果中,然后开始下一段记录。比如\fn{VI = 5 + 1, II=1+1} +如果當前比前一個大,説明這一段的值應該是當前這個值減去上一個值。比如\fn{IV = 5 – 1};否則,將當前值加入到結果中,然後開始下一段記錄。比如\fn{VI = 5 + 1, II=1+1} -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Roman to Integer -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: inline int map(const char c) { @@ -929,9 +1031,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Integer to Roman, 见 \S \ref{sec:integer-to-roman} +\item Integer to Roman, 見 \S \ref{sec:integer-to-roman} \myenddot @@ -957,14 +1059,14 @@ \subsubsection{描述} \subsubsection{分析} -模拟。 +模擬。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Count and Say -// @author 连城 (http://weibo.com/lianchengzju) -// 时间复杂度O(n^2),空间复杂度O(n) +// @author 連城 (http://weibo.com/lianchengzju) +// 時間複雜度O(n^2),空間複雜度O(n) class Solution { public: string countAndSay(int n) { @@ -991,9 +1093,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -1008,15 +1110,15 @@ \subsubsection{描述} \subsubsection{分析} -Anagram(回文构词法)是指打乱字母顺序从而得到新的单词,比如 \fn{"dormitory"} 打乱字母顺序会变成 \fn{"dirty room"} ,\fn{"tea"} 会变成\fn{"eat"}。 +Anagram(迴文構詞法)是指打亂字母順序從而得到新的單詞,比如 \fn{"dormitory"} 打亂字母順序會變成 \fn{"dirty room"} ,\fn{"tea"} 會變成\fn{"eat"}。 -回文构词法有一个特点:单词里的字母的种类和数目没有改变,只是改变了字母的排列顺序。因此,将几个单词按照字母顺序排序后,若它们相等,则它们属于同一组 anagrams 。 +迴文構詞法有一個特點:單詞裏的字母的種類和數目沒有改變,只是改變了字母的排列順序。因此,將幾個單詞按照字母順序排序後,若它們相等,則它們屬於同一組 anagrams 。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Anagrams -// 时间复杂度O(n),空间复杂度O(n) +// 時間複雜度O(n),空間複雜度O(n) class Solution { public: vector anagrams(vector &strs) { @@ -1038,9 +1140,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -1066,17 +1168,17 @@ \subsubsection{描述} \subsubsection{分析} -很有实际价值的题目。 +很有實際價值的題目。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Simplify Path -// 时间复杂度O(n),空间复杂度O(n) +// 時間複雜度O(n),空間複雜度O(n) class Solution { public: string simplifyPath(const string& path) { - vector dirs; // 当做栈 + vector dirs; // 當做棧 for (auto i = path.begin(); i != path.end();) { ++i; @@ -1084,7 +1186,7 @@ \subsubsection{代码} auto j = find(i, path.end(), '/'); auto dir = string(i, j); - if (!dir.empty() && dir != ".") {// 当有连续 '///'时,dir 为空 + if (!dir.empty() && dir != ".") {// 當有連續 '///'時,dir 為空 if (dir == "..") { if (!dirs.empty()) dirs.pop_back(); @@ -1109,9 +1211,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -1132,14 +1234,14 @@ \subsubsection{描述} \subsubsection{分析} -细节实现题。 +細節實現題。 \subsubsection{用 STL} \begin{Code} // LeetCode, Length of Last Word -// 偷懒,用 STL -// 时间复杂度O(n),空间复杂度O(1) +// 偷懶,用 STL +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: int lengthOfLastWord(const string& s) { @@ -1151,11 +1253,11 @@ \subsubsection{用 STL} \end{Code} -\subsubsection{顺序扫描} +\subsubsection{順序掃描} \begin{Code} // LeetCode, Length of Last Word -// 顺序扫描,记录每个 word 的长度 -// 时间复杂度O(n),空间复杂度O(1) +// 順序掃描,記錄每個 word 的長度 +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: int lengthOfLastWord(const string& s) { @@ -1176,7 +1278,304 @@ \subsubsection{顺序扫描} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + +\section{Longest Happy Prefix} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:longest-happy-prefix} + + +\subsubsection{描述} +A string is called a happy prefix if is a non-empty prefix which is also a suffix (excluding itself). +Given a string s. Return the longest happy prefix of s . +Return an empty string if no such prefix exists. + +\begin{Code} +Example 1: +Input: s = "level" +Output: "l" +Explanation: s contains 4 prefix excluding itself ("l", "le", "lev", "leve") +, and suffix ("l", "el", "vel", "evel"). +The largest prefix which is also suffix is given by "l". + +Example 2: +Input: s = "ababab" +Output: "abab" +Explanation: "abab" is the largest prefix which is also suffix. +They can overlap in the original string. + +Example 3: +Input: s = "leetcodeleet" +Output: "leet" + +Example 4: +Input: s = "a" +Output: "" +\end{Code} + +Constraints: +1 $<=$ s.length $<=$ $10^5$ +s contains only lowercase English letters. + +\subsubsection{分析} +細節實現題。 + + +\subsubsection{順序掃描} +\begin{Code} +// LeetCode, Longest happy prefix +// try all possible match +// 時間複雜度O(n^2),空間複雜度O(1) +class Solution { +public: + string longestPrefix(string s) { + int N = (int)s.length(); + if (N < 2) return ""; + + for (int i = 1; i < N; i++) { + int j = i; + int k = 0; + while (k < N && j < N && s[k++] == s[j++]); + + if (j == N) return s.substr(i, N - i); + } + + return ""; + } +}; +\end{Code} + +\subsubsection{Rolling Hash} +\begin{Code} +// LeetCode, Longest happy prefix +// rolling hashing +// 時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + string longestPrefix(string s) { + int times = 2; + int64_t prefixHash = 0; + int64_t suffixHash = 0; + int64_t multiplier = 1; + int64_t len = 0; + int64_t mod = 1000000007;//use some large prime as a modulo to avoid overflow errors, e.g. 10 ^ 9 + 7. + int N = s.length(); + for (int i = 0; i < N - 1; i++) { + prefixHash = (prefixHash * times + s[i]) % mod; + suffixHash = (multiplier * s[N - 1 - i] + suffixHash) % mod; + if (prefixHash == suffixHash) + len = i + 1; + multiplier = multiplier * times % mod; + } + return s.substr(0, len); + } +}; +\end{Code} + +\subsubsection{相關題目} +\begindot +\item 無 +\myenddot + +\section{Decode String} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:decode-string} + + +\subsubsection{描述} +Given an encoded string, return its decoded string. + +The encoding rule is: k[encoded_string], where the encoded_string inside the square brackets is being repeated exactly k times. Note that k is guaranteed to be a positive integer. + +You may assume that the input string is always valid; No extra white spaces, square brackets are well-formed, etc. + +Furthermore, you may assume that the original data does not contain any digits and that digits are only for those repeat numbers, k. For example, there won't be input like 3a or 2[4]. + + +\subsubsection{分析} +Nil + +\subsubsection{遞歸版} +\begin{Code} +// LeetCode +// 時間複雜度O(),空間複雜度O() +class Solution { +public: + string decodeString(string s) { + return decodeString(s.begin(), s.end()); + } +private: + template + string decodeString(BidirecIT first, BidirecIT end) + { + if (first == end) return ""; + + string result; + while (first != end) + { + if (isdigit(*first)) + { + // 取得所有數字 + auto cur = first; + while (cur != end) { if (*cur == '[') break; cur++; } + int num = stol(string(first, cur)); + first = cur; + + // 取得接下來的字串 + auto first2 = next(first); + auto end2 = first2; + int curLeft = 1; // 現在 [ 的數目 + while (end2 != end && curLeft > 0) + { + if (*end2 == '[') + curLeft++; + else if (*end2 == ']') + curLeft--; + end2++; + } + string result2 = decodeString(first2, prev(end2)); + string result3; + // 重覆地放入 + for (int i = 0; i < num; i++) + result3 += result2; + result += result3; + + first = end2; + } + else + { + // 取得所有字母 + auto cur = first; + while (cur != end) { if (*cur >= '0' && *cur <= '9') break; cur++; } + result += string(first, cur); + first = cur; + } + } + return result; + } +}; +\end{Code} + + +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot + +\section{Unique Word Abbreviation} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:unique-word-abbreviation} + + +\subsubsection{描述} +An abbreviation of a word follows the form . Below are some examples of word abbreviations: + +\begin{Code} +a) it --> it (no abbreviation) + + 1 + ↓ +b) d|o|g --> d1g + + 1 1 1 + 1---5----0----5--8 + ↓ ↓ ↓ ↓ ↓ +c) i|nternationalizatio|n --> i18n + + 1 + 1---5----0 + ↓ ↓ ↓ +d) l|ocalizatio|n --> l10n + +Additionally for any string s of size less than or equal to 2 their abbreviation is the same string s. +\end{Code} + +Find whether its abbreviation is unique in the dictionary. A word's abbreviation is called unique if any of the following conditions is met: + +\begindot +\item There is no word in dictionary such that their abbreviation is equal to the abbreviation of word. +\item Else, for all words in dictionary such that their abbreviation is equal to the abbreviation of word those words are equal to word. +\myenddot + +\subsubsection{普通數數法} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(2*n) +class ValidWordAbbr { +public: + ValidWordAbbr(vector& dictionary) { + for (const string& s : dictionary) + { + m_words[s]++; + m_abbrs[ToAbbr(s)]++; + } + } + + bool isUnique(string word) { + auto it = m_abbrs.find(ToAbbr(word)); + auto it2 = m_words.find(word); + // 若簡寫沒有出現過 + if (it == m_abbrs.end()) return true; + // 若文字相同,和所有簡寫都來自同一個字源 + else if (it2 != m_words.end() && it->second == it2->second) return true; + // 若簡寫相同,和簡寫來自不同文字 + else return false; + } +private: + string ToAbbr(string inStr) + { + int N = inStr.size(); + if (N <= 2) return inStr; + return inStr.front() + to_string(N-2) + inStr.back(); + } +private: + unordered_map m_words; + unordered_map m_abbrs; +}; +\end{Code} + +\subsubsection{改良版} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(n) +// 用 bool 來記低同一個簡寫有沒有多過一個文字的來源 +class ValidWordAbbr { +public: + ValidWordAbbr(vector& D) { + for (auto& w : D) { + Add(w); + } + } + + bool isUnique(string w) { + string abbrev; + Abbrev(w, abbrev); + if (!dict.count(abbrev)) return true; + auto& [s,good] = dict[abbrev]; + // 當 good 為 false, 我們可以肯定此短寫來自超過一個文字 + // 那麼投入來的文字,必定不會是唯一 + return good && s == w; + } +private: + void Abbrev(const string& w, string& abbrev) { + if (w.length() > 2){ + abbrev = w[0] + to_string(int(w.length()-2)) + w.back(); + } else { + abbrev = w; + } + } + void Add(string& w) { + string abbrev; + Abbrev(w, abbrev); + if (!dict.count(abbrev)) { + // 第一個文字出現時,good 必定為 true + // good 為 true 時,代表文字一致 + dict[abbrev] = {w,true}; + return; + } + auto& [s,good] = dict[abbrev]; + // 若果新加入的文字和之前的不同,good 為 false + if (good && s != w) good = false; + } +private: + unordered_map> dict; +}; +\end{Code} diff --git a/C++/chapTree.tex b/C++/chapTree.tex index 7e70c7b5..1cfd6705 100644 --- a/C++/chapTree.tex +++ b/C++/chapTree.tex @@ -1,8 +1,8 @@ -\chapter{树} +\chapter{樹} -LeetCode 上二叉树的节点定义如下: +LeetCode 上二叉樹的節點定義如下: \begin{Code} -// 树的节点 +// 樹的節點 struct TreeNode { int val; TreeNode *left; @@ -12,15 +12,15 @@ \chapter{树} \end{Code} -\section{二叉树的遍历} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{二叉樹的遍歷} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -树的遍历有两类:深度优先遍历和宽度优先遍历。深度优先遍历又可分为两种:先根(次序)遍历和后根(次序)遍历。 +樹的遍歷有兩類:深度優先遍歷和寬度優先遍歷。深度優先遍歷又可分為兩種:先根(次序)遍歷和後根(次序)遍歷。 -树的先根遍历是:先访问树的根结点,然后依次先根遍历根的各棵子树。树的先跟遍历的结果与对应二叉树(孩子兄弟表示法)的先序遍历的结果相同。 +樹的先根遍歷是:先訪問樹的根結點,然後依次先根遍歷根的各棵子樹。樹的先跟遍歷的結果與對應二叉樹(孩子兄弟表示法)的先序遍歷的結果相同。 -树的后根遍历是:先依次后根遍历树根的各棵子树,然后访问根结点。树的后跟遍历的结果与对应二叉树的中序遍历的结果相同。 +樹的後根遍歷是:先依次後根遍歷樹根的各棵子樹,然後訪問根結點。樹的後跟遍歷的結果與對應二叉樹的中序遍歷的結果相同。 -二叉树的先根遍历有:\textbf{先序遍历}(root->left->right),root->right->left;后根遍历有:\textbf{后序遍历}(left->right->root),right->left->root;二叉树还有个一般的树没有的遍历次序,\textbf{中序遍历}(left->root->right)。 +二叉樹的先根遍歷有:\textbf{先序遍歷}(root->left->right),root->right->left;後根遍歷有:\textbf{後序遍歷}(left->right->root),right->left->root;二叉樹還有個一般的樹沒有的遍歷次序,\textbf{中序遍歷}(left->root->right)。 \subsection{Binary Tree Preorder Traversal} @@ -39,19 +39,19 @@ \subsubsection{描述} / 3 \end{Code} -return \code{[1,2,3]}. +return \code{\[1,2,3\]}. Note: Recursive solution is trivial, could you do it iteratively? \subsubsection{分析} -用栈或者Morris遍历。 +用棧或者Morris遍歷。 -\subsubsection{栈} +\subsubsection{棧} \begin{Code} // LeetCode, Binary Tree Preorder Traversal -// 使用栈,时间复杂度O(n),空间复杂度O(n) +// 使用棧,時間複雜度O(n),空間複雜度O(n) class Solution { public: vector preorderTraversal(TreeNode *root) { @@ -73,10 +73,10 @@ \subsubsection{栈} \end{Code} -\subsubsection{Morris先序遍历} +\subsubsection{Morris先序遍歷} \begin{Code} // LeetCode, Binary Tree Preorder Traversal -// Morris先序遍历,时间复杂度O(n),空间复杂度O(1) +// Morris先序遍歷,時間複雜度O(n),空間複雜度O(1) class Solution { public: vector preorderTraversal(TreeNode *root) { @@ -86,22 +86,22 @@ \subsubsection{Morris先序遍历} while (cur != nullptr) { if (cur->left == nullptr) { result.push_back(cur->val); - prev = cur; /* cur刚刚被访问过 */ + prev = cur; /* cur剛剛被訪問過 */ cur = cur->right; } else { - /* 查找前驱 */ + /* 查找前驅 */ TreeNode *node = cur->left; while (node->right != nullptr && node->right != cur) node = node->right; - if (node->right == nullptr) { /* 还没线索化,则建立线索 */ - result.push_back(cur->val); /* 仅这一行的位置与中序不同 */ + if (node->right == nullptr) { /* 還沒線索化,則建立線索 */ + result.push_back(cur->val); /* 僅這一行的位置與中序不同 */ node->right = cur; - prev = cur; /* cur刚刚被访问过 */ + prev = cur; /* cur剛剛被訪問過 */ cur = cur->left; - } else { /* 已经线索化,则删除线索 */ + } else { /* 已經線索化,則刪除線索 */ node->right = nullptr; - /* prev = cur; 不能有这句,cur已经被访问 */ + /* prev = cur; 不能有這句,cur已經被訪問 */ cur = cur->right; } } @@ -112,11 +112,11 @@ \subsubsection{Morris先序遍历} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Binary Tree Inorder Traversal,见 \S \ref{sec:binary-tree-inorder-traversal} -\item Binary Tree Postorder Traversal,见 \S \ref{sec:binary-tree-postorder-traversal} -\item Recover Binary Search Tree,见 \S \ref{sec:recover-binary-search-tree} +\item Binary Tree Inorder Traversal,見 \S \ref{sec:binary-tree-inorder-traversal} +\item Binary Tree Postorder Traversal,見 \S \ref{sec:binary-tree-postorder-traversal} +\item Recover Binary Search Tree,見 \S \ref{sec:recover-binary-search-tree} \myenddot @@ -136,19 +136,19 @@ \subsubsection{描述} / 3 \end{Code} -return \code{[1,3,2]}. +return \code{\[1,3,2\]}. Note: Recursive solution is trivial, could you do it iteratively? \subsubsection{分析} -用栈或者Morris遍历。 +用棧或者Morris遍歷。 -\subsubsection{栈} +\subsubsection{棧} \begin{Code} // LeetCode, Binary Tree Inorder Traversal -// 使用栈,时间复杂度O(n),空间复杂度O(n) +// 使用棧,時間複雜度O(n),空間複雜度O(n) class Solution { public: vector inorderTraversal(TreeNode *root) { @@ -173,10 +173,10 @@ \subsubsection{栈} \end{Code} -\subsubsection{Morris中序遍历} +\subsubsection{Morris中序遍歷} \begin{Code} // LeetCode, Binary Tree Inorder Traversal -// Morris中序遍历,时间复杂度O(n),空间复杂度O(1) +// Morris中序遍歷,時間複雜度O(n),空間複雜度O(1) class Solution { public: vector inorderTraversal(TreeNode *root) { @@ -189,16 +189,16 @@ \subsubsection{Morris中序遍历} prev = cur; cur = cur->right; } else { - /* 查找前驱 */ + /* 查找前驅 */ TreeNode *node = cur->left; while (node->right != nullptr && node->right != cur) node = node->right; - if (node->right == nullptr) { /* 还没线索化,则建立线索 */ + if (node->right == nullptr) { /* 還沒線索化,則建立線索 */ node->right = cur; - /* prev = cur; 不能有这句,cur还没有被访问 */ + /* prev = cur; 不能有這句,cur還沒有被訪問 */ cur = cur->left; - } else { /* 已经线索化,则访问节点,并删除线索 */ + } else { /* 已經線索化,則訪問節點,並刪除線索 */ result.push_back(cur->val); node->right = nullptr; prev = cur; @@ -212,11 +212,11 @@ \subsubsection{Morris中序遍历} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Binary Tree Preorder Traversal,见 \S \ref{sec:binary-tree-preorder-traversal} -\item Binary Tree Postorder Traversal,见 \S \ref{sec:binary-tree-postorder-traversal} -\item Recover Binary Search Tree,见 \S \ref{sec:recover-binary-search-tree} +\item Binary Tree Preorder Traversal,見 \S \ref{sec:binary-tree-preorder-traversal} +\item Binary Tree Postorder Traversal,見 \S \ref{sec:binary-tree-postorder-traversal} +\item Recover Binary Search Tree,見 \S \ref{sec:recover-binary-search-tree} \myenddot @@ -236,25 +236,25 @@ \subsubsection{描述} / 3 \end{Code} -return \code{[3,2,1]}. +return \code{\[3,2,1\]}. Note: Recursive solution is trivial, could you do it iteratively? \subsubsection{分析} -用栈或者Morris遍历。 +用棧或者Morris遍歷。 -\subsubsection{栈} +\subsubsection{棧} \begin{Code} // LeetCode, Binary Tree Postorder Traversal -// 使用栈,时间复杂度O(n),空间复杂度O(n) +// 使用棧,時間複雜度O(n),空間複雜度O(n) class Solution { public: vector postorderTraversal(TreeNode *root) { vector result; stack s; - /* p,正在访问的结点,q,刚刚访问过的结点*/ + /* p,正在訪問的結點,q,剛剛訪問過的結點*/ const TreeNode *p = root, *q = nullptr; do { @@ -266,14 +266,14 @@ \subsubsection{栈} while (!s.empty()) { p = s.top(); s.pop(); - /* 右孩子不存在或已被访问,访问之*/ + /* 右孩子不存在或已被訪問,訪問之*/ if (p->right == q) { result.push_back(p->val); - q = p; /* 保存刚访问过的结点*/ + q = p; /* 保存剛訪問過的結點*/ } else { - /* 当前结点不能访问,需第二次进栈*/ + /* 當前結點不能訪問,需第二次進棧*/ s.push(p); - /* 先处理右子树*/ + /* 先處理右子樹*/ p = p->right; break; } @@ -286,10 +286,10 @@ \subsubsection{栈} \end{Code} -\subsubsection{Morris后序遍历} +\subsubsection{Morris後序遍歷} \begin{Code} // LeetCode, Binary Tree Postorder Traversal -// Morris后序遍历,时间复杂度O(n),空间复杂度O(1) +// Morris後序遍歷,時間複雜度O(n),空間複雜度O(1) class Solution { public: vector postorderTraversal(TreeNode *root) { @@ -305,21 +305,21 @@ \subsubsection{Morris后序遍历} cur = &dummy; while (cur != nullptr) { if (cur->left == nullptr) { - prev = cur; /* 必须要有 */ + prev = cur; /* 必須要有 */ cur = cur->right; } else { TreeNode *node = cur->left; while (node->right != nullptr && node->right != cur) node = node->right; - if (node->right == nullptr) { /* 还没线索化,则建立线索 */ + if (node->right == nullptr) { /* 還沒線索化,則建立線索 */ node->right = cur; - prev = cur; /* 必须要有 */ + prev = cur; /* 必須要有 */ cur = cur->left; - } else { /* 已经线索化,则访问节点,并删除线索 */ + } else { /* 已經線索化,則訪問節點,並刪除線索 */ visit_reverse(cur->left, prev, visit); prev->right = nullptr; - prev = cur; /* 必须要有 */ + prev = cur; /* 必須要有 */ cur = cur->right; } } @@ -327,7 +327,7 @@ \subsubsection{Morris后序遍历} return result; } private: - // 逆转路径 + // 逆轉路徑 static void reverse(TreeNode *from, TreeNode *to) { TreeNode *x = from, *y = from->right, *z; if (from == to) return; @@ -340,7 +340,7 @@ \subsubsection{Morris后序遍历} } } - // 访问逆转后的路径上的所有结点 + // 訪問逆轉後的路徑上的所有結點 static void visit_reverse(TreeNode* from, TreeNode *to, std::function< void(const TreeNode*) >& visit) { TreeNode *p = to; @@ -359,11 +359,11 @@ \subsubsection{Morris后序遍历} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Binary Tree Preorder Traversal,见 \S \ref{sec:binary-tree-preorder-traversal} -\item Binary Tree Inorder Traversal,见 \S \ref{sec:binary-tree-inorder-traversal} -\item Recover Binary Search Tree,见 \S \ref{sec:recover-binary-search-tree} +\item Binary Tree Preorder Traversal,見 \S \ref{sec:binary-tree-preorder-traversal} +\item Binary Tree Inorder Traversal,見 \S \ref{sec:binary-tree-inorder-traversal} +\item Recover Binary Search Tree,見 \S \ref{sec:recover-binary-search-tree} \myenddot @@ -394,13 +394,13 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{递归版} +\subsubsection{遞歸版} \begin{Code} // LeetCode, Binary Tree Level Order Traversal -// 递归版,时间复杂度O(n),空间复杂度O(n) +// 遞歸版,時間複雜度O(n),空間複雜度O(n) class Solution { public: vector > levelOrder(TreeNode *root) { @@ -426,7 +426,7 @@ \subsubsection{递归版} \subsubsection{迭代版} \begin{Code} // LeetCode, Binary Tree Level Order Traversal -// 迭代版,时间复杂度O(n),空间复杂度O(1) +// 迭代版,時間複雜度O(n),空間複雜度O(1) class Solution { public: vector > levelOrder(TreeNode *root) { @@ -457,15 +457,15 @@ \subsubsection{迭代版} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Binary Tree Level Order Traversal II,见 \S \ref{sec:binary-tree-tevel-order-traversal-ii} -\item Binary Tree Zigzag Level Order Traversal,见 \S \ref{sec:binary-tree-zigzag-level-order-traversal} +\item Binary Tree Level Order Traversal II,見 \S \ref{sec:binary-tree-level-order-traversal-ii} +\item Binary Tree Zigzag Level Order Traversal,見 \S \ref{sec:binary-tree-zigzag-level-order-traversal} \myenddot \subsection{Binary Tree Level Order Traversal II} -\label{sec:binary-tree-tevel-order-traversal-ii} +\label{sec:binary-tree-level-order-traversal-ii} \subsubsection{描述} @@ -491,19 +491,19 @@ \subsubsection{描述} \subsubsection{分析} -在上一题(见\S \ref{sec:binary-tree-tevel-order-traversal})的基础上,\fn{reverse()}一下即可。 +在上一題(見\S \ref{sec:binary-tree-level-order-traversal})的基礎上,\fn{reverse()}一下即可。 -\subsubsection{递归版} +\subsubsection{遞歸版} \begin{Code} // LeetCode, Binary Tree Level Order Traversal II -// 递归版,时间复杂度O(n),空间复杂度O(n) +// 遞歸版,時間複雜度O(n),空間複雜度O(n) class Solution { public: vector > levelOrderBottom(TreeNode *root) { vector> result; traverse(root, 1, result); - std::reverse(result.begin(), result.end()); // 比上一题多此一行 + std::reverse(result.begin(), result.end()); // 比上一題多此一行 return result; } @@ -524,7 +524,7 @@ \subsubsection{递归版} \subsubsection{迭代版} \begin{Code} // LeetCode, Binary Tree Level Order Traversal II -// 迭代版,时间复杂度O(n),空间复杂度O(1) +// 迭代版,時間複雜度O(n),空間複雜度O(1) class Solution { public: vector > levelOrderBottom(TreeNode *root) { @@ -547,17 +547,17 @@ \subsubsection{迭代版} level.clear(); swap(next, current); } - reverse(result.begin(), result.end()); // 比上一题多此一行 + reverse(result.begin(), result.end()); // 比上一題多此一行 return result; } }; \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Binary Tree Level Order Traversal,见 \S \ref{sec:binary-tree-tevel-order-traversal} -\item Binary Tree Zigzag Level Order Traversal,见 \S \ref{sec:binary-tree-zigzag-level-order-traversal} +\item Binary Tree Level Order Traversal,見 \S \ref{sec:binary-tree-level-order-traversal} +\item Binary Tree Zigzag Level Order Traversal,見 \S \ref{sec:binary-tree-zigzag-level-order-traversal} \myenddot @@ -588,13 +588,13 @@ \subsubsection{描述} \subsubsection{分析} -广度优先遍历,用一个bool记录是从左到右还是从右到左,每一层结束就翻转一下。 +廣度優先遍歷,用一個bool記錄是從左到右還是從右到左,每一層結束就翻轉一下。 -\subsubsection{递归版} +\subsubsection{遞歸版} \begin{Code} // LeetCode, Binary Tree Zigzag Level Order Traversal -// 递归版,时间复杂度O(n),空间复杂度O(n) +// 遞歸版,時間複雜度O(n),空間複雜度O(n) class Solution { public: vector > zigzagLevelOrder(TreeNode *root) { @@ -624,8 +624,8 @@ \subsubsection{递归版} \subsubsection{迭代版} \begin{Code} // LeetCode, Binary Tree Zigzag Level Order Traversal -// 广度优先遍历,用一个bool记录是从左到右还是从右到左,每一层结束就翻转一下。 -// 迭代版,时间复杂度O(n),空间复杂度O(n) +// 廣度優先遍歷,用一個bool記錄是從左到右還是從右到左,每一層結束就翻轉一下。 +// 迭代版,時間複雜度O(n),空間複雜度O(n) class Solution { public: vector > zigzagLevelOrder(TreeNode *root) { @@ -659,12 +659,77 @@ \subsubsection{迭代版} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Binary Tree Level Order Traversal,见 \S \ref{sec:binary-tree-tevel-order-traversal} -\item Binary Tree Level Order Traversal II,见 \S \ref{sec:binary-tree-tevel-order-traversal-ii} +\item Binary Tree Level Order Traversal,見 \S \ref{sec:binary-tree-level-order-traversal} +\item Binary Tree Level Order Traversal II,見 \S \ref{sec:binary-tree-level-order-traversal-ii} \myenddot +\subsection{Binary Tree Right side View} +\label{sec:binary-tree-right-side-view} + + +\subsubsection{描述} +Given a binary tree, imagine yourself standing on the right side of it, return the values of the nodes you can see ordered from top to bottom. + +For example: +Given binary tree \code{{3,9,20,\#,\#,15,7}}, +\begin{Code} + 3 <-- + / \ + 9 20 <-- + / \ + 15 7 <-- +\end{Code} +return as: +\begin{Code} +[3,20,7] +\end{Code} + + +\subsubsection{分析} +雙 queue BFS zigzag travel, 只選最右 + +\subsubsection{遞歸版} +\begin{Code} +// LeetCode, Binary Tree Zigzag Level Order Traversal +// 遞歸版,時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + vector rightSideView(TreeNode* root) { + if (root == nullptr) return vector(); + vector result; + + // 雙 queue BFS zigzag travel + queue cur, next; + cur.push(root); + while (!cur.empty()) + { + while (!cur.empty()) + { + TreeNode *p = cur.front(); + cur.pop(); + + if (cur.size() == 0) // 只選最右 + result.push_back(p->val); + + if (p->left) next.push(p->left); + if (p->right) next.push(p->right); + } + swap(cur, next); + } + + return result; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Binary Tree Level Order Traversal,見 \S \ref{sec:binary-tree-level-order-traversal} +\item Binary Tree Level Order Traversal II,見 \S \ref{sec:binary-tree-level-order-traversal-ii} +\myenddot \subsection{Recover Binary Search Tree} \label{sec:recover-binary-search-tree} @@ -679,16 +744,16 @@ \subsubsection{描述} \subsubsection{分析} -$O(n)$空间的解法是,开一个指针数组,中序遍历,将节点指针依次存放到数组里,然后寻找两处逆向的位置,先从前往后找第一个逆序的位置,然后从后往前找第二个逆序的位置,交换这两个指针的值。 +$O(n)$空間的解法是,開一個指針數組,中序遍歷,將節點指針依次存放到數組裏,然後尋找兩處逆向的位置,先從前往後找第一個逆序的位置,然後從後往前找第二個逆序的位置,交換這兩個指針的值。 -中序遍历一般需要用到栈,空间也是$O(n)$的,如何才能不使用栈?Morris中序遍历。 +中序遍歷一般需要用到棧,空間也是$O(n)$的,如何才能不使用棧?Morris中序遍歷。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Recover Binary Search Tree -// Morris中序遍历,时间复杂度O(n),空间复杂度O(1) +// Morris中序遍歷,時間複雜度O(n),空間複雜度O(1) class Solution { public: void recoverTree(TreeNode* root) { @@ -709,7 +774,7 @@ \subsubsection{代码} if (node->right == nullptr) { node->right = cur; - //prev = cur; 不能有这句!因为cur还没有被访问 + //prev = cur; 不能有這句!因為cur還沒有被訪問 cur = cur->left; } else { detect(broken, prev, cur); @@ -728,8 +793,8 @@ \subsubsection{代码} if (prev != nullptr && prev->val > current->val) { if (broken.first == nullptr) { broken.first = prev; - } //不能用else,例如 {0,1},会导致最后 swap时second为nullptr, - //会 Runtime Error + } //不能用else,例如 {0,1},會導致最後 swap時second為nullptr, + //會 Runtime Error broken.second = current; } } @@ -737,9 +802,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Binary Tree Inorder Traversal,见 \S \ref{sec:binary-tree-inorder-traversal} +\item Binary Tree Inorder Traversal,見 \S \ref{sec:binary-tree-inorder-traversal} \myenddot @@ -754,20 +819,20 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{递归版} -递归版 +\subsubsection{遞歸版} +遞歸版 \begin{Code} // LeetCode, Same Tree -// 递归版,时间复杂度O(n),空间复杂度O(logn) +// 遞歸版,時間複雜度O(n),空間複雜度O(logn) class Solution { public: bool isSameTree(TreeNode *p, TreeNode *q) { - if (!p && !q) return true; // 终止条件 + if (!p && !q) return true; // 終止條件 if (!p || !q) return false; // 剪枝 - return p->val == q->val // 三方合并 + return p->val == q->val // 三方合併 && isSameTree(p->left, q->left) && isSameTree(p->right, q->right); } @@ -778,7 +843,7 @@ \subsubsection{递归版} \subsubsection{迭代版} \begin{Code} // LeetCode, Same Tree -// 迭代版,时间复杂度O(n),空间复杂度O(logn) +// 迭代版,時間複雜度O(n),空間複雜度O(logn) class Solution { public: bool isSameTree(TreeNode *p, TreeNode *q) { @@ -806,9 +871,187 @@ \subsubsection{迭代版} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} +\begindot +\item Symmetric Tree,見 \S \ref{sec:symmetric-tree} +\myenddot + +\subsection{Count Univalue Subtrees} +\label{sec:count-univalue-subtrees} + + +\subsubsection{描述} +Given a binary tree, count the number of uni-value subtrees. + +A Uni-value subtree means all nodes of the subtree have the same value. + +Example: + +\begin{Code} +Input: root = [5,1,5,5,5,null,5] + + 5 + / \ + 1 5 + / \ \ + 5 5 5 + +Output: 4 +\end{Code} + +\subsubsection{分析} +無 + +\subsubsection{Pass Parent Values} +\begin{Code} +// LeetCode +// 迭代版,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + int countUnivalSubtrees(TreeNode* root) { + if (root == nullptr) return 0; + + int count = 0; + DFS(root, count, root->val - 1); + + return count; + } +private: + bool DFS(TreeNode *root, int& count, int parVal) { + if (root == nullptr) return true; + + int leftCount, rightCount; leftCount = rightCount = 0; + bool isLeftUni = DFS(root->left, leftCount, root->val); + bool isRightUni = DFS(root->right, rightCount, root->val); + + count += leftCount + rightCount; + + if (!isLeftUni || !isRightUni) return false; + + count++; + + return root->val == parVal; + } +}; +\end{Code} + +\subsubsection{遞歸版} +\begin{Code} +// LeetCode +// 迭代版,時間複雜度O(n),空間複雜度O(logn) +class Solution { +public: + int countUnivalSubtrees(TreeNode* root) { + if (root == nullptr) return 0; + + int uniCount = 0; + bool uniTrue = false; + countUnivalSubtrees(root, uniCount, uniTrue); + + return uniCount; + } +private: + void countUnivalSubtrees(TreeNode *root, int& uniCount, bool& uniTrue) { + if (root == nullptr) { + uniCount = 0; + uniTrue = false; + } + else if (root->left == nullptr && root->right == nullptr) { + uniCount = 1; + uniTrue = true; + } + else { + int uniLeft, uniRight; uniLeft = uniRight = 0; + bool isUniLeft, isUniRight; isUniLeft = isUniRight = false; + countUnivalSubtrees(root->left, uniLeft, isUniLeft); + countUnivalSubtrees(root->right, uniRight, isUniRight); + + // 三方比較 + if (root->left && root->right + && (root->val == root->left->val && isUniLeft) + && (root->val == root->right->val && isUniRight)) { + uniLeft++; + uniTrue = true; + } + else if (root->left && root->right == nullptr + && root->val == root->left->val && isUniLeft) + { uniLeft++; uniTrue = true; } // 左邊比較 + else if (root->left == nullptr && root->right + && root->val == root->right->val && isUniRight) + { uniRight++; uniTrue = true; } // 右邊比較 + + uniCount = uniLeft + uniRight; + } + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Same Tree,見 \S \ref{sec:same-tree} +\myenddot + +\subsection{Lowest Common Ancestor of Binary Tree} +\label{sec:lowest-common-ancestor-of-binary-tree} + + +\subsubsection{描述} +Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. + +According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself).” + +Given the following binary tree: root = [3,5,1,6,2,0,8,null,null,7,4] + +\begin{center} +\includegraphics[width=100pt]{lowest-common-ancestor.png}\\ +\figcaption{Loest common ancestor}\label{fig:lowest-common-ancestor} +\end{center} + +Example 1: +\begin{Code} +Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 +Output: 3 +Explanation: The LCA of nodes 5 and 1 is 3. +\end{Code} + + +Example 2: +\begin{Code} +Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 +Output: 5 +Explanation: The LCA of nodes 5 and 4 is 5 +, since a node can be a descendant of itself according to the LCA definition. +\end{Code} + +\subsubsection{分析} +無 + + +\subsubsection{遞歸版} +\begin{Code} +// LeetCode +// 遞歸版,時間複雜度O(n),空間複雜度O(1) +class Solution { +public: + TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { + if (root == nullptr) return root; + if (root == p || root == q) return root; + + TreeNode *left = lowestCommonAncestor(root->left, p, q); + TreeNode *right = lowestCommonAncestor(root->right, p, q); + + if (left && right) return root; + else if (!left) return right; + else return left; + } +}; +\end{Code} + + +\subsubsection{相關題目} \begindot -\item Symmetric Tree,见 \S \ref{sec:symmetric-tree} +\item Same Tree,見 \S \ref{sec:same-tree} \myenddot @@ -823,13 +1066,13 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{递归版} +\subsubsection{遞歸版} \begin{Code} // LeetCode, Symmetric Tree -// 递归版,时间复杂度O(n),空间复杂度O(logn) +// 遞歸版,時間複雜度O(n),空間複雜度O(logn) class Solution { public: bool isSymmetric(TreeNode *root) { @@ -837,9 +1080,9 @@ \subsubsection{递归版} return isSymmetric(root->left, root->right); } 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 // 三方合并 + 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); } @@ -850,7 +1093,7 @@ \subsubsection{递归版} \subsubsection{迭代版} \begin{Code} // LeetCode, Symmetric Tree -// 迭代版,时间复杂度O(n),空间复杂度O(logn) +// 迭代版,時間複雜度O(n),空間複雜度O(logn) class Solution { public: bool isSymmetric (TreeNode* root) { @@ -881,9 +1124,9 @@ \subsubsection{迭代版} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Same Tree,见 \S \ref{sec:same-tree} +\item Same Tree,見 \S \ref{sec:same-tree} \myenddot @@ -898,13 +1141,13 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Balanced Binary Tree -// 时间复杂度O(n),空间复杂度O(logn) +// 時間複雜度O(n),空間複雜度O(logn) class Solution { public: bool isBalanced (TreeNode* root) { @@ -916,22 +1159,22 @@ \subsubsection{代码} * otherwise, returns `-1`. */ int balancedHeight (TreeNode* root) { - if (root == nullptr) return 0; // 终止条件 + if (root == nullptr) return 0; // 終止條件 int left = balancedHeight (root->left); int right = balancedHeight (root->right); if (left < 0 || right < 0 || abs(left - right) > 1) return -1; // 剪枝 - return max(left, right) + 1; // 三方合并 + return max(left, right) + 1; // 三方合併 } }; \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -967,26 +1210,26 @@ \subsubsection{描述} \end{Code} \subsubsection{分析} -无 +無 -\subsubsection{递归版1} +\subsubsection{遞歸版1} \begin{Code} // LeetCode, Flatten Binary Tree to Linked List -// 递归版1,时间复杂度O(n),空间复杂度O(logn) +// 遞歸版1,時間複雜度O(n),空間複雜度O(logn) class Solution { public: void flatten(TreeNode *root) { - if (root == nullptr) return; // 终止条件 + if (root == nullptr) return; // 終止條件 flatten(root->left); flatten(root->right); if (nullptr == root->left) return; - // 三方合并,将左子树所形成的链表插入到root和root->right之间 + // 三方合併,將左子樹所形成的鏈表插入到root和root->right之間 TreeNode *p = root->left; - while(p->right) p = p->right; //寻找左链表最后一个节点 + while(p->right) p = p->right; //尋找左鏈表最後一個節點 p->right = root->right; root->right = root->left; root->left = nullptr; @@ -995,19 +1238,19 @@ \subsubsection{递归版1} \end{Code} -\subsubsection{递归版2} +\subsubsection{遞歸版2} \begin{Code} // LeetCode, Flatten Binary Tree to Linked List -// 递归版2 -// @author 王顺达(http://weibo.com/u/1234984145) -// 时间复杂度O(n),空间复杂度O(logn) +// 遞歸版2 +// @author 王順達(http://weibo.com/u/1234984145) +// 時間複雜度O(n),空間複雜度O(logn) class Solution { public: void flatten(TreeNode *root) { flatten(root, NULL); } private: - // 把root所代表树变成链表后,tail跟在该链表后面 + // 把root所代表樹變成鏈表後,tail跟在該鏈表後面 TreeNode *flatten(TreeNode *root, TreeNode *tail) { if (NULL == root) return tail; @@ -1022,7 +1265,7 @@ \subsubsection{递归版2} \subsubsection{迭代版} \begin{Code} // LeetCode, Flatten Binary Tree to Linked List -// 迭代版,时间复杂度O(n),空间复杂度O(logn) +// 迭代版,時間複雜度O(n),空間複雜度O(logn) class Solution { public: void flatten(TreeNode* root) { @@ -1049,9 +1292,9 @@ \subsubsection{迭代版} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot @@ -1087,15 +1330,15 @@ \subsubsection{描述} \subsubsection{分析} -要处理一个节点,可能需要最右边的兄弟节点,首先想到用广搜。但广搜不是常数空间的,本题要求常数空间。 +要處理一個節點,可能需要最右邊的兄弟節點,首先想到用廣搜。但廣搜不是常數空間的,本題要求常數空間。 -注意,这题的代码原封不动,也可以解决 Populating Next Right Pointers in Each Node I. +注意,這題的代碼原封不動,也可以解決 Populating Next Right Pointers in Each Node I. -\subsubsection{递归版} +\subsubsection{遞歸版} \begin{Code} // LeetCode, Populating Next Right Pointers in Each Node II -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: void connect(TreeLinkNode *root) { @@ -1122,7 +1365,7 @@ \subsubsection{递归版} \subsubsection{迭代版} \begin{Code} // LeetCode, Populating Next Right Pointers in Each Node II -// 时间复杂度O(n),空间复杂度O(1) +// 時間複雜度O(n),空間複雜度O(1) class Solution { public: void connect(TreeLinkNode *root) { @@ -1148,13 +1391,100 @@ \subsubsection{迭代版} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Populating Next Right Pointers in Each Node,见 \S \ref{sec:populating-next-right-pointers-in-each-node} +\item Populating Next Right Pointers in Each Node,見 \S \ref{sec:populating-next-right-pointers-in-each-node} \myenddot +\subsection{Find Duplicate Subtrees} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:find-duplicate-subtrees} + + +\subsubsection{描述} +Given a binary tree, return all duplicate subtrees. For each kind of duplicate subtrees, you only need to return the root node of any one of them. + +Two trees are duplicate if they have the same structure with same node values. + +Example 1: +\begin{Code} + 1 + / \ + 2 3 + / / \ + 4 2 4 + / + 4 +\end{Code} + +The following are two duplicate subtrees: +\begin{Code} + 2 + / + 4 +\end{Code} + +\begin{Code} + 4 +\end{Code} + +\subsubsection{遞歸版} +\begin{Code} +// 時間複雜度O(n^2),空間複雜度O(n^2) +class Solution { +public: + vector findDuplicateSubtrees(TreeNode* root) { + unordered_map cache; + vector result; + DFS(root, cache, result); + + return result; + } +private: + string DFS(TreeNode *root, unordered_map& cache, vector& result) + { + if (!root) return "#"; + string serial = to_string(root->val) + "," + DFS(root->left, cache, result) + + "," + DFS(root->right, cache, result); + cache[serial]++; + if (cache[serial] == 2) + result.push_back(root); + return serial; + } +}; +\end{Code} + +\subsubsection{遞歸版} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + vector findDuplicateSubtrees(TreeNode* root) { + unordered_map cache; + vector result; + DFS(root, cache, result); + + return result; + } +private: + string DFS(TreeNode *root, unordered_map& cache, vector& result) + { + if (!root) return "#"; + string serial = to_string(root->val) + "," + DFS(root->left, cache, result) + + "," + DFS(root->right, cache, result); + // 直接算 uid + size_t uid = hash{}(serial); + cache[uid]++; + if (cache[uid] == 2) + result.push_back(root); + // return uid 減少計算 + return to_string(uid); + } +}; +\end{Code} + + -\section{二叉树的构建} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{二叉樹的構建} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Construct Binary Tree from Preorder and Inorder Traversal} @@ -1169,13 +1499,26 @@ \subsubsection{描述} \subsubsection{分析} -无 +\begin{Code} + 1 + / \ + 2 3 + / \ / \ + 4 5 6 7 + / / \ / \ +8 9 10 11 12 +\end{Code} +\begin{Code} +Preorder: 1 2 4 8 5 9 10 3 6 7 11 12 +Inorder: 8 4 2 9 5 10 1 6 3 11 7 12 +Postorder: 8 4 9 10 5 2 6 11 12 7 3 1 +\end{Code} -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Construct Binary Tree from Preorder and Inorder Traversal -// 递归,时间复杂度O(n),空间复杂度O(\logn) +// 遞歸,時間複雜度O(n),空間複雜度O(\logn) class Solution { public: TreeNode* buildTree(vector& preorder, vector& inorder) { @@ -1193,11 +1536,11 @@ \subsubsection{代码} auto inRootPos = find(in_first, in_last, *pre_first); auto leftSize = distance(in_first, inRootPos); + auto preLeftLast = next(pre_first, leftSize + 1); + // next(in_first, leftSize) == inRootPos - root->left = buildTree(next(pre_first), next(pre_first, - leftSize + 1), in_first, next(in_first, leftSize)); - root->right = buildTree(next(pre_first, leftSize + 1), pre_last, - next(inRootPos), in_last); + root->left = buildTree(next(pre_first), preLeftLast, in_first, inRootPos); + root->right = buildTree(preLeftLast, pre_last, next(inRootPos), in_last); return root; } @@ -1205,9 +1548,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Construct Binary Tree from Inorder and Postorder Traversal,见 \S \ref{sec:construct-binary-tree-from-inorder-and-postorder-traversal} +\item Construct Binary Tree from Inorder and Postorder Traversal,見 \S \ref{sec:construct-binary-tree-from-inorder-and-postorder-traversal} \myenddot @@ -1223,13 +1566,26 @@ \subsubsection{描述} \subsubsection{分析} -无 +\begin{Code} + 1 + / \ + 2 3 + / \ / \ + 4 5 6 7 + / / \ / \ +8 9 10 11 12 +\end{Code} +\begin{Code} +Preorder: 1 2 4 8 5 9 10 3 6 7 11 12 +Inorder: 8 4 2 9 5 10 1 6 3 11 7 12 +Postorder: 8 4 9 10 5 2 6 11 12 7 3 1 +\end{Code} -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Construct Binary Tree from Inorder and Postorder Traversal -// 递归,时间复杂度O(n),空间复杂度O(\logn) +// 遞歸,時間複雜度O(n),空間複雜度O(\logn) class Solution { public: TreeNode* buildTree(vector& inorder, vector& postorder) { @@ -1246,27 +1602,203 @@ \subsubsection{代码} const auto val = *prev(post_last); TreeNode* root = new TreeNode(val); - auto in_root_pos = find(in_first, in_last, val); - auto left_size = distance(in_first, in_root_pos); - auto post_left_last = next(post_first, left_size); + auto inRootPos = find(in_first, in_last, val); + auto leftSize = distance(in_first, inRootPos); + auto postLeftLast = next(post_first, leftSize); + + root->left = buildTree(in_first, inRootPos, post_first, postLeftLast); + root->right = buildTree(next(inRootPos), in_last, postLeftLast, prev(post_last)); + + return root; + } +}; +\end{Code} + + +\subsubsection{相關題目} +\begindot +\item Construct Binary Tree from Preorder and Inorder Traversal,見 \S \ref{sec:construct-binary-tree-from-preorder-and-inorder-traversal} +\myenddot + +\subsection{Serialize and Deserialize Binary Tree} +\label{sec:serialize-and-deserialize-binary-tree} + + +\subsubsection{描述} +Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment. + +Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure. + +Example: +\begin{Code} +You may serialize the following tree: + + 1 + / \ + 2 3 + / \ + 4 5 + +as "[1,2,3,null,null,4,5]" +\end{Code} + +\subsubsection{分析} +無 + + +\subsubsection{遞歸版} +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + // Encodes a tree to a single string. + string serialize(TreeNode* root) { + string result; + serialize(root, result); + return result; + } + + // Decodes your encoded data to tree. + TreeNode* deserialize(string data) { + list parts = SplitParts(data, ','); + + return deserialize(parts); + } +private: + void serialize(TreeNode *root, string& result) { + if (root == nullptr) + result += "null,"; + else { + result += to_string(root->val) + ","; + serialize(root->left, result); + serialize(root->right, result); + } + } + TreeNode *deserialize(list& parts) { + if (parts.front() == "null") { + parts.pop_front(); + return nullptr; + } + else { + TreeNode *root = new TreeNode(StringToInt(parts.front())); + parts.pop_front(); + root->left = deserialize(parts); + root->right = deserialize(parts); + + return root; + } + } + list SplitParts(const string& str, char deli) { + list result; + for (auto i = str.begin(); i < str.end();) { + auto j = find(i, str.end(), deli); + + result.push_back(string(i, j)); + i = next(j); + } + return result; + } + int StringToInt(string num) { + if (num.size() == 0) return 0; + if (num[0] == '-') + return -1 * stoi(num.substr(1)); + else + return stoi(num); + } +}; +\end{Code} + +\subsubsection{迭代 - BFS with queue} +\begin{Code} +// LeetCode +// 時間複雜度O(n),空間複雜度O(n) +class Solution { +public: + // Encodes a tree to a single string. + string serialize(TreeNode* root) { + stringstream result; + if (root == nullptr) return result.str(); + + queue cur; + cur.push(root); + while (!cur.empty()) { + TreeNode *p = cur.front(); + cur.pop(); + + if (p == nullptr) { + result << "null,"; + continue; + } + else + result << p->val << ","; + + cur.push(p->left); + cur.push(p->right); + } + return result.str(); + } - root->left = buildTree(in_first, in_root_pos, post_first, post_left_last); - root->right = buildTree(next(in_root_pos), in_last, post_left_last, - prev(post_last)); + // Decodes your encoded data to tree. + TreeNode* deserialize(string data) { + if (data.size() == 0) return nullptr; + + auto i = find(data.begin(), data.end(), ','); + + queue cur; + // 製造 root + TreeNode *root = new TreeNode(StringToInt(string(data.begin(), i))); + i = next(i); + cur.push(root); + + while (!cur.empty()) { + TreeNode *p = cur.front(); + cur.pop(); + + // 取兩個值,一個為左,一個為右 + // 左 + if (i >= data.end()) continue; + auto j = find(i, data.end(), ','); + string nextStr = string(i, j); + if (nextStr != "null") + p->left = new TreeNode(StringToInt(nextStr)); + i = next(j); + + // 右 + if (i >= data.end()) continue; + j = find(i, data.end(), ','); + nextStr = string(i, j); + if (nextStr != "null") + p->right = new TreeNode(StringToInt(nextStr)); + i = next(j); + + // 處理下一層 + if (p->left) cur.push(p->left); + if (p->right) cur.push(p->right); + } return root; } +private: + int StringToInt(string num) { + if (num.size() == 0) return 0; + if (num[0] == '-') + return -1 * stoi(num.substr(1)); + else + return stoi(num); + } }; \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Construct Binary Tree from Preorder and Inorder Traversal,见 \S \ref{sec:construct-binary-tree-from-preorder-and-inorder-traversal} +\item Construct Binary Tree from Inorder and Postorder Traversal,見 \S \ref{sec:construct-binary-tree-from-inorder-and-postorder-traversal} \myenddot -\section{二叉查找树} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\section{二叉查找樹} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Unique Binary Search Trees} @@ -1287,7 +1819,7 @@ \subsubsection{描述} \end{Code} \subsubsection{分析} -如果把上例的顺序改一下,就可以看出规律了。 +如果把上例的順序改一下,就可以看出規律了。 \begin{Code} 1 1 2 3 3 \ \ / \ / / @@ -1296,18 +1828,18 @@ \subsubsection{分析} 2 3 1 2 \end{Code} -比如,以1为根的树的个数,等于左子树的个数乘以右子树的个数,左子树是0个元素的树,右子树是2个元素的树。以2为根的树的个数,等于左子树的个数乘以右子树的个数,左子树是1个元素的树,右子树也是1个元素的树。依此类推。 +比如,以1為根的樹的個數,等於左子樹的個數乘以右子樹的個數,左子樹是0個元素的樹,右子樹是2個元素的樹。以2為根的樹的個數,等於左子樹的個數乘以右子樹的個數,左子樹是1個元素的樹,右子樹也是1個元素的樹。依此類推。 -当数组为 $1,2,3,...,n$时,基于以下原则的构建的BST树具有唯一性: -\textbf{以i为根节点的树,其左子树由[1, i-1]构成, 其右子树由[i+1, n]构成。} +當數組為 $1,2,3,...,n$時,基於以下原則的構建的BST樹具有唯一性: +\textbf{以i為根節點的樹,其左子樹由[1, i-1]構成, 其右子樹由[i+1, n]構成。} -定义$f(i)$为以$[1,i]$能产生的Unique Binary Search Tree的数目,则 +定義$f(i)$為以$[1,i]$能產生的Unique Binary Search Tree的數目,則 -如果数组为空,毫无疑问,只有一种BST,即空树,$f(0)=1$。 +如果數組為空,毫無疑問,只有一種BST,即空樹,$f(0)=1$。 -如果数组仅有一个元素{1},只有一种BST,单个节点,$f(1)=1$。 +如果數組僅有一個元素{1},只有一種BST,單個節點,$f(1)=1$。 -如果数组有两个元素{1,2}, 那么有如下两种可能 +如果數組有兩個元素{1,2}, 那麼有如下兩種可能 \begin{Code} 1 2 \ / @@ -1315,29 +1847,29 @@ \subsubsection{分析} \end{Code} \begin{eqnarray} -f(2) &=& f(0) * f(1) \text{ ,1为根的情况} \nonumber \\ - &+& f(1) * f(0) \text{ ,2为根的情况} \nonumber +f(2) &=& f(0) * f(1) \text{ ,1為根的情況} \nonumber \\ + &+& f(1) * f(0) \text{ ,2為根的情況} \nonumber \end{eqnarray} -再看一看3个元素的数组,可以发现BST的取值方式如下: +再看一看3個元素的數組,可以發現BST的取值方式如下: \begin{eqnarray} -f(3) &=& f(0) * f(2) \text{ ,1为根的情况} \nonumber \\ - &+& f(1) * f(1) \text{ ,2为根的情况} \nonumber \\ - &+& f(2) * f(0) \text{ ,3为根的情况} \nonumber +f(3) &=& f(0) * f(2) \text{ ,1為根的情況} \nonumber \\ + &+& f(1) * f(1) \text{ ,2為根的情況} \nonumber \\ + &+& f(2) * f(0) \text{ ,3為根的情況} \nonumber \end{eqnarray} -所以,由此观察,可以得出$f$的递推公式为 +所以,由此觀察,可以得出$f$的遞推公式為 $$ f(i) = \sum_{k=1}^{i} f(k-1) \times f(i-k) $$ -至此,问题划归为一维动态规划。 +至此,問題劃歸為一維動態規劃。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Unique Binary Search Trees -// 时间复杂度O(n^2),空间复杂度O(n) +// 時間複雜度O(n^2),空間複雜度O(n) class Solution { public: int numTrees(int n) { @@ -1356,9 +1888,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Unique Binary Search Trees II,见 \S \ref{sec:unique-binary-search-trees-ii} +\item Unique Binary Search Trees II,見 \S \ref{sec:unique-binary-search-trees-ii} \myenddot @@ -1381,14 +1913,14 @@ \subsubsection{描述} \subsubsection{分析} -见前面一题。 +見前面一題。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Unique Binary Search Trees II -// 时间复杂度TODO,空间复杂度TODO +// 時間複雜度TODO,空間複雜度TODO class Solution { public: vector generateTrees(int n) { @@ -1420,9 +1952,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Unique Binary Search Trees,见 \S \ref{sec:unique-binary-search-trees} +\item Unique Binary Search Trees,見 \S \ref{sec:unique-binary-search-trees} \myenddot @@ -1444,11 +1976,11 @@ \subsubsection{描述} \subsubsection{分析} -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // Validate Binary Search Tree -// 时间复杂度O(n),空间复杂度O(\logn) +// 時間複雜度O(n),空間複雜度O(\logn) class Solution { public: bool isValidBST(TreeNode* root) { @@ -1466,9 +1998,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Validate Binary Search Tree,见 \S \ref{sec:validate-binary-search-tree} +\item Validate Binary Search Tree,見 \S \ref{sec:validate-binary-search-tree} \myenddot @@ -1484,11 +2016,11 @@ \subsubsection{分析} 二分法。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Convert Sorted Array to Binary Search Tree -// 分治法,时间复杂度O(n),空间复杂度O(logn) +// 分治法,時間複雜度O(n),空間複雜度O(logn) class Solution { public: TreeNode* sortedArrayToBST (vector& num) { @@ -1500,9 +2032,9 @@ \subsubsection{代码} RandomAccessIterator last) { const auto length = distance(first, last); - if (length <= 0) return nullptr; // 终止条件 + if (length <= 0) return nullptr; // 終止條件 - // 三方合并 + // 三方合併 auto mid = first + length / 2; TreeNode* root = new TreeNode (*mid); root->left = sortedArrayToBST(first, mid); @@ -1514,9 +2046,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Convert Sorted List to Binary Search Tree,见 \S \ref{sec:convert-sorted-list-to-binary-search-tree} +\item Convert Sorted List to Binary Search Tree,見 \S \ref{sec:convert-sorted-list-to-binary-search-tree} \myenddot @@ -1529,17 +2061,17 @@ \subsubsection{描述} \subsubsection{分析} -这题与上一题类似,但是单链表不能随机访问,而自顶向下的二分法必须需要RandomAccessIterator,因此前面的方法不适用本题。 +這題與上一題類似,但是單鏈表不能隨機訪問,而自頂向下的二分法必須需要RandomAccessIterator,因此前面的方法不適用本題。 -存在一种自底向上(bottom-up)的方法,见\myurl{http://leetcode.com/2010/11/convert-sorted-list-to-balanced-binary.html} +存在一種自底向上(bottom-up)的方法,見\myurl{http://leetcode.com/2010/11/convert-sorted-list-to-balanced-binary.html} -\subsubsection{分治法,自顶向下} -分治法,类似于 Convert Sorted Array to Binary Search Tree,自顶向下,复杂度$O(n\log n)$。 +\subsubsection{分治法,自頂向下} +分治法,類似於 Convert Sorted Array to Binary Search Tree,自頂向下,複雜度$O(n\log n)$。 \begin{Code} // LeetCode, Convert Sorted List to Binary Search Tree -// 分治法,类似于 Convert Sorted Array to Binary Search Tree, -// 自顶向下,时间复杂度O(n^2),空间复杂度O(logn) +// 分治法,類似於 Convert Sorted Array to Binary Search Tree, +// 自頂向下,時間複雜度O(n^2),空間複雜度O(logn) class Solution { public: TreeNode* sortedListToBST (ListNode* head) { @@ -1582,7 +2114,7 @@ \subsubsection{分治法,自顶向下} \subsubsection{自底向上} \begin{Code} // LeetCode, Convert Sorted List to Binary Search Tree -// bottom-up,时间复杂度O(n),空间复杂度O(logn) +// bottom-up,時間複雜度O(n),空間複雜度O(logn) class Solution { public: TreeNode *sortedListToBST(ListNode *head) { @@ -1610,65 +2142,912 @@ \subsubsection{自底向上} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Convert Sorted Array to Binary Search Tree,见 \S \ref{sec:convert-sorted-array-to-binary-search-tree} +\item Convert Sorted Array to Binary Search Tree,見 \S \ref{sec:convert-sorted-array-to-binary-search-tree} \myenddot - -\section{二叉树的递归} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -二叉树是一个递归的数据结构,因此是一个用来考察递归思维能力的绝佳数据结构。 - -递归一定是深搜(见 \S \ref{sec:dfs-vs-recursion}节 “深搜与递归的区别”),由于在二叉树上,递归的味道更浓些,因此本节用“二叉树的递归”作为标题,而不是“二叉树的深搜”,尽管本节所有的算法都属于深搜。 - -二叉树的先序、中序、后序遍历都可以看做是DFS,此外还有其他顺序的深度优先遍历,共有$3!=6$种。其他3种顺序是 \fn{root->r->l,r->root->l, r->l->root}。 - - -\subsection{Minimum Depth of Binary Tree} -\label{sec:minimum-depth-of-binary-tree} - +\subsection{Inorder Successor in BST} +\label{sec:inorder-successor-in-bst} \subsubsection{描述} -Given a binary tree, find its minimum depth. - -The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node. +Given a binary search tree and a node in it, find the in-order successor of that node in the BST. +The successor of a node p is the node with the smallest key greater than p.val. \subsubsection{分析} -无 +Using Inorder travel - -\subsubsection{递归版} +\subsubsection{代碼} \begin{Code} -// LeetCode, Minimum Depth of Binary Tree -// 递归版,时间复杂度O(n),空间复杂度O(logn) +// LeetCode +// 時間複雜度O(),空間複雜度O() class Solution { public: - int minDepth(const TreeNode *root) { - return minDepth(root, false); + TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) { + if (root == nullptr) return root; + + return DFS(root, p); } private: - static int minDepth(const TreeNode *root, bool hasbrother) { - if (!root) return hasbrother ? INT_MAX : 0; + TreeNode *m_prev; - return 1 + min(minDepth(root->left, root->right != NULL), - minDepth(root->right, root->left != NULL)); + TreeNode *DFS(TreeNode *root, TreeNode *p) + { + if (root == nullptr) return root; + + TreeNode *ansLeft = DFS(root->left, p); + + if (ansLeft != nullptr) return ansLeft; + if (m_prev == p) return root; + m_prev = root; + + return DFS(root->right, p); } }; \end{Code} -\subsubsection{迭代版} +\subsubsection{代碼} \begin{Code} -// LeetCode, Minimum Depth of Binary Tree -// 迭代版,时间复杂度O(n),空间复杂度O(logn) +// LeetCode +// 時間複雜度O(),空間複雜度O() class Solution { public: - int minDepth(TreeNode* root) { - if (root == nullptr) - return 0; + TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) { + if (!root) return root; + + stack cache; + TreeNode *cur = root; + TreeNode *prev = nullptr; + while (!cache.empty() || cur != nullptr) + { + if (cur != nullptr) + { + cache.push(cur); + cur = cur->left; + } + else + { + if (prev == p) + { + return cache.top(); + } + else + { + cur = cache.top(); + cache.pop(); - int result = INT_MAX; + prev = cur; + + cur = cur->right; + } + } + } + + return nullptr; + } +}; +\end{Code} + +\subsection{Binary Search Tree Iterator} +\label{sec:binary-search-tree-iterator} + +\subsubsection{描述} +Implement an iterator over a binary search tree (BST). Your iterator will be initialized with the root node of a BST. + +Calling next() will return the next smallest number in the BST. + +\begin{center} +\includegraphics[width=300pt]{binary-search-tree-iterator.png}\\ +\figcaption{Binary Search Tree Iterator}\label{fig:binary-search-tree-iterator} +\end{center} + +\subsubsection{分析} +Using inorder + +\subsubsection{代碼} +\begin{Code} +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode() : val(0), left(nullptr), right(nullptr) {} + * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} + * }; + */ +class BSTIterator { +public: + BSTIterator(TreeNode* root) { + m_root = root; + + // 移到最左 + while (m_root != nullptr) + { + m_cache.push(m_root); + m_root = m_root->left; + } + } + + /** @return the next smallest number */ + int next() { + if (!m_cache.empty()) + { + m_root = m_cache.top(); + m_cache.pop(); + } + + int val = m_root->val; + + m_root = m_root->right; + // 移到最左 + while (m_root != nullptr) + { + m_cache.push(m_root); + m_root = m_root->left; + } + + return val; + } + + /** @return whether we have a next smallest number */ + bool hasNext() { + return !m_cache.empty(); + } +private: + TreeNode *m_root; + stack m_cache; +}; +\end{Code} + +\subsection{Search in a Binary Search Tree} +\label{sec:search-in-a-binary-search-tree} + +\subsubsection{描述} +Given the root node of a binary search tree (BST) and a value. You need to find the node in the BST that the node's value equals the given value. Return the subtree rooted with that node. If such node doesn't exist, you should return NULL. + +For Example: +\begin{Code} +Given the tree: + 4 + / \ + 2 7 + / \ + 1 3 + +And the value to search: 2 +\end{Code} + +You should return thie subtree: +\begin{Code} + 2 + / \ + 1 3 +\end{Code} + + +\subsubsection{代碼} +\begin{Code} +class Solution { +public: + TreeNode* searchBST(TreeNode* root, int val) { + if (root == nullptr) return nullptr; + + if (root->val == val) return root; + + if (root->val > val) + return searchBST(root->left, val); + else + return searchBST(root->right, val); + } +}; +\end{Code} + +\subsection{Insert into a Binary Search Tree} +\label{sec:insert-into-a-binary-search-tree} + +\subsubsection{描述} +Given the root node of a binary search tree (BST) and a value to be inserted into the tree, insert the value into the BST. Return the root node of the BST after the insertion. It is guaranteed that the new value does not exist in the original BST. + +Note that there may exist multiple valid ways for the insertion, as long as the tree remains a BST after insertion. You can return any of them. + +For example: +\begin{Code} +Given the tree: + 4 + / \ + 2 7 + / \ + 1 3 +And the value to insert: 5 +\end{Code} + +You can return this binary search tree: +\begin{Code} + 4 + / \ + 2 7 + / \ / + 1 3 5 +\end{Code} + +This tree is also valid: +\begin{Code} + 5 + / \ + 2 7 + / \ + 1 3 + \ + 4 +\end{Code} + + +\subsubsection{遞歸版} +\begin{Code} +class Solution { +public: + TreeNode* insertIntoBST(TreeNode* root, int val) { + if (root == nullptr) { + return new TreeNode(val); // return a new node if root is null + } + if (root->val < val) { // insert to the right subtree if val > root->val + root->right = insertIntoBST(root->right, val); + } else { // insert to the left subtree if val <= root->val + root->left = insertIntoBST(root->left, val); + } + return root; + } +}; +\end{Code} + +\subsubsection{迭代版} +\begin{Code} +class Solution { +public: + TreeNode* insertIntoBST(TreeNode* root, int val) { + TreeNode *node = root; + + while (node != nullptr) + { + if (node->val < val) + { + if (node->right == nullptr) + { + node->right = new TreeNode(val); + return root; + } + else + node = node->right; + } + else + { + if (node->left == nullptr) + { + node->left = new TreeNode(val); + return root; + } + else + node = node->left; + } + } + + return new TreeNode(val); + } +}; +\end{Code} + +\subsection{Delete Node in a BST} +\label{sec:delete-node-in-a-bst} + +\subsubsection{描述} +Given a root node reference of a BST and a key, delete the node with the given key in the BST. Return the root node reference (possibly updated) of the BST. + +Basically, the deletion can be divided into two stages: + +\begindot +\item Search for a node to remove. +\item If the node is found, delete the node. +\myenddot + +For Example: +\begin{Code} +root = [5,3,6,2,4,null,7] +key = 3 + + 5 + / \ + 3 6 + / \ \ +2 4 7 + +Given key to delete is 3. So we find the node with value 3 and delete it. + +One valid answer is [5,4,6,2,null,null,7], shown in the following BST. + + 5 + / \ + 4 6 + / \ +2 7 + +Another valid answer is [5,2,6,null,4,null,7]. + + 5 + / \ + 2 6 + \ \ + 4 7 +\end{Code} + + + +\subsubsection{代碼} +\begin{Code} +class Solution { +public: + TreeNode* deleteNode(TreeNode* root, int key) { + // return null if root is null + if (!root) { + return root; + } + + // delete current node if root is the target node + if (root->val == key) { + // replace root with root->right if root->left is null + if (!root->left) { + return root->right; + } + + // replace root with root->left if root->right is null + if (!root->right) { + return root->left; + } + + // replace root with its successor if root has two children + TreeNode* p = findSuccessor(root); + root->val = p->val; + root->right = deleteNode(root->right, p->val); + return root; + } + + if (root->val < key) { + // find target in right subtree if root->val < key + root->right = deleteNode(root->right, key); + } else { + // find target in left subtree if root->val > key + root->left = deleteNode(root->left, key); + } + return root; + } +private: + /** + * findSuccesor - Helper function for finding successor + * In BST, succesor of root is the leftmost child in root's right subtree + */ + TreeNode* findSuccessor(TreeNode* root) { + TreeNode* cur = root->right; + while (cur && cur->left) { + cur = cur->left; + } + return cur; + } +}; +\end{Code} + +\subsection{Kth Largest Element in a Stream} +\label{sec:kth-largest-element-in-a-stream} + +\subsubsection{描述} +Design a class to find the kth largest element in a stream. Note that it is the kth largest element in the sorted order, not the kth distinct element. + +Your KthLargest class will have a constructor which accepts an integer k and an integer array nums, which contains initial elements from the stream. For each call to the method KthLargest.add, return the element representing the kth largest element in the stream. + +Example: +\begin{Code} +int k = 3; +int[] arr = [4,5,8,2]; +KthLargest kthLargest = new KthLargest(3, arr); +kthLargest.add(3); // returns 4 +kthLargest.add(5); // returns 5 +kthLargest.add(10); // returns 5 +kthLargest.add(9); // returns 8 +kthLargest.add(4); // returns 8 +\end{Code} + +You may assume that nums' length $\leq$ k-1 and k $\leq$ 1 + +\subsubsection{Priority Queue} +\begin{Code} +// LeetCode +// 時間複雜度O(),空間複雜度O() +namespace std { + struct SmallerThanInt { + bool operator()(const int& first, const int& second) { + return first > second; + } + }; +} + +class KthLargest { +public: + KthLargest(int k, vector& nums) { + m_k = k; + for (const int& n : nums) + { + if (m_data.size() < m_k) + m_data.push(n); + else + { + if (n > m_data.top()) + { + m_data.pop(); + m_data.push(n); + } + } + } + } + + int add(int val) { + if (m_data.size() == m_k && val < m_data.top()) return m_data.top(); + + if (m_data.size() < m_k) + m_data.push(val); + else + { + if (val > m_data.top()) + { + m_data.pop(); + m_data.push(val); + } + } + + return m_data.top(); + } +private: + priority_queue, SmallerThanInt> m_data; + int m_k; +}; + +/** + * Your KthLargest object will be instantiated and called as such: + * KthLargest* obj = new KthLargest(k, nums); + * int param_1 = obj->add(val); + */ +\end{Code} + +\subsubsection{BST method} +\begin{Code} +// LeetCode +// 時間複雜度O(),空間複雜度O() +struct Node { + Node* left; + Node* right; + int val; + int cnt; + Node(int v, int c) : left(NULL), right(NULL), val(v), cnt(c) {} +}; + +class KthLargest { +private: + Node* insertNode(Node* root, int num) { + if (!root) { + return new Node(num, 1); // return a new node if root is null + } + if (root->val < num) { // insert to the right subtree if val > root->val + root->right = insertNode(root->right, num); + } else { // insert to the left subtree if val <= root->val + root->left = insertNode(root->left, num); + } + root->cnt++; + return root; + } + int searchKth(Node* root, int k) { + // m = the size of right subtree + int m = root->right ? (root->right)->cnt : 0; + // root is the m+1 largest node in the BST + if (k == m + 1) { + return root->val; + } + if (k <= m) { + // find kth largest in the right subtree + return searchKth(root->right, k); + } else { + // find (k-m-1)th largest in the left subtree + return searchKth(root->left, k - m - 1); + } + } + Node* root; + int m_k; +public: + KthLargest(int k, vector nums) { + root = NULL; + for (int i = 0; i < nums.size(); ++i) { + root = insertNode(root, nums[i]); + } + m_k = k; + } + + int add(int val) { + root = insertNode(root, val); + return searchKth(root, m_k); + } +}; + +/** + * Your KthLargest object will be instantiated and called as such: + * KthLargest obj = new KthLargest(k, nums); + * int param_1 = obj.add(val); + */ +\end{Code} + +\subsubsection{Remake Heap - fastest} +\begin{Code} +// LeetCode +// 時間複雜度O(),空間複雜度O() +class KthLargest { +public: + KthLargest(int k, vector& nums) { + m_k = k; + m_data.resize(m_k, INT_MIN); + + m_index = 0; + for (const auto& n : nums) + { + if (m_index < m_k) + PushHeap(m_data.begin(), m_index++, 0, n); + else + { + if (m_data.front() < n) + AdjustHeap(m_data.begin(), 0, m_k, n); + } + } + } + + int add(int val) { + if (m_index == m_k && m_data.front() > val) return m_data.front(); + + if (m_index < m_k) + PushHeap(m_data.begin(), m_index++, 0, val); + else + AdjustHeap(m_data.begin(), 0, m_k, val); + + return m_data.front(); + } +private: + template + void PushHeap(RandIT first, Dist holeIndex, Dist topIndex, T data) + { + Dist parentIndex = (holeIndex - 1) / 2; + while (holeIndex > topIndex && *next(first, parentIndex) > data) + { + *next(first, holeIndex) = *next(first, parentIndex); + holeIndex = parentIndex; + parentIndex = (holeIndex - 1) / 2; + } + *next(first, holeIndex) = data; + } + template + void AdjustHeap(RandIT first, Dist holeIndex, Dist Len, T data) + { + Dist topIndex = holeIndex; + Dist childIndex = (holeIndex + 1) * 2; + while (childIndex < Len) + { + if (*next(first, childIndex) > *next(first, childIndex-1)) + childIndex--; + *next(first, holeIndex) = *next(first, childIndex); + holeIndex = childIndex; + childIndex = (holeIndex + 1) * 2; + } + if (childIndex == Len) + { + *next(first, holeIndex) = *next(first, childIndex-1); + holeIndex = childIndex-1; + } + PushHeap(first, holeIndex, topIndex, data); + } +private: + vector m_data; + int m_k; + int m_index; +}; + +/** + * Your KthLargest object will be instantiated and called as such: + * KthLargest* obj = new KthLargest(k, nums); + * int param_1 = obj->add(val); + */ +\end{Code} + +\subsection{Lowest Common Ancestor of a Binary Search Tree} +\label{sec:lowest-common-ancestor-of-a-binary-search-tree} + +\subsubsection{描述} +Given a binary search tree (BST), find the lowest common ancestor (LCA) of two given nodes in the BST. + +According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself).” + +Given binary search tree: root = [6,2,8,0,4,7,9,null,null,3,5] + +\begin{center} +\includegraphics[width=300pt]{binary-search-tree-improved.png}\\ +\figcaption{Lowest Common Ancestor of a BST}\label{fig:binary-search-tree-improved} +\end{center} + +Example 1: +\begin{Code} +Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 +Output: 6 +Explanation: The LCA of nodes 2 and 8 is 6. +\end{Code} + +Example 2: +\begin{Code} +Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4 +Output: 2 +Explanation: The LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition. +\end{Code} + +Note: +\begindot +\item All of the nodes' values will be unique. +\item p and q are different and both values will exist in the BST. +\myenddot + +\subsubsection{Code} +\begin{Code} +class Solution { +public: + TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { + while(root != NULL) + { + //If both nodes less than that of parent => move left + if(root->val > p->val and root->val > q->val) + root = root->left; + //If both nodes greater than that of parent => move right + else if(root->val < p->val and root->val < q->val) + root = root->right; + //If paths diverge then return node + else + return root; + } + + return root; + } +}; +\end{Code} + +\subsection{Contains Duplicate II} +\label{sec:contains-duplicate-ii} + +\subsubsection{描述} +Given an array of integers and an integer k, find out whether there are two distinct indices i and j in the array such that nums[i] = nums[j] and the absolute difference between i and j is at most k. + +Example 1: +\begin{Code} +Input: nums = [1,2,3,1], k = 3 +Output: true +\end{Code} + +Example 2: +\begin{Code} +Input: nums = [1,0,1,1], k = 1 +Output: true +\end{Code} + +Example 3: +\begin{Code} +Input: nums = [1,2,3,1,2,3], k = 2 +Output: false +\end{Code} + +\subsubsection{Hash map} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(min(k,n)) +class Solution { +public: + bool containsNearbyDuplicate(vector& nums, int k) { + if (nums.size() == 0) return false; + if (k == 0) return false; + unordered_set cache; + + for (size_t i = 0; i < nums.size(); i++) + { + if (cache.find(nums[i]) != cache.end()) return true; + + if (cache.size() >= k) + cache.erase(nums[i-k]); + cache.insert(nums[i]); + } + + return false; + } +}; +\end{Code} + + +\subsection{Contains Duplicate III} +\label{sec:contains-duplicate-iii} + +\subsubsection{描述} +Given an array of integers, find out whether there are two distinct indices i and j in the array such that the absolute difference between nums[i] and nums[j] is at most t and the absolute difference between i and j is at most k. + + +Example 1: +\begin{Code} +Input: nums = [1,2,3,1], k = 3, t = 0 +Output: true +\end{Code} + +Example 2: +\begin{Code} +Input: nums = [1,0,1,1], k = 1, t = 2 +Output: true +\end{Code} + +Example 3: +\begin{Code} +Input: nums = [1,5,9,1,5,9], k = 2, t = 3 +Output: false +\end{Code} + +\subsubsection{暴力方法} +\begin{Code} +// 時間複雜度O(n*min(k,n)),空間複雜度O(min(k,n)) +class Solution { +public: + bool containsNearbyAlmostDuplicate(vector& nums, int k, int t) { + for (int i = 0; i < nums.size(); i++) + { + // abs difference 會有機會大過 int32_t, 所以要用 int64_t + // 例子: [-1,2147483647], k = 1, t = 2147483647 + for (int j = max(i - k, 0); j < i; j++) + if (abs((int64_t)nums[i] - (int64_t)nums[j]) <= (int64_t)t) return true; + } + + return false; + } +}; +\end{Code} + +\subsubsection{BST method} +\begin{Code} +// 時間複雜度O(nlog(min(k,n))),空間複雜度O(min(k,n)) +class Solution { +public: + bool containsNearbyAlmostDuplicate(vector& nums, int k, int t) { + set cache; + for (int i = 0; i < nums.size(); i++) + { + if (!cache.empty()) + { + // 尋找當下數值的 successor + auto s = GetCeiling(cache, cache.begin(), nums[i]); + if (s != cache.end() && *s <= nums[i] + t) return true; + + // 尋找當下數值的 predecessor + auto g = GetFloor(cache, cache.begin(), nums[i]); + if (g != cache.end() && nums[i] <= *g + t) return true; + } + + cache.insert(nums[i]); + if (cache.size() > k) + cache.erase(nums[i - k]); + } + return false; + } +private: + template + BidIT GetCeiling(BTS& tree, BidIT first, T val) + { + return tree.lower_bound(val); + } + template + BidIT GetFloor(BTS& tree, BidIT first, T val) + { + BidIT tmp = tree.upper_bound(val); + if (tmp == tree.begin()) + return tree.end(); + else + return prev(tmp); + } +}; +\end{Code} + +\subsubsection{Bucket} +\begin{Code} +// 時間複雜度O(n),空間複雜度O(min(k,n)) +class Solution { +public: + bool containsNearbyAlmostDuplicate(vector& nums, int k, int t) { + if (t < 0) return false; + unordered_map d; + + int64_t w = (int64_t)t + 1; + for (int i = 0; i < nums.size(); i++) + { + int64_t id = GetId(nums[i], w); + // 若果同一個 bucket 有多過一個數值,代表找到答案 + if (d.find(id) != d.end()) + return true; + // 檢查前一個 bucket + if (d.find(id-1) != d.end() && abs(nums[i] - d[id-1]) < w) + return true; + // 檢查後一個 bucket + if (d.find(id+1) != d.end() && abs(nums[i] - d[id+1]) < w) + return true; + d[id] = (int64_t)nums[i]; + if (i >= k) d.erase(GetId(nums[i-k], w)); + } + + return false; + } +private: + // `-3 / 5 = 0` and but we need `-3 / 5 = -1`. + int64_t GetId(int64_t x, int64_t w) + { + return x < 0 ? (x + 1) / w - 1 : x / w; + } +}; +\end{Code} + +\section{二叉樹的遞歸} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +二叉樹是一個遞歸的數據結構,因此是一個用來考察遞歸思維能力的絕佳數據結構。 + +遞歸一定是深搜(見 \S \ref{sec:dfs-vs-recursion}節 “深搜與遞歸的區別”),由於在二叉樹上,遞歸的味道更濃些,因此本節用“二叉樹的遞歸”作為標題,而不是“二叉樹的深搜”,儘管本節所有的算法都屬於深搜。 + +二叉樹的先序、中序、後序遍歷都可以看做是DFS,此外還有其他順序的深度優先遍歷,共有$3!=6$種。其他3種順序是 \fn{root->r->l,r->root->l, r->l->root}。 + + +\subsection{Minimum Depth of Binary Tree} +\label{sec:minimum-depth-of-binary-tree} + + +\subsubsection{描述} +Given a binary tree, find its minimum depth. + +The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node. + + +\subsubsection{分析} +無 + + +\subsubsection{遞歸版} +\begin{Code} +// LeetCode, Minimum Depth of Binary Tree +// 遞歸版,時間複雜度O(n),空間複雜度O(logn) +class Solution { +public: + int minDepth(const TreeNode *root) { + return minDepth(root, false); + } +private: + static int minDepth(const TreeNode *root, bool hasbrother) { + if (!root) return hasbrother ? INT_MAX : 0; + + return 1 + min(minDepth(root->left, root->right != NULL), + minDepth(root->right, root->left != NULL)); + } +}; +\end{Code} + + +\subsubsection{迭代版} +\begin{Code} +// LeetCode, Minimum Depth of Binary Tree +// 迭代版,時間複雜度O(n),空間複雜度O(logn) +class Solution { +public: + int minDepth(TreeNode* root) { + if (root == nullptr) + return 0; + + int result = INT_MAX; stack> s; s.push(make_pair(root, 1)); @@ -1693,9 +3072,9 @@ \subsubsection{迭代版} }; \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Maximum Depth of Binary Tree,见 \S \ref{sec:maximum-depth-of-binary-tree} +\item Maximum Depth of Binary Tree,見 \S \ref{sec:maximum-depth-of-binary-tree} \myenddot @@ -1710,13 +3089,13 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Maximum Depth of Binary Tree -// 时间复杂度O(n),空间复杂度O(logn) +// 時間複雜度O(n),空間複雜度O(logn) class Solution { public: int maxDepth(TreeNode *root) { @@ -1728,9 +3107,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Minimum Depth of Binary Tree,见 \S \ref{sec:minimum-depth-of-binary-tree} +\item Minimum Depth of Binary Tree,見 \S \ref{sec:minimum-depth-of-binary-tree} \myenddot @@ -1756,17 +3135,17 @@ \subsubsection{描述} \subsubsection{分析} -题目只要求返回\fn{true}或者\fn{false},因此不需要记录路径。 +題目只要求返回\fn{true}或者\fn{false},因此不需要記錄路徑。 -由于只需要求出一个结果,因此,当左、右任意一棵子树求到了满意结果,都可以及时return。 +由於只需要求出一個結果,因此,當左、右任意一棵子樹求到了滿意結果,都可以及時return。 -由于题目没有说节点的数据一定是正整数,必须要走到叶子节点才能判断,因此中途没法剪枝,只能进行朴素深搜。 +由於題目沒有説節點的數據一定是正整數,必須要走到葉子節點才能判斷,因此中途沒法剪枝,只能進行樸素深搜。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Path Sum -// 时间复杂度O(n),空间复杂度O(logn) +// 時間複雜度O(n),空間複雜度O(logn) class Solution { public: bool hasPathSum(TreeNode *root, int sum) { @@ -1782,9 +3161,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Path Sum II,见 \S \ref{sec:path-sum-ii} +\item Path Sum II,見 \S \ref{sec:path-sum-ii} \myenddot @@ -1816,18 +3195,18 @@ \subsubsection{描述} \subsubsection{分析} -跟上一题相比,本题是求路径本身。且要求出所有结果,左子树求到了满意结果,不能return,要接着求右子树。 +跟上一題相比,本題是求路徑本身。且要求出所有結果,左子樹求到了滿意結果,不能return,要接着求右子樹。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Path Sum II -// 时间复杂度O(n),空间复杂度O(logn) +// 時間複雜度O(n),空間複雜度O(logn) class Solution { public: vector > pathSum(TreeNode *root, int sum) { vector > result; - vector cur; // 中间结果 + vector cur; // 中間結果 pathSum(root, sum, cur, result); return result; } @@ -1851,9 +3230,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Path Sum,见 \S \ref{sec:path-sum} +\item Path Sum,見 \S \ref{sec:path-sum} \myenddot @@ -1876,17 +3255,17 @@ \subsubsection{描述} \subsubsection{分析} -这题很难,路径可以从任意节点开始,到任意节点结束。 +這題很難,路徑可以從任意節點開始,到任意節點結束。 -可以利用“最大连续子序列和”问题的思路,见第\S \ref{sec:maximum-subarray}节。如果说Array只有一个方向的话,那么Binary Tree其实只是左、右两个方向而已,我们需要比较两个方向上的值。 +可以利用“最大連續子序列和”問題的思路,見第\S \ref{sec:maximum-subarray}節。如果説Array只有一個方向的話,那麼Binary Tree其實只是左、右兩個方向而已,我們需要比較兩個方向上的值。 -不过,Array可以从头到尾遍历,那么Binary Tree怎么办呢,我们可以采用Binary Tree最常用的dfs来进行遍历。先算出左右子树的结果L和R,如果L大于0,那么对后续结果是有利的,我们加上L,如果R大于0,对后续结果也是有利的,继续加上R。 +不過,Array可以從頭到尾遍歷,那麼Binary Tree怎麼辦呢,我們可以採用Binary Tree最常用的dfs來進行遍歷。先算出左右子樹的結果L和R,如果L大於0,那麼對後續結果是有利的,我們加上L,如果R大於0,對後續結果也是有利的,繼續加上R。 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Binary Tree Maximum Path Sum -// 时间复杂度O(n),空间复杂度O(logn) +// 時間複雜度O(n),空間複雜度O(logn) class Solution { public: int maxPathSum(TreeNode *root) { @@ -1909,12 +3288,12 @@ \subsubsection{代码} }; \end{Code} -注意,最后return的时候,只返回一个方向上的值,为什么?这是因为在递归中,只能向父节点返回,不可能存在L->root->R的路径,只可能是L->root或R->root。 +注意,最後return的時候,只返回一個方向上的值,為什麼?這是因為在遞歸中,只能向父節點返回,不可能存在L->root->R的路徑,只可能是L->root或R->root。 -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Maximum Subarray,见 \S \ref{sec:maximum-subarray} +\item Maximum Subarray,見 \S \ref{sec:maximum-subarray} \myenddot @@ -1963,13 +3342,13 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Populating Next Right Pointers in Each Node -// 时间复杂度O(n),空间复杂度O(logn) +// 時間複雜度O(n),空間複雜度O(logn) class Solution { public: void connect(TreeLinkNode *root) { @@ -1992,9 +3371,9 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item Populating Next Right Pointers in Each Node II,见 \S \ref{sec:populating-next-right-pointers-in-each-node-ii} +\item Populating Next Right Pointers in Each Node II,見 \S \ref{sec:populating-next-right-pointers-in-each-node-ii} \myenddot @@ -2023,13 +3402,13 @@ \subsubsection{描述} \subsubsection{分析} -无 +無 -\subsubsection{代码} +\subsubsection{代碼} \begin{Code} // LeetCode, Decode Ways -// 时间复杂度O(n),空间复杂度O(logn) +// 時間複雜度O(n),空間複雜度O(logn) class Solution { public: int sumNumbers(TreeNode *root) { @@ -2048,7 +3427,95 @@ \subsubsection{代码} \end{Code} -\subsubsection{相关题目} +\subsubsection{相關題目} \begindot -\item 无 +\item 無 \myenddot + +\subsection{Convert Binary Search Tree To Double link list} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:convert-binary-search-tree-to-double-link-list} + + +\subsubsection{描述} +Convert a Binary Search Tree to a sorted Circular Doubly-Linked List in place. + +You can think of the left and right pointers as synonymous to the predecessor and successor pointers in a doubly-linked list. For a circular doubly linked list, the predecessor of the first element is the last element, and the successor of the last element is the first element. + +We want to do the transformation in place. After the transformation, the left pointer of the tree node should point to its predecessor, and the right pointer should point to its successor. You should return the pointer to the smallest element of the linked list. + + +\begin{center} +\includegraphics[width=300pt]{convert-binary-search-tree-to-double-link-list.png}\\ +\figcaption{Convert Binary Search Tree}\label{fig:convert-binary-search-tree-to-double-link-list} +\end{center} +\subsubsection{分析} +無 + +\subsubsection{代碼} + +\begin{Code} +// LeetCode, Decode Ways +// 時間複雜度O(n),空間複雜度O(logn) +/* +// Definition for a Node. +class Node { +public: + int val; + Node* left; + Node* right; + + Node() {} + + Node(int _val) { + val = _val; + left = NULL; + right = NULL; + } + + Node(int _val, Node* _left, Node* _right) { + val = _val; + left = _left; + right = _right; + } +}; +*/ + +class Solution { +public: + Node* treeToDoublyList(Node* root) { + if (root == nullptr) return root; + + m_last = m_first = nullptr; + + DFS(root); + m_first->left = m_last; + m_last->right = m_first; + + return m_first; + } +private: + void DFS(Node *node) + { + if (node == nullptr) return; + + DFS(node->left); + + if (m_last) + { + m_last->right = node; + node->left = m_last; + } + else + { + m_first = node; + node->left = nullptr; + } + m_last = node; + + DFS(node->right); + } +private: + Node *m_last; + Node *m_first; +}; +\end{Code} diff --git a/C++/chapTrick.tex b/C++/chapTrick.tex index de99bbd0..779cb93e 100644 --- a/C++/chapTrick.tex +++ b/C++/chapTrick.tex @@ -1,28 +1,128 @@ -\chapter{编程技巧} +\chapter{編程技巧} -在判断两个浮点数a和b是否相等时,不要用\fn{a==b},应该判断二者之差的绝对值\fn{fabs(a-b)}是否小于某个阈值,例如\fn{1e-9}。 +在判斷兩個浮點數a和b是否相等時,不要用\fn{a==b},應該判斷二者之差的絕對值\fn{fabs(a-b)}是否小於某個閾值,例如\fn{1e-9}。 -判断一个整数是否是为奇数,用\fn{x \% 2 != 0},不要用\fn{x \% 2 == 1},因为x可能是负数。 +判斷一個整數是否是為奇數,用\fn{x \% 2 != 0},不要用\fn{x \% 2 == 1},因為x可能是負數。 -用\fn{char}的值作为数组下标(例如,统计字符串中每个字符出现的次数),要考虑到\fn{char}可能是负数。有的人考虑到了,先强制转型为\fn{unsigned int}再用作下标,这仍然是错的。正确的做法是,先强制转型为\fn{unsigned char},再用作下标。这涉及C++整型提升的规则,就不详述了。 +用\fn{char}的值作為數組下標(例如,統計字符串中每個字符出現的次數),要考慮到\fn{char}可能是負數。有的人考慮到了,先強制轉型為\fn{unsigned int}再用作下標,這仍然是錯的。正確的做法是,先強制轉型為\fn{unsigned char},再用作下標。這涉及C++整型提升的規則,就不詳述了。 -以下是关于STL使用技巧的,很多条款来自《Effective STL》这本书。 +以下是關於STL使用技巧的,很多條款來自《Effective STL》這本書。 -\subsubsection{vector和string优先于动态分配的数组} +\subsubsection{vector和string優先於動態分配的數組} -首先,在性能上,由于\fn{vector}能够保证连续内存,因此一旦分配了后,它的性能跟原始数组相当; +首先,在性能上,由於\fn{vector}能夠保證連續內存,因此一旦分配了後,它的性能跟原始數組相當; -其次,如果用new,意味着你要确保后面进行了delete,一旦忘记了,就会出现BUG,且这样需要都写一行delete,代码不够短; +其次,如果用new,意味着你要確保後面進行了delete,一旦忘記了,就會出現BUG,且這樣需要都寫一行delete,代碼不夠短; -再次,声明多维数组的话,只能一个一个new,例如: +再次,聲明多維數組的話,只能一個一個new,例如: \begin{Code} int** ary = new int*[row_num]; for(int i = 0; i < row_num; ++i) ary[i] = new int[col_num]; \end{Code} -用vector的话一行代码搞定, +用vector的話一行代碼搞定, \begin{Code} vector > ary(row_num, vector(col_num, 0)); \end{Code} -\subsubsection{使用reserve来避免不必要的重新分配} +\subsubsection{使用reserve來避免不必要的重新分配} +\subsubsection{Things about binary search} +The difference between using index or length. + +\begin{Code} +// LeetCode, +// 時間複雜度O(logn),空間複雜度O(1) +class solution{ +public: + int findIndex(const vector& vec, int target) { + int left, right; + left = 0; right = (int)vec.size() - 1; // right is using index + + while (left <= right) { // this line is different + int mid = left + (right - left) / 2; + if (vec[mid] < target) + left = ++mid; + else if (vec[mid] > target) + right = --mid; // this line is different + else + return mid; + } + return -1; + } +}; +\end{Code} + +\begin{Code} +// LeetCode, +// 時間複雜度O(logn),空間複雜度O(1) +class solution{ +public: + int findIndex(const vector& vec, int target) { + int left, right; + left = 0; right = (int)vec.size(); // right is using length + + while (left < right) { // this line is different + int mid = left + (right - left) / 2; + if (vec[mid] < target) + left = ++mid; + else if (vec[mid] > target) + right = mid; // this line is different + else + return mid; + } + return -1; + } +}; +\end{Code} + +\subsubsection{Hash map in c++ with custom class} + +\begin{Code} +using h = std::hash; +auto hash = [](const Node& n) + {return ((17 * 31 + h()(n.a)) * 31 + h()(n.b)) * 31 + h()(n.c);}; +auto equal = [](const Node& l, const Node& r) + {return l.a == r.a && l.b == r.b && l.c == r.c;}; +std::unordered_map m(8, hash, equal); +\end{Code} + +\begin{Code} +namespace std { +template<> +struct hash> { + size_t operator()(pair const& p) const { + return ((size_t) &(*p.first)) ^ ((size_t) &(*p.second)); + } +}; +} +\end{Code} + +\subsubsection{Priority queue in c++ with custom class} + +\begin{Code} +auto Compare = [](const auto& first, const auto& second) + { return first.second > second.second; }; +priority_queue, vector>, decltype(Compare)> + pq(Compare); +\end{Code} + +\subsubsection{Use unordered_map with pair} + +\begin{Code} +namespace std +{ + template <> + struct hash> + { + size_t operator()(pair const& p) const + { + return ((size_t)&(p.first) ^ (size_t)&(p.second)); + } + }; +} + +class Solution { +private: + unordered_set> m_example; +} +\end{Code} \ No newline at end of file diff --git a/C++/chapTrie.tex b/C++/chapTrie.tex new file mode 100644 index 00000000..4a86b07a --- /dev/null +++ b/C++/chapTrie.tex @@ -0,0 +1,436 @@ +\chapter{字首樹 Trie} +Things related to Trie data structure. +\newline + +\section{Implement Trie (Prefix Tree)} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:implement-trie} + + +\subsubsection{描述} +Implement a trie with insert, search, and startsWith methods. + +Example: +\begin{Code} +Trie trie = new Trie(); + +trie.insert("apple"); +trie.search("apple"); // returns true +trie.search("app"); // returns false +trie.startsWith("app"); // returns true +trie.insert("app"); +trie.search("app"); // returns true +\end{Code} + +Note: +\begindot +\item You may assume that all inputs are consist of lowercase letters a-z. +\item All inputs are guaranteed to be non-empty strings. +\myenddot + + +\subsubsection{代碼} +\begin{Code} +// 時間複雜度O(),空間複雜度O() +struct TrieNode { + bool flag; + map next; +}; +class Trie { +private: + TrieNode* root; + +public: + /** Initialize your data structure here. */ + Trie() { + root = new TrieNode(); + } + + /** Inserts a word into the trie. */ + void insert(string word) { + TrieNode* p = root; + for (int i = 0; i < word.length(); ++i) { + if ((p->next).count(word[i]) <= 0) { + // insert a new node if the path does not exist + (p->next).insert(make_pair(word[i], new TrieNode())); + } + p = (p->next)[word[i]]; + } + p->flag = true; + } + + /** Returns if the word is in the trie. */ + bool search(string word) { + TrieNode* p = root; + for (int i = 0; i < word.length(); ++i) { + if ((p->next).count(word[i]) <= 0) { + return false; + } + p = (p->next)[word[i]]; + } + return p->flag; + } + + /** Returns if there is any word in the trie that starts with the given prefix. */ + bool startsWith(string prefix) { + TrieNode* p = root; + for (int i = 0; i < prefix.length(); ++i) { + if ((p->next).count(prefix[i]) <= 0) { + return false; + } + p = (p->next)[prefix[i]]; + } + return true; + } +}; + +/** + * Your Trie object will be instantiated and called as such: + * Trie obj = new Trie(); + * obj.insert(word); + * bool param_2 = obj.search(word); + * bool param_3 = obj.startsWith(prefix); + */ +\end{Code} + +\section{Map Sum Pairs} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:map-sum-pairs} + + +\subsubsection{描述} +Implement a MapSum class with insert, and sum methods. + +For the method insert, you'll be given a pair of (string, integer). The string represents the key and the integer represents the value. If the key already existed, then the original key-value pair will be overridden to the new one. + +For the method sum, you'll be given a string representing the prefix, and you need to return the sum of all the pairs' value whose key starts with the prefix. + +Example 1: +\begin{Code} +Input: insert("apple", 3), Output: Null +Input: sum("ap"), Output: 3 +Input: insert("app", 2), Output: Null +Input: sum("ap"), Output: 5 +\end{Code} + +\subsubsection{分析} +1. 暴力,先記低每一個加入的文字和其數值,當要求總加時,歷遍所有 + +2. 利用字首樹 + + +\subsubsection{字首樹} +\begin{Code} +// 時間複雜度O(k),空間複雜度O(n) +class MapSum { +public: + /** Initialize your data structure here. */ + MapSum() { + + } + + void insert(string key, int val) { + TrieNode *p = &m_root; + // delta 是關鍵,用來解決 value replacement 的問題。 + int delta = val - m_cache[key]; + m_cache[key] = val; + for (const auto& c : key) + { + if ((p->m_next).count(c) <= 0) + (p->m_next).emplace(c, new TrieNode()); + + p = (p->m_next)[c]; + p->m_val += delta; + } + } + + int sum(string prefix) { + TrieNode *p = &m_root; + + for (const char& c : prefix) + { + if ((p->m_next).count(c) <= 0) + return 0; + + p = (p->m_next)[c]; + } + + return p->m_val; + } +private: + TrieNode m_root; + unordered_map m_cache; +}; + +/** + * Your MapSum object will be instantiated and called as such: + * MapSum* obj = new MapSum(); + * obj->insert(key,val); + * int param_2 = obj->sum(prefix); + */ +\end{Code} + +\section{Replace Words} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:replace-words} + + +\subsubsection{描述} +In English, we have a concept called root, which can be followed by some other words to form another longer word - let's call this word successor. For example, the root an, followed by other, which can form another word another. + +Now, given a dictionary consisting of many roots and a sentence. You need to replace all the successor in the sentence with the root forming it. If a successor has many roots can form it, replace it with the root with the shortest length. + +You need to output the sentence after the replacement. + +Example 1: +\begin{Code} +Input: dict = ["cat","bat","rat"], sentence = "the cattle was rattled by the battery" +Output: "the cat was rat by the bat" +\end{Code} + +Constraints: +\begindot +\item The input will only have lower-case letters. +\item 1 <= dict.length <= 1000 +\item 1 <= dict[i].length <= 100 +\item 1 <= sentence words number <= 1000 +\item 1 <= sentence words length <= 1000 +\myenddot + +\subsubsection{暴力計算} +\begin{Code} +// 時間複雜度O(sum of all w^2),空間複雜度O(n) +// 先記低全部 prefix,然後每個文子,每個 substring 都檢查一次並且取代原本的文字。 +class Solution { +public: + string replaceWords(vector& dict, string sentence) { + unordered_set prefixDict(dict.begin(), dict.end()); + + string result; + bool isFirst = true; + for (const string& word : GetVec(sentence)) + { + string tmp; + for (const char& w : word) + { + tmp += w; + if (prefixDict.count(tmp) > 0) + break; + } + if (isFirst) + result += tmp; + else + result += " " + tmp; + isFirst = false; + } + + return result; + } +private: + vector GetVec(const string& sentence) + { + vector result; + for (auto i = sentence.begin(); i != sentence.end(); ) + { + i = find_if(i, sentence.end(), [&](const char& c){ return (c >= 'a' && c <= 'z'); }); + if (i == sentence.end()) return result; + + auto j = find_if(i, sentence.end(), [&](const char& c){ return c == ' '; }); + result.push_back(string(i, j)); + i = j; + } + return result; + } +}; +\end{Code} + +\subsubsection{Tier} +\begin{Code} +// 時間複雜度O(k),空間複雜度O(n) +struct TrieNode +{ + bool m_isWord; + unordered_map m_next; + + TrieNode () { m_isWord = false; }; +}; +class Solution { +public: + string replaceWords(vector& dict, string sentence) { + // 造字首樹 + TrieNode root; + for (const string& s : dict) + { + TrieNode *p = &root; + // 每一個字母 + for (const char& c : s) + { + if (p->m_next.count(c) <= 0) p->m_next.insert(make_pair(c, new TrieNode)); + p = p->m_next[c]; + if (p->m_isWord) break; // 剪枝,當已經有 prefix 存在 + } + p->m_isWord = true; + } + + string result; + bool isFirst = true; + for (const string& word : GetVec(sentence, ' ')) + { + string tmp = GetByPrefix(root, word); + if (isFirst) + result += tmp; + else + result += " " + tmp; + isFirst = false; + } + + return result; + } +private: + vector GetVec(const string& sentence, char delimiter) + { + string result; + vector res; + istringstream iss(sentence); + while (getline(iss, result, delimiter)) { + res.push_back(result); + } + return res; + } + string GetByPrefix(const TrieNode& root, const string& word) + { + string result; + TrieNode const * p = &root; + for (const char& c : word) + { + result += c; + auto it = p->m_next.find(c); + if (it != p->m_next.end()) + { + p = it->second; + if (p->m_isWord) return result; + } + else + return word; + } + + return word; + } +}; +\end{Code} + +\section{Design Search Autocomplete System} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:design-search-autocomplete-system} + + +\subsubsection{描述} +Design a search autocomplete system for a search engine. Users may input a sentence (at least one word and end with a special character '\#'). For each character they type except '\#', you need to return the top 3 historical hot sentences that have prefix the same as the part of sentence already typed. Here are the specific rules: + +\begindot +\item The hot degree for a sentence is defined as the number of times a user typed the exactly same sentence before. +\item The returned top 3 hot sentences should be sorted by hot degree (The first is the hottest one). If several sentences have the same degree of hot, you need to use ASCII-code order (smaller one appears first). +\item If less than 3 hot sentences exist, then just return as many as you can. +\item When the input is a special character, it means the sentence ends, and in this case, you need to return an empty list. +\myenddot + +Your job is to implement the following functions: + +The constructor function: + +AutocompleteSystem(String[] sentences, int[] times): This is the constructor. The input is historical data. Sentences is a string array consists of previously typed sentences. Times is the corresponding times a sentence has been typed. Your system should record these historical data. + +Now, the user wants to input a new sentence. The following function will provide the next character the user types: + +List input(char c): The input c is the next character typed by the user. The character will only be lower-case letters ('a' to 'z'), blank space (' ') or a special character ('\#'). Also, the previously typed sentence should be recorded in your system. The output will be the top 3 historical hot sentences that have prefix the same as the part of sentence already typed. + +Example: +Operation: AutocompleteSystem(["i love you", "island","ironman", "i love leetcode"], [5,3,2,2]) +The system have already tracked down the following sentences and their corresponding times: +"i love you" : 5 times +"island" : 3 times +"ironman" : 2 times +"i love leetcode" : 2 times +Now, the user begins another search: + +Operation: input('i') +Output: ["i love you", "island","i love leetcode"] +Explanation: +There are four sentences that have prefix "i". Among them, "ironman" and "i love leetcode" have same hot degree. Since ' ' has ASCII code 32 and 'r' has ASCII code 114, "i love leetcode" should be in front of "ironman". Also we only need to output top 3 hot sentences, so "ironman" will be ignored. + +Operation: input(' ') +Output: ["i love you","i love leetcode"] +Explanation: +There are only two sentences that have prefix "i ". + +Operation: input('a') +Output: [] +Explanation: +There are no sentences that have prefix "i a". + +Operation: input('\#') +Output: [] +Explanation: +The user finished the input, the sentence "i a" should be saved as a historical sentence in system. And the following input will be counted as a new search. + +\subsubsection{暴力計算} +\begin{Code} +// 時間複雜度O(k * l),空間複雜度O(n + mlogm) +class AutocompleteSystem { +public: + AutocompleteSystem(vector& sentences, vector& times) { + if (sentences.size() == times.size()) + { + // 記低每個字句的出現次數 + for (size_t i = 0; i < sentences.size(); i++) + m_cache.emplace(sentences[i], times[i]); + } + } + + vector input(char c) { + if (c == '#') + { + m_cache[m_curSentence] += 1; + m_curSentence = ""; + + return vector(); + } + else + { + m_curSentence += c; + vector> resultCount; + // 歷遍每一個 sentence 找出 prefix + for (const auto& [k, v] : m_cache) + { + // 找尋 prefix + if (k.find(m_curSentence) == 0) + { + resultCount.emplace_back(k, v); + } + } + + sort(resultCount.begin(), resultCount.end() + , [&](const auto& left, const auto& right) + { + // 先比較出現次數的多少 + if (left.second == right.second) + { + // 後比較文字的先後 + return left.first < right.first; + } + else + return left.second > right.second; + }); + + vector result; result.reserve(3); + int loop = 0; + for (const auto& [str, count] : resultCount) + { + result.push_back(str); + if (++loop >= 3) break; + } + + return result; + } + } +private: + unordered_map m_cache; + string m_curSentence; +}; +\end{Code} diff --git a/C++/format.cls b/C++/format.cls index 62a6323e..078f0ff4 100644 --- a/C++/format.cls +++ b/C++/format.cls @@ -55,7 +55,7 @@ body={390pt,530pt},marginparsep=10pt,marginpar=50pt]{geometry} \setmainfont{Times New Roman} %\setmainfont{Linux Libertine} %\setmainfont{TeX Gyre Pagella} -\newfontfamily\urlfont{PT Sans Narrow} +\newfontfamily\urlfont{Sans Narrow} %\setmonofont[AutoFakeBold=1.6,AutoFakeSlant=0.17,Mapping=tex-text-tt]{Inconsolata} \setCJKfamilyfont{zhyou}{YouYuan} diff --git a/C++/images/android-unlock-patterns.png b/C++/images/android-unlock-patterns.png new file mode 100644 index 00000000..04b577a2 Binary files /dev/null and b/C++/images/android-unlock-patterns.png differ diff --git a/C++/images/binary-search-tree-improved.png b/C++/images/binary-search-tree-improved.png new file mode 100644 index 00000000..2035831d Binary files /dev/null and b/C++/images/binary-search-tree-improved.png differ diff --git a/C++/images/binary-search-tree-iterator.png b/C++/images/binary-search-tree-iterator.png new file mode 100644 index 00000000..7da4008f Binary files /dev/null and b/C++/images/binary-search-tree-iterator.png differ diff --git a/C++/images/candy-crush-example-2.png b/C++/images/candy-crush-example-2.png new file mode 100755 index 00000000..86c88e9d Binary files /dev/null and b/C++/images/candy-crush-example-2.png differ diff --git a/C++/images/convert-binary-search-tree-to-double-link-list.png b/C++/images/convert-binary-search-tree-to-double-link-list.png new file mode 100644 index 00000000..0620a447 Binary files /dev/null and b/C++/images/convert-binary-search-tree-to-double-link-list.png differ diff --git a/C++/images/counting-bits-002.png b/C++/images/counting-bits-002.png new file mode 100644 index 00000000..53461224 Binary files /dev/null and b/C++/images/counting-bits-002.png differ diff --git a/C++/images/counting-bits.png b/C++/images/counting-bits.png new file mode 100644 index 00000000..8cf08ace Binary files /dev/null and b/C++/images/counting-bits.png differ diff --git a/C++/images/cyclic-replacements.png b/C++/images/cyclic-replacements.png new file mode 100644 index 00000000..b10fa311 Binary files /dev/null and b/C++/images/cyclic-replacements.png differ diff --git a/C++/images/diagonal-traverse.png b/C++/images/diagonal-traverse.png new file mode 100644 index 00000000..4ac6a7b6 Binary files /dev/null and b/C++/images/diagonal-traverse.png differ diff --git a/C++/images/diameter-of-binary-tree.png b/C++/images/diameter-of-binary-tree.png new file mode 100644 index 00000000..f25229ab Binary files /dev/null and b/C++/images/diameter-of-binary-tree.png differ diff --git a/C++/images/distinct-subsequences.png b/C++/images/distinct-subsequences.png new file mode 100644 index 00000000..bd751e34 Binary files /dev/null and b/C++/images/distinct-subsequences.png differ diff --git a/C++/images/edit-distance.png b/C++/images/edit-distance.png new file mode 100644 index 00000000..950892ba Binary files /dev/null and b/C++/images/edit-distance.png differ diff --git a/C++/images/flatten-a-multilevel-doubly-list.png b/C++/images/flatten-a-multilevel-doubly-list.png new file mode 100644 index 00000000..e06eda8c Binary files /dev/null and b/C++/images/flatten-a-multilevel-doubly-list.png differ diff --git a/C++/images/happy-number-001.png b/C++/images/happy-number-001.png new file mode 100644 index 00000000..7ef5f164 Binary files /dev/null and b/C++/images/happy-number-001.png differ diff --git a/C++/images/insert-into-a-cyclic-sorted-list-001.jpg b/C++/images/insert-into-a-cyclic-sorted-list-001.jpg new file mode 100644 index 00000000..2b9faf00 Binary files /dev/null and b/C++/images/insert-into-a-cyclic-sorted-list-001.jpg differ diff --git a/C++/images/insert-into-a-cyclic-sorted-list-002.jpg b/C++/images/insert-into-a-cyclic-sorted-list-002.jpg new file mode 100644 index 00000000..0dd5ffaa Binary files /dev/null and b/C++/images/insert-into-a-cyclic-sorted-list-002.jpg differ diff --git a/C++/images/interleaving-string.png b/C++/images/interleaving-string.png new file mode 100644 index 00000000..91a67159 Binary files /dev/null and b/C++/images/interleaving-string.png differ diff --git a/C++/images/interval-map-cases.png b/C++/images/interval-map-cases.png new file mode 100644 index 00000000..7ba3c7f3 Binary files /dev/null and b/C++/images/interval-map-cases.png differ diff --git a/C++/images/interval-map-one-case.png b/C++/images/interval-map-one-case.png new file mode 100644 index 00000000..46a47ca9 Binary files /dev/null and b/C++/images/interval-map-one-case.png differ diff --git a/C++/images/interval-map-test-cases.drawio b/C++/images/interval-map-test-cases.drawio new file mode 100644 index 00000000..37d40100 --- /dev/null +++ b/C++/images/interval-map-test-cases.drawio @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/C++/images/interval-map-test-cases.png b/C++/images/interval-map-test-cases.png new file mode 100644 index 00000000..df7cfb61 Binary files /dev/null and b/C++/images/interval-map-test-cases.png differ diff --git a/C++/images/lowest-common-ancestor.png b/C++/images/lowest-common-ancestor.png new file mode 100644 index 00000000..04be42ec Binary files /dev/null and b/C++/images/lowest-common-ancestor.png differ diff --git a/C++/images/maximal-rectangle.png b/C++/images/maximal-rectangle.png new file mode 100644 index 00000000..08f9ae47 Binary files /dev/null and b/C++/images/maximal-rectangle.png differ diff --git a/C++/images/minimum-path-sum.png b/C++/images/minimum-path-sum.png new file mode 100644 index 00000000..ac7bcc98 Binary files /dev/null and b/C++/images/minimum-path-sum.png differ diff --git a/C++/images/network-delay-time.png b/C++/images/network-delay-time.png new file mode 100644 index 00000000..3b07d836 Binary files /dev/null and b/C++/images/network-delay-time.png differ diff --git a/C++/images/palindrome-partitioning.png b/C++/images/palindrome-partitioning.png new file mode 100644 index 00000000..d965b2f7 Binary files /dev/null and b/C++/images/palindrome-partitioning.png differ diff --git a/C++/images/perfect-squares-dp.png b/C++/images/perfect-squares-dp.png new file mode 100644 index 00000000..5afc4c97 Binary files /dev/null and b/C++/images/perfect-squares-dp.png differ diff --git a/C++/images/the-skyline-problem.png b/C++/images/the-skyline-problem.png new file mode 100644 index 00000000..700ac0d7 Binary files /dev/null and b/C++/images/the-skyline-problem.png differ diff --git a/C++/images/trapping-rain-water.png b/C++/images/trapping-rain-water.png index 578e81eb..430ffbe4 100644 Binary files a/C++/images/trapping-rain-water.png and b/C++/images/trapping-rain-water.png differ diff --git a/C++/images/word-break-ii.png b/C++/images/word-break-ii.png new file mode 100644 index 00000000..14aa9c98 Binary files /dev/null and b/C++/images/word-break-ii.png differ diff --git a/C++/images/word-ladder.png b/C++/images/word-ladder.png new file mode 100644 index 00000000..7d108161 Binary files /dev/null and b/C++/images/word-ladder.png differ diff --git a/C++/leetcode-cpp.dvi b/C++/leetcode-cpp.dvi new file mode 100644 index 00000000..cd7a572d Binary files /dev/null and b/C++/leetcode-cpp.dvi differ diff --git a/C++/leetcode-cpp.pdf b/C++/leetcode-cpp.pdf index 72d3da55..fbe04f0c 100644 Binary files a/C++/leetcode-cpp.pdf and b/C++/leetcode-cpp.pdf differ diff --git a/C++/leetcode-cpp.tex b/C++/leetcode-cpp.tex index 903a4018..7c3cc310 100644 --- a/C++/leetcode-cpp.tex +++ b/C++/leetcode-cpp.tex @@ -1,9 +1,9 @@ \documentclass[10pt,adobefonts,fancyhdr,hyperref,UTF8]{ctexbook} \usepackage{multirow} -% for \soul 删除线 +% for \soul 刪除線 \usepackage{ulem} -% 表头斜线 +% 表頭斜線 \usepackage{diagbox} \makeatletter @@ -12,7 +12,7 @@ \begin{document} \sloppy -\newcommand\BookTitle{LeetCode题解} +\newcommand\BookTitle{LeetCode題解} \pagestyle{fancy} \fancyhf{} \fancyhead[RE]{\normalfont\small\rmfamily\nouppercase{\leftmark}} @@ -25,18 +25,19 @@ \@openrightfalse \makeatother -\frontmatter % 开始前言目录,页码用罗马数字 +\frontmatter % 開始前言目錄,頁碼用羅馬數字 \include{title} \tableofcontents -\mainmatter % 开始正文,页码用阿拉伯数字 +\mainmatter % 開始正文,頁碼用阿拉伯數字 \graphicspath{{images/}} \include{chapTrick} -\include{chapLinearList} +\include{chapLinear} +\include{chapLinkedList} \include{chapString} \include{chapStackAndQueue} \include{chapTree} @@ -50,8 +51,13 @@ \include{chapDynamicProgramming} \include{chapGraph} \include{chapImplement} +\include{chapTrie} +\include{chapGoogle} +\include{chapRemake} +\include{chapDatabase} -\appendix % 开始附录,章用字母编号 + +\appendix % 開始附錄,章用字母編號 \printindex \end{document} diff --git a/C++/title.tex b/C++/title.tex index a0d042f8..e6ffca58 100644 --- a/C++/title.tex +++ b/C++/title.tex @@ -3,19 +3,19 @@ {\LARGE\textbf{\BookTitle}} \vspace{1em} - {\large 灵魂机器 (soulmachine@gmail.com)} + {\large 靈魂機器 (soulmachine@gmail.com)} \vspace{1ex} \myurl{https://github.com/soulmachine/leetcode} \vspace{1ex} - 最后更新 \today + 最後更新 \today \vspace{1em} - \textbf{\large 版权声明} + \textbf{\large 版權聲明} \end{center} -\noindent 本作品采用“Creative Commons 署名-非商业性使用-相同方式共享 3.0 Unported许可协议 -(cc by-nc-sa)”进行许可。 +\noindent 本作品採用“Creative Commons 署名-非商業性使用-相同方式共享 3.0 Unported許可協議 +(cc by-nc-sa)”進行許可。 \texttt{\small http://creativecommons.org/licenses/by-nc-sa/3.0/} \vspace{1em} diff --git a/README.md b/README.md index 68f3d338..d4f05ccd 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,63 @@ -#LeetCode题解 ------------------ -## 在线阅读 - +# LeetCode題解 -##PDF下载 -LeetCode题解(C++版).pdf +# Table of Contents +- [PDF 下載](#PDF下載) +- [LaTeX模板](#LaTeX模板) +- [在Windows下編譯](#在Windows下編譯) +- [在Ubuntu 20.04 下編譯](#在Ubuntu-2004-下編譯) +- [如何貢獻代碼](#如何貢獻代碼) -C++ 文件夹下是C++版,内容一模一样,代码是用C++写的。 +## PDF下載 +[LeetCode題解(C++版).pdf](https://github.com/SulfredLee/leetcode/blob/master/C%2B%2B/leetcode-cpp.pdf) -Java 文件夹下是Java版,目前正在编写中,由于拖延症,不知道猴年马月能完成。 +C++ 文件夾下是C++版,內容一模一樣,代碼是用C++寫的。 -##LaTeX模板 -本书使用的是陈硕开源的[模板](https://github.com/chenshuo/typeset)。这个模板制作精良,很有taste,感谢陈硕 :) +Java 文件夾下是Java版,目前正在編寫中,由於拖延症,不知道猴年馬月能完成。 -##在Windows下编译 -1. 安装Tex Live 2015 。把bin目录例如`D:\texlive\2015\bin\win32`加入PATH环境变量。 -1. 安装字体。这个LaTex模板总共使用了10个字体,下载地址 ,有的字体Windows自带了,有的字体Ubuntu自带了,但都不全,还是一次性安装完所有字体比较方便。 -1. 安装TeXstudio -1. (可选)启动Tex Live Manager,更新所有已安装的软件包。 -1. 配置TeXstudio。 - - 启动Texstudio,选择 `Options-->Configure Texstudio-->Commands`,XeLaTex 设置为 `xelatex -synctex=1 -interaction=nonstopmode %.tex`; - - 选择 `Options-->Configure Texstudio-->Build` - - Build & View 由默认的 PDF Chain 改为 Compile & View; - - Default Compiler 由默认的PdfLaTex 修改为 XeLaTex ; - - PDF Viewer 改为 “Internal PDF Viewer(windowed)”,这样预览时会弹出一个独立的窗口,这样比较方便。 - -1. 编译。用TeXstudio打开`typeset.tex`,点击界面上的绿色箭头就可以开始编译了。 - - 在下方的窗口可以看到TeXstudio正在使用的编译命令是`xelatex -synctex=1 -interaction=nonstopmode "typeset".tex` - -##在Ubuntu下编译 -1. 安装Tex Live 2015 - - 1.1. 下载TexLive 2015 的ISO 光盘,地址 - - 1.2 mount 光盘,`sudo ./install-tl` 开始安装 +## LaTeX模板 +本書使用的是陳碩開源的[模板](https://github.com/chenshuo/typeset)。這個模板製作精良,很有taste,感謝陳碩 :) - 1.3 加入环境变量 - - sudo vi /etc/profile - export PATH=$PATH:/usr/local/texlive/2015/bin/x86_64-linux - export MANPATH=$MANPATH:/usr/local/texlive/2015/texmf-dist/doc/man - export INFPATH=$INFPATH:/usr/local/texlive/2015/texmf-dist/doc/info - -1. 安装字体。这个LaTex模板总共使用了10个字体,下载地址 ,有的字体Windows自带了,有的字体Ubuntu自带了,但都不全,还是一次性安装完所有字体比较方便。 -1. 安装TeXstudio +## 在Windows下編譯 +1. 安裝Tex Live 2015 。把bin目錄例如`D:\texlive\2015\bin\win32`加入PATH環境變量。 +1. 安裝字體。這個LaTex模板總共使用了10個字體,下載地址 ,有的字體Windows自帶了,有的字體Ubuntu自帶了,但都不全,還是一次性安裝完所有字體比較方便。 +1. 安裝TeXstudio +1. (可選)啟動Tex Live Manager,更新所有已安裝的軟件包。 1. 配置TeXstudio。 - 启动Texstudio,选择 `Options-->Configure Texstudio-->Commands`,XeLaTex 设置为 `xelatex -synctex=1 -interaction=nonstopmode %.tex`; - - 选择 `Options-->Configure Texstudio-->Build` - - Build & View 由默认的 PDF Chain 改为 Compile & View; - - Default Compiler 由默认的PdfLaTex 修改为 XeLaTex ; - - PDF Viewer 改为 “Internal PDF Viewer(windowed)”,这样预览时会弹出一个独立的窗口,这样比较方便。 - -1. 编译。用TeXstudio打开`typeset.tex`,点击界面上的绿色箭头就可以开始编译了。 - - 在下方的窗口可以看到TeXstudio正在使用的编译命令是`xelatex -synctex=1 -interaction=nonstopmode "typeset".tex` -1. 懒人版镜像。如果不想进行上面繁琐的安装过程,我做好了一个Ubuntu VMware虚拟机镜像,已经装好了 TexLive 2015, TexStudio和字体(详细的安装日志见压缩包注释),开箱即用,下载地址 。 - -##如何贡献代码 -编译通过后,就具备了完整的LaTeX编译环境了。 + 啟動Texstudio,選擇 `Options-->Configure Texstudio-->Commands`,XeLaTex 設置為 `xelatex -synctex=1 -interaction=nonstopmode %.tex`; -本书模板已经写好了,基本上不需要很多LaTeX知识就可以动手了。 + 選擇 `Options-->Configure Texstudio-->Build` -欢迎给本书添加内容或纠正错误,在自己本地编译成PDF,预览没问题后,就可以发pull request过来了。 + Build & View 由默認的 PDF Chain 改為 Compile & View; -## QQ群 + Default Compiler 由默認的PdfLaTex 修改為 XeLaTex ; -237669375 + PDF Viewer 改為 “Internal PDF Viewer(windowed)”,這樣預覽時會彈出一個獨立的窗口,這樣比較方便。 -## 小密圈 +1. 編譯。用TeXstudio打開`typeset.tex`,點擊界面上的綠色箭頭就可以開始編譯了。 -![](参考资料/silicon-job.jpeg) + 在下方的窗口可以看到TeXstudio正在使用的編譯命令是`xelatex -synctex=1 -interaction=nonstopmode "typeset".tex` +## 在Ubuntu 20.04 or 24.04 下編譯 +- install package +```Bash +$ sudo apt-get install texmaker texlive-lang-chinese texlive-luatex texlive-xetex ttf-mscorefonts-installer font-manager +$ sudo apt-get install texlive-fonts-extra +$ sudo apt-get install texlive-bibtex-extra biber +``` +- download needed font from [fonts market](https://www.fontsmarket.com) + - AdobeFangsongStd-Regular.otf - [Adobe Fangsong Std R](https://www.fontsmarket.com/font-download/adobe-fangsong-std-r) + - AdobeHeitiStd-Regular.otf - [Adobe Heiti Std R](https://www.fontsmarket.com/font-download/adobe-heiti-std-r) + - AdobeKaitiStd-Regular.otf - [Adobe Kaiti Std R](https://www.fontsmarket.com/font-download/adobe-kaiti-std-r) + - AdobeSongStd-Light.otf - [Adobe Song Std L](https://www.fontsmarket.com/font-download/adobe-song-std-l) + - Sans Narrow.ttf - [Sans Narrow](https://www.fontsmarket.com/font-download/sans-narrow) +- install font through font-manager +- Use LuaLaTex to compile -## AlgoHub +## 如何貢獻代碼 +編譯通過後,就具備了完整的LaTeX編譯環境了。 - 是我建立的一个刷题网站,即将上线,敬请期待 +本書模板已經寫好了,基本上不需要很多LaTeX知識就可以動手了。 -## 纸质书 -**本书即将由电子工业出版社出版,敬请期待** +歡迎給本書添加內容或糾正錯誤,在自己本地編譯成PDF,預覽沒問題後,就可以發pull request過來了。 diff --git a/fonts/AdobeFangsongStd-Regular.otf b/fonts/AdobeFangsongStd-Regular.otf new file mode 100644 index 00000000..5f700e15 Binary files /dev/null and b/fonts/AdobeFangsongStd-Regular.otf differ diff --git a/fonts/AdobeHeitiStd-Regular.otf b/fonts/AdobeHeitiStd-Regular.otf new file mode 100644 index 00000000..7c4d336e Binary files /dev/null and b/fonts/AdobeHeitiStd-Regular.otf differ diff --git a/fonts/AdobeKaitiStd-Regular.otf b/fonts/AdobeKaitiStd-Regular.otf new file mode 100644 index 00000000..c7045cf9 Binary files /dev/null and b/fonts/AdobeKaitiStd-Regular.otf differ diff --git a/fonts/AdobeSongStd-Light.otf b/fonts/AdobeSongStd-Light.otf new file mode 100644 index 00000000..146197bb Binary files /dev/null and b/fonts/AdobeSongStd-Light.otf differ diff --git a/fonts/Sans Narrow.ttf b/fonts/Sans Narrow.ttf new file mode 100644 index 00000000..4ed60740 Binary files /dev/null and b/fonts/Sans Narrow.ttf differ diff --git a/out/puml_image/diameter_of_binary_tree/diameter-of-binary-tree.png b/out/puml_image/diameter_of_binary_tree/diameter-of-binary-tree.png new file mode 100644 index 00000000..f25229ab Binary files /dev/null and b/out/puml_image/diameter_of_binary_tree/diameter-of-binary-tree.png differ diff --git a/out/puml_image/word_ladder/word_ladder.png b/out/puml_image/word_ladder/word_ladder.png new file mode 100644 index 00000000..7d108161 Binary files /dev/null and b/out/puml_image/word_ladder/word_ladder.png differ diff --git a/puml_image/diameter_of_binary_tree.puml b/puml_image/diameter_of_binary_tree.puml new file mode 100644 index 00000000..6ddeb924 --- /dev/null +++ b/puml_image/diameter_of_binary_tree.puml @@ -0,0 +1,34 @@ +@startmindmap diameter-of-binary-tree + +skinparam backgroundColor #EEEBDC +skinparam handwritten true +top to bottom direction + +* 1 +** 2 +*** 4 +**** 8 +**** 9 +*** 5 +** 3 +*** 6 +**** 10 +***** 14 +***** 15 +****** 20 +****** 21 +******* 24 +******* 25 +**** 11 +*** 7 +**** 12 +***** 16 +***** 17 +**** 13 +***** 18 +****** 22 +****** 23 +******* 26 +******* 27 +***** 19 +@endmindmap \ No newline at end of file diff --git a/puml_image/word_ladder.puml b/puml_image/word_ladder.puml new file mode 100644 index 00000000..ba3b1bbe --- /dev/null +++ b/puml_image/word_ladder.puml @@ -0,0 +1,52 @@ +@startuml word_ladder + +skinparam backgroundColor #EEEBDC +skinparam handwritten true +left to right direction + +usecase "mod" as start_word +rectangle i=1 { + usecase "aod" as aod + usecase "..." as other_1 + usecase "zod" as zod +} +rectangle i=2 { + usecase "mad" as mad + usecase "..." as other_2 + usecase "mzd" as mzd +} +rectangle i=3 { + usecase "moa" as moa + usecase "..." as other_3 + usecase "moz" as moz +} +usecase "filter by dictionary" as f_dict +usecase "filter by seen" as f_seen +usecase "push to next level" as next_level + +start_word --> aod +start_word --> other_1 +start_word --> zod + +start_word --> mad +start_word --> other_2 +start_word --> mzd + +start_word --> moa +start_word --> other_3 +start_word --> moz + +aod --> f_dict +other_1 --> f_dict +zod --> f_dict +mad --> f_dict +other_2 --> f_dict +mzd --> f_dict +moa --> f_dict +other_3 --> f_dict +moz --> f_dict + +f_dict --> f_seen +f_seen --> next_level + +@enduml