一、215. 数组中的第K个最大元素
1.1 题目叙述
在未排序的数组中找到第 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 ≤ 数组的长度。
1.2 代码
1.2.1暴力+API
package Sort;
import java.util.Arrays;
/**
* @author : lkw
* @data : 2021/3/11 10:32
* @description :215. 数组中的第K个最大元素(难度中等)
* 方法一:暴力解法
* 时间复杂度:O(nlogn),此解法时间消耗为排序,jdk默认的是快速排序,
* 空间复杂度:O(1),使用的是原地排序
*
**/
public class findKthLargest_01 {
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
return nums[nums.length-k];
//若有5个元素,第二大,就是nums[5-2]
//若有5个元素,第四大就是nums[5-4],即nums[nums.length-k]
}
}
1.2.2 快排(不推荐使用)
package Sort;
/**
* @author : lkw
* @data : 2021/3/11 11:54
* @description :215. 数组中的第K个最大元素(难度中等)
* 方法二:快速排序
**/
/*在程序运行开始前就知道数组的元素(所有的元素都是机前确定,并且不变),可以使用快速选择排序,
// 若是动态数据流的情况,则只能使用堆排序
//快速选择:即快排中轴值计算的过程,小于轴值的放在左侧,大于的放于右侧
一轮排序后,通过排序后轴值下标与k之间的关系,选定轴值左侧或右侧,进行下一步操作
**/
public class findKthLargest_02 {
public static void main(String[] args) {
int[] nums = {23,46,20,8,11,18};
System.out.println(findKthLargest(nums, 2));
}
public static int findKthLargest(int[] nums, int k){
int left= 0;
int right= nums.length-1;
int target = nums.length-k;
while (true){
int index = partition(nums,left,right);
if (index==target){
return nums[index];
}
else if(index<target){
left=index+1;
}
else right=index-1;
}
}
public static int partition(int[] nums,int left,int right){
int pivot = nums[left];
int j=left;
for (int i = left+1; i <= right; i++) {
if (nums[i]<pivot){
j++;
swap(nums,i,j);
}
}
swap(nums,j,left);
return j;
}
public static void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i]= nums[j];
nums[j] = temp;
}
}
1.2.3 优先队列(容量为len,易理解)
补充知识:Lambda表达式
Lambda表达式:接受2个参数(数字),并返回他们的差值 (x, y) -> x – y
Java Lambda 表达式:Java Lambda 表达式 | 菜鸟教程
小顶堆:每个结点的值都小于或等于其左右孩子结点的值
小顶堆的比较器:(a, b) -> a - b
大顶堆的比较器:(a, b) -> b - a
- poll:将首个元素从队列中弹出,如果队列是空的,就返回null
- peek:查看首个元素,不会移除首个元素,如果队列是空的就返回null
思路:创建一个为len的小顶堆,输出len-k个最小元素后,堆中剩余k个最大元素,则堆顶是则是第k大元素。
小顶堆:每个结点的值都小于或等于其左右孩子结点的值
package Sort;
/**
* @author : lkw
* @data : 2021/3/11 12:14
* @description :优先队列(1)
**/
import java.util.PriorityQueue;
import java.util.Random;
/**
* 创建一个为len的小顶堆,输出len-k个元素后,堆中剩余k个最大元素,则堆顶是则是第k大元素
*/
public class findKthLargest_03 {
public static void main(String[] args) {
int[] nums = {23,20,0,8,11,18};
System.out.println(findKthLargest(nums, 2));
}
public static int findKthLargest(int[] nums, int k) {
int len = nums.length;
PriorityQueue<Integer> priorityQueue= new PriorityQueue<>(len,(a,b)->(a-b));
for (int i = 0; i <len ; i++) {
priorityQueue.add(nums[i]);
}
for (int i = 0; i < len-k; i++) {
priorityQueue.poll();
}
return priorityQueue.peek();
}
}
1.2.4 优先队列 (容量为k+1)
若我们创建一个k+1的小顶堆,先放入k个元素
再将待插入的元素放入小顶堆,此时弹出一个堆顶元素,一直保持堆内还是k个最小元素,程序最后的栈顶就是最k大元素。
可以参考:力扣
package Sort;
import java.util.PriorityQueue;
/**
* @author : lkw
* @data : 2021/3/12 10:11
* @description :
**/
public class findKthLargest_03 {
public static void main(String[] args) {
//int[] nums = {23,46,20,8,11,18};
int[] nums = {3,2,1,5,6,4};
System.out.println(findKthLargest(nums, 2));
}
public static int findKthLargest(int[] nums, int k) {
/*优先队列思想:数组长度为len,输出第k大元素,即数组排序后[0...len-k-1][len-k...len-1],后半段k个元素中最小的元素
后半段k个元素中最小的元素,可以创建一个容量为k的小顶堆,最后输出堆顶元素。
但是现在数组时乱序:我们可以创建一个k的小顶堆,始终保持有k个
则先放入k个元素,堆满之后若此时元素若大于堆顶则先弹出在放入
若我们创建一个k+1的小顶堆,先放入k个元素
再将待插入的元素放入小顶堆,此时弹出一个堆顶元素,此时堆内还是k个最小元素
*/
PriorityQueue<Integer> priorityQueue= new PriorityQueue<>(k+1,(a,b)->(a-b));
//先放入k个元素
for (int i = 0; i < k; i++) {
priorityQueue.add(nums[i]);
}
for (int i = k; i < nums.length; i++) {
priorityQueue.add(nums[i]);
priorityQueue.poll();
}
return priorityQueue.peek();
}
}
//大顶堆
public static int findKthLargest(int[] nums, int k) {
int len = nums.length;
PriorityQueue<Integer> priorityQueue= new PriorityQueue<>(len,(a,b)->(b-a));//大顶堆
for (int i = 0; i < len; i++) {
priorityQueue.add(nums[i]);
}
for (int i = 0; i < k-1; i++) {
priorityQueue.poll();
}
return priorityQueue.peek();
}
以下为桶排序的三个题
二、347.出现频率最多的 k 个元素
2.1 题目描述
给定一个非空的整数数组,返回其中出现频率前 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 是数组的大小。
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
你可以按任意顺序返回答案。
补充:理解List集合add()和addAll()的区别可参考博文:
简单易懂List集合add()和addAll()的区别_IX的博客-CSDN博客
Map.get() 方法返回指定键所映射的值 。
Map.ketSet() 方法将获取 Map 集合的所有键名。
Add方法是将传入的参数作为当前List中的一个item存储,即使你传入一个List也只会令当前的List增加1个元素 。
AddAll是传入一个List,将此List中的所有元素加入到当前List中,也就是当前List会增加的元素个数为传入的List的大小。
2.2 代码
2.2.1 桶排序
具体题解力扣
- 借助 哈希表 来建立数字和其出现频率的映射,遍历一遍数组统计元素的频率。
- 频率统计结束后,设置若干个桶,每个桶放出现频率相同的值,即第i个桶,存储出现频率为i的数。
- 倒序遍历数组获取出现顺序从大到小的排列
补充:关于Map的博文:Map 综述(一):彻头彻尾理解 HashMap_Rico's Blogs-CSDN博客_hashmap
Map 是 Key-Value 对映射的抽象接口,该映射不包括重复的键,即一个键对应一个值。HashMap 是 Java Collection Framework 的重要成员,也是Map族(如下图所示)中我们最为常用的一种。简单地说,HashMap 是基于哈希表的 Map 接口的实现,以 Key-Value 的形式存在,即存储的对象是 Entry (同时包含了 Key 和 Value) 。
同样地,HashSet 也是 Java Collection Framework 的重要成员,是 Set 接口的常用实现类,但其与 HashMap 有很多相似之处。对于 HashSet 而言,其采用 Hash 算法决定元素在Set中的存储位置,这样可以保证元素的快速存取;
HashMap首先会调用Key的hashCode方法,然后基于此获取Key哈希码,通过哈希码快速找到某个桶,这个位置可以被称之为 bucketIndex。如果两个对象的hashCode不同,那么equals一定为 false;否则,如果其hashCode相同,equals也不一定为 true。所以,理论上,hashCode 可能存在碰撞的情况。
hashCode : Object 的 native方法 , 获取对象的哈希值,用于确定该对象在哈希表中的索引位置,它实际上是一个int型整数。
List、ArrayList和LinkedList
- ArrayList是Array(动态数组)的数据结构,LinkedList是Link(链表)的数据结构。
- List是一个接口,而ArrayList是List的实现类。因此List不能作为定义一个实例对象,只能作为引用。
当数列范围太大,或者不是整数时,我们可以使用桶排序,类似于计数排序。
所有的桶保存在ArrayList集合当中,每一个桶被定义成一个ArrayList。
桶排序的博文:漫画:什么是桶排序?_程序人生的博客-CSDN博客
for (int num:nums)中的num为nums中元素的值。
String reverse = new StringBuffer(a).reverse().toString(); //翻转字符a
package Sort;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* @author : lkw
* @data : 2021/3/15 9:06
* @description :347. 前 K 个高频元素
**/
public class topKFrequent_01 {
//思路一:拿一个数组n[k+1]记录0-k的次数,然后比较数组n[k+1]的大小
/*
Map.get() 方法返回指定键所映射的值
Map.keySet() 方法将获取 Map 集合的所有键名
Add方法是将传入的参数作为当前List中的一个item存储,即使你传入一个List也只会令当前的List增加1个元素
AddAll是传入一个List,将此List中的所有元素加入到当前List中,也就是当前List会增加的元素个数为传入的List的大小
*/
//1.先统计频率
public static void main(String[] args) {
int[] nums = {1,1,1,2,2,3};
System.out.println(topKFrequent(nums, 2));
}
public static int[] topKFrequent(int[] nums, int k) {
List<Integer> reslist = new ArrayList();
//使用map存入的是键值对,统计每个元素出现的频率,元素为键,频率为值
HashMap<Integer,Integer> map = new HashMap();
for (int num:nums){
if (map.containsKey(num)) {
map.put((Integer)num,map.get(num) + 1);
}
else
map.put((Integer)num,1);
}
//桶排序,频率作为数组下标,对于出现频率不同的元素,存入对于的数组下标
//有n+1个桶,因为元素出现的频率可能有0次,1次,2次.....n次 , 一共有n+1种可能
//
List<Integer>[] list = new List[nums.length+1];//创建了一个元素都为List型的数组
for (int key: map.keySet()){
//i为出现的频率
int i = map.get(key);
if (list[i]==null){
//防止空指针,防止i第一次进来的时候报错
//因为频率相同也可放入,因为array[i]也是一个数组列表
list[i]=new ArrayList<>();
}
//创建好数组列表后,再将数值放入
list[i].add((Integer)key);//注意:这里不是else
}
//倒序遍历数组,获得频率由大到小的次序
for (int i = list.length-1;i>=0 && reslist.size()<k;i--){
if (list[i]==null) continue;
reslist.addAll(list[i]);//为啥不是else?
}
System.out.println(reslist);
int[] resArray = new int[reslist.size()];
// int[] res = new int[k];
for (int i = 0; i < k; i++) {
resArray[i] = reslist.get(i);
}
return resArray;
}
}
时间复杂度:遍历数组,统计频率的时复为O(n),桶排序的时复为O(n+1),list转数组时复为O(n),因此最后的时间复杂度为O(n).
空间复杂度:O(n), map,list,relist,reArray所占用的内存。
三、451. 根据字符出现频率排序
3.1 题目描述
给定一个字符串,请将字符串里的字符按照出现的频率降序排列。(中等难度)
示例 1:
输入:"tree"
输出:"eert"
解释: 'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。
3.2 代码
3.2.1 桶排序
此题和上一题347题为一个思路
先统计频率,在桶排序,在组合字符串。
package Sort;
import java.util.ArrayList;
import java.util.HashMap;
/**
* @author : lkw
* @data : 2021/3/21 17:20
* @description :451. 根据字符出现频率排序(中等)
**/
public class frequencySort_01 {
public static void main(String[] args) {
String s = "cccaaaabcdw";
frequencySort(s);
}
public static String frequencySort(String s) {
char array[] = s.toCharArray();
//键为:char,值为频率
HashMap<Character, Integer> map = new HashMap();
for (int i = 0; i < array.length; i++) {
char key = array[i];
if (map.containsKey(key)) {
map.put(key, map.get(key) + 1);
} else
map.put(key, 1);
}
//System.out.println(map);//{r=1, t=1, e=2}
//创建桶
//有n+1个桶,因为元素出现的频率可能有0次,1次,2次.....n次 , 一共有n+1种可能
//第i个桶存出现频率为i的字符
ArrayList<Character>[] list = new ArrayList[array.length + 1];
for (char key : map.keySet()) {
int index = map.get(key);
if (list[index] == null) {
list[index] = new ArrayList();
}
list[index].add(key);
}
System.out.println(list[3]);
//组合字符
//建议使用StringBuilder,执行效率高
// String a = "";
//倒序扫描桶,因为频率高的在最后面
StringBuilder res = new StringBuilder();
for (int i=list.length-1;i>=0;i--) {
if (list[i] != null) {
for (char ch:list[i]){
for (int j = 0; j < i; j++) {
//a=a+ch;
res.append(ch);
}
}
}
}
//System.out.println(a);
//return a;
System.out.println(res.toString());
return res.toString();
}
}
时间复杂度:O(n). 统计频率需要 O(n);创建桶遍历 HashMap 需要 O(n);遍历桶的集合需要 O(n) *两个常数,依然是 O(n)。
空间复杂度:O(n). 建立 HashMap 需要 O(n);建立 n+1个桶需要 O(n)。
最后组合字符串用的string:
最后输出组合字符串用的StringBuilder:
补充:StringBuffer和StringBuilder的运行效率和内存占用几乎一致,这是因为二者都是对对象本身进行操作,不会生成新的字符串对象。二者的区别主要是StringBuffer是线程安全的,而StringBuilder是线程不安全的。
String是不可变的对象,每次对String进行操作的时候,都会生成一个新的对象,这会对系统的性能造成影响。
执行效率竟然如此之大,因此以后建议使用StringBuilder。
四、75. 颜色分类
4.1 题目描述
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。(难度中等)
示例 1:输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]示例 2:输入:nums = [2,0,1]
输出:[0,1,2]示例 3:输入:nums = [0]
输出:[0]示例 4:输入:nums = [1]
输出:[1]
4.2 代码
4.2.1 桶排序
package Sort;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
/**
* @author : lkw
* @data : 2021/3/21 20:36
* @description :75. 颜色分类
* 桶排序
**/
public class sortColors_01 {
public static void main(String[] args) {
int nums[] = {1,1,0,2,0,1,2,2,2};
sortColors(nums);
}
public static void sortColors(int[] nums) {
//统计频率,hashmap,key:数值,value:频率
HashMap<Integer,Integer> map = new HashMap();
for (int num:nums){
if (map.containsKey(num)){
map.put(num,map.get(num)+1);
}
else
map.put(num,1);
}
//System.out.println(map);
int t=0;
for (int key: map.keySet()){
for (int i =0;i<map.get(key);i++){
nums[t++]=key;
}
}
//System.out.println(Arrays.toString(nums));
}
}
时间复杂度:O(n), 遍历数组nums时构造HashMap为O(n),最后构建nums,遍历map用时O(n),因此为O(n)。
空间复杂度:O(n), map的大小为O(n)
4.2.2 数组+指针
此代码中: 0 的优先级> 1 的优先级 >2 的优先级
即时2先被填入,但是若最后值不为2,会被覆盖掉。
package Sort;
import java.util.Arrays;
/**
* @author : lkw
* @data : 2021/3/21 21:28
* @description :数组
**/
public class sortColors_02 {
public static void main(String[] args) {
int nums[] = {1, 1, 0, 2, 0, 1, 2, 2, 2};
sortColors(nums);
}
public static void sortColors(int[] nums) {
//zero表示数字0的右侧边界,one、two分别表示1 和 2 的右侧边界
int zero = 0;
int one = 0;
int two = 0;
int length = nums.length;
for(int i = 0; i < length; i++)
{
//记录下待处理的值
int temp = nums[i];
//不管怎样,我先给你填个2
nums[two] = 2;
two ++;
// <=1的话 1的右侧边界one要向右挪一格
if(temp <= 1)
{
nums[one] = 1;
one ++;
}
//为0的话 0的右侧边界zero要向右挪一格
if(temp == 0)
{
nums[zero] = 0;
zero ++;
}
}
//System.out.println(Arrays.toString(nums));
}
}
时间复杂度:O(n)
空间复杂度: O(1)
化简以上代码:
package Sort;
import java.util.Arrays;
/**
* @author : lkw
* @data : 2021/3/22 9:21
* @description :
**/
public class sortColors_03 {
public static void main(String[] args) {
int nums[] = {1, 1, 0, 2, 0, 1, 2, 2, 2};
sortColors(nums);
}
public static void sortColors(int[] nums) {
int zeao=0,one=0;
for (int i=0;i< nums.length;i++){
int tem=nums[i];
nums[i]=2;
if (tem<=1){
nums[one++]=1;
}
if (tem==0){
nums[zeao++]=0;
}
}
//System.out.println(Arrays.toString(nums));
}
}