1438.绝对差不超过限制的最长连续子数组

91 阅读3分钟

题目:
给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit

如果不存在满足条件的子数组,则返回 0 。

算法:

方法一:双指针 + 单调队列

划定一个窗口[left, right],如何判断这个窗口内所有数绝对值之差小于limit,比较窗口内最大值和最小值是否小于等于limit即可。
问题在于left(right)右移时,我们如何更新和查询新区间的最大值和最小值?用什么数据结构呢?

答案是单调队列。

1.用两个单调队列minQ(从左到右数据非严格递增,minQ[0]保存区间最小值),maxQ(从左到右数据非严格递减,maxQ[0]保存区间最大值)。

比如nums=[1,2,5,3],maxQ依次为:[1],[2],[5],[5,3],我们不禁要问2,5入队列的时候,把1,2从maxQ挤出去是否合理?答案是合理,因为left只会往右移动,对于区间[1,2,5]的最大值来说,因为随着left的右移,1,2会先移出区间,而他们都小于5,他们移出去不影响区间最大值仍然为5,但是3是在5后面出现的,5移除区间了,3就是最大值。
所以每个数字进入maxQ的时候,把比他小的数字都可以从maxQ移出。

2.right右移时,将minQ中大于nums[right]的数全部出队列,将maxQ中小于nums[right]的数全部出队列,将nums[right]入minQ和maxQ,这样minQ,maxQ始终保存了区间nums[left, right]的最大值的递减序列和最小值的递增序列

3.left左移一个位置时,判断nums[left]是不是minQ的最小值和maxQ的最大值,如果是,出相应队列。minQ,maxQ始终保存了区间nums[left, right]的最大值的递减序列和最小值的递增序列

4.如果minQ或者maxQ长度为0了,意味着什么?最大值不存了 只能想到nums元素都相等的情况

func longestSubarray(nums []int, limit int) int {
	minQ := make([]int, 0)
	maxQ := make([]int, 0)
	ans := 0
	for left, right := 0, 0; right < len(nums); right ++ {
		for len(minQ) > 0 && minQ[len(minQ) - 1] > nums[right] {
			minQ = minQ[:len(minQ) - 1]
		}
		for len(maxQ) > 0 && maxQ[len(maxQ) - 1] < nums[right] {
			maxQ = maxQ[:len(maxQ) - 1]
		}
		minQ = append(minQ, nums[right])
		maxQ = append(maxQ, nums[right])

		for len(minQ) > 0 && len(maxQ) > 0 && maxQ[0] - minQ[0] > limit {
			if nums[left] == minQ[0] {
				minQ = minQ[1:]
			}
			if nums[left] == maxQ[0] {
				maxQ = maxQ[1:]
			}
			left ++
		}
		if right - left + 1 > ans {
			ans = right - left + 1
		}
	}
	return ans
}

方法二:双指针
注意到长度小于等于ans字符数符合条件,则长度大于等于ans的子数组必然不符合条件,存在二段性。因此我们猜测是否存在长度为mid的子数组,子数组中最大值和最小值的差小于等于limit,并根据猜测结果,调整mid大小。

func longestSubarray(nums []int, limit int) int {
    n := len(nums)
    // 注意left,right是猜测的longest subarray,取值范围是[0,n]
    left, right := 0, n
    for left < right {
        mid := (left + right + 1) >> 1
        if check(nums, limit, mid) {
            left = mid
        } else {
            right = mid - 1
        }
    }
    return right
}

func check(nums []int, limit, length int) bool {
    minQ := make([]int, 0)
	maxQ := make([]int, 0)
    
    for left, right := 0, 0; right < len(nums); right ++ {
		for len(minQ) > 0 && minQ[len(minQ) - 1] > nums[right] {
			minQ = minQ[:len(minQ) - 1]
		}
		for len(maxQ) > 0 && maxQ[len(maxQ) - 1] < nums[right] {
			maxQ = maxQ[:len(maxQ) - 1]
		}
		minQ = append(minQ, nums[right])
		maxQ = append(maxQ, nums[right])
		if right - left + 1 == length {
			if len(minQ) > 0 && len(maxQ) > 0 && maxQ[0] - minQ[0] <= limit { 
                return true
            }
            if len(minQ) > 0 && nums[left] == minQ[0] {
				minQ = minQ[1:]
			}
			if len(maxQ) > 0 && nums[left] == maxQ[0] {
				maxQ = maxQ[1:]
			}
			left ++
		}
	}
    return false
}