每日一题:1619. 删除某些元素后的数组均值

136 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

题目链接:1619. 删除某些元素后的数组均值

题目描述【easy】

image.png

解题思路

1.初见

乍一看,这题第一思路就是直接排序,采用快速排序,变态一点使用原地快排,时间复杂度O(NlogN),直接搞定;后来仔细审题,发现是要去除给定数组的前5%以及后5%的数之后取平均数,在想是不是有更快的方式,理所当然的觉得会有O(NlogN)更优即O(N)级别的方法。

代码-1.初见-原地快排

/**
 * QuickSort Internal
 * 1.select pivot
 * 2.right to left find first item lt pivot rj
 * 3.left to right find first item gt pivot li
 * 4.swap rj li
 * 5.repeat 2-4 until i >= j
 */
private void quickSortInternal(int[] arr, int lo, int hi) {

    if (lo >= hi) {
        return;
    }

    int pivot = arr[lo];
    int i = lo;
    int j = hi;
    while (i < j) {

        while (i < j && arr[j] > pivot) {
            j--;
        }

        while (i < j && arr[i] <= pivot) {
            i++;
        }

        if (i < j) {
            swapItem(arr, i, j);
        }
    }

    swapItem(arr, lo, i);

    quickSortInternal(arr, lo, i-1);
    quickSortInternal(arr, i+1, hi);
}

private void swapItem(int[] arr, int lo, int hi) {
    int tmp = arr[lo];
    arr[lo] = arr[hi];
    arr[hi] = tmp;
}

2.探索

题中要求是5%,且另有约束为数组长度为20的倍数。K*20*5%=K,岂不美哉,这不是送上门的TOP-K问题嘛,最大、最小可以视为同一个问题,这里拆分为两个TOP-K、MIN-K减少一次数组遍历;

TOP-K问题经典解法之一为大根堆、小根堆,时间复杂度为O(NlogK)。 此处解法写的比较丑陋,没有使用堆,使用数组方式维护TOP-K元素,整体时间复杂度为O(N*N/20*2)。 进一步优化最终可以做到O(N*logN/20*2) ≈ O(NlogN)。

代码-2.探索

class Solution {

    public double trimMean(int[] arr) {
        // 边界条件
        if (arr.length < 20 || arr.length % 20 != 0 || arr.length > 1000) {
            return 0L;
        }
        // 获取TOP-K、MIN-K的结果,在遍历的过程中计算totalSum
        double total = 0l;
        // 1.预处理K的数值
        int kValue = arr.length / 20;
        int[][] kArr = new int[2][kValue]; // 记录TOP-K、MIN-K
        for (int index = 0; index < kValue; index++) {
            kArr[1][index] = -1;
        }
        
        // 2.遍历数组
        for (int tmp : arr) {
            // 计算总和
            total += tmp;
            // 3.1 TOP-K
            int topMinIndex = 0;;
            while (topMinIndex < kValue) {
                if (kArr[0][topMinIndex] < tmp) {
                    break;
                }
                topMinIndex++;
            }
            // 新的TOP-K元素出现
            if (topMinIndex < kValue) {
                for (int index = kValue-1; index > topMinIndex; index--) {
                    kArr[0][index] = kArr[0][index-1];
                }
                kArr[0][topMinIndex] = tmp;
            }
            // 3.2 MIN_K
            topMinIndex = 0;
            while (topMinIndex < kValue) {
                if (kArr[1][topMinIndex] == -1 || kArr[1][topMinIndex] > tmp) {
                    break;
                }
                topMinIndex++;
            }
            // 新的MIN-K元素出现
            if (topMinIndex < kValue) {
                for (int index = kValue-1; index > topMinIndex; index--) {
                    kArr[1][index] = kArr[1][index-1];
                }
                kArr[1][topMinIndex] = tmp;
            }

        }

        // 4. 计算最终结果
        for (int index = 0 ; index < kValue; index++) {
            total -= kArr[0][index];
            if (kArr[1][index] != -1) {
                total -= kArr[1][index];
            }
        }
        return total / (arr.length - 2*kValue);
    }
}
  • 时间复杂度:O(NlogN) vs O(NlogN)【原地快排】
  • 空间复杂度:O(N) vs O(1)【原地快排】

3.归途

上述结果显示,直接排序(1.初见)和TOP-K(2.探索)求解方式最终时间复杂度基本一致,且原地快排空间复杂度O(1)更优。

从业务场景角度考虑的话,TOP-K的方式可能更加适合,当数组长度不断增长时,只需要维护数组值总和外加对应的大根堆、小根堆;相比而言,快排在数组长度每发生一次变化时,就需要进行一次排序。

不同场景下有不同的解决办法,快排快速代码简单是其优势,实际场景应用若需要用到一些中间状态数组,则需要寻找其他的解法。

道阻且长,诸位共勉!