js(60)~剑指 Offer 40. 最小的k个数

121 阅读2分钟

这个题看完题目我就知道我会写,但是因为对题目理解不到位,多走了弯路,最小的k个数,最开始没有考虑重复,然后考虑到重复以后 觉得最小的k个数不算重复,这样还是不对,比如 [0,1,1,2]最小的2个书 答案是[0,1],我觉得是[0,1,1]这样就打出来的多了.还有一点儿就是sort这个方法不是很清楚之前 默认不传值,按照第一位的数字大小排序,必须处理一下 image.png 这个题,常规思想比较简单,但是运行时间和占用内存都比较大啊,所以我看了其他解析,发现常用的还有两种方法,我就又学习了一种更优的解析.快速排序,有几种写法,我都看不懂,找了个我感觉好理解的代码,如下

方法一

// 快排思维
// 每次从[start, end]范围内的数组中随机选择一个标杆元素(代码里取得是第一个元素), 
// 然后把数组中所有小于标杆的放在数组左边,所有大于标杆的元素放在数组右边,
// 然后判断标杆元素的位置是否等于目标位置。如果目标位置小于当前位置,则继续在左边查找,如果目标位置大于当前位置,则继续在右边查找。
// 这样每次迭代都会缩小查找的范围。最理想的情况下时间复杂度是 O(logN)
// 2, 8, 1, 1, 0, 11, -1, 0 如果k  = 3 输出[-1,0,1]
 
var getLeastNumbers = function(arr, k) {
    let len = arr.length;
    if(!len || !k) return [];
    // 定义最开始和最后两个位置的下标
    let start = 0;
    let end = len - 1;
    // 寻找一次标杆元素的位置
    let index = quikSort(arr, start, end);
    // 如果标杆元素的位置不等k
    while(index !== k - 1) {
        // 标杆儿元素大于目标位置, 即目标位置小于标杆儿位置,则继续在左边查找
        if(index > k - 1) {
            end = index - 1;
        } else {
            start = index + 1;
        }
        index = quikSort(arr,start, end)
    }

    return arr.slice(0, index + 1)
};
// 2, 8, 1, 1, 0, 11, -1, 0 9 length8 如果k  = 3 输出[-1,0,1]
// 然后把数组中所有小于标杆的放在数组左边,所有大于标杆的元素放在数组右边,
function quikSort(arr, left, right) { // left-0,right-7
    let pivot = arr[left]; // d第一次是2
    while(left < right) {
        while(left < right && arr[right] >= pivot) {
            right--;
        }
        arr[left] = arr[right];
        while(left < right && arr[left] < pivot) {
            left ++;
        }
        arr[right] = arr[left];
    }
    // 
    arr[left]=pivot;
    return left;
}

方法二 最大堆

这个具体题解都写了


function swap(arr, i, j) {
    [arr[i], arr[j]] = [arr[j], arr[i]];
}

class MaxHeap {
    // constructor(arr = []) {
    //     this.container = [];
    //     if (Array.isArray(arr)) {
    //         arr.forEach(this.insert.bind(this));
    //     }
    // }
    constructor(arr = []) {
        // 初始化container 这里把空数组赋值
        this.container = [];
        // 把arr 一次入堆 判断有哦没有都行
        //  if (Array.isArray(arr)) {
            arr.forEach(this.insert.bind(this));
        // }
      
    }

    // 从底部插入
    insert(data) {
        const {container} = this;
        container.push(data);
        // 找到堆的最后一个元素 坐标
        let index = container.length - 1; 
        while(index) {
            // 找到当前坐标的父节点
            let parent = Math.floor((index - 1) / 2);
            // index 为左/右节点 如果不大于 父节点 就中止 因为最大堆父节点要比左/右节点大
            if(container[index] <= container[parent]) {
                break;
            }
            // 左/右节点大于父节点 就要和父节点调换位置
            swap(container, index, parent);
            // 为下一次while循环做准备 把父节点左右字节点就是往上移动
            index = parent;
        }
    }

    // 从顶部删除
    extract(){
        const {container} = this;
        // 判空 没有的话就停止
        if(!container.length) {
            return null;
        }
        // 交换堆的 最后一个(length-1)和头一个0的位置
        // swap(this.container, this.container.length - 1, 0)
        swap(container, 0, this.container.length - 1)

        //定义弹出去的值和弹出去以后container的长度 定义一个0元素和0元素的左子节点 2* + 1(先定义左子节点因为一定先有左子节点才会有右子节点)
        const res = this.container.pop();
        let len = this.container.length;
        let index = 0;
        let leftIndex = 2*index + 1;

        // 左子节点比container的长度小 就进入while循环
        while(leftIndex < len) {
// 若有右子节点并且右子节点比左子节点大 则把右子节点给左子节点
            let rightInx = 2*index + 2;
            if(rightInx<len && container[rightInx] > container[leftIndex]) {
                leftIndex = rightInx;
            }
         // 左节点 如果不大于 父节点index 就中止 因为最大堆父节点要比左/右节点大
            if(container[leftIndex] <= container[index]) {
                break;
            }

         //交换左子节点和父节点的位置
            swap(this.container, leftIndex, index);

         // 为下一步循环做准备重置index和leftIndex的值 把左子节点的值付给父节点相当于整体下移动 
         index = leftIndex;
         //然后重置左子节点
         leftIndex = 2*index + 1;
        }
        
    return res;

    }
    // 头节点 大顶堆cotainer有值 就返回头部0元素 否则返回null

    top() {
        if(!this.container.length) return null;
        return this.container[0]
    }

    
}


var getLeastNumbers = function(arr, k) {
    // 先写思路 求最小的k个数 则构建一个k个数的大顶堆, 最后求到数组中最小的k个数的大顶堆,因为顶堆元素就是大顶堆的最大值,所以比较剩下元素和堆顶元素,
    // 判空写不写都行
    // const length = arr.length;
    // if (k >= length) {
    //     return arr;
    // }
    // 1 先以数组钱k个值 构造大顶堆
    const heap = new MaxHeap(arr.slice(0,k));
    // 2 循环剩下的元素做对比
    for(let i = k; i < arr.length; i++) {
        // 元素比顶堆元素小则替换,否则不变
        if(arr[i] < heap.top()) {
            // 弹出堆顶
            heap.extract()
            // 把元素入队
            heap.insert(arr[i])
        }
    }
    // 返回大顶堆
    return heap.container;

};

力扣本题传送门