每日一题补题记录13

3.8

2055. 蜡烛之间的盘子

给你一个长桌子,桌子上盘子和蜡烛排成一列。给你一个下标从 0 开始的字符串 s ,它只包含字符 '*' 和 '|' ,其中 '*' 表示一个 盘子 ,'|' 表示一支 蜡烛 。

同时给你一个下标从 0 开始的二维整数数组 queries ,其中 queries[i] = [lefti, righti] 表示 子字符串 s[lefti...righti] (包含左右端点的字符)。对于每个查询,你需要找到 子字符串中 在 两支蜡烛之间 的盘子的 数目 。如果一个盘子在 子字符串中 左边和右边 都 至少有一支蜡烛,那么这个盘子满足在 两支蜡烛之间 。

比方说,s = "||**||**|*" ,查询 [3, 8] ,表示的是子字符串 "*||**|" 。子字符串中在两支蜡烛之间的盘子数目为 2 ,子字符串中右边两个盘子在它们左边和右边 都 至少有一支蜡烛。
请你返回一个整数数组 answer ,其中 answer[i] 是第 i 个查询的答案。

这种查询问题,我的思路很自然的想到了前缀和,用一个pre数组,pre[i]记录到i为止的蜡烛之间的盘子数目,那么ans[i]=pre[r[i]]-pre[l[i-1]]即可。

注意pre的处理初始化,我们要找到左右两边最靠外的蜡烛,在这两个蜡烛之间的所有的盘子都符合要求,我们先去除边界的盘子,找到最边缘的蜡烛即即可

class Solution {
    public int[] platesBetweenCandles(String s, int[][] queries) {
        int len=s.length(),left=0,right=len-1;
        //前缀和数组
        int[]pre=new int[len],ans=new int[queries.length];
        boolean edge=true;
        //注意pre的处理初始化,除了边缘,所有的盘子都符合要求
        //所以我们先去除边界的盘子即可
        while(s.charAt(left)=='*')left++;
        while(s.charAt(right)=='*')right--;
        //处理后left和right都是非边界的第一个下标
        for(int i=left;i<=right;i++){
            if(s.charAt(i)=='|')pre[i]=pre[i-1];
            else pre[i]=pre[i-1]+1;
        }
        for(int i=right+1;i<len;i++){
            pre[i]=pre[right];
        }
        for(int pr:pre){
            System.out.println(pr);
        }
        for(int i=0;i<queries.length;i++){
            int[]q=queries[i];
            int l=q[0],r=q[1];
            if(l==0)ans[i]=pre[r];
            else ans[i]=pre[r]-pre[l-1];
        }
        return ans;
    }
}

但是很不幸的WA了,看了一下样例明白自己错在哪了 

 题意要求我们计算的是,在当前区间段被围住的盘子数,而我们的原算法计算了在原数组中所有的盘子数,这显然是不对的,不过想要解决也很简单,找到当前最外侧的两个蜡烛即可。

class Solution {
    public int[] platesBetweenCandles(String s, int[][] queries) {
        int len=s.length(),left=0,right=len-1;
        //前缀和数组
        int[]pre=new int[len],ans=new int[queries.length];
        boolean edge=true;
        //注意pre的处理初始化,除了边缘,所有的盘子都符合要求
        //所以我们先去除边界的盘子即可
        while(s.charAt(left)=='*')left++;
        while(s.charAt(right)=='*')right--;
        //处理后left和right都是非边界的第一个下标
        for(int i=left;i<=right;i++){
            if(i==0) continue;
            if(s.charAt(i)=='|')pre[i]=pre[i-1];
            else pre[i]=pre[i-1]+1;
        }
        for(int i=right+1;i<len;i++){
            pre[i]=pre[right];
        }
        // for(int pr:pre){
        //     System.out.println(pr);
        // }
        for(int i=0;i<queries.length;i++){
            int[]q=queries[i];
            int l=q[0],r=q[1];
            while(s.charAt(l)=='*')l++;
            while(s.charAt(r)=='*')r--;
            if(l>=r)ans[i]=0;
            else if(l==0)ans[i]=pre[r];
            else ans[i]=pre[r]-pre[l-1];
        }
        return ans;
    }
}

结果这样一来,没有WA了,但是TLE了,一个猜测应该是找边缘蜡烛的过程,只使用了++--,用时太长了,所以我们可以考虑,在初始化前缀和这个过程中,我们可以考虑记录每个位置左侧、右侧最近的蜡烛即可(初始为-1),然后对于查询范围[l,r],我们要找到l右边最近的蜡烛也就是x=right[l],同理y=left[r],根据x/y是否等于0或者x>y再考虑,如果正常返回pre[y]-pre[x]即可。

class Solution {
    public int[] platesBetweenCandles(String s, int[][] queries) {
        int len=s.length();
        //前缀和数组
        int[]pre=new int[len],ans=new int[queries.length];
        int []left=new int[len],right=new int[len];
        //注意pre的处理初始化,统计到各个位置为止的盘子数目
        //处理后left和right记录离i左右最近的蜡烛位置

        //left,right初始为-1,证明这些盘子在界外,绝对找不到蜡烛来包裹
        for (int i = 0, l = -1; i < len; i++) {
            if (s.charAt(i) == '|') {
                l = i;
            }
            left[i] = l;
        }
        for (int i = len - 1, r = -1; i >= 0; i--) {
            if (s.charAt(i) == '|') {
                r = i;
            }
            right[i] = r;
        }
        //pre初始化
        for(int i=1;i<len;i++){
            if(s.charAt(i)=='|')pre[i]=pre[i-1];
            else pre[i]=pre[i-1]+1;
        }
        for(int i=0;i<queries.length;i++){
            int[]q=queries[i];
            int l=q[0],r=q[1];
            //真实范围为l右侧最近的蜡烛到r左侧最近的蜡烛
            int x = right[l], y = left[r];
            //处理特殊情况
            ans[i] = x == -1 || y == -1 || x >= y ? 0 : pre[y] - pre[x];
        }
        return ans;
    }
}

3.9

798. 得分最高的最小轮调

给你一个数组 nums,我们可以将它按一个非负整数 k 进行轮调,这样可以使数组变为 [nums[k], nums[k + 1], ... nums[nums.length - 1], nums[0], nums[1], ..., nums[k-1]] 的形式。此后,任何值小于或等于其索引的项都可以记作一分。

例如,数组为 nums = [2,4,1,3,0],我们按 k = 2 进行轮调后,它将变成 [1,3,0,2,4]。这将记为 3 分,因为 1 > 0 [不计分]、3 > 1 [不计分]、0 <= 2 [计 1 分]、2 <= 3 [计 1 分],4 <= 4 [计 1 分]。
在所有可能的轮调中,返回我们所能得到的最高分数对应的轮调下标 k 。如果有多个答案,返回满足条件的最小的下标 k 。

直观想法自然是k从0到len-1变化,每个k对应的数组要变化并遍历一遍,这样能过吗?时间复杂度显然是O(n^2),数据范围10^5,显然会TLE,必须要狠狠地优化。

看一下题目,我们要找的是,统计满足i-nums[i]>=0的数组,这样我们可以用一个数组来统计i-nums[i],然后k从0到len-1的变化过程中,每一轮的变化,首先分数继承上一轮的结果,实际上都是开头下标变成len-1,这个暂且不谈,但是剩下len-1个元素的下标都-1了,这个研究起来比较轻松,下标-1,那么原先i-nums[i]==0的值自然现在不满足了,那么分数中就要减掉这些数值

然后我们还要考虑首元素:1.首元素=0,那么分数-1

2.首元素>0,那么一开始就不符合条件,分数不变

3.考虑首元素的新位置nums[len-1],考虑这个位置是否符合条件,也就是0+len-1>=nums[k]?符合分数+1

我们不变化原数组,用一个cnt数组记录i-nums[i]只要确定当前的开头位置在nums[k]即可,那么我们i-nums[i]判断的对象也就变成了k(每轮减小1,因此要求越来越高),移除i-nums[i]<k,也就是从分数中移去cnt[k]

首元素判断==0还是一样

首元素新位置,我们就考虑,i-nums[i]增大了len-1,这个数值是否>=0,如果满足,分数++即可。

每轮变化计算分数后,如果刷新了最大值就更新,这就很简单了。

代码如下:

class Solution {
    public int bestRotation(int[] nums) {
        int len = nums.length,ans = 0,score = 0;
        int[] cnt = new int[200000];

        //统计原始数组状态
        //记录一开始的分数
        for(int i=0; i<len; i++) {
            if(i-nums[i] >= 0) {
                cnt[i-nums[i]]++;
                score++;
            }
        }
        int max = score;
        //开始轮调
        for(int k=0; k<len; k++) {
            //首元素离开首位置到最后,判断是否会减分
            //首元素离开改变状态更新,只有等于0的元素在离开索引0位置时
            //才会导致分数减少,并更新cnt数组
            if(nums[k] == 0) {
                cnt[k]--;
                score--;
            }
            
            //如果首元素移动到的新位置符合条件,得分加一并更新cnt数组
            if(nums[k] <= len-1) {
                //首元素迁移到末尾改变状态更新
                //(len-1)-nums[k]+(k+1) = len-nums[k]+k ,
                //(len-1)-nums[k]表示此元素迁移到末尾
                //+(k+1)是因为第k轮会淘汰cnt[k],即使符合条件的0,
                //此时的实际位置也在k+1了,所以必须要+k+1
                cnt[len-nums[k]+k]++;
                score++;
            }

            //每一轮的变化都会淘汰所有i-nums[i]==k的值,因为有n-1个数前移了
            //其中正好擦线的都G了
            score -= cnt[k];

            if(score > max) {
                max = score;
                ans = k+1;
            }
        }
        return ans;
    }
}

这个地方非常重要,如果第一时间没想明白一定要多看几遍,可以理解为每轮变化得分线都在水涨船高,到了第k轮,即使实际的i-nums[i]=0,也一定要+k+1才能保证比较中可以得分

方法二:差分


3.10

589. N 叉树的前序遍历

给定一个 n 叉树的根节点  root ,返回 其节点值的 前序遍历 。

n 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。

使用DFS进行递归前序遍历即可。

class Solution {
    List<Integer>ans;
    public List<Integer> preorder(Node root) {
        ans=new ArrayList<>();
        dfs(root,ans);
        return ans;
    }
    void dfs(Node root,List<Integer>ans){
        if(root==null)return;
        ans.add(root.val);
        for(Node node:root.children){
            dfs(node,ans);
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值