题目描述

给定一个按非降序排列的非空整数数组 nums,返回数组中绝对值最小的元素,保留该元素原来的正负号。

若两个元素的绝对值相同,约定返回数值较大的那个。例如 -2 和 2 同时最接近零时,返回 2。

请利用数组的有序性,在 $O(\log n)$ 时间、$O(1)$ 额外空间内完成查找。

示例 1:

输入:nums = [-10,-5,-2,0,3,6,9]
输出:0

示例 2:

输入:nums = [-8,-3,-1,2,4,7]
输出:-1

示例 3:

输入:nums = [-5,-2,2,9]
输出:2
解释:-2 和 2 的绝对值相同,按约定返回较大的 2。

提示:

  • nums.length >= 1。
  • nums 已按非降序排列,允许出现重复元素。
  • 数组可能全为非负数、全为非正数,也可能同时包含负数和正数。
  • 元素可能取到所用整数类型的最小值,比较绝对值时需避免溢出。

题意分析

要找的是离 0 最近的元素,而不是数组中的最小元素,也不是返回绝对值本身。例如第二个示例应返回 -1,不能返回 1。

有序性让候选范围大幅缩小:负数中越靠右越接近零,正数中越靠左越接近零。因此没有零时,只需比较最后一个负数和第一个正数。

解法:二分定位零点两侧的候选

核心思路

[!blue]

若 nums[0] >= 0,所有元素均非负,首元素就是答案;若 nums[n - 1] <= 0,所有元素均非正,末元素就是答案。这两种情况也覆盖单元素数组。

剩下的情况一定同时存在负数和正数。用闭区间 [left, right] 二分查找 0:中点等于零时直接返回;中点为负数时向右查找,为正数时向左查找。

如果没有找到零,循环结束后 left = right + 1,其中 right 指向最后一个负数,left 指向第一个正数。前面的分类处理保证两者都在数组范围内,答案只可能是这两个候选之一。

Java 先将负数转为 long 再取负,避免 Integer.MIN_VALUE 溢出。Go 不对负数候选取负,而是比较 nums[right] > -nums[left]:右侧候选一定为正数,对它取负不会溢出。两种写法在绝对值相同时都会返回正数候选,符合题目约定。

解题步骤

  1. 检查首、末元素,处理全非负和全非正的数组。
  2. 在闭区间 [0, n - 1] 中二分查找 0;遇到零立即返回。
  3. 区间为空后,取 nums[right] 和 nums[left] 作为仅有的两个候选。
  4. 安全比较绝对值,返回较小者;绝对值相等时返回正数候选。

代码实现

class Solution {
    public int findMinAbs(int[] nums) {
        int n = nums.length;

        if (nums[0] >= 0) {
            return nums[0];
        }

        if (nums[n - 1] <= 0) {
            return nums[n - 1];
        }

        int left = 0;
        int right = n - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2;

            if (nums[mid] == 0) {
                return 0;
            } else if (nums[mid] < 0) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }

        return -(long) nums[right] < nums[left] ? nums[right] : nums[left];
    }
}
func findMinAbs(nums []int) int {
    n := len(nums)
    if nums[0] >= 0 {
        return nums[0]
    }
    if nums[n-1] <= 0 {
        return nums[n-1]
    }

    left, right := 0, n-1
    for left <= right {
        mid := left + (right-left)/2
        if nums[mid] == 0 {
            return 0
        } else if nums[mid] < 0 {
            left = mid + 1
        } else {
            right = mid - 1
        }
    }

    if nums[right] > -nums[left] {
        return nums[right]
    }
    return nums[left]
}

复杂度分析

设数组长度为 $n$。

  • 时间复杂度:$O(\log n)$。每轮二分排除约一半搜索区间;全非负或全非正时直接返回。
  • 空间复杂度:$O(1)$。仅使用边界和中点等常数个变量。

关键点总结

[!green]

  • 绝对值在整个有序数组上并不单调,但元素与零的大小关系是单调的,可以据此二分。
  • 最接近零的候选一定在正负分界点两侧;存在零时,零就是最优答案。
  • 比较绝对值时避免直接对最小整数取负,返回时保留原元素的符号。

易错点总结

[!yellow]

  • 不先处理全非负、全非正的情况,二分结束后直接访问两侧候选可能越界。
  • Java 中应先转换再取负:-(long) nums[right];先对 int 取负再转换仍会溢出。
  • Go 中直接对最小 int 取负同样可能溢出;当前实现只对正数候选取负。
  • 两个候选绝对值相同时,不能随意返回负数,要遵守返回较大元素的约定。

相似题目

题目 难度 关联与区别
2239. 找到最接近 0 的数字 简单 同样寻找距离零最近的原元素,绝对值相同时取较大值。原题数组不保证有序,通常用 $O(n)$ 遍历;本题数组已排序,可以用 $O(\log n)$ 二分查找。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/74827432
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!