leetcode 面试题08.03 魔术索引
二分剪枝
思路与算法
此问题如果用暴力的方法来解决,我们只需要对原数组从前往后进行一次遍历,找到第一个可行的位置返回即可,这里不再赘述。而本方法会进行一定程度的剪枝,在一些情况下会达到较优的时间复杂度,在最差情况下仍会退化成线性的时间复杂度,这里我们分两种情况讨论。
第一种情况是数组中只有一个满足条件的答案。我们假设这个答案为 ii,那么意味着 [0 \ldots i-1][0…i−1] 的值均小于自身的下标,[i+1 \ldots n-1][i+1…n−1] 的值均大于自身的下标。我们将整个数组每个元素减去其自身所在的下标,那么最后的答案即为 00 所在的下标,且在 00 之前的元素均为负数,00 之后的元素均为正数。以 [-1,0,2,4,5][−1,0,2,4,5] 为例,减去自身下标以后以后得到 [-1,-1,0,1,1][−1,−1,0,1,1],整个数组是具有单调性的,直接二分查找即能在 O(\log n)O(logn) 的时间内找到答案 00 所在的下标。
第二种情况是数组中存在多个满足条件的答案,此时我们发现整个数组不具有任何性质。以 [0,0,2,2,5][0,0,2,2,5] 为例,我们仍进行一次将每个元素减去其自身下标的操作,得到 [0,-1,0,-1,1][0,−1,0,−1,1]。目标是要找到第一个出现的 00,而由于数组没有单调性,这里无法使用二分查找,但是我们可以依据此来进行一定程度的剪枝,我们剪枝的策略为:
每次我们选择数组的中间元素,如果当前中间元素是满足条件的答案,那么这个位置往后的元素我们都不再考虑,只要寻找左半部分是否有满足条件的答案即可。
否则我们需要查看左半部分是否有满足条件的答案,如果没有的话我们仍然需要在右半边寻找,使用的策略同上。
我们可以依靠此策略定义一个递归函数:getAnswer(nums, left, right) 返回数组 \textit{nums}nums 的下标范围 [left,right] 中第一个满足条件的答案,如果没有返回 -1−1。每次选择中间的位置 mid,此时直接先递归调用数组左半部分 getAnswer(nums, left, mid - 1) 得到返回值 leftAnswer,如果存在则直接返回,如果不存在则比较 nums[mid] 和mid 是否相等,如果相等则返回 mid,否则需要递归调用 getAnswer(nums, mid + 1, right)。
显然,此剪枝策略在 [-1,0,1,2,4]这种答案为数组的最后一个元素的情况下会退化成线性的时间复杂度,但是在一些情况下会有不错的表现。
C语言实现:
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);
}
int findMagicIndex(int* nums, int numsSize) {
return getAnswer(nums, 0, numsSize - 1);
}
复杂度分析
时间复杂度:最坏情况下会达到 O(n)O(n) 的时间复杂度,其中 nn 为数组的长度。具体分析已经在上文中讲述,这里不再赘述。
空间复杂度:递归函数的空间取决于调用的栈深度,而最坏情况下我们会递归 nn 层,即栈深度为 O(n)O(n),因此空间复杂度最坏情况下为 O(n)O(n)。
以上为官方解法
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/magic-index-lcci/solution/mo-zhu-suo-yin-by-leetcode-solution/
来源:力扣(LeetCode)
下面是一种优化后的跳跃式的遍历
也是我自己用的方法
同样用c语言实现
int findMagicIndex(int* nums, int numsSize){
//此优化适用升序数组,降序更简单,只有一个符合,可直接二分
int i=0;
int max(int a,int b){
return a>b?a:b;
}
while(i<numsSize){
if(i==nums[i])return i;
else{
i=max(i+1,nums[i]);
}
}
return -1;
}
执行用时:
16 ms, 在所有 C 提交中击败了92.68%的用户
内存消耗:6.3 MB, 在所有 C 提交中击败了80.00%的用户