From 19212e880b88ba593c6a602c23b9397bf609b834 Mon Sep 17 00:00:00 2001 From: Sainnhe Park Date: Thu, 2 Feb 2023 17:34:34 +0800 Subject: [PATCH] s0131 --- include/s0131_palindrome_partitioning.hpp | 14 ++++++++ notes/src/SUMMARY.md | 1 + notes/src/backtrack.md | 41 ++++++++++++++++++++--- notes/src/split.md | 5 +++ src/s0131_palindrome_partitioning.cpp | 40 ++++++++++++++++++++++ 5 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 include/s0131_palindrome_partitioning.hpp create mode 100644 notes/src/split.md create mode 100644 src/s0131_palindrome_partitioning.cpp diff --git a/include/s0131_palindrome_partitioning.hpp b/include/s0131_palindrome_partitioning.hpp new file mode 100644 index 0000000..1143c32 --- /dev/null +++ b/include/s0131_palindrome_partitioning.hpp @@ -0,0 +1,14 @@ +#ifndef S0131_PALINDROME_PARTITIONING_HPP +#define S0131_PALINDROME_PARTITIONING_HPP + +#include +#include + +using namespace std; + +class S0131 { + public: + vector> partition(string s); +}; + +#endif diff --git a/notes/src/SUMMARY.md b/notes/src/SUMMARY.md index 5c267e7..9c602c4 100644 --- a/notes/src/SUMMARY.md +++ b/notes/src/SUMMARY.md @@ -42,6 +42,7 @@ - [总结](./backtrack.md) - [组合问题](./combinations.md) +- [切割问题](./split.md) # STL diff --git a/notes/src/backtrack.md b/notes/src/backtrack.md index 7ba326f..e673b26 100644 --- a/notes/src/backtrack.md +++ b/notes/src/backtrack.md @@ -9,10 +9,43 @@ 关键就是要学会分析和画图,然后确定传入参数。只要传入参数确定了代码框架就确定了 (返回值一般是 `void`): -- 得有一个 `&result` 来存放结果 -- 每一层的节点所包含的数据是什么,需要一系列的参数来描述当前节点的状态(传入的参数实际上是上一轮迭代的数据)。 -- 终止条件是什么?回溯法中的每个节点并不是真的树状节点,没有 `nullptr` ,因此用空指针来判断是否到了叶子结点并不合理,需要其它的一些方法来确定是否到达叶子节点,比如高度。 -- 每次迭代要在上一次迭代的基础上进行哪些操作?需要哪些参数才能完成这些操作? +- 思考这棵树怎么画,每层遍历的逻辑是什么,每条边的操作逻辑是什么。 +- 得设计一个数据结构 `NodeState` 来存放当前节点状态。该数据结构的可扩展性必须要强,需要满足以下条件: + - 能描述当前节点的状态 + - 能作为最终结果存储 + - 能根据当前节点更新状态和撤销之前的更改 +- 得有一个 `&result` 来存放结果,这个 `&result` 通常是一个向量 `vector &`,里面存放了节点状态。 +- 其它传入参数用来完成每层遍历操作和每条边的操作。 + +设计完了数据结构之后来看看具体代码怎么写。模板如下: + +```cpp +void backtrack(NodeState &node, vector &result, int para1, int para2, int para3) { + // 终止条件 + // 回溯法中的每个节点并不是真的树状节点,没有 `nullptr` ,因此用空指针来判断是否到了叶子结点并不合理,需要其它的一些方法来确定是否到达叶子节点,比如高度。 + if (/* end condition */) { + result.push_back(node); + return; + } + + // 遍历该节点的所有子节点,即遍历下一层 + for (...) { + // 剪枝 + // 当现在的节点不可能出现我们想要的结果时,直接跳过。 + if (/* out of scope */) { + continue; + } + // 处理节点 + // 现在 node 中的数据描述的是当前节点, + // handle(node) 一般是让 node 中的数据变成子节点的数据 + handle(node); + // 递归 + backtrack(node, result, para1, para2, para3); + // 撤销数据处理,让 node 中的数据再次变回描述当前节点的数据 + revert(node); + } +} +``` 复杂度分析: diff --git a/notes/src/split.md b/notes/src/split.md new file mode 100644 index 0000000..713ddae --- /dev/null +++ b/notes/src/split.md @@ -0,0 +1,5 @@ +# 切割问题 + +## [131. 分割回文串](https://leetcode.cn/problems/palindrome-partitioning/) + +![](https://paste.sainnhe.dev/FHTE.jpg) diff --git a/src/s0131_palindrome_partitioning.cpp b/src/s0131_palindrome_partitioning.cpp new file mode 100644 index 0000000..5f51f70 --- /dev/null +++ b/src/s0131_palindrome_partitioning.cpp @@ -0,0 +1,40 @@ +#include "s0131_palindrome_partitioning.hpp" + +bool isPalindrome(const string &s, int begin, int end) { + for (int i = begin, j = end; i <= j; ++i, --j) { + if (s[i] != s[j]) return false; + } + return true; +} + +/** + * @brief 回溯 + * + * @param path 每个元素都是一个回文串 + * @param result 存放最终结果 + * @param s 待处理的字符串 + * @param startIndex s[0...startIndex - 1] 被分成了很多个子字符串,存放在 path 中 + */ +void partitionDFS(vector &path, vector> &result, const string &s, int startIndex) { + int len = s.length(); + // 终止条件 + if (startIndex == len) { + result.push_back(path); + } + + for (int i = startIndex; i < len; ++i) { + // 如果 s[startIndex...i] 是回文串,则进入下一层 + if (isPalindrome(s, startIndex, i)) { + path.push_back(s.substr(startIndex, i - startIndex + 1)); + partitionDFS(path, result, s, i + 1); + path.pop_back(); + } + } +} + +vector> S0131::partition(string s) { + vector> result{}; + vector path{}; + partitionDFS(path, result, s, 0); + return result; +}