leetcode基础题:二分查找

下面是一个标准的二分算法及其细节解读:

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left=0,right=nums.size()-1;
        int mid=0;
        while(left<=right){
            mid=((right-left)>>1)+left;
            //中间点不用(ight+left)/2防止溢出;
            //位运算速度比除法快,可以节省时间
            if(nums[mid]==target)
                return mid;
			else if(nums[mid]>target)
			//大小号可以重载为某种排序规则,比如是否是偶数、元素和下标的值是否一致,或者其他实际要求
				right=mid-1;
			else left=mid+1;
                    //左右指针不断将不符合target的元素挤出去,同时定位插入位置
                    //为什么要取mid-1?实际上是为了让两侧指针每次都运动起来
                    //如果不取mid-1而是mid,随着算法进行,必然只剩left=n和right=n+1指向的两个元素,则left元素也就是mid元素
					//如果此时mid<target,那么执行left=mid,两个指针毫无变化,下一轮还是mid=left,将进入死循环
        }
        return left;//左指针及其左边的值<=target,是为“下界”,因此一定是最终答案,如果必定能查找成功,那么不需要这个返回值
       
    }
};

(点击下方标题即可进入leetcode对应题目页面,全部题解语言为C++,难度为简单)

268.丢失的数字

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        //记没有出现的数为missnum,则missnum左边的下标和元素值一致,右边的不一致
        //寻找缺失的数实际上就是寻找第一个对不上下标的元素
        //类比二分查找比大小,实际上这里相当于重载大小符号运算为是否对齐下标
        sort(nums.begin(),nums.end());//先排序
        int left=0,right=nums.size()-1;
        while(left<=right){
            int mid=((right-left)>>1)+left;
            if(nums[mid]!=mid)
                right=mid-1;
            else left=mid+1;
        }
        return left;//由于这里不存在“=”,只有“>”和“<”,因此左指针指向的必定是第一个对不上下标的位置
    }
};

69.x的平方根

解析:二分查找也不一定是线性二分,只要能将元素分为两个可以用映射表示的部分就可以,例如元素的一个单调递增或递减函数。本题的target实际上相当于mid^2函数。

class Solution {
public:
    int mySqrt(int x) {
        int left=0,right=x;
        while(left<=right){
            long mid=((right-left)>>1)+left;//mid*mid可能会溢出,因此建议使用长整形
            long sq=mid*mid;
            if(sq==x)
                return mid;
            else if (sq>x)
                right=mid-1;
                else left=mid+1;
        }
        return right;//要求值的平方不超过target,选择右指针
    }
};

441.排列硬币
解析:又是一个很好的例子,这里的大小符号重载为n和k(k+1)/2即1+2+3+。。。+k的比较。小于k级的阶梯都是可以完整排出来的,大于k的阶梯则不能完整甚至不能构成

public:
    int arrangeCoins(int n) {
        int left=1;
        int right=n;
        while(left<=right){
            long mid=((right-left)>>1)+left;
            long stairs=(mid*(mid+1))>>1;
            if(stairs==n)
                return mid;
            else if (stairs>n)//比较大小重载为和阶梯数比较大小
                right=mid-1;
            else left=mid+1;

        }
        return left-1;
    }
};

744. 寻找比目标字母大的最小字母
解析:目前我们遇到了二分查找的两个经典情境,包括存在target的情况下进行简单查找和不存在target的情况下确定上下界。本题则是存在target但要求查找上界,因此将查找成功的情形归入下界即可。

class Solution {
public:
    char nextGreatestLetter(vector<char>& letters, char target) {
        int left=0;
        int right=letters.size()-1;
        while(left<=right){
            int mid=((right-left)>>1)+left;
            if (letters[mid]<=target)
                left=mid+1;//即使查找到了target也没有结束,实际上相当于查找到了一个小于真正目标的数字
            else right=mid-1;
            cout<<right;
        }
        return letters[left%letters.size()];
    }
};

349.两个数组的交集

解析:最简单的方法是遍历一个数组,对其中每一个元素都查重一次,复杂度为O(mn); 更好的方法是采用哈希表存储,这样查重的复杂度下降到常数级,复杂度为O(m+n),即两个数组容量之和。

class Solution {
	public:
		vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
			unordered_set<int> set1, set2;
			for (auto& num : nums1) {//auto自动使用num来遍历nums1的元素
				set1.insert(num);//利用哈希表去除重复元素
			}
			for (auto& num : nums2) {
				set2.insert(num);
			}
			return getIntersection(set1, set2);
		}

		vector<int> getIntersection(unordered_set<int>& set1, unordered_set<int>& set2) {
			if (set1.size() > set2.size()) {
				return getIntersection(set2, set1);//用短表检验长表节省时间,尤其是当两表长度相差很大时
			}
			vector<int> intersection;
			for (auto& num : set1) {
				if (set2.count(num)) {
					intersection.push_back(num);
				}
			}
			return intersection;
		}
	};
		

另有一种方法是采用双指针,实质上类似于归并排序:

class Solution {
	public:
		vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
			sort(nums1.begin(), nums1.end());
			sort(nums2.begin(), nums2.end());
			int length1 = nums1.size(), length2 = nums2.size();
			int index1 = 0, index2 = 0;
			vector<int> intersection;
			while (index1 < length1 && index2 < length2) {
				int num1 = nums1[index1], num2 = nums2[index2];
				if (num1 == num2) {
					// 保证加入元素的唯一性,如果要求返回哈希表就无所谓了
					if (!intersection.size() || num1 != intersection.back()) {
						intersection.push_back(num1);
					}
					index1++;
					index2++;
				} else if (num1 < num2) {
					index1++;//如果num1较小,那么它就是当前两个有序数组中唯一的最小,必定不是重合元素
				} else {
					index2++;//同样原因
				}
			}
			return intersection;
		}
	};

350.两个数组的交集II
解析:本题要求的并不是确切的交集,而是展示交集数字的频率(取较小值)。可以使用哈希表统计一个数组的全部数字和频率,然后遍历另一个数组后更新交集数字的频率即可。

class Solution {
	public:
		vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
			if (nums1.size() > nums2.size()) {
				return intersect(nums2, nums1);
			}
			unordered_map <int, int> m;
			for (int num : nums1) {
				++m[num];//由于两个数组都很小,所以可以直接使用下标来统计
			}
			vector<int> intersection;
			for (int num : nums2) {
				if (m.count(num)) {
					intersection.push_back(num);//加入交集统计。
					--m[num];
					if (m[num] == 0) {
						m.erase(num);//如果元素在nums2中的数量较少,那么也是取最小值
					}
				}
			}
			return intersection;
		}
	};

240.搜索二维矩阵II
解析:
我们从初始矩阵开始,每次都检索其右上角元素UR=matrix[x,y],显然一开始的时候x=0,y=n
如果UR==target,则结束搜索
如果UR>target,那么显然这一列都不符合要求,将矩阵减去这一列(y- -)
如果UR<target,那么显然这一行也不符合要求,将矩阵减去这一行(x++)
最后必定收敛到一个元素,如果还不符合,则查找失败

class Solution {
	public:
		bool searchMatrix(vector<vector<int>>& matrix, int target) {
			int m = matrix.size(), n = matrix[0].size();
			int x = 0, y = n - 1;
			while (x < m && y >= 0) {
				if (matrix[x][y] == target) {
					return true;
				}
				if (matrix[x][y] > target) {
					--y;
				}
				else {
					++x;
				}
			}
			return false;
		}
	};

378.有序矩阵中第K小的元素
解析:
直接对矩阵降维排序就可以找到答案。
也可以考虑做插入排序然后再查找,但归并也是O(n^2)数量级。合并链表的方法有顺序合并、分治合并(采用双指针即可)、优先队列合并(类似于多路归并)。
二分查找的target不一定是一个元素,也可能是元素的一个单调函数,这样同样可以二分查找,如本题的target可以看做是比某元素更大、小的元素数量。
二分查找是本题的最优解,复杂度为线性。

class Solution {
	public:
		bool check(vector<vector<int>>& matrix, int mid, int k, int n) {
			int i = n - 1;
			int j = 0;
			int num = 0;
			while (i >= 0 && j < n) {
				if (matrix[i][j] <= mid) {
					num += i + 1;
					j++;
				} else {
					i--;
				}
			}
			return num >= k;
		}

		int kthSmallest(vector<vector<int>>& matrix, int k) {
			int n = matrix.size();
			int left = matrix[0][0];
			int right = matrix[n - 1][n - 1];
			while (left < right) {
				int mid = left + ((right - left) >> 1);
				if (check(matrix, mid, k, n)) {
					right = mid;
				} else {
					left = mid + 1;
				}
			}
			return left;
		}
	};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值