747. 至少是其他数字两倍的最大数
问题描述
给你一个整数数组 nums
,其中总是存在一个最大元素。判断数组中最大元素是否至少是数组中每个其他元素的两倍。如果是,则返回最大元素的下标;否则,返回 -1。
示例:
输入: nums = [3,6,1,0]
输出: 1
解释: 6 是最大元素,6 >= 2*3, 6 >= 2*1, 6 >= 2*0,所以返回下标 1
输入: nums = [1,2,3,4]
输出: -1
解释: 4 是最大元素,但 4 < 2*3,所以返回 -1
输入: nums = [1]
输出: 0
解释: 只有一个元素,返回下标 0
算法思路
一次遍历法:
- 遍历数组找到最大值及其下标
- 再次遍历检查最大值是否至少是每个其他元素的两倍
- 优化:只需要检查第二大的元素,因为如果最大值 ≥ 2×第二大,则必然 ≥ 2×其他所有元素
核心思想:要验证最大值是否至少是所有其他元素的两倍,只需验证它是否至少是第二大元素的两倍。
代码实现
方法一:一次遍历(推荐解法)
class Solution {
/**
* 判断最大元素是否至少是其他元素的两倍
*
* @param nums 整数数组
* @return 如果满足条件返回最大元素下标,否则返回-1
*/
public int dominantIndex(int[] nums) {
// 输入校验
if (nums == null || nums.length == 0) {
return -1;
}
int n = nums.length;
// 特殊情况:只有一个元素
if (n == 1) {
return 0;
}
// 步骤1:找到最大值和第二大值
int max = Integer.MIN_VALUE;
int secondMax = Integer.MIN_VALUE;
int maxIndex = -1;
for (int i = 0; i < n; i++) {
if (nums[i] > max) {
// 发现新的最大值
secondMax = max; // 原来的最大值变成第二大
max = nums[i];
maxIndex = i;
} else if (nums[i] > secondMax) {
// 发现新的第二大值(但不是最大值)
secondMax = nums[i];
}
}
// 步骤2:检查最大值是否至少是第二大值的两倍
// 如果满足,则必然满足对所有其他元素的条件
if (max >= 2 * secondMax) {
return maxIndex;
} else {
return -1;
}
}
}
方法二:优化(一次遍历)
class Solution {
/**
* 优化:在一次遍历中完成
*
* @param nums 输入数组
* @return 结果下标
*/
public int dominantIndex(int[] nums) {
if (nums == null || nums.length == 0) {
return -1;
}
int n = nums.length;
if (n == 1) {
return 0;
}
int maxIndex = 0; // 最大值下标
int secondMax = Integer.MIN_VALUE; // 第二大值
// 遍历数组
for (int i = 1; i < n; i++) {
if (nums[i] > nums[maxIndex]) {
// 更新最大值和第二大值
secondMax = nums[maxIndex];
maxIndex = i;
} else if (nums[i] > secondMax) {
// 更新第二大值
secondMax = nums[i];
}
}
// 检查条件
return nums[maxIndex] >= 2 * secondMax ? maxIndex : -1;
}
}
方法三:暴力解法
class Solution {
/**
* 暴力解法:先找最大值,再检查每个元素
* 时间复杂度O(n),但常数因子较大
*
* @param nums 输入数组
* @return 结果下标
*/
public int dominantIndex(int[] nums) {
if (nums == null || nums.length == 0) {
return -1;
}
int n = nums.length;
if (n == 1) {
return 0;
}
// 找到最大值和其下标
int max = nums[0];
int maxIndex = 0;
for (int i = 1; i < n; i++) {
if (nums[i] > max) {
max = nums[i];
maxIndex = i;
}
}
// 检查最大值是否至少是每个其他元素的两倍
for (int i = 0; i < n; i++) {
if (i != maxIndex && max < 2 * nums[i]) {
return -1;
}
}
return maxIndex;
}
}
算法分析
-
时间复杂度:O(n)
- 方法一和二:遍历数组一次
- 方法三:遍历数组两次
- 都是线性时间
-
空间复杂度:O(1)
- 只使用常数额外空间
-
方法对比:
- 方法一:逻辑清晰,易于理解
- 方法二:代码更简洁
- 方法三:直观但效率稍低
算法过程
nums = [3,6,1,0]
:
-
遍历过程:
i=0
:max=3, maxIndex=0, secondMax=MIN_VALUE
i=1
:6>3
→secondMax=3, max=6, maxIndex=1
i=2
:1<6
但1>3
? 否 →secondMax
保持3i=3
:0<6
且0<3
→ 无变化
-
检查条件:
max=6, secondMax=3
6 >= 2*3
→6 >= 6
-
返回:
maxIndex=1
测试用例
public static void main(String[] args) {
Solution solution = new Solution();
// 测试用例1:满足条件
int[] nums1 = {3,6,1,0};
System.out.println("Test 1: " + solution.dominantIndex(nums1)); // 1
// 测试用例2:不满足条件
int[] nums2 = {1,2,3,4};
System.out.println("Test 2: " + solution.dominantIndex(nums2)); // -1
// 测试用例3:单元素
int[] nums3 = {1};
System.out.println("Test 3: " + solution.dominantIndex(nums3)); // 0
// 测试用例4:两个元素满足
int[] nums4 = {1,2};
System.out.println("Test 4: " + solution.dominantIndex(nums4)); // 1
// 测试用例5:两个元素不满足
int[] nums5 = {3,5};
System.out.println("Test 5: " + solution.dominantIndex(nums5)); // -1
// 测试用例6:空数组
int[] nums6 = {};
System.out.println("Test 6: " + solution.dominantIndex(nums6)); // -1
// 测试用例7:包含零
int[] nums7 = {0,0,0,1};
System.out.println("Test 7: " + solution.dominantIndex(nums7)); // 3
// 测试用例8:相等元素
int[] nums8 = {1,1,1,1};
System.out.println("Test 8: " + solution.dominantIndex(nums8)); // -1
}
关键点
-
关键:
- 只需检查第二大元素
- 如果最大值 ≥ 2×第二大,则必然 ≥ 2×所有其他元素
-
边界处理:
- 空数组
- 单元素数组
- 包含零的情况
-
整数溢出:
2 * secondMax
可能溢出- 可以改用
max >= secondMax * 2
或比较max / 2 >= secondMax
-
算法效率:
- 一次遍历即可完成
常见问题
-
为什么只需检查第二大元素?
- 因为第二大元素是除最大值外最大的
- 如果最大值 ≥ 2×第二大,则必然 ≥ 2×所有更小的元素
-
如何处理负数?
- 算法同样适用
- 注意负数的两倍关系
-
能否用排序?
- 可以,但时间复杂度更高O(n log n)
-
最大可能的值?
- LeetCode测试用例在int范围内
-
如何返回所有满足条件的?
- 题目保证只有一个最大元素