代码均为c++代码
x 的平方根
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4
输出: 2
示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842...,
由于返回类型是整数,小数部分将被舍去。
class Solution {
public:
int mySqrt(int x) {
long int low=0,high=x;
while(low<=high)
{
long int mid=low+(high-low)/2;
if(mid*mid==x)
return mid;
else if(mid*mid<x)
{
low=mid+1;
}
else
high=mid-1;
}
long int tmp=high*high;
if(tmp<x)
return high;
else
return high-1;
}
};
猜数字大小
我们正在玩一个猜数字游戏。 游戏规则如下:
我从 1 到 n 选择一个数字。 你需要猜我选择了哪个数字。
每次你猜错了,我会告诉你这个数字是大了还是小了。
你调用一个预先定义好的接口 guess(int num),它会返回 3 个可能的结果(-1,1 或 0):
-1 : 我的数字比较小
1 : 我的数字比较大
0 : 恭喜!你猜对了!
示例 :
输入: n = 10, pick = 6
输出: 6
// Forward declaration of guess API.
// @param num, your guess
// @return -1 if my number is lower, 1 if my number is higher, otherwise return 0
int guess(int num);
class Solution {
public:
int guessNumber(int n) {
int low=0,high=n;
while(low<=high)
{
int mid=low+(high-low)/2;
if(guess(mid)==0)
return mid;
else if(guess(mid)==-1)
high=mid-1;
else if(guess(mid)==1)
low=mid+1;
}
return -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
c++代码:
思路:
例如对于数组[0 1 2 4 5 6 7] 共有下列七种旋转方法:
0 1 2 4 5 6 7
7 0 1 2 4 5 6
6 7 0 1 2 4 5
5 6 7 0 1 2 4
4 5 6 7 0 1 2
2 4 5 6 7 0 1
1 2 4 5 6 7 0
二分搜索法的关键在于获得了中间数后,判断下面要搜索左半段还是右半段,我们观察上面加粗的数字都是升序的,由此我们可以观察出规律,如果中间的数小于最右边的数,则右半段是有序的,若中间数大于最右边数,则左半段是有序的,我们只要在有序的半段里用首尾两个数组来判断目标值是否在这一区域内,这样就可以确定保留哪半边了
class Solution {
public:
int search(vector<int>& nums, int target) {
int low=0,high=nums.size()-1;
while(low<=high)
{
int mid=low+(high-low)/2;
if(nums[mid]==target)
return mid;
else if(nums[mid]>nums[high])//说明左半部分是有序的
{
if(nums[low]<=target&&nums[mid]>target)//说明在左半部分
high=mid-1;
else
low=mid+1;
}
else//说明右半部分有序
{
if(nums[mid]<target&&nums[high]>=target)//说明在右半部分
low=mid+1;
else
high=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 是第一个错误的版本。
// Forward declaration of isBadVersion API.
bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
int low=1;
int high=n;
while(low<=high)
{
int mid=low+((high-low)>>1);
if(isBadVersion(mid)==false)
low=mid+1;
else
{
if(isBadVersion(mid-1)==false||mid==1)
return mid;
else
high=mid-1;
}
}
}
};
寻找峰值
峰值元素是指其值大于左右相邻值的元素。
给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。
数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞。
示例 1:
输入: nums = [1,2,3,1]
输出: 2
解释: 3 是峰值元素,你的函数应该返回其索引 2。
示例 2:
输入: nums = [1,2,1,3,5,6,4]
输出: 1 或 5
解释: 你的函数可以返回索引 1,其峰值元素为 2;
或者返回索引 5, 其峰值元素为 6。
说明:
你的解法应该是 O(logN) 时间复杂度的。
class Solution {
public:
int findPeakElement(vector<int>& nums) {
if(nums.size()==1)
return 0;
int n=nums.size()-1;
int low=0,high=n;
while(low<=high)
{
int mid=low+(high-low)/2;
if(nums[mid]>(mid+1>n?INT_MIN:nums[mid+1])&&nums[mid]>(mid-1<0?INT_MIN:nums[mid-1]))
return mid;
else if(nums[mid]<(mid+1>n?INT_MIN:nums[mid+1]))
low=mid+1;
else
high=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:
vector<int> searchRange(vector<int>& nums, int target) {
int n=nums.size();
int low=0,high=n-1;
vector<int> res;
int flag=0;
//先找左边界
while(low<=high)
{
int mid=low+(high-low)/2;
if(nums[mid]<target)
low=mid+1;
else if (nums[mid]>target)
high=mid-1;
else
{
if(mid==0||nums[mid-1]!=target)
{
flag=1;
res.push_back(mid);
break;
}
else
high=mid-1;
}
}
if(flag==0)
{
res.push_back(-1);
res.push_back(-1);
}
low=0,high=n-1;
//再找右边界
while(low<=high)
{
int mid=low+(high-low)/2;
if(nums[mid]<target)
low=mid+1;
else if (nums[mid]>target)
high=mid-1;
else
{
if(mid==n-1||nums[mid+1]!=target)
{
res.push_back(mid);
break;
}
else
low=mid+1;
}
}
return res;
}
};
找到 K 个最接近的元素
给定一个排序好的数组,两个整数 k 和 x,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。如果有两个数与 x 的差值一样,优先选择数值较小的那个数。
示例 1:
输入: [1,2,3,4,5], k=4, x=3
输出: [1,2,3,4]
示例 2:
输入: [1,2,3,4,5], k=4, x=-1
输出: [1,2,3,4]
说明:
k 的值为正数,且总是小于给定排序数组的长度。
数组不为空,且长度不超过 10^4
数组里的每个元素与 x 的绝对值不超过 10^4
思路:
实际上相当于在长度为n的数组中去掉n-k个数字,而且去掉的顺序肯定是从两头开始去,因为距离x最远的数字肯定在首尾出现。每次比较首尾两个数字跟x的距离,将距离大的那个数字删除,直到剩余的数组长度为k为止
class Solution {
public:
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
vector<int> res=arr;
while(res.size()>k)
{
if(x-res.front()<=res.back()-x)
res.pop_back();
else
res.erase(res.begin());
}
return res;
}
};
Pow(x, n)
实现 pow(x, n) ,即计算 x 的 n 次幂函数。
示例 1:
输入: 2.00000, 10
输出: 1024.00000
示例 2:
输入: 2.10000, 3
输出: 9.26100
示例 3:
输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25
说明:
-100.0 < x < 100.0
n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。
class Solution {
public:
double myPow(double x, int n) {
if(n<0)
return 1.0/calpow(x,-n);
else
return calpow(x,n);
}
double calpow(double x,int n)
{
if(n==0)
return 1;
double half=calpow(x,n/2);
if(n%2==0)
return half*half;
else
return x*half*half;
}
};
有效的完全平方数
给定一个正整数 num,编写一个函数,如果 num 是一个完全平方数,则返回 True,否则返回 False。
说明:不要使用任何内置的库函数,如 sqrt。
示例 1:
输入:16
输出:True
示例 2:
输入:14
输出:False
class Solution {
public:
bool isPerfectSquare(int num) {
if(num==1)
return true;
long int low=1,high=num/2;
while(low<=high)
{
long int mid=low+(high-low)/2;
if(mid*mid==num)
return true;
else if(mid*mid>num)
high=mid-1;
else
low=mid+1;
}
return false;
}
};
寻找比目标字母大的最小字母
给定一个只包含小写字母的有序数组letters 和一个目标字母 target,寻找有序数组里面比目标字母大的最小字母。
数组里字母的顺序是循环的。举个例子,如果目标字母target = ‘z’ 并且有序数组为 letters = [‘a’, ‘b’],则答案返回 ‘a’。
示例:
输入:
letters = [“c”, “f”, “j”]
target = “a”
输出: “c”
输入:
letters = [“c”, “f”, “j”]
target = “c”
输出: “f”
输入:
letters = [“c”, “f”, “j”]
target = “d”
输出: “f”
输入:
letters = [“c”, “f”, “j”]
target = “g”
输出: “j”
输入:
letters = [“c”, “f”, “j”]
target = “j”
输出: “c”
输入:
letters = [“c”, “f”, “j”]
target = “k”
输出: “c”
注:
letters长度范围在[2, 10000]区间内。
letters 仅由小写字母组成,最少包含两个不同的字母。
目标字母target 是一个小写字母。
class Solution {
public:
char nextGreatestLetter(vector<char>& letters, char target) {
int n=letters.size();
int low=0,high=n-1;
while(low<=high)
{
int mid=low+(high-low)/2;
if(letters[mid]==target)
{
if(mid+1<=n-1&&letters[mid+1]>target)
return letters[mid+1];
else if(mid+1>n-1)
return letters[0];
else
low=mid+1;
}
else if(letters[mid]<target)
low=mid+1;
else
{
if(mid==0||letters[mid-1]<target)
return letters[mid];
else
high=mid-1;
}
}
return letters[0];
}
};
寻找旋转排序数组中的最小值
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [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:
int findMin(vector<int>& nums) {
int low=0,high=nums.size()-1;
while(low<high)
{
int mid=low+(high-low)/2;
if(nums[mid]>nums[high])//说明最小值在右半部分
low=mid+1;
else
high=mid;
}
return nums[high];
}
};
寻找旋转排序数组中的最小值 II
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [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:
int findMin(vector<int>& nums) {
int n=nums.size();
int low=0,high=n-1;
if(nums[low]<nums[high])
return nums[low];
while(low<=high)
{
int mid=low+(high-low)/2;
if(nums[mid]>nums[high])
{
low=mid+1;
}
else if(nums[mid]<nums[high])
high=mid;
else
high--;
}
return nums[low];
}
};
两个数组的交集
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2]
示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [9,4]
说明:
输出结果中的每个元素一定是唯一的。
我们可以不考虑输出结果的顺序。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
if(nums1.empty()||nums2.empty())
return {};
sort(nums1.begin(),nums1.end());
sort(nums2.begin(),nums2.end());
vector<int> res={};
int n1=nums1.size(),n2=nums2.size();
int i=0,j=0;
while(i<n1&&j<n2)
{
if(nums1[i]<nums2[j])
i++;
else if(nums1[i]>nums2[j])
j++;
else
{
res.push_back(nums1[i]);
i++;
j++;
}
}
if(res.empty())
return {};
res.erase(unique(res.begin(), res.end()), res.end());
return res;
}
};
两个数组的交集 II
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]
示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]
说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
我们可以不考虑输出结果的顺序。
进阶:
如果给定的数组已经排好序呢?你将如何优化你的算法?
如果 nums1 的大小比 nums2 小很多,哪种方法更优?
如果 nums2 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
int n1=nums1.size();
int n2=nums2.size();
vector<int> result;
sort(nums1.begin(),nums1.end());
sort(nums2.begin(),nums2.end());
for(int i=0,j=0;i<n1&&j<n2;)
{
if(nums1[i]<nums2[j])
i++;
else if(nums1[i]>nums2[j])
j++;
else
{
result.push_back(nums1[i]);
i++;
j++;
}
}
return result;
}
};
两数之和 II - 输入有序数组
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int i=0,j=numbers.size()-1;
vector<int> res;
while(i!=j)
{
if(numbers[i]+numbers[j]==target)
{
res.push_back(i+1);
res.push_back(j+1);
break;
}
else if(numbers[i]+numbers[j]<target)
i++;
else
j--;
}
return res;
}
};
寻找重复数
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例 1:
输入: [1,3,4,2,2]
输出: 2
示例 2:
输入: [3,1,3,4,2]
输出: 3
说明:
不能更改原数组(假设数组是只读的)。
只能使用额外的 O(1) 的空间。
时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n=nums.size()-1;//4
int *res=new int[n+1]{0};
for(int i=0;i<=n;i++)
{
res[nums[i]]++;
}
for(int i=1;i<=n;i++)
{
if(res[i]>1)
return i;
}
}
};
寻找两个有序数组的中位数
给定两个大小为 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
c++代码:
方法一:
把两个数组按大小组合成一个有序数组,再查找这个数组的中位数,时间复杂度O(m+n)
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
if(nums1.empty()&&nums2.empty())
return -1;
else if(nums1.empty())
{
int n=nums2.size();
if(n%2==0)
return double(nums2[n/2]+nums2[n/2-1])/2;
else
return nums2[n/2];
}
else if(nums2.empty())
{
int n=nums1.size();
if(n%2==0)
return double(nums1[n/2]+nums1[n/2-1])/2;
else
return nums1[n/2];
}
vector<int> res;
int n1=nums1.size()-1,n2=nums2.size()-1;
int i=0,j=0;
while(i<=n1&&j<=n2)
{
if(nums1[i]<=nums2[j])
{
res.push_back(nums1[i]);
i++;
}
else
{
res.push_back(nums2[j]);
j++;
}
}
while(i<=n1)
{
res.push_back(nums1[i]);
i++;
}
while(j<=n2)
{
res.push_back(nums2[j]);
j++;
}
int index=res.size();
if(index%2==0)
return double(res[index/2]+res[index/2-1])/2;
else
return res[index/2];
}
};
方法二:
采用二分查找,时间复杂度O(log(m+n))
官方有解答寻找两个有序数组的中位数
找出第 k 小的距离对
给定一个整数数组,返回所有数对之间的第 k 个最小距离。一对 (A, B) 的距离被定义为 A 和 B 之间的绝对差值。
示例 1:
输入:
nums = [1,3,1]
k = 1
输出:0
解释:
所有数对如下:
(1,3) -> 2
(1,1) -> 0
(3,1) -> 2
因此第 1 个最小距离的数对是 (1,1),它们之间的距离为 0。
提示:
2 <= len(nums) <= 10000.
0 <= nums[i] < 1000000.
1 <= k <= len(nums) * (len(nums) - 1) / 2.
####c++代码
方法一
暴力解法,求出每一对距离
class Solution {
public:
int smallestDistancePair(vector<int>& nums, int k) {
if(nums.empty())
return 0;
int n=nums.size();
vector<int> count(1000000,0);
for(int i=0;i<n;i++)
{
for(int j=i+1;j<n;j++)
{
count[abs(nums[i]-nums[j])]++;
}
}
for (int i = 0; i < count.size(); i++)
{
if (count[i] >= k)
return i;
else
k -= count[i];
}
}
};
方法二
首先对nums进行排序,这样就可以得到distance的最小值left和最大值right了。然后二分查找:对于一个介于low和high之间的数mid,我们统计差值小于mid的一共有多少个,如果小于k,那么说明说明mid的取值偏小,所以修改low的值;否则修改high的值。这样不断迭代,最终当low > high的时候,low即为所求。
class Solution {
public:
int smallestDistancePair(vector<int>& nums, int k) {
sort(nums.begin(), nums.end());
int n = nums.size(), low = 0, high = nums.back() - nums[0];
while (low <= high) {
int mid = (low + high) / 2, cnt = 0, j = 0;
for (int i = 0; i < n; ++i) {
//小于mid的个数
while (j < n && nums[j] - nums[i] <= mid) {
++j;
}
cnt += j - i - 1;
}
if (cnt < k)
low = mid + 1;
else
high = mid - 1;
}
return low;
}
};
分割数组的最大值
给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。
注意:
数组长度 n 满足以下条件:
1 ≤ n ≤ 1000
1 ≤ m ≤ min(50, n)
示例:
输入:
nums = [7,2,5,10,8]
m = 2
输出:
18
解释:
一共有四种方法将nums分割为2个子数组。
其中最好的方式是将其分为[7,2,5] 和 [10,8],
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。
思路:
如果m和数组nums的个数相等,那么每个数组都是一个子数组,所以返回nums中最大的数字即可。
如果m为1,那么整个nums数组就是一个子数组,返回nums所有数字之和,所以对于其他有效的m值,返回的值必定在上面两个值之间,所以我们可以用二分搜索法来做。
我们用一个例子来分析,nums = [1, 2, 3, 4, 5], m = 3,我们将left设为数组中的最大值5,right设为数字之和15,然后我们算出中间数为10,我们接下来要做的是找出和最大且小于等于10的子数组的个数,[1, 2, 3, 4], [5],可以看到我们无法分为3组,说明mid偏大,所以我们让right=mid,然后我们再次进行二分查找哦啊,算出mid=7,再次找出和最大且小于等于7的子数组的个数,[1,2,3], [4], [5],我们成功的找出了三组,说明mid还可以进一步降低,我们让right=mid,然后我们再次进行二分查找哦啊,算出mid=6,再次找出和最大且小于等于6的子数组的个数,[1,2,3], [4], [5],我们成功的找出了三组,我们尝试着继续降低mid,我们让right=mid,然后我们再次进行二分查找哦啊,算出mid=5,再次找出和最大且小于等于5的子数组的个数,[1,2], [3], [4], [5],发现有4组,此时我们的mid太小了,应该增大mid,我们让left=mid+1,此时left=6,right=5,循环退出了,我们返回left即可
class Solution {
public:
int splitArray(vector<int>& nums, int m) {
int left=0,right=0;
for(int i=0;i<nums.size();i++)
{
left=max(left,nums[i]);//数组中的最大值
right=right+nums[i];//数组的和
}
while(left<right)
{
int mid=left+(right-left)/2;
if(cansplit(nums,m,mid))
right=mid;
else
left=mid+1;
}
return left;
}
//找出和最大且小于等于sum的子数组的个数
bool cansplit(vector<int>& nums,int m,int sum)
{
int c=1;//能分的组数
int cursum=0;
for(int i=0;i<nums.size();i++)
{
cursum+=nums[i];
if(cursum>sum)
{
cursum=nums[i];//超过了就把它放到下一组
c++;
if(c>m)//超过m组
return false;
}
}
return true;
}
};