(计数线段树)lintcode中等248 · 统计比给定整数小的数的个数

题目

描述
给定一个整数数组 (下标由 0 到 n-1,其中 n 表示数组的规模,数值范围由 0 到 10000),以及一个 查询列表。对于每一个查询,将会给你一个整数,请你返回该数组中小于给定整数的元素的数量。

样例

样例 1:

输入: array =[1,2,7,8,5] queries =[1,8,5]
输出:[0,4,2]

样例 2:

输入: array =[3,4,5,8] queries =[2,4]
输出:[0,1]

分析

利用这道题来练习一下线段树,我首先用的是区间和线段树超时了,然后用了计数线段树是可以过的,我们先来分析一下这两种方法的时间复杂度

给定数组长度n 查询数组长度m

1.区间和线段树

构造线段树
构造线段树按照叶子节点的数量即可,时间复杂度O(n)

查询
查询的次数为m 每次查询消耗为线段树的高度h,而线段树的高度又是由给定数组长度n决定的,这里为logn
所以总的时间复杂度为O(mlogn)

修改
这里不需要修改

2.计数线段树

构造线段树
这里每个节点代表这个区间数字的个数 比如[0,1]代表0的个数+1的个数
这里最大值为10000
所以我们可以认为时间复杂度为O(10000)

查询
同样跟线段树的高度有关,跟上面的同理,这里为O(log10000)
查询次数为m
总时间复杂度O(mlog10000)

修改
这里修改的次数为给定数组长度n,每次修改消耗的时间为树的高度,同理为log10000
总的时间复杂度O(nlog10000)

比较一下可以发现如果n非常大,区间和线段树更占优势,log10000大概为10几,而区间和线段树它的节点数量为2n-1,常数项为2,大概会比计数线段树快6,7倍的样子

但是当m和n都很大,都接近1e8时,计数线段树常数项都是log1e4,区间和线段树常数项为log1e8,大概是计数的两倍多,而且构造也会花费n的时间,所以这里用计数线段树更合适

这道题没有给出n和m的范围,就当作练习了,区间和之前发过了,这里就发一个计数线段树

代码部分

1.线段树节点
//线段树节点 
class SegmentTreeNode
{
public:
	int start,end;
	int count;
	SegmentTreeNode* left;
	SegmentTreeNode* right;
	SegmentTreeNode(int s,int e,int c):start(s),end(e),count(c)
	{
		left=NULL;
		right=NULL;
	}	
};
2.线段树的构造

这里我们把所有节点都计数都设置为0,在修改的部分,在把给定的数组中的每个数字进行计数

	SegmentTreeNode* build(int start,int end)
	{
		//出口
		if(start==end)
		{
			return new SegmentTreeNode(start,start,0);
		}
		
		//现在能做的事情
		SegmentTreeNode* _root=new SegmentTreeNode(start,end,0);
		int mid=(start+end)/2;
		_root->left=build(start,mid);
		_root->right=build(mid+1,end);
		
		_root->count=_root->left->count+_root->right->count;
		
		return _root;
	}
3.线段树的查询

跟之前的查询一样,节点的计数等于它左区间计数加右区间计数

	int query(SegmentTreeNode* _root,
	int start,int end,int qstart,int qend)
	{
		//出口
		if(start>=qstart&&end<=qend)
		{
			return _root->count;
		}
		
		if(end<qstart||start>qend)
		{
			return 0;
		}
		
		//现在能做的事情
		int mid=(start+end)/2;
		int leftval=query(_root->left,start,mid,qstart,qend);
		int rightval=query(_root->right,mid+1,end,qstart,qend);
		
		return leftval+rightval; 
	}
4.线段树的修改

这里是计数线段树的重点,递归到叶子节点后,我们要用当前节点的计数加上一,因为给定数组中可能出现重复的数字,其他没有变化

	void modify(SegmentTreeNode* _root,
	int start,int end,int index,int value)
	{
		//出口
		
		if(start==end&&start==index)
		{
			_root->count+=value;
			return;
		}
		
		//现在能做的事情
		int mid=(start+end)/2;
		if(index<=mid)
		{
			modify(_root->left,start,mid,index,value);
		}
		else
		{
			modify(_root->right,mid+1,end,index,value);
		}
		
		_root->count=_root->left->count+_root->right->count; 
	}

完整代码

#include <bits/stdc++.h>
using namespace std;

//线段树节点 
class SegmentTreeNode
{
public:
	int start,end;
	int count;
	SegmentTreeNode* left;
	SegmentTreeNode* right;
	SegmentTreeNode(int s,int e,int c):start(s),end(e),count(c)
	{
		left=NULL;
		right=NULL;
	}	
};

//线段树 
class SegmentTree
{
public:
	SegmentTreeNode* root;
	
	SegmentTree(int len)
	{
		root=build(0,len);
	}
	
	SegmentTreeNode* build(int start,int end)
	{
		//出口
		if(start==end)
		{
			return new SegmentTreeNode(start,start,0);
		}
		
		//现在能做的事情
		SegmentTreeNode* _root=new SegmentTreeNode(start,end,0);
		int mid=(start+end)/2;
		_root->left=build(start,mid);
		_root->right=build(mid+1,end);
		
		_root->count=_root->left->count+_root->right->count;
		
		return _root;
	}
	
	int query(SegmentTreeNode* _root,
	int start,int end,int qstart,int qend)
	{
		//出口
		if(start>=qstart&&end<=qend)
		{
			return _root->count;
		}
		
		if(end<qstart||start>qend)
		{
			return 0;
		}
		
		//现在能做的事情
		int mid=(start+end)/2;
		int leftval=query(_root->left,start,mid,qstart,qend);
		int rightval=query(_root->right,mid+1,end,qstart,qend);
		
		return leftval+rightval; 
	}
	
	void modify(SegmentTreeNode* _root,
	int start,int end,int index,int value)
	{
		//出口
		
		if(start==end&&start==index)
		{
			_root->count+=value;
			return;
		}
		
		//现在能做的事情
		int mid=(start+end)/2;
		if(index<=mid)
		{
			modify(_root->left,start,mid,index,value);
		}
		else
		{
			modify(_root->right,mid+1,end,index,value);
		}
		
		_root->count=_root->left->count+_root->right->count; 
	}
	
};


class Solution {
public:
    vector<int> countOfSmallerNumber(vector<int> &a, vector<int> &queries) {
		vector<int> ans;
		
		SegmentTree st(10001);
		for(int i=0;i<a.size();i++)
		{
			st.modify(st.root,0,10000,a[i],1);
		}
		
		for(int i=0;i<queries.size();i++)
		{
			int cnt=st.query(st.root,0,10000,0,queries[i]-1);
			ans.push_back(cnt);
		}
		
		return ans;
    }
};


int main (void)
{
	vector<int> a={1,2,7,8,5};
	vector<int> q={1,8,5};
	vector<int> ans;
	Solution s;
	ans=s.countOfSmallerNumber(a,q);
	
	for(int i=0;i<ans.size();i++)
		cout<<ans[i]<<" ";
		
	return 0;
}







总结

熟悉了一种模板之后,我们应该多熟悉这些模板的使用以及各种变形,然后根据题目给出的数据范围,时间限制,内存限制,来决定用哪种模板,用这种模板的利弊,以及怎么样变形

May you succeed

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

White boy&

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

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

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

打赏作者

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

抵扣说明:

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

余额充值