leetcode刷題 二分查詢
作為本科非科班出身的CSer也經歷了看到leetcode不知從何刷起的感覺,現在準備重新刷一下leetcode,寫部落格主要是記錄下自己的思路,儘量保持每天兩道,這篇主要總結二分查詢所遇到的問題以及變種。
打個廣告微博 @辣醬一直就這樣 歡迎同學私信討論
先說一下二分查詢的模版
二分查詢基本題型,leetcode上面好像沒有,我找了lintcode上面一道1. start + 1 < end //在相鄰的時候退出避免死迴圈 2. start + (end - start) / 2 //找中間值,裝逼寫法避免mid比Integer.max_value還大導致溢位 3. A[mid] ==, <, >//判斷要根據找target是第一次出現還是最後一次出現來決定把mid 給start 還是end 4. A[start] A[end] ? target//最後相鄰退出同樣要考慮是找target第一次出現還是最後一次出現.
這道題就是最基本的二分查詢,target有重複,需要找到target第一次出現時所在陣列的下標,很簡單套模版
接著提高一點leetcode 34. Search for a Range https://leetcode.com/problems/search-for-a-range/description/class Solution { /** * @param nums: The integer array. * @param target: Target to find. * @return: The first position of target. Position starts from 0. */ public int binarySearch(int[] nums, int target) { //write your code here if(nums.length == 0){ return -1; } int start = 0; int end = nums.length - 1; int mid ; while(start + 1 < end){ mid = start + (end - start) / 2; if(nums[mid] == target){ end = mid;//注意是找第一個出現的位置,我們就認為找到一個他就是最右邊的往左找看有沒有相同的 }else if(nums[mid] < target){ start = mid; }else{ end = mid ; } } if(nums[start] == target){ return start; } if(nums[end] ==target){ return end; } return -1; } }
這道題就是讓你找到target在陣列中的起始和結束位置,也不難,思路就是順著上一題,先二分一次找到target第一次出現的位置,再二分一次找到target最後一次出現的位置
public class Solution { public int[] searchRange(int[] nums, int target) { int []res = {-1 , -1} ; if(nums == null || nums.length == 0){ return res ; } //先找左邊的位置; int start = 0 ; int end = nums.length - 1 ; int mid ; while(start + 1 < end){ mid = start + (end - start) / 2; if(nums[mid] == target){ end = mid ;//左邊可能還有要往左邊找 }else if(nums[mid] < target){ start = mid ; }else{ end = mid ; } } if(nums[start] == target)//先判斷左邊的 { res[0] = start ; }else if(nums[end] == target){ res[0] = end ; } //右邊同左邊的全都反過來 start = 0 ; end = nums.length - 1 ; while(start + 1 < end){ mid = start + (end - start) / 2; if(nums[mid] == target){ start = mid ; }else if(nums[mid] < target){ start = mid ; }else if(nums[mid] > target){ end = mid ; } } if(nums[end] == target){ res[1] = end ; }else if(nums[start] == target){ res[1] = start ; } return res ; } }
這道題就是給了一個數組把target插入到裡面讓陣列還是有序排列,如果數組裡面有這個值就返回陣列下標,如果沒有的就找到最後一個比他小的數插入到這個數後面,如果沒有比target還小的數就直接插入到陣列最前面,還是用二分查詢,套上面的模板,最後處理end start 有些繁瑣,注意就好了
public class Solution {
public int searchInsert(int[] nums, int target) {
if(nums == null || nums.length == 0 || nums[0] > target){
return 0 ;
}
int start = 0 ;
int end = nums.length - 1 ;
int mid ;
while(start + 1 < end){
mid = start + (end - start) / 2 ;
if(nums[mid] == target){
return mid ;
}else if (nums[mid] > target){
end = mid ;
}else if (nums[mid] < target){
start = mid ;
}
}
if(nums[end] == target){
return end ;
}else if(nums[end] < target){
return end + 1 ;
}else if(nums[start] == target){
return start ;
}
return start + 1 ;
}
}
接下是74. Search
a 2D Matrix 搜尋矩陣中是否有目標值 , 矩陣的每一行和每一列都是按順序排列,每一行的第一個數都比上一行的最後一個數大,思路還是用二分查詢,一次先找到所在的行,再一次二分找到所在的列
public class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
return false ;
}
int row = matrix.length ;
int colum = matrix[0].length ;
int mid ;
//先找行
int start = 0 ;
int end = row - 1 ;
while(start + 1 < end){
mid = start + (end - start) / 2;
if(matrix[mid][0] == target){
return true ;
}else if(matrix[mid][0] > target) {
end = mid ;
}else {
start = mid ;
}
}
//處理剩下的start 和 end
if(matrix[end][0] <= target){
row = end ;
}else if (matrix[start][0] <= target){
row = start ;
}else{
return false ;
}
//再找列
start = 0;
end = colum - 1 ;
while(start + 1 < end){
mid = start + (end - start) / 2;
if(matrix[row][mid] == target){
return true ;
}else if(matrix[row][mid] < target) {
start = mid ;
}else{
end = mid ;
}
}
// 只剩兩個地方沒處理,只需要判斷是否等於就好
if(matrix[row][start] == target){
return true ;
}else if(matrix[row][end] == target){
return true ;
}
return false ;
}
}
240. Search a 2D Matrix II
這道題是上一題的提高版,還是一個二維矩陣,但是不保證下一行的數都比上一行的數大,只保證每行中後面比前面大, 每列中,下面比上面大,暴力搜尋就是時間複雜度n2,肯定是不行的,我們來看這張圖
先找到第一列的最後一個,他是最後一行最小的,如果比target大就說明最後一行都比target大,就往上挪一個,如果比target小,由於他是第一列最大的,就說明第一列都比target小,就往右挪一個,這樣如圖,最差要用O(n + m)時間就可以找到,以下是程式碼
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
return false ;
}
int x = matrix.length - 1 ;
int y = 0 ;
while(x >= 0 && y < matrix[0].length){
if(matrix[x][y] > target){
x -- ;
}else if(matrix[x][y] < target){
y ++ ;
}else{
return true ;
}
}
return false ;
}
}
還有一道題278. First Bad Version沒什麼說的,找到第一個壞的版本號,基本的二分查詢
public class Solution extends VersionControl {
public int firstBadVersion(int n) {
int start = 0 ;
int end = n ;
int mid ;
while(start + 1 < end){
mid = start + (end - start) / 2 ;
if(isBadVersion(mid)){
end = mid ;
}else{
start = mid ;
}
}
if(isBadVersion(start)){
return start ;
}
return end ;
}
}
class Solution {
public int findPeakElement(int[] nums) {
int start = 0 ;
int end = nums.length - 1 ;
int mid ;
while(start + 1 < end){
mid = start + (end - start) / 2 ;
// mid -1 < mid > mid + 1 說明mid就是峰值
if(nums[mid] > nums[mid + 1] && nums[mid] > nums[mid - 1]){
return mid ;
// mid - 1 < mid < mid + 1 說明 peak在mid右邊
}else if(nums[mid] < nums[mid + 1] && nums[mid] > nums[mid - 1]){
start = mid ;
//剩下兩種情況 凹的話 取左右哪邊都一樣,遞減的話只能取左邊,然後取交集 讓end = mid
}else{
end = mid ;
}
}
if(nums[end] > nums[start]){
return end ;
}
return start ;
}
}
26. Remove
Duplicates from Sorted Array沒什麼說的相等的留下不等的跳過
class Solution {
public int removeDuplicates(int[] nums) {
//不能有新的空間意思就是不能建立新的陣列來儲存
if(nums == null || nums.length == 0){
return 0 ;
}
int size = 0 ;
// 注意不能nums.length - 1 要不然最後一個沒有遍歷到
for(int i = 0 ; i < nums.length ; i++){
// 相等就跳過不相等留下
if(nums[size] != nums[i]){
size++ ;
nums[size] = nums[i] ;
// nums[++size] = nums[i]
}
}
return size + 1 ;
}
}
80. Remove Duplicates from Sorted Array II這個就是要做一個計數器記錄是否達到了重複兩次,沒有就留下有就跳過
class Solution {
public int removeDuplicates(int[] nums) {
if(nums == null || nums.length == 0){
return 0 ;
}
int size = 0 ;
int count = 1 ;
// 注意i為什麼要從1開始? i直接找的第二個數不是第一個 因為第一個nums[size] 和nums[i]肯定是相等的
for(int i = 1 ; i < nums.length ; i ++){
// 如果相等看有沒有達到重複兩次
if(nums[size] == nums[i]){
// 所以這裡也要注意,並不是才加了一遍, 而是如果這裡相等代表前面已經相等過一次了
if(count < 2){
size ++ ;
nums[size] = nums[i] ;
count ++ ;
}
}else{
size ++ ;
nums[size] = nums[i] ;
// 注意要把計數器復原,也可以看作是第一次相等
count = 1 ;
}
}
return size + 1 ;
}
}
88. Merge
Sorted Array考驗基本功的一題沒什麼難度
public class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int a = m - 1 ;
int b = n - 1 ;
int k = m + n - 1 ;
while(a >= 0 && b >= 0){
if(nums1[a] > nums2[b]){
nums1[k--] = nums1[a--];
}else{
nums1[k--] = nums2[b--];
}
}
while(a >= 0){
nums1[k--] = nums1[a--] ;
}
while(b >= 0){
nums1[k--] = nums2[b--] ;
}
}
}
4. Median of Two Sorted Arrays這個題很重要是重點題
題解:
首先我們先明確什麼是median,即中位數。
引用Wikipedia對中位數的定義:
計算有限個數的資料的中位數的方法是:把所有的同類資料按照大小的順序排列。如果資料的個數是奇數,則中間那個資料就是這群資料的中位數;如果資料的個數是偶數,則中間那2個數據的算術平均值就是這群資料的中位數。
因此,在計算中位數Median時候,需要根據奇偶分類討論。
解決此題的方法可以依照:尋找一個unioned sorted array中的第k大(從1開始數)的數。因而等價於尋找並判斷兩個sorted array中第k/2(從1開始數)大的數。
特殊化到求median,那麼對於奇數來說,就是求第(m+n)/2+1(從1開始數)大的數。
而對於偶數來說,就是求第(m+n)/2大(從1開始數)和第(m+n)/2+1大(從1開始數)的數的算術平均值。
那麼如何判斷兩個有序陣列A,B中第k大的數呢?
我們需要判斷A[k/2-1]和B[k/2-1]的大小。
如果A[k/2-1]==B[k/2-1],那麼這個數就是兩個陣列中第k大的數。
如果A[k/2-1]<B[k/2-1], 那麼說明A[0]到A[k/2-1]都不可能是第k大的數,所以需要捨棄這一半,繼續從A[k/2]到A[A.length-1]繼續找。當然,因為這裡捨棄了A[0]到A[k/2-1]這k/2個數,那麼第k大也就變成了,第k-k/2個大的數了。
如果 A[k/2-1]>B[k/2-1],就做之前對稱的操作就好。
這樣整個問題就迎刃而解了。
當然,邊界條件頁不能少,需要判斷是否有一個數組長度為0,以及k==1時候的情況。
以上是百度的別人的部落格,思路就是這個思路,變形就是讓你找第K小元素的時候別不會找了class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int len = nums1.length + nums2.length;
if (len % 2 == 1) {
return findKth(nums1, 0, nums2, 0, len / 2 + 1);
}
return (
findKth(nums1, 0, nums2, 0, len / 2) + findKth(nums1, 0, nums2, 0, len / 2 + 1)
) / 2.0;
}
// find kth number of two sorted array
public static int findKth(int[] nums1, int A_start,
int[] nums2, int B_start,
int k){
if (A_start >= nums1.length) {
return nums2[B_start + k - 1];
}
if (B_start >= nums2.length) {
return nums1[A_start + k - 1];
}
if (k == 1) {
return Math.min(nums1[A_start], nums2[B_start]);
}
int A_key = A_start + k / 2 - 1 < nums1.length
? nums1[A_start + k / 2 - 1]
: Integer.MAX_VALUE;
int B_key = B_start + k / 2 - 1 < nums2.length
? nums2[B_start + k / 2 - 1]
: Integer.MAX_VALUE;
if (A_key < B_key) {
return findKth(nums1, A_start + k / 2, nums2, B_start, k - k / 2);
} else {
return findKth(nums1, A_start, nums2, B_start + k / 2, k - k / 2);
}
}
}
151. Reverse Words in a String這個也比較簡單
public class Solution {
public String reverseWords(String s) {
if(s == null || s.length() == 0){
return "" ;
}
String[] a = s.split(" ") ;
StringBuilder sb = new StringBuilder() ;
for(int i = a.length - 1 ; i >= 0 ; i --){
//注意這裡不要寫 ==
if(!a[i].equals("")){
sb.append(a[i]).append(" ") ;
}
}
return sb.length() == 0 ? "" :sb.substring(0 , sb.length() - 1) ;
}
}
下面出一個思考題,如何把一個Rotated sort array復原?
答:三步翻轉法