阿翰 剑指offer 之 Day04 查找算法

本文详细介绍了多种算法来解决查找数组中重复和缺失数字的问题,包括使用HashMap、排序、Set存储以及原地交换等方法。通过分析时间复杂度和空间复杂度,展示了不同策略的优缺点,并提供了优化方案。内容涵盖了数组处理和二分查找等经典算法思路。
摘要由CSDN通过智能技术生成

目录

查找算法

1 找到重复的数

1. hashmap 存次数 

2. Arrays.stream 排序 直接查相邻重复的

3. set存储(题解)

 4. 利用数组索引!

2. 排序的数组查找数字

1. hashmap

2. 遍历

2_1. 遍历优化

3. 二分法

3 寻找缺失的数字 


查找算法

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. 排序的数组查找数字

剑指 Offer 53 - I. 在排序数组中查找数字 Ihttps://leetcode-cn.com/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/

一开始的想法就是  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;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值