剑指Offer 不修改数组找出重复的数字

acwing链接
这里题目要求不修改原来的数组,并且空间复杂度要求为 O ( 1 ) O(1) O(1)。这里可以用二分查找的方法。设 l e f t left left是搜索范围的开始, r i g h t right right为搜索范围的结束。 m i d mid mid为它们的中间值
统计数组中 [ l e f t , m i d ] [left, mid] [left,mid]之间数字的次数 c n t cnt cnt,与left和right之间的数字个数 m i d − l e f t + 1 mid- left + 1 midleft+1进行比较。

  • l e f t = r i g h t left = right left=right并且出现频率大于1,则返回 l e f t left left,否则返回-1
  • 如果 c n t > m i d − l e f t + 1 cnt > mid- left + 1 cnt>midleft+1。说明重复数字在 [ l e f t , m i d ] [left, mid] [left,mid]之间,将 r i g h t right right修改为 m i d mid mid
  • 否则,说明重复数字不在 [ l e f t , m i d ] [left, mid] [left,mid],将 l e f t left left修改为 m i d + 1 mid + 1 mid+1。注意是修改为 m i d + 1 mid + 1 mid+1,不然会出现卡死的情况。
  • 进行下一次搜索
/*
利用二分查找,不断地缩小重复数字的范围

算法复杂度O(nlogn)
*/
class Solution {
    public int duplicateInArray(int[] nums) {
        int left = 1;
        int right = nums.length - 1;
        while(left < right){
            int mid = (left + right) / 2;
            int cnt = cntInRange(nums, left, mid);
            
            if(left == right){
                if(cnt > 1)
                    return left;
                else
                    break;
            }
            if(cnt > mid - left + 1)
                right = mid;
            else
                left = mid + 1;
            // System.out.println("left = " + left + " right = " + right);
        }
        
        return -1;
        
    }
    
    // 用于统计nums中在[start, end]数字个数
    private int cntInRange(int[] nums, int start, int end){
        int cnt = 0;
        for(int num : nums){
            if(num >= start && num <= end)
                cnt++;
        }
        return cnt;
    }
}

一个小问题,假如镜像地操作,统计 [ m i d , r i g h t ] [mid, right] [mid,right]之间的数字而不是 [ l e f t , m i d ] [left, mid] [left,mid]的数字,那么就会出现卡死。知道卡死的原因。例如统计[3,4,4],前者会出问题,后者不会。

/*
利用二分查找,不断地缩小重复数字的范围

算法复杂度O(nlogn)
*/
class Solution {
    public int duplicateInArray(int[] nums) {
        int left = 1;
        int right = nums.length - 1;
        while(left < right){
            int mid = (left + right) / 2;
            int cnt = cntInRange(nums, mid, right);
            if(cnt > right - mid + 1)
                left = mid;
            else{
                right = mid - 1;
            }
            // 二分查找法很容易卡死,这里假如选择的是统计[mid, right]来判断,就会卡死。莫名其妙
        }
        
        return left;
        
    }
    
    // 用于统计nums中在[start, end]数字个数
    private int cntInRange(int[] nums, int start, int end){
        int cnt = 0;
        for(int num : nums){
            if(num >= start && num <= end)
                cnt++;
        }
        return cnt;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值