9.3 A magic index in an array A[0.. .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?
這道題定義了一個魔法序號,就是一個陣列的序號等於該位置的值的時候,這個序號就是魔法序號,給了我們一個有序陣列,讓我們來找魔法序號。這裡brute force的方法就不提了,因為沒啥考察的目的,對於高效的查詢方法我們就要首先考慮二分搜尋法,首先我們來看這種方法,沒啥特別的地方,套用一般的二分查詢法的格式即可,參見程式碼如下:
class Solution { public: int getMagicIdx(vector<int> &nums) { int left = 0, right = nums.size() - 1; while (left <= right) { int mid = (left + right) / 2; if (nums[mid] == mid) return mid; else if (nums[mid] > mid) right = mid - 1; else left = mid + 1; } return -1; } };
這道題的Follow up是說如果陣列由重複項怎麼處理,那麼傳統的二分搜尋法就會失效,因為下列這種情況可能存在:
-10 | -5 | 2 | 2 | 2 | 3 | 4 | 7 | 9 | 12 | 13 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
這種情況符合題意,但是左右兩邊都會出現魔法序號,所以二分查詢法會失效。那麼我們難道又要用地毯式搜尋了麼,其實也不必,我們可以用一種類似於二分搜尋法的遞迴方法來解決問題,就拿上面那個例子來說,第一次找到比較完中間點後,由於左右兩邊都會出現答案,所以我們左右半段要分別遞迴一下,這裡我們可以加一個trick來優化演算法,比如要遞迴左半段時,那麼新的右邊界就可以設為min(mid - 1, nums[mid]),同理遞迴右半段時,左邊界可以設為max(mid + 1, nums[mid])。還有個小trick,就是如果左半段搜到了答案,那麼直接返回即可,不用再搜右半段,因為題目讓我們找一個就行了,沒說要找出所有的Magic index,參見程式碼如下:
// Follow up class Solution { public: int getMagicIdx(vector<int> &nums) { return getMagicIdxDFS(nums, 0, nums.size() - 1); } int getMagicIdxDFS(vector<int> &nums, int start, int end) { if (end < start) return -1; int mid = (start + end) / 2; if (mid == nums[mid]) return mid; int left = getMagicIdxDFS(nums, start, min(mid - 1, nums[mid])); if (left >= 0) return left; int right = getMagicIdxDFS(nums, max(mid + 1, nums[mid]), end); return right; } };