LeetCode/子数组中占绝大多数的元素
设计一个数据结构,有效地找到给定子数组的 多数元素 。
子数组的 多数元素 是在子数组中出现 threshold 次数或次数以上(大于半数)的元素
分析
对于子区间进行多次查询,采用线段树的方法
给定的数组,我们可以将它分成任意的两部分,分别使用投票算法得到多数元素和出现的次数
如果该数组存在多数元素,那么至少是其中一个子数组的多数元素
线段树存储每个区间节点的多数元素value和count
1. 摩尔投票方法
class Solution {
public:
int majorityElement(vector<int>& nums) {
int candidate = -1; //候选人待定
int count = 0;//当前候选人计数
for (int num : nums) {//遍历所有数字
if (num == candidate)//候选人得票
++count;
else if (--count <0) {//候选人票对冲,如果小于0,更换候选人
candidate = num;
count = 1;//更换后初始票数为1
}
}
return candidate;
}
};
2. 线段树
class MajorityChecker {
struct TreeNode {
TreeNode(int x = 0, int cnt = 0): value(x), cnt(cnt) {}
TreeNode& operator+=(const TreeNode& that) {//重载操作,莫尔投票两节点竞争,用于更新,和查询时筛选
if (value == that.value)//相等求和
cnt += that.cnt;
else if (cnt >= that.cnt)//竞争
cnt -= that.cnt;
else {//竞争失败替换
value = that.value;
cnt = that.cnt - cnt;
}
return *this;
}
int left; // 节点区间左边界
int right; // 节点区间右边界
int value; // 节点存储的信息,区间内可能的多数元素
int cnt;//该元素的计数
};
private:
int n;
const vector<int> &arr;
unordered_map<int, vector<int>> loc;//记录元素的坐标和个数
vector<TreeNode> tree;//线段树
void build(int index, int left, int right) {
tree[index].left = left;
tree[index].right = right;
// 叶子节点,存储输入数组的元素值
if (left == right) {
tree[index].value = arr[left];
tree[index].cnt = 1;
return;
}
// 非叶子节点,递归构建左子树和右子树,后序遍历
int mid = (left + right) / 2;
build(index * 2 + 1, left, mid);//递归建左树,左节点对应左区间
build(index * 2 + 2, mid + 1, right);//递归建右树,右节点对应右区间
// 使用重载操作,更新当前节点的信息,相同求和,否则抵消
tree[index]+=tree[index*2+1];
tree[index]+=tree[index*2+2];
}
void se_query( int index, int left, int right,TreeNode&res) {
if(right<tree[index].left ||left>tree[index].right)
return;
// 查询区间覆盖节点区间,直接返回节点存储的信息,不再往下递归
if (left<=tree[index].left && right>=tree[index].right){
res += tree[index];
return;
}
// 否则,根据查询区间与节点区间的关系递归查询左子树或右子树
//int mid = (tree[index].left + tree[index].right) / 2;
se_query(index * 2 + 1, left, right,res);
se_query(index * 2 + 2, left, right,res);
}
public:
MajorityChecker(vector<int>& arr):arr(arr) {
n = arr.size();
for(int i=0;i<n;i++)
loc[arr[i]].push_back(i);//记录值对应下标,用于后面确认
tree.resize(n*4);//线段树长度为4n-1
build(0, 0, n-1);//从0节点开始,区间范围0~n-1,递归建立线段树
}
int query(int left, int right, int threshold) {
TreeNode res;//因为要竞争节点,得到多数元素及次数,单纯返回不了
se_query(0,left,right,res);//这里将节点传入并竞争查询
vector<int> &pos = loc[res.value];//对应可能多数元素的所有位置
//因为是闭区间,左边要是第一个,右边是最后一个
auto leftbound = lower_bound(pos.begin(), pos.end(), left);//第一个大于等于left的位置
auto rightbound = upper_bound(pos.begin(), pos.end(), right);//大于right的第一个元素
if(rightbound-leftbound>=threshold) return res.value;
else return -1;
}
};