题目
魔术索引。 在数组A[0…n-1]中,有所谓的魔术索引,满足条件A[i] = i。给定一个有序整数数组,编写一种方法找出魔术索引,若有的话,在数组A中找出一个魔术索引,如果没有,则返回-1。若有多个魔术索引,返回索引值最小的一个。
示例1:
输入:nums = [0, 2, 3, 4, 5]
输出:0
说明: 0下标的元素为0
示例2:输入:nums = [1, 1, 1]
输出:1
提示:nums长度在[1, 1000000]之间
方法:暴力剪枝
算法流程
- 不做详细介绍,简单的代码一看就懂
public int findMagicIndex(int[] nums) {
int res = 0;
while(res < nums.length) {
if(res == nums[res]) {
return res;
}else if(res < nums[res]) {
res = nums[res];
}else {
res++;
}
}
return -1;
}
python
class Solution:
def findMagicIndex(self, nums: list[int]) -> int:
res = 0
while res < len(nums):
if res == nums[res]:
return res
elif res < nums[res]:
res = nums[res]
else:
res += 1
return -1
解法二:分治剪枝
思路与算法
本方法会进行一定程度的剪枝,在一些情况下会达到较优的时间复杂度,在最差情况下仍会退化成线性的时间复杂度,这里我们分两种情况讨论。
第一种情况是数组中只有一个满足条件的答案。我们假设这个答案为i,那
么意味着[0…- 1]的值均小于自身的下标,[i+ 1…n- 1]的值均大于自身的下标。我们将整个数组每个元素减去其自身所在的下标,那么最后的答案即为0所在的下标,且脏0之前的元素均为负数,0之后的元素均为正数。以[-1,0,2,4,5] 为例,减去自身下标以后以后得到[-1,-1,0,1,1], 整个数组是具有单调性的,直接二分查找即能在O(log n)的时间内找到答案0所在的下标。
第二种情况是数组中存在多个满足条件的答案,此时我们发现整个数组不具有任何性质。以[0, 0, 2, 2, 5]为例,我们仍进行次将每个元素减去其自身下标的操作,得到[0,-1,0,-1,1]。目标是要找到第一个出现的 0,而由于数组没有单调性,这里无法使用二分查找,但是我们可以依据此来进行一定程度的剪枝,我们剪枝的策略为:
- 每次我们选择数组的中间元素,如果当前中间元素是满足条件的答案,那么这个位置往后的元素我们都不再考虑,只要寻找左半部分是否有满
条件的答案即呵。 - 否则我们需要查看左半部分是否有满足条件的答案,如果没有的话我们仍然需要在右半边寻找,使用的策略同上。
我们可以依靠此策略定义-个递归函数: getAnswer(nums, 1eft, right)
返回数组nums
的下标范围[left, right]
中第一个满足条件的答案, 如果没有返回-1。每次选择中间的位置mid,此时直接先递归调用数组左半部分getAnswer (nums,left, mid 一1)
得到返回值leftAnswer,
如果存在则直接返回,如果不存在则此较nums[mid]
和mid否相等,如果相等则返回mid,否则需要递归调用getAnswer (nums, mid + 1,right)
显然,此剪枝策略在[- 1, 0, 1,2, 4]这种答案为数组的最后一个元素的情况下会退化成线性的时间复杂度,但是在一些情况下会有不错的表现。
public int findMagicIndex(int[] nums) {
return getAnswer(nums, 0, nums.length - 1);
}
public int getAnswer(int[] nums, int left, int right) {
if (left > right) {
return -1;
}
int mid = (right - left) / 2 + left;
int leftAnswer = getAnswer(nums, left, mid - 1);
if (leftAnswer != -1) {
return leftAnswer;
} else if (nums[mid] == mid) {
return mid;
}
return getAnswer(nums, mid + 1, right);
}
class Solution1:
def findMagicIndex(self, nums: list[int]) -> int:
def getAnser(nums: list[int], left: int, right: int) -> int:
if left > right:
return -1
mid = left + (right - left) // 2;
leftAnswer = getAnser(nums, left, mid - 1)
if leftAnswer != -1:
return leftAnswer
elif nums[mid] == mid:
return mid
return getAnser(nums, mid + 1, right)
return getAnser(nums, 0, len(nums) - 1)
解法三:二分剪枝
思路与算法
对于[0, 1,4, 4, 4]这个数组,使用二分法拿出位于中间的idx
为2的数字
4,然后我们发现nums[idx] > idx
此刻我们除了可以确认idx2
不是魔术索引|外,还可以确定idx3
也肯定不是魔术索引。因为假如idx3
是魔术索的话那idx3
的值就必须是3, 0这将导致nums[idx3] < nums[dx2],
和题目的递增数组”矛盾。
所以,此刻魔术索引只可能出现在[0, mid - 1
]和[nums[mid], nums.length -1]
这两个范围里。
public static int findMagicIndex(int[] nums) {
return helper(nums, 0, nums.length - 1);
}
private static int helper(int[] nums, int i, int j) {
// TODO Auto-generated method stub
if (i > j) {
return -1;
}
int mid = (i + j) >>> 1;
if (nums[mid] < mid) {
int ans1 = helper(nums, i, nums[i]);
int ans2 = helper(nums, mid + 1, j);
return ans1 != -1 ? ans1 : ans2;
} else if (nums[mid] > mid) {
int ans1 = helper(nums, i, mid - 1);
int ans2 = helper(nums, nums[mid], j);
return ans1 != -1 ? ans1 : ans2;
} else {
int ans = helper(nums, 0, mid - 1);
return ans != -1 ? ans : mid;
}
}