数组中重复的数字

题目描述

image

思路

set集合

set集合是不允许有重复元素的,根据这个特点可以实现去重、判定重复

构建索引字典

字典

暂时理解为和map是一个东西,都是构建key与value的一一对应关系

本题中元素种类少于数组容量,所以是肯定可以构建出索引与容量的一一对应关系的,这是一种模式

而构建方法可以采用原地交换的方式,没完成索引构建就一直在原地交换,直到完成或遇见重复元素为止。

代码实现

set集合

class Solution {
    public int findRepeatNumber(int[] nums) {
        HashSet hashSet=new HashSet();
        for(int num:nums){
            if(!hashSet.add(num)){
                return num;
            }
        }
        return -1;
    }
}

构建索引字典

class Solution {
    public int findRepeatNumber(int[] nums) {
        int i=0;
        while(i<nums.length){
            if(nums[i]==i){
                i++;
                continue;
            }
            //以当前元素值为下标的元素的值与之相等,则判定为重复
            if(nums[i]==nums[nums[i]]){
                return nums[i];
            }
            //交换元素,知道构建出下标与元素值的索引关系或找到两个相同的元素为止
            int tmp=nums[i];
            nums[i]=nums[tmp];
            //注意nums[i]已经变化了
            nums[tmp]=tmp;
        }
        return -1;
    }
}

复杂度分析

时间复杂度

set集合为O(n),哈希表的插入和读取都是O(1),O(n)源于遍历

构建索引字典为O(n),不是很能理解

空间复杂度

set集合为O(n),哈希表占用

构建索引字典为O(1)

反思不足

思路

只能想到用暴力循环的方式,想不到可以用set集合

对set集合的应用场景不熟悉

对字典用于判断重复这一用法不熟悉

审题

没有意识到范围在n-1意味着本题是必定有不重复元素的

java se

HashSet类的不熟悉

  • set集合都不允许重复元素
  • add()如果添加失败会返回false

在排序数组中查找数字

题目描述

image

思路

二分查找寻边界

有序数组的元素查找肯定是优先考虑二分法

边界怎么找呢?

找左边界,要让right指针指向比目标数小的数,所以无论大于等于都要让right指向mid-1

找右边界,要让left指向比目标数大的数,所以小于和等于都要让left指向mid+1

因为在一连串等于判定中移动指针的不同,导致了能够求出两边边界

算法优化

转化成两次求右边界,以减少代码冗余

我们注意到最后减了1,所以我们可以找到第一个目标数的索引,然后找到右边界相减即可,第一个目标数的索引可以转化为刚好比他小1的数的右边界

转化成左边界亦可,一样的道理

代码实现

class Solution {
    public int search(int[] nums, int target) {
        int left=0,right=nums.length-1;
        int mid=left+(right-left)/2;
        //找左边界
        while(left<=right){
            if(nums[mid]>=target){
                right=mid-1;
            }else{
                left=mid+1;
            }
            mid=left+(right-left)/2;
        }
        int leftIndex=right;
        left=0;
        right=nums.length-1;
        mid=left+(right-left)/2;

        //找右边界
        while(left<=right){
            if(nums[mid]<=target){
                left=mid+1;
            }else{
                right=mid-1;
            }
            mid=left+(right-left)/2;
        }
        int rightIndex=left;
        return rightIndex-leftIndex-1;
    }
}

优化版

class Solution {
    public int search(int[] nums, int target) {
        return getRightIndex(nums,target)-getRightIndex(nums,target-1);
    }
    public int getRightIndex(int [] nums,int target){
        int left=0,right=nums.length-1;
        int mid=left+(right-left)/2;
        while(left<=right){
            if(nums[mid]<=target){
                left=mid+1;
            }else{
                right=mid-1;
            }
            mid=left+(right-left)/2;
        }
        return left;
    }
}

复杂度分析

时间复杂度

O(log2n),二分查找的复杂度

空间复杂度

O(1)

反思不足

思路

最开始是想到了用二分查找,但是想到的是找目标值,然后向左右遍历计数,这种虽然比直接遍历可能要好,但如果全是同一个数的话最坏仍是O(n)

刚开始并不是很理解找左右边界的算法

0-n-1中缺失的数字

题目描述

image

思路

二分法

有序数组优先考虑二分查找,只要mid索引对应的值等于mid,则一定在mid右边,否则在mid左边,根据这个关系使用二分查找

代码实现

class Solution {
    public int missingNumber(int[] nums) {
        int left=0,right=nums.length-1,mid=left+(right-left)/2;
        while(left<=right){
            if(nums[mid]==mid){
                left=mid+1;
                mid=left+(right-left)/2;
            }else{
                right=mid-1;
                mid=left+(right-left)/2;
            }
        }
        return left;
    }
}

复杂度分析

时间复杂度

O(log2n),二分查找的复杂度

空间复杂度

O(1)

反思不足

思路

注意到数与索引的关系,这是有序数组,在缺少数之前,数与索引是一一对应的。于是可以根据这个遍历数组找到缺失数

想到了二分这个方向,但一开始没想到怎么用

后来发现了其中的关系,只要mid索引对应的值等于mid,则一定在mid右边,否则在mid左边,但是还是做复杂了,在进行的过程中会自动完成寻找第一个不等的索引,而不需要自己多此一举去判断。原因在于对其执行过程的细节没理解清楚,建议下次模拟一边大致过程。