树形线段树

题目
在这里插入图片描述
自己写了dp版本。本着钻研的精神,看看线段树的解法。

看了官方题解的线段树的解释,云里雾里,我是真的不知道它在讲什么,挣扎了半天放弃。

遂去网上查找什么是线段树。

但是网络上的线段树跟树形数组长的很像,是将树拍扁的数组,而不是题解里的树结构。

FenwickTree是树状数组的意思!

但是概念是相通的。这里贴张正宗的线段树结构,最终的实现形式,你可以选择用树状数组,也可以像官方题解一样,真的造树形。

在这里插入图片描述
可能树形更直观一点?(划掉,微笑,并不)

现在来讲线段树的基本概念。如图所示,线段树就是记录区间数据的一种树。

节点有一些属性。我们来看,有哪些属性。

1、节点的范围,左右是否闭包看自己的心情,题解这边是选择闭包的。

闭包的意思,是包含端点。

2、既然是树节点 的话,必然有左右两个节点,而且,这个树现在是二叉的。你想多叉也是可以的。反正是数据结构嘛。(微笑)

class Node {
    //每个节点挂载了,这个节点的范围[range_left,range_right]
    int range_left, range_right;
    //以及它的关系节点,左右两区间,左边[range_left,mid],右边[mid+1,range_right]——自行定义的区间划分,保持统一即可
    Node left, right;
    //这个区间范围内的len,count数量,自行定义的数据结构,根据题目所求不同自行设置
    Value val;
    public Node(int start, int end) {
        range_left = start;
        range_right = end;
        left = null;
        right = null;
        val = new Value(0, 1);
    }
    public int getRangeMid() {
        return range_left + (range_right - range_left) / 2;
    }
    public Node getLeft() {
        if (left == null) left = new Node(range_left, getRangeMid());
        return left;
    }
    public Node getRight() {
        if (right == null) right = new Node(getRangeMid() + 1, range_right);
        return right;
    }
}

3、自带的信息,根据题目的不同,自行设置,这道题记录的是区间内的最长子序列的长度,以及个数

class Value {
    //范围的最大L,默认0
    int length;
    //范围的数量,默认1
    int count;
    public Value(int len, int ct) {
        length = len;
        count = ct;
    }
}

树长啥样,节点挂载了什么数据,咱现在基本上清楚了。

基本的概念就是,将一长段区间,一次分2段,分到区间的长度为1为止。

那为啥会有线段树这种数据结构呢?

当然是为了求区间的值啊,小傻瓜。

比如[4,4]的值变了,那么[4,5],[1,5],[1,10]的值肯定也变了,对吧?

这意味着我们改一个叶子节点的值,在这棵树里,大概要改log2(n个数)的节点值。

总比n的时间复杂度要强吧。

那究竟如何改呢,当然是要找到改的那个具体的节点,改完之后,往上更新了。

如何找到具体的那个节点呢,别忘了这是一棵二叉树,判断在左节点范围还是右节点范围不就行了。

好了,我们还是依托代码来讲讲

class Solution {
    //合并左右区间查出来的值,用来更新节点用的
    public Value merge(Value v1, Value v2) {
        //如果左区间的最长长度=右区间的最长长度
        if (v1.length == v2.length) {
            //左边没有找到符合
            if (v1.length == 0) return new Value(0, 1);
            //那结果就是两者相加
            return new Value(v1.length, v1.count + v2.count);
        }
        //取大的值
        return v1.length > v2.length ? v1 : v2;
    }

    public void insert(Node node, int key, Value val) {
        if (node.range_left == node.range_right) {
        //如果是叶子节点,那就直接更新其值并返回
            node.val = merge(val, node.val);
            return;
        } else if (key <= node.getRangeMid()) {
            //这货是左子节点范围的,那就去左子节点那边
            insert(node.getLeft(), key, val);
        } else {
            //这货是右子节点范围的,那就去右子节点那边
            insert(node.getRight(), key, val);
        }
        //递归回来之后,更新父节点的值(因为下面的节点更新过了)
        node.val = merge(node.getLeft().val, node.getRight().val);
    }

    public Value query(Node node, int key) {
        //如果node的right都比key小,那就是你了,node
        if (node.range_right <= key) return node.val;
        //连最左边都比key大,没救了,key你就是无,默认为L是0,count是1
        else if (node.range_left > key) return new Value(0, 1);
        //如果key在node的left-right之间,那就要综合考虑左区间和右区间的结果
        else return merge(query(node.getLeft(), key), query(node.getRight(), key));
    }

    public int findNumberOfLIS(int[] nums) {
        if (nums.length == 0) return 0;
        int min = nums[0], max = nums[0];
        for (int num: nums) {
            //找到值的最小值与最大值,确定根节点的区间————步骤1
            min = Math.min(min, num);
            max = Math.max(max, num);
        }
        //这里,树还没完全建好,只建了一个根,只有调用getLeft,Right才会建后续的节点————步骤2
        Node root = new Node(min, max);
        for (int num: nums) {
            //找到以[min,num-1]范围为尾节点的子序列的,最大长度L,以及个数count————步骤3
            Value v = query(root, num-1);
            //那么[min,num]区间的值就为[min,num-1]的长度+1,个数继承,插入到表里去————步骤4
            //如果节点本身存在,那么就是更新,不存在,那么就是新增咯————步骤4
            insert(root, num, new Value(v.length + 1, v.count));
        }
        //返回[min,max]的区间的最终结果
        return root.val.count;
    }
}

好了,这个可以当做以后做线段数组的模板了,收藏!如果要写树形数组,要多好多内存啊,一般树形数组要4n的容量,还是算了吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

rgbhi

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

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

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

打赏作者

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

抵扣说明:

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

余额充值