再刷一遍二叉树

本文详细介绍了四种二叉树操作的算法:226题的翻转二叉树、114题的二叉树展开为链表、116题的填充每个节点的下一个右侧节点指针,以及105、106题的根据遍历序列构造二叉树。每种算法都提供了清晰的递归解决方案,有助于深入理解二叉树结构及其操作。

简单复习二叉树系列算法

(一)

226. 翻转二叉树

226. 翻转二叉树
翻转一棵叉树。
在这里插入图片描述

//翻转二叉树 ✔

public TreeNode invertTree(TreeNode root){
	//递归结束条件
	if(root == null) return null;
	
	//思考在此节点上要做的事
	//即交换左右节点
	TreeNode temp;
	temp = root.left;
	root.left = root.right;
	root.right = temp;
	
	//使此节点的左右子树也进行翻转
	invertTree(root.left);
	invertTree(root.right);
	
	return root;
}


114. 二叉树展开为链表

114. 二叉树展开为链表
给你二叉树的根结点 root ,请你将它展开为一个单链表:
① 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
② 展开后的单链表应该与二叉树 先序遍历 顺序相同。

在这里插入图片描述

//二叉树展开为链表 ✔

public void flatten(TreeNode root){
	if(root == null) return;

	//左子树展开,右子树展开
	flatten(root.left);
	flatten(root.right);
	
	//①根节点的左节点为空,②右节点为原左节点,③新右节点的最尾端接原右节点
	
	//left,right先存一下原左右节点
	TreeNode left = root.left;
	TreeNode right = root.right;
	
	//①左节点为空,②右节点为原左节点
	root.left = null;
	root.right = root.left;
	
	//③新右节点的最尾端接原右节点
	//找到最尾端
	TreeNode p = root;
	while(p.right != null){
		p = p.right;
	}
	p.right = right;
}


116. 填充每个节点的下一个右侧节点指针

116. 填充每个节点的下一个右侧节点指针
填充二叉树的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
在这里插入图片描述

//填充每个节点的下一个右侧节点指针 ✔

public Node connect(Node root) {
	if(root == null) return null;
	//不能简单的 【root.left.next = root.right;】相连
	//要考虑不在同一子树中的相邻节点的连接
	//构造一个辅助函数
	connectTwoNode(root.left, root.right);
	return root;
}
public void connectTwoNode (Node node1. Node node2){
	if(node1 == null || node2 == null) return;
	node1.next = node2;
	//node1的左右子节点之间相连
	connectTwoNode(node1.left, node2.right);
	//node1的右节点和 node2的左节点之间相连
	connectTwoNode(node1.right, node2.left);
	//node2的左右子节点之间相连
	connectTwoNode(node2.left, node2.right);
}



(二)

654. 最大二叉树

654. 最大二叉树
给定一个不含重复元素的整数数组 nums 。一个以此数组直接递归构建的 最大二叉树 定义如下:
① 二叉树的根是数组 nums 中的最大元素。
② 左子树是通过数组中 最大值左边部分 递归构造出的最大二叉树。
③ 右子树是通过数组中 最大值右边部分 递归构造出的最大二叉树。
在这里插入图片描述

//最大二叉树 ✔

public TreeNode constructMaximumBinaryTree(int[] nums) {
	return build(nums, 0, nums.length-1);
}
//构造一个辅助函数,参数【数组,数组起始,数组结尾】
public TreeNode build (int[] nums, int l, int r){
	//递归结束条件
	if(l > r) return null;
	
	//构造最大二叉树,二叉树的根是数组 nums 中的最大元素。
	//找到根节点,构造根节点,并记录此值在数组中的位置
	int maxVal = nums[l];
	int index = l;
	for(int i=l; i<=r; i++){
		if(maxVal < nums[i]){
			maxVal = nums[i];
			index = i;
		}
	}
	//构造根节点
	TreeNode root = new TreeNode(maxVal);
	//左子树是通过数组中 最大值左边部分 递归构造出的最大二叉树
	//右子树是通过数组中 最大值右边部分 递归构造出的最大二叉树
	//构造左、右子树
	root.left = build(nums, l, index-1);
	root.right = build(nums, index+1, r);
	
	return root;
}


105. 从前序与中序遍历序列构造二叉树

105. 从前序与中序遍历序列构造二叉树
给定一棵树的前序遍历 preorder 与中序遍历 inorder。请构造二叉树并返回其根节点。
在这里插入图片描述

//从前序与中序遍历序列构造二叉树 ✔

public TreeNode buildTree(int[] preorder, int[] inorder){
	return build(preorder, 0, preorder.length-1, inorder, 0, inorder.length-1);
}
public TreeNode build(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd){
	//结束条件
	if(preStart > preEnd) return null;
	//二叉树的根节点是前序遍历的第一个值
	//要在中序遍历中寻找到根节点的位置,以此可将序列分为左子树的序列和右子树的序列。
	int rootVal = preorder[preStart];
	int index = -1;
	for(int i=inStart; i<=inEnd; i++){
		if(inorder[i] == rootVal){
			index = i;
			break;
		}
	}
	TreeNode root = new TreeNode(rootVal);

	//要把前序遍历的序列也分出左右子树的序列
	//设 leftSize为左子树节点个数,对前序遍历来说,【preStart+1, preStart+leftSize】即为左子树的序列。
	int leftSize = index - inStart;
	//构造左右子树
	root.left = build(preorder, preStart+1, preStart+leftSize, inorder, inStart, index-1);
	root.right = build(preorder, preStart+leftSize+1, preEnd, inorder, index+1, inEnd);

	return root;
}


106. 从中序与后序遍历序列构造二叉树

106. 从中序与后序遍历序列构造二叉树
根据一棵树的中序遍历与后序遍历构造二叉树。
在这里插入图片描述

//从中序与后序遍历序列构造二叉树 ✔

public TreeNode buildTree(int[] inorder, int[] postorder){
	return build(inorder, 0, inorder.length-1, postorder, 0, postorder.length-1);
}
public TreeNode build(int[] inorder, int inStart, int inEnd, int[] postorder, int postStart, int postEnd){
	if(inStart > inEnd) return null;
	//与上题相似,只是根节点为后序遍历序列的最后一个值
	int rootVal = postorder[postEnd];
	//中序遍历中寻找根节点
	int index = -1;
	for(int i=inStart; i<=inEnd; i++){
		if(inorder[i] == rootVal){
			index = i;
			break;
		}
	}

	TreeNode root = new TreeNode(rootVal);
	int leftSize = index - inStart;
	//构造左右子树
	root.left = build(inorder, inStart, index-1, postorder, postStart, postStart+leftSize-1);
	root.right = build(inorder, index+1, inEnd, postorder, postStart+leftSize, postEnd-1);

	return root;
}



(三)

652. 寻找重复的子树

652. 寻找重复的子树
给定一棵二叉树,返回所有重复的子树。对于同一类的重复子树,你只需要返回其中任意一棵的根结点即可。
两棵树重复是指它们具有相同的结构以及相同的结点值。
在这里插入图片描述

//寻找重复的子树 ✔

// 用哈希表记录所有子树以及出现的次数
HashMap<String, Integer> memo = new HashMap<>();
// 以列表的形式返回上述重复子树的根结点
LinkedList<TreeNode> res = new LinkedList<>();

public List<TreeNode> findDuplicateSubtrees(TreeNode root){
	findSubtrees(root);
	return res;
}
public String findSubtrees(TreeNode root){
	//二叉树序列化,空节点用 “#” 表示
	if(root == null) return "#";
	String left = findSubtrees(root.left);
	String right = findSubtrees(root.right);
	
	//序列化过程中出现的子树序列
	String subTree = left + "," + right + "," + root.val;
	
	//getOrDefault方法,获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值
	int count = memo.getOrDefault(subTree, 0);
	//count为 1说明有两个一样的子树,将此节点加入 res 列表中
	if(count == 1) res.add(root);
	//子树存入哈希表
	memo.put(subTree, count+1);
	return subTree;
}



😛😛感谢看到这里,希望对理解二叉树有帮助。
<think>我们正在为准备LeetCode算法题的用户总结C语言基础语法。LeetCode题目通常涉及数据结构、算法逻辑和编程技巧,因此需要掌握C语言的核心语法和常用库函数。以下内容将重点覆盖LeetCode题中常用的C语言知识点。 ### 一、基本结构 每个C程序都包含一个main函数,但LeetCode的题目通常只需要实现指定的函数,不需要完整的main函数。例如: ```c // LeetCode 题目示例:两数之和 int* twoSum(int* nums, int numsSize, int target, int* returnSize) { // 函数实现 } ``` ### 二、数据类型 1. **基本类型**: - 整型:`int`(常用)、`long`、`long long`(用于大整数) - 浮点型:`float`、`double`(LeetCode较少用) - 字符型:`char` 2. **数组**:固定大小,如`int arr[10]`;动态数组通常通过指针和内存管理实现。 3. **指针**:C语言核心,用于操作数组、字符串、动态内存等。 4. **结构体**:用于定义复杂数据结构,如链表节点、树节点等。 ```c struct ListNode { int val; struct ListNode *next; }; ``` ### 三、控制结构 1. **条件语句**: ```c if (condition) { ... } else if (condition) { ... } else { ... } ``` 2. **循环语句**: - `for`循环:`for (int i=0; i<n; i++) { ... }` - `while`循环:`while (condition) { ... }` - `do-while`循环:较少用 ### 四、函数 - 函数定义:注意返回类型和参数类型。 - 递归:常用于树、链表等题目。 ### 五、动态内存管理 - 分配:`malloc`、`calloc` ```c int* arr = (int*)malloc(n * sizeof(int)); // 动态数组 ``` - 释放:`free`(LeetCode题目中通常不需要手动释放,但实际开发中必须注意) ### 六、字符串处理 - C字符串以`'\0'`结尾,常用库函数: - `strlen(s)`:长度 - `strcpy(dest, src)`:复制 - `strcmp(s1, s2)`:比较 - `strcat(dest, src)`:拼接 - 注意:字符串操作常涉及指针和内存管理。 ### 七、常用库函数 1. **输入输出**:LeetCode中不需要,但调试时可用`printf`。 2. **数学函数**:`#include <math.h>` - `abs()`、`fabs()`:绝对值 - `pow()`、`sqrt()`:幂和平方根 3. **排序与搜索**:`#include <stdlib.h>` - `qsort()`:快速排序函数,需自定义比较函数。 ```c int compare(const void* a, const void* b) { return (*(int*)a - *(int*)b); } qsort(arr, n, sizeof(int), compare); ``` ### 八、数据结构实现 1. **数组**:最基础,随机访问O(1)。 2. **链表**: - 单链表:定义见前文结构体。 - 操作:插入、删除、反转等。 3. **栈与队列**: - 栈:数组模拟(静态大小)或链表实现。 - 队列:数组循环队列或链表实现。 4. **树**: - 二叉树:节点包含值、左右子节点指针。 ```c struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; }; ``` - 遍历:递归(前序、中序、后序)或迭代(使用栈)。 ### 九、算法常用技巧 1. **双指针**:数组、链表问题常用。 2. **递归与分治**:树、排序(如归并排序)等。 3. **哈希表**:C标准库无内置,LeetCode题目中可自己实现简单哈希或使用`uthash`库(但通常题目允许时用数组代替)。 4. **位运算**:`&`, `|`, `^`, `~`, `<<`, `>>`。 ### 十、调试与注意事项 - 数组越界、空指针访问是常见错误。 - 内存泄漏:动态分配后未释放(LeetCode通常忽略,但需注意)。 - 边界条件:空输入、单个元素等。 ### 示例题目:反转链表 ```c struct ListNode* reverseList(struct ListNode* head) { struct ListNode *prev = NULL, *curr = head, *next = NULL; while (curr != NULL) { next = curr->next; curr->next = prev; prev = curr; curr = next; } return prev; } ``` 通过掌握以上基础语法和技巧,可以解决LeetCode上的大部分简单和中等题目。建议从简单题开始,逐步提升难度。 §§相关问题§§ 1. C语言中如何实现动态数组? 2. 在LeetCode中如何用C语言处理字符串问题? 3. C语言中常用的排序算法有哪些?如何实现? 4. 如何用C语言实现二叉树的前序遍历? 5. C语言中内存泄漏如何避免? 6. 双指针技巧在数组问题中的应用有哪些? [^1]: 通过以上的题指南,你可以逐步用 C 语言征服 LeetCode 上的 50 道经典算法题。在题的过程中,不断积累经验,提升自己的算法能力和编程水平。相信经过一段时间的努力,你会在算法领域取得显著的进步。 [^2]: 【C语言题——7道leetcode简单题】。通过以上的7道leetcode小题,不知道对你是否有所启发与帮助。还是那三个字:多练习。不要好高骛远,要夯实自己的基础,多多总结,这总是有好处的,熟能生巧嘛,最后,这次题就先到这里结束了,觉得不错的话,也可以点个赞哦!🌹</think>### C语言基础语法总结(LeetCode算法题适用) #### 一、核心数据结构 1. **数组与指针** - 静态数组:`int arr[10];` - 动态数组:`int* arr = (int*)malloc(n * sizeof(int));` - 指针操作:`*(arr+i)` 等价于 `arr[i]` ```c // 示例:双指针法(反转数组) void reverse(int* nums, int numsSize) { int left = 0, right = numsSize - 1; while (left < right) { int temp = nums[left]; nums[left++] = nums[right]; nums[right--] = temp; } } ``` 2. **字符串处理** - 字符串本质:`char str[] = "hello";`(以 `\0` 结尾) - 常用函数: - `strlen(s)` 获取长度 - `strcpy(dest, src)` 复制字符串 - `sprintf` 格式化字符串 3. **结构体**(链表/树必备) ```c // 链表节点 struct ListNode { int val; struct ListNode* next; }; // 二叉树节点 struct TreeNode { int val; struct TreeNode* left; struct TreeNode* right; }; ``` --- #### 二、内存管理(重点!) 1. **动态分配** - `malloc(size)`:分配未初始化内存 - `calloc(n, size)`:分配并初始化为0 - `realloc(ptr, size)`:调整已分配内存大小 ```c // 动态二维数组 int** createMatrix(int rows, int cols) { int** matrix = (int**)malloc(rows * sizeof(int*)); for (int i = 0; i < rows; i++) { matrix[i] = (int*)calloc(cols, sizeof(int)); } return matrix; } ``` 2. **内存释放** - `free(ptr)`:释放后需置空 `ptr = NULL` - 内存泄漏检查工具:Valgrind --- #### 三、算法常用技巧 1. **递归模板** ```c int recursion(int param) { // 终止条件 if (base_case) return value; // 递归处理子问题 int result = recursion(new_param); // 合并结果 return process(result); } ``` 2. **排序与搜索** - 快速排序:`qsort(arr, n, sizeof(int), compare);` - 二分查找: ```c int binarySearch(int* arr, int size, int target) { int left = 0, right = size - 1; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == target) return mid; if (arr[mid] < target) left = mid + 1; else right = mid - 1; } return -1; } ``` --- #### 四、LeetCode实战技巧 1. **输入输出规范** - 函数接口已定义,只需实现函数体 - 返回数组时需设置返回大小:`*returnSize = result_len;` 2. **调试方法** - 本地测试框架: ```c int main() { int nums[] = {1,2,3,4}; int returnSize; int* result = twoSum(nums, 4, 5, &returnSize); // 打印结果 free(result); } ``` 3. **性能优化** - 用空间换时间:哈希表(`uthash.h` 第三方库) - 避免重复计算:记忆化搜索 - 循环优化:减少嵌套层数 --- #### 五、必学标准库函数 | 头文件 | 常用函数 | LeetCode应用场景 | |--------------|-----------------------------|-----------------------| | `<stdlib.h>` | malloc/free, qsort, rand | 动态内存/排序/随机数 | | `<string.h>` | strlen, strcpy, memset | 字符串处理 | | `<math.h>` | sqrt, pow, abs | 数学运算 | | `<ctype.h>` | isdigit, tolower | 字符处理 | > 提示:LeetCode已包含常用标准库,无需额外引入[^1]。 --- ### 学习路径建议 1. **初级阶段**:掌握指针、数组、结构体的内存模型 2. **中级阶段**:熟练动态内存管理+递归应用 3. **高级阶段**:实现经典数据结构(哈希表、堆) > 多练习基础题是提升的关键[^2]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值