整理一下刷题过程中的思路,在这里进行一下总结与分享。
github地址:https://github.com/lvjian0706/Leetcode-solutions
github项目是刚刚新建的,陆续会将整理的代码以及思路上传上去,代码是基于C++与python的。同时会将基础的排序算法等也一并进行整理上传。
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))
你可以假设 nums1 和 nums2 不会同时为空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
class Solution {
public:
/*
寻找两个数组第k小的元素
1. 当nums1为空时,返回nums2中第k小的元素;当nums2为空时,返回nums1中第k小的元素;
2. 当k==1时,nums1[0]和nums2[0]中的最小值为返回值;
3.1 如果nums1[k/2-1]
double findKth(vector<int>& nums1, vector<int>& nums2, int len1, int len2, int k){
int left1 = 0, left2 = 0;
while(true){
if(left1==len1){
return nums2[left2+k-1];
}
if(left2==len2){
return nums1[left1+k-1];
}
if(k==1){
return min(nums1[left1], nums2[left2]);
}
int new_left1 = min(left1+k/2-1, len1-1);
int new_left2 = min(left2+k/2-1, len2-1);
if(nums1[new_left1] <= nums2[new_left2]){
k -= new_left1 - left1 + 1;
left1 = new_left1 + 1;
}
else{
k -= new_left2 - left2 + 1;
left2 = new_left2 + 1;
}
}
}
/*
寻找两个正序数组的中位数,即找到两个数组合并后第(nums1.size()+nums2.size())/2小的元素
*/
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
/*
k = (nums1.size()+nums2.size())/2;
当nums1.size()+nums2.size()为奇数时,返回第k+1小的元素;
当nums1.size()+nums2.size()为偶数时,返回第k小的元素和第k+1小的元素均值;
*/
int len1 = nums1.size();
int len2 = nums2.size();
int k = (len1+len2)/2;
if((len1+len2)%2==0) return (findKth(nums1, nums2, len1, len2, k) + findKth(nums1, nums2, len1, len2, k+1)) / 2;
else return findKth(nums1, nums2, len1, len2, k+1);
}
};
假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
class Solution {
public:
/*
有序数组查找目标值:二分查找
1. 需要将nums[mid]与nums[left]进行比较,判断旋转区间
2. 根据旋转区间范围以及target与nums[mid]的比较共同查找目标值
*/
int search(vector<int>& nums, int target) {
int left=0, right=nums.size()-1;
while(left<=right){
int mid = left + (right - left) / 2;
if(nums[mid]==target) return mid;
/*
只有一个元素且不等于target时,返回-1;
*/
else if(left==right){
return -1;
}
/*
nums[mid]大于nums[left]时,数组最小值在nums[mid]右边:
1. nums[mid]大于target时,需要根据target与nums[left]大小判断target所在区间
1.1 target大于nums[left]时,target位于nums[left:mid-1]
1.2 target小于nums[left]时,target位于nums[mid+1:]
2. nums[mid]小于target时,target位于nums[mid+1:]
*/
if(nums[mid]>nums[left]){
if(nums[mid]>target){
if(target>nums[left]) right = mid-1;
else if(target<nums[left]) left = mid+1;
else return left;
}
else if(nums[mid]<target){
left = mid+1;
}
}
/*
nums[mid]小于nums[left]时,数组最小值在nums[mid]左边:
1. nums[mid]小于target时,需要根据target与nums[left]大小判断target所在区间
1.1 target大于nums[left]时,target位于nums[left:mid-1]
1.2 target小于nums[left]时,target位于nums[mid+1:]
2. nums[mid]大于target时,target位于nums[left:mid-1]
*/
else if(nums[mid]<nums[left]){
if(nums[mid]<target){
if(target>nums[left]) right = mid-1;
else if(target<nums[left]) left = mid+1;
else return left;
}
else if(nums[mid]>target){
right = mid-1;
}
}
/*
nums[mid]等于nums[0]时,数组只有两个元素:
1. nums[mid]不等于target并且nums[mid+1]不等于target时时,没有目标值
2. 否则返回索引值;
*/
else{
if(nums[mid]!=target){
if(nums[mid+1]==target) return mid+1;
else return -1;
}
else return mid;
}
}
/*
如果没有找到,返回-1;
*/
return -1;
}
};
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
class Solution {
public:
/*
查找数组中元素的起始位置即左端点
1. 当nums[mid]==target时,需要根据nums[mid-1]判断是否为左端点
1.1 nums[mid-1]
int searchLeft(vector<int>& nums, int target){
int left=0, right=nums.size()-1;
while(right>=left){
int mid = left + (right - left) / 2;
if(nums[mid]==target){
if(mid==0 || nums[mid-1]<target) return mid;
else if(nums[mid-1]==target) right = mid - 1;
}
else if(nums[mid]>target) right = mid - 1;
else if(nums[mid]<target) left = mid + 1;
}
/*
没有找到target时,返回-1;
*/
return -1;
}
/*
查找数组中元素的起始位置即左端点
1. 当nums[mid]==target时,需要根据nums[mid+1]判断是否为右端点
1.1 nums[mid+1]>target,是右端点,返回索引值
1.2 mid==nums.size()-1, 位于数组边界,是右端点,返回len(nums-1)
1.3 nums[mid-1]==target, 不是左端点,将left更新为mid+1继续判断
2. 当nums[mid]不等于target时,缩小搜索区间继续查找
*/
int searchRight(vector<int>& nums, int target){
int left=0, right=nums.size()-1;
while(right>=left){
int mid = left + (right - left) / 2;
if(nums[mid]==target){
if(mid==nums.size()-1 || nums[mid+1]>target) return mid;
else if(nums[mid+1]==target) left = mid + 1;
}
else if(nums[mid]>target) right = mid - 1;
else if(nums[mid]<target) left = mid + 1;
}
/*
没有找到target时,返回-1;
*/
return -1;
}
/*
有序数组查找元素:二分查找
数组中存在重复数字,因此需要对要查找元素的左端点和右端点进行分别处理;
*/
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> ans;
int left = searchLeft(nums, target);
int right = searchRight(nums, target);
ans.push_back(left);
ans.push_back(right);
return ans;
}
};
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5
输出: 2
示例 2:
输入: [1,3,5,6], 2
输出: 1
示例 3:
输入: [1,3,5,6], 7
输出: 4
示例 4:
输入: [1,3,5,6], 0
输出: 0
class Solution {
public:
/*
有序数组查找目标值:二分查找
1. nums[mid]==target时,返回索引值
2. nums[mid]>target时,需要根据nums[mid-1]的值与target的比较结果进行判定:
2.1 nums[mid-1]>=target时,目标值在mid左侧,right更新为mid-1;
2.2 nums[mid-1]target时,目标值应该插入在mid-1和mid中间,返回mid+1;
3.2 nums[mid+1]<=target时,目标值在mid右侧,left更新为mid+1;
3.3 考虑越界情况,mid==nums.size()-1时,目标值大于数组最后一个元素,应该插入到nums[nums.size()]处;
*/
int searchInsert(vector<int>& nums, int target) {
int left = 0, right = nums.size()-1;
while(left<=right){
int mid = left + (right - left) / 2;
if(nums[mid]==target){
return mid;
}
else if(nums[mid]>target){
if(mid==0 || nums[mid-1]<target) return mid;
else if(nums[mid-1]>=target) right = mid - 1;
}
else if(nums[mid]<target){
if(mid==nums.size()-1 || nums[mid+1]>target) return mid+1;
else if(nums[mid+1]<=target) left = mid + 1;
}
}
return -1;
}
};
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
示例 1:
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 3
输出: true
示例 2:
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 13
输出: false
//方法1
class Solution {
public:
/*
在第一列中查找目标值应该属于哪一行:在一维数组中查找目标值,有则返回索引,没有返回小于且最接近目标值的索引;
1. 如果vec[mid]==target, 返回mid;
2. 如果vec[mid]target,返回mid;
2.3 如果mid==vec.size()-1,说明在右边界,返回vec.size()-1;
3. 如果vec[mid]>target:
2.1 如果vec[mid-1]=target,在mid左边继续查找;
3.3 如果mid==0,说明在左边界,返回0;
*/
int searchCol(vector<int> vec, int target){
int left = 0, right = vec.size()-1;
while(left<=right){
int mid = left + (right - left) / 2;
if(vec[mid]==target) return mid;
else if(vec[mid]<target){
if(mid==vec.size()-1) return mid;
else if(vec[mid+1]<=target) left = mid + 1;
else return mid;
}
else if(vec[mid]>target){
if(mid==0) return mid;
else if(vec[mid-1]>=target) right = mid - 1;
else return mid-1;
}
}
return -1;
}
/*
在对应行中查找目标值:在一维数组中查找目标值,有则返回索引,没有返回-1;
*/
int searchTarget(vector<int> vec, int target){
int left = 0, right = vec.size()-1;
while(left<=right){
int mid = left + (right - left) / 2;
if(vec[mid]==target) return mid;
else if(vec[mid]<target) left = mid + 1;
else if(vec[mid]>target) right = mid - 1;
}
return -1;
}
/*
有序数组搜索目标值:二分查找
由于是二维数组,所以需要在两个维度进行搜索,由于每行都是升序,且下一行的第一个数大于该行最后一个数,因此可以将问题转化为:
1. 搜索第一列,判断目标值存在的情况下,应该属于哪一行(即查找目标值或小于且最接近目标值的值);
2. 找到目标值所在行后,简化问题为在升序的一维数组中查找目标值,使用标准二分查找方法即可;
*/
bool searchMatrix(vector<vector<int>>& matrix, int target) {
/*
如果数组为空,则返回false;
*/
if(matrix.size()==0 || matrix[0].size()==0) return false;
/*
定义vector存放第一列元素
*/
vector<int> firstcol;
int m = matrix.size();
for(int i=0; i<m; i++){
firstcol.push_back(matrix[i][0]);
}
/*
找到对应行
*/
int col = searchCol(firstcol, target);
cout<<col<<endl;
/*
在该行中找target
*/
int row = searchTarget(matrix[col], target);
cout<<row<<endl;
return true ? row!=-1 : false;
}
};
//方法2
class Solution {
public:
/*
将二维数组拉伸为一位数组进行搜索,其中,一位数组中matrix[mid]在二维数组中的位置为matrix[mid/row][mid%row]
*/
bool searchMatrix(vector<vector<int>>& matrix, int target) {
if(matrix.size()==0 || matrix[0].size()==0) return false;
int col = matrix.size(), row = matrix[0].size();
int left = 0, right = col * row - 1;
while(right>=left){
int mid = left + (right - left) / 2;
if(matrix[mid/row][mid%row]==target) return true;
else if(matrix[mid/row][mid%row]<target) left = mid + 1;
else if(matrix[mid/row][mid%row]>target) right = mid - 1;
}
return false;
}
};
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。
编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。
示例 1:
输入: nums = [2,5,6,0,0,1,2], target = 0
输出: true
示例 2:
输入: nums = [2,5,6,0,0,1,2], target = 3
输出: false
class Solution {
public:
/*
旋转升序数组中查找目标值:二分查找;遇到重复元素数组,可以在循环过程中先去掉两端重复元素,方便判断
1. 当数组为空时,返回false;
2. 为了方便判断,循环过程中先将两端的重复元素去掉
2.1 当nums[left]==nums[left+1]时,left++;
2.2 当nums[right]==nums[right--]时,right--;
3. 当只有一个元素时,比较是否一致即可;
4. nums[mid]==target,返回true;
5. nums[mid]nums[left],说明从左到右依次为nums[left],nums[mid],target,left=mid+1;
5.2 nums[mid]nums[left]时, 说明从左到右依次为nums[left],target,nums[mid],right=mid-1;
5.2.2 targettarget时,结合nums[mid],target,nums[left]三者关系共同判断:
6.1 nums[mid]nums[left]时:
6.2.1 target>nums[left]时, 说明从左到右依次为nums[left],target,nums[mid],right=mid-1;
6.2.2 target
bool search(vector<int>& nums, int target) {
if(nums.size()==0) return false;
int left=0, right=nums.size()-1;
while(left<=right){
while(left+1<=right && nums[left]==nums[left+1]) left++;
while(right-1>=left && nums[right]==nums[right-1]) right--;
if(left==right) return nums[left]==target;
int mid = left + (right - left) / 2;
if(nums[mid]==target) return true;
else if(nums[mid]<target){
if(nums[mid]==nums[left]) return nums[mid+1]==target;
else if(nums[mid]>nums[left]) left = mid + 1;
else if(nums[mid]<nums[left]){
if(target>nums[left]) right=mid-1;
else if(target<nums[left]) left=mid+1;
else if(target==nums[left]) return true;
}
}
else if(nums[mid]>target){
if(nums[mid]==nums[left]) return nums[mid+1]==target;
else if(nums[mid]<nums[left]) right=mid-1;
else if(nums[mid]>nums[left]){
if(target>nums[left]) right=mid-1;
else if(target<nums[left]) left=mid+1;
else if(target==nums[left]) return true;
}
}
}
return false;
}
};
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
你可以假设数组中不存在重复元素。
示例 1:
输入: [3,4,5,1,2]
输出: 1
示例 2:
输入: [4,5,6,7,0,1,2]
输出: 0
class Solution {
public:
/*
查找旋转数组中最小值:由于原始数组是升序数组,所以使用二分查找,找原始数组中起始点
原始数组最小点应该小于左右两侧元素,具体的最小点应该小于左侧元素
1. 当nums[0]nums[mid+1],第一个元素为最大值,返回nums[1];
3. nums[mid]nums[mid-1],需要判断当前点在最小点左边还是右边,可以结合与nums[0]的关系进行判断:
nums[mid]>nums[0],由于数组旋转过,所以最小点左边是升序数组,且最小值右边恒小于左边,因此当前元素位于最小点左边,left=mid+1;否则,right=mid-1;
*/
int findMin(vector<int>& nums) {
/*
只有一个元素时,返回该元素
*/
if(nums.size()==1) return nums[0];
int left = 0, right = nums.size()-1;
while(left<=right){
int mid = left + (right - left) / 2;
if(nums[0]<nums[nums.size()-1]) return nums[0];
else if(mid==0 && nums[mid]>nums[mid+1]) return nums[1];
else if(nums[mid] < nums[mid-1]) return nums[mid];
else if(nums[mid] > nums[mid-1]){
if(nums[mid] > nums[0]) left = mid + 1;
else if(nums[mid] < nums[0]) right = mid - 1;
}
}
return -1;
}
};
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
注意数组中可能存在重复的元素。
示例 1:
输入: [1,3,5]
输出: 1
示例 2:
输入: [2,2,2,0,1]
输出: 0
class Solution {
public:
/*
寻找旋转的升序数组最小值:二分查找,数组中的最小值恒小于左右两侧的第一个不同元素;
在本题中,适合使用nums[mid]与nums[right]的值进行判断;且需要将数组两端重复元素去重;
1. nums中没有元素返回-1;nums中只有一个元素,直接返回;
2. 如果nums[0]=nums[mid-1],还在升序区间,且最小值在nums[mid]左侧,right=mid-1;
5.2 nums[mid]nums[right],最小值在nums[mid]右侧,left=mid+1;
*/
int findMin(vector<int>& nums) {
if(nums.size()==1) return nums[0];
if(nums[0]<nums[nums.size()-1]) return nums[0];
int left=0, right=nums.size()-1;
while(left<=right){
while(left+1<=right && nums[left]==nums[left+1]){
left++;
}
while(right-1>=left && nums[right]==nums[right-1]){
right--;
}
if(left==right) return nums[left];
int mid = left + (right - left) / 2;
if(nums[mid]<nums[right]){
if(nums[mid]>=nums[mid-1]) right = mid - 1;
else if(nums[mid]<nums[mid-1]) return nums[mid];
}
else if(nums[mid]>nums[right]) left = mid + 1;
}
return -1;
}
};
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
示例:
给定 n = 5,并且 version = 4 是第一个错误的版本。
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。
// The API isBadVersion is defined for you.
// bool isBadVersion(int version);
class Solution {
public:
/*
由于错误版本之后的所有版本都是错的,因此,需要找到第一个isBadVersion(mid)==false的版本:
1. isBadVersion(mid)==true,说明当前版本或当前版本之前的版本出现错误,需要根据上一个版本的情况进行判断
1.1 isBadVersion(mid-1)==true,说明当前版本之前的版本出现错误,right=mid-1;
1.2 isBadVersion(mid-1)==false,说明当前版本之前的版本都是正确的,当前版本为第一个出错的版本,返回mid;
1.3 mid==1时,为边界情况,说明从第一个版本就出错了;
2. isBadVersion(mid)==false,说明当前版本没有问题,left=mid+1;
*/
int firstBadVersion(int n) {
int left = 1, right = n;
while(left<=right){
int mid = left + (right - left) / 2;
if(isBadVersion(mid)==false) left = mid + 1;
else if(isBadVersion(mid)==true){
if(mid==1 || isBadVersion(mid-1)==false) return mid;
else if(isBadVersion(mid-1)==true) right = mid - 1;
}
}
return 0;
}
};