215. 数组中的第K个最大元素(Medium)
在未排序的数组中找到第 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 总是有效的,且 1 ≤ k ≤ 数组的长度。
方法一:利用自带的排序算法
直接利用java自带的排序算法进行排序,然后返回数组中第k-1个元素即可
class Solution {
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
return nums[nums.length - k];
}
}
方法二:优先级队列
使用优先级队列,本质上和使用java自带的排序算法相同
class Solution {
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<>(); // 小顶堆
for (int val : nums) {
pq.add(val);
if (pq.size() > k) // 维护堆的大小为 K
pq.poll();
}
return pq.peek();
}
}
方法三:用快速选择来找到第k大的元素,快速选择是快速排序的变形,不同的是不用递归进左右两部分数组,只需要进其中一个就行,这样就有点类似于二分法查找元素了
class Solution {
public int findKthLargest(int[] nums, int k) {
int left = 0;
int right = nums.length - 1;
//二分法的思想
while(true){
int pos = partition(nums, left, right);
if(pos == k - 1){
return nums[pos];
}
if(pos > k - 1){
right = pos - 1;
}else{
left = pos + 1;
}
}
}
//partition过程
private int partition(int[] nums, int left, int right){
int pivot = nums[left]; //中轴值
int l = left + 1; //左边数组终点
int r = right; //右边数组起点
while(l <= r){
if(nums[l] < pivot && nums[r] > pivot){
//数组从大到小排列
swap(nums, l++, r--);
}
if(nums[l] >= pivot){
++l;
}
if(nums[r] <= pivot){
--r;
}
}
swap(nums, left, r); //讲left上的元素换到r上,就可以得到pivot左边元素大,右边小
return r;
}
//交换
private void swap(int[] nums, int l, int r){
int tmp = nums[l];
nums[l] = nums[r];
nums[r] = tmp;
}
}
347. Top K Frequent Elements(出现频率最多的k个元素)
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
说明:
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
解法一:小根堆
先使用HashMap建立起数字与次数的对应关系,然后使用堆排序,建立一个大小为k的小根堆,然后将元素添加进去,然后再 pop 出来
class Solution {
public List<Integer> topKFrequent(int[] nums, int k) {
HashMap<Integer,Integer> map = new HashMap();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
}
// 遍历map,用最小堆保存频率最大的k个元素
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return map.get(a) - map.get(b);
}
});
for (Integer key : map.keySet()) {
if (pq.size() < k) {
pq.add(key);
} else if (map.get(key) > map.get(pq.peek())) {
pq.remove();
pq.add(key);
}
}
// 取出最小堆中的元素
List<Integer> res = new ArrayList<>();
while (!pq.isEmpty()) {
res.add(pq.remove());
}
return res;
}
}
解法二:桶排序
先使用 HashMap 建立起数字与次数的对应关系,然后使用桶排序,
public class Solution {
public List<Integer> topKFrequent(int[] nums, int k) {
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
}
//桶排序,频率作为数组的下标,对于出现频率相同的数字,存入同个下标
List<Integer>[] bucket = new List[nums.length + 1];
for (Integer key : map.keySet()) {
int i = map.get(key); //获取数字出现的频率作为下标
if (bucket[i] == null){
bucket[i] = new ArrayList<Integer>();
}
bucket[i].add(key);
}
List<Integer> result = new ArrayList<Integer>();
//倒序遍历数组获取k个数
for (int i = bucket.length - 1; i >= 0 && result.size() < k; i--) {
if (bucket[i] == null){
continue;
}
result.addAll(bucket[i]);
}
return result;
}
}
451. 根据字符出现频率排序(Medium)
给定一个字符串,请将字符串里的字符按照出现的频率降序排列。
示例 1:
输入:
"tree"
输出:
"eert"
解释:
'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。
示例 2:
输入:
"cccaaa"
输出:
"cccaaa"
解释:
'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。
示例 3:
输入:
"Aabb"
输出:
"bbAa"
解释:
此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。
解法一:堆排序
题意是给定一个字符串,按照字符出现次数递减排序,跟上题思路类似,可以使用堆排序
class Solution {
public String frequencySort(String s) {
//自定义一个Item类
class Item{
public Item(Character character, Integer count) {
this.character = character;
this.count = count;
}
private Character character;
private int count;
}
//使用优先级队列存储,自定义比较器
Map<Character,Integer> map = new HashMap<>();
PriorityQueue<Item> priorityQueue = new PriorityQueue<>((item1,item2)->item2.count-item1.count);
for (int i=0;i<s.length();i++){
char c = s.charAt(i);
map.put(c,map.get(c)==null?1:map.get(c)+1);
}
map.forEach((k,v)->{
final Item item = new Item(k, v);
priorityQueue.offer(item);
});
StringBuilder stringBuilder = new StringBuilder();
while(!priorityQueue.isEmpty()){
final Item item = priorityQueue.poll();
for (int i = 0; i < item.count; i++) {
stringBuilder.append(item.character);
}
}
return stringBuilder.toString();
}
}
解法二:桶排序
使用桶排序,跟上题相同,先用HashMap统计好每个字符出现的次数,然后用一个数组存储,最后从后往前遍历存储进StringBuilder
public static String frequencySort(String s) {
HashMap<Character, Integer> map = new HashMap<Character, Integer>();
for (int i = 0; i < s.length(); i++) {
map.put(s.charAt(i), map.getOrDefault(s.charAt(i), 0) + 1);
}
List<Character>[] chs = new List[s.length() + 1];
for (Character c : map.keySet()) {
int fre = map.get(c);
if (chs[fre] == null){
chs[fre] = new ArrayList<Character>();
}
chs[fre].add(c);
}
StringBuilder sb = new StringBuilder();
List<Character> result = new ArrayList<Character>();
for (int i = chs.length - 1; i >= 0; i--) {
if (chs[i] == null){
continue;
}
for (int j = 0; j < chs[i].size(); j++) {
for (int l = 0; l < i; l++) {
sb.append(chs[i].get(j));
}
}
}
return sb.toString();
}
75. 颜色分类(Medium)
给定一个包含红色、白色和蓝色,一共 n个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:
不能使用代码库中的排序函数来解决这道题。
示例:
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
解法一:由于题目规定的颜色只有三种,用“0,1,2”表示,那么可以用扫描两遍数组,第一遍对颜色计数,第二遍按照颜色个数重写数组。
解法二:归并排序
该题属于典型的荷兰国旗问题,可以使用归并排序的思路,将一个数组分为三块,左边的是‘0’,中间的是‘1’,右边的是‘2’,设好2个指针zero=-1和two=nums.length,从前往后扫描,遇到0就与++zero交换,遇到1就i++,遇到2余--two交换。一遍之后就排序好了。
class Solution {
//for循环版本
public void sortColors(int[] nums) {
int zero = -1;
int two = nums.length;
for(int i = 0; i < nums.length; i++){
if(nums[i] == 0){
swap(nums, ++zero, i);
}else if(nums[i] == 2){
swap(nums, --two, i--);
}
}
}
//while循环版本
public void sortColors(int[] nums) {
int zero = -1;
int two = nums.length;
int cur = 0;
while(cur < two){
if(nums[cur] == 0){
swap(nums, ++zero, cur++);
}else if(nums[cur] == 2){
swap(nums, --two, cur);
}else{
cur++;
}
}
}
private void swap(int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
}
}