阿里13年研发笔试题 - 寻找有序数组中元素值等于其下标的所有元素

问题

给定一个升序排列的整型数组A,其元素的值都两两不相等。请设计一高效的算法找出中间所有A[i] = i的下标。 并分析其复杂度。(不分析复杂度不得分)


Java Code

public class FindSubscript {

    @Test
    public void test() {
        int[] nums = new int[]{-2,-1,2,3,4};
        ArrayList<Integer> res = findSubscript2(nums);
        for(int ele : res) {
            System.out.print(ele + " ");
        }
    }

/***********************************************************************************/
    //版本一:对数组元素逐个遍历
    public ArrayList<Integer> findSubscript1(int[] nums) {
        int n = nums.length;
        ArrayList<Integer> allSub = new ArrayList<Integer>();

        for(int i = 0; i < n; ++i) {
            if(nums[i] == i)
                allSub.add(i);
            else if(nums[i] > i)
                break;
        }

        return allSub;
    }

/***********************************************************************************/   
    //版本二:先二分查找区域中的某个元素,再分别线性查找区域的左边界和右边界
    public ArrayList<Integer> findSubscript2(int[] nums) {
        ArrayList<Integer> allSub = new ArrayList<Integer>();
        int n = nums.length;

        //如果数组为空,或其首元素(尾元素)大于(小于)自身的下标,则不可能会有元素值等于其下标
        if(n == 0 || nums[0] > 0 || nums[n-1] < n-1) 
            return allSub;

        //否则至少有一个元素的值等于其下标
        int pivot = binaryFindPivot(nums, 0, n-1);
        allSub.add(pivot);

        //向左查找所有的目标元素
        for(int i = pivot-1; i >= 0 && i == nums[i]; --i)
            allSub.add(i);

        //向右查找所有的目标元素
        for(int i = pivot+1; i < n && i == nums[i]; ++i)
            allSub.add(i);

        return allSub;  
    }


    //二分查找数组中元素值等于其下标的元素(保证至少有一个)
    public int binaryFindPivot(int[] nums, int i, int j) {
        int mid = (i + j)/2;
        if(nums[mid] == mid)//如果如果中间元素的值正好等于其下标
            return mid;
        else if(nums[mid] > mid)//如果中间元素的值大于其下标,在左半边区域寻找目标元素
            return binaryFindPivot(nums, i, mid - 1);
        else
            return binaryFindPivot(nums, mid + 1, j);

    }

/***********************************************************************************/
    //版本三:二分查找区域以及区域的左右边界
    public ArrayList<Integer> findSubscript3(int[] nums) {
        ArrayList<Integer> allSub = new ArrayList<Integer>();
        int n = nums.length;
        if(n == 0 || nums[0] > 0 || nums[n-1] < n-1) return allSub;

        int[] bound = binaryFind(nums, 0, n-1);
        for(int i = bound[0]; i <= bound[1]; ++i)
            allSub.add(i);

        return allSub;
    }

    //二分寻找数组中元素值等于下标的这个区域(即区域的左边界和右边界)
    public int[] binaryFind(int[] nums, int i, int j) {
        int mid = (i + j)/2;
        if(nums[mid] == mid) {//如果如果中间元素的值正好等于其下标
            i = binarySearchLower(nums, i, mid);//在左半边区域搜索左边界
            j = binarySearchUpper(nums, mid, j);//在右半边区域搜索右边界
            return new int[]{i, j};
        }else if(nums[mid] > mid)//如果中间元素的值大于其下标,在左半边区域寻找左边界和右边界
            return binaryFind(nums, i, mid - 1);
        else
            return binaryFind(nums, mid + 1, j);
    }

    //二分查找区域的左边界(数组在位置j附近有一段区域内元素值等于其下标)
    public int binarySearchLower(int[] nums, int i, int j) {
        int mid = (i + j)/2;//mid选择正中间或偏左
        if(nums[mid] == mid) {//case1:如果中间元素的值正好等于其下标,
            if(mid == 0 || nums[mid - 1] < mid - 1)//且其左边的元素小于下标值或当前元素是数组首元素,
                return mid;//则已经找到左边界
            else
                j = mid;//否则继续在左半边区域搜索左边界
        }else if(nums[mid] < mid) {//case2:如果中间元素的值小于其下标,
            if(nums[mid + 1] == mid + 1) return mid + 1;//且其右边元素正好等于下标,则已经找到左边界
            i = mid;//否则继续在右半边区域搜索左边界
        }else//case3:如果中间元素的值大于其下标
            j = mid;

        return binarySearchLower(nums, i, j);
    }

    //二分查找区域的右边界(数组在位置i附近有一段区域内元素值等于其下标)
    public int binarySearchUpper(int[] nums, int i, int j) {
        int mid = (i + j + 1)/2;//mid选择正中间或偏右
        if(nums[mid] == mid) {
            if(mid == j || nums[mid + 1] > mid + 1)
                return mid;
            else
                i = mid;
        }else if(nums[mid] > mid) {
            if(nums[mid - 1] == mid - 1) return mid - 1;
            j = mid;
        }else
            i = mid;

        return binarySearchUpper(nums, i, j);
    }

}

分析

  • 版本一解法的复杂度为O(n);版本二解法的复杂度为O(log(n))+O(m),其中m为数组中元素值等于下标的区域的长度;版本三解法的复杂度为O(log(n))+O(m),如果只需要查找区域的左右边界,则复杂度为O(log(n))。

  • 本题的关键在于发现问题的规律,即如果升序数组中出现元素值等于其下标,则这些元素必定连成一个区间段,且不会出现2个以上的这种区间段。

  • 由于数组是有序的,所以很容易想到用二分查找的方法,版本二和三的区别在于,前者只通过二分查找得到这个区间中的某一个元素,然后线性查找这个区间得到所有符合条件的元素,后者则是先用二分查找得到这个区间段在数组中的大致范围,然后继续用二分法查找这个区间的左边界和右边界。

  • 代码中binarySearchLower函数中第9行的判断是不可省略的,因为当i+1=j时,mid本就等于i,此后指针i和j不会再发生变化了,所以如果不判断mid+1的值,则binarySearchLower会一直递归直到堆栈溢出。另外,每次判断mid+1有一个好处,如果mid + 1正好就是左边界,那么我们可以提前找到它而少几次二分查找。对于binarySearchUpper函数也是类似的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值