diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 00000000..ccd81dff
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,7 @@
+{
+ "name": "TeX Live",
+ "image": "soulmachine/texlive:latest",
+ "extensions": [
+ "James-Yu.latex-workshop"
+ ]
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..1ed19897
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+*.log
+*.toc
+*.aux
+*.idx
+*.out
+*.synctex.gz
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 00000000..72ac5ac3
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,31 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "lettcode-C++",
+ "type": "shell",
+ "command": "xelatex",
+ "args": [
+ "-synctex=1",
+ "-interaction=nonstopmode",
+ "leetcode-cpp.tex"
+ ],
+ "options": {
+ "cwd": "${workspaceFolder}/C++/"
+ }
+ },
+ {
+ "label": "lettcode-Java",
+ "type": "shell",
+ "command": "xelatex",
+ "args": [
+ "-synctex=1",
+ "-interaction=nonstopmode",
+ "leetcode-java.tex"
+ ],
+ "options": {
+ "cwd": "${workspaceFolder}/Java/"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/C++/.DS_Store b/C++/.DS_Store
new file mode 100644
index 00000000..cd3a2cf8
Binary files /dev/null and b/C++/.DS_Store differ
diff --git "a/C++/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"
deleted file mode 100644
index a690e7be..00000000
Binary files "a/C++/LeetCodet\351\242\230\350\247\243(C++\347\211\210).pdf" and /dev/null differ
diff --git a/C++/README.md b/C++/README.md
index 6894ad25..a856af82 100644
--- a/C++/README.md
+++ b/C++/README.md
@@ -1,5 +1,7 @@
-#C++版
------------------
-**下载**:LeetCode题解(C++版).pdf
+# C++版
-书的内容与Java版一摸一样,不过代码是用C++写的。本书的代码使用 C++ 11 标准。
+## 编译
+
+```bash
+docker run -it --rm -v $(pwd):/project -w /project soulmachine/texlive xelatex -interaction=nonstopmode leetcode-cpp.tex
+````
diff --git a/C++/chapBFS.tex b/C++/chapBFS.tex
index 17ff93f0..826d7c4a 100644
--- a/C++/chapBFS.tex
+++ b/C++/chapBFS.tex
@@ -36,57 +36,160 @@ \subsubsection{描述}
\subsubsection{分析}
+求最短路径,用广搜。
-\subsubsection{代码}
+\subsubsection{单队列}
\begin{Code}
//LeetCode, Word Ladder
+// 时间复杂度O(n),空间复杂度O(n)
+struct state_t {
+ string word;
+ int level;
+
+ state_t() { word = ""; level = 0; }
+ state_t(const string& word, int level) {
+ this->word = word;
+ this->level = level;
+ }
+
+ bool operator==(const state_t &other) const {
+ return this->word == other.word;
+ }
+};
+
+namespace std {
+ template<> struct hash {
+ public:
+ size_t operator()(const state_t& s) const {
+ return str_hash(s.word);
+ }
+ private:
+ std::hash str_hash;
+ };
+}
+
+
class Solution {
public:
- 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 q;
+ unordered_set visited; // 判重
+
+ auto state_is_valid = [&](const state_t& s) {
+ return dict.find(s.word) != dict.end() || s.word == end;
+ };
+ auto state_is_target = [&](const state_t &s) {return s.word == end; };
+ auto state_extend = [&](const state_t &s) {
+ unordered_set result;
+
+ for (size_t i = 0; i < s.word.size(); ++i) {
+ state_t new_state(s.word, s.level + 1);
+ for (char c = 'a'; c <= 'z'; c++) {
+ // 防止同字母替换
+ if (c == new_state.word[i]) continue;
+
+ swap(c, new_state.word[i]);
+
+ if (state_is_valid(new_state) &&
+ visited.find(new_state) == visited.end()) {
+ result.insert(new_state);
+ }
+ swap(c, new_state.word[i]); // 恢复该单词
+ }
+ }
- queue next, current; // 当前层,下一层
- unordered_set visited; // 判重
- unordered_map father;
- int level = 0; // 层次
- bool found = false;
+ return result;
+ };
+
+ state_t start_state(start, 0);
+ q.push(start_state);
+ visited.insert(start_state);
+ while (!q.empty()) {
+ // 千万不能用 const auto&,pop() 会删除元素,
+ // 引用就变成了悬空引用
+ const auto state = q.front();
+ q.pop();
+
+ if (state_is_target(state)) {
+ return state.level + 1;
+ }
+
+ const auto& new_states = state_extend(state);
+ for (const auto& new_state : new_states) {
+ q.push(new_state);
+ visited.insert(new_state);
+ }
+ }
+ return 0;
+ }
+};
+\end{Code}
+
+
+\subsubsection{双队列}
+\begin{Code}
+//LeetCode, Word Ladder
+// 时间复杂度O(n),空间复杂度O(n)
+class Solution {
+public:
+ int ladderLength(const string& start, const string &end,
+ const unordered_set &dict) {
+ queue current, next; // 当前层,下一层
+ unordered_set visited; // 判重
+
+ int level = -1; // 层次
+
+ auto state_is_valid = [&](const string& s) {
+ return dict.find(s) != dict.end() || s == end;
+ };
+ auto state_is_target = [&](const string &s) {return s == end;};
+ auto state_extend = [&](const string &s) {
+ unordered_set result;
+
+ 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 (state_is_valid(new_word) &&
+ visited.find(new_word) == visited.end()) {
+ result.insert(new_word);
+ }
+ swap(c, new_word[i]); // 恢复该单词
+ }
+ }
+
+ return result;
+ };
current.push(start);
- while (!current.empty() && !found) {
+ visited.insert(start);
+ while (!current.empty()) {
++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;
- }
+ while (!current.empty()) {
+ // 千万不能用 const auto&,pop() 会删除元素,
+ // 引用就变成了悬空引用
+ const auto state = current.front();
+ current.pop();
+
+ if (state_is_target(state)) {
+ return level + 1;
+ }
- 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 auto& new_states = state_extend(state);
+ for (const auto& new_state : new_states) {
+ next.push(new_state);
+ visited.insert(new_state);
}
}
- swap(next, current); //!!! 交换两个队列
+ swap(next, current);
}
- if (found) return level+1;
- else return 0;
+ return 0;
}
};
\end{Code}
@@ -134,73 +237,257 @@ \subsubsection{描述}
\subsubsection{分析}
跟 Word Ladder比,这题是求路径本身,不是路径长度,也是BFS,略微麻烦点。
-这题跟普通的广搜有很大的不同,就是要输出所有路径,因此在记录前驱和判重地方与普通广搜略有不同。
+求一条路径和求所有路径有很大的不同,求一条路径,每个状态节点只需要记录一个前驱即可;求所有路径时,有的状态节点可能有多个父节点,即要记录多个前驱。
+如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的处理,因为我们要找的是最短路径。
-\subsubsection{代码}
+
+\subsubsection{单队列}
\begin{Code}
//LeetCode, Word Ladder II
+// 时间复杂度O(n),空间复杂度O(n)
+struct state_t {
+ string word;
+ int level;
+
+ state_t() { word = ""; level = 0; }
+ state_t(const string& word, int level) {
+ this->word = word;
+ this->level = level;
+ }
+
+ bool operator==(const state_t &other) const {
+ return this->word == other.word;
+ }
+};
+
+namespace std {
+ template<> struct hash {
+ public:
+ size_t operator()(const state_t& s) const {
+ return str_hash(s.word);
+ }
+ private:
+ std::hash str_hash;
+ };
+}
+
+
class Solution {
public:
- vector > findLadders(string start, string end,
- const unordered_set &dict) {
+ vector > findLadders(const string& start,
+ const string& end, const unordered_set &dict) {
+ queue q;
+ unordered_set visited; // 判重
+ unordered_map > father; // DAG
+
+ auto state_is_valid = [&](const state_t& s) {
+ return dict.find(s.word) != dict.end() || s.word == end;
+ };
+ auto state_is_target = [&](const state_t &s) {return s.word == end; };
+ auto state_extend = [&](const state_t &s) {
+ unordered_set result;
+
+ for (size_t i = 0; i < s.word.size(); ++i) {
+ state_t new_state(s.word, s.level + 1);
+ for (char c = 'a'; c <= 'z'; c++) {
+ // 防止同字母替换
+ if (c == new_state.word[i]) continue;
+
+ swap(c, new_state.word[i]);
+
+ if (state_is_valid(new_state)) {
+ auto visited_iter = visited.find(new_state);
+
+ if (visited_iter != visited.end()) {
+ if (visited_iter->level < new_state.level) {
+ // do nothing
+ } else if (visited_iter->level == new_state.level) {
+ result.insert(new_state);
+ } else { // not possible
+ throw std::logic_error("not possible to get here");
+ }
+ } else {
+ result.insert(new_state);
+ }
+ }
+ swap(c, new_state.word[i]); // 恢复该单词
+ }
+ }
+
+ return result;
+ };
+
+ vector> result;
+ state_t start_state(start, 0);
+ q.push(start_state);
+ visited.insert(start_state);
+ while (!q.empty()) {
+ // 千万不能用 const auto&,pop() 会删除元素,
+ // 引用就变成了悬空引用
+ const auto state = q.front();
+ q.pop();
+
+ // 如果当前路径长度已经超过当前最短路径长度,
+ // 可以中止对该路径的处理,因为我们要找的是最短路径
+ if (!result.empty() && state.level + 1 > result[0].size()) break;
+
+ if (state_is_target(state)) {
+ vector path;
+ gen_path(father, start_state, state, path, result);
+ continue;
+ }
+ // 必须挪到下面,比如同一层A和B两个节点均指向了目标节点,
+ // 那么目标节点就会在q中出现两次,输出路径就会翻倍
+ // visited.insert(state);
+
+ // 扩展节点
+ const auto& new_states = state_extend(state);
+ for (const auto& new_state : new_states) {
+ if (visited.find(new_state) == visited.end()) {
+ q.push(new_state);
+ }
+ visited.insert(new_state);
+ father[new_state].push_back(state);
+ }
+ }
+
+ return result;
+ }
+private:
+ void gen_path(unordered_map > &father,
+ const state_t &start, const state_t &state, vector &path,
+ vector > &result) {
+ path.push_back(state.word);
+ if (state == start) {
+ if (!result.empty()) {
+ if (path.size() < result[0].size()) {
+ result.clear();
+ result.push_back(path);
+ reverse(result.back().begin(), result.back().end());
+ } else if (path.size() == result[0].size()) {
+ result.push_back(path);
+ reverse(result.back().begin(), result.back().end());
+ } else { // not possible
+ throw std::logic_error("not possible to get here ");
+ }
+ } else {
+ result.push_back(path);
+ reverse(result.back().begin(), result.back().end());
+ }
+
+ } else {
+ for (const auto& f : father[state]) {
+ gen_path(father, start, f, path, result);
+ }
+ }
+ path.pop_back();
+ }
+};
+\end{Code}
+
+
+\subsubsection{双队列}
+
+\begin{Code}
+//LeetCode, Word Ladder II
+// 时间复杂度O(n),空间复杂度O(n)
+class Solution {
+public:
+ vector > findLadders(const string& start,
+ const string& end, const unordered_set &dict) {
+ // 当前层,下一层,用unordered_set是为了去重,例如两个父节点指向
+ // 同一个子节点,如果用vector, 子节点就会在next里出现两次,其实此
+ // 时 father 已经记录了两个父节点,next里重复出现两次是没必要的
+ unordered_set current, next;
unordered_set visited; // 判重
- unordered_map > father; // 树
- unordered_set current, next; // 当前层,下一层,用集合是为了去重
+ unordered_map > father; // DAG
- int level = 0; // 层数
- bool found = false;
+ int level = -1; // 层次
- current.insert(start);
- while (!current.empty() && !found) {
- ++level;
- // 先将本层全部置为已访问,防止同层之间互相指向
- for (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)移动到最上面了
- }
+ auto state_is_valid = [&](const string& s) {
+ return dict.find(s) != dict.end() || s == end;
+ };
+ auto state_is_target = [&](const string &s) {return s == end;};
+ auto state_extend = [&](const string &s) {
+ unordered_set result;
+
+ 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]); // restore
+ swap(c, new_word[i]);
+
+ if (state_is_valid(new_word) &&
+ visited.find(new_word) == visited.end()) {
+ result.insert(new_word);
}
+ swap(c, new_word[i]); // 恢复该单词
+ }
+ }
+
+ return result;
+ };
+
+ vector > result;
+ current.insert(start);
+ while (!current.empty()) {
+ ++ level;
+ // 如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的
+ // 处理,因为我们要找的是最短路径
+ if (!result.empty() && level+1 > result[0].size()) break;
+
+ // 1. 延迟加入visited, 这样才能允许两个父节点指向同一个子节点
+ // 2. 一股脑current 全部加入visited, 是防止本层前一个节点扩展
+ // 节点时,指向了本层后面尚未处理的节点,这条路径必然不是最短的
+ for (const auto& state : current)
+ visited.insert(state);
+ for (const auto& state : current) {
+ if (state_is_target(state)) {
+ vector path;
+ gen_path(father, path, start, state, result);
+ continue;
+ }
+
+ const auto new_states = state_extend(state);
+ for (const auto& new_state : new_states) {
+ next.insert(new_state);
+ father[new_state].push_back(state);
}
}
current.clear();
swap(current, next);
}
- vector > result;
- if (found) {
- vector path;
- buildPath(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);
if (word == start) {
- result.push_back(path);
+ if (!result.empty()) {
+ if (path.size() < result[0].size()) {
+ result.clear();
+ result.push_back(path);
+ } else if(path.size() == result[0].size()) {
+ result.push_back(path);
+ } else {
+ // not possible
+ throw std::logic_error("not possible to get here");
+ }
+ } else {
+ result.push_back(path);
+ }
reverse(result.back().begin(), result.back().end());
} else {
- for (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();
@@ -209,6 +496,139 @@ \subsubsection{代码}
\end{Code}
+\subsubsection{图的广搜}
+
+本题还可以看做是图上的广搜。给定了字典 \fn{dict},可以基于它画出一个无向图,表示单词之间可以互相转换。本题的本质就是已知起点和终点,在图上找出所有最短路径。
+
+\begin{Code}
+//LeetCode, Word Ladder II
+// 时间复杂度O(n),空间复杂度O(n)
+class Solution {
+public:
+ vector > findLadders(const string& start,
+ const string &end, const unordered_set &dict) {
+ const auto& g = build_graph(dict);
+ vector pool;
+ queue q; // 未处理的节点
+ // value 是所在层次
+ unordered_map visited;
+
+ auto state_is_target = [&](const state_t *s) {return s->word == end; };
+
+ vector> result;
+ q.push(create_state(nullptr, start, 0, pool));
+ while (!q.empty()) {
+ state_t* state = q.front();
+ q.pop();
+
+ // 如果当前路径长度已经超过当前最短路径长度,
+ // 可以中止对该路径的处理,因为我们要找的是最短路径
+ if (!result.empty() && state->level+1 > result[0].size()) break;
+
+ if (state_is_target(state)) {
+ const auto& path = gen_path(state);
+ if (result.empty()) {
+ result.push_back(path);
+ } else {
+ if (path.size() < result[0].size()) {
+ result.clear();
+ result.push_back(path);
+ } else if (path.size() == result[0].size()) {
+ result.push_back(path);
+ } else {
+ // not possible
+ throw std::logic_error("not possible to get here");
+ }
+ }
+ continue;
+ }
+ visited[state->word] = state->level;
+
+ // 扩展节点
+ auto iter = g.find(state->word);
+ if (iter == g.end()) continue;
+
+ for (const auto& neighbor : iter->second) {
+ auto visited_iter = visited.find(neighbor);
+
+ if (visited_iter != visited.end() &&
+ visited_iter->second < state->level + 1) {
+ continue;
+ }
+
+ q.push(create_state(state, neighbor, state->level + 1, pool));
+ }
+ }
+
+ // release all states
+ for (auto state : pool) {
+ delete state;
+ }
+ return result;
+ }
+
+private:
+ struct state_t {
+ state_t* father;
+ string word;
+ int level; // 所在层次,从0开始编号
+
+ state_t(state_t* father_, const string& word_, int level_) :
+ father(father_), word(word_), level(level_) {}
+ };
+
+ state_t* create_state(state_t* parent, const string& value,
+ int length, vector& pool) {
+ state_t* node = new state_t(parent, value, length);
+ pool.push_back(node);
+
+ return node;
+ }
+ vector gen_path(const state_t* node) {
+ vector path;
+
+ while(node != nullptr) {
+ path.push_back(node->word);
+ node = node->father;
+ }
+
+ reverse(path.begin(), path.end());
+ return path;
+ }
+
+ unordered_map > build_graph(
+ const unordered_set& dict) {
+ unordered_map > adjacency_list;
+
+ for (const auto& word : dict) {
+ for (size_t i = 0; i < word.size(); ++i) {
+ string new_word(word);
+ for (char c = 'a'; c <= 'z'; c++) {
+ // 防止同字母替换
+ if (c == new_word[i]) continue;
+
+ swap(c, new_word[i]);
+
+ if ((dict.find(new_word) != dict.end())) {
+ auto iter = adjacency_list.find(word);
+ if (iter != adjacency_list.end()) {
+ iter->second.insert(new_word);
+ } else {
+ adjacency_list.insert(pair>(word, unordered_set()));
+ adjacency_list[word].insert(new_word);
+ }
+ }
+ swap(c, new_word[i]); // 恢复该单词
+ }
+ }
+ }
+ return adjacency_list;
+ }
+};
+\end{Code}
+
+
\subsubsection{相关题目}
\begindot
@@ -243,17 +663,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 +685,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 +694,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 state_is_valid = [&](const state_t &s) {
+ const int x = s.first;
+ const int y = s.second;
+ if (x < 0 || x >= m || y < 0 || y >= n || board[x][y] != 'O')
+ 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 (state_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 (state_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,11 +753,10 @@ \section{小结} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{适用场景}
-注意,这里的总结是一种经验,一种概率,不是绝对的结论!
\textbf{输入数据}:没什么特征,不像深搜,需要有“递归”的性质。如果是树或者图,概率更大。
-\textbf{状态转换图}:树或者图。
+\textbf{状态转换图}:树或者DAG图。
\textbf{求解目标}:求最短。
@@ -323,7 +765,7 @@ \subsection{思考的步骤}
\begin{enumerate}
\item 是求路径长度,还是路径本身(或动作序列)?
\begin{enumerate}
- \item 如果是求路径长度,则状态里面要存路径长度
+ \item 如果是求路径长度,则状态里面要存路径长度(或双队列+一个全局变量)
\item 如果是求路径本身或动作序列
\begin{enumerate}
\item 要用一棵树存储宽搜过程中的路径
@@ -335,10 +777,16 @@ \subsection{思考的步骤}
\item 如何扩展状态?这一步跟第2步相关。状态里记录的数据不同,扩展方法就不同。对于固定不变的数据结构(一般题目直接给出,作为输入数据),如二叉树,图等,扩展方法很简单,直接往下一层走,对于隐式图,要先在第1步里想清楚状态所带的数据,想清楚了这点,那如何扩展就很简单了。
-\item 关于判重,状态是否存在完美哈希方案?即将状态一一映射到整数,互相之间不会冲突。
+\item 如何判断重复?如果状态转换图是一颗树,则永远不会出现回路,不需要判重;如果状态转换图是一个图(这时候是一个图上的BFS),则需要判重。
\begin{enumerate}
- \item 如果不存在,则需要使用通用的哈希表(自己实现或用标准库,例如\fn{unordered_set})来判重;自己实现哈希表的话,如果能够预估状态个数的上限,则可以开两个数组,head和next,表示哈希表,参考第 \S \ref{subsec:eightDigits}节方案2。
- \item 如果存在,则可以开一个大布尔数组,作为哈希表来判重,且此时可以精确计算出状态总数,而不仅仅是预估上限。
+ \item 如果是求最短路径长度或一条路径,则只需要让“点”(即状态)不重复出现,即可保证不出现回路
+ \item 如果是求所有路径,注意此时,状态转换图是DAG,即允许两个父节点指向同一个子节点。具体实现时,每个节点要\textbf{“延迟”}加入到已访问集合\fn{visited},要等一层全部访问完后,再加入到\fn{visited}集合。
+ \item 具体实现
+ \begin{enumerate}
+ \item 状态是否存在完美哈希方案?即将状态一一映射到整数,互相之间不会冲突。
+ \item 如果不存在,则需要使用通用的哈希表(自己实现或用标准库,例如\fn{unordered_set})来判重;自己实现哈希表的话,如果能够预估状态个数的上限,则可以开两个数组,head和next,表示哈希表,参考第 \S \ref{subsec:eightDigits}节方案2。
+ \item 如果存在,则可以开一个大布尔数组,来判重,且此时可以精确计算出状态总数,而不仅仅是预估上限。
+ \end{enumerate}
\end{enumerate}
\item 目标状态是否已知?如果题目已经给出了目标状态,可以带来很大便利,这时候可以从起始状态出发,正向广搜;也可以从目标状态出发,逆向广搜;也可以同时出发,双向广搜。
@@ -350,8 +798,8 @@ \subsection{代码模板}
对于队列,可以用\fn{queue},也可以把\fn{vector}当做队列使用。当求长度时,有两种做法:
\begin{enumerate}
-\item 只用一个队列,但在状态结构体\fn{state_t}里增加一个整数字段\fn{step},表示走到当前状态用了多少步,当碰到目标状态,直接输出\fn{step}即可。这个方案,可以很方便的变成A*算法,把队列换成优先队列即可。
-\item 用两个队列,\fn{current, next},分别表示当前层次和下一层,另设一个全局整数\fn{level},表示层数(也即路径长度),当碰到目标状态,输出\fn{level}即可。这个方案,状态可以少一个字段,节省内存。
+\item 只用一个队列,但在状态结构体\fn{state_t}里增加一个整数字段\fn{level},表示当前所在的层次,当碰到目标状态,直接输出\fn{level}即可。这个方案,可以很容易的变成A*算法,把\fn{queue}替换为\fn{priority_queue}即可。
+\item 用两个队列,\fn{current, next},分别表示当前层次和下一层,另设一个全局整数\fn{level},表示层数(也即路径长度),当碰到目标状态,输出\fn{level}即可。这个方案,状态里可以不存路径长度,只需全局设置一个整数\fn{level},比较节省内存;
\end{enumerate}
对于hashset,如果有完美哈希方案,用布尔数组(\fn{bool visited[STATE_MAX]}或\fn{vector visited(STATE_MAX, false)})来表示;如果没有,可以用STL里的\fn{set}或\fn{unordered_set}。
@@ -359,81 +807,393 @@ \subsection{代码模板}
对于树,如果用STL,可以用\fn{unordered_map father}表示一颗树,代码非常简洁。如果能够预估状态总数的上限(设为STATE_MAX),可以用数组(\fn{state_t nodes[STATE_MAX]}),即树的双亲表示法来表示树,效率更高,当然,需要写更多代码。
-\begin{Codex}[label=bfs_template1.cpp]
+\subsubsection{如何表示状态}
+
+\begin{Codex}[label=bfs_common.h]
+/** 状态 */
+struct state_t {
+ int data1; /** 状态的数据,可以有多个字段. */
+ int data2; /** 状态的数据,可以有多个字段. */
+ // dataN; /** 其他字段 */
+ int action; /** 由父状态移动到本状态的动作,求动作序列时需要. */
+ int level; /** 所在的层次(从0开始),也即路径长度-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 反向生成路径.
+ * @brief 反向生成路径,求一条路径.
* @param[in] father 树
* @param[in] target 目标节点
* @return 从起点到target的路径
*/
-template
vector gen_path(const unordered_map &father,
const state_t &target) {
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;
}
/**
- * @brief 广搜.
- * @param[in] state_t 状态,如整数,字符串,一维数组等
+ * 反向生成路径,求所有路径.
+ * @param[in] father 存放了所有路径的树
+ * @param[in] start 起点
+ * @param[in] state 终点
+ * @return 从起点到终点的所有路径
+ */
+void gen_path(unordered_map > &father,
+ const string &start, const state_t& state, vector &path,
+ vector > &result) {
+ path.push_back(state);
+ if (state == start) {
+ if (!result.empty()) {
+ if (path.size() < result[0].size()) {
+ result.clear();
+ result.push_back(path);
+ } else if(path.size() == result[0].size()) {
+ result.push_back(path);
+ } else {
+ // not possible
+ throw std::logic_error("not possible to get here");
+ }
+ } else {
+ result.push_back(path);
+ }
+ reverse(result.back().begin(), result.back().end());
+ } else {
+ for (const auto& f : father[state]) {
+ gen_path(father, start, f, path, result);
+ }
+ }
+ path.pop_back();
+}
+\end{Codex}
+
+
+\subsubsection{求最短路径长度或一条路径}
+
+\textbf{单队列的写法}
+
+\begin{Codex}[label=bfs_template.cpp]
+#include "bfs_common.h"
+
+/**
+ * @brief 广搜,只用一个队列.
+ * @param[in] start 起点
+ * @param[in] data 输入数据
+ * @return 从起点到目标状态的一条最短路径
+ */
+vector bfs(state_t &start, const vector> &grid) {
+ queue q; // 队列
+ unordered_set visited; // 判重
+ unordered_map father; // 树,求路径本身时才需要
+
+ // 判断状态是否合法
+ auto state_is_valid = [&](const state_t &s) { /*...*/ };
+
+ // 判断当前状态是否为所求目标
+ auto state_is_target = [&](const state_t &s) { /*...*/ };
+
+ // 扩展当前状态
+ auto state_extend = [&](const state_t &s) {
+ unordered_set result;
+ for (/*...*/) {
+ const state_t new_state = /*...*/;
+ if (state_is_valid(new_state) &&
+ visited.find(new_state) != visited.end()) {
+ result.insert(new_state);
+ }
+ }
+ return result;
+ };
+
+ assert (start.level == 0);
+ q.push(start);
+ while (!q.empty()) {
+ // 千万不能用 const auto&,pop() 会删除元素,
+ // 引用就变成了悬空引用
+ const state_t state = q.front();
+ q.pop();
+ visited.insert(state);
+
+ // 访问节点
+ if (state_is_target(state)) {
+ return return gen_path(father, target); // 求一条路径
+ // return state.level + 1; // 求路径长度
+ }
+
+ // 扩展节点
+ vector new_states = state_extend(state);
+ for (const auto& new_state : new_states) {
+ q.push(new_state);
+ father[new_state] = state; // 求一条路径
+ // visited.insert(state); // 优化:可以提前加入 visited 集合,
+ // 从而缩小状态扩展。这时 q 的含义略有变化,里面存放的是处理了一半
+ // 的节点:已经加入了visited,但还没有扩展。别忘记 while循环开始
+ // 前,要加一行代码, visited.insert(start)
+ }
+ }
+
+ return vector();
+ //return 0;
+}
+\end{Codex}
+
+
+\textbf{双队列的写法}
+\begin{Codex}[label=bfs_template1.cpp]
+#include "bfs_common.h"
+
+/**
+ * @brief 广搜,使用两个队列.
* @param[in] start 起点
- * @param[in] state_is_target 判断状态是否是目标的函数
- * @param[in] state_extend 状态扩展函数
+ * @param[in] data 输入数据
* @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 type& data) {
queue next, current; // 当前层,下一层
unordered_set visited; // 判重
- unordered_map father;
+ unordered_map father; // 树,求路径本身时才需要
+
+ int level = -1; // 层次
- int level = 0; // 层次
- bool found = false;
- state_t target;
+ // 判断状态是否合法
+ auto state_is_valid = [&](const state_t &s) { /*...*/ };
+
+ // 判断当前状态是否为所求目标
+ auto state_is_target = [&](const state_t &s) { /*...*/ };
+
+ // 扩展当前状态
+ auto state_extend = [&](const state_t &s) {
+ unordered_set result;
+ for (/*...*/) {
+ const state_t new_state = /*...*/;
+ if (state_is_valid(new_state) &&
+ visited.find(new_state) != visited.end()) {
+ result.insert(new_state);
+ }
+ }
+ return result;
+ };
current.push(start);
- while (!current.empty() && !found) {
+ while (!current.empty()) {
++level;
- while (!current.empty() && !found) {
- const state_t state = current.front();
+ while (!current.empty()) {
+ // 千万不能用 const auto&,pop() 会删除元素,
+ // 引用就变成了悬空引用
+ const auto state = current.front();
current.pop();
- vector new_states = state_extend(state, visited);
- for (auto iter = new_states.begin();
- iter != new_states.end() && ! found; ++iter) {
- const state_t new_state(*iter);
-
- if (state_is_target(new_state)) {
- found = true; //找到了
- target = new_state;
- father[new_state] = state;
- break;
- }
+ visited.insert(state);
+ if (state_is_target(state)) {
+ return return gen_path(father, state); // 求一条路径
+ // return state.level + 1; // 求路径长度
+ }
+
+ const auto& new_states = state_extend(state);
+ for (const auto& new_state : new_states) {
next.push(new_state);
- // visited.insert(new_state); 必须放到 state_extend()里
father[new_state] = state;
+ // visited.insert(state); // 优化:可以提前加入 visited 集合,
+ // 从而缩小状态扩展。这时 current 的含义略有变化,里面存放的是处
+ // 理了一半的节点:已经加入了visited,但还没有扩展。别忘记 while
+ // 循环开始前,要加一行代码, visited.insert(start)
}
}
swap(next, current); //!!! 交换两个队列
}
- if (found) {
- return gen_path(father, target);
- //return level + 1;
- } else {
- return vector();
- //return 0;
+ return vector();
+ // return 0;
+}
+\end{Codex}
+
+
+\subsubsection{求所有路径}
+
+\textbf{单队列}
+
+\begin{Codex}[label=bfs_template.cpp]
+/**
+ * @brief 广搜,使用一个队列.
+ * @param[in] start 起点
+ * @param[in] data 输入数据
+ * @return 从起点到目标状态的所有最短路径
+ */
+vector > bfs(const state_t &start, const type& data) {
+ queue q;
+ unordered_set visited; // 判重
+ unordered_map > father; // DAG
+
+ auto state_is_valid = [&](const state_t& s) { /*...*/ };
+ auto state_is_target = [&](const state_t &s) { /*...*/ };
+ auto state_extend = [&](const state_t &s) {
+ unordered_set result;
+ for (/*...*/) {
+ const state_t new_state = /*...*/;
+ if (state_is_valid(new_state)) {
+ auto visited_iter = visited.find(new_state);
+
+ if (visited_iter != visited.end()) {
+ if (visited_iter->level < new_state.level) {
+ // do nothing
+ } else if (visited_iter->level == new_state.level) {
+ result.insert(new_state);
+ } else { // not possible
+ throw std::logic_error("not possible to get here");
+ }
+ } else {
+ result.insert(new_state);
+ }
+ }
+ }
+
+ return result;
+ };
+
+ vector> result;
+ state_t start_state(start, 0);
+ q.push(start_state);
+ visited.insert(start_state);
+ while (!q.empty()) {
+ // 千万不能用 const auto&,pop() 会删除元素,
+ // 引用就变成了悬空引用
+ const auto state = q.front();
+ q.pop();
+
+ // 如果当前路径长度已经超过当前最短路径长度,
+ // 可以中止对该路径的处理,因为我们要找的是最短路径
+ if (!result.empty() && state.level + 1 > result[0].size()) break;
+
+ if (state_is_target(state)) {
+ vector path;
+ gen_path(father, start_state, state, path, result);
+ continue;
+ }
+ // 必须挪到下面,比如同一层A和B两个节点均指向了目标节点,
+ // 那么目标节点就会在q中出现两次,输出路径就会翻倍
+ // visited.insert(state);
+
+ // 扩展节点
+ const auto& new_states = state_extend(state);
+ for (const auto& new_state : new_states) {
+ if (visited.find(new_state) == visited.end()) {
+ q.push(new_state);
+ }
+ visited.insert(new_state);
+ father[new_state].push_back(state);
+ }
+ }
+
+ return result;
+}
+\end{Codex}
+
+
+\textbf{双队列的写法}
+
+\begin{Codex}[label=bfs_template.cpp]
+#include "bfs_common.h"
+
+/**
+ * @brief 广搜,使用两个队列.
+ * @param[in] start 起点
+ * @param[in] data 输入数据
+ * @return 从起点到目标状态的所有最短路径
+ */
+vector > bfs(const state_t &start, const type& data) {
+ // 当前层,下一层,用unordered_set是为了去重,例如两个父节点指向
+ // 同一个子节点,如果用vector, 子节点就会在next里出现两次,其实此
+ // 时 father 已经记录了两个父节点,next里重复出现两次是没必要的
+ unordered_set current, next;
+ unordered_set visited; // 判重
+ unordered_map > father; // DAG
+
+ int level = -1; // 层次
+
+ // 判断状态是否合法
+ auto state_is_valid = [&](const state_t &s) { /*...*/ };
+
+ // 判断当前状态是否为所求目标
+ auto state_is_target = [&](const state_t &s) { /*...*/ };
+
+ // 扩展当前状态
+ auto state_extend = [&](const state_t &s) {
+ unordered_set result;
+ for (/*...*/) {
+ const state_t new_state = /*...*/;
+ if (state_is_valid(new_state) &&
+ visited.find(new_state) != visited.end()) {
+ result.insert(new_state);
+ }
+ }
+ return result;
+ };
+
+ vector > result;
+ current.insert(start);
+ while (!current.empty()) {
+ ++ level;
+ // 如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的
+ // 处理,因为我们要找的是最短路径
+ if (!result.empty() && level+1 > result[0].size()) break;
+
+ // 1. 延迟加入visited, 这样才能允许两个父节点指向同一个子节点
+ // 2. 一股脑current 全部加入visited, 是防止本层前一个节点扩展
+ // 节点时,指向了本层后面尚未处理的节点,这条路径必然不是最短的
+ for (const auto& state : current)
+ visited.insert(state);
+ for (const auto& state : current) {
+ if (state_is_target(state)) {
+ vector path;
+ gen_path(father, path, start, state, result);
+ continue;
+ }
+
+ const auto new_states = state_extend(state);
+ for (const auto& new_state : new_states) {
+ next.insert(new_state);
+ father[new_state].push_back(state);
+ }
+ }
+
+ current.clear();
+ swap(current, next);
}
+
+ return result;
}
\end{Codex}
+
diff --git a/C++/chapBruteforce.tex b/C++/chapBruteforce.tex
index 094ab051..4c9e4037 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;
+ 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()); // 本题对顺序有要求,需要排序
+ 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,24 +436,25 @@ \subsubsection{代码}
\begin{Code}
// LeetCode, Permutations
// 重新实现 next_permutation()
+// 时间复杂度O(n!),空间复杂度O(1)
class Solution {
public:
- vector> permute(vector& num) {
+ vector > permute(vector &num) {
+ vector > result;
sort(num.begin(), num.end());
- vector> permutations;
-
do {
- permutations.push_back(num);
- } while (next_permutation(num.begin(), num.end())); // 见第2.1节
-
- return permutations;
+ result.push_back(num);
+ // 调用的是 2.1.12 节的 next_permutation()
+ // 而不是 std::next_permutation()
+ } while(next_permutation(num.begin(), num.end()));
+ return result;
}
};
\end{Code}
-\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,98 @@ \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;
+ if (digits.empty()) return 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) {
+ if (digits.empty()) return vector();
+ 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..f6c4c107 100644
--- a/C++/chapDFS.tex
+++ b/C++/chapDFS.tex
@@ -23,87 +23,123 @@ \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) {
+ // prev 表示前一个隔板, start 表示当前隔板
+ void dfs(string &s, vector& path,
+ vector> &result, size_t prev, size_t start) {
if (start == s.size()) { // 最后一个隔板
if (isPalindrome(s, prev, start - 1)) { // 必须使用
- 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 (start < end && 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 (start < end && 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 +164,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 +178,7 @@ \subsubsection{代码}
\begin{Code}
// LeetCode, Unique Paths
// 深搜,小集合可以过,大集合会超时
+// 时间复杂度O(n^4),空间复杂度O(n)
class Solution {
public:
int uniquePaths(int m, int n) {
@@ -162,27 +199,28 @@ \subsubsection{代码}
\begin{Code}
// LeetCode, Unique Paths
// 深搜 + 缓存,即备忘录法
+// 时间复杂度O(n^2),空间复杂度O(n^2)
class Solution {
public:
int uniquePaths(int m, int n) {
- // 0行和0列未使用
- this->f = vector >(m + 1, vector(n + 1, 0));
- return dfs(m, n);
+ // f[x][y] 表示 从(0,0)到(x,y)的路径条数
+ f = vector >(m, vector(n, 0));
+ f[0][0] = 1;
+ return dfs(m - 1, n - 1);
}
private:
vector > f; // 缓存
int dfs(int x, int y) {
- if (x < 1 || y < 1) return 0; // 数据非法,终止条件
-
- if (x == 1 && y == 1) return 1; // 回到起点,收敛条件
+ if (x < 0 || y < 0) return 0; // 数据非法,终止条件
- return getOrUpdate(x - 1, y) + getOrUpdate(x, y - 1);
- }
+ if (x == 0 && y == 0) return f[0][0]; // 回到起点,收敛条件
- int getOrUpdate(int x, int y) {
- if (f[x][y] > 0) return f[x][y];
- else return f[x][y] = dfs(x, y);
+ if (f[x][y] > 0) {
+ return f[x][y];
+ } else {
+ return f[x][y] = dfs(x - 1, y) + dfs(x, y - 1);
+ }
}
};
\end{Code}
@@ -201,6 +239,7 @@ \subsubsection{代码}
\begin{Code}
// LeetCode, Unique Paths
// 动规,滚动数组
+// 时间复杂度O(n^2),空间复杂度O(n)
class Solution {
public:
int uniquePaths(int m, int n) {
@@ -208,9 +247,9 @@ \subsubsection{代码}
f[0] = 1;
for (int i = 0; i < m; i++) {
for (int j = 1; j < n; j++) {
- // 左边的f[j],表示更新后的f[j],与公式中的f[i[[j]对应
+ // 左边的f[j],表示更新后的f[j],与公式中的f[i][j]对应
// 右边的f[j],表示老的f[j],与公式中的f[i-1][j]对应
- f[j] = f[j - 1] + f[j];
+ f[j] = f[j] + f[j - 1];
}
}
return f[n - 1];
@@ -298,46 +337,48 @@ \subsubsection{代码}
// 深搜 + 缓存,即备忘录法
class Solution {
public:
- int uniquePathsWithObstacles(vector > &obstacleGrid) {
+ int uniquePathsWithObstacles(const vector >& obstacleGrid) {
const int m = obstacleGrid.size();
const int n = obstacleGrid[0].size();
- // 0行和0列未使用
- this->f = vector >(m + 1, vector(n + 1, 0));
- return dfs(obstacleGrid, m, n);
+ if (obstacleGrid[0][0] || obstacleGrid[m - 1][n - 1]) return 0;
+
+ f = vector >(m, vector(n, 0));
+ f[0][0] = obstacleGrid[0][0] ? 0 : 1;
+ return dfs(obstacleGrid, m - 1, n - 1);
}
private:
vector > f; // 缓存
- int dfs(const vector > &obstacleGrid,
+ // @return 从 (0, 0) 到 (x, y) 的路径总数
+ int dfs(const vector >& obstacleGrid,
int x, int y) {
- if (x < 1 || y < 1) return 0; // 数据非法,终止条件
+ if (x < 0 || y < 0) return 0; // 数据非法,终止条件
// (x,y)是障碍
- if (obstacleGrid[x-1][y-1]) return 0;
+ if (obstacleGrid[x][y]) return 0;
- if (x == 1 and y == 1) return 1; // 回到起点,收敛条件
+ if (x == 0 and y == 0) return f[0][0]; // 回到起点,收敛条件
- return getOrUpdate(obstacleGrid, x - 1, y) +
- getOrUpdate(obstacleGrid, x, y - 1);
- }
-
- int getOrUpdate(const vector > &obstacleGrid,
- int x, int y) {
- if (f[x][y] > 0) return f[x][y];
- else return f[x][y] = dfs(obstacleGrid, x, y);
+ if (f[x][y] > 0) {
+ return f[x][y];
+ } else {
+ return f[x][y] = dfs(obstacleGrid, x - 1, y) +
+ dfs(obstacleGrid, x, y - 1);
+ }
}
};
\end{Code}
\subsection{动规}
-与上一题类似,但要特别注意第一列的障碍。在上一题中,第一列全部是1,但是在这一题中不同,第一列如果某一行有障碍物,那么后面的行应该为0。
+与上一题类似,但要特别注意第一列的障碍。在上一题中,第一列全部是1,但是在这一题中不同,第一列如果某一行有障碍物,那么后面的行全为0。
\subsubsection{代码}
\begin{Code}
// LeetCode, Unique Paths II
// 动规,滚动数组
+// 时间复杂度O(n^2),空间复杂度O(n)
class Solution {
public:
int uniquePathsWithObstacles(vector > &obstacleGrid) {
@@ -346,30 +387,14 @@ \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;
- }
+ f[0] = f[0] == 0 ? 0 : (obstacleGrid[i][0] ? 0 : 1);
+ for (int j = 1; j < n; j++)
+ f[j] = obstacleGrid[i][j] ? 0 : (f[j] + f[j - 1]);
}
- 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];
}
};
@@ -417,32 +442,97 @@ \subsubsection{描述}
\subsubsection{分析}
+
经典的深搜题。
-\subsubsection{代码}
+设置一个数组 \fn{vector C(n, 0)}, \fn{C[i]} 表示第i行皇后所在的列编号,即在位置 (i, C[i]) 上放了一个皇后,这样用一个一维数组,就能记录整个棋盘。
+
+
+\subsubsection{代码1}
+\begin{Code}
+// LeetCode, N-Queens
+// 深搜+剪枝
+// 时间复杂度O(n!*n),空间复杂度O(n)
+class Solution {
+public:
+ vector > solveNQueens(int n) {
+ vector > result;
+ vector C(n, -1); // C[i]表示第i行皇后所在的列编号
+ dfs(C, result, 0);
+ return result;
+ }
+private:
+ void dfs(vector &C, vector > &result, int row) {
+ const int N = C.size();
+ if (row == N) { // 终止条件,也是收敛条件,意味着找到了一个可行解
+ vector solution;
+ for (int i = 0; i < N; ++i) {
+ string s(N, '.');
+ for (int j = 0; j < N; ++j) {
+ if (j == C[i]) s[j] = 'Q';
+ }
+ solution.push_back(s);
+ }
+ result.push_back(solution);
+ return;
+ }
+
+ for (int j = 0; j < N; ++j) { // 扩展状态,一列一列的试
+ const bool ok = isValid(C, row, j);
+ if (!ok) continue; // 剪枝,如果非法,继续尝试下一列
+ // 执行扩展动作
+ C[row] = j;
+ dfs(C, result, row + 1);
+ // 撤销动作
+ // C[row] = -1;
+ }
+ }
+
+ /**
+ * 能否在 (row, col) 位置放一个皇后.
+ *
+ * @param C 棋局
+ * @param row 当前正在处理的行,前面的行都已经放了皇后了
+ * @param col 当前列
+ * @return 能否放一个皇后
+ */
+ bool isValid(const vector &C, int row, int col) {
+ for (int i = 0; i < row; ++i) {
+ // 在同一列
+ if (C[i] == col) return false;
+ // 在同一对角线上
+ if (abs(i - row) == abs(C[i] - col)) return false;
+ }
+ return true;
+ }
+};
+\end{Code}
+
+
+\subsubsection{代码2}
\begin{Code}
// LeetCode, N-Queens
// 深搜+剪枝
+// 时间复杂度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->columns = vector(n, false);
+ this->main_diag = vector(2 * n - 1, false);
+ this->anti_diag = vector(2 * n - 1, false);
vector > result;
- vector C(n, 0); // C[i]表示第i行皇后所在的列编号
- dfs(0, C, result);
+ vector C(n, -1); // C[i]表示第i行皇后所在的列编号
+ dfs(C, result, 0);
return result;
}
private:
// 这三个变量用于剪枝
- vector columns; // 表示已经放置的皇后占据了哪些列
- vector principal_diagonals; // 占据了哪些主对角线
- vector counter_diagonals; // 占据了哪些副对角线
+ vector columns; // 表示已经放置的皇后占据了哪些列
+ 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 +548,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] && !main_diag[row - j + N - 1] &&
+ !anti_diag[row + j];
+ if (!ok) continue; // 剪枝,如果非法,继续尝试下一列
+ // 执行扩展动作
+ C[row] = j;
+ columns[j] = main_diag[row - j + N - 1] = anti_diag[row + j] = true;
+ dfs(C, result, row + 1);
+ // 撤销动作
+ // C[row] = -1;
+ columns[j] = main_diag[row - j + N - 1] = anti_diag[row + j] = false;
}
}
};
@@ -498,51 +584,106 @@ \subsubsection{分析}
只需要输出解的个数,不需要输出所有解,代码要比上一题简化很多。设一个全局计数器,每找到一个解就增1。
-\subsubsection{代码}
+\subsubsection{代码1}
+\begin{Code}
+// LeetCode, N-Queens II
+// 深搜+剪枝
+// 时间复杂度O(n!*n),空间复杂度O(n)
+class Solution {
+public:
+ int totalNQueens(int n) {
+ this->count = 0;
+
+ vector C(n, 0); // C[i]表示第i行皇后所在的列编号
+ dfs(C, 0);
+ return this->count;
+ }
+private:
+ int count; // 解的个数
+
+ void dfs(vector &C, int row) {
+ const int N = C.size();
+ if (row == N) { // 终止条件,也是收敛条件,意味着找到了一个可行解
+ ++this->count;
+ return;
+ }
+
+ for (int j = 0; j < N; ++j) { // 扩展状态,一列一列的试
+ const bool ok = isValid(C, row, j);
+ if (!ok) continue; // 剪枝:如果合法,继续递归
+ // 执行扩展动作
+ C[row] = j;
+ dfs(C, row + 1);
+ // 撤销动作
+ // C[row] = -1;
+ }
+ }
+ /**
+ * 能否在 (row, col) 位置放一个皇后.
+ *
+ * @param C 棋局
+ * @param row 当前正在处理的行,前面的行都已经放了皇后了
+ * @param col 当前列
+ * @return 能否放一个皇后
+ */
+ bool isValid(const vector &C, int row, int col) {
+ for (int i = 0; i < row; ++i) {
+ // 在同一列
+ if (C[i] == col) return false;
+ // 在同一对角线上
+ if (abs(i - row) == abs(C[i] - col)) return false;
+ }
+ return true;
+ }
+};
+\end{Code}
+
+
+\subsubsection{代码2}
\begin{Code}
// LeetCode, N-Queens II
// 深搜+剪枝
+// 时间复杂度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->columns = vector