题目 #
You are given an array nums of size n consisting of distinct integers from 1 to n and a positive integer k.
Return the number of non-empty subarrays in nums that have a median equal to k.
Note:
- The median of an array is the middle element after sorting the array in ascending order. If the array is of even length, the median is the left middle element.
- For example, the median of [2,3,1,4] is 2, and the median of [8,4,3,5,1] is 4.
- A subarray is a contiguous part of an array.
Example 1:
Input: nums = [3,2,1,4,5], k = 4
Output: 3
Explanation: The subarrays that have a median equal to 4 are: [4], [4,5] and [1,4,5].
Example 2:
Input: nums = [2,3,1], k = 3
Output: 1
Explanation: [3] is the only subarray that has a median equal to 3.
Constraints:
- n == nums.length
- $1 <= n <= 10^5$
- 1 <= nums[i], k <= n
- The integers in nums are distinct.
思路1 统计一边大于或小于等于,然后计算另一边 #
分析 #
- 此题目中的中位数可以推出来
- 奇数长度下
$$左侧小于+右侧小于 = 左侧大于+右侧大于$$ $$左侧小于-左侧大于 = 右侧大于-右侧小于$$
- 偶数长度下
$$左侧小于-左侧大于+1 = 右侧大于-右侧小于$$
- 从中位数开始,向左就是大于为-1,小于为1,累加之后如果左侧和右侧相等就找到一个子数组
- 那么步骤就是先统计左边,然后去右边找有没有匹配的
- 边界情况,中位数既要算到左边也要算到右边,可以认为是以中位数为边界,向右找要算上,中位数本身也要统计向左边有几个0或-1
代码 #
1func countSubarrays1(nums []int, k int) int {
2 // 1. 先找中位数的位置
3 v := 0
4 for i := range nums {
5 if nums[i] == k {
6 v = i
7 break
8 }
9 }
10
11 // 2. 向左开始进行累加,使用map进行存储
12 recordMap := make(map[int]int)
13 recordMap[0] = 1 // 中位数的位置本身就是0
14 tmp := 0
15 for i := v - 1; i >= 0; i-- {
16 if nums[i] > k {
17 tmp--
18 } else {
19 tmp++
20 }
21 if _, ok := recordMap[tmp]; !ok {
22 recordMap[tmp] = 0
23 }
24 recordMap[tmp]++
25 }
26
27 // 向右开始查找
28 result := 0
29 tmp = 0
30 for i := v; i < len(nums); i++ {
31 if nums[i] > k {
32 tmp++
33 } else if nums[i] < k {
34 tmp--
35 }
36 // 左侧小于-左侧大于 = 右侧大于-右侧小于
37 if count, ok := recordMap[tmp]; ok {
38 result += count
39 }
40 // 左侧小于-左侧大于+1 = 右侧大于-右侧小于
41 if count, ok := recordMap[tmp-1]; ok {
42 result += count
43 }
44 }
45 return result
46}
思路2 简化成一次遍历 #
分析 #
- 还是上面的思路,最左侧其实等于 $\sum 左侧小于 - \sum 左侧大于$
- 整体统计数据加上 $\sum 左侧大于 - \sum 左侧小于$,那么最左边的就是0,第二个就是大于加一,小于减一,和右边计算是一致的
- 只需要考虑边界情况,将中位数计算到两边即可,那么一次遍历就解决了问题
- 第一位是按照0开始计算的,所以每个i都是在算i+1的结果,所以左边的遍历只需要到k所在位置前一个就好
代码 #
1func countSubarrays(nums []int, k int) int {
2 result := 0
3 v := len(nums)
4 tmp := 0
5 recordMap := make(map[int]int)
6 recordMap[0] = 1 // 第一位是0
7 for i := range nums {
8 if nums[i] > k {
9 tmp++
10 } else if nums[i] < k {
11 tmp--
12 } else {
13 v = i
14 }
15 // 每个i计算的都是下一位的值,所以不需要到中位数所在的位置
16 if i < v {
17 if _, ok := recordMap[tmp]; !ok {
18 recordMap[tmp] = 0
19 }
20 recordMap[tmp]++
21 } else {
22 if count, ok := recordMap[tmp]; ok {
23 result += count
24 }
25 if count, ok := recordMap[tmp-1]; ok {
26 result += count
27 }
28 }
29 }
30 return result
31}