【分模組練習】二分查詢

abyss_miracle發表於2020-12-08

二分查詢思路簡單,但細節很搞人。個人習慣用左閉右開的區間寫法,以下是模板:

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int n = nums.size();
        int left = 0;
        int right = n; // 我們定義target在左閉右開的區間裡,[left, right)  
        while (left < right) { // 因為left == right的時候,在[left, right)是無效的空間
            int middle = left + ((right - left) >> 1);
            if (nums[middle] > target) {
                right = middle; // target 在左區間,因為是左閉右開的區間,nums[middle]一定不是我們的目標值,所以right = middle,在[left, middle)中繼續尋找目標值
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右區間,在 [middle+1, right)中
            } else { // nums[middle] == target
                return middle; // 陣列中找到目標值的情況,直接返回下標
            }
        }
        return right;
    }
};

69.x 的平方根 【找一個數】

大意:
實現 int sqrt(int x) 函式。
計算並返回 x 的平方根,其中 x 是非負整數。
由於返回型別是整數,結果只保留整數的部分,小數部分將被捨去。

樣例:
輸入: 8
輸出: 2
說明: 8 的平方根是 2.82842…,
由於返回型別是整數,小數部分將被捨去。

思路:相當於求x方 - a = 0的解。所以就是在[0,a]之間二分法找一個數可以x * x = a;

class Solution {
public:
	int mySqrt(int a) {
        if(a == 0 || a == 1) return a;
		int n = a - 1;
		int left = 1, right = a,sqrt;
		while (left < right)  //左閉右開
		{
			int mid = left + (right - left) / 2;
			sqrt = a / mid;
			if (sqrt == mid)
				return mid;
			else if (sqrt < mid) //目標值在左區間中,此時mid一定不是目標值
				right = mid;  //右邊是開區間
			else
				left = mid + 1; //左閉是閉區間,而Mid已經不是目標值了,所以要加一
		}
		return right-1;  //退出時,左右重合
	}
};

34.在排序陣列中查詢元素的第一個和最後一個位置 【區間查詢】

大意:
給定一個按照升序排列的整數陣列 nums,和一個目標值 target。找出給定目標值在陣列中的開始位置和結束位置。
如果陣列中不存在目標值 target,返回 [-1, -1]。

樣例:
輸入:nums = [5,7,7,8,8,10], target = 8
輸出:[3,4]

class Solution {
public:
	vector<int> searchRange(vector<int>& nums, int target) {
		if (nums.empty())
			return vector<int>{-1, -1};
		int lower = lower_bound(nums, target);
		int high = high_bound(nums, target);

		if (lower == nums.size() || nums[lower] != target) //沒有賦值的情況
			return vector<int>{-1, -1};

		return vector<int>{lower, high-1};
	}
	
	int lower_bound(vector<int>& nums,int target)
	{
		int l = 0, r = nums.size();
		while (l < r)
		{
			int mid = l + (r - l) / 2;
			if (nums[mid] < target)
				l = mid + 1;
			else if (nums[mid] >= target)  //等於也要繼續往左邊找
				r = mid;
		}
		return r;  //左閉右開寫法,最後 跳出來l == r所以這裡寫l,r都可以
    }
    
	int high_bound(vector<int>& nums, int target)  //尋找第一個大於target的數
	{
		int l = 0,r = nums.size();
		while (l < r)
		{
			int mid = l + (r - l) / 2;
			if (nums[mid] > target)
				r = mid;
			else
				l = mid + 1;  //如果相等也要往右邊找,因為要找到第一個大於Target的數
		}
		return r;
	}
};

81. 搜尋旋轉排序陣列 II 【旋轉陣列】

大意:假設按照升序排序的陣列在預先未知的某個點上進行了旋轉。
( 例如,陣列 [0,0,1,2,2,5,6] 可能變為 [2,5,6,0,0,1,2] )。
編寫一個函式來判斷給定的目標值是否存在於陣列中。若存在返回 true,否則返回 false。

即給的陣列是由某個遞增陣列平移變形而來的。

輸入: nums = [2,5,6,0,0,1,2], target = 0
輸出: true

思路:二分時,先找確定的區間。如確定右邊是有序的,而且target又可以落入區間內,就先去右邊找target。在這裡要注意,target必須在有序的範圍內 (這裡上屆下屆都要寫,因為如果不寫,可能會漏查)。如果不在,就要到另一半區間去嘗試二分尋找。
如給陣列[3,1],和target = 3;
一開始判斷1是有序的遞增數列,如果不寫上界,就會直接結束程式,然而3是在mid的左邊。

class Solution {
public:
	bool search(vector<int>& nums, int target) {
		int left = 0, right = nums.size();
		while (left < right)
		{
			int mid = left + (right - left) / 2;
			if (nums[mid] == target)
				return true;
			
			if (nums[mid] == nums[left])  //沒法知道有序無序
				++left;
			else if (nums[mid] > nums[left]) //中間大於最左邊,說明左邊有序
			{
				if (nums[mid] > target && target >= nums[left]) //target落入左邊且大小合法
					right = mid;								
				else																			
					left = mid + 1;		
			}
			else
			{
				int end = (right == nums.size() ? nums.size() - 1 : right);
				if (nums[mid] < target && target <= nums[end])//target落入右邊且合法
					left = mid + 1;						// 如輸入[3, 1], target = 3
				else
					right = mid;	//落入右邊但大小不合法,說不定在左邊可以找到,所以要去嘗試一下左邊
			}
		}

		return false;
	}
};

相關文章