当前位置: 首页 > article >正文

分治的思想(力扣965、力扣144、牛客KY11)

引言

分治思想是将问题分解为更小子问题,分别解决后再合并结果。二叉树中常用此思想,因其结构递归,易分解为左右子树问题,递归解决后合并结果。

这篇文章会讲解用分治的思想去解决二叉树的一些题目,顺便会强调在做二叉树的题过程中我容易出错的地方。

单值二叉树:力扣965

我们来看一个题目,这个题就用到了分治的思想:

题目链接:965. 单值二叉树 - 力扣(LeetCode)

7605f75e223e4bdb9247a9441312bd20.png

要想检查一棵树里所有节点的值是否相等,最简单的方式是把每个节点挨个挨个遍历一遍,看看每个节点的值是否与孩子节点(孩子节点不为空的情况下)的值相等。但是这就没有用到分治的思想了。

如何通过分治的思想来解决问题呢?把大问题拆成很多个小问题。求一棵树里所有节点的值是否相等(大问题),就是求左右子树里节点的值是否与根节点的值相等(小问题)。

bool isUnivalTree(struct TreeNode* root) {
    if(root==NULL){
        return true;
    }
    if((root->left&&root->val!=root->left->val)||(root->right&&root->val!=root->right->val))
    {
        //检查左右孩子的值是否与根的值相等
        return false;
    }
    return isUnivalTree(root->left)&&isUnivalTree(root->right);
}

二叉树的前序遍历:力扣144

如果学过二叉树的基础知识掌握的比较好,那它的前序遍历对你来说就是小菜一碟。那为什么这里还要把它拿出来讲解呢?我们先来看一下这道题:144. 二叉树的前序遍历 - 力扣(LeetCode)

我们发现,这道题比我们平时实现的二叉树的前序遍历又额外提了一个要求:将节点的值按照前序遍历的顺序存放在数组中,并将这个数组作为返回值返回。

额外补充一句:在力扣中,如果要求我们返回数组,那我们需要记录它的数组大小。在这个题中,preOrderTraversal函数中的第二个参数returnSize记录的就是最后要返回数组的大小。

分析

其实,只要掌握了二叉树的前序遍历,就算是把里面节点的值按照前序遍历的顺序存放在数组中也不难,但是里面我们有些点需要注意:

首先,我们需要先计算出二叉树中有几个节点,这样我们才好根据节点个数来开辟数组。

附上代码:

int treeSize(struct TreeNode* root)
{
	if (root == NULL) {
		return 0;
	}
	return treeSize(root->left) + treeSize(root->right) + 1;
}

需要注意的问题

然后,我们要再弄一个进行前序遍历的函数,每遍历到一个节点就把它存进数组。

易错代码:

void preOrder(struct TreeNode* root,int* a,int i)
{
    if(root==NULL){
        return;
    }
    a[i++]=root->val;
    preOrder(root->left,a,i);
    preOrder(root->right,a,i);
}
int* preorderTraversal(struct TreeNode* root, int* returnSize) {
    int size=treeSize(root),i=0;
    int* a=(int*)malloc(sizeof(int)*size);
    preOrder(root,a,i);
    *returnSize=i;
    return a;
}

这段代码中存在一些问题,特别是在递归函数 preOrder 中对索引 i 的处理上。

问题分析:

1.索引传递问题: 在 preOrder 函数中,索引 i 是以值传递的方式传递的。这意味着每次递归调用 preOrder 时,都会创建一个新的 i 的副本,而这个副本在递归调用结束后就会被销毁。因此,递归调用中的 i++ 操作并不会影响到外层调用中的 i 值。

举个例子,当 preOrder 第一次被调用时,i 的值是 0。在将根节点的值存入数组后,i 被增加到 1,但这个增加的 i 只在当前函数栈帧中有效。当递归调用左子树或右子树时,i 又会重新从 0 开始,这显然是不正确的。

2.数组大小问题: 虽然数组 a 的大小是通过 treeSize 函数正确计算出来的,但由于索引传递的问题,最终可能并不会填满整个数组。实际上,由于每次递归调用都使用了一个新的 i 副本,所以可能只会在数组的第一个位置存储值。
3.返回大小问题: 由于索引 i 没有正确传递和更新,*returnSize 最终会被设置为一个错误的值。这意味着调用者无法知道实际存储了多少个节点值。

解决方案:

要解决这个问题,需要确保索引 i 在递归调用中能够正确传递和更新:

  • 使用指针传递索引:将 i 的地址传递给 preOrder 函数,这样函数就可以直接修改 i 的值了。

有些人会想到用静态变量或全局变量的做法,但是这里不推荐,这种方法虽然可以工作,但会使函数变得不那么通用和可重用。

正确代码:

void preOrder(struct TreeNode* root,int* a,int* pi)
{
    if(root==NULL){
        return;
    }
    a[(*pi)++]=root->val;
    preOrder(root->left,a,pi);
    preOrder(root->right,a,pi);
}

int* preorderTraversal(struct TreeNode* root, int* returnSize) {
    int size=treeSize(root),i=0;
    int* a=(int*)malloc(sizeof(int)*size);
    preOrder(root,a,&i);
    *returnSize=i;
    return a;
}

二叉树遍历:牛客KY11

题目链接:二叉树遍历_牛客题霸_牛客网

这个题目要求我们根据一串二叉树先序遍历字符串建立链式存储的二叉树,其中#代表该节点为空节点。

分析

我们先根据题目中给出的示例1将二叉树还原出来:abc##de#g##f###

79831e2ce09a45498d384f77c81cc41a.png

结合还原的过程,我们看一下如何通过遍历前序遍历字符串来还原二叉树:

先序遍历的特点是“根左右”,即先访问根节点,再访问左子树,最后访问右子树。

当我们遍历前序遍历字符串时,遇到 # 则返回 NULL,否则创建新节点并递归构建左右子树。

但这里有一个点需要注意,与上一道题中遍历数组时更新索引是一个问题。为了保证索引的正确传递和更新,我们需要往函数中传入索引的地址。

附上代码:

#include <stdio.h>
 
typedef struct binaryTreeNode{
    struct binaryTreeNode* left,*right;
    char val;
}BTNode;
BTNode* createTree(char* a,int* pi)
{
    if(a[(*pi)]=='#'){
        (*pi)++;
        return NULL;
    }
    BTNode* node=(BTNode*)malloc(sizeof(node));
    if(node==NULL){
        perror("malloc fail");
        return NULL;
    }
    node->val=a[(*pi)++];
    node->left=createTree(a, pi);
    node->right=createTree(a, pi);
    return node;
}
void inOrder(BTNode* root)
{
    if(root==NULL){
        return;
    }
     
    inOrder(root->left);
    printf("%c ",root->val);
    inOrder(root->right);
}
int main() {
    char a[100];
    scanf("%s",a);
    int i=0;
    BTNode* tree=createTree(a, &i);
    inOrder(tree);
    return 0;
}

 


http://www.kler.cn/a/420307.html

相关文章:

  • 互联网 Java 面试八股文汇总(2025 最新整理)
  • ElasticSearch easy-es 聚合函数 group by 混合写法求Top N 词云 分词
  • 040集——CAD中放烟花(CAD—C#二次开发入门)
  • java将word docx pdf转换为图片(不需要额外下载压缩包,直接导入maven坐标)
  • 第四十四篇 EfficientNetV1、V2模型详解
  • 装饰器—购物打折
  • SQL进阶技巧:非等值连接--单向近距离匹配
  • python 的while break continue 嵌套循环
  • 人工智能-卷积神经网络(学习向)
  • 如何搭建JMeter分布式集群环境来进行性能测试
  • 【N 卡 掉驱动 Driver 】NVML ERROR: Driver Not Loaded
  • 做异端中的异端 -- Emacs裸奔之路3: 上古神键Hyper
  • C++,Python,Javascripts操作文件读写,字符串分割
  • 什么是JAVA反射??? 常用的API有哪些???怎么获取Class对象.....
  • 循环神经网络设计同样可以使用预训练词“嵌入”
  • 20241129解决在Ubuntu20.04下编译中科创达的CM6125的Android10出现找不到库文件libtinfo.so.5的问题
  • 【Java基础】笔记
  • MySQL 索引创建 大数据查询 性能测试 SQL优化 慢查询
  • 游戏引擎学习第30天
  • C#面向对象之访问限制,类基础,继承
  • QT:将QTableWidget内容写入txt文件中
  • Go-Web之TCP网络连接
  • HTTPS 的安全秘密:对称加密与非对称加密的完美结合
  • Python学习38天
  • 【赵渝强老师】PostgreSQL的物理存储结构
  • 【jvm】C1编译器和C2编译器的区别