编程总结
每每刷完一道题后,其思想和精妙之处没有地方记录,本篇博客用以记录刷题过程中的遇到的算法和技巧
二叉树的遍历 – 前中后序遍历–递归法 – 等级:白银
- 确定递归函数的参数和返回值
- 确定终止条件
- 确定单层递归的逻辑
// 前序 根左右
void PreOrder(struct TreeNode *root, int *ret, int *retIndex)
{
if (root == NULL) {
return;
}
ret[(*retIndex)++] = root->val; // 根计算
PreOrder(root->left, ret, retIndex); // 左
PreOrder(root->right, ret, retIndex); // 右
}
// 中序 左根右
void inorder(struct TreeNode *root, int *res, int *resSize)
{
if (root == NULL) {
return;
}
inorder(root->left, res, resSize); // 左
res[(*resSize)++] = root->val; // 根
inorder(root->right, res, resSize); // 右
}
// 后序 左右根
void postorder(struct TreeNode *root, int *res, int *resSize)
{
if (root == NULL) {
return;
}
postorder(root->left, res, resSize);
postorder(root->right, res, resSize);
res[(*resSize)++] = root->val;
}
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int *preorderTraversal(struct TreeNode *root, int *returnSize)
{
int retIndex = 0;
int *ret = (int *)malloc(sizeof(int) * 100);
memset(ret, 0, sizeof(int) * 100);
PreOrder(root, ret, &retIndex);
*returnSize = retIndex;
return ret;
}
二叉树的层序遍历 – BFS – 等级:青铜
int **levelOrder(struct TreeNode *root, int *returnSize, int **returnColumnSizes)
{
*returnSize = 0;
if (root == NULL) {
return NULL;
}
struct TreeNode *queue[2000];
int **res = (int **)malloc(sizeof(int *) * 2000);
*returnColumnSizes = (int *)malloc(sizeof(int *) * 2000);
int rear = 0, front = 0, count = 0;
queue[rear++] = root; // 入队列
while (front != rear) { // 终止条件,队列不为空
int len = rear - front; // 当前层的节点数量
count = 0;
res[*returnSize] = (int *)malloc(sizeof(int)*(len));
for (int i = 0; i < len; i++) {
struct TreeNode *temp = queue[front++]; // 出队列
res[*returnSize][count++] = temp->val;
if (temp->left) {
queue[rear++] = temp->left; // 左节点入队列
}
if (temp->right) {
queue[rear++] = temp->right; // 右节点入队列
}
}
(*returnColumnSizes)[*returnSize] = len; // returnColunmnSizes每一层有多少元素
(*returnSize)++; // returnSize有多少层
}
return res;
}
翻转二叉树 – 等级:青铜
90% of our engineers use the softwar you wrote(Homebrew), but you can’t invert a binary tree on a whiteboard so fuck off.
struct TreeNode *invertTree(struct TreeNode *root)
{
if (root == NULL) { // 1.递归终止条件
return NULL;
}
// 后序遍历,左右根
struct TreeNode *left = invertTree(root->left);
struct TreeNode *right = invertTree(root->right);
// 交换 root 的左右孩子
root->left = right; // 先递下去,归的时候遇到节点时,交换其左右节点
root->right = left; // 本质时交换每个除根节点外节点的左右子节点
return root;
}
101. 对称二叉树 – 等级:青铜
bool isSymmetric(struct TreeNode *root)
{
if (root == NULL) {
return true;
}
return compareTree(root->left, root->right);
}
// 后序 -- 左右根 l_root 左子树, r_root 右子树
bool compareTree(struct TreeNode *left, struct TreeNode *right)
{
if (left == NULL && right == NULL) {
return true;
} else if (left == NULL && right != NULL) { // 异常情况:左空右不空;左不空右空;
return false;
} else if (left != NULL && right == NULL) {
return false;
}
if (left->val == right->val) {
bool outside = compareTree(left->left, right->right); // 外侧
bool inside = compareTree(left->right, right->left); // 内侧
return (outside && inside); // 后序遍历,左右根
}
return false;
}
二叉树最大深度
int maxDepth(struct TreeNode *root)
{
int depth1 = 0,depth2 = 0;
if (root == NULL) {
return 0;
}
// 后序遍历 -- 左右根
depth1 = maxDepth(root->left);
depth2 = maxDepth(root->right);
return fmax(depth1, depth2) + 1;
}
思路:
- 先递归 depth1 = maxDepth(root->right)找到右边最下的叶子节点,返回0;
- 执行 depth2 = maxDepth(root->left) 返回0;
- 返回 fmax(0,1) 为1,继续归回来的叶子节点的上层遍历。
二叉树最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
int minDepth(struct TreeNode *root)
{
int min_depth = 0;
int leftHight = 0;
int rightHight = 0;
if (root == NULL) {
return 0;
}
// 后序遍历 左右根
leftHight = minDepth(root->left);
rightHight = minDepth(root->right);
// 处理左右节点是空节点的情况,左为空右不为空,说明右边高1
if (root->left == NULL && root->right != NULL) {
return rightHight + 1;
}
// 右为空左不为空,说明左边高1
if (root->left != NULL && root->right == NULL) {
return leftHight + 1;
}
// 左右子树均不为空
min_depth = fmin(leftHight, rightHight) + 1;
return min_depth;
}
-------------------------------------------分割线---------------------------------------------------
110. 平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
思路:在下面求解平衡二叉树时,只不过是二叉树深度做了一点点变化,在每次都先判断深度差是否大于1.
int maxDepth(struct TreeNode *root) {
int depth1 = 0, depth2 = 0;
if (root == NULL) {
return 0;
}
//printf("root is %d, depth1 is %d ,depth2 is %d\n", root->val, depth1, depth2);
depth1 = maxDepth(root->left);
depth2 = maxDepth(root->right);
if (abs(depth1 - depth2) > 1) {
return -999999; // 在层次比较深的时候,注意返回值过小,后面+1可能会影响最终结果
}
return fmax(depth1, depth2) + 1;
}
bool isBalanced(struct TreeNode *root) {
if (root == NULL) {
return true;
}
return maxDepth(root) >= 0; // 判断非负即可
}
1)先递下去,往左开始,递到根节点
2)归回来一个节点,执行depth2 = maxDepth(root->right);
3)左右都执行完成后,回到节点
4)往右节点递下去
5)继续按之前的方式递,递归到满足终止条件为止
6)归到图中节点时,其右节点的深度减掉左节点NULL的深度 大于1,为非平衡节点,返回 -2
abs(depth1 - depth2) > 1
7)再归到上一节点时,由于depth2 == -2 ,上一节点直接返回 -2
8)再归到上一节点时,由于depth1 == -2 ,上一节点直接返回 -2,程序完成
563. 二叉树的坡度
给定一个二叉树,计算整个树的坡度。
一个树的节点的坡度定义即为,该节点左子树的结点之和和右子树结点之和的差的绝对值。空结点的的坡度是0。
整个树的坡度就是其所有节点的坡度之和。
int tilt = 0;
int traverse(struct TreeNode *root)
{
if (root == NULL) {
return 0;
}
int left = traverse(root->left);
int right = traverse(root->right);
tilt += abs(left - right);
return (left + right + root->val);
}
int findTilt(struct TreeNode *root) {
tilt = 0; // Attention : 用例之间有重复利用全局变量,一定要清除全局变量的值
traverse(root);
return tilt; // 返回坡度
}
建议不使用全局变量:
int traverse(struct TreeNode *root, int *res)
{
if (root == NULL) {
return 0;
}
int left = traverse(root->left, res);
int right = traverse(root->right,res);
*res += abs(left - right);
return left + right + root->val;
}
int findTilt(struct TreeNode *root) {
int res = 0;
traverse(root, &res);
return res; //返回坡度
}
1)先递左子树
2)递到叶子节点3,遇到3节点的左右子树均为NULL,递终止,计算该节点和,left + right + root->val 为 3.
3) 节点2 的左子树3完成,回到节点2,此时程序继续执行代码:int right = traverse(root->right);
4)对于节点1,其叶子左右节点均为NULL,递终止,返回
5)此时,节点2的左右节点均已完成,执行代码 tilt += abs(left - right),计算坡度
6)此时,左子树归完成,执行代码 right = traverse(root->right),继续对于3节点的右子树遍历一遍。
7)递到左节点6,其子节点都为NULL,所以递终止,执行代码:int right = traverse(root->right);
8)右节点5,其子节点都为NULL,递终止,执行归 return left + right + root->val;
9)回到节点3,其左右节点都访问完成,归回来了,执行代码 return left + right + root->val,
617. 合并二叉树
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
struct TreeNode *mergeTrees(struct TreeNode *t1, struct TreeNode *t2) {
if (t1 == NULL) { // 递终止条件
return t2;
}
if (t2 == NULL) {
return t1;
}
t1->val += t2->val;
t1->left = mergeTrees(t1->left, t2->left);
t1->right = mergeTrees(t1->right, t2->right);
return t1;
}
在遍历时,如果两棵树的当前节点均不为空,我们就将它们的值进行相加,并对它们的左孩子和右孩子进行递归合并;如果其中有一棵树为空,那么我们返回另一颗树作为结果;如果两棵树均为空,此时返回任意一棵树均可(因为都是空)
1)考虑合并的是如下的二叉树,左和右,计算 t1->val = t1->val + t2->val;
2)同时递左子树
3)继续递
4)到叶子节点了,递终止,返回 NULL
5)再往节点5的右节点递下去,此时,t2 == NULL 返回 t1
6)这时根节点的左边节点都已经遍历完成,开始往节点遍历:
7)遇到节点9的左节点为NULL,return t2
8)此时本来节点9无左节点,由于 t1->left = t2(赋值),所以有了新节点
9)接着访问右节点
10)同理赋值给了 t1->right
11)递归结束,回到根节点
965.单值二叉树
如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树。
只有给定的树是单值二叉树时,才返回 true;否则返回 false。
思路:就是判断二叉树的所有值都相等
但是二叉树里可以有NULL,NULL节点不算和其他节点不相等,所以需要处理好。
方法是如下,挺巧妙的
int value = 0;
int UnivalTree(struct TreeNode *root)
{
if (root == NULL) { // 递终止条件,也避免了和NULL节点比较
return true;
}
if (root->val != value) { // 都与根节点去比较,不用再多构造变量左右来比较
return false;
}
return UnivalTree(root->left) && UnivalTree(root->right);
}
bool isUnivalTree(struct TreeNode *root) {
if (root == NULL) {
return true;
}
value = root->val;
return UnivalTree(root);
}
572. 另一个树的子树
给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
示例 1:
给定的树 s:
3
/ \
4 5
/
1 2
给定的树 t:
4
/
1 2
返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。
bool compare(struct TreeNode* root1,struct TreeNode* root2)
{
if(root1 == NULL && root2 == NULL)
return true;
if(root1 == NULL || root2 == NULL)
return false;
if(root1->val != root2->val)
return false;
return compare(root1->left,root2->left) && compare(root1->right,root2->right); // 递归比较左右
}
bool isSubtree(struct TreeNode* s, struct TreeNode* t)
{
if(s == NULL && t != NULL) // s已经到底
return false;
return compare(s,t) || isSubtree(s->left,t) || isSubtree(s->right,t); // 判 根 左 右
}
给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
bool check(TreeNode *o, TreeNode *t) {
if (!o && !t) { // o 和 t 都到根节点了,返回 true
return true;
}
if ((o && !t) || (!o && t) || (o->val != t->val)) { // o 和 t有一个到了根节点另一个没有到,或者值不相等,返回false
return false;
}
// 在 o 节点和 t 节点相等时,递归下去
return check(o->left, t->left) && check(o->right, t->right);
}
bool dfs(TreeNode *o, TreeNode *t) {
if (!o) {
return false;
}
// 如果没有check(o,t)不相等,就递归o->left 和 o->right
return check(o, t) || dfs(o->left, t) || dfs(o->right, t);
}
bool isSubtree(TreeNode *s, TreeNode *t) {
return dfs(s, t);
}
114. 二叉树展开为链表
给你二叉树的根结点 root ,请你将它展开为一个单链表:
展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
展开后的单链表应该与二叉树 先序遍历 顺序相同。
void PreOrder(struct TreeNode *root, int *ret, int *retIndex)
{
if (root == NULL) {
return;
}
ret[(*retIndex)++] = root->val;
PreOrder(root->left, ret, retIndex);
PreOrder(root->right, ret, retIndex);
}
int preorderTraversal(struct TreeNode *root, int *returnSize)
{
int retIndex = 0;
int i = 1;
int *ret = (int *)malloc(sizeof(int) * 2000); // 2000是因为题目树中节点数为2000
memset(ret, 0, sizeof(int)*2000);
PreOrder(root, ret, &retIndex);
*returnSize = retIndex;
struct TreeNode *root_tmp = root;
root_tmp->val = ret[0];
root_tmp->right = NULL;
root_tmp->left = NULL;
if (*returnSize == 1) {
root = root_tmp;
return 0;
}
// 先做先序遍历,结果保留在ret里面,然后将二叉树重组回去
while (i < *returnSize) {
struct TreeNode *tmp = NULL;
tmp = (struct TreeNode *)malloc(sizeof(struct TreeNode));
tmp->val = ret[i];
tmp->right = NULL;
tmp->left = NULL;
root_tmp->right = tmp;
root_tmp->left = NULL;
root_tmp = root_tmp->right;
i++;
}
free(ret);
return 0;
}
void flatten(struct TreeNode *root)
{
int returnSize = 0;
if (root == NULL) {
return;
}
preorderTraversal(root, &returnSize);
}
剑指 Offer 54. 二叉搜索树的第k大节点
给定一棵二叉搜索树,请找出其中第k大的节点。
本文解法基于此性质:二叉搜索树的中序遍历为 递增序列
void PreOrder(struct TreeNode *root, int *ret, int *retIndex)
{
if (root == NULL) {
return;
}
PreOrder(root->left, ret, retIndex);
ret[(*retIndex)++] = root->val; // 中序遍历
PreOrder(root->right, ret, retIndex);
}
int kthLargest(struct TreeNode *root, int k)
{
int retIndex = 0;
int i = 1;
int *ret = (int *)malloc(sizeof(int) * 10000);
memset(ret, 0, sizeof(int) * 10000);
PreOrder(root, ret, &retIndex);
return ret[retIndex - k];
}
112. 路径总和
思路是:
- 先判断根节点,因为下面要访问 root->left 和 root->right;
- 递归终止条件:判断叶子节点是否满足;
- 递归左节点下去;
- 递归右节点下去;
// 注意递归终止条件
bool hasPathSum(struct TreeNode *root, int sum)
{
if (root == NULL) {
return false; // 手法1:不能找根节点,这是不符合题意的,不能写 return sum == 0; 根节点返回false.
}
// 手法2:找到叶子节点,题目要求找叶子节点的路径和,此时判断 sum 是否== target.
if (root->left == NULL && root->right == NULL) {
return sum == root->val;
}
if (hasPathSum(root->left, sum - root->val) == true) {
return true;
}
if (hasPathSum(root->right, sum - root->val) == true) {
return true;
}
return false;
}