周赛344(前后缀分解、模拟、树上贪心)

文章目录

  • 周赛344
    • [2670. 找出不同元素数目差数组](https://leetcode.cn/problems/find-the-distinct-difference-array/)
      • 前后缀分解
    • [2671. 频率跟踪器](https://leetcode.cn/problems/frequency-tracker/)
      • 模拟(两个哈希表)
    • [2672. 有相同颜色的相邻元素数目](https://leetcode.cn/problems/number-of-adjacent-elements-with-the-same-color/)
      • 模拟
    • [2673. 使二叉树所有路径值相等的最小代价](https://leetcode.cn/problems/make-costs-of-paths-equal-in-a-binary-tree/)
      • 贪心

周赛344

2670. 找出不同元素数目差数组

难度简单0

给你一个下标从 0 开始的数组 nums ,数组长度为 n

nums不同元素数目差 数组可以用一个长度为 n 的数组 diff 表示,其中 diff[i] 等于前缀 nums[0, ..., i] 中不同元素的数目 减去 后缀 nums[i + 1, ..., n - 1] 中不同元素的数目。

返回 nums不同元素数目差 数组。

注意 nums[i, ..., j] 表示 nums 的一个从下标 i 开始到下标 j 结束的子数组(包含下标 ij 对应元素)。特别需要说明的是,如果 i > j ,则 nums[i, ..., j] 表示一个空子数组。

示例 1:

输入:nums = [1,2,3,4,5]
输出:[-3,-1,1,3,5]
解释:
对于 i = 0,前缀中有 1 个不同的元素,而在后缀中有 4 个不同的元素。因此,diff[0] = 1 - 4 = -3 。
对于 i = 1,前缀中有 2 个不同的元素,而在后缀中有 3 个不同的元素。因此,diff[1] = 2 - 3 = -1 。
对于 i = 2,前缀中有 3 个不同的元素,而在后缀中有 2 个不同的元素。因此,diff[2] = 3 - 2 = 1 。
对于 i = 3,前缀中有 4 个不同的元素,而在后缀中有 1 个不同的元素。因此,diff[3] = 4 - 1 = 3 。
对于 i = 4,前缀中有 5 个不同的元素,而在后缀中有 0 个不同的元素。因此,diff[4] = 5 - 0 = 5 。

示例 2:

输入:nums = [3,2,3,4,2]
输出:[-2,-1,0,2,3]
解释:
对于 i = 0,前缀中有 1 个不同的元素,而在后缀中有 3 个不同的元素。因此,diff[0] = 1 - 3 = -2 。
对于 i = 1,前缀中有 2 个不同的元素,而在后缀中有 3 个不同的元素。因此,diff[1] = 2 - 3 = -1 。
对于 i = 2,前缀中有 2 个不同的元素,而在后缀中有 2 个不同的元素。因此,diff[2] = 2 - 2 = 0 。
对于 i = 3,前缀中有 3 个不同的元素,而在后缀中有 1 个不同的元素。因此,diff[3] = 3 - 1 = 2 。
对于 i = 4,前缀中有 3 个不同的元素,而在后缀中有 0 个不同的元素。因此,diff[4] = 3 - 0 = 3 。 

提示:

  • 1 <= n == nums.length <= 50
  • 1 <= nums[i] <= 50

前后缀分解

python

class Solution:
    def distinctDifferenceArray(self, nums: List[int]) -> List[int]:
        # 预处理后缀不同元素的个数
        n = len(nums)
        suf = [0] * (n+1)
        s = set()
        for i in range(n-1, -1, -1):
            s.add(nums[i])
            suf[i] = len(s)
        print(suf)

        ans = [0] * n
        s = set()
        for i in range(n):
            s.add(nums[i])
            # 这里减去suf[i+1] 因为题目要求后缀不包括i
            ans[i] = len(s) - suf[i+1]
        return ans

比赛时写的暴力

class Solution {
    public int[] distinctDifferenceArray(int[] nums) {
        int n = nums.length;
        int[] res = new int[n];
        Set<Integer> pre = new HashSet<>();
        for(int i = 0; i < n; i++){
            pre.add(nums[i]);
            Set<Integer> suf = new HashSet<>();
            for(int j = i+1; j < n; j++){
                suf.add(nums[j]);
            }
            res[i] = pre.size() - suf.size();
        }
        return res;
    }
}

2671. 频率跟踪器

难度中等2

请你设计并实现一个能够对其中的值进行跟踪的数据结构,并支持对频率相关查询进行应答。

实现 FrequencyTracker 类:

  • FrequencyTracker():使用一个空数组初始化 FrequencyTracker 对象。
  • void add(int number):添加一个 number 到数据结构中。
  • void deleteOne(int number):从数据结构中删除一个 number 。数据结构 可能不包含 number ,在这种情况下不删除任何内容。
  • bool hasFrequency(int frequency): 如果数据结构中存在出现 frequency 次的数字,则返回 true,否则返回 false

示例 1:

输入
["FrequencyTracker", "add", "add", "hasFrequency"]
[[], [3], [3], [2]]
输出
[null, null, null, true]

解释
FrequencyTracker frequencyTracker = new FrequencyTracker();
frequencyTracker.add(3); // 数据结构现在包含 [3]
frequencyTracker.add(3); // 数据结构现在包含 [3, 3]
frequencyTracker.hasFrequency(2); // 返回 true ,因为 3 出现 2 次

示例 2:

输入
["FrequencyTracker", "add", "deleteOne", "hasFrequency"]
[[], [1], [1], [1]]
输出
[null, null, null, false]

解释
FrequencyTracker frequencyTracker = new FrequencyTracker();
frequencyTracker.add(1); // 数据结构现在包含 [1]
frequencyTracker.deleteOne(1); // 数据结构现在为空 []
frequencyTracker.hasFrequency(1); // 返回 false ,因为数据结构为空

示例 3:

输入
["FrequencyTracker", "hasFrequency", "add", "hasFrequency"]
[[], [2], [3], [1]]
输出
[null, false, null, true]

解释
FrequencyTracker frequencyTracker = new FrequencyTracker();
frequencyTracker.hasFrequency(2); // 返回 false ,因为数据结构为空
frequencyTracker.add(3); // 数据结构现在包含 [3]
frequencyTracker.hasFrequency(1); // 返回 true ,因为 3 出现 1 次

提示:

  • 1 <= number <= 105
  • 1 <= frequency <= 105
  • 最多调用 adddeleteOnehasFrequency 共计 2 * 105

模拟(两个哈希表)

一个哈希表记录数字和该数字出现频率,另一个哈希表记录出现频率的频率

class FrequencyTracker {
    
    Map<Integer, Integer> map; // num, freq
    Map<Integer, Integer> fre; // freq, cnt

    public FrequencyTracker() {
        map = new HashMap<>();
        fre = new HashMap<>();
    }
    
    public void add(int number) {
        if(map.containsKey(number)){
            int f = map.get(number);
            map.put(number, f+1);
            fre.put(f, fre.get(f) - 1);
            fre.put(f+1, fre.getOrDefault(f+1, 0) + 1);
        }else{
            map.put(number, 1);
            fre.put(1, fre.getOrDefault(1, 0) + 1);
        }
    }
    
    public void deleteOne(int number) {
        if(!map.containsKey(number) || map.get(number) == 0) 
            return;
        int f = map.get(number);
        map.put(number, f-1);
        fre.put(f, fre.get(f) - 1);
        fre.put(f-1, fre.getOrDefault(f-1, 0) + 1);
    }
    
    public boolean hasFrequency(int frequency) {
        return fre.containsKey(frequency) && fre.get(frequency) != 0;
    }
}

2672. 有相同颜色的相邻元素数目

难度中等0

给你一个下标从 0 开始、长度为 n 的数组 nums 。一开始,所有元素都是 未染色 (值为 0 )的。

给你一个二维整数数组 queries ,其中 queries[i] = [indexi, colori]

对于每个操作,你需要将数组 nums 中下标为 indexi 的格子染色为 colori

请你返回一个长度与 queries 相等的数组 answer ,其中 answer[i]是前 i 个操作 之后 ,相邻元素颜色相同的数目。

更正式的,answer[i] 是执行完前 i 个操作后,0 <= j < n - 1 的下标 j 中,满足 nums[j] == nums[j + 1]nums[j] != 0 的数目。

示例 1:

输入:n = 4, queries = [[0,2],[1,2],[3,1],[1,1],[2,1]]
输出:[0,1,1,0,2]
解释:一开始数组 nums = [0,0,0,0] ,0 表示数组中还没染色的元素。
- 第 1 个操作后,nums = [2,0,0,0] 。相邻元素颜色相同的数目为 0 。
- 第 2 个操作后,nums = [2,2,0,0] 。相邻元素颜色相同的数目为 1 。
- 第 3 个操作后,nums = [2,2,0,1] 。相邻元素颜色相同的数目为 1 。
- 第 4 个操作后,nums = [2,1,0,1] 。相邻元素颜色相同的数目为 0 。
- 第 5 个操作后,nums = [2,1,1,1] 。相邻元素颜色相同的数目为 2 。

示例 2:

输入:n = 1, queries = [[0,100000]]
输出:[0]
解释:一开始数组 nums = [0] ,0 表示数组中还没染色的元素。
- 第 1 个操作后,nums = [100000] 。相邻元素颜色相同的数目为 0 。

提示:

  • 1 <= n <= 105
  • 1 <= queries.length <= 105
  • queries[i].length == 2
  • 0 <= indexi <= n - 1
  • 1 <= colori <= 105

模拟

核心在于:对i位置的修改只会影响到i-1iii+1两个位置的对答案的贡献

我们把“改掉 color[idx]”分成两步:第一步是把 color[idx] 原来的颜色去掉,第二步是把 color[idx] 新的颜色加上。

  • 注意到改掉 color[idx],只会影响两对相邻元素,即 (color[idx - 1], color[idx])(color[idx], color[idx + 1])。因此检查这两对元素的变化情况即可。
class Solution {
    public int[] colorTheArray(int n, int[][] queries) {
        int sum = 0;
        int[] res = new int[queries.length];
        int[] arr = new int[n];
        for(int i = 0; i < queries.length; i++){
            int pos = queries[i][0], val = queries[i][1];
            // 把 A[idx] 原来的颜色去掉,检查受影响的两对元素原来是不是相等的
            if(pos > 0 && arr[pos-1] != 0 && arr[pos] != 0 && arr[pos-1] == arr[pos]){
                sum -= 1;
            }
            if(pos < n-1 && arr[pos+1] != 0 && arr[pos] != 0 && arr[pos] == arr[pos+1]){
                sum -= 1;
            }
            // 把 A[idx] 新的颜色加上,检查受影响的两对元素现在是不是相等的
            arr[pos] = val;
            if(pos > 0 && arr[pos-1] != 0 && arr[pos] != 0 && arr[pos-1] == arr[pos]) sum += 1;
            if(pos < n-1 && arr[pos+1] != 0 && arr[pos] != 0 && arr[pos] == arr[pos+1]) sum += 1;
            res[i] = sum;
        }
        return res;
    }
}

2673. 使二叉树所有路径值相等的最小代价

难度中等1

给你一个整数 n 表示一棵 满二叉树 里面节点的数目,节点编号从 1n 。根节点编号为 1 ,树中每个非叶子节点 i 都有两个孩子,分别是左孩子 2 * i 和右孩子 2 * i + 1

树中每个节点都有一个值,用下标从 0 开始、长度为 n 的整数数组 cost 表示,其中 cost[i] 是第 i + 1 个节点的值。每次操作,你可以将树中 任意 节点的值 增加 1 。你可以执行操作 任意 次。

你的目标是让根到每一个 叶子结点 的路径值相等。请你返回 最少 需要执行增加操作多少次。

注意:

  • 满二叉树 指的是一棵树,它满足树中除了叶子节点外每个节点都恰好有 2 个节点,且所有叶子节点距离根节点距离相同。
  • 路径值 指的是路径上所有节点的值之和。

示例 1:

输入:n = 7, cost = [1,5,2,2,3,3,1]
输出:6
解释:我们执行以下的增加操作:
- 将节点 4 的值增加一次。
- 将节点 3 的值增加三次。
- 将节点 7 的值增加两次。
从根到叶子的每一条路径值都为 9 。
总共增加次数为 1 + 3 + 2 = 6 。
这是最小的答案。

示例 2:

输入:n = 3, cost = [5,3,3]
输出:0
解释:两条路径已经有相等的路径值,所以不需要执行任何增加操作。

提示:

  • 3 <= n <= 105
  • n + 12 的幂
  • cost.length == n
  • 1 <= cost[i] <= 104

题解:https://leetcode.cn/problems/make-costs-of-paths-equal-in-a-binary-tree/solution/tan-xin-jian-ji-xie-fa-pythonjavacgo-by-5svh1/

贪心

考虑根到两个互为兄弟节点的叶子的两条路径。

由于这两条路径除了叶子节点不一样,其余节点都一样,所以为了让这两条路径的路径和相等,必须修改叶子节点的值。 可不可以修改根节点的值使两条路径相等呢?不行,因为每个左右子树都要单独对齐,他们之间的差距不能靠上层弥补

设叶子节点的值分别为 x 和 y,假设 x <= y,是否需要同时增加 x 和 y ?
这是不需要的,把 x 增加 y - x 就行,因为我们可以增加它们的祖先节点的值,使得它们俩的路径和与其它的路径和相等,这样可以节省操作次数

**对于不是叶子的兄弟节点,又要如何比较和计算呢?**和上面的分析一样,从根到当前节点的路径,除了这两个兄弟节点不一样,其余节点都一样。所以把路径和从叶子往上传,这样就可以比较了。

class Solution:
    def minIncrements(self, n: int, cost: List[int]) -> int:
        for i in range(2, n+1):
            cost[i - 1] += cost[i // 2 - 1] # 累加路径和
        ans = 0
        for i in range(n // 2, 0 , -1):
            ans += abs(cost[i * 2 - 1] - cost[i * 2]) # 把两个子节点变成一样的
            cost[i-1] = max(cost[i * 2 - 1], cost[i * 2]) # 把子节点的路径和返回给当前节点
        return ans

也可以直接一次遍历上计算和累加路径和

class Solution:
    def minIncrements(self, n: int, cost: List[int]) -> int:
        ans = 0
        for i in range(n // 2, 0 , -1):
            ans += abs(cost[i * 2 - 1] - cost[i * 2]) # 把两个子节点变成一样的
            cost[i-1] += max(cost[i * 2 - 1], cost[i * 2]) # 把子节点的路径和返回给当前节点
        return ans

你可能感兴趣的:(算法刷题记录,leetcode,数据结构)