查看: 2928| 回复: 13
跳转到指定楼层
上一主题 下一主题
收起左侧

[数组] find magic number 这道题值得警惕

全局:

注册一亩三分地论坛,查看更多干货!

您需要 登录 才可以下载或查看附件。没有帐号?注册账号

x
题目:find magic number
(1) find a magic number that A[i] = i in a sorted array (monotonic increasing order), return the smallest magic number (index). return -1 if not found.
example: -2 -1 2 4
answer: 2 (a[2] == 2)

(2) (modified version of problem (1)) elements can be duplicate
example: [1, 1, 1]
answer: 1 (a[1] == 1)


(1) 标准做法是 binary search,O(logN),比 O(N) 要好。
比如:
int magicIndex(int a[], int n){   
    int low = 0, high = n - 1;
    while (low <= high) {
        int mid = (low + high) / 2;
        if (a[mid] == mid)
            return mid;
        else if (a[mid] < mid)
            low = mid + 1;
        else high = mid - 1;
    }
    return -1;
}

对于 (2),虽然很多流行做法也是 binary search,O(N)。
但是实际上,binary search 的做法是错的,因为 binary search 一旦找到 a[mid] == mid,就没法找到更小的一个解了

比如:
[0, 2, 2, 2, 3]
mid = (0 + 4)/2 = 2 直接就找到 a[mid] = mid,也就是 a[2] = 2,这只是一个可行解,但是正确的解是 a[0] = 0。
根据数组元素递增(但可重复)的特性,当 a[i] > i 的时候,左右两侧都可能存在解。这对于(1)条件下的单调递增情况下是不同的,
在单调递增情况下,如果 a[i] > i,那么就必须在 i 左侧找,因为 a[i + 1] > a[i] > i,i + 1 > i => 必然有: a[i+1] > i + 1,所以右侧不可能存在解。
如果不是单调递增的情况,那么 a[i + 1] >= a[i] > i,因此当 a[i] > i 的时候,可以假定 a[i] 不变,增大 i,因此存在 a[i+1] == i + 1,因此右侧可能存在解。而显然,左侧就更可能存在解了。
而又因为是要返回最小的 magic number,也就是 index,那么就应该在左侧。

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);
    }
}

上面的解答,在 case [0, 2, 2, 3] 的时候会返回 2,而正确的解是 0.

同理,下面的这个 binary search 也是错的
int findMagicIndex(int nums[], int n) {
    int low = 0, high = n;
    while (low < high) {
        int mid = low + (high - low) / 2;
        if (nums[mid] != mid) {
            if (nums[mid] >= 0) {
                high = mid;
            } else {
                low = mid + 1;
            }
        } else {
            low = mid + 1;
        }
    }

    return low - 1;
}

正确的解答应该是从 index = 0 开始搜索,如果当前 a[i] == i,那么理解返回当前的解,因为 index 是从小到大的,所以保证了返回的是 smallest index。
如果遇到 a[i]  > i,那么应该把 i 置为 a[i],在 index = a[i] 的位置继续判断是否 a[a[i]] == a[i]。
比如 [2, 3, 4, 5] a[0] = 2, i = 0, => a[0] > 0, i = 2; =>  a[2] = 4 => a[2] > 2 = > i = 4; => return -1;

否则,如果 a[i] < i,那么继续在右侧找。

注意:上面两种情况都是在右侧找,也就是 a[i] < i 和 a[i] > i 时,都在右侧找,不同的是,第一种情况是跳着找,第二种情况只前进一步。

代码:
int findMagicIndex(int a[], int n) {

    int i = 0;
    while (i < n) {
        if (a[i] == i) {
            return i;
        } else if (a[i] > i) {
            i = a[i];
        } else{
            i++;
        }
    }
    return -1;
}

写了这么多,希望好心人给加点米 😊





评分

参与人数 8大米 +21 收起 理由
a_small_potato + 3 很有用的信息!
14417335 + 10
ShikiH + 2 给你点个赞!
AresLuo + 1 赞一个
szyyn95 + 2 给你点个赞!

查看全部评分


上一篇:刷题刷得我不想刷了……
下一篇:研究生ds方向,刷题对于求职提高大吗?
推荐
queensberry 2020-7-13 03:30:22 | 只看该作者
全局:
试了下用binary search解第二问,复杂度在极端情况下从二分退化成了线性,但在随机化test case情况下复杂度还是好于O(N)(也可能是测试设计不够完善)
代码如下,欢迎补充test case

  1. import java.util.Arrays;
  2. import java.util.Random;

  3. /**
  4. * find magic number
  5. * (1) find a magic number that A[i] = i in a sorted array (monotonic increasing order), return the smallest magic number (index). return -1 if not found.
  6. * example: -2 -1 2 4
  7. * answer: 2 (a[2] == 2)
  8. *
  9. * (2) (modified version of problem (1)) elements can be duplicate
  10. * example: [1, 1, 1]
  11. * answer: 1 (a[1] == 1)
  12. */
  13. public class FindMagicNumber {
  14.     public static void main(String[] args) {
  15.         int[] test1 = new int[] {-2, -1, 3, 4};
  16.         findMagicNumber1(test1);
  17.         findMagicNumber2(test1);

  18.         int[] test2 = new int[] {};
  19.         findMagicNumber1(test2);
  20.         findMagicNumber2(test2);

  21.         int[] test3 = new int[] {0, 1, 2, 3, 4};
  22.         findMagicNumber1(test3);
  23.         findMagicNumber2(test3);

  24.         int[] test4 = new int[] {1, 1, 1};
  25.         findMagicNumber2(test4);

  26.         int[] test5 = new int[] {0, 1, 1, 2, 3};
  27.         findMagicNumber2(test5);

  28.         int[] test6 = new int[] {-1, -1, -1, 3, 4};
  29.         findMagicNumber2(test6);

  30.         int[] test7 = new int[] {-1, -1, -1, 100, 100};
  31.         findMagicNumber2(test7);

  32.         int[] test8 = new int[10000];
  33.         for (int i = 0; i < test8.length; i++) {
  34.             test8[i] = i + 1;
  35.         }
  36.         findMagicNumber2(test8);

  37.         int[] test9 = new int[10000];
  38.         for (int i = 0; i < test9.length; i++) {
  39.             test9[i] = i;
  40.         }
  41.         findMagicNumber1(test9);
  42.         findMagicNumber2(test9);

  43.         int[] test10 = new int[10000];
  44.         for (int i = 0; i < test10.length; i++) {
  45.             if (i > 4998) {
  46.                 test10[i] = i + 1;
  47.             } else {
  48.                 test10[i] = i;
  49.             }
  50.         }
  51.         findMagicNumber2(test10);

  52.         int[] test11 = new int[10000];
  53.         for (int i = 0; i < test11.length; i++) {
  54.             if (i < 4999) {
  55.                 test11[i] = i + 1;
  56.             } else {
  57.                 test11[i] = i;
  58.             }
  59.         }
  60.         findMagicNumber2(test11);

  61.         Random rand = new Random();
  62.         int[] test12 = new int[10000];
  63.         int sum = 0;
  64.         int found = 0;
  65.         int tn = 10000;
  66.         for (int k = 1; k <= tn; k++) {
  67.             for (int i = 0; i < test12.length; i++) {
  68.                 test12[i] = rand.nextInt(10000) - rand.nextInt(rand.nextInt(300) + 1);
  69.             }
  70.             Arrays.sort(test12);
  71.             found += findMagicNumber2(test12, false) != -1 ? 1 : 0;
  72.             sum += count2;
  73.         }
  74.         System.out.println("Average recursion count is: " + (sum / tn) + ", found " + found + " cases");
  75.     }

  76.     static int count1 = 0;
  77.     static int count2 = 0;

  78.     public static int findMagicNumber1(int[] nums) {
  79.         count1 = 0;
  80.         if (nums.length == 0) {
  81.             System.out.println("Find Magic Number 1");
  82.             System.out.println("Test case is: " + Arrays.toString(nums));
  83.             System.out.println("O(N) = " + nums.length + ", Recursion count = " + count1);
  84.             System.out.println("Result is: -1");
  85.             System.out.println();
  86.             return -1;
  87.         }

  88.         int l = 0;
  89.         int r = nums.length - 1;

  90.         while (l < r) {
  91.             count1++;
  92.             int m = (l + r) >> 1;
  93.             if (nums[m] == m) {
  94.                 r = m;
  95.             } else if (nums[m] < m) {
  96.                 l = m + 1;
  97.             } else {
  98.                 r = m - 1;
  99.             }
  100.         }
  101.         System.out.println("Find Magic Number 1");
  102.         System.out.println("Test case is: " + Arrays.toString(nums));
  103.         System.out.println("O(N) = " + nums.length + ", Recursion count = " + count1);
  104.         System.out.println("Result is: " + (nums[l] == l ? l : -1));
  105.         System.out.println();

  106.         return nums[l] == l ? l : -1;
  107.     }

  108.     public static int findMagicNumber2(int[] nums, boolean print) {
  109.         count2 = 0;
  110.         int res = findMagicNumber2Impl(nums, 0, nums.length - 1);
  111.         if (print) {
  112.             System.out.println("Find Magic Number 2");
  113.             System.out.println("Test case is: " + Arrays.toString(nums));
  114.             System.out.println("O(N) = " + nums.length + ", Recursion count = " + count2);
  115.             System.out.println("Result is: " + (res < nums.length ? res : -1));
  116.             System.out.println();
  117.         }
  118.         return res < nums.length ? res : -1;
  119.     }

  120.     public static int findMagicNumber2(int[] nums) {
  121.         return findMagicNumber2(nums, true);
  122.     }

  123.     private static int findMagicNumber2Impl(int[] nums, int l, int r) {
  124.         count2++;
  125.         if (nums.length == 0) {
  126.             return Integer.MAX_VALUE;
  127.         }
  128.         if (l >= r) {
  129.             return l < nums.length ? (nums[l] == l ? l : Integer.MAX_VALUE) : Integer.MAX_VALUE;
  130.         } else {
  131.             int m = (l + r) >> 1;
  132.             if (nums[m] == m) {
  133.                 return findMagicNumber2Impl(nums, l, m);
  134.             } else if (nums[m] > m) {
  135.                 int r1 = findMagicNumber2Impl(nums, l, m - 1);
  136.                 if (r1 < Integer.MAX_VALUE) {
  137.                     return r1;
  138.                 } else {
  139.                     return findMagicNumber2Impl(nums, nums[m], r);
  140.                 }
  141.             } else {
  142.                 int r1 = findMagicNumber2Impl(nums, l, nums[m]);
  143.                 if (r1 < Integer.MAX_VALUE) {
  144.                     return r1;
  145.                 } else {
  146.                     return findMagicNumber2Impl(nums, m + 1, r);
  147.                 }
  148.             }
  149.         }
  150.     }
  151. }
复制代码



回复

使用道具 举报

推荐
 楼主| juniway 2020-7-13 09:47:57 | 只看该作者
全局:
经网友提醒,(1)中给出的原解答是错的,因为应对 case [0, 1, 2] 会得出错解答。

正确的应该如下:
int findMagicIndex(int a[], int n) {
    int low = 0, high = n - 1;
    while(low <= high) {
        int mid = (low + high) / 2;
        if (a[mid] >= mid) // 找到一个解之后,继续往左找。
            high = mid - 1;
        else low = mid + 1;
    }

    return low;
}


原理:一旦找到一个解,那么要么左边邻居也是解,要么左边不可能存在解。因为 index i 是连续的,而如果 a[i] 不连续的话,那么一旦错位,就不可能对齐了,所以一个可行解的邻居不是解的话,那么其它地方就不可能有解了。
回复

使用道具 举报

推荐
jliu 2020-7-13 03:00:15 | 只看该作者
全局:
这道题可以用二分法的前提是能保证在最后一个A=i 之后,都有A > i, 在第一个A=i 之前都有A < i. 这样就可以用lower bound的二分法变种

  1. arr = [ 0, 1, 3, 4]

  2. left = 0
  3. right = len(arr)
  4. while left < right:
  5.     mid = left + (right - left)/2
  6.     if arr[mid] >= mid:
  7.         right = mid
  8.     else:
  9.         left = mid + 1
  10. print arr[right], right
复制代码

如果数组是 0,1,1,4,就无法用二分法了
回复

使用道具 举报

🔗
yangff 2020-7-12 18:41:44 | 只看该作者
全局:
本帖最后由 yangff 于 2020-7-12 18:47 编辑

这样最坏还是O(N), 顺便1的那个二分也有点问题
回复

使用道具 举报

🔗
AlbertZhong 2020-7-12 22:24:47 | 只看该作者
全局:
(1)的二分显然不对。。。
假设有对于一个{a_i},a[i] = i。应该返回的是0,但按照这个写法,会在mid = (1+n-1)/2之后直接返回。。。
回复

使用道具 举报

🔗
qy530389826 2020-7-12 23:26:05 | 只看该作者
全局:
我之前都没有注意到这个
回复

使用道具 举报

全局:
谢谢题主分享!
另外是不是也可以把二分法给这么改一下,作为一个铺垫算法,显示我们的优化过程:
弄多次二分法,每次二分法找到一个magic number 之后,对于左边subarray 再做一次,一直做到subarray回复没有magic number 为止……
回复

使用道具 举报

🔗
daywalker128 2020-7-13 01:38:02 | 只看该作者
全局:
AresLuo 发表于 2020-7-13 00:12
谢谢题主分享!
另外是不是也可以把二分法给这么改一下,作为一个铺垫算法,显示我们的优化过程:
弄多次二 ...

这样在最坏情况下还不如直接遍历
回复

使用道具 举报

🔗
magicpig 2020-7-13 02:53:10 | 只看该作者
全局:
(1) 的解法略有瑕疵,在a[mid] == mid的情况下应该记录下mid的值并继续对左侧进行二分。
回复

使用道具 举报

🔗
family2018 2020-7-13 03:21:37 | 只看该作者
全局:
即使是第一种情况如果求最小解,楼主考虑过[0,1,2,3,4,5]的binary解法么
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

>
快速回复 返回顶部 返回列表