题目
描述
给定一个整数数组(下标由 0 到 n-1,其中 n 表示数组的规模),以及一个查询列表。每一个查询列表有两个整数 [start, end]。 对于每个查询,计算出数组中从下标 start 到 end 之间的数的最小值,并返回在结果列表中。
样例
样例1:
输入:数组 :[1,2,7,8,5] 查询 :[(1,2),(0,4),(2,4)]。输出:[2,1,5]
样例2:
输入:数组 :[4,5,7,1] 查询 :[(1,2),(1,3)]。输出:[5,1]
挑战
每次查询在O(logN)的时间内完成
分析
这道题如果用暴力的方法非常好想,每次查询遍历一遍查询区间,时间复杂度O(n),但是当查询次数非常多可能造成很多重复的情况,例如[0,4],[0,5],查完第一个区间后只需要跟下标为5的元素进行比较即可得出第二个的结果,所以这里我们用到了线段树
代码部分
1.线段树节点
//线段树节点
class SegmentTreeNode
{
public:
int start,end;
int minval;
SegmentTreeNode* left; //左右区间
SegmentTreeNode* right;
SegmentTreeNode(int s,int e,int m):start(s),end(e),minval(m)
{
left=NULL;
right=NULL;
}
};
2.线段树
线段树构造
大体思路:创建一个节点,递归的创建它的左右区间节点,然后回溯的地方将左右区间的最小值赋值给当前节点
SegmentTreeNode* build(int start,int end,vector<int> &nums)
{
//特判
if(start>end)
return NULL;
if(start==end)
return new SegmentTreeNode(start,end,nums[start]);
SegmentTreeNode* root=new SegmentTreeNode(start,end,0xffffff);
int mid=(start+end)/2;
root->left=build(start,mid,nums);
root->right=build(mid+1,end,nums);
if(root->left!=NULL)
root->minval=min(root->minval,root->left->minval);
if(root->right!=NULL)
root->minval=min(root->minval,root->right->minval);
return root;
}
线段树查询
也是一个递归查询过程
当前能做的事情:递归的查询左右区间的最小值,将左右区间较小的那个值返回
出口:当区间跟查询区间没有重合部分直接返回一个大值(因为我们要求最小值),当区间在查询区间中的时候直接返回当前节点的最小值
int querymin(SegmentTreeNode* _root,int start,int end,int qstart,int qend)
{
//出口
if(qend<start||qstart>end)
{
return 0xffffff;
}
if(qstart<=start&&qend>=end)
{
return _root->minval;
}
//现在能做的事情
int mid=(start+end)/2;
int leftmin=querymin(_root->left,start,mid,qstart,qend);
int rightmin=querymin(_root->right,mid+1,end,qstart,qend);
return min(leftmin,rightmin);
}
3.解题部分
特判断一下给定数组为空或查询列表为空时,直接返回一个空数组
创建线段树后直接循环的遍历每次查询,将结果存到答案数组中
SegmentTree st(a);
for(int i=0;i<len;i++)
{
int start=queries[i].start;
int end=queries[i].end;
int minval=st.querymin(st.root,0,a.size()-1,start,end);
ans.push_back(minval);
}
完整代码
#include <bits/stdc++.h>
using namespace std;
class Interval
{
public:
int start, end;
Interval(int start, int end)
{
this->start = start;
this->end = end;
}
};
//线段树节点
class SegmentTreeNode
{
public:
int start,end;
int minval;
SegmentTreeNode* left; //左右区间
SegmentTreeNode* right;
SegmentTreeNode(int s,int e,int m):start(s),end(e),minval(m)
{
left=NULL;
right=NULL;
}
};
//线段树
class SegmentTree
{
public:
SegmentTreeNode *root; //根节点
SegmentTree(vector<int> nums)
{
root=build(0,nums.size()-1,nums);
}
SegmentTreeNode* build(int start,int end,vector<int> &nums)
{
//特判
if(start>end)
return NULL;
if(start==end)
return new SegmentTreeNode(start,end,nums[start]);
SegmentTreeNode* root=new SegmentTreeNode(start,end,0xffffff);
int mid=(start+end)/2;
root->left=build(start,mid,nums);
root->right=build(mid+1,end,nums);
if(root->left!=NULL)
root->minval=min(root->minval,root->left->minval);
if(root->right!=NULL)
root->minval=min(root->minval,root->right->minval);
return root;
}
int querymin(SegmentTreeNode* _root,int start,int end,int qstart,int qend)
{
//出口
if(qend<start||qstart>end)
{
return 0xffffff;
}
if(qstart<=start&&qend>=end)
{
return _root->minval;
}
//现在能做的事情
int mid=(start+end)/2;
int leftmin=querymin(_root->left,start,mid,qstart,qend);
int rightmin=querymin(_root->right,mid+1,end,qstart,qend);
return min(leftmin,rightmin);
}
};
class Solution {
public:
vector<int> intervalMinNumber(vector<int> &a, vector<Interval> &queries) {
vector<int> ans;
int len=queries.size();
if(len==0||a.size()==0) //特判
return ans;
SegmentTree st(a);
for(int i=0;i<len;i++)
{
int start=queries[i].start;
int end=queries[i].end;
int minval=st.querymin(st.root,0,a.size()-1,start,end);
ans.push_back(minval);
}
return ans;
}
};
int main (void)
{
vector<int> nums={1,2,7,8,5};
vector<Interval> queries={ Interval(1,2),Interval(0,4),Interval(2,4) };
Solution s;
vector<int> ans;
ans=s.intervalMinNumber(nums,queries);
for(int i=0;i<ans.size();i++)
cout<<ans[i]<<" ";
return 0;
}
总结
在练习运用这些模板时,主要练习两个方面
1.遇到一道题能想到运用的模板和时间复杂度空间复杂度
2.学习模板的思路后,自己的代码实现能力(如何把抽象的东西具体实现)