0 课程地址
https://coding.imooc.com/lesson/207.html#mid=13847
1 重点关注
1.1 线段树应用场景
如3.1和3.2对比,不可变的数组进行逻辑运算时,数组运行速度更快,但是用线段树也未尝不可
如3.3运行时间,当进行单元素更新操作时,数组的时间复杂度时O(n),而线段树是O(logn),如果是多个元素更新,那么差距更大。所以有更新操作的时候用线段树更快。
2 课程内容
3 Coding
3.1 leetCode303问题解析1(线段树实现)
- 需求
给定一个整数数组 nums,处理以下类型的多个查询: 计算索引 left 和 right (包含 left 和 right)之间的 nums 元素的 和 ,其中 left <= right 实现 NumArray 类: NumArray(int[] nums) 使用数组 nums 初始化对象 int sumRange(int i, int j) 返回数组 nums 中索引 left 和 right 之间的元素的 总和 ,包含 left 和 right 两点(也就是 nums[left] + nums[left + 1] + ... + nums[right] ) 示例 1: 输入: ["NumArray", "sumRange", "sumRange", "sumRange"] [[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]] 输出: [null, 1, -1, -3] 解释: NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]); numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3) numArray.sumRange(2, 5); // return -1 (3 + (-5) + 2 + (-1)) numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1)) 提示: 1 <= nums.length <= 104 -105 <= nums[i] <= 105 0 <= i <= j < nums.length 最多调用 104 次 sumRange 方法 来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/range-sum-query-immutable
- 代码实现
package com.company; /** * 1 线段树解题 * @author weidoudou * @date 2023/1/18 6:47 **/ class NumArray { SegmentTree<Integer> segmentTree; public NumArray(int[] nums) { if(nums.length==0){ throw new IllegalArgumentException("数组长度为0"); } //装箱 Integer[] numsInteger = new Integer[nums.length]; for(int i = 0;i<nums.length;i++){ numsInteger[i] = nums[i]; } segmentTree = new SegmentTree<>(numsInteger,(a,b)->{return a+b;}); } public int sumRange(int left, int right) { return segmentTree.query(left, right); } } /** * Your NumArray object will be instantiated and called as such: * NumArray obj = new NumArray(nums); * int param_1 = obj.sumRange(left,right); */
- 线段树类:
package com.company;
/**
* 线段树
* 根据入参数组的元素个数,制造出线段树节点元素个数
* @author weidoudou
* @date 2023/1/15 15:24
**/
public class SegmentTree<E> {
//1 初始化线段树元素和数组元素
private E[] data;
private E[] tree;
private Merge<E> merger;
/**
* 初始化数组元素和tree(4*元素个数上节有推导)
* @author weidoudou
* @date 2023/1/15 15:34
* @param nums 请添加参数描述
* @return null
**/
public SegmentTree(E [] nums,Merge<E> merger){
data = (E[])new Object[nums.length];
for(int i = 0; i < nums.length; i++){
data[i] = nums[i];
}
this.merger = merger;
tree = (E[])new Object[4*nums.length];
buildSegmentTree(0,0,data.length-1);
}
//2 创建线段树
private void buildSegmentTree(int index,int l,int r){
//2.1 终止条件
if(l==r){
tree[index] = data[l];
//2.2 循环条件
}else{
//定义mid 将线段树节点平均拆分
int mid = l+(r-l)/2;
int indexL = getLeftChild(index);
int indexR = getRightChild(index);
buildSegmentTree(indexL,l,mid);
buildSegmentTree(indexR,mid+1,r);
tree[index] = merger.merge(tree[indexL],tree[indexR]);
}
}
//3 查询线段树区间
public E query(int queryL,int queryR){
if(queryL<0||queryR>= data.length||queryL>queryR){
throw new IllegalArgumentException("index is error");
}
return query(queryL,queryR,0,0, data.length-1);
}
private E query(int queryL,int queryR,int index,int l,int r){
if(queryL == l&&queryR == r){
return tree[index];
}
int mid = l+(r-l)/2;
int leftIndex = getLeftChild(index);
int rightIndex = getRightChild(index);
//如果
if(queryL>=mid+1){
return query(queryL,queryR,rightIndex,mid+1,r);
}else if(queryR<=mid){
return query(queryL, queryR,leftIndex,l,mid);
}else{
E leftE = query(queryL, mid,leftIndex,l,mid);
E rightE = query(mid+1,queryR,rightIndex,mid+1,r);
return merger.merge(leftE, rightE);
}
}
//4 基本方法
public int getSize(){
return data.length;
}
public E get(int index){
if(index<0||index>=getSize()){
throw new IllegalArgumentException("get失败");
}
return data[index];
}
public int getLeftChild(int index){
return index*2+1;
}
public int getRightChild(int index){
return index*2+2;
}
public String toString(){
StringBuffer sbf = new StringBuffer();
sbf.append("[");
for(int i = 0;i< tree.length;i++){
if(tree[i]!=null){
sbf.append(tree[i]);
}else{
sbf.append("null");
}
if(i!=tree.length-1){
sbf.append(",");
}
}
sbf.append("]");
return sbf.toString();
}
}
- 自定义接口
package com.company;
/**
* 融合函数
* @author weidoudou
* @date 2023/1/17 6:38
**/
public interface Merge<E> {
E merge(E left,E right);
}
- 测试结果
3.2 leetCode303问题解析2(数组实现)
- 需求
如3.1
- 代码实现
package com.company; /** * 1 常规方法解题 * @author weidoudou * @date 2023/1/18 6:47 **/ class NumArray2 { int[] sumE; public NumArray2(int[] nums) { sumE = new int[nums.length+1]; sumE[0] = 0; for(int i = 1;i<=nums.length;i++){ sumE[i] = sumE[i-1]+nums[i-1]; } } public int sumRange(int left, int right) { return sumE[right+1]-sumE[left]; } } /** * Your NumArray object will be instantiated and called as such: * NumArray obj = new NumArray(nums); * int param_1 = obj.sumRange(left,right); */
- 测试结果
3.3 leetCode307问题解析
- 需求
给你一个数组 nums ,请你完成两类查询。 其中一类查询要求 更新 数组 nums 下标对应的值 另一类查询要求返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的 和 ,其中 left <= right 实现 NumArray 类: NumArray(int[] nums) 用整数数组 nums 初始化对象 void update(int index, int val) 将 nums[index] 的值 更新 为 val int sumRange(int left, int right) 返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的 和 (即,nums[left] + nums[left + 1], ..., nums[right]) 示例 1: 输入: ["NumArray", "sumRange", "update", "sumRange"] [[[1, 3, 5]], [0, 2], [1, 2], [0, 2]] 输出: [null, 9, null, 8] 解释: NumArray numArray = new NumArray([1, 3, 5]); numArray.sumRange(0, 2); // 返回 1 + 3 + 5 = 9 numArray.update(1, 2); // nums = [1,2,5] numArray.sumRange(0, 2); // 返回 1 + 2 + 5 = 8 提示: 1 <= nums.length <= 3 * 104 -100 <= nums[i] <= 100 0 <= index < nums.length -100 <= val <= 100 0 <= left <= right < nums.length 调用 update 和 sumRange 方法次数不大于 3 * 104 来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/range-sum-query-mutable
- 代码实现
package com.company; class NumArray { int[] sumE; int[] data; public NumArray(int[] nums) { data = new int[nums.length]; for(int i = 0;i<nums.length;i++){ data[i] = nums[i]; } sumE = new int[nums.length+1]; sumE[0] = 0; for(int i = 1;i<=nums.length;i++){ sumE[i] = sumE[i-1]+nums[i-1]; } } public void update(int index, int val) { data[index] = val; for(int i = index+1;i<=data.length;i++){ sumE[i] = sumE[i-1]+data[i-1]; } } public int sumRange(int left, int right) { return sumE[right+1]-sumE[left]; } }
- 测试结果
诸葛