首页 > 其他分享 >LeetCode 408场周赛,Q3. 统计 1 显著的字符串的数量;问题分析

LeetCode 408场周赛,Q3. 统计 1 显著的字符串的数量;问题分析

时间:2024-07-28 22:42:14浏览次数:16  
标签:周赛 Q3 int sum pos value seg zero LeetCode

https://leetcode.cn/contest/weekly-contest-408/problems/count-the-number-of-substrings-with-dominant-ones/description/、、

这题难度是middle,但是确实有点强思维的味道,赛时思考了许久,没想到好方向,最后想了个线段树的解法。。当然最后超时了 861 / 884,二十多个用例过不去;

简单说下一开始用线段树来解的思路,因为是需要统计所有的子数组,数组 n 是 4e4,然后其实我当时想法是,可以递推的方式,每次向后挪一位,这样每次n + 1,规模就扩大一次,这样一次操作其实就两类,也就是要么是0,要么是 1,那么我们维护一棵线段树,树的子节点一开始是 [i, i],维护每棵子树的 max 和 min,以及每个节点的0和1个数,那么每次规模扩大,也就是新增一个1或者0,就是子节点变成 [i, i + x] 这个范围中 0 的个数和 1的个数,换句话说新增1 就是范围内加1,新增0就是范围内 减去原来0个数的平方,增加目前0个数的平方
这里可以使用 lazy 下放的优化方式;如果当前新增的是 0,且当前范围子树内 max 小于 0 ,那么可以认定全部都不符合,直接在父节点加上一个 0,后续lazy 下放,同理如果当前新增的是 1,且当前范围子树内 min 大于等于 -1,那么直接在父节点上加上一个1,后续 lazy 下方

具体看代码实现:

class Solution {
public:
    const static int maxn = 4e4 + 1;
    struct node {
        int max;
        int min;
        int zero;
        int first;
    };
    node seg[maxn * 4];
    node value[maxn];

    long long ans = 0;
    void initTree(int l, int r, int pos) {
        if (l == r) {
            seg[pos].max = value[l].max;
            seg[pos].min = value[l].min;
            seg[pos].zero = value[l].zero;
            seg[pos].first = value[l].first;
            return ;
        }

        int mid = (l + r) >> 1;
        initTree(l, mid, pos << 1);
        initTree(mid + 1, r, pos << 1 | 1);

        seg[pos].max = max(seg[pos << 1].max, seg[pos << 1 | 1].max);
        seg[pos].min = min(seg[pos << 1].min, seg[pos << 1 | 1].min);
        seg[pos].zero = 0;
        seg[pos].first = 0;
    }

    void addZero(int l, int r, int left, int right, int pos) {
        if (l == r) {
            seg[pos].zero ++;
            seg[pos].max = seg[pos].first - 1L * seg[pos].zero * seg[pos].zero;
            seg[pos].min = seg[pos].max;
            if (seg[pos].max >= 0) ans ++;
            return ;
        }

        if (l >= left && r <= right && seg[pos].max + seg[pos].first < 0) {
            seg[pos].zero ++ ;
            return ;
        }

        if (seg[pos].zero > 0) {
            seg[pos << 1].zero += seg[pos].zero;
            seg[pos << 1 | 1].zero += seg[pos].zero;
            seg[pos].zero = 0;
        }
        if (seg[pos].first > 0) {
            seg[pos << 1].first += seg[pos].first;
            seg[pos << 1 | 1].first += seg[pos].first;
            seg[pos].first = 0;
        }

        int mid = (l + r) >> 1;
        if (left <= mid) {
            addZero(l, mid, left, right, pos << 1);
        }
        if (right > mid) {
            addZero(mid + 1, r, left, right, pos << 1 | 1);
        }

        seg[pos].max = max(seg[pos << 1].max, seg[pos << 1 | 1].max);
        seg[pos].min = min(seg[pos << 1].min, seg[pos << 1 | 1].min);
    }

    void addFirst(int l, int r, int left, int right, int pos) {
        if (l == r) {
            seg[pos].first ++;
            seg[pos].max = seg[pos].first - 1L * seg[pos].zero * seg[pos].zero;
            seg[pos].min = seg[pos].max;
            if (seg[pos].max >= 0) ans ++;
            return ;
        }

        if (l >= left && r <= right && seg[pos].min + seg[pos].first >= -1) {
            seg[pos].first ++;
            ans += r - l + 1;
            return ;
        }

        if (seg[pos].zero > 0) {
            seg[pos << 1].zero += seg[pos].zero;
            seg[pos << 1 | 1].zero += seg[pos].zero;
            seg[pos].zero = 0;
        }
        if (seg[pos].first > 0) {
            seg[pos << 1].first += seg[pos].first;
            seg[pos << 1 | 1].first += seg[pos].first;
            seg[pos].first = 0;
        }

        int mid = (l + r) >> 1;
        if (left <= mid) {
            addFirst(l, mid, left, right, pos << 1);
        }
        if (right > mid) {
            addFirst(mid + 1, r, left, right, pos << 1 | 1);
        }

        seg[pos].max = max(seg[pos << 1].max, seg[pos << 1 | 1].max);
        seg[pos].min = min(seg[pos << 1].min, seg[pos << 1 | 1].min);
    }

    int numberOfSubstrings(string s) {
        for (int i = 0; i < maxn * 4; ++ i) {
            seg[i].max = 0;
            seg[i].min = 0;
            seg[i].zero = 0;
            seg[i].first = 0;
        }

        int zero = 0;
        for (int i = 0; i < s.length(); ++ i) {
            if (s[i] == '0') zero ++;
            int first = i - zero + 1;

            if (s[i] == '0') {
                value[i + 1].max = -1;
                value[i + 1].min = -1;
                value[i + 1].zero = 1;
                value[i + 1].first = 0;
            } else {
                value[i + 1].zero = 0;
                value[i + 1].first = 1;
                value[i + 1].max = 1;
                value[i + 1].min = 1;
                ans ++;
            }
        }

        initTree(1, s.length(), 1);
        for (int i = 1; i < s.length(); ++ i) {
            if (s[i] == '0') {
                addZero(1, s.length(), 1, i, 1);
            } else {
                addFirst(1, s.length(), 1, i, 1);
            }
        }

        return ans;
    }
};

下面是正确的思路

考虑到其实平方的增速是很大的,同样是每次子数组规模扩大1,会有两个操作,新增0,新增1,所以对于一个区间如果当前 1数量 - 0数量平方少于0,那么至少需要向后移动这么多位,才可能大于等于0(全部是1情况),那么可以考虑贪心的优化方式,也就是对于从 i 开始的子区间,j 从 i 到字符串末尾,定义 value = 1数量 - 0数量平方,如果value小于0,可以向后移动value位,如果value 大于等于0,那么考虑目前需要增加多少个0 可以让value 小于0,也就是需要 [sqrt(1数量) 向上取整 - 当前0数量] 个,那么也就可以向后移动 [sqrt(1数量) 向上取整 - 当前0数量] 位。这样的贪心思路,可以大大优化执行耗时

具体看代码逻辑

class Solution {
public:
    const static int maxn = 4e4 + 1;
    int sum[maxn][2];
    int numberOfSubstrings(string s) {
        long long ans = 0;
        sum[0][0] = sum[0][1] = 0;
        if (s[0] == '1') sum[1][1] = 1, sum[1][0] = 0;
        if (s[0] == '0') sum[1][0] = 1, sum[1][1] = 0;
        for (int i = 1; i < s.length(); ++ i) {
            sum[i + 1][1] = sum[i][1] + (s[i] == '1');
            sum[i + 1][0] = sum[i][0] + (s[i] == '0');
        }
        
        for (int i = 0; i < s.length(); ++ i) {
            int j = i + 1;
            while (j <= s.length()) {
                int zero = sum[j][0] - sum[i][0];
                int one = sum[j][1] - sum[i][1];

                int zero2 = zero * zero;
                int diff = one - zero2;
                if (diff >= 0) {
                    int need_zero = sqrt(one);
                    if (need_zero * need_zero <= one) need_zero ++;
                    int diff_zero = need_zero - zero;

                    int l = s.length();
                    ans += 1L * min(diff_zero, l - j + 1);
                    j += diff_zero;
                } else {
                    j -= diff;
                }
            }
        }

        return ans;
    }
};

标签:周赛,Q3,int,sum,pos,value,seg,zero,LeetCode
From: https://www.cnblogs.com/wanshe-li/p/18328999

相关文章

  • LeetCode530. 二叉搜索树的最小绝对差
    题目链接:https://leetcode.cn/problems/minimum-absolute-difference-in-bst/description/题目叙述:给你一个二叉搜索树的根节点root,返回树中任意两不同节点值之间的最小差值。差值是一个正数,其数值等于两值之差的绝对值。示例1:输入:root=[4,2,6,1,3]输出:1示例2:输......
  • LeetCode654. 最大二叉树
    题目链接:https://leetcode.cn/problems/maximum-binary-tree/description/题目叙述给定一个不重复的整数数组nums。最大二叉树可以用下面的算法从nums递归地构建:创建一个根节点,其值为nums中的最大值。递归地在最大值左边的子数组前缀上构建左子树。递归地在最大......
  • (leetcode学习)295. 数据流的中位数
    中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。例如arr=[2,3,4] 的中位数是3 。例如 arr=[2,3]的中位数是(2+3)/2=2.5。实现MedianFinder类:MedianFinder()初始化MedianFinder 对象。voidaddN......
  • LeetCode700. 二叉搜索树中的搜索
    题目链接:https://leetcode.cn/problems/search-in-a-binary-search-tree/description/题目叙述:给定二叉搜索树(BST)的根节点root和一个整数值val。你需要在BST中找到节点值等于val的节点。返回以该节点为根的子树。如果节点不存在,则返回null。示例1:输入:root=[1......
  • 【代码随想录训练营第42期 Day10打卡 LeetCode 232.用栈实现队列 225. 用队列实现栈 2
    目录一、做题心得二、题目与题解题目一:232.用栈实现队列题目链接题解题目二:225.用队列实现栈题目链接题解题目三:20.有效的括号题目链接题解题目四:1047.删除字符串中的所有相邻重复项 题目链接题解三、小结一、做题心得今天是代码随想录训练营打卡的第1......
  • LeetCode_sql_day07(579. 查询员工的累计薪水,2173.最多连胜的次数)
    描述:579.查询员工的累计薪水编写一个解决方案,在一个统一的表中计算出每个员工的 累计工资汇总 。员工的 累计工资汇总 可以计算如下:对于该员工工作的每个月,将 该月 和 前两个月 的工资 加 起来。这是他们当月的 3个月总工资和 。如果员工在前几个月没有为公......
  • LeetCode1005. K 次取反后最大化的数组和
    题目链接:https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/description/题目叙述:给你一个整数数组nums和一个整数k,按以下方法修改该数组:选择某个下标i并将nums[i]替换为-nums[i]。重复这个过程恰好k次。可以多次选择同一个下标i。以这种......
  • LeetCode面试150——189轮转数组
    题目难度:中等默认优化目标:最小化平均时间复杂度。Python默认为Python3。1题目描述给定一个整数数组nums,将数组中的元素向右轮转k个位置,其中k是非负数。示例1:输入:nums=[1,2,3,4,5,6,7],k=3输出:[5,6,7,1,2,3,4]解释:向右轮转1步:[7,1,2,3,4,5,6]......
  • leetcode-7
    题目:给你一个32位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。如果反转后整数超过32位的有符号整数的范围 [−231, 231 −1] ,就返回0。推导:代码:classSolution{public:intreverse(intx){intres=0;while(x!=0){......
  • LeetCode222.完全二叉树的节点个数
    题目链接:https://leetcode.cn/problems/count-complete-tree-nodes/description/题目叙述给你一棵完全二叉树的根节点root,求出该树的节点个数。完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该......