目录
查找算法
1 找到重复的数
1. hashmap 存次数
利用map.get() 判断是否有 其实可以在判断有无之后直接输出~不用算次数了 换用set即可!
Set集合的特点是不能存储重复元素,不能保持元素插入时的顺序,且key值最多允许有一个null值。Map中的key与Set集合特点相同,所以如果将Map中的value值当作key的附属的话,所有的key值就可以组成一个value的 set。
这里既然用map的key 可行,换做set应该是更棒的!
public int findRepeatNumber(int[] nums) {
HashMap<Integer, Integer> map = new HashMap();
for (int i = 0; i < nums.length; i++) {
if (map.get(nums[i]) == null){
map.put(nums[i], 1);
}else {
return nums[i];
}
}
return -1;
}
2. Arrays.stream 排序 直接查相邻重复的
public int findRepeatNumber_1(int[] nums) {
IntStream sorted = Arrays.stream(nums).sorted();
int[] sorted_array= sorted.toArray();
HashMap<Integer, Integer> map = new HashMap();
for (int i = 0; i < sorted_array.length - 1; i++) {
int temp = sorted_array[i];
if (temp == sorted_array[i+1])
return temp;
}
return -1;
}
3. set存储(题解)
// 存值就可以了
public int findRepeatNumber_3(int[] nums) {
Set<Integer> set = new HashSet<Integer>();
int repeat = -1;
for (int num : nums) {
if (!set.add(num)) {
repeat = num;
break;
}
}
return repeat;
}
杂性分析
- 时间复杂度:O(n)。遍历数组一遍。使用哈希集合(HashSet),添加元素的时间复杂度为 O(1),故总的时间复杂度是 O(n)。
- 空间复杂度:O(n)。不重复的每个元素都可能存入集合,因此占用 O(n) 额外空间。
4. 利用数组索引!
题目说明尚未被充分使用,即 在一个长度为 n 的数组 nums 里的所有数字都在 0 ~ n-1 的范围内 。 此说明含义:数组元素的 索引 和 值 是 一对多 的关系。
- 从头到尾扫描这个数组中的数字,当扫描到下标为i的数字时,
- 首先比较这个数字(用m表示)是不是等于 i
- 如果是则接着扫描下一个数字;
- 如果不是,则拿它和第m个数字进行比较,
- 如果它和第m个数字相等,就找到了一个重复的数字(该数字在下标为i和m的位置都出现了)
- 如果它和第m个数字不相等,就把第i个数字和第m个数字交换,把m放到属于它的位置
- 接下来再重复这个比较、交换的过程,直到我们发现一个重复的数字。
关键点是只有 nums[i] == i 的时候i才递增,保证找到相同元素前不会漏掉某些元素的处理。
//错在应该只有在nums[i]==i 的时候++i 这样才能全部归位
public int findRepeatNumber_4(int[] nums){
for (int i = 0; i < nums.length; i++) {
if(nums[i] == i) {
continue;
}
if(nums[nums[i]] == nums[i]) return nums[i];
reverse(nums, i, nums[i]);
}
return -1;
}
public void reverse(int[] nums, int i, int j){
int temp ;
temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public int findRepeatNumber_5(int[] nums){
int i = 0;
while(i < nums.length) {
if(nums[i] == i) {
i++;
continue;
}
if(nums[nums[i]] == nums[i]) return nums[i];
int tmp = nums[i];
nums[i] = nums[tmp];
nums[tmp] = tmp;
}
return -1;
}
}
复杂度分析:
- 时间复杂度 O(N) : 遍历数组使用 O(N) ,每轮遍历的判断和交换操作使用 O(1) 。
- 空间复杂度 O(1) : 使用常数复杂度的额外空间。
继续优化 原地交换题解~
public int findRepeatNumber_4(int[] nums){
for (int i = 0; i < nums.length ; i++) {
if ( nums[i] != i){
if (nums[nums[i]] == nums[i]){
return nums[i];
}else{
reverse(nums, i, nums[i]);
}
i--;
}
}
return -1;
}
public void reverse(int[] nums, int i, int j){
int temp ;
temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public int findRepeatNumber_4_1(int[] nums){
for (int i = 0; i < nums.length ; i++) {
if ( nums[i] != i){
if (nums[nums[i]] == nums[i]){
return nums[i];
}else{
reverse(nums, i, nums[i]);
}
}
else{
i++;
}
}
return -1;
}
public void reverse(int[] nums, int i, int j){
int temp ;
temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
同样的代码执行多次,内存消耗还是有偏差的~
如果只是时间优先就用字典,
还有空间要求,就用指针+原地排序数组,
如果面试官要求不能修改原数组,可以建立一个辅助数组,长度为n+1,逐一把原数组的每个数字复制到辅助数组,如果原数组复制的是m,就把它复制到辅助数组下标为m的位置。
如果还要求不能修改原数组且辅助空间也有要求为O(1),还得用二分法思想,
把从 1~n 的数字从中间的数字 m 分为两部分,前面一半为 1~m,后面一半为 m+1~n。如果1~m 的数字的数目超过m,那么这一半的区间里一定包含重复的数宇;否则,另一半m+1~n的区间里一定包含重复的数宇。我们可以继续把包含重复数字的区间一分为二,直到找到一个重复的数字。这个过程和二分 查找算法很类似,只是多了一步统计区间里数字的数目。
计算区间内的数字数目应该会被调用O(logn)次,每次调用需要O(n)的时间,总的时间复杂度为O(nlogn),空间复杂度为O(1)。
但是如果遇到某个数字出现了多次刚好等于那个区间的总个数,是无法找出来的。
譬如遇到{2,3,5,4,3,2,6,7},在{1,2}区间是2次 范围内也是有两个数字。
2. 排序的数组查找数字
一开始的想法就是 hashmap
1. hashmap
// hashmap 不要忘记不存在的情况
// hashmap适合排序与非排序
public int search(int[] nums, int target) {
HashMap<Integer, Integer> map = new HashMap();
for (int i = 0; i < nums.length; i++) {
if (map.get(nums[i]) == null){
map.put(nums[i], 1);
}else {
map.put(nums[i], map.get(nums[i])+1);
}
}
return map.get(target) != null ? map.get(target):0;
}
时间复杂度O(n)
空间复杂度O(n)
2. 遍历
public int search_1(int[] nums, int target) {
int n = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == target){
n++;
}
}
return n;
}
时间复杂度O(n)
空间复杂度O(1)
2_1. 遍历优化
public int search_1_1(int[] nums, int target) {
int n = 0;
int temp = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != target && temp == 1){
break;
}
if (nums[i] == target){
n++;
temp = 1;
}
}
return n;
}
加了个temp标识是否已经找完了,利用空间换时间,时间复杂度还是O(n)
3. 二分法
class Solution {
public int search(int[] nums, int target) {
int leftIdx = binarySearch(nums, target, true);
int rightIdx = binarySearch(nums, target, false) - 1;
if (leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] == target && nums[rightIdx] == target) {
return rightIdx - leftIdx + 1;
}
return 0;
}
public int binarySearch(int[] nums, int target, boolean lower) {
int left = 0, right = nums.length - 1, ans = nums.length;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] > target || (lower && nums[mid] >= target)) {
right = mid - 1;
ans = mid;
} else {
left = mid + 1;
}
}
return ans;
}
}
官方题解 不甚明白~ 看大佬解释的才清晰明了,修改了第二个版本。
public int search_3(int[] nums, int target) {
// 1. nums[m] < target target 在[m+1,j]中 so i = m + 1
// 2. nums[m] > target target 在[i,m-1]中 so j = m - 1
// 3. nums[m] == target
// 右边界 在[m+1,j]中 i = m + 1
// 左边界 在[i,m-1]中 j = m - 1
// so 求右边界 nums[m] == target 合并到 nums[m] < target 都为 [m+1,j]
// 即 if(nums[m] <= target) i = m + 1;
// else j = m - 1;
// 求左边界 nums[m] == target 合并到 nums[m] > target 都为 [i,m-1]
// 即 if(nums[m] < target) i = m + 1;
// else j = m - 1;
// 搜索右边界 right
int i = 0, j = nums.length - 1;
int m = 0;
while(i <= j) {
m = (i + j) / 2;
if(nums[m] <= target) i = m + 1;
else j = m - 1;
}
// 退出循环是靠 j 左移 so i留在原地 m也是
int right = i;
// 若数组中无 target ,则提前返回
if(j >= 0 && nums[j] != target) return 0;
// 搜索左边界 right
i = 0; j = nums.length - 1;
while(i <= j) {
m = (i + j) / 2;
if(nums[m] < target) i = m + 1;
else j = m - 1;
}
// 退出循环是靠 i 右移 so j留在原地
int left = j;
return right - left - 1;
}
优化基于:查找完右边界 right = i 后,即 nums[i] 指向右边界,则 nums[j] 指向最右边的 target (若存在)
复杂度分析:
- 时间复杂度 O(log N) : 二分法为对数级别复杂度。
- 空间复杂度 O(1) : 几个变量使用常数大小的额外空间。
3 寻找缺失的数字
剑指 Offer 53 - II. 0~n-1中缺失的数字https://leetcode-cn.com/problems/que-shi-de-shu-zi-lcof/
排序数组中的搜索问题,首先想到 二分法 解决
和上一题类似,都是用二分法找左右边界,这次条件只需要判断是否相等即可。
class Solution {
public int missingNumber(int[] nums) {
int start = 0, end = nums.length - 1;
int mid = 0;
while(start <= end){
mid = ((end - start) >> 1) + start;
if(nums[mid] != mid)
end = mid - 1;
else
start = mid + 1 ;
}
return start;
}
}