【力扣roadmap】1906. 查询差绝对值的最小值

19 阅读3分钟

题目描述

image.png

思路

这里介绍两种写法,一种是过不去的,也是我自己想的,一种是可以过的,官解给的。

首先是过不去的dp做法,会超时。 思路比较简单,区间[l,r]的答案可以从两个状态转移过来,第一个是[l+1,r],通过添加元素nums[l]转移得到;第二个是[l,r-1],通过添加元素nums[r]转移得到。 于是你有下面的状态转移方程

dp[l][r]=min(min(dp[l+1][r],nums[l]在区间[l+1,r]离其最近且与nums[l]不等的绝对差),min(dp[l][r1],nums[r]在区间[l,r1]离其最近且与nums[l]不等的绝对差))dp[l][r] = min(\\ min(dp[l+1][r] , nums[l]在区间[l+1,r]离其最近且与nums[l]不等的绝对差),\\ min(dp[l][r-1] , nums[r]在区间[l,r-1]离其最近且与nums[l]不等的绝对差)\\ )

难点在怎么判断nums[l]在区间[l+1,r]离其最近且与nums[l]不等的绝对差呢?

我是这么做的:将区间[l+1,r]的元素取出来进行排序,然后使用nums[l]在有序区间进行二分查找,找到最后一个比nums[l]小的元素和第一个比nums[l]大的元素。然后取最小差值。

但这种做法超时,那么官解怎么写的?官解给了个貌似1e7的跑法(查询1e5 * 数值区间100)。 记录了每个前缀情况下,此时出现的所有元素情况,使用一个长度为101的数组记录。 pre[i][c]表示前i个元素,c出现的个数。 那么,pre[i-1][c] != pre[j][c] ,就说明区间[i,j]中有c出现。 那么,我们让c1到100依次遍历,如果c1出现且连续地c2也出现(c2c1c2 \ge c1), 那么c2-c1可以尝试刷新一次答案。

代码

dp

class Solution:
    def minDifference(self, nums: List[int], queries: List[List[int]]) -> List[int]:
        
        # dp[l][r] = min(  
        #    min(dp[l][r-1] , r在区间[l,r-1]进行二分查找找到最近的元素),
        #    min(dp[l+1][r-1] , l在区间[l+1,r]进行二分查找找到最近的元素)
        # )

        def lower_bound(nums, target) :
            l , r = 0 , len(nums)
            while l < r :
                mid = l + (r-l) // 2
                if nums[mid] < target :
                    l = mid + 1 
                else :
                    r = mid 
            return l

        @cache
        def dfs(l : int , r : int ) -> int :
            
            if r <= l :
                return inf # 非法区间
            
            if r == l + 1 :
                return abs(nums[r]-nums[l]) if nums[l] != nums[r] else inf 
            
            case1 = inf 
            # 尝试找到nums[r]在区间[l,r-1]的最近的元素
            interval = sorted(nums[l:r])
            # nums[r]在里面进行二分查找 找到最近的而且不等于nums[r]的元素
            target_idx1 = lower_bound(interval , nums[r]) - 1 # 最后一个小于nums[r]的元素
            target_idx2 = lower_bound(interval , nums[r] + 1) # 第一个大于nums[r]的元素
            if len(interval) > target_idx1 >= 0 :
                case1 = min(case1 , abs(nums[r] - interval[target_idx1]))
            if len(interval) > target_idx2 >= 0 :
                case1 = min(case1 , abs(nums[r] - interval[target_idx2]))
            case1 = min(dfs(l,r-1) , case1 )

            case2 = inf 
            # 尝试找到nums[l]在区间[l+1,r]的最近的元素
            interval = sorted(nums[l+1:r+1])
            # nums[l]在里面进行二分查找 找到最近的而且不等于nums[r]的元素
            target_idx1 = lower_bound(interval , nums[l]) - 1 # 最后一个小于nums[l]的元素
            target_idx2 = lower_bound(interval , nums[l] + 1) # 第一个大于nums[l]的元素
            if len(interval) > target_idx1 >= 0:
                case2 = min(case1 , abs(nums[l] - interval[target_idx1]))
            if len(interval) > target_idx2 >= 0:
                case2 = min(case1 , abs(nums[l] - interval[target_idx2]))
            case2 = min(dfs(l,r-1) , case2 )


            return min(case1, case2)

        ans = []
        for ql , qr in queries :
            cur = dfs(ql,qr)
            if cur == 0 or cur is inf : cur = -1 
            ans.append(cur) 
        return ans 

前缀和

class Solution:
    def minDifference(self, nums: List[int], queries: List[List[int]]) -> List[int]:
        
        C = 100 

        pre = [[0] * 101] 

        for x in nums :
            cur = pre[-1][:]  
            # 注意不能写成cur=pre[-1],因为这种写法没有数组的复制会导致所有修改只改动了pre[0]
            cur[x] += 1 
            pre.append(cur)
        ans = []
        for l , r in queries:
            l += 1 # 修正下标偏移
            r += 1 # 修正下标偏移
            cur_ans = inf 
            last = 0 
            for i in range(1,101) :
                if pre[l-1][i] != pre[r][i] : # 检查区间[l,r]是否有c的出现
                    if last != 0 :
                        cur_ans = min(cur_ans , i - last)
                    last = i 
            if cur_ans is inf :
                cur_ans = -1 
            ans.append(cur_ans)
        return ans