@xmruibi
2015-02-13T03:43:23.000000Z
字数 7912
阅读 791
Algorithm
This is conclusion for binary search problems.
// notice left < right 还是 left<=right
// 当出现 left/right = middle 时 不可有等号 否则死循环
// 谨慎处理 middle+1|-1 的赋值语句, 严防越过目标位置
int search(int a[]): // return indexint lo = 0;int hi = a.length()-1;while(lo<=hi):int mi = lo + (hi-lo)/2;if(i == a[mi])return mi;else if(a[mi]<target)hi = mi-1;elselo = mi+1;return -1;
此题也就是求target在数组中第一次出现的位置。这里可能会有人想先直接用原始的二分查找,如果不存在直接返回-1,如果存在,然后再顺序找到这个等于target值区间的最左位置,这样的话,最坏情况下的复杂度就是O(n)了,没有完全发挥出二分查找的优势。
int search(int a[]): // return indexint lo = 0;int hi = a.length()-1;while(lo<=hi):int mi = lo + (hi-lo)/2;if(i == a[mi])while(a[mi-1] == a[mi])mi--;return mi;else if(i<a[mi])hi = mi-1;elselo = mi+1;return -1;/* 循环过程中,当low大于0时,A[low-1]是小于target的,因为A[mid] < target时,low=mid+1;当high小于n-1时,A[high]是大于等于target的,因为A[mid] >= target时,high = mid;循环结束时,low 等于 high,所以,如果A[low](A[high])等于target,那么low(high)就是target出现的最小位置,否则target在数组中不存在。*/int searchFirstPos(int a[]): // return indexint lo = 0;int hi = a.length() - 1;while(lo<hi):int mi = lo + (hi-lo)>>1;if(a[mi]>target)lo = mi+1;elsehi = mi;if(lo!=target)return -1;elsereturn lo;
int searchLastPos(int a[]): // return indexint lo = 0;int hi = a.length() - 1;while(lo<hi):/* 这里中间位置的计算就不能用low+((high-low)>>1)了,因为当low+1等于high且A[low] <= target时,会死循环;所以这里要使用low+((high-low+1)>>1),这样能够保证循环会正常结束。*/int mi = lo + (hi-lo+1)>>1;if(a[mi]>target)hi = mi-1;elselo = mi;if(hi!=target)return -1;elsereturn hi;
int searchLastLessThan(int a[]):int lo = 0;int hi = a.length() - 1;while(lo<hi):int mi = lo + (hi-lo+1)>>1;if(a[mi]<target)lo = mi;elsehi = mi - 1;return a[lo]<target?lo:-1;
int searchFirstGreaterThan(int a[]):int lo = 0;int hi = a.length() - 1;while(lo<hi):int mi = lo + (hi-lo)>>1;if(a[mi]> target)hi = mi;elselo = mi + 1;return a[hi]<target?hi:-1;
int count(int A[], int n, int target):int firstPos = searchFirstPos(A, n, target); // 第一次出现位置if(firstPos == -1)return 0;int lastPos = searchLastPos(A, n, target); // 最后一次出现位置return lastPos-firstPos+1; // 出现次数6.* Search for a Range -- LeetCode: similar with the aforementioned solution// idea is find the first index of target and// the left element of the first element larger than targetsearchRange(int[] A, int target):int start = helper(A, target);if (start == A.length || A[start] != target)return new int[]{-1, -1};int end = helper(A,target+1)-1;int helper(int a[], int lo, int hi):while(lo<hi):int mi = lo + (hi - lo)>>1;//low <= mid < highif(a[mi]>target)lo = mi+1;elsehi = mi;return lo;
int search(int a[]): // return indexint lo = 0;int hi = a.length()-1;while(lo<hi):int mi = lo + (hi-lo)/2;if(a[mi]>=target)hi = mi;elselo = mi+1;return hi;
//找第一个大于等于0的位置,然后和前一个元素的绝对值比较,返回绝对值较小的元素的位置。
int search(int a[]): // return indexint lo = 0;int hi = a.length()-1;while(lo<hi):int mi = lo + (hi-lo)>>1;if(a[mi]>=0)hi = mi;elselo = mi+1;/* 循环结束时,如果low != n-1,A[low] >= 0,如果low>0,A[low-1] < 0 */if(low > 0 && abs(A[low-1]) < abs(A[low]))return low-1;elsereturn low;
// A.length < B.length otherwise change A,B
// keep A[lenB/2] <= B[lenB/2] otherwise change A,B
// Arrays.copyOfRange(src, 0, 2)
private int helper(int A[], int B[], int i, int i2, int j, int j2, int k){int m = i2-i+1;int n = j2-j+1;if(m>n)return helper(B,A,j,j2,i,i2,k);if(m==0) // A is nullreturn B[j+k-1];if(k==1) // k == 1 means the minimal number of two arraysreturn Math.min(A[i],B[j]);int posA = Math.min(k/2,m); //!!int posB = k-posA; //!!if(A[i+posA-1]==B[j+posB-1])return A[i+posA-1];else if(A[i+posA-1]<B[j+posB-1]) // 取 A右[i+posA,i2] B左 j,j+posB-1return helper(A,B,i+posA,i2,j,j+posB-1,k-posA);elsereturn helper(A,B,i,i+posA-1,j+posB,j2,k-posB);}
Sqrt:
ONLY VALID WHEN THE RETURN VALUE IS INTEGER!
int sqrt(int x):if(x < 0)return -1;if(x == 0)return 0;int l = 1;int r = x>>1 +1;while(l<r):int m = l+(r-l)>>1;if(x/m=>m&&m+1>x/m+1)return m;else if(x/m<m):r = m-1;elsel = m+1;return 0;
Pow:
// considering with divide and conquerdouble pow(int x, int n):if(n==0)return 1.0;double half = pow(x,n/2);if(n%2==0)return half*half;else:if(n%2>0): // n>0return (half*half)*x;else: // n<0return (half*half)/x;
Division:
int divide(int dividend, int divisor):int res = 0;// several corner cases:if(divisor == 0) // illegal inputreturn Integer.MAX_VALUE;else if (divisor == 1)return dividend;else if(dividend == Integer.MIN_VALUE):if (divisor == -1) // ensure the result in the range of 2^31-1 ~ -2^31return (dividend == Integer.MIN_VALUE) ? Integer.MAX_VALUE : -dividend;dividend += Math.abs(divisor);res++;if(divisor == Integer.MIN_VALUE)return res;boolean isNeg = (dividend^divisor)>>>31 == 1;dividend = Math.abs(dividend);divisor = Math.abs(divisor);// 位操作法。。。int digit = 0; //while(divisor <= (dividend>>1)): // 除数 小于 被除数的二分一divisor <<= 1; // 除数*2digit++;while(digit>=0):if(dividend>=divisor):res += 1<<digit; // 1 左移 digit位 2^digitaldividend -= divisor;divisor >>= 1;digit--;return isNeg?-res:res;// 二分法???
如果中间元素大于其相邻后续元素,则中间元素左侧(包含该中间元素)必包含一个局部最大值。
如果中间元素小于其相邻后续元素,则中间元素右侧必包含一个局部最大值。
int findPeak(int[] a):int l = 0;int r = a.length - 1;while(l<=r):int mi = l + (r-l)/2;if(l ==r)return l;if(a[mi]<a[mi+1])l = mi+1;elser = mi;return 0;
因为矩阵是行有序并且列有序,查找只需要先按行查找,定位出在哪一行之后再进行列查找即可,所以就是进行两次二分查找。
时间复杂度是O(logm+logn),空间上只需两个辅助变量,因而是O(1).
boolean searchMatrix(int[][] matrix, int target):if(matrix == null || matrix.length==0 || matrix[0].length==0)return false;int row = 0;// find rowint l = 0;int r = matrix.length - 1;while(l<=r):int m = l + (r - l)>>1;if(matrix[m][0]==target)return true;if(matrix[m][0]>target)r = m-1;elsel = m+1;row = r;if(row<0)return false;l = 0;r = matrix[0].length - 1;while(l<r):int m = l + (r - l)>>1;if(matrix[row][m]==target)return true;if(matrix[row][m]<target)r = m - 1;elsel = m + 1;return false;
如 0 1 2 4 5 6 7 可能变成 4 5 6 7 0 1 2
我们先比较中间元素是否是目标值,如果是返回位置。如果不是,我们就应该想办法将搜索区间减少一半。因为存在旋转变化,所以我们要多做一些判断。
我们知道因为只有一次旋转变化,所以中间元素两边的子数组肯定有一个是有序的,那么我们可以判断target是不是在这个有序的子数组中,
从而决定是搜索这个子数组还是搜索另一个子数组。
You should assume no duplicate exists!!
(1) 如果target==A[m],那么m就是我们要的结果,直接返回;
(2) 如果A[m]<A[r],那么说明从m到r一定是有序的(没有受到rotate的影响),那么我们只需要判断target是不是在m到r之间,
如果是则把左边缘移到m+1,否则就target在另一半,即把右边缘移到m-1。
(3) 如果A[m]>=A[r],那么说明从l到m一定是有序的,同样只需要判断target是否在这个范围内,相应的移动边缘即可。
时间复杂度是O(logn),空间复杂度是O(1)。
int search(int[] A, int target):int l = 0;int r = A.length;while(l<r):int m = l + (r-l)>>1;if(A[m]==target)return m;if(A[m]>=A[l]): // l ~ m 升序if(A[m]>target&&A[l]<=target) //target 不能小于lr = m-1;elsel = m+1;else:if(A[m]<target&&A[r]>=target) // target 不能大于rl = m+1;elser = m-1;return -1;
Duplicate is allowed!
但我们会遇到中间和边缘相等的情况,我们就丢失了哪边有序的信息,因为哪边都有可能是有序的结果。
假设原数组是{1,2,3,3,3,3,3},那么旋转之后有可能是{3,3,3,3,3,1,2},或者{3,1,2,3,3,3,3},
这样的我们判断左边缘和中心的时候都是3,如果我们要寻找1或者2,我们并不知道应该跳向哪一半。
解决的办法只能是对边缘移动一步,直到边缘和中间不在相等或者相遇,这就导致了会有不能切去一半的可能。
所以最坏情况(比如全部都是一个元素,或者只有一个元素不同于其他元素,而他就在最后一个)
就会出现每次移动一步,总共是n步,算法的时间复杂度变成O(n)。
boolean search(int[] A, int target):int l = 0;int r = A.length;while(l<r):int m = l + (r-l)>>1;if(A[m]==target)return true;if(A[m]>A[l]): // l ~ m 升序if(A[m]>target&&A[l]<=target) //target 不能小于lr = m-1;elsel = m+1;else if(A[m]<A[l]): // m ~ r 升序if(A[m]<target&&A[r]>=target) // target 不能大于rl = m+1;elser = m-1;else // A[m] == A[l]l++; // this is the solution to deal with duplicated elementsreturn false;
假如翻转后的数组以第 x 个结点分为两部分 A[0..x] 和 A[x+1..n]。
则 A[0..x] 这一段是有序递增的, A[x+1..m] 这一段也是有序递增的。
并且因为原数组是有序递增的,A[0..x] 中所有数都会大于 A[x+1..n] 中的任何数。
所以我们其实就是需要找到结点 A[x+1],这个结点的值就是最小值。
No duplicated:
Solution One:
int searchMin(int[] A):if(A.length == 0)return o;int l = 0, r = A.length - 1;while(l<r):int m = l+ (r-l)>>1;if(A[l]>A[r]):if(A[l]<=A[m])l = m+1;elser = m;elsereturn A[left];return A[left];
Has duplicated:
int searchMin(int[] A):int l = 0;int r = num.length-1;int min = Integer.MAX_VALUE;while(l < r-1):int m = l+ (r-l)>>1;if(A[m]>A[l]):min = Math.min(min,A[l]);l = m + 1;else if(A[m]<A[l]):min = Math.min(min,A[m]);r = m - 1;elsel++;min = Math.min(Math.min(min, A[l]), A[r]);return min;
int searchKthInRotatedArray(int A[], int n, int k):int posMin = searchMinInRotatedArray(A, n);return (posMin+k-1)%n;