【路飞】数据流中的第 K 大元素、数组中的第K个最大元素、查找和最小的 K 对数字、前K个高频单词

156 阅读5分钟

703. 数据流中的第 K 大元素

设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

请实现 KthLargest 类:

KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。 int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。 示例:

输入:
["KthLargest", "add", "add", "add", "add", "add"]
[[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]
输出:
[null, 4, 5, 5, 8, 8]

解释:
KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);
kthLargest.add(3);   // return 4
kthLargest.add(5);   // return 5
kthLargest.add(10);  // return 5
kthLargest.add(9);   // return 8
kthLargest.add(4);   // return 8

解题思路:因为数组是一组无序数组,因为求得是第k大元素,所以需要将数组排序,因为求得是最大数,所以可以根据大顶堆来实现,当data数组的长度达到k之后就开始跟下面数字判断,,代码如下:

var KthLargest = function(k, nums) {
    if(k == 0) return 0;
    //因为data cnt kk都是全局变量,所以需要清空下
    data = [];
    cnt = 0;
    //全局记录k
    kk = k;
    //因为nums是数组,所以一次将数据加入到data
    for(let i = 0; i < nums.length; i ++){
        KthLargest.prototype.add(nums[i]);
    }
    return ;
};
KthLargest.prototype.add = function(val) {
    //给data添加数据
    push_up(val);
    //后判断是否长度大于kk。如果大就删掉多余的数据,此处删的都是比data[0]大的数,data里已经有k个数了,剩下的数据谁大删谁,保持dada.length == k
    if(data.length > kk) pop_down();
    //此时的data.length == kk,堆顶元素就是第k大元素
    return data[0];
};
//以下代码就是维持大顶堆的特性,前面有文章介绍
let data = [],cnt = 0, kk = 0;
var push_up = function(val){
    data.push(val);
    let idx = cnt;//当前节点
    let gIdx = parseInt((idx - 1) / 2);//根节点
    while(idx && data[idx] < data[gIdx]){
        swap(data[idx],idx,data[gIdx],gIdx);
        idx = gIdx;
        gIdx = parseInt((idx - 1)/2);
    }
    cnt ++;
    return;
}

var pop_down = function(){
    if(data.length <= 1){
        data = [];
        cnt = 0;
        return;
    }
    //删除最大值,将尾结点放在开头,位置树结构
    data[0] = data.pop();
    cnt --;
    let idx = 0;//当前节点
    let n = cnt -1;//最大节点个数
    let zIdx = 2 * idx + 1;//左子节点
    while(zIdx <= n){
        let temp = idx;//三角区最小值下标
        if( data[idx] > data[zIdx] ) temp = zIdx;
        if(zIdx + 1 <= n && data[temp] > data[zIdx + 1]) temp = zIdx + 1;
        if(temp == idx) break;
        swap(data[idx],idx,data[temp],temp);
        idx = temp;
        zIdx = 2*idx +1;
    }
    return;
}

var swap = function(v1,i1,v2,i2){
    data[i1] = v2;
    data[i2] = v1;
}

215. 数组中的第K个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6]k = 4
输出: 4

解题思路:这题跟上一题类似,求第k个最大数,其实就是将数组从小到大后,取第k个最大数,就是取第倒数第k个数,所以用小顶堆来时先,只留下大于k的数,当数组data.length == k的时候,剩下的数字跟堆顶元素比较,谁小删谁,代码如下:

var findKthLargest = function(nums, k) {
   //重置全局变量
    data = [],cnt = 0;
    //依次将无序数组nums加入新数组data
    for(let i = 0; i < nums.length; i ++){
       //当数组长度小于k的时候,直接新增就好,不等就跟堆顶元素比较是否新增
        if(data.length >= k){
            //当前数字如果大于堆顶元素就替换data中最小元素,新增加然后删除堆顶元素
            if(nums[i] > data[0]){
                push_up(nums[i]);
                pop_down();
            }
        }else{
           //新增元素,并保持大顶堆特性,堆顶元素最大
            push_up(nums[i]);
        }
    }
    return data[0];
};
//以下代码是维护小顶堆特性,前面文章有介绍
let data = [],cnt = 0, kk = 0;
var push_up = function(val){
    data.push(val);
    let idx = cnt;//当前节点
    let gIdx = parseInt((idx - 1) / 2);//根节点
    while(idx && data[idx] < data[gIdx]){
        swap(data[idx],idx,data[gIdx],gIdx);
        idx = gIdx;
        gIdx = parseInt((idx - 1)/2);
    }
    cnt ++;
    return;
}

var pop_down = function(){
    if(data.length <= 1){
        data = [];
        cnt = 0;
        return;
    }
    //删除最大值,将尾结点放在开头,位置树结构
    data[0] = data.pop();
    cnt --;
    let idx = 0;//当前节点
    let n = cnt -1;//最大节点个数
    let zIdx = 2 * idx + 1;//左子节点
    while(zIdx <= n){
        let temp = idx;//三角区最小值下标
        if( data[idx] > data[zIdx] ) temp = zIdx;
        if(zIdx + 1 <= n && data[temp] > data[zIdx + 1]) temp = zIdx + 1;
        if(temp == idx) break;
        swap(data[idx],idx,data[temp],temp);
        idx = temp;
        zIdx = 2*idx +1;
    }
    return;
}

var swap = function(v1,i1,v2,i2){
    data[i1] = v2;
    data[i2] = v1;
}

373. 查找和最小的 K 对数字

给定两个以 升序排列 的整数数组 nums1 和 nums2 , 以及一个整数 k 。

定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2 。

请找到和最小的 k 个数对 (u1,v1),  (u2,v2)  ...  (uk,vk) 。

示例 1:

输入: nums1 = [1,7,11], nums2 = [2,4,6], k = 3
输出: [1,2],[1,4],[1,6]
解释: 返回序列中的前 3 对数:
     [1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]

示例 2:

输入: nums1 = [1,1,2], nums2 = [1,2,3], k = 2
输出: [1,1],[1,1]
解释: 返回序列中的前 2 对数:
     [1,1],[1,1],[1,2],[2,1],[1,2],[2,2],[1,3],[1,3],[2,3]

示例 3:

输入: nums1 = [1,2], nums2 = [3], k = 3 
输出: [1,3],[2,3]
解释: 也可能序列中所有的数对都被返回:[1,3],[2,3]

解题思路:这题求最小几个数,索引用到的是大顶堆,这题上面题相比较只要加一个方法来判断当前数组和是否大于根节点数据和,如果大于就更换当前节点和根节点,代码如下:

var kSmallestPairs = function(nums1, nums2, k) {
    data = [],cnt = 0;
    //将nums1 和 num2组成数组,添加到data数组
    for(let i = 0; i < nums1.length; i ++){
        for(let j = 0; j < nums2.length; j ++){
            //当data的长度小于k的时候新增,不等于就判断当前数组之和是否大于根节点之和,谁大删除谁
            if(data.length >= k){
                //compare()方法是判断两个数组之和大小的,如果当前数组的和小于data根节点之和,就先新增,然后将大的去掉
                let isPush =  compare([nums1[i],nums2[j]],data[0]);
                if(!isPush){
                    push_up([nums1[i],nums2[j]]);
                    pop_down();
                } else {
                    break;
                }
            }else{
                push_up([nums1[i],nums2[j]])
            }
        }
    }
    return data;
};
var compare = function(num1,num2){
    if(!num2) return true;
    return num1[0] + num1[1] > num2[0] + num2[1];
}
let data = [],cnt = 0;
var push_up = function(val){
    data.push(val);
    let idx = cnt;//当前节点
    let gIdx = parseInt((idx - 1) / 2);//根节点
    //这里之比较的是当前节点和根节点的值大小,现在比较的是数组和的大小,然后决定是否交换
    let idxAnd = compare(data[idx],data[gIdx]);
    while(idx && idxAnd){
        swap(data[idx],idx,data[gIdx],gIdx);
        idx = gIdx;
        gIdx = parseInt((idx - 1)/2);
        idxAnd = compare(data[idx],data[gIdx]);
    }
    cnt ++;
    return;
}

var pop_down = function(){
    if(data.length <= 1){
        data = [];
        cnt = 0;
        return;
    }
    //删除最大值,将尾结点放在开头,位置树结构
    data[0] = data.pop();
    cnt --;
    let idx = 0;//当前节点
    let n = cnt -1;//最大节点个数
    let zIdx = 2 * idx + 1;//左子节点
    while(zIdx <= n){
        let temp = idx;//三角区最大值下标
        let idxAnd = compare(data[idx],data[zIdx])
        if( !idxAnd ) temp = zIdx;
        let idxAnd2 = compare(data[temp],data[zIdx + 1]);
        if(zIdx + 1 <= n && !idxAnd2) temp = zIdx + 1;
        if(temp == idx) break;
        swap(data[idx],idx,data[temp],temp);
        idx = temp;
        zIdx = 2*idx +1;
    }
    return;
}

var swap = function(v1,i1,v2,i2){
    data[i1] = v2;
    data[i2] = v1;
}

692. 前K个高频单词

给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。

返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。

示例 1:

输入: words = ["i", "love", "leetcode", "i", "love", "coding"], k = 2
输出: ["i", "love"]
解析: "i""love" 为出现次数最多的两个单词,均为2次。
    注意,按字母顺序 "i""love" 之前。

示例 2:

输入: ["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"], k = 4
输出: ["the", "is", "sunny", "day"]
解析: "the", "is", "sunny""day" 是出现次数最多的四个单词,
    出现次数依次为 4, 3, 21 次。

解题思路:本题求得是最大几个数,所以用小顶堆处理,
1.先统计每个单词的出现的数量,然后根基统计的数量维持小顶堆特性
2.如果单词出现次数相等,谁单词小小谁大,代码如下:

var topKFrequent = function(words, k) {
    //统计每个单词出现次数
    let ans = {};
    //初始化全局变量
    data = [];
    cnt = 0;
    //统计单词出现次数
    for(let i = 0; i < words.length; i ++){
        if(ans[words[i]]) ans[words[i]] += 1; 
        else ans[words[i]] = 1;
    }
    //遍历单词,我们可以根据单词获得单词数量
    for(let i in ans){
        if(data.length >= k){
            //compare()判断单词出现的次数和相等时单词大小
            if(compare(i,data[0],ans)){
              pop_down(ans);
              push_up(i,ans);
           }
        } else {
           push_up(i,ans)
        }
    
    }
    let data2 = [];
    while(data.length > 0){
        data2.unshift(data[0]);
        pop_down(ans);
    }
    return data2;
};
let data = [],cnt = 0;

var push_up = function(val,ans){
    data.push(val);
    let idx = cnt;//当前节点
    let gIdx = parseInt((idx - 1) / 2);//根节点
    while(idx){
        if(ans[data[idx]] < ans[data[gIdx]] || (ans[data[idx]] == ans[data[gIdx]] && data[idx] > data[gIdx])){
            swap(data[idx],idx,data[gIdx],gIdx);
            idx = gIdx;
            gIdx = parseInt((idx - 1)/2);
        } else {
            break;
        }
    }
    cnt ++;
    return;
}

var pop_down = function(ans){
    if(data.length <= 1){
        data = [];
        cnt = 0;
        return;
    }
    //删除最大值,将尾结点放在开头,位置树结构
    data[0] = data.pop();
    cnt --;
    let idx = 0;//当前节点
    let n = cnt -1;//最大节点个数
    let zIdx = 2 * idx + 1;//左子节点
    while(zIdx <= n){
        let temp = idx;//三角区最小值下标
        if( compare(data[idx],data[zIdx],ans)) temp = zIdx;
        if(zIdx + 1 <= n && compare(data[temp],data[zIdx + 1],ans)) temp = zIdx + 1;
        if(temp == idx) break;
        swap(data[idx],idx,data[temp],temp);
        idx = temp;
        zIdx = 2*idx +1;
    }
    return;
}

var swap = function(v1,i1,v2,i2){
    data[i1] = v2;
    data[i2] = v1;
}

var compare = function(a,b,ans){
    if(ans[a] > ans[b] || (ans[a] == ans[b] && a < b)) return true;
    else return false
}