LeetCode進階1086-Hash思想

概要

本篇主要介紹LeetCode上第1086題,本篇素材主要來源於平常刷題習慣整理記錄,做爲數據結構算法開篇,但願更多的展示刷題思路,學習數據結構與算法的方法技巧。本篇會有個小「彩蛋」,「彩蛋」在筆者這裏的定義是能夠提升算法執行效率的小技巧,後續關於數據結構與算法的系列推文中,筆者也會盡量多的埋一些「彩蛋」。千里之行,始於足下,共勉~算法

題意分析

1086 前五科的平均分

Given a list of scores of different students, return the average score of each student's top five scores in the order of each student's id.數組

Each entry items[i] has items[i][0] the student's id, and items[i][1] the student's score. The average score is calculated using integer division.bash

Example 1:微信

Input: [[1,91],[1,92],[2,93],[2,97],[1,60],[2,77],[1,65],[1,87],[1,100],[2,100],[2,76]] Output: [[1,87],[2,88]] Explanation: The average of the student with id = 1 is 87. The average of the student with id = 2 is 88.6. But with integer division their average converts to 88.數據結構

Note:學習

1 <= items.length <= 1000 items[i].length == 2 The IDs of the students is between 1 to 1000 The score of the students is between 1 to 100 For each student, there are at least 5 scoresui

翻譯版

給出一個不一樣學生的分數二維數組,根據每一個學生的id,將每一個學生的排在前五的平均分數返回。編碼

items[i]中items[i][0]表明學生的id,items[i][1]表明學生的得分。平均分數是用整數除法計算spa

例1:翻譯

輸入:[1,91],[1,92],[2,93],[2,97],[1,60],[2,77],[1,65],[1,87],[1,87][1100],[2100],[2,76]] 輸出:[[1,87],[2,88]] 解釋: id爲1的學生的平均數是87。 在id=2的學生中,平均爲88.6。可是隨着整除,平均值變爲88。

備註:

1 <= items.length <= 1000 Items[i].length == 2 學生的id在1到1000之間 這些學生的分數在1到100之間 每一個學生至少有5個得分

  • 本題在LeetCode上在Hash Table分類下

分析

結合LeetCode的官方分類能夠猜想出本題解題思路偏向Hash思想,故至少有一種思路是Hash方向,具體實踐時能夠嘗試不一樣的解題思路。因爲是從不少分數中選出分數最高的前5個計算平均分,所以天然能聯想到最大堆的基本數據結構,而Java標準庫提供了堆相關的優先隊列PriorityQueue,所以第二種思路考慮使用堆思想。

思路設計

方法一:Hash 思想

考慮題目描述id限制在1~1000之間,天然能夠聯想到Hash思想裏面比較經典桶結構,故考慮使用1~1001的桶結構來存儲不一樣id,數組下標即id。每一個id對應一個排序數組存儲前5排名的分數,具體實現思路以下:

一、二維數組int n[1001][5]存儲id,排前5名分數數組,分數數組降序排列。
     二、遍歷items數組:
       i.若n[i]爲null說明爲新id,建立n[i]=int[5]的排序數組,並將當前分數加入數組第1位;
       ii.不然,若score得分低於排序數組中最後一位n[i][4]最小值,則繼續循環;
       iii.不然,for循環遍歷排序數組,交換排序插入新分數;
     三、遍歷數組n,若n[i]數組爲null,說明當前桶中無數據即無對應學生id,循環繼續;
       不然,取出n[i]處排序數組,循環累加總分計算平均分,id和平均分存入數組。
複製代碼

方法二:堆排序

堆實現思路相對簡單,使用大小爲5的最小堆隊列,每次插入新值,移除隊頭的最小值,保證隊列是最大的5個數,這樣隊列中始終是排名前5的分數。遍歷完成後再計算每一個id對應堆隊列的平均分。爲何使用最小堆是由於Java標準庫中的堆隊列PriorityQueue的設定就是最小堆(標準庫:->_-> 怪我咯),堆實現相對簡單所以不做詳細思路拆分,直接上代碼。

編碼實踐

Hash實現

public int[][] highFive(int[][] items) {
	int n[][] = new int[1001][];
	for (int i = 0; i < items.length; ++i) {
		int id = items[i][0];
		int score = items[i][1];
		if (n[id] == null) {
			n[id] = new int[5];
			n[id][0] = score;
			continue;
		}
		if (score <= n[id][4]) {
			continue;
		}
		for (int j = 0; j < 5; j++) {
			if (score > n[id][j]) {
				int temp = n[id][j];
				n[id][j] = score;
				score = temp;
			}
		}
	}
	List<int[]> result = new ArrayList<>();
	for (int i = 0; i < 1001; ++i) {
		if (n[i] == null) {
			continue;
		}
		int total = 0;
		for (int score : n[i]) {
			total += score;
		}
		result.add(new int[] { i, total / 5 });
	}
	return result.toArray(new int[0][0]);
}
複製代碼

堆實現

public int[][] highFive(int[][] items) {
	List<Integer> ids = new ArrayList<Integer>();
	HashMap<Integer, PriorityQueue<Integer>> map = new HashMap<Integer, PriorityQueue<Integer>>();
	for (int i = 0; i < items.length; ++i) {
		PriorityQueue<Integer> queue = map.get(items[i][0]);
		if (queue == null) {
			queue = new PriorityQueue<Integer>(5);
			map.put(items[i][0], queue);
			ids.add(items[i][0]);
		}
		queue.add(items[i][1]);
		if (queue.size() > 5) {
			queue.poll();
		}
	}
	int result[][] = new int[ids.size()][2];
	for (int i = 0; i < ids.size(); ++i) {
		PriorityQueue<Integer> queue = map.get(ids.get(i));
		int sum = 0;
		while (!queue.isEmpty()) {
			sum += queue.poll();
		}
		result[i][0] = ids.get(i);
		result[i][1] = sum / 5;
	}
	return result;
}
複製代碼

堆-提交結果

彩蛋

仔細觀察上述兩種思路的實現代碼會發現,有幾處for循環的時候使用的都是++i,而不是i++,這即是本篇當中的彩蛋。關於彩蛋後續不按期會有講解(廢話...彩蛋不就是下次才能看明白的麼->_->)

Alt

掃一掃 關注個人微信訂閱號
相關文章
相關標籤/搜索