LeetCode703. 數(shù)據(jù)流中的第K大元素

題目描述

設(shè)計(jì)一個(gè)找到數(shù)據(jù)流中第K大元素的類(class)喧锦。注意是排序后的第K大元素室叉,不是第K個(gè)不同的元素步鉴。

你的 KthLargest 類需要一個(gè)同時(shí)接收整數(shù) k 和整數(shù)數(shù)組nums 的構(gòu)造器,它包含數(shù)據(jù)流中的初始元素术裸。每次調(diào)用 KthLargest.add旭蠕,返回當(dāng)前數(shù)據(jù)流中第K大的元素停团。

示例:

int k = 3;
int[] arr = [4,5,8,2];
KthLargest kthLargest = new KthLargest(3, arr);
kthLargest.add(3);   // returns 4
kthLargest.add(5);   // returns 5
kthLargest.add(10);  // returns 5
kthLargest.add(9);   // returns 8
kthLargest.add(4);   // returns 8

說明:

可以假設(shè) nums 的長度≥ k-1k ≥ 1。

這道題被打上的標(biāo)簽是堆的一種數(shù)據(jù)結(jié)構(gòu)掏熬,那么就用堆的思路去解決這道題佑稠。

堆的性質(zhì)有兩種:

  1. 左右節(jié)點(diǎn)的元素值不能大于父節(jié)點(diǎn)或者不能小于父節(jié)點(diǎn)的元素值;
  2. 堆是一種完全二叉樹

先了解堆

1550833872918.png

上面的數(shù)據(jù)結(jié)構(gòu)是一種二叉堆旗芬,更多的堆可以是三叉堆甚至d叉堆舌胶。。疮丛。

  • 最小堆:每個(gè)父節(jié)點(diǎn)都比左右節(jié)點(diǎn)的元素值要小
  • 最大堆:每個(gè)父節(jié)點(diǎn)都比左右節(jié)點(diǎn)的元素值要大

移除某個(gè)節(jié)點(diǎn)和堆頂一樣的幔嫂,判斷條件就是左右節(jié)點(diǎn)有沒有比父節(jié)點(diǎn)的元素更小的值辆它。

添加節(jié)點(diǎn)也是一樣的,因?yàn)槎咽且环N完全二叉樹履恩,底層采用數(shù)組的形式锰茉,添加節(jié)點(diǎn)加在數(shù)組的末尾,然后和父節(jié)點(diǎn)比較切心,如果比父節(jié)點(diǎn)小的則交換飒筑,直到堆頂。如果沒有比父節(jié)點(diǎn)小的則停止交換绽昏。

刪除堆頂.gif

過程

題目要求是找到排序后的第K大個(gè)元素值协屡,可以使用只有K長度的最小堆,堆頂?shù)脑刂祫t是最小的而涉。

初始化.gif

添加元素并返回堆頂元素值

kthLargest.add(5)
add添加元素并返回堆頂元素.gif
kthLargest.add(10)
siftdown.gif

最終代碼如下:

class KthLargest {
    private int k;
    MinHeap<Integer> arr;

    public KthLargest(int k, int[] nums) {
        this.k = k;
        // 只有k長度的最小堆
        arr = new MinHeap<>(k);
        for (int num : nums) {
            add(num);
        }
    }

    public int add(int val) {
        arr.heapify(val);
        return arr.peek();
    }

    /**
     * 自定義最小堆著瓶,底層采用數(shù)組數(shù)據(jù)結(jié)構(gòu)
     * E 數(shù)據(jù)類型需要繼承Comparable联予,具有數(shù)值比較的功能
     */
    private class MinHeap<E extends Comparable<E>> {
        private Array<E> data;

        public MinHeap() {
            data = new Array<>();
        }

        public MinHeap(int capacity) {
            data = new Array<>(capacity);
        }

        /**
         * 0
         * 1   2
         * 3   4   5   6
         * 父節(jié)點(diǎn) i
         * 左節(jié)點(diǎn) 2 * i + 1
         * 右節(jié)點(diǎn) 2 * i + 2
         *
         * @param index
         * @return 返回index父節(jié)點(diǎn)的索引
         */
        private int parent(int index) {
            if (index == 0) {
                throw new IllegalArgumentException("索引為0沒有父節(jié)點(diǎn)");
            }
            return (index - 1) / 2;
        }

        private int leftChild(int index) {
            return index * 2 + 1;
        }

        public void add(E e) {
            // 使用數(shù)組數(shù)據(jù)結(jié)構(gòu)添加元素置于末尾
            data.add(e);
            siftUp(data.getSize() - 1);
        }

        /**
         * 最小堆的性質(zhì):左右節(jié)點(diǎn)的元素值不能大于父節(jié)點(diǎn)的元素值
         *
         * @param index 當(dāng)前索引
         */
        private void siftUp(int index) {
            // 當(dāng)前節(jié)點(diǎn)與父結(jié)點(diǎn)進(jìn)行比較啼县,當(dāng)前節(jié)點(diǎn)比父節(jié)點(diǎn)小則進(jìn)行交換
            while (index > 0 && data.get(parent(index)).compareTo(data.get(index)) > 0) {
                data.swap(index, parent(index));
                index = parent(index);
            }
        }

        /**
         * 這里和Java中的PriorityQueue類 heapify 方法實(shí)現(xiàn)同樣的效果
         * ★★★★★
         * 根據(jù)題意
         * 如果待比較的元素比最小堆堆頂大,則替換
         *
         * @param e 待比較的元素值
         */
        public void heapify(E e) {
            if (peek() == null) {
                data.add(e);
                return;
            } else if (data.getSize() < k) {
                data.add(e);
                siftUp(data.getSize() - 1);
            } else if (peek().compareTo(e) < 0) {
                data.set(e);
                // 從堆頂開始
                siftDown(0);
            }
        }

        /**
         * @param index
         */
        private void siftDown(int index) {
            // while循環(huán)沸久,依據(jù)條件是是否存在左孩子
            while (leftChild(index) < data.getSize()) {
                int j = leftChild(index);
                // 尋找左右孩子的最小的元素值
                if (j + 1 < data.getSize() && data.get(j).compareTo(data.get(j + 1)) > 0) {
                    // 存在右孩子而且右孩子的元素值比左孩子的元素值更小
                    j++;
                }
                if (data.get(index).compareTo(data.get(j)) < 0) {
                    break;
                }
                // 運(yùn)行到此處說明存在左右孩子的元素值比父節(jié)點(diǎn)更小
                data.swap(index, j);
                index = j;
            }
        }

        /**
         * @return 返回頂節(jié)點(diǎn)
         */
        public E peek() {
            if (data.getSize() == 0) {
                return null;
            }
            return data.get(0);
        }

        public int getSize() {
            return data.getSize();
        }

        public boolean isEmpty() {
//            return getSize() == 0 ? true: false;
            return data.isEmpty();
        }
    }

    /**
     * 自定義數(shù)組季眷,數(shù)據(jù)類型為E
     */
    private class Array<E> {
        private E[] data;
        private int size;

        public Array(int capacity) {
            data = (E[]) new Object[capacity];
            size = 0;
        }

        public Array() {
            // 數(shù)組長度默認(rèn)為10
            this(10);
        }

        /**
         * @param index 當(dāng)前索引
         * @return 返回元素值
         */
        public E get(int index) {
            if (index < 0 || index > size - 1) {
                throw new IllegalArgumentException("數(shù)組越界");
            }
            return data[index];
        }

        public void set(E e) {
            set(0, e);
        }

        public void set(int index, E e) {
            data[index] = e;
        }

        /**
         * 添加元素
         *
         * @param index 索引
         * @param e     元素值
         */
        public void add(int index, E e) {
            // 為什么index > size 而不是 index > size - 1
            // 是因?yàn)樵谀┪惶砑訑?shù)據(jù)的時(shí)候剛好是在size上面,所以index最大為size
            if (index < 0 || index > size) {
                throw new IllegalArgumentException("index索引不在數(shù)組范圍內(nèi)");
            }
            // 擴(kuò)容
            if (size == data.length) {
                resize(2 * data.length);
            }
            // 將index索引后面的數(shù)值往后移一位
            for (int i = size; i > index; i--) {
                data[i] = data[i - 1];
            }
            data[index] = e;
            size++;
        }

        /**
         * 末尾添加元素
         *
         * @param e 元素值
         */
        public void add(E e) {
            add(size, e);
        }

        /**
         * 刪除元素
         *
         * @param index 索引
         * @return 返回被刪除的元素
         */
        public E remove(int index) {
            if (index < 0 || index > size - 1) {
                throw new IllegalArgumentException("index索引不在數(shù)組范圍內(nèi)");
            }
            E ret = data[index];
            // index索引后的元素往前移動一位
            for (int i = index; i < size - 1; i++) {
                data[index] = data[index + 1];
            }
            size--;
            data[size - 1] = null;
            // 縮容卷胯,而且數(shù)組為1的時(shí)候就不能再除2 子刮,1/2 = 0,長度為0 的數(shù)組就不能擴(kuò)容
            if (size == data.length / 4 && data.length / 2 != 0) {
                resize(data.length / 2);
            }
            return ret;
        }

        /**
         * 輸出末尾元素
         *
         * @return 返回被刪除的元素
         */
        public E remove() {
            return remove(size - 1);
        }

        private void resize(int newCapacity) {
            E[] newData = (E[]) new Object[newCapacity];
            for (int i = 0; i < size; i++) {
                newData[i] = data[i];
            }
            data = newData;
        }

        /**
         * @return 返回?cái)?shù)組的長度
         */
        public int getSize() {
            return size;
        }

        public boolean isEmpty() {
            return size == 0 ? true : false;
        }

        public void swap(int index1, int index2) {
            E tmp = data[index1];
            data[index1] = data[index2];
            data[index2] = tmp;
        }
    }
}

|(來自大數(shù)據(jù)架構(gòu)筆記公眾號)|

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窑睁,一起剝皮案震驚了整個(gè)濱河市挺峡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌担钮,老刑警劉巖橱赠,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異箫津,居然都是意外死亡狭姨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進(jìn)店門苏遥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饼拍,“玉大人,你說我怎么就攤上這事田炭∈Τ” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵教硫,是天一觀的道長司澎。 經(jīng)常有香客問我欺缘,道長,這世上最難降的妖魔是什么挤安? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任谚殊,我火速辦了婚禮,結(jié)果婚禮上蛤铜,老公的妹妹穿的比我還像新娘嫩絮。我一直安慰自己,他們只是感情好围肥,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布剿干。 她就那樣靜靜地躺著,像睡著了一般穆刻。 火紅的嫁衣襯著肌膚如雪置尔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天氢伟,我揣著相機(jī)與錄音榜轿,去河邊找鬼。 笑死朵锣,一個(gè)胖子當(dāng)著我的面吹牛谬盐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播诚些,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼飞傀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了诬烹?” 一聲冷哼從身側(cè)響起砸烦,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绞吁,沒想到半個(gè)月后幢痘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掀泳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年雪隧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片员舵。...
    茶點(diǎn)故事閱讀 38,747評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脑沿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出马僻,到底是詐尸還是另有隱情庄拇,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站措近,受9級特大地震影響溶弟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瞭郑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一辜御、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧屈张,春花似錦擒权、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泰涂。三九已至洗贰,卻和暖如春刻获,著一層夾襖步出監(jiān)牢的瞬間统捶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工屠凶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留翁都,地道東北人潮罪。 一個(gè)月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓姨谷,卻偏偏與公主長得像逗宁,于是被迫代替她去往敵國和親映九。 傳聞我的和親對象是個(gè)殘疾皇子梦湘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評論 2 350