Medium

题目描述

给定一个大小为 n 的数组 nums,由非负整数组成。你的任务是对数组进行一些(可能为零次)操作,使所有元素都变为 0。

在一次操作中,你可以选择一个子数组 [i, j](其中 0 <= i <= j < n),并将该子数组中最小的非负整数的所有出现都设置为 0。

返回使数组中所有元素都变为 0 所需的最少操作次数。

示例 1:

输入:nums = [0,2]

输出:1

解释: 选择子数组 [1,1](即 [2]),其中最小的非负整数是 2。将所有出现的 2 设置为 0,得到 [0,0]。 因此,所需的最少操作次数为 1。

示例 2:

输入:nums = [3,1,2,1]

输出:3

解释: 选择子数组 [1,3](即 [1,2,1]),其中最小的非负整数是 1。将所有出现的 1 设置为 0,得到 [3,0,2,0]。 选择子数组 [2,2](即 [2]),其中最小的非负整数是 2。将所有出现的 2 设置为 0,得到 [3,0,0,0]。 选择子数组 [0,0](即 [3]),其中最小的非负整数是 3。将所有出现的 3 设置为 0,得到 [0,0,0,0]。 因此,所需的最少操作次数为 3。

示例 3:

输入:nums = [1,2,1,2,1,2]

输出:4

约束条件:

  • 1 <= n == nums.length <= 10^5
  • 0 <= nums[i] <= 10^5

提示:

  • 按从小到大的顺序处理 nums 中的值(不包括 0)。
  • 对于每个目标值 v,识别其最大连续段(nums[i] == v 的子数组);每个段可以在一次操作中归零。
  • 将这些段设置为零后,动态更新剩余数组并对下一个值重复。

解题思路

解题思路

这道题的核心思想是贪心策略结合排序处理。关键观察是:我们必须按照从小到大的顺序处理非零值,因为操作会将子数组中的最小值设为零。

主要思路:

  1. 排序处理:将所有非零值按升序排序,这样确保每次操作时,当前值确实是子数组中的最小值。

  2. 连续段计算:对于每个值,我们需要计算它形成了多少个连续的段。每个连续段需要一次操作来清零。关键是要考虑已经被清零的位置对连续性的影响。

  3. 动态更新:使用布尔数组标记哪些位置已被清零。当处理某个值时,只考虑尚未被清零的相同值的位置,计算它们形成的连续段数量。

  4. 优化实现:可以使用哈希表存储每个值的所有位置,然后按值的大小依次处理。

算法步骤:

  • 遍历数组,记录每个非零值及其所有出现位置
  • 按值的大小升序处理每个非零值
  • 对于当前值,在未被清零的位置中计算连续段数量
  • 将该值的所有位置标记为已清零
  • 累加操作次数

这种方法确保了每次操作都是最优的,因为我们总是选择当前可处理的最小值,并且最大化每次操作的效果。

代码实现

class Solution {
public:
    int minOperations(vector<int>& nums) {
        int n = nums.size();
        map<int, vector<int>> positions;
        
        // Record positions of each non-zero value
        for (int i = 0; i < n; i++) {
            if (nums[i] != 0) {
                positions[nums[i]].push_back(i);
            }
        }
        
        vector<bool> cleared(n, false);
        int operations = 0;
        
        // Process values in ascending order
        for (auto& [value, pos] : positions) {
            int segments = 0;
            bool inSegment = false;
            
            // Count contiguous segments for this value
            for (int p : pos) {
                if (!cleared[p]) {
                    if (!inSegment) {
                        segments++;
                        inSegment = true;
                    }
                } else {
                    inSegment = false;
                }
            }
            
            operations += segments;
            
            // Mark all positions of this value as cleared
            for (int p : pos) {
                cleared[p] = true;
            }
        }
        
        return operations;
    }
};
class Solution:
    def minOperations(self, nums: List[int]) -> int:
        from collections import defaultdict
        
        n = len(nums)
        positions = defaultdict(list)
        
        # Record positions of each non-zero value
        for i, num in enumerate(nums):
            if num != 0:
                positions[num].append(i)
        
        cleared = [False] * n
        operations = 0
        
        # Process values in ascending order
        for value in sorted(positions.keys()):
            pos_list = positions[value]
            segments = 0
            in_segment = False
            
            # Count contiguous segments for this value
            for p in pos_list:
                if not cleared[p]:
                    if not in_segment:
                        segments += 1
                        in_segment = True
                else:
                    in_segment = False
            
            operations += segments
            
            # Mark all positions of this value as cleared
            for p in pos_list:
                cleared[p] = True
        
        return operations
public class Solution {
    public int MinOperations(int[] nums) {
        int n = nums.Length;
        var positions = new Dictionary<int, List<int>>();
        
        // Record positions of each non-zero value
        for (int i = 0; i < n; i++) {
            if (nums[i] != 0) {
                if (!positions.ContainsKey(nums[i])) {
                    positions[nums[i]] = new List<int>();
                }
                positions[nums[i]].Add(i);
            }
        }
        
        bool[] cleared = new bool[n];
        int operations = 0;
        
        // Process values in ascending order
        var sortedKeys = positions.Keys.OrderBy(x => x);
        foreach (int value in sortedKeys) {
            var posList = positions[value];
            int segments = 0;
            bool inSegment = false;
            
            // Count contiguous segments for this value
            foreach (int p in posList) {
                if (!cleared[p]) {
                    if (!inSegment) {
                        segments++;
                        inSegment = true;
                    }
                } else {
                    inSegment = false;
                }
            }
            
            operations += segments;
            
            // Mark all positions of this value as cleared
            foreach (int p in posList) {
                cleared[p] = true;
            }
        }
        
        return operations;
    }
}
var minOperations = function(nums) {
    const n = nums.length;
    const positions = new Map();
    
    // Record positions of each non-zero value
    for (let i = 0; i < n; i++) {
        if (nums[i] !== 0) {
            if (!positions.has(nums[i])) {
                positions.set(nums[i], []);
            }
            positions.get(nums[i]).push(i);
        }
    }
    
    const cleared = new Array(n).fill(false);
    let operations = 0;
    
    // Process values in ascending order
    const sortedValues = Array.from(positions.keys()).sort((a, b) => a - b);
    
    for (const value of sortedValues) {
        const posList = positions.get(value);
        let segments = 0;
        let inSegment = false;
        
        // Count contiguous segments for this value
        for (const p of posList) {
            if (!cleared[p]) {
                if (!inSegment) {
                    segments++;
                    inSegment = true;
                }
            } else {
                inSegment = false;
            }
        }
        
        operations += segments;
        
        // Mark all positions of this value as cleared
        for (const p of posList) {
            cleared[p] = true;
        }
    }
    
    return operations;
};

复杂度分析

复杂度分析
时间复杂度O(n log n),其中 n 是数组长度。主要开销来自对值的排序和遍历所有位置
空间复杂度O(n),用于存储位置信息和标记数组