一.回溯
理论基础
树型递归穷举+剪枝
应用
2.1 组合总和II 40
题目描述:
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
有限重复,加和目标
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
if (sum == target) {
result.push_back(path);
return;
}
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) continue;
sum += candidates[i];
path.push_back(candidates[i]);
used[i] = true;
// 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次
backtracking(candidates, target, sum, i + 1, used);
used[i] = false;
sum -= candidates[i];
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<bool> used(candidates.size(), false);
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, 0, used);
return result;
}
};
2.2 N皇后 51
题目描述:
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
isValid
class Solution {
private:
vector<vector<string>> result;
// n 为输入的棋盘大小
// row 是当前递归到***的第几行了
void backtracking(int n, int row, vector<string>& chessboard) {
if (row == n) {
result.push_back(chessboard);
return;
}
for (int col = 0; col < n; col++) {
if (isValid(row, col, chessboard, n)) {
chessboard[row][col] = 'Q';
backtracking(n, row + 1, chessboard);
chessboard[row][col] = '.';
}
}
}
bool isValid(int row, int col, vector<string>& chessboard, int n) {
int count = 0;
// 检查列
for (int i = 0; i < row; i++)
if (chessboard[i][col] == 'Q') return false;
// 检查45度角
for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--)
if (chessboard[i][j] == 'Q') return false;
// 检查135度角
for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++)
if (chessboard[i][j] == 'Q') return false;
return true;
}
public:
vector<vector<string>> solveNQueens(int n) {
std::vector<std::string> chessboard(n, std::string(n, '.'));
backtracking(n, 0, chessboard);
return result;
}
};
2.3 分割回文串131
题目描述:
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
条件回溯,完成跳出,矩阵标记
class Solution {
private:
vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
vector<vector<bool>> isPalindrome;
// isPalindrome[i][j] 代表 s[i:j](双边包括)是否是回文字串
void backtracking(const string& s, int startIndex) {
// 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
if (startIndex >= s.size()) {
result.push_back(path);
return;
}
for (int i = startIndex; i < s.size(); i++) {
if (isPalindrome[startIndex][i]) {
// 获取[startIndex,i]在s中的子串
string str = s.substr(startIndex, i - startIndex + 1);
path.push_back(str);
}
else continue;
backtracking(s, i + 1);
path.pop_back();
}
}
void computePalindrome(const string& s) {
isPalindrome.resize(s.size(), vector<bool>(s.size(), false));
// 根据字符串s, 刷新布尔矩阵的大小
for (int i = s.size() - 1; i >= 0; i--) {
// 需要倒序计算, 保证在i行时, i+1行已经计算好了
for (int j = i; j < s.size(); j++) {
if (j == i) isPalindrome[i][j] = true;
else if (j - i == 1) isPalindrome[i][j] = (s[i] == s[j]);
else isPalindrome[i][j] = (s[i] == s[j] && isPalindrome[i + 1][j - 1]);
}
}
}
public:
vector<vector<string>> partition(string s) {
computePalindrome(s);
backtracking(s, 0);
return result;
}
};
2.4 二叉树的最大深度104
前序+回溯
class Solution {
public:
int re;
void getdepth(TreeNode* node, int depth) {
re = depth > re ? depth : re;
if (!node->left && !node->right) return;
if (node->left) {
depth++;
getdepth(node->left, depth);
depth--;
}
if (node->right) {
depth++;
getdepth(node->right, depth);
depth--;
}
}
int maxDepth(TreeNode* root) {
re = 0;
if (!root) return re;
getdepth(root, 1);
return re;
}
};
二.递归
反转链表 206
题目描述:
三指针
尾节点指向nullptr不可省略
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head || !head->next) return head;
ListNode* p = head;
if (!head->next->next) {
head = head->next;
head->next = p;
p->next = nullptr;
return head;
}
ListNode* q = p->next;
ListNode* r = q->next;
p->next = nullptr;
while (r) {
q->next = p;
p = q;
q = r;
r = r->next;
}
q->next = p;
head = q;
return head;
}
};
正向递归:
class Solution {
public:
ListNode* reverse(ListNode* pre,ListNode* cur){
if(cur == NULL) return pre;
ListNode* temp = cur->next;
cur->next = pre;
// 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
// pre = cur;
// cur = temp;
return reverse(cur,temp);
}
ListNode* reverseList(ListNode* head) {
// 和双指针法初始化是一样的逻辑
// ListNode* cur = head;
// ListNode* pre = NULL;
return reverse(NULL, head);
}
};
反向递归:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 边缘条件判断
if(head == NULL) return NULL;
if (head->next == NULL) return head;
// 递归调用,翻转第二个节点开始往后的链表
ListNode *last = reverseList(head->next);
// 翻转头节点与第二个节点的指向
head->next->next = head;
// 此时的 head 节点为尾节点,next 需要指向 NULL
head->next = NULL;
return last;
}
};
二叉树
2.1 深度优先
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec) {
if (!cur) return;
vec.push_back(cur->val); //中
traversal(cur->left, vec); //左
traversal(cur->right, vec); //右
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int>re;
traversal(root, re);
return re;
}
};
2.2 层序遍历102
class Solution {
public:
void traversal(TreeNode* cur, vector<vector<int>>& re, int depth) {
if (!cur) return;
if (re.size() == depth) re.push_back(vector<int>());
re[depth].push_back(cur->val);
traversal(cur->left, re, depth + 1);
traversal(cur->right, re, depth + 1);
}
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> re;
int depth = 0;
traversal(root, re, depth);
return re;
}
};
2.3翻转二叉树226
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (!root) return root;
swap(root->left, root->right);
invertTree(root->left);
invertTree(root->right);
return root;
}
};
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (!root) return root;
invertTree(root->left);
swap(root->left, root->right);
//依然要遍历左孩子,因为中间节点已经翻转
invertTree(root->left);
return root;
}
};
2.4对称二叉树II 101
class Solution {
public:
bool compare(TreeNode* left, TreeNode* right) {
if (!left && !right) return true;
else if (left && !right) return false;
else if (!left && right) return false;
else if (left->val != right->val) return false;
bool outside = compare(left->left, right->right);
bool inside = compare(left->right, right->left);
return outside && inside;
}
bool isSymmetric(TreeNode* root) {
if (!root) return true;
return compare(root->left, root->right);
}
};
2.5 二叉树的最小深度111
class Solution {
public:
int getDepth(TreeNode* node) {
if (node == NULL) return 0;
int leftDepth = getDepth(node->left);
int rightDepth = getDepth(node->right);
if (node->left == NULL && node->right != NULL)
return 1 + rightDepth;
if (node->left != NULL && node->right == NULL)
return 1 + leftDepth;
int result = 1 + min(leftDepth, rightDepth);
return result;
}
int minDepth(TreeNode* root) {
return getDepth(root);
}
};
class Solution {
public:
int minDepth(TreeNode* root) {
if (root == NULL) return 0;
if (root->left == NULL && root->right != NULL)
return 1 + minDepth(root->right);
if (root->left != NULL && root->right == NULL)
return 1 + minDepth(root->left);
return 1 + min(minDepth(root->left), minDepth(root->right));
}
};
2.6 平衡二叉树110
一个二叉树 每个节点 的左右两个子树的高度差的绝对值不超过 1 。
class Solution {
public:
int getHeight(TreeNode* node) {
if (node == NULL) return 0;
int leftHeight = getHeight(node->left);
if (leftHeight == -1) return -1;
int rightHeight = getHeight(node->right);
if (rightHeight == -1) return -1;
return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);
}
bool isBalanced(TreeNode* root) {
return getHeight(root) == -1 ? false : true;
}
};
2.7 二叉树的所有路径257
traversal是void函数,形参需要引用
递归+回溯
class Solution {
private:
void traversal(TreeNode* cur, vector<int>& path, vector<string>& result) {
path.push_back(cur->val);
if (cur->left == NULL && cur->right == NULL) {
string sPath;
for (int i = 0; i < path.size() - 1; i++) {
sPath += to_string(path[i]);
sPath += "->";
}
sPath += to_string(path[path.size() - 1]);
result.push_back(sPath);
return;
}
if (cur->left) {
traversal(cur->left, path, result);
path.pop_back(); // 回溯
}
if (cur->right) {
traversal(cur->right, path, result);
path.pop_back(); // 回溯
}
}
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
vector<int> path;
if (root == NULL) return result;
traversal(root, path, result);
return result;
}
};
递归
class Solution {
private:
void traversal(TreeNode* cur, string path, vector<string>& result) {
path += to_string(cur->val); // 中
if (cur->left == NULL && cur->right == NULL) {
result.push_back(path);
return;
}
if (cur->left) traversal(cur->left, path + "->", result); // 左
if (cur->right) traversal(cur->right, path + "->", result); // 右
}
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
string path;
if (root == NULL) return result;
traversal(root, path, result);
return result;
}
};
2.8 路径总和112
题目描述:
判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if (root == nullptr) return false;
if (!root->left && !root->right && targetSum == root->val)return true;
return hasPathSum(root->left, targetSum - root->val) || hasPathSum(root->right, targetSum - root->val);
}
};
2.8.5路径总和II 113
题目描述:
找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
// 递归函数不需要返回值,因为我们要遍历整个树
void traversal(TreeNode* cur, int count) {
if (!cur->left && !cur->right && count == 0) { // 遇到了叶子节点且找到了和为targetSum的路径
result.push_back(path);
return;
}
if (!cur->left && !cur->right) return ; // 遇到叶子节点而没有找到合适的边,直接返回
if (cur->left) { // 左 (空节点不遍历)
path.push_back(cur->left->val);
count -= cur->left->val;
traversal(cur->left, count); // 递归
count += cur->left->val; // 回溯
path.pop_back(); // 回溯
}
if (cur->right) { // 右 (空节点不遍历)
path.push_back(cur->right->val);
count -= cur->right->val;
traversal(cur->right, count); // 递归
count += cur->right->val; // 回溯
path.pop_back(); // 回溯
}
return ;
}
public:
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
result.clear();
path.clear();
if (root == nullptr) return result;
path.push_back(root->val); // 把根节点放进路径
traversal(root, targetSum - root->val);
return result;
}
};
2.9 合并二叉树617
题目描述:
给你两棵二叉树: root1 和 root2 。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
注意: 合并过程必须从两个树的根节点开始。
前序原树:
class Solution {
public:
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
if (t1 == NULL) return t2; // 如果t1为空,合并之后就应该是t2
if (t2 == NULL) return t1; // 如果t2为空,合并之后就应该是t1
// 修改了t1的数值和结构
t1->val += t2->val; // 中
t1->left = mergeTrees(t1->left, t2->left); // 左
t1->right = mergeTrees(t1->right, t2->right); // 右
return t1;
}
};
前序新树:
class Solution {
public:
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
if (t1 == NULL) return t2;
if (t2 == NULL) return t1;
// 重新定义新的节点,不修改原有两个树的结构
TreeNode* root = new TreeNode(0);
root->val = t1->val + t2->val;
root->left = mergeTrees(t1->left, t2->left);
root->right = mergeTrees(t1->right, t2->right);
return root;
}
};
2.10 二叉搜索树中的搜索700
题目描述:
给定二叉搜索树(BST)的根节点 root 和一个整数值 val。
你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
if (root == NULL || root->val == val) return root;
if (root->val > val) return searchBST(root->left, val);
if (root->val < val) return searchBST(root->right, val);
return NULL;
}
};
2.11 验证二叉搜索树98
题目描述:
class Solution {
private:
// 将二叉搜索树转换为有序数组
vector<int> vec;
void traversal(TreeNode* root) {
if (root == NULL) return;
traversal(root->left);
vec.push_back(root->val);
traversal(root->right);
}
public:
bool isValidBST(TreeNode* root) {
vec.clear();
traversal(root);
// 注意要小于等于,搜索树里不能有相同元素
for (int i = 1; i < vec.size(); i++)
if (vec[i] <= vec[i - 1]) return false;
return true;
}
};
简化递归:
class Solution {
public:
TreeNode* pre = NULL;
bool isValidBST(TreeNode* root) {
if (root == NULL) return true;
bool left = isValidBST(root->left);
if (pre && pre->val >= root->val) return false;
pre = root;
bool right = isValidBST(root->right);
return left && right;
}
};
2.12 二叉搜索树的最小绝对差530
题目描述:
给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
class Solution {
private:
int result = INT_MAX;
TreeNode* pre;
void traversal(TreeNode* cur) {
if (cur == NULL) return;
traversal(cur->left);
if (pre) result = min(result, cur->val - pre->val);
pre = cur;
traversal(cur->right);
}
public:
int getMinimumDifference(TreeNode* root) {
traversal(root);
return result;
}
};
2.13 二叉搜索树中的众数501
题目描述:
给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
class Solution {
private:
int maxCount; // 最大次数
int count; // 统计次数
TreeNode* pre;
vector<int> result;
void searchBST(TreeNode* cur) {
if (cur == NULL) return ;
searchBST(cur->left);
if (pre == NULL) count = 1;
else if (pre->val == cur->val) count++;
else count = 1;
pre = cur;
if (count == maxCount) result.push_back(cur->val);
if (count > maxCount) {
maxCount = count;
result.clear();
result.push_back(cur->val);
}
searchBST(cur->right);
return ;
}
public:
vector<int> findMode(TreeNode* root) {
count = 0;
maxCount = 0;
TreeNode* pre = NULL; // 记录前一个节点
result.clear();
searchBST(root);
return result;
}
};
2.14 二叉搜索树的最近公共祖先235
题目描述:
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
(一个节点也可以是它自己的祖先)。
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == q || root == p || root == NULL) return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (left && right) return root;
if (left == NULL) return right;
return left;
}
};
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root->val > p->val && root->val > q->val)
return lowestCommonAncestor(root->left, p, q);
else if (root->val < p->val && root->val < q->val)
return lowestCommonAncestor(root->right, p, q);
else return root;
}
};
2.15 二叉搜索树中的插入操作701
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (root == NULL) {
TreeNode* node = new TreeNode(val);
return node;
}
if (root->val > val) root->left = insertIntoBST(root->left, val);
if (root->val < val) root->right = insertIntoBST(root->right, val);
return root;
}
};
class Solution {
private:
TreeNode* parent;
void traversal(TreeNode* cur, int val) {
if (cur == NULL) {
TreeNode* node = new TreeNode(val);
if (val > parent->val) parent->right = node;
else parent->left = node;
return;
}
parent = cur;
if (cur->val > val) traversal(cur->left, val);
if (cur->val < val) traversal(cur->right, val);
return;
}
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
parent = new TreeNode(0);
if (root == NULL) root = new TreeNode(val);
traversal(root, val);
return root;
}
};
2.16 删除二叉搜索树中的节点450
题目描述:
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
// 第一种情况:没找到删除的节点,遍历到空节点直接返回了
if (root == nullptr) return root;
if (root->val == key) {
// 第二种情况:左右孩子都为空(叶子节点),直接删除节点,返回NULL为根节点
if (!root->left && !root->right) return nullptr;
// 第三种情况:其左孩子空,右孩子不空,删除节点,右孩子补位 ,返回右孩子为根节点
else if (root->left == nullptr) return root->right;
// 第四种情况:其右孩子空,左孩子不空,删除节点,左孩子补位,返回左孩子为根节点
else if (root->right == nullptr) return root->left;
delete root;
// 第五种情况:左右都不为空,则将删除节点的左子树放到删除节点的右子树的最左面节点的左孩子的位置,并返回删除节点右孩子为新的根节点。
else {
TreeNode* cur = root->right; // 找右子树最左面的节点
while(cur->left) cur = cur->left;
cur->left = root->left; // 把要删除的节点左子树放在cur的左孩子的位置
TreeNode* tmp = root; // 把root节点保存一下,下面来删除
root = root->right; // 返回旧root的右孩子作为新root
delete tmp;
return root;
}
}
if (root->val > key) root->left = deleteNode(root->left, key);
if (root->val < key) root->right = deleteNode(root->right, key);
return root;
}
};
2.17 修剪二叉搜索树669
题目描述:
给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
if (root == nullptr) return nullptr;
if (root->val < low) return trimBST(root->right, low, high);
if (root->val > high) return trimBST(root->left, low, high);
root->left = trimBST(root->left, low, high);
root->right = trimBST(root->right, low, high);
return root;
}
};
2.18 将有序数组转换为二叉搜索树108
题目描述:
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。
class Solution {
private:
TreeNode* traversal(vector<int>& nums, int left, int right) {
if (left > right) return nullptr;
int mid = left + ((right - left) / 2);
TreeNode* root = new TreeNode(nums[mid]);
root->left = traversal(nums, left, mid - 1);
root->right = traversal(nums, mid + 1, right);
return root;
}
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
TreeNode* root = traversal(nums, 0, nums.size() - 1);
return root;
}
};
2.19 把二叉搜索树转换为累加树538
题目描述:
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
class Solution {
private:
int pre = 0;
void traversal(TreeNode* cur) { // 右中左
if (cur == NULL) return;
traversal(cur->right);
cur->val += pre;
pre = cur->val;
traversal(cur->left);
}
public:
TreeNode* convertBST(TreeNode* root) {
pre = 0;
traversal(root);
return root;
}
};