leetcode
leetcode 2501 ~ 2550
最多 K 个重复元素的最长子数组

最多 K 个重复元素的最长子数组

难度:

标签:

题目描述

You are given an integer array nums and an integer k.

The frequency of an element x is the number of times it occurs in an array.

An array is called good if the frequency of each element in this array is less than or equal to k.

Return the length of the longest good subarray of nums.

A subarray is a contiguous non-empty sequence of elements within an array.

 

Example 1:

Input: nums = [1,2,3,1,2,3,1,2], k = 2
Output: 6
Explanation: The longest possible good subarray is [1,2,3,1,2,3] since the values 1, 2, and 3 occur at most twice in this subarray. Note that the subarrays [2,3,1,2,3,1] and [3,1,2,3,1,2] are also good.
It can be shown that there are no good subarrays with length more than 6.

Example 2:

Input: nums = [1,2,1,2,1,2,1,2], k = 1
Output: 2
Explanation: The longest possible good subarray is [1,2] since the values 1 and 2 occur at most once in this subarray. Note that the subarray [2,1] is also good.
It can be shown that there are no good subarrays with length more than 2.

Example 3:

Input: nums = [5,5,5,5,5,5,5], k = 4
Output: 4
Explanation: The longest possible good subarray is [5,5,5,5] since the value 5 occurs 4 times in this subarray.
It can be shown that there are no good subarrays with length more than 4.

 

Constraints:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 109
  • 1 <= k <= nums.length

代码结果

运行时间: 224 ms, 内存: 31.4 MB


/* 
 * 思路:使用滑动窗口算法和Java Stream API。 
 * 我们维护一个窗口,并使用哈希表记录当前窗口内每个元素的频率。 
 * 如果某个元素的频率超过k,则移动窗口的左端以缩小窗口,直到所有元素频率都<=k。 
 * 在这个过程中,记录窗口的最大长度。
 */ 
import java.util.HashMap; 
import java.util.Map; 
import java.util.stream.IntStream; 

public class Solution { 
    public int longestGoodSubarray(int[] nums, int k) { 
        Map<Integer, Integer> count = new HashMap<>(); 
        int[] result = {0}; 
        IntStream.range(0, nums.length).forEach(right -> { 
            count.put(nums[right], count.getOrDefault(nums[right], 0) + 1); 
            while (count.get(nums[right]) > k) { 
                count.put(nums[result[0]], count.get(nums[result[0]]) - 1); 
                result[0]++; 
            } 
            result[0] = Math.max(result[0], right - result[0] + 1); 
        }); 
        return result[0]; 
    } 
}

解释

方法:

此题解采用了滑动窗口的方法来找出最长的好子数组。通过一个左指针l和一个右指针r来定义窗口的边界。随着右指针r的向右移动,使用哈希表cnt来记录窗口内每个元素的出现次数。每次右指针向右移动时,将新的元素加入计数,如果此元素的计数超过k,则记录当前窗口的长度,并调整左指针l,直到该元素的计数不超过k。这样可以确保窗口内的所有元素频率都不超过k,从而找到满足条件的最长子数组。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
在算法中,你是如何确保更新`ans`时考虑了所有可能的最长好子数组?
在算法中,每次当右指针`r`向右移动并发现一个元素的计数超过了`k`时,就会更新`ans`记录当前的最长好子数组长度。这是在前一个元素的计数导致窗口不再有效时进行的。随后,左指针`l`被调整直到窗口内的所有元素的频率都不超过`k`。因此,每次窗口状态变化都会考虑是否更新`ans`,确保覆盖了所有可能的最长好子数组的情况。
🦆
为什么在元素频率超过`k`时,需要通过移动左指针`l`来调整窗口,而不是采用其他方法?
当元素频率超过`k`时,说明当前窗口已经不满足题目条件(即窗口内的所有元素的频率都不超过`k`)。为了快速恢复窗口的有效性,最直接的方法是减少窗口内频率超标的元素数量,这可以通过移动左指针`l`实现。这种方法可以直接减少特定元素的计数,而如果采用其他方法如重新构建窗口或跳过某些元素,则可能导致较大的时间开销和复杂性增加。
🦆
在循环结束后,为何需要再次更新`ans`来计算最后一个窗口的长度?这是否意味着前面的循环中可能漏掉了某些情况?
在循环结束后需要再次更新`ans`是因为循环内部更新`ans`的条件是元素的计数超过`k`时,这时会提前结束当前窗口的考察。如果整个数组被遍历完而没有触发计数超过`k`的情况,最后一个窗口可能不会在循环中被正确地更新。因此,循环结束后需要再次检查并更新`ans`以确保最后一个窗口也被考虑进去。这不是漏掉了某些情况,而是确保所有情况都被正确处理。
🦆
哈希表`cnt`在记录元素频率时,是否有可能因为重复元素的频繁增减导致性能问题?
虽然哈希表`cnt`会频繁地增加和减少元素的计数,但哈希表的平均时间复杂度为O(1),因此从理论上讲,增减操作的性能是非常高效的。然而,在极端情况下(例如非常大的数组或极端的数据分布),频繁的操作可能导致性能下降。但在大多数实际应用中,使用哈希表来记录元素频率是有效且性能良好的方法。

相关问题