leetcode1521. 找到最接近目标值的函数值
![3b41e1e5e42d5e314f599c80de83223c.png](https://i-blog.csdnimg.cn/blog_migrate/c57054d3ec3fd677d5c82a2ff4b5131d.png)
Winston 构造了一个如上所示的函数 func
。他有一个整数数组 arr
和一个整数 target
,他想找到让 |func(arr, l, r) - target|
最小的 l
和 r
。
请你返回 |func(arr, l, r) - target|
的最小值。
请注意, func
的输入参数 l
和 r
需要满足 0 <= l, r < arr.length
。
示例 1:
输入:arr = [9,12,3,7,15], target = 5
输出:2
解释:所有可能的 [l,r] 数对包括 [[0,0],[1,1],[2,2],[3,3],[4,4],[0,1],[1,2],[2,3],[3,4],[0,2],[1,3],[2,4],[0,3],[1,4],[0,4]], Winston 得到的相应结果为 [9,12,3,7,15,8,0,3,7,0,0,3,0,0,0] 。最接近 5 的值是 7 和 3,所以最小差值为 2 。
示例 2:
输入:arr = [1000000,1000000,1000000], target = 1
输出:999999
解释:Winston 输入函数的所有可能 [l,r] 数对得到的函数值都为 1000000 ,所以最小差值为 999999 。
示例 3:
输入:arr = [1,2,4,8,16], target = 0
输出:0
提示:
1 <= arr.length <= 10^5
1 <= arr[i] <= 10^6
0 <= target <= 10^7
方法:位运算
思路:
首先,我们看func函数的操作,func(arr,l,r),返回的是arr[l]到arr[r]的所有数按位求与。即arr[l]&arr[l+1]&...&arr[r]
。
设arr长度为n,那么我们暴力的方法,就是建立一个n * n的数组dp,dp[i][j]
表示从arr[i]到arr[j]按位求与的值。然后找到满足条件的最小值即可,但是这样方法,时间复杂度接近O(n^2)
,无法通过。
那么我们需要根据位运算的知识来进行优化,对于按位求与,我们可以得到下面两个性质。
a&b&c = a&(b&c) =(a&b)&c
,存在结合律。那么我们设求与的最右边的坐标为r,假设以r为右边界的求和值分别为a1、a2、a3、...、an,这些值分别对应着最左边的坐标l=0、1、2...,那么,以r+1为最右坐标的求和值则为a1&arr[r+1]、a2&arr[r+1]、a3&arr[r+1]、...、arr[r+1]。因此,我们对r进行遍历,从0开始,使用一个数组来保存以r为右边界的求与值,那么求r+1为右边界的时候,只需对所有值再对arr[r+1]求与即可,再加上一个arr[r+1],即只一个数的情况。这样,遍历完r,我们也就遍历到了所有值。- 上面的方法遍历了所有值,复杂度是没有改变的,但是,以r为右边界的值中,是有重复的,我们使用集合替代数组,计算r+1时,遍历的数为集合中的元素,消去了重复元素,可以大大降低复杂度。因为以r为右边界的求与值,最多只有20个值。这是因为,假设arr[r]的二进制中有n个1,经过与操作后,0肯定不会变,只有1会变,而1也只会变成0;无论arr[r]前面是多少个值求与,都可以看作arr[r]与前面所有数的与的结果,这一个数求与,且可以看做arr[r]从与arr[r-1]求与开始,一步一步向前,如果1变成0则不可逆。因此最多只有n种以arr[r]为右边界求与值结果。根据给定的数据范围,n最大为2
因此,我们就可以使用一个集合来保存目前以r为右边界的所有求与和,根据集合中的值更新res,然后遍历r,不断更新res,最后得到答案。
因为每个r最多有20个值。即logn,所以时间复杂度为O(nlogn)。
空间复杂度为O(logn)。
代码:
class Solution:
def closestToTarget(self, arr: List[int], target: int) -> int:
n = len(arr)
# 目前可能出现的按位求和的值
values = set()
# 添加第一个,arr[0],即以0为右边的值,初始化res
values.add(arr[0])
res = abs(arr[0]-target)
# 遍历剩余的值arr值,计算以arr[i]为right可以得到的按位求与值,更新答案
for i in range(1,n):
values = {value & arr[i] for value in values}
values.add(arr[i])
res = min(res,min(abs(value-target) for value in values))
# 返回答案
return res
结果:
![f051fa08a4e28fb582d130d22a7eacfe.png](https://i-blog.csdnimg.cn/blog_migrate/03ae2b8bafdbff0d9b768da5fd403c56.png)