题目
自己写了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的容量,还是算了吧。