【备战秋招】每日一题:2023.3.15-阿里OD(第一题)-满二叉树计数

文章介绍了一个关于探索古老森林秘密的故事,故事中涉及到了满二叉树的概念和计算方法。通过输入节点数量及它们的子节点信息,利用动态规划策略计算以每个节点为根的子树中满二叉树的节点数量。文章提供了C++、Python、Java和Go等语言的代码实现,并给出了相关题目推荐,帮助读者加深对树上动态规划的理解。
摘要由CSDN通过智能技术生成

为了更好的阅读体检,可以查看我的算法学习博客
在线评测链接:P1082

题目内容

在一个遥远的国度,有一个古老的神秘森林,被认为是森林之王的家园。传说森林之王是一只拥有巨大力量和智慧的生物,掌管着整个森林的命运。为了探索森林之王的秘密,许多勇敢的探险家一直在进入这片神秘的森林中。然而,进入森林之后,他们都没有回来过,因此这个秘密依然没有被解开。

有一天,一位名叫塔子哥的年轻探险家也进入了森林。他翻越了陡峭的山峰,穿过了茂密的丛林,终于到达了一处古老的废墟。在废墟的中心,塔子哥发现了一棵神奇的二叉树。这棵二叉树是如此的美丽,以至于塔子哥不禁驻足观赏,他想知道这棵二叉树有多少个节点满足以该节点为根的子树是满二叉树。于是,他开始了他的计算,希望能够揭开这个森林之王的秘密。

我们定义一棵树是满二叉树,当且仅当每一层的节点数量都达到了最大值(即无法在这一层添加新节点)。

输入描述

第一行输入一个正整数 n n n,代表节点的数量。

接下来的 n n n行,第i行输入两个整数 l i l_{i} li r i r_{i} ri,代表 i i i号节点的左儿子和右儿子。请注意,如果一个节点没有左儿子/右儿子,则对应的 l i l_{i} li/ r i r_{i} ri为- 1 1 1

1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1n105

输出描述

子树为满二又树的节点数量。

样例

输入

4
2 -1
3 4
-1 -1
-1 -1

输出

3

思路

简单树上dp

根据满二叉树的定义:左右儿子是相同高度的满二叉树 或者 叶子节点。

状态:定义 d p [ i ] dp[i] dp[i] 代表以 i i i为根的子树是多少高度的满二叉树.(不是满二叉树时 d p [ i ] = 0 dp[i] = 0 dp[i]=0).

转移:
d p [ i ] = { 1 i i s l e a f d p [ l ] + 1 d p [ l ] ≠ 0 ∧ d p [ r ] ≠ 0 ∧ d p [ l ] = d p [ r ] ∧ d p [ l ] ≠ 0 0 o t h e r s \large dp[i] =\left\{\begin{matrix} 1& i\quad is\quad leaf\\ dp[l] + 1 & dp[l] \neq 0 \wedge dp[r] \neq 0 \wedge dp[l] =dp[r] \wedge dp[l] \neq 0\\ 0 & others \end{matrix}\right. dp[i]= 1dp[l]+10iisleafdp[l]=0dp[r]=0dp[l]=dp[r]dp[l]=0others
t o p − d o w n top-down topdown地求一遍 d p dp dp值即可。最后答案为
a n s = ∑ i = 1 n [ d p [ i ] ≠ 0 ] ans = \large \sum_{i=1}^{n} [dp[i] \neq 0] ans=i=1n[dp[i]=0]
注意,题目没有规定根。所以需要在读图的时候统计一下入度。接着寻找入度为0的点来当作根。

类似题目推荐

本题是比较简单的树上 d p dp dp。这里提供更多习题

LeetCode
  1. 112. 路径总和
  2. 129. 求根到叶子节点数字之和
CodeFun2000

难度依次递增

P1141 2023.04.01 美团春招-第五题-染色の树

P1190 2023.04.12-华为实习笔试-第二题-获取最多食物

P1196 2023-04-19-华为实习笔试-第二题-塔子哥出城

P1150 2023.03.30-拼多多-第二题-修复道路

P1193 2023.04.13-腾讯音乐-暑期实习-第二题-价值二叉树

P1081 2023.3.13-百度-第三题-树上同色连通块

代码

CPP

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int e[maxn][2] , d[maxn] , dp[maxn];
void dfs (int u){
    // l , r为左右儿子节点
    int l = e[u][0];
    int r = e[u][1];
    // 转移case 1,也是边界条件
    if (l == -1 && r == -1) {
        dp[u] = 1;
        return;
    }
    if (l != -1) dfs(l);
    if (r != -1) dfs(r);
    // 转移case 2
    if (l != -1 && r != -1 && dp[l] == dp[r] && dp[l] != 0) dp[u] = dp[l] + 1;
    // 转移case 3
    else dp[u] = 0;
}
int main() {
    int n;
    cin >> n;
    // 读入图
    for (int i = 1 ; i <= n ; i++){
        cin >> e[i][0] >> e[i][1];
        // d用来记录度数
        if (e[i][0] != -1) d[e[i][0]]++;
        if (e[i][1] != -1) d[e[i][1]]++;
    }
    // 寻找根
    int rt = -1;
    for (int i = 1 ; i <= n ; i++)
        if (d[i] == 0)
            rt = i;
   	// 从根开始dfs
    dfs(rt);
    // 求答案
    int ans = 0;
    for (int i = 1 ; i <= n ; i++)
        ans += (dp[i] != 0);
    cout << ans << endl;
	return 0;
}

python

maxn = 10**5 + 5
n = int(input())

# 初始化边的数组和度数的数组
e = [[0, 0] for i in range(maxn)]
d = [0 for i in range(maxn)]

# 读入边和计算度数
for i in range(1, n + 1):
    e[i][0], e[i][1] = map(int, input().split())
    if e[i][0] != -1: d[e[i][0]] += 1
    if e[i][1] != -1: d[e[i][1]] += 1

rt = -1
for i in range(1, n + 1):
    if d[i] == 0:
        rt = i

# dfs 过程
dp = [0 for i in range(maxn)]
def dfs(u):
    l, r = e[u]
    if l == -1 and r == -1:
        dp[u] = 1
        return
    if l != -1: dfs(l)
    if r != -1: dfs(r)
    if l != -1 and r != -1 and dp[l] == dp[r] and dp[l] != 0:
        dp[u] = dp[l] + 1
    else:
        dp[u] = 0
dfs(rt)

# 计算答案
ans = 0
for i in range(1, n + 1):
    if dp[i] != 0: ans += 1
print(ans)

Java

import java.util.*;

class TreeNode{
    int val;
    TreeNode left;
    TreeNode right;
    public TreeNode(int val){
        this.val = val;
    }
}

public class Main {
    //记忆化数组,-2代表未记录,-1代表当前结点对应的子树不是完全二叉树
    //否则代表当前是完全二叉树,且值为当前完全二叉树的深度,叶子节点深度为1
    static int[] memo;
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        memo = new int[n];
        Arrays.fill(memo, -2);
        TreeNode[] nodes = new TreeNode[n];
        for(int i=0;i<n;i++) nodes[i] = new TreeNode(i);
        // 输入为两个子节点的可以定义节点建树,就不计算入度找根节点
        for(int i=0;i<n;i++){
            int l = in.nextInt();
            int r = in.nextInt();
            if(l!=-1) nodes[i].left = nodes[l-1];
            if(r!=-1) nodes[i].right = nodes[r-1];
        }
        int result = 0;
        for(int i=0;i<n;i++){
            if(isFull(nodes[i]) >= 0) result++;
        }
        System.out.println(result);
    }

    public static int isFull(TreeNode node){
        if(node == null) return 0;
        if(memo[node.val] != -2) return memo[node.val];
        int left = isFull(node.left);
        int right = isFull(node.right);
        if(left == -1 || right == -1 || left != right){
            memo[node.val] = -1;
        }else{
            memo[node.val] = left + 1;
        }
        return memo[node.val];
    }

}

Go

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	sc := bufio.NewReader(os.Stdin)
	var n int
	fmt.Fscanln(sc, &n)
	nodes := make([][]int, n+1)
	for i := 1; i <= n; i++ {
		var l, r int
		fmt.Fscanln(sc, &l, &r)
		nodes[i] = []int{l, r}
	}
	test3(n, nodes)
}

func test3(n int, nodes [][]int) {
	deeps := make([]int, n+1)
	var dfs func(node int)
	dfs = func(node int) {
		left, right := nodes[node][0], nodes[node][1]
		// 当前节点为叶节点
		if left == -1 && right == -1 {
			deeps[node] = 1
			return
		}
		// 当前节点只有左子树或右子树
		if left == -1 {
			deeps[node] = -1
			dfs(right)
			return
		}
		if right == -1 {
			deeps[node] = -1
			dfs(left)
			return
		}
		// 当前节点的左右子树还未搜索
		if deeps[left] == 0 {
			dfs(left)
		}
		if deeps[right] == 0 {
			dfs(right)
		}
		// 当前节点左右子树都搜索过了, 且左右子树不都为满二叉树
		if deeps[left] == -1 || deeps[right] == -1 {
			deeps[node] = -1
			return
		}
		// 当前节点的左右子树均为满二叉树, 且高度一致
		if deeps[left] == deeps[right] {
			deeps[node] = deeps[left] + 1
		} else {
			deeps[node] = -1
		}
		return
	}
	dfs(1)
	res := 0
	for i := 1; i < len(deeps); i++ {
		if deeps[i] > 0 {
			res++
		}
	}
	fmt.Println(res)
}
// by chuyu

Js

process.stdin.resume();
process.stdin.setEncoding('utf-8');
let input = '';
process.stdin.on('data', (data) => {
    input += data;
    return;
});
process.stdin.on('end', () => {
    const lines = input.trim().split('\n');
    const maxn = 100005;
    const e = new Array(maxn).fill(0).map(() => new Array(2).fill(0));
    const d = new Array(maxn).fill(0);
    let dp = new Array(maxn).fill(0);

    let n = parseInt(lines[0].trim());

    // 读入边和计算度数
    for (let i = 1; i <= n; i++) {
    const [u, v] = lines[i].trim().split(' ').map(Number);
    e[i][0] = u;
    e[i][1] = v;
    if (u !== -1) d[u]++;
    if (v !== -1) d[v]++;
    }

    let rt = -1;
    for (let i = 1; i <= n; i++) {
    if (d[i] === 0) {
        rt = i;
        break;
    }
    }

    function dfs(u) {
    let l = e[u][0];
    let r = e[u][1];

    if (l === -1 && r === -1) {
        dp[u] = 1;
        return;
    }

    if (l !== -1) dfs(l);
    if (r !== -1) dfs(r);

    if (l !== -1 && r !== -1 && dp[l] === dp[r] && dp[l] !== 0) {
        dp[u] = dp[l] + 1;
    } else {
        dp[u] = 0;
    }
    }

    dfs(rt);

    // 计算答案
    let ans = 0;
    for (let i = 1; i <= n; i++) {
    if (dp[i] !== 0) ans++;
    }
    console.log(ans);
});

题目内容均收集自互联网,如如若此项内容侵犯了原著者的合法权益,可联系我: (CSDN网站注册用户名: 塔子哥学算法) 进行删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔子哥学算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值