前言 - 引言面試
題目: 一類有序數組旋轉查值問題. 例如:
有序數組 [ 1, 2, 3, 5, 5, 7, 7, 8, 9 ] 旋轉後爲 [ 5, 7, 7, 8, 9, 1, 2, 3, 5 ] 如何從中找出一個值索引, not found return -1.
(同事面試時手寫最簡單一題, 回來和我說了一下, 就記下作個終結者系列)算法
這種旋轉數組有個特色. 你們看圖數組
相信你們豁然開朗了. 這裏給個網上爛大街答案測試
// // [1, 2, 3, 5, 5, 7, 7, 8, 9] // 升序數組翻轉後 // [5, 7, 7, 8, 9, 1, 2, 3, 5] // 查找 value, return index, not found return -1; // int search(int a[], int len, int v) { int begin, end, mid; // 異常判斷 if (a == NULL || len <= 0) return -1; begin = 0; end = len; while (begin < end) { mid = (begin + end) / 2; if (a[mid] == v) return mid; if (a[begin] < a[mid]) { // 左邊有序 [begin, mid] if (a[begin] <= v && v < a[mid]) end = mid; else begin = mid + 1; } else if (a[begin] > a[mid]) { // 右邊有序 [mid, end) if (a[mid] < v && v <= a[end - 1]) begin = mid + 1; else end = mid; } else { ++begin; } } // 沒有找到 return -1; }
這裏使用 [begin, end) 二分法技巧. 代碼支持升序旋轉重複數組. 最壞狀況(全重複)算法複雜度是 O(n).spa
不過有個問題, 若是不知道是升序 asc 仍是降序 desc. 那就須要額外判斷了. code
// search_sort_state - 排序狀態 -1 faild, 0 desc, 1 asc static int search_sort_state(int a[], int len) { int state, i, s[3]; if (a == NULL || len <= 0) return -1; // 默認 desc 降序 if (len == 1) return 0; // 1, 2 asc 升序, 但必須反轉爲 2, 1 變成降序. 於是當前降序 desc 本來就是升序 asc if (len == 2) return a[0] > a[1]; // 摘取不重複的3個內容 s[0] = a[0]; // 開始找 s[1] for (i = 1; i < len; ++i) { if (a[i] == s[0]) continue; break; } // 全部值都同樣, 走默認降序 if (i >= len) return 0; s[1] = a[i]; // 開始找 s[2] while (i < len) { if (a[i] == s[0] || a[i] == s[1]) { ++i; continue; } break; } // 只有兩個不同的值, 走默認降序 if (i >= len) return s[0] > s[1]; s[2] = a[i]; state = 0; state += s[0] > s[1] ? 1 : -1; state += s[1] > s[2] ? 1 : -1; state += s[2] > s[0] ? 1 : -1; return state >= 0 ? 0 : 1; }
最後是本身想得一個排序狀態判別的算法(自我感受巧妙). 試圖找出不重複三個數. 例如blog
6 7 5 有 排序
6 < 7 <索引
7 > 5 >get
5 < 5 <
原生組合是 5 6 7
於是 < 居可能是升序. > 居可能是降序. (核心緣由是旋轉數組大小關係只改變一次)
正文 - 擴展
有了上面鋪墊那咱們開始碼一個問題終結者代碼. 但願有所感悟
// bsearch_asc - 二分查找升序查找 static int bsearch_asc(int a[], int begin, int end, int v) { // 簡單判斷 if (begin >= end || v < a[begin] || v > a[end - 1]) return -1; // 二分查找 do { int mid = (begin + end) / 2; int val = a[mid]; if (val == v) return mid; if (val < v) begin = mid + 1; else end = mid; } while (begin < end); return -1; } static int search_asc(int a[], int len, int v) { int begin = 0, end = len; // 異常判斷 if (begin >= end) return -1; while (begin < end) { int mid = (begin + end) / 2; if (a[mid] == v) return mid; if (a[begin] < a[mid]) { // 左邊有序 [begin, mid] if (a[begin] <= v && v < a[mid]) return bsearch_asc(a, begin, mid, v); // 右邊無序, 繼續循環 begin = mid + 1; } else if (a[begin] > a[mid]) { // 右邊有序 [mid, end) if (a[mid] < v && v <= a[end - 1]) return bsearch_asc(a, mid + 1, end, v); // 左邊無須, 繼續循環 end = mid; } else { ++begin; } } // 沒有找到 return -1; } // bsearch_desc - 二分查找降序查找 static int bsearch_desc(int a[], int begin, int end, int v) { // 簡單判斷 if (begin >= end || v > a[begin] || v < a[end - 1]) return -1; // 二分查找 do { int mid = (begin + end) / 2; int val = a[mid]; if (val == v) return mid; if (val > v) begin = mid + 1; else end = mid; } while (begin < end); return -1; } static int search_desc(int a[], int len, int v) { int begin = 0, end = len; while (begin < end) { int mid = (begin + end) / 2; if (a[mid] == v) return mid; if (a[begin] > a[mid]) { // 左邊有序 [begin, mid] if (a[begin] >= v && v > a[mid]) return bsearch_desc(a, begin, mid, v); // 右邊無序, 繼續循環 begin = mid + 1; } else if (a[begin] < a[mid]) { // 右邊有序 [mid, end) if (a[mid] > v && v >= a[end - 1]) return bsearch_desc(a, mid + 1, end, v); // 左邊無須, 繼續循環 end = mid; } else { ++begin; } } // 沒有找到 return -1; }
// // 題目: // 一類有序數組旋轉查值問題. // 例如: // 有序數組 [ 1, 2, 3, 5, 5, 7, 7, 8, 9 ] 旋轉後爲 [ 5, 7, 7, 8, 9, 1, 2, 3, 5 ] // 如何從中找出一個值索引, not found return -1. int search_upgrade(int a[], int len, int v) { int state, i, s[3]; if (a == NULL || len <= 0) return -1; // 默認 desc 降序 if (len == 1) { if (a[0] == v) return 0; return -1; } if (len == 2) { if (a[0] == v) return 0; if (a[1] == v) return 1; return -1; } // 摘取不重複的3個內容 s[0] = a[0]; // 開始找 s[1] for (i = 1; i < len; ++i) { if (a[i] == s[0]) continue; break; } // 全部值都同樣, 走默認降序 if (i >= len) { if (s[0] == v) return 0; return -1; } s[1] = a[state = i]; // 開始找 s[2] while (i < len) { if (a[i] == s[0] || a[i] == s[1]) { ++i; continue; } break; } // 只有兩個不同的值, 走默認降序 if (i >= len) { if (s[0] == v) return 0; if (s[1] == v) return state; return -1; } s[2] = a[i]; state = 0; state += s[0] > s[1] ? 1 : -1; state += s[1] > s[2] ? 1 : -1; state += s[2] > s[0] ? 1 : -1; // desc 降序, 旋轉 if (state >= 0) return search_desc(a, len, v); // asc 升序, 旋轉 return search_asc(a, len, v); }
不一樣分支不一樣代碼, 針對性強. 代碼最壞的狀況是 O(n).
這裏不妨來個測試演示
#include <stdio.h> #include <stdlib.h> #define LEN(a) (sizeof(a)/sizeof(*a)) // print - 數據內容打印 #define INT_BR (15) static void print(int a[], int len) { int i = 0; while (i < len) { printf(" %d", a[i]); if (!(++i % INT_BR)) putchar('\n'); } if (i % INT_BR) putchar('\n'); } int search_upgrade(int a[], int len, int v); // sort - 旋轉查找 int main(int argc, char * argv[]) { int i, v; int a[] = { 5, 7, 7, 8, 9, 1, 2, 3, 5 }; print(a, LEN(a)); // 開始測試 v = 8; i = search_upgrade(a, LEN(a), v); printf("%d -> %d\n", v, i);
v = 5; i = search_upgrade(a, LEN(a), v); printf("%d -> %d\n", v, i); v = 6; i = search_upgrade(a, LEN(a), v); printf("%d -> %d\n", v, i); v = 7; i = search_upgrade(a, LEN(a), v); printf("%d -> %d\n", v, i); v = 4; i = search_upgrade(a, LEN(a), v); printf("%d -> %d\n", v, i); int b[] = { 7, 6, 5, 3, 3, 2, 1, 9, 9, 8, 8, 7, 7 }; print(b, LEN(b)); // 開始測試 v = 8; i = search_upgrade(b, LEN(b), v); printf("%d -> %d\n", v, i); v = 5; i = search_upgrade(b, LEN(b), v); printf("%d -> %d\n", v, i); v = 6; i = search_upgrade(b, LEN(b), v); printf("%d -> %d\n", v, i); v = 7; i = search_upgrade(b, LEN(b), v); printf("%d -> %d\n", v, i); v = 4; i = search_upgrade(b, LEN(b), v); printf("%d -> %d\n", v, i); return EXIT_SUCCESS; }
當前輸出結果以下
$ gcc -g -Wall sort.c ; ./a.out 5 7 7 8 9 1 2 3 5 8 -> 3 5 -> 0 6 -> -1 7 -> 2 4 -> -1 7 6 5 3 3 2 1 9 9 8 8 7 7 8 -> 10 5 -> 2 6 -> 1 7 -> 0 4 -> -1
後記 - 感謝
錯誤是不免的 ~ 歡迎指正 : )
小橋 - https://music.163.com/#/song?id=493042772