面试题03.找出数组中重复的数字

package SwordOffer;

import java.util.HashSet;
import java.util.Set;

/**
* @Description: 找出数组中重复的数字。


在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

示例 1:

输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3 
 

限制:

2 <= n <= 100000

* @Param:  
* @return:  
* @Author: lvhong
* @Date:  
* @E-mail lvhong282@163.com
*/ 
public class lab3easy {
    public static void mian ( String [] args){
        int [] test={2, 3, 1, 0, 2, 5, 3};
        findRepeatNumber(test);
    }
        //思路1:hashmap或者集合向里面输入,有重复数字会输出     空间复杂度O(n),时间复杂度O(n)
        public static int findRepeatNumber(int[] nums) {
//            Set<Integer> set = new HashSet<Integer>();
//            int repeat = -1;
//            for (int num : nums) {
//                if (!set.add(num)) {
//                    repeat = num;
//                    break;
//                }
//            }
//            return repeat;

            Set<Integer> IntSet = new HashSet<Integer>();
            int out = -1;
            for(int num : nums){
                if(!IntSet.add(num)){
                    out = num;
                    break;
                }
            }
        return  out;
        }
//思路2:鸽巢(抽屉?),替换位置,从头开始判断一个数字是否等于他的下标处的数字,不等于就替换到相应的位置,遍历执行,空间复杂度O(1),时间复杂度O(n),会修改位置
   public int findRepeatNumber2(int[] nums ){
         int i;
        int length=nums.length;
        int tmp;
         for(i=0;i<length;i++){
           while(nums[i]!=i) {
               if (nums[i] == nums[nums[i]]) {
                   return nums[i];
               } else {
                   tmp = nums[i];
                   nums[i] = nums[tmp];
                   nums[tmp] = tmp;
               }
           }
       }
        return -1;
    }
//思路3:暴力解法:直接遍历的查找每一个数字与其他数组内的元素是否相同,空间复杂度O(n^2),时间复杂度O(n^2)

        //int i,j;
        //int length=nums.length;
        //for(i=0;i<length;i++){
        //    for(j=i+1;j<length;j++){
        //        if(nums[i]==nums[j]){
        //          return nums[i];  
        //        }
        //    }
        //}
        //return -1;
//思路4:对应不可以改变原数组且空间复杂度为O(1),时间换空间,

        //4.1  快慢指针   (问题287.寻找重复数的官方解法)   空间复杂度O(n) 时间复杂度O(1)
        public int findDuplicateFastAndLow(int[] nums) {
            // Find the intersection point of the two runners.
            int tortoise = nums[0];
            int hare = nums[0];
            do {
                tortoise = nums[tortoise];
                hare = nums[nums[hare]];
            } while (tortoise != hare);

            // Find the "entrance" to the cycle.
            int ptr1 = nums[0];
            int ptr2 = tortoise;
            while (ptr1 != ptr2) {
                ptr1 = nums[ptr1];
                ptr2 = nums[ptr2];
            }

            return ptr1;
        }

        //4.2 二分  (思路和源码源自:liweiwei1419   )   空间复杂度O(nlogn)  时间复杂度O(1)
/*
思路:这道题要求我们查找的数是一个整数,并且给出了这个整数的范围(在 11 和 nn 之间,包括 1 和 n),并且给出了一些限制,于是可以使用二分查找法定位在一个区间里的整数。

这个问题应用二分法与绝大多数其它问题应用二分法的不同点是:正着思考是容易的,即思考哪边区间存在重复数是容易的,因为依然是有抽屉原理做保证。我们依然通过一个具体的例子来分析应该如何编写代码。

以 [1, 2, 2, 3, 4, 5, 6, 7] 为例,一共 8 个数,n + 1 = 8,n = 7,根据题目意思,每个数都在 1 和 7 之间。

例如:区间 [1, 7] 的中位数是 4,遍历整个数组,统计小于等于 4 的整数的个数,至多应该为 4 个。换句话说,整个数组里小于等于 4 的整数的个数如果严格大于 4 个,就说明重复的数存在于区间 [1, 4],它的反面是:重复的数存在于区间 [5, 7]。

于是,二分法的思路是先猜一个数(有效范围 [left, right]里的中间数 mid),然后统计原始数组中小于等于这个中间数的元素的个数 cnt,如果 cnt 严格大于 mid,(注意我加了着重号的部分“小于等于”、“严格大于”)依然根据抽屉原理,重复元素就应该在区间 [left, mid] 里。

*/
        public int findDuplicateBinary(int[] nums) {
            int len = nums.length;
            int left = 1;
            int right = len - 1;
            while (left < right) {
                int mid = (left + right) >>> 1;

                int cnt = 0;
                for (int num : nums) {
                    if (num <= mid) {
                        cnt += 1;
                    }
                }

                // 根据抽屉原理,小于等于 4 的个数如果严格大于 4 个
                // 此时重复元素一定出现在 [1, 4] 区间里

                if (cnt > mid) {
                    // 重复元素位于区间 [left, mid]
                    right = mid;
                } else {
                    // if 分析正确了以后,else 搜索的区间就是 if 的反面
                    // [mid + 1, right]
                    left = mid + 1;
                }
            }
            return left;
        }

/*
思路 2:(思路和源码源自:liweiwei1419   )

先猜一个数(有效范围 [left, right]里的中间数 mid),然后统计原始数组中严格小于这个中间数的元素的个数 cnt,如果 cnt 大于等于 mid,(注意我加了着重号的部分“严格小于”、“大于等于”)依然根据抽屉原理,重复元素就应该在区间 [left, mid - 1] 里。

*/

        public int findDuplicateBinary2(int[] nums) {
            int len = nums.length;
            int left = 1;
            int right = len - 1;
            while (left < right) {
                int mid = (left + right + 1) >>> 1;

                int cnt = 0;
                for (int num : nums) {
                    if (num < mid) {
                        cnt += 1;
                    }
                }

                // 根据抽屉原理,严格小于 4 的数的个数如果大于等于 4 个,
                // 此时重复元素一定出现在 [1, 3] 区间里

                if (cnt >= mid) {
                    // 重复的元素一定出现在 [left, mid - 1] 区间里
                    right = mid - 1;
                } else {
                    // if 分析正确了以后,else 搜索的区间就是 if 的反面
                    // [mid, right]
                    // 注意:此时需要调整中位数的取法为上取整
                    left = mid;
                }
            }
            return left;
        }

}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值