D - Kth Excluded Java

D - Kth Excluded Java

题目

https://atcoder.jp/contests/abc205/tasks/abc205_d

给N个正整数
会有Q次查询
每次查询给一个正整数k,需要返回第k小的正整数(并且不能是给的N个数中的数)

解决思路

核心思路:差分数列 + 二分法

1、计算差分数列

1 2 3 4 5 6 7 8 9 10 11 12 …

给定数列:A = [3, 6, 7, 9, 10]

差分数列:a = [2, 4, 4, 5, 5]

差分数列计算:ai = Ai - i (假设 i 从1开始,不同于代码中数组下标从0开始)

差分数列含义:ai表示Ai之前有多少不同于整个A数列的数 。

A1=3,3之前有1、2,一共有2个不同于A的数,因此a1=2;

A2=6,6之前有1、2、4、5,因此一共有4个不同于A的数,因此a2=4;

A3=7,7之前有1、2、4、5,因此一共有4个不同于A的数,因此a3=4

2、二分法找到k对应差分数列a的位置

寻找什么位置:需要找到a中小于k的元素中,最大的索引index,也就是说k是在A[index]到A[index + 1]之间的数,即a[index] < k <= a[index + 1]

k=3,则index=1,a[index] = a1 = 2;

k=4,则index=1,a[index] = a1 = 2;

k=5,则index=3,a[index] = a3 = 4;

计算含义:用一个具体的case举例,k=3时,需要寻找不同于A的第3小的一个数(可以直接看出来是4),那么我们根据上述规则寻找到index=1,在差分数列中对应a1=2,原数列A1=3,即A1之前只有2个可选的数,所以第3小一定在比A1大;index + 1 = 2,对应a2=4,A2=6,即A2之前有4个可选的数,所以第3小一定比A2小;这样就定位了k在A1和A2之间。

3、根据寻找到的位置计算结果

找到位置后如何计算结果:res = A[index] + (k - a[index])

k=3,则index=1,此时A1=3, a1=2,则res = 3 + (3 - 2) = 4;

k=4,则index=1,此时A1=3, a1=2,则res = 3 + (4 - 2) = 5;

k=5,则index=3,此时A3=7, a3=4,则res = 7 + (5 - 4) = 8;

计算含义:继续使用上述的case,k=3时,我们定位了k在A1和A2之间。A1之前有2个可选的数,那么k - ai = 3 - 2 = 1,这个1表明第k小的数比A1大1,因此res = A1 + 1 = 3 + 1 = 4;

疑问点:

不知道上述解决思路还有没有可以优化的地方?
下边附上代码,如何看自己是否满足要求(Time Limit: 3 sec / Memory Limit: 1024 MB)?代码在逻辑上、耗时上、内存上,还有没有可以优化的点呀?

Java代码

import com.alibaba.fastjson.JSON;

public class DKExcluded {

    /**
     * 二分法寻找某个值对应的index
     */
    public static int dichotomy(int[] arr, int k) {
        int start = 0;
        int end = arr.length - 1;
        while (start <= end) {
            int mid = (start + end) / 2;
            if (k == arr[mid]) {
                return mid;
            } else if (k < arr[mid]) {
                end = mid - 1;
            } else {
                start = mid + 1;
            }
        }
        return -1;
    }

    /**
     * 给N个正整数(不包含0)
     * 会有Q次查询
     * 每次查询给一个正整数k,需要返回第k小的正整数(并且不能是给的N个数中的数)
     */
    public static int[] findQKth(int[] arr, int[] qArr) {
        int[] kArr = new int[qArr.length];
        int i = 0;
        for (int q : qArr) {
            kArr[i] = findKth(arr, q);
            i++;
        }
        return kArr;
    }
    private static int findKth(int[] arr, int k) {
        int size = arr.length;;
        // 差分数列 diffArr[i]表示 arr[i]前有多少个不同于arr数列的数(不包含0)
        int[] diffArr = new int[size];
        for (int i = 0; i < size; i++) {
            diffArr[i] = arr[i] - (i + 1);
        }

        // 边界处理
        if (k <= diffArr[0]) {
            return k;
        }
        if (k > diffArr[size - 1]) {
            return arr[size - 1] + (k - diffArr[size - 1]);
        }

        // 二分法找到index
        int index = findIndex(diffArr, k);

        // 根据index计算第k小的正整数
        return calculateKth(arr, diffArr, k, index);
    }

    /**
     * 二分法
     * arr从小达到排序的数组,可能存在元素相同的情况
     * arr[0] < k <= arr[arr.length - 1]
     * 寻找小于k的元素中最大的index
     */
    private static int findIndex(int[] arr, int k) {
        int start = 0;
        int end = arr.length - 1;
        int mid;
        while (start <= end) {
            mid = (start + end) / 2;
            if (k <= arr[mid]) {
                if (k > arr[mid - 1]) {
                    return mid - 1;
                } else {
                    end = mid - 1;
                }
            } else { // k > arr[mid]
                start = mid + 1;
            }
        }
        return -1;
    }

    private static int calculateKth(int[] arr, int[] diffArr, int k, int index) {
        return arr[index] + k - diffArr[index];
    }

    public static void main(String[] args) {
        int[] arr = new int[] {3, 5, 6, 7};
        int[] qArr = new int[] {2, 5, 3};
        // 期望:2, 9, 4
        int[] kArr = findQKth(arr, qArr);
        System.out.println(JSON.toJSONString(kArr));

        int[] arr2 = new int[] {1, 2, 3, 4, 5};
        int[] qArr2 = new int[] {1, 10};
        // 期望:6, 15
        int[] kArr2 = findQKth(arr2, qArr2);
        System.out.println(JSON.toJSONString(kArr2));
    }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值