2021ALI算法0428笔试题之二:二叉排序树的最长支

这篇博客探讨了如何在二叉排序树中找到最长的分支,即最长的递增或递减子序列。通过建树法或动态规划的方法,可以解决此问题。文章提供了两种算法实现,一种是通过构建并遍历二叉树来计算最长分支,另一种是利用动态规划直接求解最长递增和递减子序列。示例代码展示了如何用C++实现建树法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目概述

小明有一颗n个节点的二叉排序树,他想知道这棵树的最长的支的长度。一棵树的支表示为:

1)左支。选择某节点为起始,移动到其左儿子,再以左儿子为其起始移动到左儿子的左儿子。重复进行,当某个节点无左儿子时结束,其递归次数为左支的长度。

2)右支。定义类似于左支。

一棵二叉树中的最长支指树中长度最长的左支或右支。

给你一个长度为n的序列,请你按照这个序列的顺序插入初始为空的二叉排序树中。要求每次插入后仍满足二叉排序树的定义。

如有二叉排序树为:

   3
  / \
 1   4

向其插入2后变为:

  3
 / \
1   4
 \
  2

请你告诉小明这棵二叉排序树最长的支有多长,并告诉他最长的支的个数。

输入描述:

第一行为一个n,表示二叉树的节点数。

第二行为n个不同的正整数a_i2 \leq n \leq 10^5, \ 1 \leq a_i \leq n

示例一:

输入:
6
5 2 6 3 1 4

输出:
2 2

说明:
      5
     / \
    2   6
   / \    
  1   3
       \
        4    

解题思路

方法一:建树法

我们按照题目的意思来,首先构造一棵二叉搜索树(Binary Search Tree)。建树的代价为O(n log n)。在建树的过程中,对每个节点,维护两个值L_len和R_len,分别表示以该节点为分支的最后一个节点时,该分支作为左支及右支的长度。同时,维护一个记录当前遇到的最长的分支长度cur_len以及对应数目cur_num。假设现在插入节点a_j,其父节点为a_i,则a_j的各个变量的值为:

L\_len(a_j)=L\_len(a_i)*(a_j < a_i)+1

R\_len(a_j)=R\_len(a_i)*(a_j > a_i)+1

随后更新全局分支长度及对应的数目即可。

方法二:动态规划

分析一下最长的左右支的特点,发现:最长的左支,就是原数组中的最长递增子序列;最长的右支,就是最长递减子序列。因此这个问题就等价于回答最长递增/减子序列的数目和长度。这个问题的时间复杂度为O(n log n)。

时间复杂度O(n^2)的动态规划做法:

维护一维数组dp,dp[i]表示以A[i]元素结尾的最长递增子序列的长度。

递推公式为:dp[i]=max\{dp[j]+1, dp[i]\} \ (1 \leq j < i, \ A[j] < A[i])

在更新数组长度时,同时维护一个count数组,用于统计以A[i]结尾的最长子序列的数目。

如何将时间复杂度降为O(n log n)呢?

​​​​​​这里可以参照最长递增子序列的解法。

示例代码

方法一:

#include<iostream>
#include<math.h>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;

class ListNode 
{
public:
	int val, L_len, R_len;
	ListNode *L_node, *R_node;
	ListNode(int x) { val = x; L_len = 1; R_len = 1; L_node = NULL; R_node = NULL; }
}*LN;

int main()
{
	int N;
	cin >> N;
	int *arr = new int[N];
	for (int i = 0; i < N; ++i)
		cin >> arr[i];
	
	ListNode *root, *p;
	root = &ListNode(arr[0]);
	vector<ListNode*> lists;
	for (int i = 1; i < N; ++i)
	{
		ListNode* t = (ListNode*)malloc(sizeof(ListNode));
		t->val = arr[i];
		t->L_len = t->R_len = 1;
		t->L_node = t->R_node = NULL;
		lists.push_back(t);
	}
	int cur_len = 1, cur_num = 1;
	for (int i = 1; i < N; ++i)
	{
		p = lists[i - 1];
		ListNode *cur_p = root;
		while (1)
		{
			if (cur_p->val > p->val)
			{
				if (cur_p->L_node == NULL)
				{
					cur_p->L_node = p;
					p->L_len = cur_p->L_len + 1;
					if (p->L_len > cur_len)
					{
						cur_len = p->L_len;
						cur_num = 1;
					}
					else if (p->L_len == cur_len)
						cur_num++;
					break;
				}
				else
					cur_p = cur_p->L_node;
			}
			else
			{
				if (cur_p->R_node == NULL)
				{
					cur_p->R_node = p;
					p->R_len = cur_p->R_len + 1;
					if (p->R_len > cur_len)
					{
						cur_len = p->R_len;
						cur_num = 1;
					}
					else if (p->R_len == cur_len)
						cur_num++;
					break;
				}
				else
    				cur_p = cur_p->R_node;
			}
		}
		
	}
	
	cout << cur_len - 1 << ' ' << cur_num << endl;
	return 0;
}

需要注意的是,建树的时候每个节点都需要提前申请空间。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值