前言
最近也刷了点leetcode的题目,画了几页草稿纸,一边画一边分析,发现过程还是挺有意思的,拿出几题正经的画了画图,也算加深理解了,主要还是链表、二叉树有关的。可能是个人习惯,都是用C语言实现的,先定义俩数据结构,就直接开始了。
struct ListNode {
int val;
struct ListNode *next;
};
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
二叉树右视图
leetcode199:给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
分析: 从右边看,其实就是层序遍历,而我们能看到的是每一层最右边的节点。
执行步骤:
- 层序遍历一般用队列实现,先将
root入队列 - 计算当前队列大小,也就是上一层中节点的数量
queueSize - 循环取出队列节点,同时将左右子节点入队列,判断是最后一个取出的节点,加入到返回结果中
- 循环2、3步骤,直到队列空了。
int* rightSideView(struct TreeNode* root, int* returnSize){
* returnSize = 0;
if (root == NULL) return NULL;
int *res = malloc(sizeof(int));
struct TreeNode **queue = malloc(sizeof(struct TreeNode *) * 10000);
int head = 0; // 队头
int tail = 0; // 队尾
queue[tail++] = root; // 1、先加入根节点root
while (head < tail) {
int size = tail - head; // 2、计算当前队列size
for (int i = 0; i < size; i++) {
struct TreeNode *node = queue[head++]; // 3.1、取出队列节点
if (i == size - 1) { // 3.2、最右一个节点加入res
res[(*returnSize)++] = node->val;
res = realloc(res, sizeof(int) * ((*returnSize) + 1));
}
// 3.3、将左右子节点加队列
if (node->left) queue[tail++] = node->left;
if (node->right) queue[tail++] = node->right;
}
}
return res;
}
Top K问题
leetcode215:无序数组中找到第K大的数,并返回。
分析:首先想到将数组排序,然后取出arr[size - k]的元素。这里考虑使用二叉堆来解决,只需将k个元素排序即可,二叉堆也是二叉树的一种,特点是任意节点的值大于其子节点的值,根节点是最大值。
- 这个数组是二叉树的层序遍历,所以需要把这个数组想象成一颗二叉堆,根据堆的特点移动最大值到根节点(也就是数组的第一个元素),获取最大值,并重复K次。
- 移动节点这里使用二叉堆的上滤,节点和其左右节点比较,如果子节点大于父节点,将二者交换位置,意味是从二叉树的由底至顶的第一个非叶子节点开始,因为要和子节点比较,叶子节点是没有子节点的。
- 时间复杂度:O(nLogN)
执行步骤:
- 建立二叉堆,第一个非叶子节点的索引通过数组长度得到
size/2 - 1, 从下往上的对[0, size/2 - 1]闭区间内的节点执行一次上滤,获得最大值。 - 根据当前节点索引为
parent,可得到子节点索引child = parent*2 + 1,parent和较大的子节点比较,如果较大子节点大于parent,则交换父子节点位置。 - 交换完成后,继续向下移动,
parent = child。 - 循环执行步骤2、3,对
[0, size/2 - 1]区间所有节点完成下滤,就完成二叉堆建立。 - 数组首个元素则为最大值,将首位0和末尾end元素交换位置
- 排除末尾的最大值,再次对索引
[0, end - 1]进行上滤,重复步骤2、3 - 循环K次执行步骤5、6,最K大的数为
arr[size - k]
节点从下往上下滤的过程,对应步骤1、2、3、4
交换最大值,步骤5、6、7
// 下滤
void heapify(int *nums, int start, int end) {
int parent = start;
int child = parent*2 + 1;
while (child <= end) {
// 获取左右节点中较大的子节点,索引记为child
if (child + 1 <= end && nums[child + 1] > nums[child]) child++;
if (nums[parent] < nums[child]) {
swap(&nums[parent], &nums[child]); // 交换父节点和较大子节点位置
parent = child; // 继续向下移动
child = parent*2 + 1;
} else {
break;
}
}
}
int findKthLargest(int* nums, int numsSize, int k){
// 对[0, numsSize/2 - 1]区间内由下到上遍历节点,并对节点进行下滤
for (int i = numsSize/2 - 1; i >= 0; i--) {
heapify(nums, i, numsSize - 1);
}
for (int i = 0; i < k; i++) {
int end = numsSize - 1 - i; // 末尾的索引
swap(&nums[0], &nums[end]); // 数组首位最大值和末尾交换位置
heapify(nums, 0, end - 1); // [0, end - 1]进行下滤,-1是排除当前最大值
}
return nums[numsSize - k];
}
复制包含随机指针的复杂链表
leetcode138:对每个节点包含随机指针random的深拷贝,random可以指向链表中的任何节点或空节点。
分析:深拷贝说明得创建新节点,由于是随机指针,先考虑遍历将新节点全部创建出来,再对random赋值
执行步骤:
- 遍历原节点创建对应的新节点,每个新创建的节点是在原节点后面,原节点和新节点紧挨着
- 再次遍历,设置新节点的
random指针,则有node->next->random = node->random->next - 删除原节点,返回新节点的头节点
- 时间复杂度:O(N)
为了方便看图,省略了random指针的指向,使用节点在链表的位置代替,从0开始,
random=0表示指向链表第0个节点
struct Node {
int val;
struct Node *next;
struct Node *random;
};
struct Node* copyRandomList(struct Node* head) {
if (head == NULL) return NULL;
struct Node* cur = head;
// 1、插入新节点
while (cur) {
struct Node *tmp = cur->next;
// 原节点创建对应的新节点,每个新创建的节点是在原节点后面
struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
newNode->val = cur->val;
newNode->next = cur->next;
cur->next = newNode;
cur = tmp;
}
// 2、赋值random指针
cur = head;
while (cur && cur->next) {
if (cur->random) {
cur->next->random = cur->random->next;
} else { // 随机指针是NULL情况
cur->next->random = NULL;
}
cur = cur->next->next;
}
struct Node *dummyHead = (struct Node *)malloc(sizeof(struct Node));
dummyHead->val = 0;
dummyHead->next = head;
// 3、删除原节点
cur = dummyHead;
while (cur->next) {
cur->next = cur->next->next;
cur = cur->next;
}
return dummyHead->next;
}
二叉树展开为链表形式
leetcode114:将二叉树展开为链表,展开后的链表和二叉树前序遍历一致 分析:要求展开后和前序遍历一致,可以将节点通过前序遍历存放到数组,再次遍历重组链表,两次遍历时间复杂度为O(N),空间复杂度O(N),存放节点数组的大小 执行步骤:
- 二叉树前序遍历将节点放到数组。
- 再次遍历数组,取出节点,将节点的左子树设置NULL,右子树按遍历顺序依次设置。
void perorderTraversal(struct TreeNode* root, struct TreeNode** arr, int *size) {
if (root == NULL) return root;
arr[(*size)++] = root;
perorderTraversal(root->left, arr, size);
perorderTraversal(root->right, arr, size);
}
void flatten(struct TreeNode* root){
if (root == NULL) return root;
struct TreeNode **arr = malloc(sizeof(struct TreeNode*) * 2001);
int size = 0;
// 前序遍历,存放每个二叉树节点
perorderTraversal(root, arr, &size);
struct TreeNode* cur = arr[0]; // 从根节点开始
// i从索引1开始
for (int i = 1; i < size; i++) {
struct TreeNode *node = arr[i];
cur->right = node; // 右子树依次从arr取出
cur->left = NULL; // 设置左子树为NULL
cur = node;
}
return root;
}