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;

    }
};
posted @ 2023-04-18 00:01  失控D大白兔  阅读(13)  评论(0编辑  收藏  举报