• 数组Magic Index


    Question

    A magic index in an array A[1...n-1] is defined to be an index such that A[i] = i. Given a sorted array of distinct integers, write a method to find a magic index, if one exists, in array A. FOLLOW UP What if the values are not distinct?

    给定一个数组A,其中有一个位置被称为Magic Index,含义是:如果i是Magic Index,则A[i] = i。假设A中的元素递增有序、且不重复,请给出方法,找到这个Magic Index。更进一步,当A中允许有重复的元素,该怎么办呢?

    Solution for distinct value

    一些同学在遇到这个题目的时候,往往会觉得比较简单。扫描一遍,不就ok了么?O(n)的。很简单呀。可是,大家要注意到,还有一个条件没有用:A中的元素是有序递增的。这个条件,并不是放在这里迷惑大家的,而是有更大的作用的。这个时候,该如何想呢?O(n)不是最好的方法,更好的是什么呢?怎么利用数组有序呢?在有序的数组中查找一个满足特定元素的条件,我们通常会想到二分查找。 我们来回顾一下二分查找,对于要查找的目标t,我们首先与数组中间的元素比较,如果t大于中间的元素,则在右半部分继续查找;如果t小于中间的元素,则在左半部分,继续查找。

    那么,我们的题目能够利用上述的思想呢?我们来看一个具体的例子:

    0 1 2 3 4 5 6
    -10 -5 1 2 4 10 12

    mid=3,A[mid] = 2,即A[mid] < mid。接下来,我们应该在哪一边查找呢?我们知道数组的元素是递增有序,且不重复的,也就是说,在A[mid]左边的元素,比A[mid]都要小,没有重复,意味着什么呢?每向左移动一位,至少减1。所以,在mid左边,不可能有一个i,A[i]=i的。如果有,根据前面的分析,我们知道A[mid] - A[i] >= mid - i, 如果A[i] = i,则,A[mid] >= mid, 这与事实A[mid] < mid相悖。所以,接下来,只能在右边进行查找。代码与二分查找也很像。

    #include<iostream>
    using namespace std;
    
    int magicIndex(int a[],int n)
    {
        int low=0,high=n-1,mid;
        while(low<=high)
        {
            mid=(low+high)/2;
            if(a[mid]==mid)
            {
                return mid;
            }
            else if(a[mid]<mid)
            {
                low=mid+1;
            }
            else //a[mid]>mid
            {
                high=mid-1;
            }
        }
        return -1;
    }
    int main()
    {
        int a[]={-10,-5,1,2,4,10,12};
        int n=sizeof(a)/sizeof(a[0]);
        int result=magicIndex(a,n);
        cout<<result<<endl;
    }

    Solution for not distinct value

    如果数组A中,有重复元素,是什么情况呢?

    我们假设不要二分思想,从遍历的角度看:从头到尾走一边如果a[i]等于i的话,找到了;如果大于i的话,让i=a[i],不然i++继续找。这样最差的情况才是O(n)
    至于为什么可以让i=a[i],原因由于数列是递增的,所以数组元素在{i, a[i]}的区间中,肯定不可能存在magic index。这样看上去是不是跳跃着前进啊。:) 理解上面的话很重要,其实如果a[i]>i的话,由于相差a[i]-i个位置,所有[i,a[i]]中间的元素都不可能相等。

     1 int find_magic_index (int *list, int count) {
     2     int i=0;
     3     while (i<count) {
     4         if (list[i] == i)
     5             return i;
     6         else if (list[i] > i)
     7             i = list[i];
     8         else
     9             i++;
    10     }
    11     return -1;
    12 }
     

     经过前面没有重复元素的分析,我们知道,是否有重复的主要差别在,数组的元素从右到左进行递减,每次不一定至少是1了,有可能是0了。让我们直观的看一下影响吧。

    0 1 2 3 4 5 6
    -10 2 2 2 9 10 12

    看上面的数组,同样A[mid] < mid。我们应该继续查右边么?显然,右边并不存在Magic Index。查找右边,就会找不到这样的Magic Index。此时,应该如何处理呢?我们无法确定,Magic Index是在左边,还是在右边了。那就两边都递归进行处理吧。

    在这里还有一个小技巧,我们就是要分别递归处理[0, mid - 1]和[mid + 1, end](end是数组长度-1)么?我们看一个具体的例子:

    0 1 2 3 4 5 6 7 8
    -10 2 2 2 2 10 12 13 20

    这个例子,当我们进行左半部分递归处理的时候,需要考虑的范围是[0, 3]。可实际上,我们只需要考虑[0, 2]。原因是,数组元素在mid=4的左边的值都要小于或者等于A[mid]=2,所以最大的一个有可能是Magic Index的,就是index为A[mid]的情况。所以,这时右边的边界应该是min(mid - 1, A[mid])。

    那么,右边的情况呢?如下例子:

    0 1 2 3 4 5 6 7 8
    -10 2 2 2 9 10 12 13 20

    此时,要在右半部分进行查找,范围一般是[5, 8]。但是,由于数组有序,后面的值,一定是大于等于A[mid]=9的。所以,有可能是Magic Index的最小Index是9,也就是说右边的递归,应该是从索引为9的位置开始。此例,就意味着,无需处理右边了。

    #include<iostream>
    using namespace std;//有重复元素
    int findMagicIndex(int a[],int low,int high)
    {
         int mid=(low+high)/2;
         if(a[mid]==mid)
         {
             return mid;
         }
         else if(a[mid]<mid)
         {
             return findMagicIndex(a,low,a[mid]);
         }
         else //a[mid]>mid
         {
             return findMagicIndex(a,a[mid],high);
         }
    }
    
    int main()
    {
        
        int a[]={-10,2,2,2,2,10,12,13,20};
        int n=sizeof(a)/sizeof(a[0]);
        int result=findMagicIndex(a,0,n-1);
        cout<<result<<endl;
    }

    输出:2.

    参考:https://gist.github.com/sing1ee/5982361

  • 相关阅读:
    break (Statements) – JavaScript 中文开发手册
    :first-of-type (Selectors) – CSS 中文开发手册
    no-new-symbol (Rules) – Eslint 中文开发手册
    Null-terminated byte strings (Strings) – C 中文开发手册
    HTML DOM Time 对象
    Java 实例 – 向文件中追加数据
    HTML DOM Hidden type 属性
    在Java中扩展原始转换
    字体变位 | font-variant-position (Fonts) – CSS 中文开发手册
    逆波兰计算器完整版
  • 原文地址:https://www.cnblogs.com/youxin/p/3349799.html
Copyright © 2020-2023  润新知