一、数组中重复的数字
剑指 Offer 03. 数组中重复的数字https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
限制:
2 <= n <= 100000
解题思路:
1.暴力解法:i从0~n-1遍历数组,j从i~n-1遍历数组,nums[i]等于nums[j]时便找到了重复数字,但此方法时间复杂度O(N^2),还有更优解。
2.根据所有数字都在0~n-1的范围内的特性,可以将数值作为索引映射到数组中,如数组已有元素则说明此元素重复。
3.利用哈希表建立映射,遍历一遍数组即可找到重复数字。
具体细节:
1.初始化哈希表,遍历数组将数字添加至哈希表中。
2.若数字已存在则返回此数字,若不存在则继续遍历直至结束。
3.若都无重复数字则返回-1.
复杂度分析:
1.时间复杂度:遍历一遍数组,且哈希表存储和查找都为O(1),所以整体时间复杂度为O(N)。
2.空间复杂度:哈希表占用O(N)的空间复杂度。
代码实现:
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
unordered_map<int, bool> mymap;
for (int i = 0; i < nums.size(); i++) {
if (mymap[nums[i]]) return nums[i];
mymap[nums[i]] = true;
}
return -1;
}
};
二、在排序数组中查找数字
统计一个数字在排序数组中出现的次数。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: 0
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109
解题思路:
1.暴力解法:遍历数组统计相同元素的次数,但时间复杂度和数组非递减的特性并没有用到。
2.二分查找:排序数组首先想到二分查找,所有目标元素在一起排列,故有左右两个下标,统计目标元素的个数便可转化为右下标减去左下标即可。
具体细节:
1.初始化左下标为left = 0,右下标为right = n - 1.
2.循环查找目标元素的左右边界,计算mid = (left + right) / 2;
若 nums[mid] < target ,则 target 在闭区间 [mid + 1, right] 中,因此执行 left = mid + 1;
若 nums[mid] > target ,则 target 在闭区间 [left, mid - 1]中,因此执行 right = mid - 1;
若 nums[mid] == target ,则右边界 right 在闭区间 [mid + 1, right] 中;左边界 left在闭区间 [left, mid - 1] 中。
因此分为以下两种情况:
若查找 右边界 right,则执行 left = mid + 1 ;(跳出时 left 指向左边界);
若查找 左边界 left ,则执行 right = mid - 1 ;(跳出时 right 指向右边界)。
3.算法优化:找到右边界后,若无目标元素则直接返回0,无需查找左边界。若有目标元素则左边界可以直接从[0, right]之间查找。
复杂度分析:
1.时间复杂度:两次二分查找需O(logN)的时间复杂度。
2.空间复杂度:只需常数级别的额外变量,故只需O(1)的空间复杂的。
代码实现:
class Solution {
public:
int search(vector<int>& nums, int target) {
if (nums.size() == 0) return 0;
int left, right;
int l = 0, r = nums.size() - 1;
while (l <= r) {
int m = (l + r) / 2;
if (nums[m] > target) r = m - 1;
else l = m + 1;
}
if (r >= 0 && nums[r] != target) return 0;
right = r;
l = 0;
while (l <= r) {
int m = (l + r) / 2;
if (nums[m] >= target) r = m - 1;
else l = m + 1;
}
left = l;
return right - left + 1;
}
};
三、0 ~ n-1 中缺失的数字
剑指 Offer 53 - II. 0~n-1中缺失的数字https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例 1:
输入: [0,1,3]
输出: 2
示例 2:
输入: [0,1,2,3,4,5,6,7,9]
输出: 8
限制:
1 <= 数组长度 <= 10000
解题思路:
1.暴力解法:循序遍历数组,若数组元素不等于下标则返回下标,但还有时间复杂度更优的解法。
2.二分查找:二分查找下标不等于元素的首位元素就是要找的数字。
具体细节:
1.初始化下标left = 0 ,right = n - 1。
2.循环查找下标不等于元素的最左元素,计算 mid = (left + right) / 2;
若 nums[m] = m ,则 “下标不等于元素区间的最左元素” 一定在闭区间 [mid + 1, right] 中,因此执行 left = m + 1;
若 nums[m] ≠ m,则 “下标不等于元素区间的最左元素” 一定在闭区间 [left, mid - 1] 中,因此执行 right = m - 1;
3.循环过后left指向下标不等于元素区间的最左元素即要找的数字。
复杂度分析:
1.时间复杂度:二分查找O(logN)。
2.空间复杂度:常数的额外变量故O(1)。
代码实现:
class Solution {
public:
int missingNumber(vector<int>& nums) {
int left = 0, right = nums.size() - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] == mid) left = mid + 1;
else right = mid - 1;
}
return left;
}
};