[Leetcode Weekly Contest]271

链接:LeetCode

[Leetcode]2103. 环和杆

总计有 n 个环,环的颜色可以是红、绿、蓝中的一种。这些环分布穿在 10 根编号为 0 到 9 的杆上。

给你一个长度为 2n 的字符串 rings ,表示这 n 个环在杆上的分布。rings 中每两个字符形成一个 颜色位置对 ,用于描述每个环:

第 i 对中的 第一个 字符表示第 i 个环的 颜色('R'、'G'、'B')。
第 i 对中的 第二个 字符表示第 i 个环的 位置,也就是位于哪根杆上('0' 到 '9')。
例如,"R3G2B1" 表示:共有 n == 3 个环,红色的环在编号为 3 的杆上,绿色的环在编号为 2 的杆上,蓝色的环在编号为 1 的杆上。

找出所有集齐 全部三种颜色 环的杆,并返回这种杆的数量。

遍历即可。

class Solution {
    public int countPoints(String rings) {
        int res = 0;
        var ringMap = new HashMap<Integer, HashSet<Character>>();
        for(int i=0;i<rings.length();++i)
        {
            if((i&1)!=0)
            {
                int ring = rings.charAt(i) - '0'; // (int) rings.charAt(i)
                var set = ringMap.getOrDefault(ring, new HashSet<Character>());
                set.add(rings.charAt(i-1));
                ringMap.put(ring, set);
            }
        }
        for (HashSet<Character> set:ringMap.values()) {
            if(set.size() == 3) res ++;
        }
        return res;
    }
}

[Leetcode]2104. 子数组范围和

给你一个整数数组 nums 。nums 中,子数组的 范围 是子数组中最大元素和最小元素的差值。
返回 nums 中 所有 子数组范围的 和 。
子数组是数组中一个连续 非空 的元素序列。

暴力算法,即记录子数组最大元素和最小元素,循环即可,时间复杂度\(O(N^2)\)

class Solution {
    public long subArrayRanges(int[] nums) {
        int n = nums.length;
        long res = 0;
        for(int i=0;i<n;++i){
            int mn = nums[i], mx = nums[i];
            for (int j=i+1;j<n;++j) {
                mn = Math.min(mn, nums[j]);
                mx = Math.max(mx, nums[j]);
                res += mx - mn;
            }
        }
        return res;
    }
}

另外, 我们可以考虑每个元素作为最大值出现在了多少子数组中,以及作为最小值出现在了多少子数组中,即分别计算每个元素对于最大值和最小值的贡献。令left[i] 为左侧严格大于 num[i] 的最近元素位置(不存在时为 -1),right[i] 为右侧大于等于 num[i] 的最近元素位置(不存在时为 n),那么为了包含索引i,该子数组的区间起始点有(i−left[i])种情况,终止点有(right[i]−i)种情况,两者的乘积就是子区间的个数。
这里求left以及right数组就需要用到单调栈了。

class Solution {
    public long subArrayRanges(int[] nums) {
        int n = nums.length;
        long res = 0;
        int[] leftLarger = new int[n], leftSmaller = new int[n];
        int[] rightLarger = new int[n], rightSmaller = new int[n];
        Arrays.fill(leftLarger, -1);
        Arrays.fill(leftSmaller, -1);
        Arrays.fill(rightLarger, n);
        Arrays.fill(rightSmaller, n);
        arrayLeft(nums,leftLarger,leftSmaller);
        arrayRight(nums,rightLarger,rightSmaller);
        for(int i=0;i<n;i++)
        {
            res += 1L * nums[i] *  ((rightLarger[i]-i)*(i-leftLarger[i]) - (rightSmaller[i]-i)*(i-leftSmaller[i]));
        }
        return res;
    }

    //   get first right index > nums[i] with first right index < nums[i]
    public void arrayRight(int[] nums, int[] rightLarger, int[] rightSmaller) {
        int n = nums.length;
        Deque<Integer> increasedStack = new ArrayDeque<>();
        Deque<Integer> decreasedStack = new ArrayDeque<>();
        for(int i=0;i<n;++i) {
            while(!increasedStack.isEmpty() && nums[increasedStack.peek()] > nums[i]){
                rightSmaller[increasedStack.pop()] = i;
            }
            while(!decreasedStack.isEmpty() && nums[decreasedStack.peek()] < nums[i]){
                rightLarger[decreasedStack.pop()] = i;
            }
            increasedStack.push(i);
            decreasedStack.push(i);
        }
    }
    //   get first left index >= nums[i] with first left index <= nums[i]
    public void arrayLeft(int[] nums, int[] leftLarger, int[] leftSmaller) {
        int n = nums.length;
        Deque<Integer> increasedStack = new ArrayDeque<>();
        Deque<Integer> decreasedStack = new ArrayDeque<>();
        for(int i=n-1;i>=0;--i) {
            while(!increasedStack.isEmpty() && nums[increasedStack.peek()] >= nums[i]){
                leftSmaller[increasedStack.pop()] = i;
            }
            while(!decreasedStack.isEmpty() && nums[decreasedStack.peek()] <= nums[i]){
                leftLarger[decreasedStack.pop()] = i;
            }
            increasedStack.push(i);
            decreasedStack.push(i);
        }
    }
}

[Leetcode]2105. 给植物浇水 II

Alice 和 Bob 打算给花园里的 n 株植物浇水。植物排成一行,从左到右进行标记,编号从 0 到 n - 1 。其中,第 i 株植物的位置是 x = i 。

每一株植物都需要浇特定量的水。Alice 和 Bob 每人有一个水罐,最初是满的 。他们按下面描述的方式完成浇水:

Alice 按 从左到右 的顺序给植物浇水,从植物 0 开始。Bob 按 从右到左 的顺序给植物浇水,从植物 n - 1 开始。他们 同时 给植物浇水。
如果没有足够的水 完全 浇灌下一株植物,他 / 她会立即重新灌满浇水罐。
不管植物需要多少水,浇水所耗费的时间都是一样的。
不能 提前重新灌满水罐。
每株植物都可以由 Alice 或者 Bob 来浇水。
如果 Alice 和 Bob 到达同一株植物,那么当前水罐中水更多的人会给这株植物浇水。如果他俩水量相同,那么 Alice 会给这株植物浇水。
给你一个下标从 0 开始的整数数组 plants ,数组由 n 个整数组成。其中,plants[i] 为第 i 株植物需要的水量。另有两个整数 capacityA 和 capacityB 分别表示 Alice 和 Bob 水罐的容量。返回两人浇灌所有植物过程中重新灌满水罐的 次数 。

双指针。根据题意遍历即可。

class Solution {
    public int minimumRefill(int[] plants, int capacityA, int capacityB) {
        int left = 0, right = plants.length - 1;
        int res = 0, left_capacity = capacityA, right_capacity = capacityB;
        while(left <= right) {
            if (left == right) {
                if(Math.max(left_capacity,right_capacity) < plants[left]) res ++;
            }
            else {
                if(left_capacity < plants[left]) {
                    left_capacity = capacityA;
                    res ++;
                }
                if(right_capacity < plants[right]) {
                    right_capacity = capacityB;
                    res ++;
                }
                left_capacity -= plants[left];
                right_capacity -= plants[right];
            }
            left ++;
            right --;
        }
        return res;
    }
}

[Leetcode]2106. 摘水果

在一个无限的 x 坐标轴上,有许多水果分布在其中某些位置。给你一个二维整数数组 fruits ,其中 fruits[i] = [positioni, amounti] 表示共有 amounti 个水果放置在 positioni 上。fruits 已经按 positioni 升序排列 ,每个 positioni 互不相同 。

另给你两个整数 startPos 和 k 。最初,你位于 startPos 。从任何位置,你可以选择 向左或者向右 走。在 x 轴上每移动 一个单位 ,就记作 一步 。你总共可以走 最多 k 步。你每达到一个位置,都会摘掉全部的水果,水果也将从该位置消失(不会再生)。

返回你可以摘到水果的 最大总数 。

显然在最优的方案中,最多掉头一次。那么就有两种情况:

  • 先往左,如果还有步数,再往右
  • 先往右,如果还有步数,再往左

针对这两种情况,我们无非是需要计算线段 [l, r] 覆盖了多少水果。
线段有多少种可能呢?假设人向左走 \(y\) 步,然后回到原点,再向右走 \(x\) 步,那么区间长度就是:\(x + 2y\) ,其中 \(x + 2y \le k\) ,区间表示为 [startPos - y, startPos + x] 。
同理,如果向右走 \(y\) 步,然后回到原点,再向左走 \(x\) 步,那么区间表示为 [startPos - x, startPos + y] 。
所以我们枚举 \(y\) 长度就可以把所有最长线段都枚举出来。下面我们计算线段 [l, r]水果个数的时候,就可以用前缀和的知识,由于并不是在每一个点都有水果,我们就优化到线段[l, r]所覆盖的有水果区间[l_hi, r_lo]上, 其中l_hi是指l右侧第一个有水果的索引,同理,r_lo指r左侧第一个有水果的索引。这里就需要二分法来进行优化。
最终解决方案即遍历+前缀和+二分

class Solution {
    public int maxTotalFruits(int[][] fruits, int startPos, int k) {
        int res = 0, n = fruits.length, cur = 0;
        int[] preSum = new int[n];
        for(int i=0;i<n; ++i) {
            cur += fruits[i][1];
            preSum[i] = cur;
        }
        for(int x=Math.min(startPos,fruits[0][0]); x<=startPos; ++x) {
            if(startPos-x > k) continue;
            int xi = GetSmaller(fruits, x);
            int y = Math.max(k - 2*(startPos-x) + startPos, startPos + (int) ((k-(startPos-x)) / 2));
            int yi = GetSmallerOrEqualIndex(fruits, y);
            if(yi == -1) continue;
            else if(xi == -1)  res = Math.max(res, preSum[yi]);
            else res = Math.max(res, preSum[yi]- preSum[xi]);
        }
        return res;
    }

    public int GetSmallerOrEqualIndex(int[][] fruits, int target) {
        int lo = 0, hi = fruits.length-1;
        while(lo <= hi) {
            int mid = lo + ((hi - lo) >> 1);
            int pos = fruits[mid][0];
            if (pos == target) return mid;
            if (pos > target) hi = mid - 1;
            else lo = mid + 1;
        }
        return hi;
    }

    public int GetSmaller(int[][] fruits, int target) {
        int lo = 0, hi = fruits.length-1;
        while(lo <= hi) {
            int mid = lo + ((hi - lo) >> 1);
            int pos = fruits[mid][0];
            if (pos >= target) hi = mid - 1;
            else lo = mid + 1;
        }
        return hi;
    }
}

Leetcode

posted @ 2022-03-07 21:28  Jamest  阅读(38)  评论(0编辑  收藏  举报