剑指 Offer(第2版)面试题 53:在排序数组中查找数字
剑指 Offer(第2版)面试题 53:在排序数组中查找数字
题目一:数字在排序数组中出现的次数
题目来源:67. 数字在排序数组中出现的次数
题目描述:统计一个数字在排序数组中出现的次数。
思路 1:顺序遍历,统计出现次数
代码:
class Solution {
public:
int getNumberOfK(vector<int>& nums , int k) {
int count = 0;
for(int &num:nums)
if(num == k)
count++;
return count;
}
};
复杂度分析:
时间复杂度:O(n),其中 n 是数组 nums 的长度。
空间复杂度:O(1)。
思路 2:二分查找
代码:
class Solution {
public:
int getNumberOfK(vector<int>& nums , int k) {
return upper_bound(nums.begin(), nums.end(), k) - lower_bound(nums.begin(), nums.end(), k);
}
};
自己实现 upper_bound 和 lower_bound:
class Solution
{
public:
int getNumberOfK(vector<int> &nums, int k)
{
if (nums.empty())
return 0;
int firstIdx = getFirstTargetIndex(nums, k), lastIdx = getLastTargetIndex(nums, k);
if (firstIdx != -1 && lastIdx != -1)
return lastIdx - firstIdx + 1;
return 0;
}
// 辅函数 - 得到数组 nums 中第一个 target 的下标
int getFirstTargetIndex(vector<int> &nums, int target)
{
if (nums.empty())
return -1;
int left = 0, right = nums.size() - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (nums[mid] == target)
{
if ((mid > 0 && nums[mid - 1] != target) || mid == 0)
return mid;
else
right = mid - 1;
}
else if (nums[mid] > target)
right = mid - 1;
else
left = mid + 1;
}
return -1;
}
// 辅函数 - 得到数组 nums 中最后一个 target 的下标
int getLastTargetIndex(vector<int> &nums, int target)
{
if (nums.empty())
return -1;
int left = 0, right = nums.size() - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (nums[mid] == target)
{
if ((mid < nums.size() - 1 && nums[mid + 1] != target) || mid == nums.size() - 1)
return mid;
else
left = mid + 1;
}
else if (nums[mid] > target)
right = mid - 1;
else
left = mid + 1;
}
return -1;
}
};
书上的 upper_bound 和 lower_bound 是递归实现的,也可以。
复杂度分析:
时间复杂度:O(logn),其中 n 是数组 nums 的长度。
空间复杂度:O(1)。
题目二:0 到 n-1 中缺失的数字
题目链接:68. 0 到 n-1 中缺失的数字
题目描述:一个长度为 n−1 的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围 0 到 n−1 之内。在范围 0 到 n−1 的 n 个数字中有且只有一个数字不在该数组中,请找出这个数字。
思路1:求和
0 到 n−1 的所有数字之和为 n(n-1)/2,数组 nums 的和为 accumulate(nums.begin(), nums.end(), 0),两者之差就是缺失的数字。
代码:
class Solution
{
public:
int getMissingNumber(vector<int> &nums)
{
int n = nums.size();
return n * (n + 1) / 2 - accumulate(nums.begin(), nums.end(), 0);
}
};
复杂度分析:
时间复杂度:O(n),其中 n 是数组 nums 的长度。
空间复杂度:O(1)。
思路2:异或
异或的性质:
- a ^ a = 0
- 0 ^ a = a
- a ^ a ^ b = b
我们可以将数组下标异或数组元素,最后再异或数组长度即可得到缺失的数字。
如果没缺数字,异或的最终值即数组的长度。
代码:
class Solution
{
public:
int getMissingNumber(vector<int> &nums)
{
if (nums.empty())
return 0;
int ans = 0;
for (int i = 0; i < nums.size(); i++)
ans ^= (i ^ nums[i]);
return ans ^ nums.size();
}
};
复杂度分析:
时间复杂度:O(n),其中 n 是数组 nums 的长度。
空间复杂度:O(1)。
思路3:二分查找
这道题目给定的是递增数组,假设数组中第一个缺失的数是 x,那么数组中的数如下所示:
缺失的数 x 之前的数是与数组下标一一对应的,而之后的数(包括 x 自己)不与下标一一对应。即数组左边蓝色部分都满足 nums[i] == i
,数组右边橙色部分都不满足 nums[i] == i
,因此我们可以二分出分界点 x 的值。
特殊情况:当所有数都满足 nums[i] == i
时,表示缺失的是 n。
代码:
class Solution
{
public:
int getMissingNumber(vector<int> &nums)
{
if (nums.empty())
return 0;
int n = nums.size();
int left = 0, right = n - 1;
while (left < right)
{
int mid = (left + right) / 2;
if(nums[mid] != mid)
right = mid;
else
left = mid + 1;
}
return nums[left] == left ? n : left;
}
};
复杂度分析:
时间复杂度:O(logn),其中 n 是数组 nums 的长度。
空间复杂度:O(1)。
题目三:数组中数值和下标相等的元素
题目链接:69. 数组中数值和下标相等的元素
题目描述:假设一个单调递增的数组里的每个元素都是整数并且是唯一的。请编程实现一个函数找出数组中任意一个数值等于其下标的元素。
思路1:遍历
代码:
class Solution {
public:
int getNumberSameAsIndex(vector<int>& nums) {
if (nums.empty())
return -1;
for (int i = 0; i< nums.size(); i++)
if (nums[i] == i)
return i;
return -1;
}
};
复杂度分析:
时间复杂度:O(n),其中 n 是数组 nums 的长度。
空间复杂度:O(1)。
思路2:二分查找
代码:
class Solution {
public:
int getNumberSameAsIndex(vector<int>& nums) {
if (nums.empty())
return -1;
int n = nums.size();
int left = 0, right = n - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (nums[mid] == mid)
return mid;
else if (nums[mid] > mid)
right = mid - 1;
else
left = mid + 1;
}
return -1;
}
};
复杂度分析:
时间复杂度:O(logn),其中 n 是数组 nums 的长度。
空间复杂度:O(1)。