求n个数字的前K个数或者求中位数

本文介绍了一种基于快速排序的改进算法,用于查找数组中的中位数和前K个最小元素。通过递归地应用分区操作,算法能有效地定位目标元素,特别适用于大数据集的中位数和排名统计。
中位数
public class median {

    private boolean bOdd;//是否奇偶数
    private int kv;//k值
    private double medium;
    int partition(double a[], int low, int high) {
        double tmp = a[low];
        int i = low, j = high;
        while (i < j) {
            while (i < j && a[j] >= tmp){
                j--;
            }
            while (i < j && a[i] <= tmp){
                i++;
            }
            swap(a, i, j);
        }
        a[low] = a[i];
        a[i] = tmp;
        return i;
    }

    void swap(double a[], int i, int j) {
        if(i == j){
            return;
        }
        double tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
    void findMedium(double a[]){
        bOdd = a.length % 2 == 0;
        kv = a.length / 2 + 1;
        medium = 0;
        findK(a, 0, a.length - 1, kv, -1);
    }

    /**
     * 需求第K小
     * @param a
     * @param low
     * @param high
     * @param k
     * @param prePart  上回分裂中将该中位数分到右边的分裂点
     */
    void findK(double a[], int low, int high, int k, int prePart){
        if(low > high){
            return;
        }
        int pos = partition(a, low, high);
        int left = pos - low + 1;//左边个数
        if(k > left){//中位数在分裂点右边,将该分裂点作为下次迭代的prePart
            findK(a, pos + 1, high, k - left, pos);
        }
        else if(k < left){//中位数在分裂点左边,本次的prePart作为下次迭代的prePart
            findK(a, low, pos - 1, k, prePart);
        }
        else{
            if(bOdd){//偶数时的中位数处理,取两个中位数的均值
                double v1 = a[pos];
                double v2 = 0;
                if(low >= pos){
                    v2 = a[prePart]; //左边无值时取prePart
                }else{
                    v2 = findMax(a, low, pos - 1);//左边有值时取左边最大值
                }
                medium = (v1 + v2) / 2;
                System.out.println(medium);
            }else{
                medium = a[pos];
                System.out.println(medium);
            }
        }
    }

    double findMax(double a[], int low, int high){
        double max = a[low];
        for(int i = low + 1; i <= high; i ++){
            if(a[i] > max){
                max = a[i];
            }
        }
        return max;
    }

    double getMedium(){
        return medium;
    }

    public static void main(String[] args) {
        median m = new median();
        double a[] = {6,1,2,3,7,8,5,4,9};
        m.findMedium(a);
        m.findK(a,0,a.length - 1, 9, -1);
    }
}

 

// 求前K个数

public class medianK {

    private int kv;//k值
    private double medium;
    int partition(double a[], int low, int high) {
        double tmp = a[low];
        int i = low, j = high;
        while (i < j) {
            while (i < j && a[j] >= tmp){
                j--;
            }
            while (i < j && a[i] <= tmp){
                i++;
            }
            swap(a, i, j);
        }
        a[low] = a[i];
        a[i] = tmp;
        return i;
    }

    void swap(double a[], int i, int j) {
        if(i == j){
            return;
        }
        double tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }


    /**
     * 需求第K小
     * @param a
     * @param low
     * @param high
     * @param k
     * @param prePart  上回分裂中将该中位数分到右边的分裂点
     */
    void findK(double a[], int low, int high, int k, int prePart){
        if(low > high){
            return;
        }
        int pos = partition(a, low, high);
        int left = pos - low + 1;//左边个数
        if(k > left){//中位数在分裂点右边,将该分裂点作为下次迭代的prePart
            findK(a, pos + 1, high, k - left, pos);
        }
        else if(k < left){//中位数在分裂点左边,本次的prePart作为下次迭代的prePart
            findK(a, low, pos - 1, k, prePart);
        }else {
            for (int i=0;i<k;i++){
                System.out.println(a[i]);
            }
        }
    }

    double findMax(double a[], int low, int high){
        double max = a[low];
        for(int i = low + 1; i <= high; i ++){
            if(a[i] > max){
                max = a[i];
            }
        }
        return max;
    }

    double getMedium(){
        return medium;
    }

    public static void main(String[] args) {
        medianK m = new medianK();
        double a[] = {6,1,2,3,7,8,5,4,9};
//        m.findMedium(a);
        m.findK(a,0,a.length - 1, 5, -1);
    }
}

 

<think>首先,用户的问题是基于提供的参考资料,查找计算第n个所有位不含5的数字的方法。用户特别提到“所有位不含5的数字”,意思是数字的每一位都不包含数字5。 回顾提供的引用: - 引用[1]:关于计算具有唯一数字数字个数的代码(countNumbersWithUniqueDigits)。似乎是在处理数字的每一位都不重复的数字计数问题。这与用户的问题相似但不相同,因为用户关心的是所有位不含5,而不是数字唯一。 - 引用[2]:计算阶乘和的代码,似乎与问题无关。 - 引用[3]:计算包含数字1的整数个数的代码。这涉及到数字的每一位的检查,但用户是要排除数字5。 用户的问题具体是:“计算第n个所有位不含5的数字的方法”。所以,我需要找到一个方法,给定n,输出第n个所有位都不包含数字5的正整数。例如,第1个不含5的数字是1,第2个是2,...第5个是6(因为5被跳过),第6个是7,依此类推。 为了回答这个问题,我应该: 1. **理解问题**:序列是所有不含数字5的正整数,按从小到大排列。找到第n个这样的数字。 2. **参考提供的引用**:引用[1]是关于数字唯一性的计数,但思路可能类似,因为它涉及数字的每一位。引用[3]是关于检查包含特定数字(这里是1)的数字,但用户是要排除数字5。 3. **生成一个方法**:我需要描述一个算法来计算第n个不含5的数字。 - **思路**:这类似于使用九进制系统,因为数字0-9中排除5后,有9个可能的数字:0,1,2,3,4,6,7,8,9。但数字不能以0开头,所以对于k位数,第一位有8个选择(1-4,6-9),其他位有9个选择(0-4,6-9)。 - **计数序列**:序列是1,2,3,4,6,7,8,9,10,11,12,13,14,16,...(跳过5,15,25等)。 - **计算第n个**:一种方法是计算直到某个点的不含5的数字个数,然后二分搜索或直接构造。 4. **类似问题**:引用[1]中的代码处理了从最高位开始计数数字,考虑了数字的位数。引用[3]遍历所有数字检查条件。 5. **高效方法**:遍历所有数字可能低效,尤其是n大时。更好的方法是使用数字系统的概念。 - **九进制表示**:由于排除数字5,数字系统实际上有9个数字:0-4,6-9,但由于数字不能以0开头,我们需要调整。 - **步骤**: - 计算总共有多少不含5的数字小于某个值。 - 使用二分搜索找到第n个数字。 但用户要的是直接计算第n个的方法。 6. **直接构造**:可以将数字视为在基9系统中,但映射数字。标准基9使用数字0-8。映射:将0-4映射到0-4,5-8映射到6-9?等一下。 - 数字0-9排除5:有效数字:0,1,2,3,4,6,7,8,9。索引0到8。 - 对应基9的0-8:但基9数字0-8,映射到有效数字: - 0 -> 0 - 1 -> 1 - 2 -> 2 - 3 -> 3 - 4 -> 4 - 5 -> 6 - 6 -> 7 - 7 -> 8 - 8 -> 9 - 然后,数字不能以0开头,所以对于给定的n,我们需要找到对应的数字序列。 7. **计算第n个**: - 首先,计算有多少个不含5的数字。 - 总数字在k位以下:对于1位数,有8个(1-4,6-9)。 - 2位数:第一位8选择,第二位9选择,所以72。 - 3位数:8*9*9=648,依此类推。 - 因此,总不含5的数字小于10^k的个数可以计算。 - 然后,给定n,找到最小k使得总数字>=n。 - 然后,在那个k位数字中,找到第n个。 但更简单的方法是模拟数字系统。 **算法描述**: - 类似于将n转换为一个特殊基9数字。 - 但因为数字不能从0开始,我们需要调整。 - 思路:考虑数字是从1开始的序列。 - 步骤: 1. 计算每个位数的数字个数: - 设f(k)为k位不含5的数字个数。 - f(1) = 8 (1-4,6-9) - f(k) = 8 * 9^{k-1} for k >=1 2. 给定n,找到最小的m使得 sum_{k=1 to m} f(k) >= n。 - 这确定数字的位数。 3. 然后,在m位数字中,计算偏移:令offset = n - sum_{k=1 to m-1} f(k) - 1 (因为从0开始索引)。 4. 现在,offset 从0到f(m)-1。 5. 构造数字:第一位有8选择(1-4,6-9),所以第一位数字 = (offset / 9^{m-1}) 然后映射到实际数字。 - 设digit_values = [0,1,2,3,4,6,7,8,9] 对应索引0到8。 - 但第一位不能是0,所以对于第一位,索引范围0-7对应数字1,2,3,4,6,7,8,9。 - 映射:index 0->1, 1->2, 2->3, 3->4, 4->6, 5->7, 6->8, 7->9 - 其他位,映射:index 0->0,1->1,2->2,3->3,4->4,5->6,6->7,7->8,8->9 6. 所以,offset 可以看作一个基9数字,但第一位有限制。 - 首先,计算offset0 = offset - 第一位:idx1 = offset0 / (9^{m-1}) - 实际第一位数字:从 [1,2,3,4,6,7,8,9] 中取idx1-th元素 - 然后,offset1 = offset0 % (9^{m-1}) - 第二位:idx2 = offset1 / (9^{m-2}) - 实际数字:从 [0,1,2,3,4,6,7,8,9] 中取idx2-th元素 - 依此类推直到最后一位。 8. **实现**:在代码中,我们可以模拟这个过程。 9. **引用相关部分**:引用[1]中的计数方法类似,因为它也计算了不同位数的数字个数。引用[3]遍历检查每个数字,但效率低。 10. **回答结构**: - 解释问题。 - 描述方法。 - 提供伪代码或代码。 - 最后生成相关问题。 11. **代码示例**:类似引用[1]和[3],我可以用Java或C++写一个函数。 例如,在Java中: ```java public int findNthNo5(int n) { // Find the number of digits m int digits = 1; long total = 0; long prevTotal = 0; long base = 9; // for other digits // f(1)=8, f(2)=72, etc. while (total < n) { long count = (digits == 1) ? 8 : 8 * (long)Math.pow(9, digits - 1); prevTotal = total; total += count; if (total < n) { digits++; } } // Now, digits is the number of digits long offset = n - prevTotal - 1; // zero-based index in the m-digit numbers // Digits: first digit: 8 choices, others: 9 choices int[] digitMapFirst = {1,2,3,4,6,7,8,9}; // for first digit int[] digitMapOther = {0,1,2,3,4,6,7,8,9}; // for other digits int result = 0; long power = (long)Math.pow(9, digits - 1); // for the first digit for (int i = 0; i < digits; i++) { int idx; if (i == 0) { idx = (int)(offset / power); int digit = digitMapFirst[idx]; result = result * 10 + digit; offset %= power; } else { power /= 9; // adjust power for next digit if (power == 0) power = 1; // handle last digit idx = (int)(offset / power); int digit = digitMapOther[idx]; result = result * 10 + digit; offset %= power; } } return result; } ``` 注意:代码需要处理边界,例如当n小的时候。 12. **验证**:例如,n=1: 应该返回1;n=5: 应该是6(因为1,2,3,4,6);n=9: 应该是9;n=10: 应该是10(10不含5)。 13. **引用标识**:在解释中提到类似引用[1]的计数方法。 14. **生成相关问题**: - 1. 如何高效计算所有位不含特定数字数字序列的第n个元素? - 2. 所有位不含5的数字序列的性质和应用? - 3. 类似问题:计算所有位不含0的数字的第n个? 15. **完整回答**: - 解释问题。 - 描述方法。 - 提供代码示例。 - 引用。 - 相关问题。 </think>### 计算第n个所有位不含5的数字的方法 用户需要计算第n个所有位不含数字5的正整数序列中的元素。该序列包含所有十进制表示中每一位数字均不为5的正整数,从小到大排列。例如: - 序列几个元素:1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16,...(跳过5, 15, 25等含5的数字)。 - 当n=5时,应为6(序列:1,2,3,4,6);n=10时,应为10(序列:1,2,3,4,6,7,8,9,10)。 #### 方法描述 该问题可以通过**数字系统映射**高效解决。由于数字5被排除,有效数字集合为{0,1,2,3,4,6,7,8,9}(共9个数字),但数字不能以0开头。因此: 1. **数字位数确定**:先计算不同位数下不含5的数字个数,定位目标数字的位数$ m $。 - 1位数:8个(1-4,6-9) - $ m $位数:$ 8 \times 9^{m-1} $个(首位8选择;其余位各9选择) - 累计总数公式:$ S(m) = \sum_{k=1}^{m} 8 \times 9^{k-1} $ 2. **定位并构造数字**:给定n: - 找到最小位数$ m $,使得$ S(m) \geq n $。 - 计算在$ m $位数中的索引偏移量:$ \text{offset} = n - S(m-1) - 1 $(从0开始索引)。 - 将offset视为一个“基9”数字(但首位特殊处理),映射到有效数字: - 首位:映射索引到{1,2,3,4,6,7,8,9}(8选择) - 其他位:映射索引到{0,1,2,3,4,6,7,8,9}(9选择) 该方法避免了遍历所有数字的低效性(如引用[3]的方法),类似于引用[1]中对唯一数字计数的优化思路,但针对排除数字5做了调整[^1]。 #### 代码实现(Java) 以下Java函数计算第n个不含5的数字。时间复杂度为$ O(\log n) $,适用于大n。 ```java public class NthNoFiveNumber { public static int findNthNo5(int n) { if (n <= 0) return -1; // 无效输入处理 if (n <= 8) return n; // n=1至8直接返回(跳过5) // 步骤1: 确定数字位数m int digits = 1; long total = 8; // digits=1时的总数 long prevTotal = 0; // 存储digits-1位及以下的总数 while (total < n) { digits++; prevTotal = total; // 计算m位数的不含5数字个数: 8 * 9^{digits-1} long count = 8 * (long)Math.pow(9, digits - 1); total += count; } // 此时目标数字的位数为digits long
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值