diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7e722f9d --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.log +*.toc +*.aux +*.idx +*.out +leetcode-cpp.pdf diff --git "a/C++/LeetCodet\351\242\230\350\247\243(C++\347\211\210).pdf" "b/C++/LeetCodet\351\242\230\350\247\243(C++\347\211\210).pdf" index a690e7be..bfa73a3d 100644 Binary files "a/C++/LeetCodet\351\242\230\350\247\243(C++\347\211\210).pdf" and "b/C++/LeetCodet\351\242\230\350\247\243(C++\347\211\210).pdf" differ diff --git a/C++/chapBFS.tex b/C++/chapBFS.tex index 17ff93f0..3d420b92 100644 --- a/C++/chapBFS.tex +++ b/C++/chapBFS.tex @@ -41,51 +41,59 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} //LeetCode, Word Ladder +// 时间复杂度O(n),空间复杂度O(n) class Solution { public: - typedef string state_t; - int ladderLength(string start, string end, + int ladderLength(const string& start, const string &end, const unordered_set &dict) { - if (start.size() != end.size()) return 0; - if (start.empty() || end.empty()) return 0; + queue current, next; // 当前层,下一层 + unordered_set visited; // 判重 - queue next, current; // 当前层,下一层 - unordered_set visited; // 判重 - unordered_map father; int level = 0; // 层次 bool found = false; + auto state_is_target = [&](const string &s) {return s == end;}; + auto state_extend = [&](const string &s) { + vector result; + + for (size_t i = 0; i < s.size(); ++i) { + string new_word(s); + for (char c = 'a'; c <= 'z'; c++) { + if (c == new_word[i]) continue; + + swap(c, new_word[i]); + + if (dict.count(new_word) > 0 && + !visited.count(new_word)) { + result.push_back(new_word); + visited.insert(new_word); + } + swap(c, new_word[i]); // 恢复该单词 + } + } + + return result; + }; + current.push(start); while (!current.empty() && !found) { ++level; while (!current.empty() && !found) { - const string str(current.front()); current.pop(); - - for (size_t i = 0; i < str.size(); ++i) { - string new_word(str); - for (char c = 'a'; c <= 'z'; c++) { - if (c == new_word[i]) continue; - - swap(c, new_word[i]); - if (new_word == end) { - found = true; //找到了 - father[new_word] = str; - break; - } - - if (dict.count(new_word) > 0 - && !visited.count(new_word)) { - next.push(new_word); - visited.insert(new_word); - father[new_word] = str; - } - swap(c, new_word[i]); // 恢复该单词 + const string str = current.front(); + current.pop(); + + const auto& new_states = state_extend(str); + for (const auto& state : new_states) { + next.push(state); + if (state_is_target(state)) { + found = true; //找到了 + break; } } } - swap(next, current); //!!! 交换两个队列 + swap(next, current); } - if (found) return level+1; + if (found) return level + 1; else return 0; } }; @@ -141,42 +149,51 @@ \subsubsection{代码} \begin{Code} //LeetCode, Word Ladder II +// 时间复杂度O(n),空间复杂度O(n) class Solution { public: vector > findLadders(string start, string end, const unordered_set &dict) { + unordered_set current, next; // 当前层,下一层,用集合是为了去重 unordered_set visited; // 判重 unordered_map > father; // 树 - unordered_set current, next; // 当前层,下一层,用集合是为了去重 - int level = 0; // 层数 bool found = false; + auto state_is_target = [&](const string &s) {return s == end;}; + auto state_extend = [&](const string &s) { + unordered_set result; + + for (size_t i = 0; i < s.size(); ++i) { + string new_word(s); + for (char c = 'a'; c <= 'z'; c++) { + if (c == new_word[i]) continue; + + swap(c, new_word[i]); + + if ((dict.count(new_word) > 0|| new_word == end) && + !visited.count(new_word)) { + result.insert(new_word); + } + swap(c, new_word[i]); // 恢复该单词 + } + } + + return result; + }; + current.insert(start); while (!current.empty() && !found) { - ++level; // 先将本层全部置为已访问,防止同层之间互相指向 - for (auto word : current) + for (const auto& word : current) visited.insert(word); - for (auto word : current) { - for (size_t i = 0; i < word.size(); ++i) { - string new_word = word; - for (char c = 'a'; c <= 'z'; ++c) { - if (c == new_word[i]) continue; - swap(c, new_word[i]); - - if (new_word == end) found = true; //找到了 - - if (visited.count(new_word) == 0 - && (dict.count(new_word) > 0 || - new_word == end)) { - next.insert(new_word); - father[new_word].push_back(word); - // visited.insert(new_word)移动到最上面了 - } - - swap(c, new_word[i]); // restore - } + for (const auto& word : current) { + const auto new_states = state_extend(word); + for (const auto &state : new_states) { + if (state_is_target(state)) found = true; + next.insert(state); + father[state].push_back(word); + // visited.insert(state); // 移动到最上面了 } } @@ -186,12 +203,12 @@ \subsubsection{代码} vector > result; if (found) { vector path; - buildPath(father, path, start, end, result); + gen_path(father, path, start, end, result); } return result; } private: - void buildPath(unordered_map > &father, + void gen_path(unordered_map > &father, vector &path, const string &start, const string &word, vector > &result) { path.push_back(word); @@ -199,8 +216,8 @@ \subsubsection{代码} result.push_back(path); reverse(result.back().begin(), result.back().end()); } else { - for (auto f : father[word]) { - buildPath(father, path, start, f, result); + for (const auto& f : father[word]) { + gen_path(father, path, start, f, result); } } path.pop_back(); @@ -243,17 +260,17 @@ \subsubsection{描述} \subsubsection{分析} -广搜。从上下左右四个边界往里走,凡是能碰到的\fn{'O'},都是跟边界接壤的,应该删除。 +广搜。从上下左右四个边界往里走,凡是能碰到的\fn{'O'},都是跟边界接壤的,应该保留。 \subsubsection{代码} \begin{Code} // LeetCode, Surrounded Regions -// BFS +// BFS,时间复杂度O(n),空间复杂度O(n) class Solution { public: void solve(vector> &board) { - if (board.size() == 0) return; + if (board.empty()) return; const int m = board.size(); const int n = board[0].size(); @@ -265,8 +282,8 @@ \subsubsection{代码} bfs(board, j, 0); bfs(board, j, n - 1); } - for (int i = 0; i < n; i++) - for (int j = 0; j < m; j++) + for (int i = 0; i < m; i++) + for (int j = 0; j < n; j++) if (board[i][j] == 'O') board[i][j] = 'X'; else if (board[i][j] == '+') @@ -274,25 +291,48 @@ \subsubsection{代码} } private: void bfs(vector> &board, int i, int j) { - queue q; - visit(board, i, j, q); - while (!q.empty()) { - int cur = q.front(); q.pop(); - const int x = cur / board[0].size(); - const int y = cur % board[0].size(); - visit(board, x - 1, y, q); - visit(board, x, y - 1, q); - visit(board, x + 1, y, q); - visit(board, x, y + 1, q); - } - } - void visit(vector> &board, int i, int j, queue &q) { + typedef pair state_t; + queue q; const int m = board.size(); const int n = board[0].size(); - if (i < 0 || i >= m || j < 0 || j >= n || board[i][j] != 'O') - return; - board[i][j] = '+'; // 既有标记功能又有去重功能 - q.push(i * n + j); + + auto is_valid = [&](const state_t &s) { + const int x = s.first; + const int y = s.second; + if (x < 0 || x >= m || y < 0 || y >= n || board[x][y] != 'O') + return false; + return true; + }; + + auto state_extend = [&](const state_t &s) { + vector result; + const int x = s.first; + const int y = s.second; + // 上下左右 + const state_t new_states[4] = {{x-1,y}, {x+1,y}, + {x,y-1}, {x,y+1}}; + for (int k = 0; k < 4; ++k) { + if (is_valid(new_states[k])) { + // 既有标记功能又有去重功能 + board[new_states[k].first][new_states[k].second] = '+'; + result.push_back(new_states[k]); + } + } + + return result; + }; + + state_t start = { i, j }; + if (is_valid(start)) { + board[i][j] = '+'; + q.push(start); + } + while (!q.empty()) { + auto cur = q.front(); + q.pop(); + auto new_states = state_extend(cur); + for (auto s : new_states) q.push(s); + } } }; \end{Code} @@ -310,7 +350,6 @@ \section{小结} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{适用场景} -注意,这里的总结是一种经验,一种概率,不是绝对的结论! \textbf{输入数据}:没什么特征,不像深搜,需要有“递归”的性质。如果是树或者图,概率更大。 @@ -323,7 +362,7 @@ \subsection{思考的步骤} \begin{enumerate} \item 是求路径长度,还是路径本身(或动作序列)? \begin{enumerate} - \item 如果是求路径长度,则状态里面要存路径长度 + \item 如果是求路径长度,则状态里面要存路径长度(或双队列+一个全局变量) \item 如果是求路径本身或动作序列 \begin{enumerate} \item 要用一棵树存储宽搜过程中的路径 @@ -359,7 +398,44 @@ \subsection{代码模板} 对于树,如果用STL,可以用\fn{unordered_map father}表示一颗树,代码非常简洁。如果能够预估状态总数的上限(设为STATE_MAX),可以用数组(\fn{state_t nodes[STATE_MAX]}),即树的双亲表示法来表示树,效率更高,当然,需要写更多代码。 +\subsubsection{双队列的写法} \begin{Codex}[label=bfs_template1.cpp] +/** 状态 */ +struct state_t { + int data1; /** 状态的数据,可以有多个字段. */ + int data2; /** 状态的数据,可以有多个字段. */ + // dataN; /** 其他字段 */ + int action; /** 由父状态移动到本状态的动作,求动作序列时需要. */ + int count; /** 所花费的步骤数(也即路径长度-1),求路径长度时需要; + 不过,采用双队列时不需要本字段,只需全局设一个整数 */ + bool operator==(const state_t &other) const { + return true; // 根据具体问题实现 + } +}; + +// 定义hash函数 + +// 方法1:模板特化,当hash函数只需要状态本身,不需要其他数据时,用这个方法比较简洁 +namespace std { +template<> struct hash { + size_t operator()(const state_t & x) const { + return 0; // 根据具体问题实现 + } +}; +} + +// 方法2:函数对象,如果hash函数需要运行时数据,则用这种方法 +class Hasher { +public: + Hasher(int _m) : m(_m) {}; + size_t operator()(const state_t &s) const { + return 0; // 根据具体问题实现 + } +private: + int m; // 存放外面传入的数据 +}; + + /** * @brief 反向生成路径. * @param[in] father 树 @@ -372,11 +448,10 @@ \subsection{代码模板} vector path; path.push_back(target); - state_t cur = target; - while (father.find(cur) != father.end()) { - cur = father.at(cur); + for (state_t cur = target; father.find(cur) != father.end(); + cur = father.at(cur)) path.push_back(cur); - } + reverse(path.begin(), path.end()); return path; @@ -386,31 +461,38 @@ \subsection{代码模板} * @brief 广搜. * @param[in] state_t 状态,如整数,字符串,一维数组等 * @param[in] start 起点 - * @param[in] state_is_target 判断状态是否是目标的函数 - * @param[in] state_extend 状态扩展函数 + * @param[in] grid 输入数据 * @return 从起点到目标状态的一条最短路径 */ template -vector bfs(state_t &start, bool (*state_is_target)(const state_t&), - vector(*state_extend)(const state_t&, - unordered_set &visited)) { +vector bfs(const state_t &start, const vector> &grid) { queue next, current; // 当前层,下一层 unordered_set visited; // 判重 - unordered_map father; + unordered_map father; // 树 int level = 0; // 层次 - bool found = false; - state_t target; + bool found = false; // 是否找到目标 + state_t target; // 符合条件的目标状态 + + // 判断当前状态是否为所求目标 + auto state_is_target = [&](const state_t &s) {return true; }; + // 扩展当前状态 + auto state_extend = [&](const state_t &s) { + vector result; + // ... + return result; + }; current.push(start); + visited.insert(start); while (!current.empty() && !found) { ++level; while (!current.empty() && !found) { const state_t state = current.front(); current.pop(); - vector new_states = state_extend(state, visited); - for (auto iter = new_states.begin(); - iter != new_states.end() && ! found; ++iter) { + vector new_states = state_extend(state); + for (auto iter = new_states.cbegin(); + iter != new_states.cend() && ! found; ++iter) { const state_t new_state(*iter); if (state_is_target(new_state)) { @@ -437,3 +519,71 @@ \subsection{代码模板} } } \end{Codex} + + +\subsubsection{只用一个队列的写法} +双队列的写法,当求路径长度时,不需要在状态里设置一个\fn{count}字段记录路径长度,只需全局设置一个整数\fn{level},比较节省内存;只用一个队列的写法,当求路径长度时,需要在状态里设置一个\fn{count}字段,不过,这种写法有一个好处 —— 可以很容易的变为A*算法,把\fn{queue}替换为\fn{priority_queue}即可。 + +\begin{Codex}[label=bfs_template2.cpp] +// 与模板1相同的部分,不再重复 +// ... + +/** + * @brief 广搜. + * @param[in] state_t 状态,如整数,字符串,一维数组等 + * @param[in] start 起点 + * @param[in] grid 输入数据 + * @return 从起点到目标状态的一条最短路径 + */ +template +vector bfs(state_t &start, const vector> &grid) { + queue q; // 队列 + unordered_set visited; // 判重 + unordered_map father; // 树 + + int level = 0; // 层次 + bool found = false; // 是否找到目标 + state_t target; // 符合条件的目标状态 + + // 判断当前状态是否为所求目标 + auto state_is_target = [&](const state_t &s) {return true; }; + // 扩展当前状态 + auto state_extend = [&](const state_t &s) { + vector result; + // ... + return result; + }; + + start.count = 0; + q.push(start); + visited.insert(start); + while (!q.empty() && !found) { + const state_t state = q.front(); + q.pop(); + vector new_states = state_extend(state); + for (auto iter = new_states.cbegin(); + iter != new_states.cend() && ! found; ++iter) { + const state_t new_state(*iter); + + if (state_is_target(new_state)) { + found = true; //找到了 + target = new_state; + father[new_state] = state; + break; + } + + q.push(new_state); + // visited.insert(new_state); 必须放到 state_extend()里 + father[new_state] = state; + } + } + + if (found) { + return gen_path(father, target); + //return level + 1; + } else { + return vector(); + //return 0; + } +} +\end{Codex} \ No newline at end of file diff --git a/C++/chapBruteforce.tex b/C++/chapBruteforce.tex index 094ab051..08014864 100644 --- a/C++/chapBruteforce.tex +++ b/C++/chapBruteforce.tex @@ -29,57 +29,56 @@ \subsubsection{描述} \end{Code} -\subsection{增量构造法} +\subsection{递归} + + +\subsubsection{增量构造法} 每个元素,都有两种选择,选或者不选。 -\subsubsection{代码} \begin{Code} // LeetCode, Subsets -// 增量构造法,朴素深搜 +// 增量构造法,深搜,时间复杂度O(2^n),空间复杂度O(n) class Solution { public: vector > subsets(vector &S) { + sort(S.begin(), S.end()); // 输出要求有序 vector > result; - vector cur; - sort(S.begin(), S.end()); // 本题对顺序有要求,需要排序 - - subsets(S, cur, 0, result); + vector path; + subsets(S, path, 0, result); return result; } private: - static void subsets(const vector &S, vector &cur, int step, + static void subsets(const vector &S, vector &path, int step, vector > &result) { if (step == S.size()) { - result.push_back(cur); + result.push_back(path); return; } // 不选S[step] - subsets(S, cur, step + 1, result); + subsets(S, path, step + 1, result); // 选S[step] - cur.push_back(S[step]); - subsets(S, cur, step + 1, result); - cur.pop_back(); + path.push_back(S[step]); + subsets(S, path, step + 1, result); + path.pop_back(); } }; \end{Code} -\subsection{位向量法} +\subsubsection{位向量法} 开一个位向量\fn{bool selected[n]},每个元素可以选或者不选。 - -\subsubsection{代码} \begin{Code} // LeetCode, Subsets -// 位向量法,也属于朴素深搜 +// 位向量法,深搜,时间复杂度O(2^n),空间复杂度O(n) class Solution { public: vector > subsets(vector &S) { + sort(S.begin(), S.end()); // 输出要求有序 + vector > result; vector selected(S.size(), false); - sort(S.begin(), S.end()); // 本题对顺序有要求,需要排序 - subsets(S, selected, 0, result); return result; } @@ -106,22 +105,47 @@ \subsubsection{代码} \end{Code} -\subsection{二进制法} +\subsection{迭代} + + +\subsubsection{增量构造法} +\begin{Code} +// LeetCode, Subsets +// 迭代版,时间复杂度O(2^n),空间复杂度O(1) +class Solution { +public: + vector > subsets(vector &S) { + sort(S.begin(), S.end()); // 输出要求有序 + vector > result(1); + for (auto elem : S) { + result.reserve(result.size() * 2); + auto half = result.begin() + result.size(); + copy(result.begin(), half, back_inserter(result)); + for_each(half, result.end(), [&elem](decltype(result[0]) &e){ + e.push_back(elem); + }); + } + return result; + } +}; +\end{Code} + + +\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|B_2, B_1 \& B_2, B_1 \^ B_2$分别对应集合的并、交、对称差。 +这种方法最巧妙。因为它不仅能生成子集,还能方便的表示集合的并、交、差等集合运算。设两个集合的位向量分别为$B_1$和$B_2$,则$B_1\cup B_2, B_1 \cap B_2, B_1 \triangle B_2$分别对应集合的并、交、对称差。 二进制法,也可以看做是位向量法,只不过更加优化。 -\subsubsection{代码} \begin{Code} // LeetCode, Subsets -// 二进制法 +// 二进制法,时间复杂度O(2^n),空间复杂度O(1) class Solution { public: vector > subsets(vector &S) { + sort(S.begin(), S.end()); // 输出要求有序 vector > result; - sort(S.begin(), S.end()); // 本题对顺序有要求,需要排序 const size_t n = S.size(); vector v; @@ -173,15 +197,48 @@ \subsubsection{分析} 这题有重复元素,但本质上,跟上一题很类似,上一题中元素没有重复,相当于每个元素只能选0或1次,这里扩充到了每个元素可以选0到若干次而已。 -\subsubsection{代码} +\subsection{递归} + + +\subsubsection{增量构造法} \begin{Code} // LeetCode, Subsets II -// 增量构造法 +// 增量构造法,版本1,时间复杂度O(2^n),空间复杂度O(n) class Solution { public: vector > subsetsWithDup(vector &S) { + sort(S.begin(), S.end()); // 必须排序 + vector > result; - sort(S.begin(), S.end()); // 本题对顺序有要求,需要排序 + vector path; + + dfs(S, S.begin(), path, result); + return result; + } + +private: + static void dfs(const vector &S, vector::iterator start, + vector &path, vector > &result) { + result.push_back(path); + + for (auto i = start; i < S.end(); i++) { + if (i != start && *i == *(i-1)) continue; + path.push_back(*i); + dfs(S, i + 1, path, result); + path.pop_back(); + } + } +}; +\end{Code} + +\begin{Code} +// LeetCode, Subsets II +// 增量构造法,版本2,时间复杂度O(2^n),空间复杂度O(n) +class Solution { +public: + vector > subsetsWithDup(vector &S) { + vector > result; + sort(S.begin(), S.end()); // 必须排序 unordered_map count_map; // 记录每个元素的出现次数 for_each(S.begin(), S.end(), [&count_map](int e) { @@ -225,14 +282,16 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{位向量法} \begin{Code} // LeetCode, Subsets II -// 位向量法 +// 位向量法,时间复杂度O(2^n),空间复杂度O(n) class Solution { public: vector > subsetsWithDup(vector &S) { - vector > result; - sort(S.begin(), S.end()); // 本题对顺序有要求,需要排序 + vector > result; // 必须排序 + sort(S.begin(), S.end()); vector count(S.back() - S.front() + 1, 0); // 计算所有元素的个数 for (auto i : S) { @@ -269,6 +328,66 @@ \subsubsection{代码} \end{Code} +\subsection{迭代} + + +\subsubsection{增量构造法} +\begin{Code} +// LeetCode, Subsets II +// 增量构造法 +// 时间复杂度O(2^n),空间复杂度O(1) +class Solution { +public: + vector > subsetsWithDup(vector &S) { + sort(S.begin(), S.end()); // 必须排序 + vector > result(1); + + size_t previous_size = 0; + for (size_t i = 0; i < S.size(); ++i) { + const size_t size = result.size(); + for (size_t j = 0; j < size; ++j) { + if (i == 0 || S[i] != S[i-1] || j >= previous_size) { + result.push_back(result[j]); + result.back().push_back(S[i]); + } + } + previous_size = size; + } + return result; + } +}; +\end{Code} + + +\subsubsection{二进制法} +\begin{Code} +// LeetCode, Subsets II +// 二进制法,时间复杂度O(2^n),空间复杂度O(1) +class Solution { +public: + vector > subsetsWithDup(vector &S) { + sort(S.begin(), S.end()); // 必须排序 + // 用 set 去重,不能用 unordered_set,因为输出要求有序 + set > result; + const size_t n = S.size(); + vector v; + + for (size_t i = 0; i < 1U << n; ++i) { + for (size_t j = 0; j < n; ++j) { + if (i & 1 << j) + v.push_back(S[j]); + } + result.insert(v); + v.clear(); + } + vector > real_result; + copy(result.begin(), result.end(), back_inserter(real_result)); + return real_result; + } +}; +\end{Code} + + \subsubsection{相关题目} \begindot \item Subsets,见 \S \ref{sec:subsets} @@ -293,6 +412,7 @@ \subsection{next_permutation()} \subsubsection{代码} \begin{Code} // LeetCode, Permutations +// 时间复杂度O(n!),空间复杂度O(1) class Solution { public: vector > permute(vector &num) { @@ -316,6 +436,7 @@ \subsubsection{代码} \begin{Code} // LeetCode, Permutations // 重新实现 next_permutation() +// 时间复杂度O(n!),空间复杂度O(1) class Solution { public: vector> permute(vector& num) { @@ -333,7 +454,7 @@ \subsubsection{代码} \end{Code} -\subsection{深搜} +\subsection{递归} 本题是求路径本身,求所有解,函数参数需要标记当前走到了哪步,还需要中间结果的引用,最终结果的引用。 扩展节点,每次从左到右,选一个没有出现过的元素。 @@ -344,6 +465,7 @@ \subsubsection{代码} \begin{Code} // LeetCode, Permutations // 深搜,增量构造法 +// 时间复杂度O(n!),空间复杂度O(n) class Solution { public: vector > permute(vector& num) { @@ -408,7 +530,7 @@ \subsection{重新实现next_permutation()} 重新实现\fn{std::next_permutation()},代码与上一题相同。 -\subsection{深搜} +\subsection{递归} 递归函数\fn{permute()}的参数\fn{p},是中间结果,它的长度又能标记当前走到了哪一步,用于判断收敛条件。 扩展节点,每次从小到大,选一个没有被用光的元素,直到所有元素被用光。 @@ -419,7 +541,7 @@ \subsection{深搜} \subsubsection{代码} \begin{Code} // LeetCode, Permutations II -// 深搜 +// 深搜,时间复杂度O(n!),空间复杂度O(n) class Solution { public: vector > permuteUnique(vector& num) { @@ -507,14 +629,11 @@ \subsubsection{描述} \end{Code} -\subsubsection{分析} -生成组合问题。 - - -\subsubsection{代码} +\subsection{递归} \begin{Code} // LeetCode, Combinations // 深搜,递归 +// 时间复杂度O(n!),空间复杂度O(n) class Solution { public: vector > combine(int n, int k) { @@ -540,6 +659,34 @@ \subsubsection{代码} \end{Code} +\subsection{迭代} +\begin{Code} +// LeetCode, Combinations +// use prev_permutation() +// 时间复杂度O((n-k)!),空间复杂度O(n) +class Solution { +public: + vector > combine(int n, int k) { + vector values(n); + iota(values.begin(), values.end(), 1); + + vector select(n, false); + fill_n(select.begin(), k, true); + + vector > result; + do{ + vector one(k); + for (int i = 0, index = 0; i < n; ++i) + if (select[i]) + one[index++] = values[i]; + result.push_back(one); + } while(prev_permutation(select.begin(), select.end())); + return result; + } +}; +\end{Code} + + \subsubsection{相关题目} \begindot \item Next Permutation, 见 \S \ref{sec:next-permutation} @@ -547,3 +694,96 @@ \subsubsection{相关题目} \item Permutations, 见 \S \ref{sec:permutations} \item Permutations II, 见 \S \ref{sec:permutations-ii} \myenddot + + +\section{Letter Combinations of a Phone Number } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:letter-combinations-of-a-phone-number } + + +\subsubsection{描述} +Given a digit string, return all possible letter combinations that the number could represent. + +A mapping of digit to letters (just like on the telephone buttons) is given below. + +\begin{center} +\includegraphics[width=150pt]{phone-keyboard.png}\\ +\figcaption{Phone Keyboard}\label{fig:phone-keyboard} +\end{center} + +\textbf{Input:}Digit string \code{"23"} + +\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{递归} +\begin{Code} +// LeetCode, Letter Combinations of a Phone Number +// 时间复杂度O(3^n),空间复杂度O(n) +class Solution { +public: + const vector keyboard { " ", "", "abc", "def", // '0','1','2',... + "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" }; + + vector letterCombinations (const string &digits) { + vector result; + dfs(digits, 0, "", result); + return result; + } + + void dfs(const string &digits, size_t cur, string path, + vector &result) { + if (cur == digits.size()) { + result.push_back(path); + return; + } + for (auto c : keyboard[digits[cur] - '0']) { + dfs(digits, cur + 1, path + c, result); + } + } +}; +\end{Code} + + +\subsection{迭代} +\begin{Code} +// LeetCode, Letter Combinations of a Phone Number +// 时间复杂度O(3^n),空间复杂度O(1) +class Solution { +public: + const vector keyboard { " ", "", "abc", "def", // '0','1','2',... + "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" }; + + vector letterCombinations (const string &digits) { + vector result(1, ""); + for (auto d : digits) { + const size_t n = result.size(); + const size_t m = keyboard[d - '0'].size(); + + result.resize(n * m); + for (size_t i = 0; i < m; ++i) + copy(result.begin(), result.begin() + n, result.begin() + n * i); + + for (size_t i = 0; i < m; ++i) { + auto begin = result.begin(); + for_each(begin + n * i, begin + n * (i+1), [&](string &s) { + s += keyboard[d - '0'][i]; + }); + } + } + return result; + } +}; +\end{Code} + + +\subsubsection{相关题目} +\begindot +\item 无 +\myenddot diff --git a/C++/chapDFS.tex b/C++/chapDFS.tex index 62b91a63..b747a1e3 100644 --- a/C++/chapDFS.tex +++ b/C++/chapDFS.tex @@ -23,87 +23,124 @@ \subsubsection{描述} \subsubsection{分析} 在每一步都可以判断中间结果是否为合法结果,用回溯法。 -一个长度为n的字符串,有n+1个地方可以砍断,每个地方可断可不断,前后两个隔板默认已经使用,因此复杂度为$O(2^{n-1})$ +一个长度为n的字符串,有$n-1$个地方可以砍断,每个地方可断可不断,因此复杂度为$O(2^{n-1})$ -\subsubsection{代码} +\subsubsection{深搜1} \begin{Code} //LeetCode, Palindrome Partitioning +// 时间复杂度O(2^n),空间复杂度O(n) class Solution { public: vector> partition(string s) { vector> result; - vector output; // 一个partition方案 - DFS(s, 0, 1, output, result); + vector path; // 一个partition方案 + dfs(s, path, result, 0, 1); return result; } // s[0, prev-1]之间已经处理,保证是回文串 // prev 表示s[prev-1]与s[prev]之间的空隙位置,start同理 - void DFS(string &s, size_t prev, size_t start, vector& output, - vector> &result) { + void dfs(string &s, vector& path, + vector> &result, size_t prev, size_t start) { if (start == s.size()) { // 最后一个隔板 if (isPalindrome(s, prev, start - 1)) { // 必须使用 - output.push_back(s.substr(prev, start - prev)); - result.push_back(output); - output.pop_back(); + path.push_back(s.substr(prev, start - prev)); + result.push_back(path); + path.pop_back(); } return; } // 不断开 - DFS(s, prev, start + 1, output, result); + dfs(s, path, result, prev, start + 1); // 如果[prev, start-1] 是回文,则可以断开,也可以不断开(上一行已经做了) if (isPalindrome(s, prev, start - 1)) { - // 不断开,if 上一行已经做了 // 断开 - output.push_back(s.substr(prev, start - prev)); - DFS(s, start, start + 1, output, result); - output.pop_back(); + path.push_back(s.substr(prev, start - prev)); + dfs(s, path, result, start, start + 1); + path.pop_back(); } } - bool isPalindrome(string &s, int start, int end) { - while (start < end) { - if (s[start++] != s[end--]) return false; + bool isPalindrome(const string &s, int start, int end) { + while (s[start] == s[end]) { + ++start; + --end; } - return true; + return start >= end; } }; \end{Code} +\subsubsection{深搜2} 另一种写法,更加简洁。这种写法也在 Combination Sum, Combination Sum II 中出现过。 \begin{Code} //LeetCode, Palindrome Partitioning +// 时间复杂度O(2^n),空间复杂度O(n) class Solution { public: vector> partition(string s) { vector> result; - vector output; // 一个partition方案 - DFS(s, 0, output, result); + vector path; // 一个partition方案 + DFS(s, path, result, 0); return result; } // 搜索必须以s[start]开头的partition方案 - void DFS(string &s, int start, vector& output, - vector> &result) { + void DFS(string &s, vector& path, + vector> &result, int start) { if (start == s.size()) { - result.push_back(output); + result.push_back(path); return; } for (int i = start; i < s.size(); i++) { if (isPalindrome(s, start, i)) { // 从i位置砍一刀 - output.push_back(s.substr(start, i - start + 1)); - DFS(s, i + 1, output, result); // 继续往下砍 - output.pop_back(); // 撤销上一个push_back的砍 + path.push_back(s.substr(start, i - start + 1)); + DFS(s, path, result, i + 1); // 继续往下砍 + path.pop_back(); // 撤销上上行 } } } - bool isPalindrome(string &s, int start, int end) { - while (start < end) { - if (s[start] != s[end]) return false; - start++; - end--; + bool isPalindrome(const string &s, int start, int end) { + while (s[start] == s[end]) { + ++start; + --end; } - return true; + return start >= end; + } +}; +\end{Code} + + +\subsubsection{动规} +\begin{Code} +// LeetCode, Palindrome Partitioning +// 动规,时间复杂度O(n^2),空间复杂度O(1) +class Solution { +public: + vector > partition(string s) { + const int n = s.size(); + bool p[n][n]; // whether s[i,j] is palindrome + fill_n(&p[0][0], n * n, false); + for (int i = n - 1; i >= 0; --i) + 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] + for (int i = n - 1; i >= 0; --i) { + for (int j = i; j < n; ++j) + if (p[i][j]) { + const string palindrome = s.substr(i, j - i + 1); + if (j + 1 < n) { + for (auto v : sub_palins[j + 1]) { + v.insert(v.begin(), palindrome); + sub_palins[i].push_back(v); + } + } else { + sub_palins[i].push_back(vector { palindrome }); + } + } + } + return sub_palins[0]; } }; \end{Code} @@ -128,7 +165,7 @@ \subsubsection{描述} How many possible unique paths are there? \begin{center} -\includegraphics[width=300pt]{robot-maze.png}\\ +\includegraphics[width=200pt]{robot-maze.png}\\ \figcaption{Above is a $3 \times 7$ grid. How many possible unique paths are there?}\label{fig:unique-paths} \end{center} @@ -142,6 +179,7 @@ \subsubsection{代码} \begin{Code} // LeetCode, Unique Paths // 深搜,小集合可以过,大集合会超时 +// 时间复杂度O(n^4),空间复杂度O(n) class Solution { public: int uniquePaths(int m, int n) { @@ -162,6 +200,7 @@ \subsubsection{代码} \begin{Code} // LeetCode, Unique Paths // 深搜 + 缓存,即备忘录法 +// 时间复杂度O(n^2),空间复杂度O(n^2) class Solution { public: int uniquePaths(int m, int n) { @@ -201,6 +240,7 @@ \subsubsection{代码} \begin{Code} // LeetCode, Unique Paths // 动规,滚动数组 +// 时间复杂度O(n^2),空间复杂度O(n) class Solution { public: int uniquePaths(int m, int n) { @@ -338,6 +378,7 @@ \subsubsection{代码} \begin{Code} // LeetCode, Unique Paths II // 动规,滚动数组 +// 时间复杂度O(n^2),空间复杂度O(n) class Solution { public: int uniquePathsWithObstacles(vector > &obstacleGrid) { @@ -346,30 +387,12 @@ \subsubsection{代码} if (obstacleGrid[0][0] || obstacleGrid[m-1][n-1]) return 0; vector f(n, 0); + f[0] = obstacleGrid[0][0] ? 0 : 1; - // 寻找第一列的第一个障碍在哪一行 - int first_col_obstacle = INT_MAX; - for (int i = 0; i < m; i++) { - if (obstacleGrid[i][0]) { - first_col_obstacle = i; - break; - } - } + for (int i = 0; i < m; i++) + for (int j = 0; j < n; j++) + f[j] = obstacleGrid[i][j] ? 0 : (j == 0 ? 0 : f[j - 1]) + f[j]; - for (int i = 0; i < m; i++) { - // 第一列如果某一行有障碍物,那么后面的行应该为0。 - if(i >= first_col_obstacle) f[0] = 0; - else f[0] = 1; - for (int j = 1; j < n; j++) { - if (!obstacleGrid[i][j]) { - // 左边的f[j],表示更新后的f[j],与公式中的f[i[[j]对应 - // 右边的f[j],表示老的f[j],与公式中的f[i-1][j]对应 - f[j] = f[j - 1] + f[j]; - } else { - f[j] = 0; - } - } - } return f[n - 1]; } }; @@ -423,26 +446,26 @@ \subsubsection{代码} \begin{Code} // LeetCode, N-Queens // 深搜+剪枝 +// 时间复杂度O(n!),空间复杂度O(n) class Solution { public: vector > solveNQueens(int n) { this->columns = vector(n, 0); - this->principal_diagonals = vector(2 * n, 0); - this->counter_diagonals = vector(2 * n, 0); + this->main_diag = vector(2 * n, 0); + this->anti_diag = vector(2 * n, 0); vector > result; vector C(n, 0); // C[i]表示第i行皇后所在的列编号 - dfs(0, C, result); + dfs(C, result, 0); return result; } private: // 这三个变量用于剪枝 vector columns; // 表示已经放置的皇后占据了哪些列 - vector principal_diagonals; // 占据了哪些主对角线 - vector counter_diagonals; // 占据了哪些副对角线 + vector main_diag; // 占据了哪些主对角线 + vector anti_diag; // 占据了哪些副对角线 - void dfs(int row, vector &C, - vector > &result) { + void dfs(vector &C, vector > &result, int row) { const int N = C.size(); if (row == N) { // 终止条件,也是收敛条件,意味着找到了一个可行解 vector solution; @@ -458,20 +481,16 @@ \subsubsection{代码} } for (int j = 0; j < N; ++j) { // 扩展状态,一列一列的试 - const bool ok = columns[j] == 0 && - principal_diagonals[row + j] == 0 - && counter_diagonals[row - j + N] == 0; - if (ok) { // 剪枝:如果合法,继续递归 - // 执行扩展动作 - C[row] = j; - columns[j] = principal_diagonals[row + j] = - counter_diagonals[row - j + N] = 1; - dfs(row + 1, C, result); - // 撤销动作 - // C[row] = 0; - columns[j] = principal_diagonals[row + j] = - counter_diagonals[row - j + N] = 0; - } + const bool ok = columns[j] == 0 && main_diag[row + j] == 0 && + anti_diag[row - j + N] == 0; + if (!ok) continue; // 剪枝:如果合法,继续递归 + // 执行扩展动作 + C[row] = j; + columns[j] = main_diag[row + j] = anti_diag[row - j + N] = 1; + dfs(C, result, row + 1); + // 撤销动作 + // C[row] = 0; + columns[j] = main_diag[row + j] = anti_diag[row - j + N] = 0; } } }; @@ -502,47 +521,47 @@ \subsubsection{代码} \begin{Code} // LeetCode, N-Queens II // 深搜+剪枝 +// 时间复杂度O(n!),空间复杂度O(n) class Solution { public: int totalNQueens(int n) { this->count = 0; this->columns = vector(n, 0); - this->principal_diagonals = vector(2 * n, 0); - this->counter_diagonals = vector(2 * n, 0); + this->main_diag = vector(2 * n, 0); + this->anti_diag = vector(2 * n, 0); vector C(n, 0); // C[i]表示第i行皇后所在的列编号 - dfs(0, C); + dfs(C, 0); return this->count; } private: int count; // 解的个数 // 这三个变量用于剪枝 vector columns; // 表示已经放置的皇后占据了哪些列 - vector principal_diagonals; // 占据了哪些主对角线 - vector counter_diagonals; // 占据了哪些副对角线 + vector main_diag; // 占据了哪些主对角线 + vector anti_diag; // 占据了哪些副对角线 - void dfs(int row, vector &C) { + void dfs(vector &C, int row) { const int N = C.size(); if (row == N) { // 终止条件,也是收敛条件,意味着找到了一个可行解 - this->count++; + ++this->count; return; } for (int j = 0; j < N; ++j) { // 扩展状态,一列一列的试 const bool ok = columns[j] == 0 && - principal_diagonals[row + j] == 0 - && counter_diagonals[row - j + N] == 0; - if (ok) { // 剪枝:如果合法,继续递归 - // 执行扩展动作 - C[row] = j; - columns[j] = principal_diagonals[row + j] = - counter_diagonals[row - j + N] = 1; - dfs(row + 1, C); - // 撤销动作 - // C[row] = 0; - columns[j] = principal_diagonals[row + j] = - counter_diagonals[row - j + N] = 0; - } + main_diag[row + j] == 0 && + anti_diag[row - j + N] == 0; + if (!ok) continue; // 剪枝:如果合法,继续递归 + // 执行扩展动作 + C[row] = j; + columns[j] = main_diag[row + j] = + anti_diag[row - j + N] = 1; + dfs(C, row + 1); + // 撤销动作 + // C[row] = 0; + columns[j] = main_diag[row + j] = + anti_diag[row - j + N] = 0; } } }; @@ -575,6 +594,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Restore IP Addresses +// 时间复杂度O(n^4),空间复杂度O(n) class Solution { public: vector restoreIpAddresses(string s) { @@ -658,6 +678,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Combination Sum +// 时间复杂度O(n!),空间复杂度O(n) class Solution { public: vector > combinationSum(vector &nums, int target) { @@ -726,6 +747,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Combination Sum II +// 时间复杂度O(n!),空间复杂度O(n) class Solution { public: vector > combinationSum2(vector &nums, int target) { @@ -770,76 +792,6 @@ \subsubsection{相关题目} \myenddot -\section{Letter Combinations of a Phone Number } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:letter-combinations-of-a-phone-number } - - -\subsubsection{描述} -Given a digit string, return all possible letter combinations that the number could represent. - -A mapping of digit to letters (just like on the telephone buttons) is given below. - -\begin{center} -\includegraphics{phone-keyboard.png}\\ -\figcaption{Phone Keyboard}\label{fig:phone-keyboard} -\end{center} - -\textbf{Input:}Digit string \code{"23"} - -\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{分析} -无 - - -\subsubsection{代码} -\begin{Code} -// LeetCode, Letter Combinations of a Phone Number -class Solution { -public: - const vector keyboard { - " ", // '0' - "", // '1' - "abc", // '2' - "def", // ... - "ghi", - "jkl", - "mno", - "pqrs", - "tuv", - "wxyz" - }; - - vector letterCombinations (const string &digits) { - vector result; - dfs(digits, 0, "", result); - return result; - } - - void dfs(const string &digits, size_t cur, string path, - vector &result) { - if (cur == digits.size()) { - result.push_back(path); - return; - } - for (auto c : keyboard[digits[cur] - '0']) { - dfs(digits, cur + 1, path + c, result); - } - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item 无 -\myenddot - - \section{Generate Parentheses } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \label{sec:generate-parentheses} @@ -858,28 +810,31 @@ \subsubsection{分析} 一步步构造字符串。当左括号出现次数$ generateParenthesis(int n) { vector result; - if (n > 0) generator(n, "", 0, 0, result); + if (n > 0) generate(n, "", 0, 0, result); return result; } // l 表示 ( 出现的次数, r 表示 ) 出现的次数 - void generator(int n, string s, int l, int r, vector &result) { + void generate(int n, string s, int l, int r, vector &result) { if (l == n) { result.push_back(s.append(n - r, ')')); return; } - generator(n, s + '(', l + 1, r, result); - if (l > r) generator(n, s + ")", l, r + 1, result); + generate(n, s + '(', l + 1, r, result); + if (l > r) generate(n, s + ")", l, r + 1, result); } }; \end{Code} + +\subsubsection{代码2} 另一种递归写法,更加简洁。 \begin{Code} // LeetCode, Generate Parentheses @@ -921,12 +876,12 @@ \subsubsection{描述} You may assume that there will be only one unique solution. \begin{center} -\includegraphics[width=200pt]{sudoku.png}\\ +\includegraphics[width=150pt]{sudoku.png}\\ \figcaption{A sudoku puzzle...}\label{fig:sudoku} \end{center} \begin{center} -\includegraphics[width=200pt]{sudoku-solution.png}\\ +\includegraphics[width=150pt]{sudoku-solution.png}\\ \figcaption{...and its solution numbers marked in red}\label{fig:sudoku-solution} \end{center} @@ -938,6 +893,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Sudoku Solver +// 时间复杂度O(9^4),空间复杂度O(1) class Solution { public: bool solveSudoku(vector > &board) { @@ -967,7 +923,7 @@ \subsubsection{代码} return false; for (i = 3 * (x / 3); i < 3 * (x / 3 + 1); i++) for (j = 3 * (y / 3); j < 3 * (y / 3 + 1); j++) - if (i != x && j != y && board[i][j] == board[x][y]) + if ((i != x || j != y) && board[i][j] == board[x][y]) return false; return true; } @@ -1012,6 +968,7 @@ \subsubsection{代码} \begin{Code} // LeetCode, Word Search // 深搜,递归 +// 时间复杂度O(n^2*m^2),空间复杂度O(n^2) class Solution { public: bool exist(vector > &board, string word) { @@ -1061,8 +1018,6 @@ \section{小结} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{适用场景} -注意,这里的总结是一种经验,一种概率,不是绝对的结论! - \textbf{输入数据}:如果是递归数据结构,如单链表,二叉树,集合,则百分之百可以用深搜;如果是非递归数据结构,如一维数组,二维数组,字符串,图,则概率小一些。 \textbf{状态转换图}:树或者图。 @@ -1074,8 +1029,8 @@ \subsection{思考的步骤} \begin{enumerate} \item 是求路径条数,还是路径本身(或动作序列)?深搜最常见的三个问题,求可行解的总数,求一个可行解,求所有可行解。 \begin{enumerate} + \item 如果是路径条数,则不需要存储路径。 \item 如果是求路径本身,则要用一个数组\fn{path[]}存储路径。跟宽搜不同,宽搜虽然最终求的也是一条路径,但是需要存储扩展过程中的所有路径,在没找到答案之前所有路径都不能放弃;而深搜,在搜索过程中始终只有一条路径,因此用一个数组就足够了。 - \item 如果是路径条数,则不需要存储路径。 \end{enumerate} \item 只要求一个解,还是要求所有解?如果只要求一个解,那找到一个就可以返回;如果要求所有解,找到了一个后,还要继续扩展,直到遍历完。广搜一般只要求一个解,因而不需要考虑这个问题(广搜当然也可以求所有解,这时需要扩展到所有叶子节点,相当于在内存中存储整个状态转换图,非常占内存,因此广搜不适合解这类问题)。 @@ -1086,8 +1041,8 @@ \subsection{思考的步骤} \item 关于判重 \begin{enumerate} - \item 如果状态转换图是一棵树,则不需要判重,因为在遍历过程中不可能重复。 - \item 如果状态转换图是一个图,则需要判重,方法跟广搜相同,见第 \S \ref{sec:bfs-template} 节。这里跟第8步中的加缓存是相同的,如果有重叠子问题,则需要判重,此时加缓存自然也是有效果的。 + \item 是否需要判重?如果状态转换图是一棵树,则不需要判重,因为在遍历过程中不可能重复;如果状态转换图是一个DAG,则需要判重。这一点跟BFS不一样,BFS的状态转换图总是DAG,必须要判重。 + \item 怎样判重?跟广搜相同,见第 \S \ref{sec:bfs-template} 节。同时,DAG说明存在重叠子问题,此时可以用缓存加速,见第8步。 \end{enumerate} \item 终止条件是什么?终止条件是指到了不能扩展的末端节点。对于树,是叶子节点,对于图或隐式图,是出度为0的节点。 @@ -1101,16 +1056,16 @@ \subsection{思考的步骤} \item 如何加速? \begin{enumerate} \item 剪枝。深搜一定要好好考虑怎么剪枝,成本小收益大,加几行代码,就能大大加速。这里没有通用方法,只能具体问题具体分析,要充分观察,充分利用各种信息来剪枝,在中间节点提前返回。 - \item 缓存。如果子问题的解会被重复利用,可以考虑使用缓存。 + \item 缓存。 \begin{enumerate} - \item 前提条件:子问题的解会被重复利用,即子问题之间的依赖关系是有向无环图(DAG)。如果依赖关系是树状的(例如树,单链表),没必要加缓存,因为子问题只会一层层往下,用一次就再也不会用到,加了缓存也没什么加速效果。 + \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{代码模板} @@ -1119,14 +1074,15 @@ \subsection{代码模板} /** * dfs模板. * @param[in] input 输入数据指针 - * @param[inout] cur or gap 标记当前位置或距离目标的距离 * @param[out] path 当前路径,也是中间结果 * @param[out] result 存放最终结果 + * @param[inout] cur or gap 标记当前位置或距离目标的距离 * @return 路径长度,如果是求路径本身,则不需要返回长度 */ -void dfs(type *input, type *path, int cur or gap, type *result) { +void dfs(type &input, type &path, type &result, int cur or gap) { if (数据非法) return 0; // 终止条件 - if (cur == input.size( or gap == 0)) { // 收敛条件 + if (cur == input.size()) { // 收敛条件 + // if (gap == 0) { 将path放入result } @@ -1158,10 +1114,10 @@ \subsection{深搜与递归的区别} 深搜,是逻辑意义上的算法,递归,是一种物理意义上的实现,它和迭代(iteration)是对应的。深搜,可以用递归来实现,也可以用栈来实现;而递归,一般总是用来实现深搜。可以说,\textbf{递归一定是深搜,深搜不一定用递归}。 -递归有两种加速策略,一种是\textbf{剪枝(prunning)},对中间结果进行判断,提前返回;一种是\textbf{加缓存}(就变成了memoization,备忘录法),缓存中间结果,防止重复计算,用空间换时间。 +递归有两种加速策略,一种是\textbf{剪枝(prunning)},对中间结果进行判断,提前返回;一种是\textbf{缓存},缓存中间结果,防止重复计算,用空间换时间。 -其实,递归+缓存,就是一种 memorization 。所谓\textbf{memorization}(翻译为备忘录法,见第 \S \ref{sec:dp-vs-memorization}节),就是"top-down with cache"(自顶向下+缓存),它是Donald Michie 在1968年创造的术语,表示一种优化技术,在top-down 形式的程序中,使用缓存来避免重复计算,从而达到加速的目的。 +其实,递归+缓存,就是 memorization。所谓\textbf{memorization}(翻译为备忘录法,见第 \S \ref{sec:dp-vs-memorization}节),就是"top-down with cache"(自顶向下+缓存),它是Donald Michie 在1968年创造的术语,表示一种优化技术,在top-down 形式的程序中,使用缓存来避免重复计算,从而达到加速的目的。 \textbf{memorization 不一定用递归},就像深搜不一定用递归一样,可以在迭代(iterative)中使用 memorization 。\textbf{递归也不一定用 memorization},可以用memorization来加速,但不是必须的。只有当递归使用了缓存,它才是 memorization 。 -既然递归一定是深搜,为什么很多书籍都同时使用这两个术语呢?在递归味道更浓的地方,一般用递归这个术语,在深搜更浓的场景下,用深搜这个术语,读者心里要弄清楚他俩大部分时候是一回事。在单链表、二叉树等递归数据结构上,递归的味道更浓,这时用递归这个术语;在图、隐士图等数据结构上,递归的比重不大,深搜的意图更浓,这时用深搜这个术语。 +既然递归一定是深搜,为什么很多书籍都同时使用这两个术语呢?在递归味道更浓的地方,一般用递归这个术语,在深搜更浓的场景下,用深搜这个术语,读者心里要弄清楚他俩大部分时候是一回事。在单链表、二叉树等递归数据结构上,递归的味道更浓,这时用递归这个术语;在图、隐式图等数据结构上,深搜的味道更浓,这时用深搜这个术语。 diff --git a/C++/chapDivideAndConquer.tex b/C++/chapDivideAndConquer.tex index b35f6d02..25120ea6 100644 --- a/C++/chapDivideAndConquer.tex +++ b/C++/chapDivideAndConquer.tex @@ -17,6 +17,7 @@ \subsubsection{代码} \begin{Code} //LeetCode, Pow(x, n) // 二分法,$x^n = x^{n/2} * x^{n/2} * x^{n\%2}$ +// 时间复杂度O(logn),空间复杂度O(1) class Solution { public: double pow(double x, int n) { @@ -58,17 +59,17 @@ \subsubsection{代码} \begin{Code} // LeetCode, Longest Substring Without Repeating Characters // 二分查找 +// 时间复杂度O(logn),空间复杂度O(1) class Solution { public: int sqrt(int x) { int left = 1, right = x / 2; - int mid; int last_mid; // 记录最近一次mid if (x < 2) return x; while(left <= right) { - mid = left + (right - left) / 2; + const int mid = left + (right - left) / 2; if(x / mid > mid) { // 不要用 x > mid * mid,会溢出 left = mid + 1; last_mid = mid; diff --git a/C++/chapDynamicProgramming.tex b/C++/chapDynamicProgramming.tex index 18bd030d..a6ec1d08 100644 --- a/C++/chapDynamicProgramming.tex +++ b/C++/chapDynamicProgramming.tex @@ -32,6 +32,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Triangle +// 时间复杂度O(n^2),空间复杂度O(1) class Solution { public: int minimumTotal (vector>& triangle) { @@ -72,79 +73,72 @@ \subsubsection{分析} 如果之前SubArray的总体和为0或者小于0的话,我们认为其对后续结果是没有贡献,甚至是有害的(小于0时)。这种情况下我们选择以这个数字开始,另起一个SubArray。 -设状态为d[j],表示以S[j]结尾的最大连续子序列和,则状态转移方程如下: +设状态为\fn{f[j]},表示以\fn{S[j]}结尾的最大连续子序列和,则状态转移方程如下: \begin{eqnarray} -d[j] &=& \max\left\{d[j-1]+S[j],S[j]\right\}, \text{ 其中 }1 \leq j \leq n \nonumber \\ -target &=& \max\left\{d[j]\right\}, \text{ 其中 }1 \leq j \leq n \nonumber +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]不独立,与前面的某些数组成一个连续子序列,则最大连续子序列和为$d[j-1]+S[j]$。 +\item 情况一,S[j]不独立,与前面的某些数组成一个连续子序列,则最大连续子序列和为$f[j-1]+S[j]$。 \item 情况二,S[j]独立划分成为一段,即连续子序列仅包含一个数S[j],则最大连续子序列和为$S[j]$。 \myenddot 其他思路: \begindot -\item 思路1:直接在i到j之间暴力枚举,复杂度是$O(n^3)$ -\item 思路2:处理后枚举,连续子序列的和等于两个前缀和之差,复杂度$O(n^2)$。 -\item 思路3:分治法,把序列分为两段,分别求最大连续子序列和,然后归并,复杂度$O(n\log n)$ -\item 思路4:把思路2$O(n^2)$的代码稍作处理,得到$O(n)$的算法 -\item 思路5:当成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) +class Solution { +public: + int maxSubArray(int A[], int n) { + int result = INT_MIN, f = 0; + for (int i = 0; i < n; ++i) { + f = max(f + A[i], A[i]); + result = max(result, f); + } + return result; + } +}; +\end{Code} + + +\subsubsection{思路5} \begin{Code} // LeetCode, Maximum Subarray +// 时间复杂度O(n),空间复杂度O(n) class Solution { public: int maxSubArray(int A[], int n) { return mcss(A, n); - //return mcss_dp(A, n); } private: - /** - * @brief 最大连续子序列和,思路四 - * @param[in] S 数列 - * @param[in] n 数组的长度 - * @return 最大连续子序列和 - */ - static int mcss(int S[], int n) { + // 思路5,求最大连续子序列和 + static int mcss(int A[], int n) { int i, result, cur_min; - int *sum = (int*) malloc((n + 1) * sizeof(int)); // 前n项和 + int *sum = new int[n + 1]; // 前n项和 sum[0] = 0; result = INT_MIN; cur_min = sum[0]; for (i = 1; i <= n; i++) { - sum[i] = sum[i - 1] + S[i - 1]; + sum[i] = sum[i - 1] + A[i - 1]; } for (i = 1; i <= n; i++) { result = max(result, sum[i] - cur_min); cur_min = min(cur_min, sum[i]); } - free(sum); - return result; - } - - /** - * @brief 最大连续子序列和,动规 - * @param[in] S 数列 - * @param[in] n 数组的长度 - * @return 最大连续子序列和 - */ - static int mcss_dp(int S[], int n) { - int i, result; - int *d = (int*) malloc(n * sizeof(int)); - d[0] = S[0]; - result = d[0]; - for (i = 1; i < n; i++) { - d[i] = max(S[i], d[i - 1] + S[i]); - if (result < d[i]) - result = d[i]; - } - free(d); + delete[] sum; return result; } }; @@ -193,21 +187,20 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} -//LeetCode, Palindrome Partitioning II +// LeetCode, Palindrome Partitioning II +// 时间复杂度O(n^2),空间复杂度O(n^2) class Solution { public: int minCut(string s) { - const int len = s.size(); - int f[len+1]; - bool p[len][len]; + const int n = s.size(); + int f[n+1]; + bool p[n][n]; + fill_n(&p[0][0], n * n, false); //the worst case is cutting by each char - for (int i = 0; i <= len; i++) - f[i] = len - 1 - i; // 最后一个f[len]=-1 - for (int i = 0; i < len; i++) - for (int j = 0; j < len; j++) - p[i][j] = false; - for (int i = len - 1; i >= 0; i--) { - for (int j = i; j < len; j++) { + for (int i = 0; i <= n; i++) + f[i] = n - 1 - i; // 最后一个f[n]=-1 + for (int i = n - 1; i >= 0; i--) { + for (int j = i; j < n; j++) { if (s[i] == s[j] && (j - i < 2 || p[i + 1][j - 1])) { p[i][j] = true; f[i] = min(f[i], f[j + 1] + 1); @@ -241,6 +234,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Maximal Rectangle +// 时间复杂度O(n^2),空间复杂度O(n) class Solution { public: int maximalRectangle(vector > &matrix) { @@ -310,6 +304,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Best Time to Buy and Sell Stock III +// 时间复杂度O(n),空间复杂度O(n) class Solution { public: int maxProfit(vector& prices) { @@ -361,18 +356,48 @@ \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]}。因此状态转移方程如下: \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} -\subsubsection{代码} + +\subsubsection{递归} \begin{Code} // LeetCode, Interleaving String -// 动规,二维数组 +// 递归,会超时,仅用来帮助理解 +class Solution { +public: + bool isInterleave(string s1, string s2, string s3) { + if (s3.length() != s1.length() + s2.length()) + return false; + + return isInterleave(begin(s1), end(s1), begin(s2), end(s2), + begin(s3), end(s3)); + } + + template + bool isInterleave(InIt first1, InIt last1, InIt first2, InIt last2, + InIt first3, InIt last3) { + if (first3 == last3) + return first1 == last1 && first2 == last2; + + return (*first1 == *first3 + && isInterleave(next(first1), last1, first2, last2, + next(first3), last3)) + || (*first2 == *first3 + && isInterleave(first1, last1, next(first2), last2, + next(first3), last3)); + } +}; +\end{Code} + + +\subsubsection{动规} +\begin{Code} +// LeetCode, Interleaving String +// 二维动规,时间复杂度O(n^2),空间复杂度O(n^2) class Solution { public: bool isInterleave(string s1, string s2, string s3) { @@ -398,9 +423,11 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{动规+滚动数组} \begin{Code} // LeetCode, Interleaving String -// 动规,滚动数组 +// 二维动规+滚动数组,时间复杂度O(n^2),空间复杂度O(n) class Solution { public: bool isInterleave(string s1, string s2, string s3) { @@ -428,34 +455,6 @@ \subsubsection{代码} }; \end{Code} -\begin{Code} -// LeetCode, Interleaving String -// 递归,小集合可以过,大集合会超时 -class Solution { -public: - bool isInterleave(string s1, string s2, string s3) { - if (s3.length() != s1.length() + s2.length()) - return false; - - return isInterleave(begin(s1), end(s1), begin(s2), end(s2), - begin(s3), end(s3)); - } - - template - bool isInterleave(InIt first1, InIt last1, InIt first2, InIt last2, - InIt first3, InIt last3) { - if (first3 == last3) - return first1 == last1 && first2 == last2; - - return (*first1 == *first3 - && isInterleave(next(first1), last1, first2, last2, - next(first3), last3)) - || (*first2 == *first3 - && isInterleave(first1, last1, next(first2), last2, - next(first3), last3)); - } -}; -\end{Code} \subsubsection{相关题目} \begindot @@ -519,18 +518,19 @@ \subsubsection{分析} 加缓存,可以用数组或HashMap。本题维数较高,用HashMap,\fn{map}和\fn{unordered_map}均可。 -既然可以用记忆化搜索,这题也一定可以用动规。设状态为\fn{f[i][j][k]},表示长度为$i$,起点为\fn{s1[j]}和起点为\fn{s2[]}两个字符串是否互为scramble,则状态转移方程为 +既然可以用记忆化搜索,这题也一定可以用动规。设状态为\fn{f[n][i][j]},表示长度为$n$,起点为\fn{s1[i]}和起点为\fn{s2[j]}两个字符串是否互为scramble,则状态转移方程为 \begin{Code} -f[i][j][k]} = (f[s][j][k] && f[i-s][j+s][k+s]) - || (f[s][j][k-s] && f[i-s][j+s][k]) +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, Interleaving String -// 递归,小集合可以过,大集合会超时 +// 递归,会超时,仅用来帮助理解 +// 时间复杂度O(n^6),空间复杂度O(1) class Solution { public: bool isScramble(string s1, string s2) { @@ -556,9 +556,50 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{动规} +\begin{Code} +// LeetCode, Interleaving String +// 动规,时间复杂度O(n^3),空间复杂度O(n^3) +class Solution { +public: + bool isScramble(string s1, string s2) { + const int N = s1.size(); + if (N != s2.size()) return false; + + // 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); + + for (int i = 0; i < N; i++) + for (int j = 0; j < N; j++) + f[1][i][j] = s1[i] == s2[j]; + + for (int n = 1; n <= N; ++n) { + for (int i = 0; i + n <= N; ++i) { + for (int j = 0; j + n <= N; ++j) { + for (int k = 1; k < n; ++k) { + if ((f[k][i][j] && f[n - k][i + k][j + k]) || + (f[k][i][j + n - k] && f[n - k][i + k][j])) { + f[n][i][j] = true; + break; + } + } + } + } + } + return f[N][0][0]; + } +}; +\end{Code} + + +\subsubsection{递归+剪枝} \begin{Code} // LeetCode, Interleaving String // 递归+剪枝 +// 时间复杂度O(n^6),空间复杂度O(1) class Solution { public: bool isScramble(string s1, string s2) { @@ -590,9 +631,12 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{备忘录法} \begin{Code} // LeetCode, Interleaving String // 递归+map做cache +// 时间复杂度O(n^3),空间复杂度O(n^3) class Solution { public: bool isScramble(string s1, string s2) { @@ -610,16 +654,16 @@ \subsubsection{代码} if (length == 1) return *first1 == *first2; for (int i = 1; i < length; ++i) - if ((cachedIsScramble(first1, first1 + i, first2) - && cachedIsScramble(first1 + i, last1, first2 + i)) - || (cachedIsScramble(first1, first1 + i, last2 - i) - && cachedIsScramble(first1 + i, last1, first2))) + if ((getOrUpdate(first1, first1 + i, first2) + && getOrUpdate(first1 + i, last1, first2 + i)) + || (getOrUpdate(first1, first1 + i, last2 - i) + && getOrUpdate(first1 + i, last1, first2))) return true; return false; } - bool cachedIsScramble(Iterator first1, Iterator last1, Iterator first2) { + bool getOrUpdate(Iterator first1, Iterator last1, Iterator first2) { auto key = make_tuple(first1, last1, first2); auto pos = cache.find(key); @@ -629,6 +673,8 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{备忘录法} \begin{Code} typedef string::const_iterator Iterator; typedef tuple Key; @@ -650,6 +696,7 @@ \subsubsection{代码} // LeetCode, Interleaving String // 递归+unordered_map做cache,比map快 +// 时间复杂度O(n^3),空间复杂度O(n^3) class Solution { public: unordered_map cache; @@ -667,16 +714,16 @@ \subsubsection{代码} return *first1 == *first2; for (int i = 1; i < length; ++i) - if ((cachedIsScramble(first1, first1 + i, first2) - && cachedIsScramble(first1 + i, last1, first2 + i)) - || (cachedIsScramble(first1, first1 + i, last2 - i) - && cachedIsScramble(first1 + i, last1, first2))) + if ((getOrUpdate(first1, first1 + i, first2) + && getOrUpdate(first1 + i, last1, first2 + i)) + || (getOrUpdate(first1, first1 + i, last2 - i) + && getOrUpdate(first1 + i, last1, first2))) return true; return false; } - bool cachedIsScramble(Iterator first1, Iterator last1, Iterator first2) { + bool getOrUpdate(Iterator first1, Iterator last1, Iterator first2) { auto key = make_tuple(first1, last1, first2); auto pos = cache.find(key); @@ -712,7 +759,7 @@ \subsubsection{分析} \end{Code} -\subsubsection{代码} +\subsubsection{备忘录法} \begin{Code} // LeetCode, Minimum Path Sum // 备忘录法 @@ -744,6 +791,8 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{动规} \begin{Code} // LeetCode, Minimum Path Sum // 二维动规 @@ -773,6 +822,8 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{动规+滚动数组} \begin{Code} // LeetCode, Minimum Path Sum // 二维动规+滚动数组 @@ -834,22 +885,24 @@ \subsubsection{分析} \end{enumerate} -\subsubsection{代码} +\subsubsection{动规} \begin{Code} // LeetCode, Edit Distance -// 二维动规 +// 二维动规,时间复杂度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个隔板 - int f[word1.size() + 1][word2.size() + 1]; - for (size_t i = 0; i <= word1.size(); i++) + int f[n + 1][m + 1]; + for (size_t i = 0; i <= n; i++) f[i][0] = i; - for (size_t j = 0; j <= word2.size(); j++) + for (size_t j = 0; j <= m; j++) f[0][j] = j; - for (size_t i = 1; i <= word1.size(); i++) { - for (size_t j = 1; j <= word2.size(); j++) { + for (size_t i = 1; i <= n; i++) { + for (size_t j = 1; j <= m; j++) { if (word1[i - 1] == word2[j - 1]) f[i][j] = f[i - 1][j - 1]; else { @@ -858,14 +911,17 @@ \subsubsection{代码} } } } - return f[word1.size()][word2.size()]; + return f[n][m]; } }; \end{Code} + +\subsubsection{动规+滚动数组} \begin{Code} // LeetCode, Edit Distance // 二维动规+滚动数组 +// 时间复杂度O(n*m),空间复杂度O(n) class Solution { public: int minDistance(const string &word1, const string &word2) { @@ -934,7 +990,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Decode Ways -// 动规 +// 动规,时间复杂度O(n),空间复杂度O(1) class Solution { public: int numDecodings(const string &s) { @@ -987,8 +1043,9 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} -// LeetCode, Decode Ways +// LeetCode, Distinct Subsequences // 二维动规+滚动数组 +// 时间复杂度O(m*n),空间复杂度O(n) class Solution { public: int numDistinct(const string &S, const string &T) { @@ -1029,14 +1086,15 @@ \subsubsection{描述} \subsubsection{分析} 设状态为$f(i)$,表示\fn{s[0,i]}是否可以分词,则状态转移方程为 $$ -f(i) = any_of(f(j) and s[j+1,i] in dict), 0 \leq j < i +f(i) = any\_of(f(j) \&\& s[j+1,i] \in dict), 0 \leq j < i $$ -\subsubsection{代码} +\subsubsection{深搜} \begin{Code} // LeetCode, Word Break // 深搜,超时 +// 时间复杂度O(2^n),空间复杂度O(n) class Solution { public: bool wordBreak(string s, unordered_set &dict) { @@ -1056,16 +1114,18 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{动规} \begin{Code} // LeetCode, Word Break -// 动规 +// 动规,时间复杂度O(n^2),空间复杂度O(n) class Solution { public: bool wordBreak(string s, unordered_set &dict) { // 长度为n的字符串有n+1个隔板 - vector f(s.length() + 1, false); - f[0] = true; - for (int i = 1; i <= s.length(); ++i) { + vector f(s.size() + 1, false); + f[0] = true; // 空字符串 + for (int i = 1; i <= s.size(); ++i) { for (int j = i - 1; j >= 0; --j) { if (f[j] && dict.find(s.substr(j, i - j)) != dict.end()) { f[i] = true; @@ -1073,7 +1133,7 @@ \subsubsection{代码} } } } - return f[s.length()]; + return f[s.size()]; } }; \end{Code} @@ -1108,17 +1168,17 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Word Break II -// 动规 +// 动规,时间复杂度O(n^2),空间复杂度O(n^2) class Solution { public: vector wordBreak(string s, unordered_set &dict) { // 长度为n的字符串有n+1个隔板 vector f(s.length() + 1, false); - // path[i][j]为true,表示s[j, i)是一个合法单词,可以从j处切开 + // prev[i][j]为true,表示s[j, i)是一个合法单词,可以从j处切开 // 第一行未用 - vector > prev(s.length()+1, vector(s.length())); + vector > prev(s.length() + 1, vector(s.length())); f[0] = true; - for (int i = 1; i <= s.length(); ++i) { + for (size_t i = 1; i <= s.length(); ++i) { for (int j = i - 1; j >= 0; --j) { if (f[j] && dict.find(s.substr(j, i - j)) != dict.end()) { f[i] = true; @@ -1138,12 +1198,12 @@ \subsubsection{代码} int cur, vector &path, vector &result) { if (cur == 0) { string tmp; - for (auto iter = path.rbegin(); iter != path.rend(); ++iter) + for (auto iter = path.crbegin(); iter != path.crend(); ++iter) tmp += *iter + " "; - tmp.erase(tmp.end()-1); + tmp.erase(tmp.end() - 1); result.push_back(tmp); } - for (int i = 0; i < s.length(); ++i) { + for (size_t i = 0; i < s.size(); ++i) { if (prev[cur][i]) { path.push_back(s.substr(i, cur - i)); gen_path(s, prev, i, path, result); diff --git a/C++/chapGraph.tex b/C++/chapGraph.tex index 6efee335..c3f30918 100644 --- a/C++/chapGraph.tex +++ b/C++/chapGraph.tex @@ -22,10 +22,10 @@ \subsubsection{描述} OJ's undirected graph serialization: Nodes are labeled uniquely. -We use \code{#} as a separator for each node, and \code{,} as a separator for node label and each neighbour of the node. -As an example, consider the serialized graph \code{\{0,1,2#1,2#2,2\}}. +We use \code{\#} as a separator for each node, and \code{,} as a separator for node label and each neighbour of the node. +As an example, consider the serialized graph \code{\{0,1,2\#1,2\#2,2\}}. -The graph has a total of three nodes, and therefore contains three parts as separated by \code{#}. +The graph has a total of three nodes, and therefore contains three parts as separated by \code{\#}. \begin{enumerate} \item First node is labeled as 0. Connect node 0 to both nodes 1 and 2. \item Second node is labeled as 1. Connect node 1 to node 2. @@ -47,10 +47,10 @@ \subsubsection{分析} 广度优先遍历或深度优先遍历都可以。 -\subsubsection{代码} +\subsubsection{DFS} \begin{Code} // LeetCode, Clone Graph -// DFS +// DFS,时间复杂度O(n),空间复杂度O(n) class Solution { public: UndirectedGraphNode *cloneGraph(const UndirectedGraphNode *node) { @@ -78,9 +78,11 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{BFS} \begin{Code} // LeetCode, Clone Graph -// BFS +// BFS,时间复杂度O(n),空间复杂度O(n) class Solution { public: UndirectedGraphNode *cloneGraph(const UndirectedGraphNode *node) { diff --git a/C++/chapGreedy.tex b/C++/chapGreedy.tex index 420caa25..86b67e3a 100644 --- a/C++/chapGreedy.tex +++ b/C++/chapGreedy.tex @@ -31,28 +31,27 @@ \subsubsection{分析} f[i] = max(f[i-1], A[i-1])-1, i > 0 $$ -\subsubsection{代码} + +\subsubsection{代码1} \begin{Code} // LeetCode, Jump Game -// 思路一 +// 思路1,时间复杂度O(n),空间复杂度O(1) class Solution { public: bool canJump(int A[], int n) { - int right_most = 0; // 最右能跳到哪里 - for (int start = 0; start <= right_most; start++) { - if (A[start] + start > right_most) - right_most = A[start] + start; - - if (right_most >= n - 1) return true; - } - return false; + int reach = 1; // 最右能跳到哪里 + for (int i = 0; i < reach && reach < n; ++i) + reach = max(reach, i + 1 + A[i]); + return reach >= n; } }; \end{Code} + +\subsubsection{代码2} \begin{Code} // LeetCode, Jump Game -// 思路二 +// 思路2,时间复杂度O(n),空间复杂度O(1) class Solution { public: bool canJump (int A[], int n) { @@ -69,9 +68,11 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{代码3} \begin{Code} // LeetCode, Jump Game -// 思路三,动规 +// 思路三,动规,时间复杂度O(n),空间复杂度O(n) class Solution { public: bool canJump(int A[], int n) { @@ -114,9 +115,10 @@ \subsubsection{分析} 贪心法。 -\subsubsection{代码} +\subsubsection{代码1} \begin{Code} // LeetCode, Jump Game II +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: int jump(int A[], int n) { @@ -142,6 +144,32 @@ \subsubsection{代码} \end{Code} +\subsubsection{代码2} +\begin{Code} +// LeetCode, Jump Game II +// 时间复杂度O(n),空间复杂度O(1) +class Solution { +public: + int jump(int A[], int n) { + int result = 0; + // the maximum distance that has been reached + int last = 0; + // the maximum distance that can be reached by using "ret+1" steps + int cur = 0; + for (int i = 0; i < n; ++i) { + if (i > last) { + last = cur; + ++result; + } + cur = max(cur, i + A[i]); + } + + return result; + } +}; +\end{Code} + + \subsubsection{相关题目} \begindot \item Jump Game ,见 \S \ref{sec:jump-game} @@ -166,6 +194,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Best Time to Buy and Sell Stock +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: int maxProfit(vector &prices) { @@ -208,6 +237,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Best Time to Buy and Sell Stock II +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: int maxProfit(vector &prices) { @@ -251,24 +281,24 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Longest Substring Without Repeating Characters -// 贪心法 +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: int lengthOfLongestSubstring(string s) { const int ASCII_MAX = 26; int last[ASCII_MAX]; // 记录字符上次出现过的位置 + int start = 0; // 记录当前子串的起始位置 + fill(last, last + ASCII_MAX, -1); // 0也是有效位置,因此初始化为-1 - int len = 0, max_len = 0; - for (size_t i = 0; i < s.size(); i++, len++) { - if (last[s[i] - 'a'] >= 0) { - max_len = max(len, max_len); - len = 0; - i = last[s[i] - 'a'] + 1; - fill(last, last + ASCII_MAX, -1); // 重新开始 + int max_len = 0; + for (int i = 0; i < s.size(); i++) { + if (last[s[i] - 'a'] >= start) { + max_len = max(i - start, max_len); + start = last[s[i] - 'a'] + 1; } last[s[i] - 'a'] = i; } - return max(len, max_len); // 别忘了最后一次,例如"abcd" + return max((int)s.size() - start, max_len); // 别忘了最后一次,例如"abcd" } }; \end{Code} @@ -297,6 +327,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Container With Most Water +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: int maxArea(vector &height) { diff --git a/C++/chapImplement.tex b/C++/chapImplement.tex index 8d74b962..6c0aeab8 100644 --- a/C++/chapImplement.tex +++ b/C++/chapImplement.tex @@ -32,6 +32,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} //LeetCode, Reverse Integer +// 时间复杂度O(logn),空间复杂度O(1) class Solution { public: int reverse (int x) { @@ -79,6 +80,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} //LeetCode, Palindrome Number +// 时间复杂度O(1),空间复杂度O(1) class Solution { public: bool isPalindrome(int x) { @@ -102,61 +104,7 @@ \subsubsection{代码} \subsubsection{相关题目} \begindot \item Reverse Integer, 见 \S \ref{sec:reverse-integer} -\myenddot - - -\section{Two Sum} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\label{sec:wordladder} - - -\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)$ - - -\subsubsection{代码} -\begin{Code} -//LeetCode, Two Sum -// 方法2:hash。用一个哈希表,存储每个数对应的下标,复杂度O(n),代码如下, -class Solution { -public: - vector twoSum(vector &numbers, int target) { - unordered_map mapping; - vector result; - for (int i = 0; i < numbers.size(); i++) { - mapping[numbers[i]] = i; - } - for (int i = 0; i < numbers.size(); i++) { - const int gap = target - numbers[i]; - if (mapping.find(gap) != mapping.end()) { - result.push_back(i + 1); - result.push_back(mapping[gap] + 1); - break; - } - } - return result; - } -}; -\end{Code} - - -\subsubsection{相关题目} - -\begindot -\item 无 +\item Valid Palindrome, 见 \S \ref{sec:valid-palindrome} \myenddot @@ -192,6 +140,7 @@ \subsubsection{代码} }; //LeetCode, Insert Interval +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: vector insert(vector &intervals, Interval newInterval) { @@ -250,6 +199,7 @@ \subsubsection{代码} //LeetCode, Merge Interval //复用一下Insert Intervals的解法即可 +// 时间复杂度O(n1+n2+...),空间复杂度O(1) class Solution { public: vector merge(vector &intervals) { @@ -314,8 +264,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Minimum Window Substring -// 双指针,动态维护一个区间。尾指针不断往后扫,当扫到有一个窗口包含了所有T的字符 -// 后,然后再收缩头指针,直到不能再收缩为止。最后记录所有可能的情况中窗口最小的 +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: string minWindow(string S, string T) { @@ -384,19 +333,19 @@ \subsubsection{分析} 常见的做法是将字符转化为一个int,一一对应,形成一个int数组。但是这样很浪费空间,一个int32的最大值是$2^{31}-1=2147483647$,可以与9个字符对应,由于有乘法,减半,则至少可以与4个字符一一对应。一个int64可以与9个字符对应。 -\subsubsection{代码} - +\subsubsection{代码1} \begin{Code} // LeetCode, Multiply Strings // @author 连城 (http://weibo.com/lianchengzju) // 一个字符对应一个int +// 时间复杂度O(n*m),空间复杂度O(n+m) typedef vector bigint; bigint make_bigint(string const& repr) { bigint n; transform(repr.rbegin(), repr.rend(), back_inserter(n), [](char c) { return c - '0'; }); - return move(n); + return n; } string to_string(bigint const& n) { @@ -404,7 +353,7 @@ \subsubsection{代码} transform(find_if(n.rbegin(), prev(n.rend()), [](char c) { return c > '\0'; }), n.rend(), back_inserter(str), [](char c) { return c + '0'; }); - return move(str); + return str; } bigint operator*(bigint const& x, bigint const& y) { @@ -417,7 +366,7 @@ \subsubsection{代码} z[i + j] %= 10; } - return move(z); + return z; } class Solution { @@ -428,9 +377,12 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{代码2} \begin{Code} // LeetCode, Multiply Strings // 9个字符对应一个int64_t +// 时间复杂度O(n*m/81),空间复杂度O((n+m)/9) /** 大整数类. */ class BigInt { public: @@ -551,10 +503,10 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Substring with Concatenation of All Words +// 时间复杂度O(n*m),空间复杂度O(m) class Solution { public: - vector findSubstring(string s, vector& dict) - { + vector findSubstring(string s, vector& dict) { size_t wordLength = dict.front().length(); size_t catLength = wordLength * dict.size(); vector result; @@ -619,10 +571,10 @@ \subsubsection{分析} 另一种思路,下一行第一个元素和最后一个元素赋值为1,中间的每个元素,等于上一行的左上角和右上角元素之和。 -\subsubsection{代码} - +\subsubsection{从左到右} \begin{Code} // LeetCode, Pascal's Triangle +// 时间复杂度O(n^2),空间复杂度O(n) class Solution { public: vector > generate(int numRows) { @@ -646,6 +598,28 @@ \subsubsection{代码} \end{Code} +\subsubsection{从右到左} +\begin{Code} +// LeetCode, Pascal's Triangle +// 时间复杂度O(n^2),空间复杂度O(n) +class Solution { +public: + vector > generate(int numRows) { + vector > result; + vector array; + for (int i = 1; i <= numRows; i++) { + for (int j = i - 2; j > 0; j--) { + array[j] = array[j - 1] + array[j]; + } + array.push_back(1); + result.push_back(array); + } + return result; + } +}; +\end{Code} + + \subsubsection{相关题目} \begindot \item Pascal's Triangle II,见 \S \ref{sec:pascals-triangle-ii} @@ -674,21 +648,19 @@ \subsubsection{代码} \begin{Code} // LeetCode, Pascal's Triangle II -// 滚动数组 +// 滚动数组,时间复杂度O(n^2),空间复杂度O(n) class Solution { public: - vector getRow(int rowIndex) { - vector result(rowIndex + 2, 0); - - result[1] = 1; - for (int i = 0; i < rowIndex; i++) { - for (int j = rowIndex + 1; j > 0; j--) { - result[j] = result[j - 1] + result[j]; - } - } - result.erase(result.begin()); - return result; - } + 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} @@ -719,45 +691,33 @@ \subsubsection{描述} \subsubsection{分析} -无 +模拟。 \subsubsection{代码} \begin{Code} // LeetCode, Spiral Matrix +// @author 龚陆安 (http://weibo.com/luangong) +// 时间复杂度O(n^2),空间复杂度O(1) class Solution { public: - vector spiralOrder(vector > &matrix) { - const int m = matrix.size(); - if (m == 0) return vector(); - const int n = matrix[0].size(); - if (n == 0) return vector(); - - vector result(m * n, 0); - - int layer, index; - for (layer = min(m, n), index = 0; layer > 1; layer -= 2) { - const int offset = (min(m, n) - layer) / 2; - // left to right - for (int i = offset; i < n - offset - 1; i++) - result[index++] = matrix[offset][i]; - // top to bottom - for (int i = offset; i < m - offset - 1; i++) - result[index++] = matrix[i][n - offset - 1]; - // right to left - for (int i = n - offset - 1; i > offset; i--) - result[index++] = matrix[m - offset - 1][i]; - // bottom to top - for (int i = m - offset - 1; i > offset; i--) - result[index++] = matrix[i][offset]; - } - - if (layer == 1) { // 最后一行 - if (m < n) - for (int i = m / 2; i < n - m / 2; i++) - result[index++] = matrix[m / 2][i]; - else - for (int i = n / 2; i < m - n / 2; i++) - result[index++] = matrix[i][n / 2]; + vector spiralOrder(vector >& matrix) { + vector result; + if (matrix.empty()) return result; + int beginX = 0, endX = matrix[0].size() - 1; + int beginY = 0, endY = matrix.size() - 1; + while (true) { + // From left to right + for (int j = beginX; j <= endX; ++j) result.push_back(matrix[beginY][j]); + if (++beginY > endY) break; + // From top to bottom + for (int i = beginY; i <= endY; ++i) result.push_back(matrix[i][endX]); + if (beginX > --endX) break; + // From right to left + for (int j = endX; j >= beginX; --j) result.push_back(matrix[endY][j]); + if (beginY > --endY) break; + // From bottom to top + for (int i = endY; i >= beginY; --i) result.push_back(matrix[i][beginX]); + if (++beginX > endX) break; } return result; } @@ -795,34 +755,60 @@ \subsubsection{分析} 这题比上一题要简单。 -\subsubsection{代码} +\subsubsection{代码1} \begin{Code} // LeetCode, Spiral Matrix II +// 时间复杂度O(n^2),空间复杂度O(n^2) class Solution { public: vector > generateMatrix(int n) { - if (n == 0) return vector >(); - - vector > matrix(n, vector(n, 0)); - - int num = 1, layer; - for (layer = n; layer > 1; layer -= 2) { - const int offset = (n - layer) / 2; - // left to right - for (int i = offset; i < n - offset - 1; i++) - matrix[offset][i] = num++; - // top to bottom - for (int i = offset; i < n - offset - 1; i++) - matrix[i][n - offset - 1] = num++; - // right to left - for (int i = n - offset - 1; i > offset; i--) - matrix[n - offset - 1][i] = num++; - // bottom to top - for (int i = n - offset - 1; i > offset; i--) - matrix[i][offset] = num++; + vector > matrix(n, vector(n)); + int begin = 0, end = n - 1; + int num = 1; + + while (begin < end) { + for (int j = begin; j < end; ++j) matrix[begin][j] = num++; + for (int i = begin; i < end; ++i) matrix[i][end] = num++; + for (int j = end; j > begin; --j) matrix[end][j] = num++; + for (int i = end; i > begin; --i) matrix[i][begin] = num++; + ++begin; + --end; } - if (layer == 1) matrix[n / 2][n / 2] = num; + if (begin == end) matrix[begin][begin] = num; + + return matrix; + } +}; +\end{Code} + + +\subsubsection{代码2} +\begin{Code} +// LeetCode, Spiral Matrix II +// @author 龚陆安 (http://weibo.com/luangong) +// 时间复杂度O(n^2),空间复杂度O(n^2) +class Solution { +public: + vector > generateMatrix(int n) { + vector< vector > matrix(n, vector(n)); + if (n == 0) return matrix; + int beginX = 0, endX = n - 1; + int beginY = 0, endY = n - 1; + int num = 1; + while (true) { + for (int j = beginX; j <= endX; ++j) matrix[beginY][j] = num++; + if (++beginY > endY) break; + + for (int i = beginY; i <= endY; ++i) matrix[i][endX] = num++; + if (beginX > --endX) break; + + for (int j = endX; j >= beginX; --j) matrix[endY][j] = num++; + if (beginY > --endY) break; + + for (int i = endY; i >= beginY; --i) matrix[i][beginX] = num++; + if (++beginX > endX) break; + } return matrix; } }; @@ -882,7 +868,8 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} -//LeetCode, ZigZag Conversion +// LeetCode, ZigZag Conversion +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: string convert(string s, int nRows) { @@ -923,17 +910,45 @@ \subsubsection{分析} 最简单的方法,是不断减去被除数。在这个基础上,可以做一点优化,每次把被除数翻倍,从而加速。 -\subsubsection{代码} +\subsubsection{代码1} \begin{Code} // LeetCode, Divide Two Integers +// 时间复杂度O(logn),空间复杂度O(1) class Solution { public: int divide(int dividend, int divisor) { - int result = 0; + // 当 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 + long long result = 0; + while (a >= b) { + long long c = b; + for (int i = 0; a >= c; ++i, c <<= 1) { + a -= c; + result += 1 << i; + } + } + + return ((dividend^divisor) >> 31) ? (-result) : (result); + } +}; +\end{Code} + + +\subsubsection{代码2} +\begin{Code} +// LeetCode, Divide Two Integers +// 时间复杂度O(logn),空间复杂度O(1) +class Solution { +public: + int divide(int dividend, int divisor) { + int result = 0; // 当 dividend = INT_MIN时,divisor = -1时,结果会溢出 const bool sign = (dividend > 0 && divisor < 0) || (dividend < 0 && divisor > 0); // 异号 - // 当 dividend = INT_MIN, divisor =-1 时,会溢出,所以用 unsigned int + // 当 dividend = INT_MIN时,-dividend会溢出,所以用 unsigned int unsigned int a = dividend >= 0 ? dividend : -dividend; unsigned int b = divisor >= 0 ? divisor : -divisor; @@ -1005,6 +1020,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Text Justification +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: vector fullJustify(vector &words, int L) { @@ -1064,6 +1080,108 @@ \subsubsection{代码} \end{Code} +\subsubsection{相关题目} +\begindot +\item 无 +\myenddot + + +\section{Max Points on a Line} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:Max-Points-on-a-Line} + + +\subsubsection{描述} +Given $n$ points on a 2D plane, find the maximum number of points that lie on the same straight line. + + +\subsubsection{分析} +暴力枚举法。两点决定一条直线,$n$个点两两组合,可以得到$\dfrac{1}{2}n(n+1)$条直线,对每一条直线,判断$n$个点是否在该直线上,从而可以得到这条直线上的点的个数,选择最大的那条直线返回。复杂度$O(n^3)$。 + +上面的暴力枚举法以“边”为中心,再看另一种暴力枚举法,以每个“点”为中心,然后遍历剩余点,找到所有的斜率,如果斜率相同,那么一定共线对每个点,用一个哈希表,key为斜率,value为该直线上的点数,计算出哈希表后,取最大值,并更新全局最大值,最后就是结果。时间复杂度$O(n^2)$,空间复杂度$O(n)$。 + + +\subsubsection{以边为中心} +\begin{Code} +// LeetCode, Max Points on a Line +// 暴力枚举法,以边为中心,时间复杂度O(n^3),空间复杂度O(1) +class Solution { +public: + int maxPoints(vector &points) { + if (points.size() < 3) return points.size(); + int result = 0; + + for (int i = 0; i < points.size() - 1; i++) { + for (int j = i + 1; j < points.size(); j++) { + int sign = 0; + int a, b, c; + if (points[i].x == points[j].x) sign = 1; + else { + a = points[j].x - points[i].x; + b = points[j].y - points[i].y; + c = a * points[i].y - b * points[i].x; + } + 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)) + count++; + } + if (count > result) result = count; + } + } + return result; + } +}; +\end{Code} + + +\subsubsection{以点为中心} +\begin{Code} +// LeetCode, Max Points on a Line +// 暴力枚举,以点为中心,时间复杂度O(n^2),空间复杂度O(n) +class Solution { +public: + int maxPoints(vector &points) { + if (points.size() < 3) return points.size(); + int result = 0; + + 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共线的最大点数 + + for (int j = i + 1; j < points.size(); j++) { + double slope; // 斜率 + if (points[i].x == points[j].x) { + slope = std::numeric_limits::infinity(); + if (points[i].y == points[j].y) { + ++ samePointNum; + continue; + } + } else { + slope = 1.0 * (points[i].y - points[j].y) / + (points[i].x - points[j].x); + } + + int count = 0; + if (slope_count.find(slope) != slope_count.end()) + count = ++slope_count[slope]; + else { + count = 2; + slope_count[slope] = 2; + } + + if (point_max < count) point_max = count; + } + result = max(result, point_max + samePointNum); + } + return result; + } +}; +\end{Code} + + \subsubsection{相关题目} \begindot \item 无 diff --git a/C++/chapLinearList.tex b/C++/chapLinearList.tex index f236788e..fed9cff2 100644 --- a/C++/chapLinearList.tex +++ b/C++/chapLinearList.tex @@ -20,12 +20,13 @@ \subsubsection{描述} \subsubsection{分析} +无 - -\subsubsection{代码} +\subsubsection{代码1} \begin{Code} // LeetCode, Remove Duplicates from Sorted Array +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: int removeDuplicates(int A[], int n) { @@ -42,8 +43,23 @@ \subsubsection{代码} \end{Code} +\subsubsection{代码2} +\begin{Code} +// LeetCode, Remove Duplicates from Sorted Array +// 使用STL,时间复杂度O(n),空间复杂度O(1) +class Solution { +public: + int removeDuplicates(int A[], int n) { + return distance(A, unique(A, A + n)); + } +}; +\end{Code} + + +\subsubsection{代码3} \begin{Code} -// LeetCode, Remove Duplicates from Sorted Array,使用STL +// LeetCode, Remove Duplicates from Sorted Array +// 使用STL,时间复杂度O(n),空间复杂度O(1) class Solution { public: int removeDuplicates(int A[], int n) { @@ -54,8 +70,7 @@ \subsubsection{代码} OutIt removeDuplicates(InIt first, InIt last, OutIt output) { while (first != last) { *output++ = *first; - first = find_if(first, last, - bind1st(not_equal_to(), *first)); + first = upper_bound(first, last, *first); } return output; @@ -89,28 +104,45 @@ \subsubsection{分析} 加一个变量记录一下元素出现的次数即可。这题因为是已经排序的数组,所以一个变量即可解决。如果是没有排序的数组,则需要引入一个hashmap来记录出现次数。 -\subsubsection{代码} +\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(int A[], int n) { - if (n == 0) return 0; + if (n <= 2) return n; - int occur = 1; + int index = 2; + for (int i = 2; i < n; i++){ + if (A[i] != A[index - 2]) + A[index++] = A[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(int A[], int n) { int index = 0; - for (int i = 1; i < n; i++) { - if (A[index] == A[i]) { - if (occur < 2) { - A[++index] = A[i]; - occur++; - } - } else { - A[++index] = A[i]; - occur = 1; - } + for (int i = 0; i < n; ++i) { + if (i > 0 && i < n - 1 && A[i] == A[i - 1] && A[i] == A[i + 1]) + continue; + + A[index++] = A[i]; } - return index + 1; + return index; } }; \end{Code} @@ -144,6 +176,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Search in Rotated Sorted Array +// 时间复杂度O(log n),空间复杂度O(1) class Solution { public: int search(int A[], int n, int target) { @@ -201,6 +234,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Search in Rotated Sorted Array II +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: bool search(int A[], int n, int target) { @@ -215,12 +249,12 @@ \subsubsection{代码} else first = mid + 1; } else if (A[first] > A[mid]) { - if (A[mid] <= target && target <= A[last-1]) + if (A[mid] < target && target <= A[last-1]) first = mid + 1; else last = mid; } else - //skip duplicate one, A[start] == A[mid] + //skip duplicate one first++; } return false; @@ -277,6 +311,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Median of Two Sorted Arrays +// 时间复杂度O(log(m+n)),空间复杂度O(log(m+n)) class Solution { public: double findMedianSortedArrays(int A[], int m, int B[], int n) { @@ -285,23 +320,23 @@ \subsubsection{代码} return find_kth(A, m, B, n, total / 2 + 1); else return (find_kth(A, m, B, n, total / 2) - + find_kth(A, m, B, n, total / 2 + 1)) / 2; + + find_kth(A, m, B, n, total / 2 + 1)) / 2.0; } private: - static double find_kth(int A[], int m, int B[], int n, int k) { + static int find_kth(int A[], int m, int B[], int n, int k) { //always assume that m is equal or smaller than n if (m > n) return find_kth(B, n, A, m, k); if (m == 0) return B[k - 1]; if (k == 1) return min(A[0], B[0]); //divide k into two parts - int pa = min(k / 2, m), pb = k - pa; - if (A[pa - 1] < B[pb - 1]) - return find_kth(A + pa, m - pa, B, n, k - pa); - else if (A[pa - 1] > B[pb - 1]) - return find_kth(A, m, B + pb, n - pb, k - pb); + 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[pa - 1]; + return A[ia - 1]; } }; \end{Code} @@ -339,9 +374,10 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // Leet Code, Longest Consecutive Sequence +// 时间复杂度O(n),空间复杂度O(n) class Solution { public: - int longestConsecutive(vector const& num) { + int longestConsecutive(const vector &num) { unordered_map used; for (auto i : num) used[i] = false; @@ -373,6 +409,47 @@ \subsubsection{代码} }; \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 &num) { + unordered_map map; + int size = num.size(); + int l = 1; + for (int i = 0; i < size; i++) { + if (map.find(num[i]) != map.end()) continue; + map[num[i]] = 1; + if (map.find(num[i] - 1) != map.end()) { + l = max(l, mergeCluster(map, num[i] - 1, num[i])); + } + if (map.find(num[i] + 1) != map.end()) { + l = max(l, mergeCluster(map, num[i], num[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 @@ -380,6 +457,65 @@ \subsubsection{相关题目} \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 &num, int target) { + unordered_map mapping; + vector result; + for (int i = 0; i < num.size(); i++) { + mapping[num[i]] = i; + } + for (int i = 0; i < num.size(); i++) { + const int gap = target - num[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} @@ -403,13 +539,15 @@ \subsubsection{描述} \subsubsection{分析} -先排序,然后二分查找,复杂度 $O(n^2 \log n)$。 +先排序,然后左右夹逼,复杂度 $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& num) { @@ -419,17 +557,23 @@ \subsubsection{代码} const int target = 0; auto last = num.end(); - for (auto a = num.begin(); a < prev(last, 2); - a = upper_bound(a, prev(last, 2), *a)) { - for (auto b = next(a); b < prev(last); - b = upper_bound(b, prev(last), *b)) { - const int c = target - *a - *b; - - if (binary_search(next(b), last, c)) - result.push_back(vector { *a, *b, c }); + for (auto a = num.begin(); a < prev(last, 2); ++a) { + auto b = next(a); + auto c = prev(last); + while (b < c) { + if (*a + *b + *c < target) { + ++b; + } else if (*a + *b + *c > target) { + --c; + } else { + result.push_back({ *a, *b, *c }); + ++b; + --c; + } } } - + sort(result.begin(), result.end()); + result.erase(unique(result.begin(), result.end()), result.end()); return result; } }; @@ -438,6 +582,7 @@ \subsubsection{代码} \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 @@ -455,13 +600,13 @@ \subsubsection{描述} \subsubsection{分析} -先排序,然后左右夹逼,复杂度 $O(n^3)$。 +先排序,然后左右夹逼,复杂度 $O(n^2)$。 \subsubsection{代码} \begin{Code} // LeetCode, 3Sum Closest -// 先排序,然后左右夹逼 +// 先排序,然后左右夹逼,时间复杂度O(n^2),空间复杂度O(1) class Solution { public: int threeSumClosest(vector& num, int target) { @@ -470,8 +615,7 @@ \subsubsection{代码} sort(num.begin(), num.end()); - for (auto a = num.begin(); a != prev(num.end(), 2); - a = upper_bound(a, prev(num.end(), 2), *a)) { + for (auto a = num.begin(); a != prev(num.end(), 2); ++a) { auto b = next(a); auto c = prev(num.end()); @@ -484,10 +628,8 @@ \subsubsection{代码} min_gap = gap; } - if (sum < target) - b = upper_bound(b, c, *b); - else - c = prev(lower_bound(b, c, *c)); + if (sum < target) ++b; + else --c; } } @@ -499,6 +641,7 @@ \subsubsection{代码} \subsubsection{相关题目} \begindot +\item Two sum, 见 \S \ref{sec:Two-sum} \item 3Sum, 见 \S \ref{sec:3sum} \item 4Sum, 见 \S \ref{sec:4sum} \myenddot @@ -528,14 +671,15 @@ \subsubsection{描述} \subsubsection{分析} -跟 3Sum 很类似,先排序,然后二分查找,复杂度 $O(n^3 \log n)$,会超时。 +先排序,然后左右夹逼,复杂度 $O(n^3)$,会超时。 -然后想到,可以先缓存两个数的和,最终复杂度$O(n^3)$。这个策略也适用于 3Sum 。 +可以用一个hashmap先缓存两个数的和,最终复杂度$O(n^3)$。这个策略也适用于 3Sum 。 -\subsubsection{代码} + +\subsubsection{左右夹逼} \begin{Code} // LeetCode, 4Sum -// 先排序,然后二分查找,复杂度n^3*logn,会超时 +// 先排序,然后左右夹逼,时间复杂度O(n^3),空间复杂度O(1) class Solution { public: vector> fourSum(vector& num, int target) { @@ -544,57 +688,147 @@ \subsubsection{代码} sort(num.begin(), num.end()); auto last = num.end(); - for (auto a = num.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)) { - for (auto c = next(b); c < prev(last); - c = upper_bound(c, prev(last), *c)) { - const int d = target - *a - *b - *c; - - if (binary_search(next(c), last, d)) - result.push_back(vector { *a, *b, *c, d }); + for (auto a = num.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 -// 先缓存两个数的和,复杂度O(n^3) +// 用一个hashmap先缓存两个数的和 +// 时间复杂度,平均O(n^2),最坏O(n^4),空间复杂度O(n^2) class Solution { public: vector > fourSum(vector &num, int target) { - if (num.size() < 4) return vector >(); + vector> result; + if (num.size() < 4) return result; sort(num.begin(), num.end()); - map > > cache; + unordered_map > > cache; for (size_t a = 0; a < num.size(); ++a) { for (size_t b = a + 1; b < num.size(); ++b) { cache[num[a] + num[b]].push_back(pair(a, b)); } } - set> result; // 去重,因为 num 里有重复元素 - for (size_t c = 2; c < num.size(); ++c) { + for (int c = 0; c < num.size(); ++c) { for (size_t d = c + 1; d < num.size(); ++d) { const int key = target - num[c] - num[d]; - if (cache.find(key) != cache.end()) { - for (size_t k = 0; k < cache[key].size(); ++k) { - if (c <= cache[key][k].second) continue; // 有重叠 + 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( { num[vec[k].first], + num[vec[k].second], num[c], num[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& num, int target) { + vector> result; + if (num.size() < 4) return result; + sort(num.begin(), num.end()); + + unordered_multimap> cache; + for (int i = 0; i + 1 < num.size(); ++i) + for (int j = i + 1; j < num.size(); ++j) + cache.insert(make_pair(num[i] + num[j], make_pair(i, j))); + + for (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 = { num[a], num[b], num[c], num[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& num, int target) { + vector> result; + if (num.size() < 4) return result; + sort(num.begin(), num.end()); - result.insert(vector { num[cache[key][k].first], - num[cache[key][k].second], num[c], num[d] }); + auto last = num.end(); + for (auto a = num.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 vector >(result.begin(), result.end()); + return result; } }; \end{Code} @@ -602,6 +836,7 @@ \subsubsection{代码} \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 @@ -621,9 +856,10 @@ \subsubsection{分析} 无 -\subsubsection{代码} +\subsubsection{代码1} \begin{Code} // LeetCode, Remove Element +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: int removeElement(int A[], int n, int elem) { @@ -639,6 +875,19 @@ \subsubsection{代码} \end{Code} +\subsubsection{代码2} +\begin{Code} +// LeetCode, Remove Element +// 使用remove(),时间复杂度O(n),空间复杂度O(1) +class Solution { +public: + int removeElement(int A[], int n, int elem) { + return distance(A, remove(A, A+n, elem)); + } +}; +\end{Code} + + \subsubsection{相关题目} \begindot \item 无 @@ -676,6 +925,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Next Permutation +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: void nextPermutation(vector &num) { @@ -693,7 +943,7 @@ \subsubsection{代码} // 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 and !(*pivot < *prev(pivot))) + while (pivot != rlast && *pivot >= *prev(pivot)) ++pivot; // No such elemenet found, current sequence is already the largest @@ -763,17 +1013,17 @@ \subsubsection{分析} \begin{eqnarray} k_2 &=& k\%(n-1)! \nonumber \\ a_2 &=& k_2/(n-2)! \nonumber \\ -... &=& ... \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{代码} +\subsubsection{使用next_permutation()} \begin{Code} // LeetCode, Permutation Sequence -// 思路一:next_permutation(),TLE +// 使用next_permutation(),TLE class Solution { public: string getPermutation(int n, int k) { @@ -792,9 +1042,11 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{康托编码} \begin{Code} // LeetCode, Permutation Sequence -// 康托编码 +// 康托编码,时间复杂度O(n),空间复杂度O(1) class Solution { public: string getPermutation(int n, int k) { @@ -824,9 +1076,9 @@ \subsubsection{代码} --k; // 康托编码从0开始 for (int i = n - 1; i > 0; k %= base, base /= i, --i) { - auto pos = next(S.begin(), k / base); - result.push_back(*pos); - S.erase(pos); + auto a = next(S.begin(), k / base); + result.push_back(*a); + S.erase(a); } result.push_back(S[0]); // 最后一个 @@ -855,7 +1107,7 @@ \subsubsection{描述} The Sudoku board could be partially filled, where empty cells are filled with the character \fn{'.'}. \begin{center} -\includegraphics[width=200pt]{sudoku.png}\\ +\includegraphics[width=150pt]{sudoku.png}\\ \figcaption{A partially filled sudoku which is valid}\label{fig:sudoku} \end{center} @@ -866,9 +1118,10 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Valid Sudoku +// 时间复杂度O(n^2),空间复杂度O(1) class Solution { public: - bool isValidSudoku(vector>& board) { + bool isValidSudoku(const vector>& board) { bool used[9]; for (int i = 0; i < 9; ++i) { @@ -903,8 +1156,7 @@ \subsubsection{代码} if (used[ch - '1']) return false; - used[ch - '1'] = true; - return true; + return used[ch - '1'] = true; } }; \end{Code} @@ -948,11 +1200,10 @@ \subsubsection{分析} \end{enumerate} -\subsubsection{代码} - +\subsubsection{代码1} \begin{Code} // LeetCode, Trapping Rain Water -// 思路1 +// 思路1,时间复杂度O(n),空间复杂度O(n) class Solution { public: int trap(int A[], int n) { @@ -980,9 +1231,11 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{代码2} \begin{Code} // LeetCode, Trapping Rain Water -// 思路2 +// 思路2,时间复杂度O(n),空间复杂度O(1) class Solution { public: int trap(int A[], int n) { @@ -1002,11 +1255,14 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{代码3} 第三种解法,用一个栈辅助,小于栈顶的元素压入,大于等于栈顶就把栈里所有小于或等于当前值的元素全部出栈处理掉。 \begin{Code} // LeetCode, Trapping Rain Water // 用一个栈辅助,小于栈顶的元素压入,大于等于栈顶就把栈里所有小于或 // 等于当前值的元素全部出栈处理掉,计算面积,最后把当前元素入栈 +// 时间复杂度O(n),空间复杂度O(n) class Solution { public: int trap(int a[], int n) { @@ -1064,17 +1320,17 @@ \subsubsection{分析} 如下图,首先沿着副对角线翻转一次,然后沿着水平中线翻转一次。 \begin{center} -\includegraphics{rotate-image.png}\\ +\includegraphics[width=200pt]{rotate-image.png}\\ \figcaption{Rotate Image}\label{fig:rotate-image} \end{center} 或者,首先沿着水平中线翻转一次,然后沿着主对角线翻转一次。 -\subsubsection{代码} +\subsubsection{代码1} \begin{Code} -// LeetCode, Trapping Rain Water -// 思路 1 +// LeetCode, Rotate Image +// 思路 1,时间复杂度O(n^2),空间复杂度O(1) class Solution { public: void rotate(vector>& matrix) { @@ -1091,9 +1347,10 @@ \subsubsection{代码} }; \end{Code} +\subsubsection{代码2} \begin{Code} -// LeetCode, Trapping Rain Water -// 思路 2 +// LeetCode, Rotate Image +// 思路 2,时间复杂度O(n^2),空间复杂度O(1) class Solution { public: void rotate(vector>& matrix) { @@ -1129,9 +1386,10 @@ \subsubsection{分析} 高精度加法。 -\subsubsection{代码} +\subsubsection{代码1} \begin{Code} // LeetCode, Plus One +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: vector plusOne(vector &digits) { @@ -1141,15 +1399,42 @@ \subsubsection{代码} private: // 0 <= digit <= 9 void add(vector &digits, int digit) { - int carry = digit; + int c = digit; // carry, 进位 for (auto it = digits.rbegin(); it != digits.rend(); ++it) { - *it += carry; - carry = *it / 10; + *it += c; + c = *it / 10; *it %= 10; } - if (carry > 0) digits.insert(digits.begin(), 1); + 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} @@ -1181,11 +1466,15 @@ \subsubsection{分析} 这是一个斐波那契数列。 +方法1,递归,太慢;方法2,迭代。 -\subsubsection{代码} +方法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) { @@ -1193,7 +1482,7 @@ \subsubsection{代码} int cur = 1; for(int i = 1; i <= n ; ++i){ int tmp = cur; - cur = prev + cur; + cur += prev; prev = tmp; } return cur; @@ -1201,13 +1490,15 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{数学公式} \begin{Code} // LeetCode, Climbing Stairs -// 数学公式 +// 数学公式,时间复杂度O(1),空间复杂度O(1) class Solution { public: int climbStairs(int n) { - double s = sqrt(5); + const double s = sqrt(5); return floor((pow((1+s)/2, n+1) + pow((1-s)/2, n+1))/s + 0.5); } }; @@ -1246,7 +1537,7 @@ \subsubsection{描述} \subsubsection{分析} -格雷码(Gray Code)的定义请参考 wikipedia \myurl{http://en.wikipedia.org/wiki/Gray_code}。 +格雷码(Gray Code)的定义请参考 \myurl{http://en.wikipedia.org/wiki/Gray_code}。 \textbf{自然二进制码转换为格雷码:$g_0=b_0, g_i=b_i \oplus b_{i-1}$} @@ -1260,20 +1551,20 @@ \subsubsection{分析} 这题要求生成$n$比特的所有格雷码。 -最简单的方法,利用数学公式,对从 $0\sim2^n-1$的所有整数,转化为格雷码。 +方法1,最简单的方法,利用数学公式,对从 $0\sim2^n-1$的所有整数,转化为格雷码。 -$n$比特的格雷码,可以递归地从$n-1$比特的格雷码生成。如图\S \ref{fig:gray-code-construction}所示。 +方法2,$n$比特的格雷码,可以递归地从$n-1$比特的格雷码生成。如图\S \ref{fig:gray-code-construction}所示。 \begin{center} -\includegraphics{gray-code-construction.png}\\ +\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{代码} +\subsubsection{数学公式} \begin{Code} // LeetCode, Gray Code -// 数学公式 +// 数学公式,时间复杂度O(2^n),空间复杂度O(1) class Solution { public: vector grayCode(int n) { @@ -1288,21 +1579,20 @@ \subsubsection{代码} static unsigned int binary_to_gray(unsigned int n) { return n ^ (n >> 1); } - static unsigned int gray_to_binary(unsigned int g) { - for (unsigned int mask = g >> 1; mask != 0; mask = mask >> 1) - g = g ^ mask; - return g; - } }; \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< > &matrix) { @@ -1379,9 +1669,11 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{代码2} \begin{Code} // LeetCode, Set Matrix Zeroes -// 常数空间 +// 时间复杂度O(m*n),空间复杂度O(1) class Solution { public: void setZeroes(vector > &matrix) { @@ -1453,6 +1745,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Gas Station +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: int canCompleteCircuit(vector &gas, vector &cost) { @@ -1498,33 +1791,60 @@ \subsubsection{分析} 无 -\subsubsection{代码} +\subsubsection{迭代版} \begin{Code} // LeetCode, Candy +// 时间复杂度O(n),空间复杂度O(n) class Solution { public: int candy(vector &ratings) { const int n = ratings.size(); - int result = n; vector increment(n); // 左右各扫描一遍 - for (int i = 0, inc = 1; i < n; i++) - if (i >= 1 && ratings[i] > ratings[i - 1]) + 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 - 1, inc = 1; i >= 0; i--) - if (i < n - 1 && ratings[i] > ratings[i + 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} - for (int i = 0; i < n; i++) - result += increment[i]; - return result; +\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} @@ -1551,14 +1871,15 @@ \subsubsection{分析} 异或,不仅能处理两次的情况,只要出现偶数次,都可以清零。 -\subsubsection{代码} +\subsubsection{代码1} \begin{Code} // LeetCode, Single Number +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: int singleNumber(int A[], int n) { - int x = A[0]; - for (size_t i = 1; i < n; ++i) + int x = 0; + for (size_t i = 0; i < n; ++i) x ^= A[i]; return x; } @@ -1566,6 +1887,19 @@ \subsubsection{代码} \end{Code} +\subsubsection{代码2} +\begin{Code} +// LeetCode, Single Number +// 时间复杂度O(n),空间复杂度O(1) +class Solution { +public: + int singleNumber(int A[], int n) { + return accumulate(A, A + n, 0, bit_xor()); + } +}; +\end{Code} + + \subsubsection{相关题目} \begindot \item Single Number II, 见 \S \ref{sec:single-number-ii} @@ -1586,19 +1920,19 @@ \subsubsection{描述} \subsubsection{分析} 本题和上一题 Single Number,考察的是位运算。 -方法1:创建一个长度为\fn{sizeof(int)}的数组\fn{count[sizeof(int)]},\fn{count[i]}表示所有元素的1在$i$位出现的次数。如果\fn{count[i]}是3的整数倍,则忽略;否则就把该位取出来组成答案。 +方法1:创建一个长度为\fn{sizeof(int)}的数组\fn{count[sizeof(int)]},\fn{count[i]}表示在在$i$位出现的1的次数。如果\fn{count[i]}是3的整数倍,则忽略;否则就把该位取出来组成答案。 -方法2:用\fn{ones}记录到当前处理的元素为止,二进制1出现“1次”(mod 3 之后的 1)的有哪些二进制位;用\fn{twos}记录到当前计算的变量为止,二进制1出现“2次”(mod 3 之后的 2)的有哪些二进制位。当\fn{ones}和\fn{twos}中的某一位同时为1时表示该二进制位上1出现了3次,此时需要清零。即\textbf{用二进制模拟三进制运算}。最终\fn{ones}记录的是最终结果。 +方法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{代码} +\subsubsection{代码1} \begin{Code} // LeetCode, Single Number II -// 方法1 +// 方法1,时间复杂度O(n),空间复杂度O(1) class Solution { public: int singleNumber(int A[], int n) { - const int W = sizeof(int) * 8; // 整数字长 - int count[W]; // 每个位上1出现的次数 + const int W = sizeof(int) * 8; // 一个整数的bit数,即整数字长 + int count[W]; // count[i]表示在在i位出现的1的次数 fill_n(&count[0], W, 0); for (int i = 0; i < n; i++) { for (int j = 0; j < W; j++) { @@ -1615,22 +1949,24 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{代码2} \begin{Code} // LeetCode, Single Number II -// 方法2 +// 方法2,时间复杂度O(n),空间复杂度O(1) class Solution { public: int singleNumber(int A[], int n) { - int ones = 0, twos = 0, threes = 0; + int one = 0, two = 0, three = 0; for (int i = 0; i < n; ++i) { - twos |= (ones & A[i]); - ones ^= A[i]; - threes = ~(ones & twos); - ones &= threes; - twos &= threes; + two |= (one & A[i]); + one ^= A[i]; + three = ~(one & two); + one &= three; + two &= three; } - return ones; + return one; } }; \end{Code} @@ -1673,14 +2009,15 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} -//LeetCode, Add Two Numbers -//跟Add Binary 很类似 +// LeetCode, Add Two Numbers +// 跟Add Binary 很类似 +// 时间复杂度O(m+n),空间复杂度O(1) class Solution { public: ListNode *addTwoNumbers(ListNode *l1, ListNode *l2) { - ListNode head(-1); // 头节点 + ListNode dummy(-1); // 头节点 int carry = 0; - ListNode *prev = &head; + ListNode *prev = &dummy; for (ListNode *pa = l1, *pb = l2; pa != nullptr || pb != nullptr; pa = pa == nullptr ? nullptr : pa->next, @@ -1694,7 +2031,7 @@ \subsubsection{代码} } if (carry > 0) prev->next = new ListNode(carry); - return head.next; + return dummy.next; } }; \end{Code} @@ -1731,41 +2068,28 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Reverse Linked List II +// 迭代版,时间复杂度O(n),空间复杂度O(1) class Solution { public: ListNode *reverseBetween(ListNode *head, int m, int n) { - if(m >= n) return head; - ListNode dummy(0); - ListNode *h = &dummy; - h->next = head; - - int count = 0; - ListNode *p = h, *pm, *pn; - for (; p; p = p->next) { - if (count == m-1) pm = p; - if (count == n) { - pn = p; - break; - } - count++; - } - p = pm; - pm = pm->next; - if (m == 1) head = pn; // 若m=1,则pn就变为首节点 - - p->next = pn; - p = pm->next; - pm->next = pn->next; - - ListNode *q = p->next; // pm->p->q - while(pm != pn) { - p->next = pm; - pm = p; - p = q; - if(q) q = q->next; + 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 head; + return dummy.next; } }; \end{Code} @@ -1798,24 +2122,23 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Partition List +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: ListNode* partition(ListNode* head, int x) { - if (head == nullptr) return head; - - ListNode left_dummy(0); // 头结点 - ListNode right_dummy(0); // 头结点 + ListNode left_dummy(-1); // 头结点 + ListNode right_dummy(-1); // 头结点 auto left_cur = &left_dummy; auto right_cur = &right_dummy; - for (; head; head = head->next) { - if (head->val < x) { - left_cur->next = head; - left_cur = head; + for (ListNode *cur = head; cur; cur = cur->next) { + if (cur->val < x) { + left_cur->next = cur; + left_cur = cur; } else { - right_cur->next = head; - right_cur = head; + right_cur->next = cur; + right_cur = cur; } } @@ -1853,25 +2176,51 @@ \subsubsection{分析} 无 -\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; - ListNode * prev = head; - ListNode *cur = head->next; - while (cur != nullptr) { + + for (ListNode *prev = head, *cur = head->next; cur; cur = cur->next) { if (prev->val == cur->val) { - ListNode* tmp = cur; - cur = cur->next; - prev->next = cur; - delete tmp; - continue; + prev->next = cur->next; + delete cur; } else { - prev = prev->next; - cur = cur->next; + prev = cur; } } return head; @@ -1905,9 +2254,37 @@ \subsubsection{分析} 无 -\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) { @@ -1966,6 +2343,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Remove Rotate List +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: ListNode *rotateRight(ListNode *head, int k) { @@ -2023,16 +2401,15 @@ \subsubsection{分析} \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(0); - dummy.next = head; + ListNode dummy{-1, head}; ListNode *p = &dummy, *q = &dummy; - for (int i = 0; i < n; i++) { // q先走n步 + for (int i = 0; i < n; i++) // q先走n步 q = q->next; - } while(q->next) { // 一起走 p = p->next; @@ -2074,6 +2451,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Swap Nodes in Pairs +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: ListNode *swapPairs(ListNode *head) { @@ -2096,6 +2474,7 @@ \subsubsection{代码} 下面这种写法更简洁,但题目规定了不准这样做。 \begin{Code} // LeetCode, Swap Nodes in Pairs +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: ListNode* swapPairs(ListNode* head) { @@ -2143,9 +2522,43 @@ \subsubsection{分析} 无 -\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) { @@ -2204,12 +2617,10 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Copy List with Random Pointer -// 两遍扫描 +// 两遍扫描,时间复杂度O(n),空间复杂度O(1) class Solution { public: RandomListNode *copyRandomList(RandomListNode *head) { - if (head == nullptr) return nullptr; - for (RandomListNode* cur = head; cur != nullptr; ) { RandomListNode* node = new RandomListNode(cur->label); node->next = cur->next; @@ -2224,15 +2635,15 @@ \subsubsection{代码} } // 分拆两个单链表 - RandomListNode new_head(-1); - for (RandomListNode* cur = head, *new_cur = &new_head; + 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 new_head.next; + return dummy.next; } }; \end{Code} @@ -2242,3 +2653,261 @@ \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-Cachet} + + +\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++/chapSearching.tex b/C++/chapSearching.tex index d1900349..e008d75b 100644 --- a/C++/chapSearching.tex +++ b/C++/chapSearching.tex @@ -21,10 +21,11 @@ \subsubsection{分析} 已经排好了序,用二分查找。 -\subsubsection{代码} +\subsubsection{使用STL} \begin{Code} // LeetCode, Search for a Range -// 偷懒的做法 +// 偷懒的做法,使用STL +// 时间复杂度O(logn),空间复杂度O(1) class Solution { public: vector searchRange(int A[], int n, int target) { @@ -38,9 +39,12 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{重新实现 lower_bound 和 upper_bound} \begin{Code} // LeetCode, Search for a Range // 重新实现 lower_bound 和 upper_bound +// 时间复杂度O(logn),空间复杂度O(1) class Solution { public: vector searchRange (int A[], int n, int target) { @@ -113,6 +117,7 @@ \subsubsection{代码} \begin{Code} // LeetCode, Search Insert Position // 重新实现 lower_bound +// 时间复杂度O(logn),空间复杂度O(1) class Solution { public: int searchInsert(int A[], int n, int target) { @@ -170,9 +175,11 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Search a 2D Matrix +// 时间复杂度O(logn),空间复杂度O(1) class Solution { public: bool searchMatrix(const vector>& matrix, int target) { + if (matrix.empty()) return false; const size_t m = matrix.size(); const size_t n = matrix.front().size(); diff --git a/C++/chapSorting.tex b/C++/chapSorting.tex index bad2df53..a1192eb5 100644 --- a/C++/chapSorting.tex +++ b/C++/chapSorting.tex @@ -18,6 +18,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} //LeetCode, Merge Sorted Array +// 时间复杂度O(m+n),空间复杂度O(1) class Solution { public: void merge(int A[], int m, int B[], int n) { @@ -55,22 +56,20 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} //LeetCode, Merge Two Sorted Lists +// 时间复杂度O(min(m,n)),空间复杂度O(1) class Solution { public: ListNode *mergeTwoLists(ListNode *l1, ListNode *l2) { - ListNode head(-1); - for (ListNode* p = &head; l1 != nullptr || l2 != nullptr; p = p->next) { - int val1 = l1 == nullptr ? INT_MAX : l1->val; - int val2 = l2 == nullptr ? INT_MAX : l2->val; - if (val1 <= val2) { - p->next = l1; - l1 = l1->next; - } else { - p->next = l2; - l2 = l2->next; - } + if (l1 == nullptr) return l2; + if (l2 == nullptr) return l1; + ListNode dummy(-1); + ListNode *p = &dummy; + for (; l1 != nullptr && l2 != nullptr; p = p->next) { + if (l1->val > l2->val) { p->next = l2; l2 = l2->next; } + else { p->next = l1; l1 = l1->next; } } - return head.next; + p->next = l1 != nullptr ? l1 : l2; + return dummy.next; } }; \end{Code} @@ -98,6 +97,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} //LeetCode, Merge k Sorted Lists +// 时间复杂度O(n1+n2+...),空间复杂度O(1) class Solution { public: ListNode *mergeKLists(vector &lists) { @@ -137,6 +137,118 @@ \subsubsection{相关题目} \myenddot +\section{Insertion Sort List} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:Insertion-Sort-List} + + +\subsubsection{描述} +Sort a linked list using insertion sort. + + +\subsubsection{分析} +无 + + +\subsubsection{代码} +\begin{Code} +// LeetCode, Insertion Sort List +// 时间复杂度O(n^2),空间复杂度O(1) +class Solution { +public: + ListNode *insertionSortList(ListNode *head) { + ListNode dummy(INT_MIN); + //dummy.next = head; + + for (ListNode *cur = head; cur != nullptr;) { + auto pos = findInsertPos(&dummy, cur->val); + ListNode *tmp = cur->next; + cur->next = pos->next; + pos->next = cur; + cur = tmp; + } + return dummy.next; + } + + ListNode* findInsertPos(ListNode *head, int x) { + ListNode *pre = nullptr; + for (ListNode *cur = head; cur != nullptr && cur->val <= x; + pre = cur, cur = cur->next) + ; + return pre; + } +}; +\end{Code} + + +\subsubsection{相关题目} +\begindot +\item Sort List, 见 \S \ref{sec:Sort-List} +\myenddot + + +\section{Sort List} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:Sort-List} + + +\subsubsection{描述} +Sort a linked list in $O(n log n)$ time using constant space complexity. + + +\subsubsection{分析} +常数空间且$O(nlogn)$,单链表适合用归并排序,双向链表适合用快速排序。本题可以复用 "Merge Two Sorted Lists" 的代码。 + + +\subsubsection{代码} +\begin{Code} +// LeetCode, Sort List +// 归并排序,时间复杂度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); // 后半段排序 + return mergeTwoLists(l1, l2); + } + + // Merge Two Sorted Lists + ListNode *mergeTwoLists(ListNode *l1, ListNode *l2) { + ListNode dummy(-1); + for (ListNode* p = &dummy; l1 != nullptr || l2 != nullptr; p = p->next) { + int val1 = l1 == nullptr ? INT_MAX : l1->val; + int val2 = l2 == nullptr ? INT_MAX : l2->val; + if (val1 <= val2) { + p->next = l1; + l1 = l1->next; + } else { + p->next = l2; + l2 = l2->next; + } + } + return dummy.next; + } +}; +\end{Code} + + +\subsubsection{相关题目} +\begindot +\item Insertion Sort List,见 \S \ref{sec:Insertion-Sort-List} +\myenddot + + \section{First Missing Positive} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \label{sec:first-missing-positive} @@ -158,6 +270,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, First Missing Positive +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: int firstMissingPositive(int A[], int n) { @@ -217,11 +330,11 @@ \subsubsection{分析} 第3种思路,利用快速排序里 partition 的思想,第一次将数组按0分割,第二次按1分割,排序完毕,可以推广到$n$种颜色,每种颜色有重复元素的情况。 -\subsubsection{代码} - +\subsubsection{代码1} \begin{Code} // LeetCode, Sort Colors // Counting Sort +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: void sortColors(int A[], int n) { @@ -238,9 +351,11 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{代码2} \begin{Code} // LeetCode, Sort Colors -// 双指针 +// 双指针,时间复杂度O(n),空间复杂度O(1) class Solution { public: void sortColors(int A[], int n) { @@ -259,20 +374,36 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{代码3} +\begin{Code} +// LeetCode, Sort Colors +// use partition() +// 时间复杂度O(n),空间复杂度O(1) +class Solution { +public: + void sortColors(int A[], int n) { + partition(partition(A, A + n, bind1st(equal_to(), 0)), A + n, + bind1st(equal_to(), 1)); + } +}; +\end{Code} + + +\subsubsection{代码4} \begin{Code} // LeetCode, Sort Colors -// @author 连城 (http://weibo.com/lianchengzju) -// partition +// 重新实现 partition() +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: - void sortColors(int a[], int n) { - split(split(a, a + n, bind1st(equal_to(), 0)), a + n, + void sortColors(int A[], int n) { + partition(partition(A, A + n, bind1st(equal_to(), 0)), A + n, bind1st(equal_to(), 1)); } private: - //@return pivot 的位置 template - ForwardIterator split(ForwardIterator first, ForwardIterator last, + ForwardIterator partition(ForwardIterator first, ForwardIterator last, UnaryPredicate pred) { auto pos = first; diff --git a/C++/chapStackAndQueue.tex b/C++/chapStackAndQueue.tex index ac517a8d..2315b2ef 100644 --- a/C++/chapStackAndQueue.tex +++ b/C++/chapStackAndQueue.tex @@ -21,6 +21,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Valid Parentheses +// 时间复杂度O(n),空间复杂度O(n) class Solution { public: bool isValid (string const& s) { @@ -67,10 +68,10 @@ \subsubsection{分析} 无 -\subsubsection{代码} +\subsubsection{使用栈} \begin{Code} // LeetCode, Longest Valid Parenthese -// 使用栈,复杂度 O(n) +// 使用栈,时间复杂度O(n),空间复杂度O(n) class Solution { public: int longestValidParentheses(string s) { @@ -100,6 +101,75 @@ \subsubsection{代码} }; \end{Code} +\subsubsection{Dynamic Programming, One Pass} +\begin{Code} +// LeetCode, Longest Valid Parenthese +// 时间复杂度O(n),空间复杂度O(n) +// @author 一只杰森(http://weibo.com/wjson) +class Solution { +public: + int longestValidParentheses(string s) { + vector f(s.size(), 0); + int ret = 0; + for (int i = s.size() - 2; i >= 0; --i) { + int match = i + f[i + 1] + 1; + // case: "((...))" + if (s[i] == '(' && match < s.size() && s[match] == ')') { + f[i] = f[i + 1] + 2; + // if a valid sequence exist afterwards "((...))()" + if (match + 1 < s.size()) f[i] += f[match + 1]; + } + ret = max(ret, f[i]); + } + return ret; + } +}; +\end{Code} + + +\subsubsection{两遍扫描} +\begin{Code} +// LeetCode, Longest Valid Parenthese +// 两遍扫描,时间复杂度O(n),空间复杂度O(1) +// @author 曹鹏(http://weibo.com/cpcs) +class Solution { +public: + int longestValidParentheses(string s) { + int answer = 0, depth = 0, start = -1; + for (int i = 0; i < s.size(); ++i) { + if (s[i] == '(') { + ++depth; + } else { + --depth; + if (depth < 0) { + start = i; + depth = 0; + } else if (depth == 0) { + answer = max(answer, i - start); + } + } + } + + depth = 0; + start = s.size(); + for (int i = s.size() - 1; i >= 0; --i) { + if (s[i] == ')') { + ++depth; + } else { + --depth; + if (depth < 0) { + start = i; + depth = 0; + } else if (depth == 0) { + answer = max(answer, start - i); + } + } + } + return answer; + } +}; +\end{Code} + \subsubsection{相关题目} \begindot @@ -116,12 +186,12 @@ \subsubsection{描述} Given $n$ non-negative integers representing the histogram's bar height where the width of each bar is 1, find the area of largest rectangle in the histogram. \begin{center} -\includegraphics{histogram.png}\\ +\includegraphics[width=120pt]{histogram.png}\\ \figcaption{Above is a histogram where width of each bar is 1, given height = \fn{[2,1,5,6,2,3]}.}\label{fig:histogram} \end{center} \begin{center} -\includegraphics{histogram-area.png}\\ +\includegraphics[width=120pt]{histogram-area.png}\\ \figcaption{The largest rectangle is shown in the shaded area, which has area = 10 unit.}\label{fig:histogram-area} \end{center} @@ -141,21 +211,21 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Largest Rectangle in Histogram +// 时间复杂度O(n),空间复杂度O(n) class Solution { public: int largestRectangleArea(vector &height) { stack s; height.push_back(0); int result = 0; - for (int i = 0; i < height.size(); i++) { + for (int i = 0; i < height.size(); ) { if (s.empty() || height[i] > height[s.top()]) - s.push(i); + s.push(i++); else { int tmp = s.top(); s.pop(); result = max(result, height[tmp] * (s.empty() ? i : i - s.top() - 1)); - i--; } } return result; @@ -171,5 +241,94 @@ \subsubsection{相关题目} \myenddot +\subsection{Evaluate Reverse Polish Notation} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\label{sec:Evaluate-Reverse-Polish-Notation} + + +\subsubsection{描述} +Evaluate the value of an arithmetic expression in Reverse Polish Notation. + +Valid operators are \fn{+, -, *, /}. Each operand may be an integer or another expression. + +Some examples: +\begin{Code} + ["2", "1", "+", "3", "*"] -> ((2 + 1) * 3) -> 9 + ["4", "13", "5", "/", "+"] -> (4 + (13 / 5)) -> 6 +\end{Code} + + +\subsubsection{分析} +无 + + +\subsubsection{递归版} +\begin{Code} +// LeetCode, Evaluate Reverse Polish Notation +// 递归,时间复杂度O(n),空间复杂度O(logn) +class Solution { +public: + int evalRPN(vector &tokens) { + int x, y; + auto token = tokens.back(); tokens.pop_back(); + if (is_operator(token)) { + y = evalRPN(tokens); + x = evalRPN(tokens); + if (token[0] == '+') x += y; + else if (token[0] == '-') x -= y; + else if (token[0] == '*') x *= y; + else x /= y; + } else { + size_t i; + x = stoi(token, &i); + } + return x; + } +private: + bool is_operator(const string &op) { + return op.size() == 1 && string("+-*/").find(op) != string::npos; + } +}; +\end{Code} + + +\subsubsection{迭代版} +\begin{Code} +// LeetCode, Max Points on a Line +// 迭代,时间复杂度O(n),空间复杂度O(logn) +class Solution { +public: + int evalRPN(vector &tokens) { + stack s; + for (auto token : tokens) { + if (!is_operator(token)) { + s.push(token); + } else { + int y = stoi(s.top()); + s.pop(); + int x = stoi(s.top()); + s.pop(); + if (token[0] == '+') x += y; + else if (token[0] == '-') x -= y; + else if (token[0] == '*') x *= y; + else x /= y; + s.push(to_string(x)); + } + } + return stoi(s.top()); + } +private: + bool is_operator(const string &op) { + return op.size() == 1 && string("+-*/").find(op) != string::npos; + } +}; +\end{Code} + + +\subsubsection{相关题目} +\begindot +\item 无 +\myenddot + + \section{队列} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/C++/chapString.tex b/C++/chapString.tex index 43b7c95d..b7df2026 100644 --- a/C++/chapString.tex +++ b/C++/chapString.tex @@ -25,26 +25,16 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // Leet Code, Valid Palindrome +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: bool isPalindrome(string s) { transform(s.begin(), s.end(), s.begin(), ::tolower); auto left = s.begin(), right = prev(s.end()); while (left < right) { - if (!::isalnum(*left)) { - ++left; - continue; - } - if (!::isalnum(*right)) { - --right; - continue; - } - if (*left == *right) { - ++left; - --right; - } else { - return false; - } + if (!::isalnum(*left)) ++left; + else if (!::isalnum(*right)) --right; + else if (*left != *right) return false; } return true; } @@ -54,7 +44,7 @@ \subsubsection{代码} \subsubsection{相关题目} \begindot -\item 无 +\item Palindrome Number, 见 \S \ref{sec:palindrome-number} \myenddot @@ -72,10 +62,10 @@ \subsubsection{分析} 暴力算法的复杂度是 $O(m*n)$,代码如下。更高效的的算法有KMP算法、Boyer-Mooer算法和Rabin-Karp算法。面试中暴力算法足够了,一定要写得没有BUG。 -\subsubsection{代码} +\subsubsection{暴力匹配} \begin{Code} // LeetCode, Implement strStr() -// 暴力解法,复杂度O(N*M) +// 暴力解法,时间复杂度O(N*M),空间复杂度O(1) class Solution { public: char *strStr(const char *haystack, const char *needle) { @@ -100,7 +90,75 @@ \subsubsection{代码} p1 = p1_old + 1; } - return NULL; + return nullptr; + } +}; +\end{Code} + + +\subsubsection{KMP} +\begin{Code} +// LeetCode, Implement strStr() +// KMP,时间复杂度O(N+M),空间复杂度O(M) +class Solution { +public: + char *strStr(const char *haystack, const char *needle) { + int pos = kmp(haystack, needle); + if (pos == -1) return nullptr; + else return (char*)haystack + pos; + } +private: + /* + * @brief 计算部分匹配表,即next数组. + * + * @param[in] pattern 模式串 + * @param[out] next next数组 + * @return 无 + */ + static void compute_prefix(const char *pattern, int next[]) { + int i; + int j = -1; + const int m = strlen(pattern); + + next[0] = j; + for (i = 1; i < m; i++) { + while (j > -1 && pattern[j + 1] != pattern[i]) j = next[j]; + + if (pattern[i] == pattern[j + 1]) j++; + next[i] = j; + } + } + + /* + * @brief KMP算法. + * + * @param[in] text 文本 + * @param[in] pattern 模式串 + * @return 成功则返回第一次匹配的位置,失败则返回-1 + */ + static int kmp(const char *text, const char *pattern) { + int i; + int j = -1; + const int n = strlen(text); + const int m = strlen(pattern); + if (n == 0 && m == 0) return 0; /* "","" */ + if (m == 0) return 0; /* "a","" */ + int *next = (int*)malloc(sizeof(int) * m); + + compute_prefix(pattern, next); + + for (i = 0; i < n; i++) { + while (j > -1 && pattern[j + 1] != text[i]) j = next[j]; + + if (text[i] == pattern[j + 1]) j++; + if (j == m - 1) { + free(next); + return i-j; + } + } + + free(next); + return -1; } }; \end{Code} @@ -144,15 +202,16 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, String to Integer (atoi) +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: int atoi(const char *str) { int num = 0; int sign = 1; - const int len = strlen(str); + const int n = strlen(str); int i = 0; - while (str[i] == ' ' && i < len) i++; + while (str[i] == ' ' && i < n) i++; if (str[i] == '+') i++; @@ -161,7 +220,7 @@ \subsubsection{代码} i++; } - for (; i < len; i++) { + for (; i < n; i++) { if (str[i] < '0' || str[i] > '9') break; if (num > INT_MAX / 10 || @@ -205,15 +264,16 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} //LeetCode, Add Binary +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: string addBinary(string a, string b) { string result; - const size_t max_len = a.size() > b.size() ? a.size() : b.size(); + const size_t n = a.size() > b.size() ? a.size() : b.size(); reverse(a.begin(), a.end()); reverse(b.begin(), b.end()); int carry = 0; - for (size_t i = 0; i < max_len; i++) { + for (size_t i = 0; i < n; i++) { const int ai = i < a.size() ? a[i] - '0' : 0; const int bi = i < b.size() ? b[i] - '0' : 0; const int val = (ai + bi + carry) % 2; @@ -267,11 +327,11 @@ \subsubsection{分析} 思路三: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 -// 备忘录法,TLE +// 备忘录法,会超时 +// 时间复杂度O(n^2),空间复杂度O(n^2) typedef string::const_iterator Iterator; namespace std { @@ -321,35 +381,42 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{动规} \begin{Code} // LeetCode, Longest Palindromic Substring -// 动规 +// 动规,时间复杂度O(n^2),空间复杂度O(n^2) class Solution { public: string longestPalindrome(string s) { - const int len = s.size(); - int f[len][len]; - memset(f, 0, len * len * sizeof(int)); //TODO: fill, fill_n - int maxL = 1, start = 0; // 最长回文子串的长度,起点 + const int n = s.size(); + bool f[n][n]; + fill_n(&f[0][0], n * n, false); + // 用 vector 会超时 + //vector > f(n, vector(n, false)); + size_t max_len = 1, start = 0; // 最长回文子串的长度,起点 for (size_t i = 0; i < s.size(); i++) { - f[i][i] = 1; + f[i][i] = true; for (size_t j = 0; j < i; j++) { // [j, i] f[j][i] = (s[j] == s[i] && (i - j < 2 || f[j + 1][i - 1])); - if (f[j][i] && maxL < (i - j + 1)) { - maxL = i - j + 1; + if (f[j][i] && max_len < (i - j + 1)) { + max_len = i - j + 1; start = j; } } } - return s.substr(start, maxL); + return s.substr(start, max_len); } }; \end{Code} + +\subsubsection{Manacher’s Algorithm} \begin{Code} // LeetCode, Longest Palindromic Substring // Manacher’s Algorithm +// 时间复杂度O(n),空间复杂度O(n) class Solution { public: // Transform S into T. @@ -368,10 +435,10 @@ \subsubsection{代码} string longestPalindrome(string s) { string T = preProcess(s); - int n = T.length(); + const int n = T.length(); // 以T[i]为中心,向左/右扩张的长度,不包含T[i]自己, // 因此 P[i]是源字符串中回文串的长度 - int *P = new int[n]; + int P[n]; int C = 0, R = 0; for (int i = 1; i < n - 1; i++) { @@ -392,17 +459,16 @@ \subsubsection{代码} } // Find the maximum element in P. - int maxLen = 0; - int centerIndex = 0; + int max_len = 0; + int center_index = 0; for (int i = 1; i < n - 1; i++) { - if (P[i] > maxLen) { - maxLen = P[i]; - centerIndex = i; + if (P[i] > max_len) { + max_len = P[i]; + center_index = i; } } - delete[] P; - return s.substr((centerIndex - 1 - maxLen) / 2, maxLen); + return s.substr((center_index - 1 - max_len) / 2, max_len); } }; \end{Code} @@ -447,22 +513,22 @@ \subsubsection{分析} 这是一道很有挑战的题。 -\subsubsection{代码} -递归版。 +\subsubsection{递归版} \begin{Code} // LeetCode, Regular Expression Matching -// 递归版 +// 递归版,时间复杂度O(n),空间复杂度O(1) class Solution { public: bool isMatch(const char *s, const char *p) { if (*p == '\0') return *s == '\0'; + // next char is not '*', then must match current character if (*(p + 1) != '*') { if (*p == *s || (*p == '.' && *s != '\0')) return isMatch(s + 1, p + 1); else return false; - } else { + } else { // next char is '*' while (*p == *s || (*p == '.' && *s != '\0')) { if (isMatch(s, p + 2)) return true; @@ -474,7 +540,8 @@ \subsubsection{代码} }; \end{Code} -迭代版。 + +\subsubsection{迭代版} \begin{Code} \end{Code} @@ -521,16 +588,33 @@ \subsubsection{分析} 主要是\fn{'*'}的匹配问题。\fn{p}每遇到一个\fn{'*'},就保留住当前\fn{'*'}的坐标和\fn{s}的坐标,然后\fn{s}从前往后扫描,如果不成功,则\fn{s++},重新扫描。 -\subsubsection{代码} -递归版。 +\subsubsection{递归版} \begin{Code} +// LeetCode, Wildcard Matching +// 递归版,会超时,用于帮助理解题意 +// 时间复杂度O(n!*m!),空间复杂度O(n) +class Solution { +public: + bool isMatch(const char *s, const char *p) { + if (*p == '*') { + while (*p == '*') ++p; //skip continuous '*' + if (*p == '\0') return true; + while (*s != '\0' && !isMatch(s, p)) ++s; + return *s != '\0'; + } + else if (*p == '\0' || *s == '\0') return *p == *s; + else if (*p == *s || *p == '?') return isMatch(++s, ++p); + else return false; + } +}; \end{Code} -迭代版。 + +\subsubsection{迭代版} \begin{Code} // LeetCode, Wildcard Matching -// 迭代版 +// 迭代版,时间复杂度O(n*m),空间复杂度O(1) class Solution { public: bool isMatch(const char *s, const char *p) { @@ -543,7 +627,7 @@ \subsubsection{代码} case '*': star = true; s = str, p = ptr; - while (*p == '*') p++; + while (*p == '*') p++; //skip continuous '*' if (*p == '\0') return true; str = s - 1; ptr = p - 1; @@ -583,30 +667,46 @@ \subsubsection{分析} 从位置0开始,对每一个位置比较所有字符串,直到遇到一个不匹配。 -\subsubsection{代码} +\subsubsection{纵向扫描} \begin{Code} // LeetCode, Longest Common Prefix -// 从位置0开始,对每一个位置比较所有字符串,直到遇到一个不匹配 +// 纵向扫描,从位置0开始,对每一个位置比较所有字符串,直到遇到一个不匹配 +// 时间复杂度O(n1+n2+...) +// @author 周倩 (http://weibo.com/zhouditty) class Solution { public: string longestCommonPrefix(vector &strs) { - if (strs.size() == 0) return ""; - string prefix; - int pos = 0; // 当前位置,等于 prefix 的长度 - - while (true) { - char c; - int i = 0; - for (; i < strs.size(); i++) { - if (i == 0) c = strs[0][pos]; - if (strs[i].size() == pos || c != strs[i][pos]) - break; + if (strs.empty()) return ""; + + 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); } - if (i != strs.size()) break; - ++pos; - prefix.append(1, c); } - return prefix; + return strs[0]; + } +}; +\end{Code} + + +\subsubsection{横向扫描} +\begin{Code} +// LeetCode, Longest Common Prefix +// 横向扫描,每个字符串与第0个字符串,从左到右比较,直到遇到一个不匹配, +// 然后继续下一个字符串 +// 时间复杂度O(n1+n2+...) +class Solution { +public: + string longestCommonPrefix(vector &strs) { + if (strs.empty()) return ""; + + 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::[]的文档 + right_most = j - 1; + + return strs[0].substr(0, right_most + 1); } }; \end{Code} @@ -643,11 +743,11 @@ \subsubsection{分析} 本题的功能与标准库中的\fn{strtod()}功能类似。 -\subsubsection{代码} +\subsubsection{有限自动机} \begin{Code} // LeetCode, Valid Number // @author 龚陆安 (http://weibo.com/luangong) -// finite automata +// finite automata,时间复杂度O(n),空间复杂度O(n) class Solution { public: bool isNumber(const char *s) { @@ -700,10 +800,12 @@ \subsubsection{代码} }; \end{Code} + +\subsubsection{使用strtod()} \begin{Code} // LeetCode, Valid Number // @author 连城 (http://weibo.com/lianchengzju) -// 偷懒,直接用 strtod() +// 偷懒,直接用 strtod(),时间复杂度O(n) class Solution { public: bool isNumber (char const* s) { @@ -744,40 +846,21 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Integer to Roman -// @author 连城 (http://weibo.com/lianchengzju) +// 时间复杂度O(num),空间复杂度O(1) class Solution { public: - string intToRoman (int n) { - vector> radixes { - { 1000, "M" }, - { 900, "CM" }, - { 500, "D" }, - { 400, "CD" }, - { 100, "C" }, - { 90, "XC" }, - { 50, "L" }, - { 40, "XL" }, - { 10, "X" }, - { 9, "IX" }, - { 5, "V" }, - { 4, "IV" }, - { 1, "I" } - }; + string intToRoman(int num) { + const int radix[] = {1000, 900, 500, 400, 100, 90, + 50, 40, 10, 9, 5, 4, 1}; + const string symbol[] = {"M", "CM", "D", "CD", "C", "XC", + "L", "XL", "X", "IX", "V", "IV", "I"}; string roman; - - while (n > 0) { - auto radix = radixes.begin(); - - for (; n / radix->first == 0; ++radix) - ; - - for (int i = 0; i < n / radix->first; ++i) - roman += radix->second; - - n %= radix->first; + for (size_t i = 0; num > 0; ++i) { + int count = num / radix[i]; + num %= radix[i]; + for (; count > 0; --count) roman += symbol[i]; } - return roman; } }; @@ -809,9 +892,10 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Roman to Integer +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: - inline int c2n(const char c) { + inline int map(const char c) { switch (c) { case 'I': return 1; case 'V': return 5; @@ -827,10 +911,10 @@ \subsubsection{代码} int romanToInt(string s) { int result = 0; for (size_t i = 0; i < s.size(); i++) { - if (i > 0 && c2n(s[i]) > c2n(s[i - 1])) { - result += (c2n(s[i]) - 2 * c2n(s[i - 1])); + if (i > 0 && map(s[i]) > map(s[i - 1])) { + result += (map(s[i]) - 2 * map(s[i - 1])); } else { - result += c2n(s[i]); + result += map(s[i]); } } return result; @@ -874,18 +958,19 @@ \subsubsection{代码} \begin{Code} // LeetCode, Count and Say // @author 连城 (http://weibo.com/lianchengzju) +// 时间复杂度O(n^2),空间复杂度O(n) class Solution { public: string countAndSay(int n) { string s("1"); while (--n) - s = get_next(s); + s = getNext(s); return s; } - string get_next(const string &s) { + string getNext(const string &s) { stringstream ss; for (auto i = s.begin(); i != s.end(); ) { @@ -925,6 +1010,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Anagrams +// 时间复杂度O(n),空间复杂度O(n) class Solution { public: vector anagrams(vector &strs) { @@ -936,7 +1022,7 @@ \subsubsection{代码} } vector result; - for (auto it = group.begin(); it != group.end(); it++) { + for (auto it = group.cbegin(); it != group.cend(); it++) { if (it->second.size() > 1) result.insert(result.end(), it->second.begin(), it->second.end()); } @@ -980,6 +1066,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Simplify Path +// 时间复杂度O(n),空间复杂度O(n) class Solution { public: string simplifyPath(string const& path) { @@ -1042,10 +1129,11 @@ \subsubsection{分析} 细节实现题。 -\subsubsection{代码} +\subsubsection{用 STL} \begin{Code} // LeetCode, Length of Last Word // 偷懒,用 STL +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: int lengthOfLastWord(const char *s) { @@ -1058,15 +1146,17 @@ \subsubsection{代码} \end{Code} +\subsubsection{顺序扫描} \begin{Code} // LeetCode, Length of Last Word // 顺序扫描,记录每个 word 的长度 +// 时间复杂度O(n),空间复杂度O(1) class Solution { public: int lengthOfLastWord(const char *s) { int len = 0; while (*s) { - if (*(s++) != ' ') + if (*s++ != ' ') ++len; else if (*s && *s != ' ') len = 0; diff --git a/C++/chapTree.tex b/C++/chapTree.tex index 5948f491..056d8e02 100644 --- a/C++/chapTree.tex +++ b/C++/chapTree.tex @@ -23,6 +23,357 @@ \section{二叉树的遍历} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 二叉树的先根遍历有:\textbf{先序遍历}(root->left->right),root->right->left;后根遍历有:\textbf{后序遍历}(left->right->root),right->left->root;二叉树还有个一般的树没有的遍历次序,\textbf{中序遍历}(left->root->right)。 +\subsection{Binary Tree Preorder Traversal} +\label{sec:binary-tree-preorder-traversal} + + +\subsubsection{描述} +Given a binary tree, return the \emph{preorder} traversal of its nodes' values. + +For example: +Given binary tree \code{\{1,\#,2,3\}}, +\begin{Code} + 1 + \ + 2 + / + 3 +\end{Code} +return \code{[1,2,3]}. + +Note: Recursive solution is trivial, could you do it iteratively? + + +\subsubsection{分析} +用栈或者Morris遍历。 + + +\subsubsection{栈} +\begin{Code} +// LeetCode, Binary Tree Preorder Traversal +// 使用栈,时间复杂度O(n),空间复杂度O(n) +class Solution { +public: + vector preorderTraversal(TreeNode *root) { + vector result; + const TreeNode *p; + stack s; + + p = root; + if (p != nullptr) s.push(p); + + while (!s.empty()) { + p = s.top(); + s.pop(); + result.push_back(p->val); + + if (p->right != nullptr) s.push(p->right); + if (p->left != nullptr) s.push(p->left); + } + return result; + } +}; +\end{Code} + + +\subsubsection{Morris先序遍历} +\begin{Code} +// LeetCode, Binary Tree Preorder Traversal +// Morris先序遍历,时间复杂度O(n),空间复杂度O(1) +class Solution { +public: + vector preorderTraversal(TreeNode *root) { + vector result; + TreeNode *cur, *prev; + + cur = root; + while (cur != nullptr) { + if (cur->left == nullptr) { + result.push_back(cur->val); + 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); /* 仅这一行的位置与中序不同 */ + node->right = cur; + prev = cur; /* cur刚刚被访问过 */ + cur = cur->left; + } else { /* 已经线索化,则删除线索 */ + node->right = nullptr; + /* prev = cur; 不能有这句,cur已经被访问 */ + cur = cur->right; + } + } + } + return result; + } +}; +\end{Code} + + +\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} +\myenddot + + +\subsection{Binary Tree Inorder Traversal} +\label{sec:binary-tree-inorder-traversal} + + +\subsubsection{描述} +Given a binary tree, return the \emph{inorder} traversal of its nodes' values. + +For example: +Given binary tree \code{\{1,\#,2,3\}}, +\begin{Code} + 1 + \ + 2 + / + 3 +\end{Code} +return \code{[1,3,2]}. + +Note: Recursive solution is trivial, could you do it iteratively? + + +\subsubsection{分析} +用栈或者Morris遍历。 + + +\subsubsection{栈} +\begin{Code} +// LeetCode, Binary Tree Inorder Traversal +// 使用栈,时间复杂度O(n),空间复杂度O(n) +class Solution { +public: + vector inorderTraversal(TreeNode *root) { + vector result; + const TreeNode *p = root; + stack s; + + while (!s.empty() || p != nullptr) { + if (p != nullptr) { + s.push(p); + p = p->left; + } else { + p = s.top(); + s.pop(); + result.push_back(p->val); + p = p->right; + } + } + return result; + } +}; +\end{Code} + + +\subsubsection{Morris中序遍历} +\begin{Code} +// LeetCode, Binary Tree Inorder Traversal +// Morris中序遍历,时间复杂度O(n),空间复杂度O(1) +class Solution { +public: + vector inorderTraversal(TreeNode *root) { + vector result; + TreeNode *cur, *prev; + + cur = root; + while (cur != nullptr) { + if (cur->left == nullptr) { + result.push_back(cur->val); + prev = cur; + cur = cur->right; + } else { + /* 查找前驱 */ + TreeNode *node = cur->left; + while (node->right != nullptr && node->right != cur) + node = node->right; + + if (node->right == nullptr) { /* 还没线索化,则建立线索 */ + node->right = cur; + /* prev = cur; 不能有这句,cur还没有被访问 */ + cur = cur->left; + } else { /* 已经线索化,则访问节点,并删除线索 */ + result.push_back(cur->val); + node->right = nullptr; + prev = cur; + cur = cur->right; + } + } + } + return result; + } +}; +\end{Code} + + +\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} +\myenddot + + +\subsection{Binary Tree Postorder Traversal} +\label{sec:binary-tree-postorder-traversal} + + +\subsubsection{描述} +Given a binary tree, return the \emph{postorder} traversal of its nodes' values. + +For example: +Given binary tree \code{\{1,\#,2,3\}}, +\begin{Code} + 1 + \ + 2 + / + 3 +\end{Code} +return \code{[3,2,1]}. + +Note: Recursive solution is trivial, could you do it iteratively? + + +\subsubsection{分析} +用栈或者Morris遍历。 + + +\subsubsection{栈} +\begin{Code} +// LeetCode, Binary Tree Postorder Traversal +// 使用栈,时间复杂度O(n),空间复杂度O(n) +class Solution { +public: + vector postorderTraversal(TreeNode *root) { + vector result; + /* p,正在访问的结点,q,刚刚访问过的结点*/ + const TreeNode *p, *q; + stack s; + + p = root; + + do { + while (p != nullptr) { /* 往左下走*/ + s.push(p); + p = p->left; + } + q = nullptr; + while (!s.empty()) { + p = s.top(); + s.pop(); + /* 右孩子不存在或已被访问,访问之*/ + if (p->right == q) { + result.push_back(p->val); + q = p; /* 保存刚访问过的结点*/ + } else { + /* 当前结点不能访问,需第二次进栈*/ + s.push(p); + /* 先处理右子树*/ + p = p->right; + break; + } + } + } while (!s.empty()); + + return result; + } +}; +\end{Code} + + +\subsubsection{Morris后序遍历} +\begin{Code} +// LeetCode, Binary Tree Postorder Traversal +// Morris后序遍历,时间复杂度O(n),空间复杂度O(1) +class Solution { +public: + vector postorderTraversal(TreeNode *root) { + vector result; + TreeNode dummy(-1); + TreeNode *cur, *prev = nullptr; + std::function < void(const TreeNode*)> visit = + [&result](const TreeNode *node){ + result.push_back(node->val); + }; + + dummy.left = root; + cur = &dummy; + while (cur != nullptr) { + if (cur->left == nullptr) { + prev = cur; /* 必须要有 */ + cur = cur->right; + } else { + TreeNode *node = cur->left; + while (node->right != nullptr && node->right != cur) + node = node->right; + + if (node->right == nullptr) { /* 还没线索化,则建立线索 */ + node->right = cur; + prev = cur; /* 必须要有 */ + cur = cur->left; + } else { /* 已经线索化,则访问节点,并删除线索 */ + visit_reverse(cur->left, prev, visit); + prev->right = nullptr; + prev = cur; /* 必须要有 */ + cur = cur->right; + } + } + } + return result; + } +private: + // 逆转路径 + static void reverse(TreeNode *from, TreeNode *to) { + TreeNode *x = from, *y = from->right, *z; + if (from == to) return; + + while (x != to) { + z = y->right; + y->right = x; + x = y; + y = z; + } + } + + // 访问逆转后的路径上的所有结点 + static void visit_reverse(TreeNode* from, TreeNode *to, + std::function< void(const TreeNode*) >& visit) { + TreeNode *p = to; + reverse(from, to); + + while (true) { + visit(p); + if (p == from) + break; + p = p->right; + } + + reverse(to, from); + } +}; +\end{Code} + + +\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} +\myenddot + + \subsection{Binary Tree Level Order Traversal} \label{sec:binary-tree-tevel-order-traversal} @@ -53,30 +404,57 @@ \subsubsection{分析} 无 -\subsubsection{代码} +\subsubsection{递归版} +\begin{Code} +// LeetCode, Binary Tree Level Order Traversal +// 递归版,时间复杂度O(n),空间复杂度O(n) +class Solution { +public: + vector > levelOrder(TreeNode *root) { + vector> result; + traverse(root, 1, result); + return result; + } + + void traverse(TreeNode *root, size_t level, vector> &result) { + if (!root) return; + + if (level > result.size()) + result.push_back(vector()); + + result[level-1].push_back(root->val); + traverse(root->left, level+1, result); + traverse(root->right, level+1, result); + } +}; +\end{Code} + + +\subsubsection{迭代版} \begin{Code} -//LeetCode, Binary Tree Level Order Traversal +// LeetCode, Binary Tree Level Order Traversal +// 迭代版,时间复杂度O(n),空间复杂度O(1) class Solution { public: vector > levelOrder(TreeNode *root) { vector > result; if(root == nullptr) return result; - queue current_level, next_level; + queue current, next; vector level; // elments in level level - current_level.push(root); - while (!current_level.empty()) { - while (!current_level.empty()) { - TreeNode* node = current_level.front(); - current_level.pop(); + current.push(root); + while (!current.empty()) { + while (!current.empty()) { + TreeNode* node = current.front(); + current.pop(); level.push_back(node->val); - if (node->left != nullptr) next_level.push(node->left); - if (node->right != nullptr) next_level.push(node->right); + if (node->left != nullptr) next.push(node->left); + if (node->right != nullptr) next.push(node->right); } result.push_back(level); level.clear(); - swap(next_level, current_level); //!!! how to use swap + swap(next, current); } return result; } @@ -118,45 +496,64 @@ \subsubsection{描述} \subsubsection{分析} -在Binary Tree Level Order Traversal I(见\S \ref{sec:binary-tree-tevel-order-traversal})的基础上,用一个list作为栈,每次在头部插入,就可以实现倒着输出 +在上一题(见\S \ref{sec:binary-tree-tevel-order-traversal})的基础上,\fn{reverse()}一下即可。 -\subsubsection{代码} +\subsubsection{递归版} \begin{Code} -//LeetCode, Binary Tree Level Order Traversal II -//在Binary Tree Level Order Traversal I的基础上,用一个list作为栈 -//每次在头部插入,就可以实现倒着输出 +// LeetCode, Binary Tree Level Order Traversal II +// 递归版,时间复杂度O(n),空间复杂度O(n) class Solution { public: vector > levelOrderBottom(TreeNode *root) { - list > tmp_result; + vector> result; + traverse(root, 1, result); + std::reverse(result.begin(), result.end()); // 比上一题多此一行 + return result; + } - queue q; - q.push(root); - q.push(nullptr); // level separator + void traverse(TreeNode *root, size_t level, vector> &result) { + if (!root) return; - vector level; // elements in one level - while(!q.empty()) { - TreeNode *cur = q.front(); q.pop(); + if (level > result.size()) + result.push_back(vector()); + + result[level-1].push_back(root->val); + traverse(root->left, level+1, result); + traverse(root->right, level+1, result); + } +}; +\end{Code} - if(cur) { - level.push_back(cur->val); - if(cur->left) q.push(cur->left); - if(cur->right) q.push(cur->right); - } else { - if(level.size() > 0) { - tmp_result.push_front(level); - level.clear(); - q.push(nullptr); - } - } - } - vector > ret; - for (auto e : tmp_result) { - ret.push_back(e); +\subsubsection{迭代版} +\begin{Code} +// LeetCode, Binary Tree Level Order Traversal II +// 迭代版,时间复杂度O(n),空间复杂度O(1) +class Solution { +public: + vector > levelOrderBottom(TreeNode *root) { + vector > result; + if(root == nullptr) return result; + + queue current, next; + vector level; // elments in level level + + current.push(root); + while (!current.empty()) { + while (!current.empty()) { + TreeNode* node = current.front(); + current.pop(); + level.push_back(node->val); + if (node->left != nullptr) next.push(node->left); + if (node->right != nullptr) next.push(node->right); + } + result.push_back(level); + level.clear(); + swap(next, current); } - return ret; + reverse(result.begin(), result.end()); // 比上一题多此一行 + return result; } }; \end{Code} @@ -199,10 +596,41 @@ \subsubsection{分析} 广度优先遍历,用一个bool记录是从左到右还是从右到左,每一层结束就翻转一下。 -\subsubsection{代码} +\subsubsection{递归版} +\begin{Code} +// LeetCode, Binary Tree Zigzag Level Order Traversal +// 递归版,时间复杂度O(n),空间复杂度O(n) +class Solution { +public: + vector > zigzagLevelOrder(TreeNode *root) { + vector> result; + traverse(root, 1, result, true); + return result; + } + + void traverse(TreeNode *root, size_t level, vector> &result, + bool left_to_right) { + if (!root) return; + + if (level > result.size()) + result.push_back(vector()); + + if (left_to_right) + result[level-1].push_back(root->val); + else + result[level-1].insert(result[level-1].begin(), root->val); + + traverse(root->left, level+1, result, !left_to_right); + traverse(root->right, level+1, result, !left_to_right); + } +}; +\end{Code} + +\subsubsection{迭代版} \begin{Code} //LeetCode, Binary Tree Zigzag Level Order Traversal //广度优先遍历,用一个bool记录是从左到右还是从右到左,每一层结束就翻转一下。 +// 迭代版,时间复杂度O(n),空间复杂度O(n) class Solution { public: vector > zigzagLevelOrder(TreeNode *root) { @@ -210,7 +638,7 @@ \subsubsection{代码} if (nullptr == root) return result; queue q; - bool l2r = true; //left to right + bool left_to_right = true; //left to right vector level; // one level's elements q.push(root); @@ -223,17 +651,14 @@ \subsubsection{代码} if (cur->left) q.push(cur->left); if (cur->right) q.push(cur->right); } else { - if (l2r) { + if (left_to_right) { result.push_back(level); } else { - vector tmp; - for (auto iter = level.rbegin(); iter != level.rend(); ++iter) { - tmp.push_back(*iter); - } - result.push_back(tmp); + reverse(level.begin(), level.end()); + result.push_back(level); } level.clear(); - l2r = !l2r; + left_to_right = !left_to_right; if (q.size() > 0) q.push(nullptr); } @@ -252,106 +677,6 @@ \subsubsection{相关题目} \myenddot -\subsection{Binary Tree Inorder Traversal} -\label{sec:binary-tree-inorder-traversal} - - -\subsubsection{描述} -Given a binary tree, return the inorder traversal of its nodes' values. - -For example: -Given binary tree \code{\{1,\#,2,3\}}, -\begin{Code} - 1 - \ - 2 - / - 3 -\end{Code} -return \code{[1,3,2]}. - -Note: Recursive solution is trivial, could you do it iteratively? - - -\subsubsection{分析} -不用递归,可用栈,Morris中序遍历或者线索二叉树哦。 - - -\subsubsection{代码} - -栈 -\begin{Code} -// LeetCode, Binary Tree Inorder Traversal -// 使用栈 -class Solution { -public: - vector inorderTraversal(TreeNode *root) { - vector result; - const TreeNode *p = root; - stack s; - - while (!s.empty() || p != nullptr) { - if (p != nullptr) { - s.push(p); - p = p->left; - } else { - p = s.top(); - s.pop(); - result.push_back(p->val); - p = p->right; - } - } - return result; - } -}; -\end{Code} - -Morris中序遍历 -\begin{Code} -// LeetCode, Binary Tree Inorder Traversal -// Morris中序遍历 -class Solution { -public: - vector inorderTraversal(TreeNode *root) { - vector result; - TreeNode* prev = nullptr; - TreeNode* cur = root; - - while (cur != nullptr) { - if (cur->left == nullptr) { - result.push_back(cur->val); - prev = cur; - cur = cur->right; - } else { - auto node = cur->left; - - while (node->right != nullptr && node->right != cur) - node = node->right; - - if (node->right == nullptr) { - node->right = cur; - //prev = cur; 不能有这句,因为cur还没有被访问 - cur = cur->left; - } else { - result.push_back(cur->val); - node->right = nullptr; - prev = cur; - cur = cur->right; - } - } - } - return result; - } -}; -\end{Code} - - -\subsubsection{相关题目} -\begindot -\item Recover Binary Search Tree,见 \S \ref{sec:recover-binary-search-tree} -\myenddot - - \subsection{Recover Binary Search Tree} \label{sec:recover-binary-search-tree} @@ -374,7 +699,7 @@ \subsubsection{代码} \begin{Code} // LeetCode, Recover Binary Search Tree -// Morris中序遍历 +// Morris中序遍历,时间复杂度O(n),空间复杂度O(1) class Solution { public: void recoverTree(TreeNode* root) { @@ -443,16 +768,16 @@ \subsubsection{分析} 无 -\subsubsection{代码} +\subsubsection{递归版} 递归版 \begin{Code} // LeetCode, Same Tree -// 迭代 +// 递归版,时间复杂度O(n),空间复杂度O(logn) class Solution { public: bool isSameTree(TreeNode *p, TreeNode *q) { if (!p && !q) return true; // 终止条件 - if (!p || !q) return false; // 剪纸 + if (!p || !q) return false; // 剪枝 return p->val == q->val // 三方合并 && isSameTree(p->left, q->left) && isSameTree(p->right, q->right); @@ -460,10 +785,11 @@ \subsubsection{代码} }; \end{Code} -迭代版 + +\subsubsection{迭代版} \begin{Code} // LeetCode, Same Tree -// 迭代 +// 迭代版,时间复杂度O(n),空间复杂度O(logn) class Solution { public: bool isSameTree(TreeNode *p, TreeNode *q) { @@ -511,11 +837,10 @@ \subsubsection{分析} 无 -\subsubsection{代码} -递归版 +\subsubsection{递归版} \begin{Code} // LeetCode, Symmetric Tree -// 递归版 +// 递归版,时间复杂度O(n),空间复杂度O(logn) class Solution { public: bool isSymmetric(TreeNode *root) { @@ -531,10 +856,11 @@ \subsubsection{代码} }; \end{Code} -迭代版 + +\subsubsection{迭代版} \begin{Code} // LeetCode, Symmetric Tree -// 迭代版 +// 迭代版,时间复杂度O(n),空间复杂度O(logn) class Solution { public: bool isSymmetric (TreeNode* root) { @@ -588,6 +914,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Balanced Binary Tree +// 时间复杂度O(n),空间复杂度O(logn) class Solution { public: bool isBalanced (TreeNode* root) { @@ -601,12 +928,12 @@ \subsubsection{代码} int balancedHeight (TreeNode* root) { if (root == nullptr) return 0; // 终止条件 - int lhs = balancedHeight (root->left); - int rhs = balancedHeight (root->right); + int left = balancedHeight (root->left); + int right = balancedHeight (root->right); - if (lhs < 0 || rhs < 0 || abs(lhs - rhs) > 1) return -1; // 剪枝 + if (left < 0 || right < 0 || abs(left - right) > 1) return -1; // 剪枝 - return max(lhs, rhs) + 1; // 三方合并 + return max(left, right) + 1; // 三方合并 } }; \end{Code} @@ -653,62 +980,59 @@ \subsubsection{分析} 无 -\subsubsection{代码} -递归版 +\subsubsection{递归版1} \begin{Code} // LeetCode, Flatten Binary Tree to Linked List -// 递归版1,作者:王顺达,http://weibo.com/u/1234984145 +// 递归版1,时间复杂度O(n),空间复杂度O(logn) class Solution { public: void flatten(TreeNode *root) { - flatten(root, NULL); - } -private: - // 把root所代表树变成链表后,tail跟在该链表后面 - TreeNode *flatten(TreeNode *root, TreeNode *tail) { - if (NULL == root) return tail; + if (root == nullptr) return; // 终止条件 - root->right = flatten(root->left, flatten(root->right, tail)); - root->left = NULL; - return root; + flatten(root->left); + flatten(root->right); + + if (nullptr == root->left) return; + + // 三方合并,将左子树所形成的链表插入到root和root->right之间 + TreeNode *p = root->left; + while(p->right) p = p->right; //寻找左链表最后一个节点 + p->right = root->right; + root->right = root->left; + root->left = nullptr; } }; \end{Code} + +\subsubsection{递归版2} \begin{Code} // LeetCode, Flatten Binary Tree to Linked List // 递归版2 +// @author 王顺达(http://weibo.com/u/1234984145) +// 时间复杂度O(n),空间复杂度O(logn) class Solution { public: void flatten(TreeNode *root) { - if (root == nullptr) return; // 终止条件 - //1.flat the left subtree - if (root->left) flatten(root->left); - - //2.flatten the right subtree - if (root->right) flatten(root->right); - - //3.if no left return - if (nullptr == root->left) return; + flatten(root, NULL); + } +private: + // 把root所代表树变成链表后,tail跟在该链表后面 + TreeNode *flatten(TreeNode *root, TreeNode *tail) { + if (NULL == root) return tail; - //4.insert left sub tree between root and root->right - //4.1.find the last node in left - TreeNode *p = root->left; - while(p->right) p = p->right; - p->right = root->right; - //4.2.connect right sub tree after left sub tree - p->right = root->right; - //4.3.move left sub tree to the root's right sub tree - root->right = root->left; - root->left = nullptr; + root->right = flatten(root->left, flatten(root->right, tail)); + root->left = NULL; + return root; } }; \end{Code} -迭代版 + +\subsubsection{迭代版} \begin{Code} // LeetCode, Flatten Binary Tree to Linked List -// 迭代版 +// 迭代版,时间复杂度O(n),空间复杂度O(logn) class Solution { public: void flatten(TreeNode* root) { @@ -773,39 +1097,61 @@ \subsubsection{描述} \subsubsection{分析} -要处理一个节点,可能需要最右边的兄弟节点,因此用广搜。 +要处理一个节点,可能需要最右边的兄弟节点,首先想到用广搜。但广搜不是常数空间的,本题要求常数空间。 -同时注意,这题的代码原封不动,可以解决上一题! +注意,这题的代码原封不动,也可以解决 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) class Solution { public: void connect(TreeLinkNode *root) { - vector > result; - if (root == nullptr) - return; + if (root == nullptr) return; + + TreeLinkNode dummy(-1); + for (TreeLinkNode *curr = root, *prev = &dummy; + curr; curr = curr->next) { + if (curr->left != nullptr){ + prev->next = curr->left; + prev = prev->next; + } + if (curr->right != nullptr){ + prev->next = curr->right; + prev = prev->next; + } + } + connect(dummy.next); + } +}; +\end{Code} - vector nextLevel, currentLevel; - currentLevel.push_back(root); - while (!currentLevel.empty()) { - while (!currentLevel.empty()) { - TreeLinkNode* node = currentLevel.front(); - currentLevel.erase(currentLevel.begin()); - - if (!currentLevel.empty()) - node->next = currentLevel.front(); - else - node->next = nullptr; - - if (node->left != nullptr) - nextLevel.push_back(node->left); - if (node->right != nullptr) - nextLevel.push_back(node->right); + +\subsubsection{迭代版} +\begin{Code} +// LeetCode, Populating Next Right Pointers in Each Node II +// 时间复杂度O(n),空间复杂度O(1) +class Solution { +public: + void connect(TreeLinkNode *root) { + while (root) { + TreeLinkNode * next = nullptr; // the first node of next level + TreeLinkNode * prev = nullptr; // previous node on the same level + for (; root; root = root->next) { + if (!next) next = root->left ? root->left : root->right; + + if (root->left) { + if (prev) prev->next = root->left; + prev = root->left; + } + if (root->right) { + if (prev) prev->next = root->right; + prev = root->right; + } } - swap(nextLevel, currentLevel); //!!! how to use swap + root = next; // turn to next level } } }; @@ -839,6 +1185,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Construct Binary Tree from Preorder and Inorder Traversal +// 递归,时间复杂度O(n),空间复杂度O(\logn) class Solution { public: TreeNode* buildTree(vector& preorder, vector& inorder) { @@ -892,6 +1239,7 @@ \subsubsection{分析} \subsubsection{代码} \begin{Code} // LeetCode, Construct Binary Tree from Inorder and Postorder Traversal +// 递归,时间复杂度O(n),空间复杂度O(\logn) class Solution { public: TreeNode* buildTree(vector& inorder, vector& postorder) { @@ -999,6 +1347,7 @@ \subsubsection{代码} \begin{Code} // LeetCode, Unique Binary Search Trees +// 时间复杂度O(n^2),空间复杂度O(n) class Solution { public: int numTrees(int n) { @@ -1049,6 +1398,7 @@ \subsubsection{代码} \begin{Code} // LeetCode, Unique Binary Search Trees II +// 时间复杂度TODO,空间复杂度TODO class Solution { public: vector generateTrees(int n) { @@ -1108,6 +1458,7 @@ \subsubsection{代码} \begin{Code} // LeetCode, Validate Binary Search Tree +// 时间复杂度O(n),空间复杂度O(\logn) class Solution { public: bool isValidBST(TreeNode* root) { @@ -1147,7 +1498,7 @@ \subsubsection{代码} \begin{Code} // LeetCode, Convert Sorted Array to Binary Search Tree -// 分治法 +// 分治法,时间复杂度O(n),空间复杂度O(logn) class Solution { public: TreeNode* sortedArrayToBST (vector& num) { @@ -1159,8 +1510,7 @@ \subsubsection{代码} RandomAccessIterator last) { const auto length = distance(first, last); - if (length == 0) return nullptr; // 终止条件 - if (length == 1) return new TreeNode(*first); // 收敛条件 + if (length <= 0) return nullptr; // 终止条件 // 三方合并 auto mid = first + length / 2; @@ -1193,13 +1543,13 @@ \subsubsection{分析} 存在一种自底向上(bottom-up)的方法,见\myurl{http://leetcode.com/2010/11/convert-sorted-list-to-balanced-binary.html} -\subsubsection{代码} +\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(nlogn) +// 自顶向下,时间复杂度O(n^2),空间复杂度O(logn) class Solution { public: TreeNode* sortedListToBST (ListNode* head) { @@ -1238,10 +1588,11 @@ \subsubsection{代码} }; \end{Code} -自底向上,复杂度$O(n)$。 + +\subsubsection{自底向上} \begin{Code} // LeetCode, Convert Sorted List to Binary Search Tree -// bottom-up,复杂度O(nlogn) +// bottom-up,时间复杂度O(n),空间复杂度O(logn) class Solution { public: TreeNode *sortedListToBST(ListNode *head) { @@ -1296,43 +1647,31 @@ \subsubsection{描述} \subsubsection{分析} 无 -\subsubsection{代码} -递归版 +\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; - else { - d = INT_MAX; - minDepth(root, 1); - return d; - } + int minDepth(const TreeNode *root) { + return minDepth(root, false); } private: - int d; - void minDepth(TreeNode *root, int cur) { - if (root != nullptr) { - if (root->left == nullptr && root->right == nullptr) { // 叶子节点 - d = min(cur, d); - return; - } - if (cur < d) { // 剪枝 - minDepth(root->left, cur+1); - minDepth(root->right, cur+1); - } - } + 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) { @@ -1387,14 +1726,13 @@ \subsubsection{代码} \begin{Code} // LeetCode, Maximum Depth of Binary Tree +// 时间复杂度O(n),空间复杂度O(logn) class Solution { public: int maxDepth(TreeNode *root) { if (root == nullptr) return 0; - int lmax = maxDepth(root->left); - int rmax = maxDepth(root->right); - return max(lmax, rmax) + 1; + return max(maxDepth(root->left), maxDepth(root->right)) + 1; } }; \end{Code} @@ -1438,17 +1776,17 @@ \subsubsection{代码} \begin{Code} // LeetCode, Path Sum +// 时间复杂度O(n),空间复杂度O(logn) class Solution { public: bool hasPathSum(TreeNode *root, int sum) { if (root == nullptr) return false; - if (root->left == nullptr && root->right == nullptr) { // leaf - if (sum == root->val) return true; - else return false; - } - if (hasPathSum(root->left, sum - root->val)) return true; - if (hasPathSum(root->right, sum - root->val)) return true; + if (root->left == nullptr && root->right == nullptr) // leaf + return sum == root->val; + + return hasPathSum(root->left, sum - root->val) + || hasPathSum(root->right, sum - root->val); } }; \end{Code} @@ -1494,6 +1832,7 @@ \subsubsection{代码} \begin{Code} // LeetCode, Path Sum II +// 时间复杂度O(n),空间复杂度O(logn) class Solution { public: vector > pathSum(TreeNode *root, int sum) { @@ -1510,9 +1849,8 @@ \subsubsection{代码} cur.push_back(root->val); if (root->left == nullptr && root->right == nullptr) { // leaf - if (gap == root->val) { + if (gap == root->val) result.push_back(cur); - } } pathSum(root->left, gap - root->val, cur, result); pathSum(root->right, gap - root->val, cur, result); @@ -1558,6 +1896,7 @@ \subsubsection{代码} \begin{Code} // LeetCode, Binary Tree Maximum Path Sum +// 时间复杂度O(n),空间复杂度O(logn) class Solution { public: int maxPathSum(TreeNode *root) { @@ -1634,12 +1973,13 @@ \subsubsection{描述} \subsubsection{分析} - +无 \subsubsection{代码} \begin{Code} // LeetCode, Populating Next Right Pointers in Each Node +// 时间复杂度O(n),空间复杂度O(logn) class Solution { public: void connect(TreeLinkNode *root) { @@ -1699,6 +2039,7 @@ \subsubsection{代码} \begin{Code} // LeetCode, Decode Ways +// 时间复杂度O(n),空间复杂度O(logn) class Solution { public: int sumNumbers(TreeNode *root) { diff --git a/C++/chapTrick.tex b/C++/chapTrick.tex index ebe716d1..c83c26ee 100644 --- a/C++/chapTrick.tex +++ b/C++/chapTrick.tex @@ -2,6 +2,10 @@ \chapter{编程技巧} 在判断两个浮点数a和b是否相等时,不要用\fn{a==b},应该判断二者之差的绝对值\fn{fabs(a-b)}是否小于某个阀值,例如\fn{1e-9}。 +判断一个整数是否是为奇数,用\fn{x \% 2 != 0},不要用\fn{x \% 2 == 1},因为x可能是负数。 + +用\fn{char}的值作为数组下标(例如,统计字符串中每个字符出现的次数),要考虑到\fn{char}可能是负数。有的人考虑到了,先强制转型为\fn{unsigned int}再用作下标,这仍然是错的。正确的做法是,先强制转型为\fn{unsigned char},再用作下标。这涉及C++整型提升的规则,就不详述了。 + 以下是关于STL使用技巧的,很多条款来自《Effective STL》这本书。 \subsubsection{vector和string优先于动态分配的数组} diff --git a/C++/format.cls b/C++/format.cls index e5ffee72..62a6323e 100644 --- a/C++/format.cls +++ b/C++/format.cls @@ -52,8 +52,8 @@ body={390pt,530pt},marginparsep=10pt,marginpar=50pt]{geometry} \setlength\abovecaptionskip{0pt} -%\setmainfont{Times New Roman} -\setmainfont{Linux Libertine O} +\setmainfont{Times New Roman} +%\setmainfont{Linux Libertine} %\setmainfont{TeX Gyre Pagella} \newfontfamily\urlfont{PT Sans Narrow} %\setmonofont[AutoFakeBold=1.6,AutoFakeSlant=0.17,Mapping=tex-text-tt]{Inconsolata} diff --git a/C++/leetcode-cpp.tex b/C++/leetcode-cpp.tex index 890c5776..903a4018 100644 --- a/C++/leetcode-cpp.tex +++ b/C++/leetcode-cpp.tex @@ -48,6 +48,7 @@ \include{chapDivideAndConquer} \include{chapGreedy} \include{chapDynamicProgramming} +\include{chapGraph} \include{chapImplement} \appendix % 开始附录,章用字母编号 diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..cf29599a --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ +Copyright (c) 2013, soulmachine and The Contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + + * Neither the name of the soulmachine nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git "a/\345\217\202\350\200\203\350\265\204\346\226\231/leetcode Single Number II - \344\275\215\350\277\220\347\256\227\345\244\204\347\220\206\346\225\260\347\273\204\344\270\255\347\232\204\346\225\260 - \344\273\243\351\207\221\346\241\245 - \345\215\232\345\256\242\345\233\255.pdf" "b/\345\217\202\350\200\203\350\265\204\346\226\231/leetcode Single Number II - \344\275\215\350\277\220\347\256\227\345\244\204\347\220\206\346\225\260\347\273\204\344\270\255\347\232\204\346\225\260 - \344\273\243\351\207\221\346\241\245 - \345\215\232\345\256\242\345\233\255.pdf" new file mode 100644 index 00000000..4e528219 Binary files /dev/null and "b/\345\217\202\350\200\203\350\265\204\346\226\231/leetcode Single Number II - \344\275\215\350\277\220\347\256\227\345\244\204\347\220\206\346\225\260\347\273\204\344\270\255\347\232\204\346\225\260 - \344\273\243\351\207\221\346\241\245 - \345\215\232\345\256\242\345\233\255.pdf" differ