How to efficiently spawn a binary tree from certain nodes? - algorithm

Not sure if it is a proper place to ask for such question.. But I will just post it anyway...
Suppose I have a binary tree, which certain nodes marked as red:
n1
/ \
red n2
/ \ \
n3 n4 red
/ \
n5 n6
So what I would like to do, is for every red node, spawn the tree into two new trees, and put each of the child into one tree.
So for the above case, it would become four trees like this:
n1
/ \
n3 n2
/
n5
n1
/ \
n4 n2
/
n5
n1
/ \
n3 n2
\
n6
n1
/ \
n4 n2
\
n6
It seems a pretty clean-defined problem.. but so far I just cannot come up with a good solution for this..
Could anyone shed some lights on this problem? Thank you a lot!

A few observations that can lead to a clean implementation:
if there are n red nodes, then there will be 2n trees (this ignores the situation where the red node is a leaf -- those probably don't matter and can be eliminated by a pre-processing step).
any number k between 0 and 2n - 1 can represent one configuration -- the decision taken at the ith red node (i.e. whether to keep the left or the right sub-tree) is indicated by the ith bit of k. This bit can be easily obtained using bit-wise operations, e.g. by comparing k & (1 << i) with 0.
The main function that can generate trees one by one would look like this:
void spawnAllTrees(baseTree) {
int nRed = countRedNodes(baseTree);
// this assigns to each red node an index between 0 and nRed - 1
// (e.g. according to a pre-order tree traversal).
// it computes a hash map denoted as "redIndex" which
// stores the mapping from Node to int
computeRedIndices(baseTree);
for (int k = 0; k < (1 << nRed); k++) {
crtTree = spawnTree(baseTree, k);
}
}
The code for spawnTree would be:
Node spawnTree(baseTreeNode, k) {
if (baseTreeNode.isRed()) {
idx = redIndex[baseTreeNode];
if (!bitIsSet(k, idx)) {
return spawnTree(baseTreeNode->left(), k);
} else {
return spawnTree(baseTreeNode->right(), k);
} else {
return new Node(baseTreeNode->value(),
spawnTree(baseTreeNode->left(), k),
spawnTree(baseTreeNode->right(), k));
}
}
EDIT:
Changed the algorithm a little bit -- incrementing a counter to determine the current red node index is not valid. Different decisions at a certain red node could make another red node receive different indices for different configurations.

Since binary trees can be represented as linear expressions the binary tree
n1
/ \
red n2
/ \ \
n3 n4 red
/ \
n5 n6
can be represented as the linear expression ((n3 red n4) n1 (n2 (n5 red n6)))
Now a linear expression for a binary tree can be represented as a BNF and red can be replaced with an | or operator
<s> ::= <a> n1 (n2 <b>)
<a> ::= n3 | n4
<b> ::= n5 | n6
Now all the possible combinations (walks) of this BNF are
n3 n1 (n2 n5)
n3 n1 (n2 n6)
n4 n1 (n2 n5)
n4 n1 (n2 n6)
and these can be turned back into the trees of your answer.

Here is the algorithm:
node main_root_address; //address of very first node
function myfunc_inorder(root_address) //call this from main
{
if root_address is null return;
myfunc_inorder(root_address->left);
if(root_address->value is "red")
{
node temp = root_address;
root_previous->parent = root_address->left;
//inside each node along with value and pointer to left and right subtree store the address of the parent node.
myfunc_inorder(main_root_address);
root_previous->parent = root_address->right;
myfunc_inorder(main_root_address);
root_address = temp;
}
myfunc_inorder(root_address->right);
}
How this algorithm is working?
First I will start with "node n1", then move to "node red" then to "node n3" then back to "node red"...Here I will replace "red" with left sub-tree and then with right sub-tree and repeat the algorithm until no red is left...

Related

Count nodes within k distance of marked nodes in grid

I am attempting to solve a coding challenge however my solution is not very performant, I'm looking for advice or suggestions on how I can improve my algorithm.
The puzzle is as follows:
You are given a grid of cells that represents an orchard, each cell can be either an empty spot (0) or a fruit tree (1). A farmer wishes to know how many empty spots there are within the orchard that are within k distance from all fruit trees.
Distance is counted using taxicab geometry, for example:
k = 1
[1, 0]
[0, 0]
the answer is 2 as only the bottom right spot is >k distance from all trees.
My solution goes something like this:
loop over grid and store all tree positions
BFS from the first tree position and store all empty spots until we reach a neighbour that is beyond k distance
BFS from the next tree position and store the intersection of empty spots
Repeat step 3 until we have iterated over all tree positions
Return the number of empty spots remaining after all intersections
I have found that for large grids with large values of k, my algorithm becomes very slow as I end up checking every spot in the grid multiple times. After doing some research, I found some solutions for similar problems that suggest taking the two most extreme target nodes and then only comparing distance to them:
https://www.codingninjas.com/codestudio/problem-details/count-nodes-within-k-distance_992849
https://www.geeksforgeeks.org/count-nodes-within-k-distance-from-all-nodes-in-a-set/
However this does not work for my challenge given certain inputs like below:
k = 4
[0, 0, 0, 1]
[0, 1, 0, 0]
[0, 0, 0, 0]
[1, 0, 0, 0]
[0, 0, 0, 0]
Using the extreme nodes approach, the bottom right empty spot is counted even though it is 5 distance away from the middle tree.
Could anyone point me towards a more efficient approach? I am still very new to these types of problems so I am finding it hard to see the next step I should take.
There is a simple, linear time solution to this problem because of the grid and distance structure. Given a fruit tree with coordinates (a, b), consider the 4 diagonal lines bounding the box of distance k around it. The diagonals going down and to the right have a constant value of x + y, while the diagonals going down and to the left have a constant value of x - y.
A point (x, y) is inside the box (and therefore, within distance k of (a, b)) if and only if:
a + b - k <= x + y <= a + b + k, and
a - b - k <= x - y <= a - b + k
So we can iterate over our fruit trees (a, b) to find four numbers:
first_max = max(a + b - k); first_min = min(a + b + k);
second_max = max(a - b - k); second_min = min(a - b + k);
where min and max are taken over all fruit trees. Then, iterate over empty cells (or do some math and subtract fruit tree counts, if your grid is enormous), counting how many empty spots (x,y) satisfy
first_max <= x + y <= first_min, and
second_max <= x - y <= second_min.
This Python code (written in a procedural style) illustrates this idea. Each diagonal of each bounding box cuts off exactly half of the plane, so this is equivalent to intersection of parallel half planes:
fruit_trees = [(a, b) for a in range(len(grid))
for b in range(len(grid[0]))
if grid[a][b] == 1]
northwest_half_plane = -infinity
southeast_half_plane = infinity
southwest_half_plane = -infinity
northeast_half_plane = infinity
for a, b in fruit_trees:
northwest_half_plane = max(northwest_half_plane, a - b - k)
southeast_half_plane = min(southeast_half_plane, a - b + k)
southwest_half_plane = max(southwest_half_plane, a + b - k)
northeast_half_plane = min(northeast_half_plane, a + b + k)
count = 0
for x in range(len(grid)):
for y in range(len(grid[0])):
if grid[x][y] == 0:
if (northwest_half_plane <= x - y <= southeast_half_plane
and southwest_half_plane <= x + y <= northeast_half_plane):
count += 1
print(count)
Some notes on the code: Technically the array coordinates are a quarter-turn rotated from the Cartesian coordinates of the picture, but that is immaterial here. The code is left deliberately bereft of certain 'optimizations' which may seem obvious, for two reasons: 1. The best optimization depends on the input format of fruit trees and the grid, and 2. The solution, while being simple in concept and simple to read, is not simple to get right while writing, and it's important that the code be 'obviously correct'. Things like 'exit early and return 0 if a lower bound exceeds an upper bound' can be added later if the performance is necessary.
As Answered by #kcsquared ,Providing an implementation in JAVA
public int solutionGrid(int K, int [][]A){
int m=A.length;
int n=A[0].length;
int k=K;
//to store the house coordinates
Set<String> houses=new HashSet<>();
//Find the house and store the coordinates
for(int i=0;i<m;i++) {
for (int j = 0; j < n; j++) {
if (A[i][j] == 1) {
houses.add(i + "&" + j);
}
}
}
int northwest_half_plane = Integer.MIN_VALUE;
int southeast_half_plane = Integer.MAX_VALUE;
int southwest_half_plane = Integer.MIN_VALUE;
int northeast_half_plane = Integer.MAX_VALUE;
for(String ele:houses){
String arr[]=ele.split("&");
int a=Integer.valueOf(arr[0]);
int b=Integer.valueOf(arr[1]);
northwest_half_plane = Math.max(northwest_half_plane, a - b - k);
southeast_half_plane = Math.min(southeast_half_plane, a - b + k);
southwest_half_plane = Math.max(southwest_half_plane, a + b - k);
northeast_half_plane = Math.min(northeast_half_plane, a + b + k);
}
int count = 0;
for(int x=0;x<m;x++) {
for (int y = 0; y < n; y++) {
if (A[x][y] == 0){
if ((northwest_half_plane <= x - y && x - y <= southeast_half_plane)
&& southwest_half_plane <= x + y && x + y <= northeast_half_plane){
count += 1;
}
}
}
}
return count;
}
This wouldn't be easy to implement but could be sublinear for many cases, and at most linear. Consider representing the perimeter of each tree as four corners (they mark a square rotated 45 degrees). For each tree compute it's perimeter intersection with the current intersection. The difficulty comes with managing the corners of the intersection, which could include more than one point because of the diagonal alignments. Run inside the final intersection to count how many empty spots are within it.
Since you are using taxicab distance, BFS is unneccesary. You can compute the distance between an empty spot and a tree directly.
This algorithm is based on a suggestion by https://stackoverflow.com/users/3080723/stef
// select tree near top left corner
SET flag false
LOOP r over rows
LOOP c over columns
IF tree at c, r
SET t to tree at c,r
SET flag true
BREAK
IF flag
BREAK
LOOP s over empty spots
Calculate distance between s and t
IF distance <= k
ADD s to spotlist
LOOP s over spotlist
LOOP t over trees, starting at bottom right corner
Calculate distance between s and t
IF distance > k
REMOVE s from spotlist
BREAK
RETURN spotlist

DFS and BFS Time and Space complexities of 'Number of islands' on Leetcode

Here is the question description. The first 2 suggested solutions involve DFS and BFS. This question refers to the 1st two approaches: DFS and BFS.
I have included the problem statement here for easier reading.
Given a 2d grid map of '1's (land) and '0's (water), count the number of
islands. An island is surrounded by water and is formed by connecting adjacent
lands horizontally or vertically. You may assume all four edges of the grid are
all surrounded by water.
Example 1:
Input:
11110
11010
11000
00000
Output: 1
Example 2:
Input:
11000
11000
00100
00011
Output: 3
I am unclear as to why the time complexity for both DFS and BFS is O(rows * columns) for both. I see how this is the case where the grid is just full of 0's - we simply have to check each cell. However, doesn't the DFS approach add more time to the search? Even if we mark the cells we visited by changing them to 0 in the dfs methods, we still would revisit all the cells because of the two outer loops. If dfs could be have time complexity of O(n) in the case of a big grid with large row and column numbers, wouldn't the time complexity be O(rows * columns * max[rows, cols])? Moreover, isn't the same case with the BFS approach where it is O(rows * cols * possibleMaxSizeOfQueue) where possibleMaxSizeOfQueue could again be max[rows, cols]?
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
dfs(grid, r, c);
}
}
}
How is DFS's space complexity O(rows*cols)? Is it not possible/common to consider the call stack space as freed when a recursion branch returns?
How is the space complexity for BFS O(min(rows, cols))? The way I see it, the queue could be full of all elements in the case of a grid with just 1's thereby giving O(rows*cols) for BFS space complexity.
DFS Solution
class Solution {
void dfs(char[][] grid, int r, int c) {
int nr = grid.length;
int nc = grid[0].length;
if (r < 0 || c < 0 || r >= nr || c >= nc || grid[r][c] == '0') {
return;
}
grid[r][c] = '0';
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c - 1);
dfs(grid, r, c + 1);
}
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int nr = grid.length;
int nc = grid[0].length;
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
dfs(grid, r, c);
}
}
}
return num_islands;
}
}
Time complexity : O(M×N) where M is the number of rows
and N is the number of columns.
Space complexity : worst case O(M×N) in case that the
grid map is filled with lands where DFS goes by M×N deep.
BFS Solution
class Solution {
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int nr = grid.length;
int nc = grid[0].length;
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
grid[r][c] = '0'; // mark as visited
Queue<Integer> neighbors = new LinkedList<>();
neighbors.add(r * nc + c);
while (!neighbors.isEmpty()) {
int id = neighbors.remove();
int row = id / nc;
int col = id % nc;
if (row - 1 >= 0 && grid[row-1][col] == '1') {
neighbors.add((row-1) * nc + col);
grid[row-1][col] = '0';
}
if (row + 1 < nr && grid[row+1][col] == '1') {
neighbors.add((row+1) * nc + col);
grid[row+1][col] = '0';
}
if (col - 1 >= 0 && grid[row][col-1] == '1') {
neighbors.add(row * nc + col-1);
grid[row][col-1] = '0';
}
if (col + 1 < nc && grid[row][col+1] == '1') {
neighbors.add(row * nc + col+1);
grid[row][col+1] = '0';
}
}
}
}
}
return num_islands;
}
}
Time complexity : O(M×N) where M is the number of rows
and N is the number of columns.
Space complexity : O(min(M,N)) because in worst case where
the grid is filled with lands, the size of queue can grow up to
min(M,N).
DFS' time complexity is proportional to the total number of vertexes and edges of the graph visited. In that case, there are N*M vertexes and slightly less than 4*N*M edges, their sum is still O(N*M).
Why so: because we process each edge exactly once in each direction. Situation where recursive call is immediately terminated does not matter as time spent for that call can be accounted for on the call site; and there is at most once call for each directed edge, hence O(N*M).
BFS' time complexity is quite similar. Maximal length of the queue does not matter at all because at no point we examine it in a whole. Queue only gets "append" and "remove first" queries, which can be processed in constant time regardless of queue's size. If we need to check whether a vertex was already visited, we do so in constant time.
Worst-case space complexity for DFS is Theta(N*M): just take any "snake-wise" maze:
......
#####.
......
.#####
......
Here DFS will be forced to traverse the path in whole before it stops and starts freeing up the stack. However, in no situation there will be more than N*M+1 elements on the stack.
Worst-case space complexity for BFS is indeed not O(max(N, M)): it's Theta(N*M) even when we're considering simple grid. Here is an example from math.stackexchange.com:
If we start BFS in the red point, it will end up with a queue which contains all leafs of the tree, their number is proportional to N*M. One can also truncate 3/4rd of the example and make the red dot appear in the upper-left corner.
Looks like the solution you've read is wrong in respect to BFS' worst case memory consumption.
#yeputons: I don't think the space complexity for BFS will be proportional to N * M.
When you say a queue could have at max all the leaf elements (when starting at center) that actually means 2*(N+M) elements at Max.
And when starting from one of the corners it indeed is O(min(m, n)), because number of elements being added to the queue are constrained.
This is a classic example of BFS implementation. Only catch here is that we don't need extra space to mark a node as visited. Existing grid matrix can be reused to identify a visited node. I have made a small video explaining this logic. Please check this out
https://www.youtube.com/watch?v=GkG4cQzyFoU
I think the space complexity of BFS is indeed O(min(M, N)) where M is the row length and N is the column length. Please see the below examples:
When you start traversing a matrix from the corner, the maximum number of cells/nodes you can have in the queue is k where k is the number of cells on a diagonal line in the matrix, which means k = min(M, N).
When you start traversing a matrix from the centre, the maximum number of cells/nodes you can have in the queue is {1, 4, 8, 12, 16, ..., 4i} where i is the i-th layer. And such cells fit in a matrix of min size {1, 4, 9, 16, 25, ..., i*i} respectively. Please see the below scratch:
We know that i is min(M, N), so yet again we have space complexity of O(4 * min(M, N)) which is O(min(M,N)).
Below is my attempt at arguing against #yeputon's answer:
I think the space complexity of BFS in the answer from #yeputons is not applicable to matrix traversal. The plot shown in that answer is a plot of a binary tree laid out in a matrix, but we are traversing in a ternary tree fashion except for the first step where we branch out to 4 branches. The traversal is more like what's described here in a different answer to the Maths Stack Exchange question quoted by #yeputon (https://math.stackexchange.com/a/2219954/758829). But I feel that this is still not the same, because when traversing a matrix in a BFS way, we go from the starting point outwards only. Traversal in both answers to the Maths Stack Exchange question goes recursively and that means it's not strictly outwards from the starting point.
Please correct me if I'm wrong. Thank you!

What is the optimal way to find common nodes in two BST

I am looking for the most optimal in terms of complexity(space and time).
My approach until now is:
Traverse one tree in-order and for each nodeId, search that nodeId in second tree.
Node structure:
struct node{
long long nodeId;
node *left;
node *right;
};
Please let me know if any doubts about the question.
Assuming one tree is n long and the other is m long, your approach is O(n*m) long, while you could do it in O(n+m).
Let us say you have to pointers to where you are in each tree (location_1 and location_2). Start going through both trees at the same time, and each time decide which pointer you want to advance. Lets say location_1 is pointing at a node with nodeId = 4 and location_2 is at nodeId = 7. In this case location_1 moves forwards since any node before the one it is looking at has a smaller value then 4.
By "moves forwards" I mean an in-order traverse of the tree.
You can do this with a standard merge in O(n+m) time. The general algorithm is:
n1 = Tree1.Root
n2 = Tree2.Root
while (n1 != NULL && n2 != NULL)
{
if (n1.Value == n2.Value)
{
// node exists in both trees. Advance to next node.
n1 = GetInorderSuccessor(n1)
n2 = GetInorderSuccessor(n2)
}
else if (n1.Value > n2.Value)
{
// n2 is not in Tree1
n2 = GetInorderSuccessor(n2)
}
else
{
// n1 is smaller than n2
// n1 is not in Tree2
n1 = GetInorderSuccessor(n1)
}
}
// At this point, you're at the end of one of the trees
// The other tree has nodes that are not in the other.
while (n1 != NULL)
{
// n1 is not in Tree2. Output it.
// and then get the next node.
n1 = GetInorderSuccessor(n1)
}
while (n2 != NULL)
{
// n2 is not in Tree1. Output it.
// and then get the next node.
n2 = GetInorderSuccessor(n2)
}
The only hard part here is that GetInorderSuccessor call. If you're doing this with a tree, then you'll need to maintain state through successive calls to that function. You can't depend on a standard recursive tree traversal to do it for you. Basically, you need a tree iterator.
The other options is to first traverse each tree to make a list of the nodes in order, and then write the merge to work work with those lists.
Slightly modified Morris Traversal should solve the solution with movement similar to merge part of merge sort. Time Complexity: O(m + n), Space Complexity: O(1).

Dynamic Programming: Optimal Binary Search Tree

Allright, I'm hoping someone can explain this to me. I'm studying for finals and I can't quite figure something out.
The problem is dynamic programming; constructing an optimal binary search tree (OBST). I understand dynamic programming in general and the concepts of this problem in particular, but I don't understand the recursive form of this problem.
I get that we're constructing optimal binary search trees for an increasing subset of these nodes and keeping the answers in a table as we go along to avoid recalculation. I also get that when you root the tree at a_{k}, all of the successful nodes from a_{1} through a_{k-1} along with their corresponding fictitious unsuccessful nodes (i.e. the leaves of the tree) are in the left subtree, and then the ones in the right subtree are a_{k+1} through a_{n}.
Here's the recursive form of the equation that I don't understand:
c(i, j) = min (i < k <= j) {c(i, k-1) + c(k, j) + p(k) + w(i, k-1) + w(k +j)}
where w(i, j) = q(i) + sum from i+1 to j (q(l) + p(l)).
So in c(i,j), from left to right, we have the cost of left subtree + cost of right subtree + probability of successful search for root + w(i, k-1) + w(k +j).
My confusion is how c(i, k-1) differs from w(i, k-1).
The text is Computer Algorithms by Horowitz, Sahni, and Rajasekeran but I've also read CLRS on OBSTs and searched online, and nothing I've come across does a good job of explaining the difference between those parts of the equation.
c(i,j) represents the expected cost of searching an optimal binary search tree containing the keys ki, ..., kj. w(i,j) represents the probability sum of the subtree containing the keys ki, ..., kj. For the formula:
c(i, j) = min (i < k <= j) {c(i, k-1) + c(k, j) + p(k) + w(i, k-1) + w(k,j)}
c(i,k-1)+w(i,k-1) reresents the cost for the left subtree if we choose key k as the root.
c(k,j)+w(k,j) represents the cost for the right subtree.
p(k) represents the cost for the root k.
Notice that: If we choose key k as the root, then the left subtree contains the keys ki, ..., k(k-1) and the right subtree contains the kyes
k(k+1), ..., kj. But we can not simply say that:
c(i,j)=min (i < k <= j) {c(i, k-1) + c(k, j) + p(k)}
Because when we choose the key k for the root, the generated subtrees has their depth added by 1. So c(i,k-1)+w(i,k-1) will be the right cost for the left subtree!
This is a subtle way of calculating frequency*depth for a node at a particular depth.
Each time a node is evaluated as a root, while summing up its left (or right) subtree, you are adding sum of frequency to increase depth of all children.
For example, assume nodes 'A','B' and 'C', where 'A' is root, 'B' is left child of 'A' and 'C' is left child of 'B'. (There are no right children to make things simple.)
In bottom up manner, with leaf 'C' as root:
cost is Pr(C) = freqC*1 (no children)
with 'B' as root:
cost = Pr(B) + Cost[C,C] + sum of children freq
= freqB*1 + freqC*1 + freqC*1
= freqB*1 + freqC*2
where Pr(B) = freqB*1
Cost[C,C] = freqC*1
sum of children freq = freqC*1
And finally, with 'A' as root:
cost = Pr(A) + Cost[C,B] + sum of children freq
= freqA*1 + freqB*1 + freqC*2 + freqB*1 + freqC*1
= freqA*1 + freqB*2 + freqC*3
where Pr(A) = freqA*1
Cost[C,B] = freqB*1 + freqC*2
sum of children freq = freqB*1 + freqC*1

Proof for number of internal nodes in a tree

I was reading about compressed tries and read the following:
a compressed trie is a tree which has L leaves and every internal node in the trie has at least 2 children.
Then the author wrote that a tree with L leaves such that every internal node has alteast 2 children, has atmost L-1 internal nodes. I am really confused why this is true.
Can somebody offer a inductive proof for it?
Inductive proof:
we will prove it by induction on L - the number of leaves in the tree.
base: a tree made out of 1 leaf is actually a tree with only a root. It has 0 internal nodes, and the claim is true.
assume the claim is true for a compressed tree with L leaves.
Step: let T be a tree with L+1 leaves. choose an arbitrary leaf, let it be l, and trim it.
In order to make the tree compressed again - you need to make l's father a leaf [if l's father has more then 2 sons including l, skip this step]. We do it by giving it the same value as l's brother, and trimming the brother.
Now you have a tree T' with L leaves.
By induction: T' has at most L-1 internal nodes.
so, T had L-1+1 = L internal nodes, and L+1 leaves, at most.
Q.E.D.
alternative proof:
a binary tree with L leaves has L-1 internal nodes (1 + 2 + 4 + ... + L/2 = L-1)
Since at "worst case" you have a binary tree [every internal node has at least 2 sons!], then you cannot have more then L-1 internal nodes!
You should try drawing a tree with L internal nodes, where every node has 2 children and there are L leaves. If you see why this is impossible, it won't be hard to figure out why it does work for L-1 internal nodes.
Ok, so I'll have a go.
Define trees for a start:
T_0 = { Leaf }
T_i = T_i-1 union { Node(c1, ..., cn) | n >= 1 && ci in T_i-1 }
Trees = sum T_i
Now, the (sketch) proof of your assertion.
It is easy to check it for T_0
For T_i: if t \in T_i it is either in T_i-1 or in the new elements. In the former case, use IH. In the latter case check the assertion (simple: if cis have L_i leaves, t has L = L_1 + ... + L_n leaves. It also hase no more than L_1 - 1 + L_2 - 1 + ... + L_n - 1 + 1 inner nodes (by IH for children, +1 for self). Because we assume each inner node has at least two children (that's the fact from the trie definition), it is no more than L_1 + l_2 + ... + L_n - 2 + 1 = L - 1).
By induction, if the assertion holds for t in T_i for all i, it holds for t in Trees.

Resources