● 239. 滑动窗口最大值
1.前言
气鼠我了,理解了思路自己写写了快2小时还没写出来啊啊,bug频出,实在没心情总结了!!!!
只能说不愧是困难题,明天一定更新出来,外加11天的!!!!!气🐭我了
1.22日更新
(说实话我感觉写博客的时候记录心情可以帮助我更好的坚持下去,就像写日记一样,嘻嘻)
2.什么是deque
在Java中,`Deque`(双端队列)是一种允许我们从两端添加或移除元素的线性集合。它是“Double Ended Queue”的缩写,可以被当作栈(后进先出)或队列(先进先出)来使用。`Deque`是一个接口,它的实现类如`ArrayDeque`和`LinkedList`提供了实际的功能。
3.Deque的主要操作
1. **添加元素**:
- `addFirst(E e)`: 在双端队列的开头插入元素。
- `addLast(E e)`: 在双端队列的末尾插入元素。
- `offerFirst(E e)`: 在双端队列的开头插入元素,如果没有足够空间则返回`false`。
- `offerLast(E e)`: 在双端队列的末尾插入元素,如果没有足够空间则返回`false`。2. **移除元素**:
- `removeFirst()`: 移除并返回双端队列的第一个元素。
- `removeLast()`: 移除并返回双端队列的最后一个元素。
- `pollFirst()`: 移除并返回双端队列的第一个元素,如果双端队列为空则返回`null`。
- `pollLast()`: 移除并返回双端队列的最后一个元素,如果双端队列为空则返回`null`。3. **检查元素**:
- `getFirst()`: 返回双端队列的第一个元素。
- `getLast()`: 返回双端队列的最后一个元素。
- `peekFirst()`: 返回双端队列的第一个元素,如果双端队列为空则返回`null`。
- `peekLast()`: 返回双端队列的最后一个元素,如果双端队列为空则返回`null`。4. **栈操作**:
- `push(E e)`: 将元素推入双端队列表示的栈(相当于`addFirst(E e)`)。
- `pop()`: 移除并返回双端队列表示的栈的顶部元素(相当于`removeFirst()`)。
示例代码
import java.util.Deque;
import java.util.ArrayDeque;
public class DequeExample {
public static void main(String[] args) {
Deque<Integer> deque = new ArrayDeque<>();
// 添加元素
deque.addFirst(1);
deque.addLast(2);
// 访问元素
System.out.println("第一个元素: " + deque.getFirst()); // 输出 1
System.out.println("最后一个元素: " + deque.getLast()); // 输出 2
// 移除元素
System.out.println("移除的第一个元素: " + deque.removeFirst()); // 输出 1
System.out.println("移除的最后一个元素: " + deque.removeLast()); // 输出 2
}
}
在使用`Deque`时,请注意区分返回值为`null`和抛出异常的方法。例如,`addFirst`和`offerFirst`都会在队列的开头添加元素,但`addFirst`在无法添加时会抛出异常,而`offerFirst`则返回`false`。同理,`removeFirst`和`pollFirst`都会移除队列开头的元素,但`removeFirst`在队列为空时会抛出异常,而`pollFirst`则返回`null`。
● 347.前 K 个高频元素
1.堆的相关操作
在Java中,大顶堆(最大堆)和小顶堆(最小堆)通常可以通过`PriorityQueue`类来实现。`PriorityQueue`是基于优先级堆的无界队列,它可以根据提供的`Comparator`来决定元素的优先级顺序。默认情况下,`PriorityQueue`是一个小顶堆,但你可以通过自定义比较器使其成为大顶堆。
(1) 小顶堆(最小堆)
小顶堆中,父节点的值小于或等于其子节点的值。在Java的`PriorityQueue`中,默认情况下就是小顶堆。
1.创建小顶堆
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
2.添加元素
minHeap.offer(10);
minHeap.add(5);
minHeap.offer(20);
(2) 大顶堆(最大堆)
大顶堆中,父节点的值大于或等于其子节点的值。在Java中,通过提供自定义的比较器给`PriorityQueue`,可以创建一个大顶堆。
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
(3)注意事项
- `PriorityQueue`不是线程安全的。如果在多线程环境中使用,需要额外的线程同步措施。
- `offer`和`poll`方法在插入和移除元素时会调整堆,以保持堆的特性。
- `peek`方法用于查看队列头部的元素,但不会移除它。
- 在使用自定义对象时,确保实现了合适的比较逻辑,要么通过实现`Comparable`接口,要么通过提供`Comparator`。
(4)offer与add的区别
在Java的`PriorityQueue`类中,`offer`和`add`方法都用于向优先级队列中添加元素。这两个方法在大多数情况下是可以互换的,但它们之间有一些细微的差别:
1. **返回值和异常处理**:
- `add(E e)`: 这个方法在添加元素时,如果无法添加(例如,由于容量限制),会抛出一个`IllegalStateException`异常。它的返回类型是`boolean`,但在正常情况下总是返回`true`。
- `offer(E e)`: 这个方法在添加元素时,如果无法添加元素(例如,队列容量已满),不会抛出异常,而是返回`false`。这使得`offer`在容量受限的队列中更为安全。2. **使用场景**:
- 通常情况下,如果你确定队列会接受添加的元素(例如,使用的是基于链表的`PriorityQueue`,它实际上没有容量限制),那么`add`和`offer`都可以使用。
- 如果你使用的队列可能会由于容量限制而拒绝添加元素,或者你想更谨慎地处理添加元素的情况,那么最好使用`offer`。在实际应用中,由于`PriorityQueue`通常没有固定的大小限制,`add`和`offer`在功能上几乎是等价的。但是,`offer`通常被认为是更好的选择,因为它通过返回值而不是异常来表明操作的成功与否,这使得代码的逻辑更清晰,也更容易处理特殊情况。
2.自定义的Comparable
这里确实还不太熟悉,chat老师讲的很清晰,可以很好的理解
(1)chat老师概述
自定义`Comparable`接口是Java中的一个常用做法,用于定义对象的自然排序。这在需要将对象存储到排序集合(如`TreeSet`)或者使用排序算法(如`Collections.sort`)时特别有用。在您的例子中,`Comparable`用于定义如何比较`FreqElement`对象,以便在优先队列(`PriorityQueue`)中进行排序。
让我们详细地了解如何实现`Comparable`接口:
1. 实现`Comparable`接口
要自定义`Comparable`,您的类需要实现`Comparable`接口,并重写`compareTo`方法。`compareTo`方法定义了对象的排序逻辑。
```java
class FreqElement implements Comparable<FreqElement> {
int num; // 数字
int freq; // 频率
public FreqElement(int num, int freq) {
this.num = num;
this.freq = freq;
}
// 实现compareTo方法
@Override
public int compareTo(FreqElement other) {
// 在这里定义排序逻辑
}
}
```
2. 定义排序逻辑
`compareTo`方法返回一个整数,表示对象之间的比较结果:
- 如果当前对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
在您的例子中,我们希望根据频率`freq`对`FreqElement`对象进行排序。为了创建一个最小堆,我们需要定义较小的频率为较高的优先级。
```java
@Override
public int compareTo(FreqElement other) {
return this.freq - other.freq; // 最小的freq具有最高优先级
}
这里,`compareTo`方法通过减法来比较两个对象的`freq`值。如果当前对象的`freq`小于另一个对象的`freq`,它将返回一个负数,表示当前对象在优先队列中应该排在另一个对象之前。
3. 使用`FreqElement`对象
现在您的`FreqElement`类实现了`Comparable`接口,您可以将这些对象添加到优先队列中,并且队列会根据`freq`的值自动进行排序。
```java
PriorityQueue<FreqElement> minHeap = new PriorityQueue<>();
minHeap.add(new FreqElement(1, 5)); // num=1, freq=5
minHeap.add(new FreqElement(2, 3)); // num=2, freq=3
minHeap.add(new FreqElement(3, 10)); // num=3, freq=10
// 从队列中取出元素将按照freq的升序进行
FreqElement e = minHeap.poll(); // 将返回 num=2, freq=3 的对象
```
在这个例子中,优先队列`minHeap`会根据`FreqElement`对象的`freq`值排序,`freq`值最小的对象会首先从队列中被移除。
4.总结
通过实现`Comparable`接口和重写`compareTo`方法,您可以为您的对象定义自然排序。这在使用排序集合或队列时非常有用,可以根据您定义的排序逻辑自动排序对象。在您的例子中,这使得`PriorityQueue`可以根据`FreqElement`对象的频率来排序。
(2)细节解析
在Java中,`compareTo`方法用于定义两个对象之间的自然排序逻辑。在您的例子中,`compareTo`方法被用来比较`FreqElement`对象的`freq`属性,以决定哪个对象应该在优先队列中排在前面。
```java
PriorityQueue<FreqElement> minHeap = new PriorityQueue<>();
minHeap.add(new FreqElement(1, 5)); // num=1, freq=5
minHeap.add(new FreqElement(2, 3)); // num=2, freq=3
minHeap.add(new FreqElement(3, 10)); // num=3, freq=10
// 从队列中取出元素将按照freq的升序进行
FreqElement e = minHeap.poll(); // 将返回 num=2, freq=3 的对象
```
这里的逻辑解释如下:
- 当`this.freq`小于`other.freq`时,`this.freq - other.freq`的结果是一个负数。根据`Comparable`接口的约定,这表示`this`对象应该排在`other`对象之前。也就是说,在最小堆中,频率较小的元素具有更高的优先级。
- 当`this.freq`等于`other.freq`时,结果是`0`,表示这两个对象在排序时被视为相等。
- 当`this.freq`大于`other.freq`时,结果是一个正数,表示`this`对象应该排在`other`对象之后。总的来说,这个`compareTo`方法通过比较两个`FreqElement`对象的`freq`属性来确定它们在优先队列中的排序顺序。由于我们使用的是最小堆,所以`freq`值较小的元素会被赋予更高的优先级,从而排在队列的前面。这样,当从优先队列中取出元素时,我们总是首先得到`freq`值最小的那个元素。
3.Map遍历操作
总是记不清,记录在这里
for (Map.Entry<Integer, Integer> entry : map.entrySet())
4.Code
import java.util.*;
class Solution {
class FreqElement implements Comparable<FreqElement> {
int num;
int freq;
public FreqElement(int num, int freq) {
this.num = num;
this.freq = freq;
}
@Override
public int compareTo(FreqElement other) {
return this.freq - other.freq; // 最小堆
}
}
public int[] topKFrequent(int[] nums, int k) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
PriorityQueue<FreqElement> minHeap = new PriorityQueue<>();
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
FreqElement element = new FreqElement(entry.getKey(), entry.getValue());
if (minHeap.size() < k) {
minHeap.offer(element);
} else if (element.freq > minHeap.peek().freq) {
minHeap.poll();
minHeap.offer(element);
}
}
int[] result = new int[k];
for (int i = 0; i < k; i++) {
result[i] = minHeap.poll().num;
}
return result;
}
}
5.注意事项以及后续拓展题目
(1)为什么使用堆
这个主要是因为需要求前k个点排序而不要求对整体排序,类似题的比较类似于
链接
给定一个字符串
s
,根据字符出现的 频率 对其进行 降序排序 。一个字符出现的 频率 是它出现在字符串中的次数。返回 已排序的字符串 。如果有多个答案,返回其中任何一个。
但是这个题其实就可以不用堆了,我的做法是先用hashmap统计词频然后转换为list排序
总体来说虽然思想是符合的,但是相关的转换应用的还是不太好,例如几处需要注意的细节
(2)代码细节
List<Character> list = new ArrayList<Character>(result.keySet());
Collections.sort(list, (a, b) -> result.get(b) - result.get(a));
StringBuffer sb = new StringBuffer();
1.第一步
这个代码对我来说有点复杂,拆分一下:把所有的key都装载进入list。
在Java中,
result.keySet()
是Map
接口中的一个方法,用于获取映射(Map)的所有键(Key)的集合。
result.keySet()
2.第二步
arraylist的初始化
我一开始不知道可以这样初始化啊。。
以下是一些常用的初始化方法:
-
空的ArrayList:
List<String> list = new ArrayList<>();
-
带有初始容量的ArrayList(这可以提高性能,如果你知道将要存储的元素数量):
List<String> list = new ArrayList<>(50); // 初始容量为50
-
从已有集合初始化ArrayList:
Set<String> set = new HashSet<>(); set.add("Apple"); set.add("Banana"); List<String> list = new ArrayList<>(set); // 使用Set初始化ArrayList
-
使用Arrays.asList初始化:
List<String> list = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));
-
声明时直接初始化:
int[] array = new int[] {1, 2, 3, 4, 5};
-
声明并分配空间,然后初始化:
int[] array = new int[5]; array[0] = 1; array[1] = 2; array[2] = 3; array[3] = 4; array[4] = 5;
-
简化的初始化(在声明时不需要指定数组类型):
int[] array = {1, 2, 3, 4, 5};
-
使用Java 8特性,如Streams初始化:
Integer[] array = Stream.of(1, 2, 3, 4, 5).toArray(Integer[]::new);
3.第三步
排序,原理:
Collections.sort(list, (a, b) -> result.get(b) - result.get(a));
这行代码是Java中的一个常见模式,用于根据特定规则对一个列表(`list`)进行排序。它结合了`Collections.sort`方法和一个Lambda表达式,以定制排序逻辑。下面我将详细解释这行代码的工作原理:
### Collections.sort 方法
- `Collections.sort(List<T> list, Comparator<? super T> c)` 是Java集合框架的一部分。这个方法用于对指定的列表进行排序。
- 它需要两个参数:一个是要排序的列表(`list`),另一个是一个比较器(`Comparator`),用于定义列表中元素的排序逻辑。### Lambda 表达式
- `(a, b) -> result.get(b) - result.get(a)` 是一个Lambda表达式,它实现了`Comparator`接口的`compare`方法。
- 这个表达式接收两个参数`a`和`b`,它们是列表中的元素。这里假设`list`是一个包含可以作为`result`键的元素的列表。
- `result.get(b) - result.get(a)` 获取与`b`和`a`关联的值,并计算它们的差。### 排序逻辑
- `result.get(b) - result.get(a)` 的结果决定了`a`和`b`在列表中的排序顺序。具体来说:
- 如果`result.get(b)`大于`result.get(a)`,则结果为正数,表示在排序后的列表中`b`应该排在`a`之前。
- 如果`result.get(b)`等于`result.get(a)`,则结果为零,表示`a`和`b`的顺序无所谓。
- 如果`result.get(b)`小于`result.get(a)`,则结果为负数,表示在排序后的列表中`a`应该排在`b`之前。
- 这意味着列表最终会根据`result`映射中的值进行降序排序。### 举个例子
假设有一个`List<Integer> list`和一个`Map<Integer, Integer> result`,其中`list`包含一些整数,`result`将这些整数映射到它们的一些值。这行代码将会根据`result`中与列表元素相关联的值对`list`进行降序排序。
### 结论
这行代码是一种简洁且强大的方式,用于根据与列表元素相关联的映射值对列表进行排序。它展示了如何灵活地使用Java 8的Lambda表达式和`Collections`工具类。
(3)stringbuilder和stringbuffer的区别
StringBuilder
和StringBuffer
在Java中都用于创建可修改的字符串,但它们之间有一些关键的区别:
同步性:
StringBuffer
是线程安全的,这意味着它的所有公共方法都是同步的,可以在多线程环境中安全使用。如果一个StringBuffer
被多个线程同时访问,并且至少有一个线程修改了StringBuffer
的内容,那么它仍然保持数据的一致性和完整性。StringBuilder
不是线程安全的。它的方法没有被同步,因此它在单线程环境下的性能比StringBuffer
要好,因为它避免了同步带来的性能开销。但是,在多线程环境中,对StringBuilder
的操作可能不是安全的。性能:
- 由于
StringBuffer
中的方法是同步的,它在多线程操作中是安全的,但这也意味着它的性能通常会比StringBuilder
慢,尤其是在单线程环境中。StringBuilder
通常比StringBuffer
快,因为它避免了同步带来的开销。在单线程环境下,或者在多线程环境中对字符串操作不涉及多个线程共享同一StringBuilder
实例的情况下,StringBuilder
是一个更好的选择。使用场景:
- 如果需要在多线程环境中操作可变字符串,并且对线程安全有要求,应该使用
StringBuffer
。- 如果是在单线程环境中操作可变字符串,或者虽然是多线程但没有共享
StringBuilder
实例的情况,推荐使用StringBuilder
以获得更好的性能。两者在API和功能上非常相似,都提供了添加、删除、插入和反转字符串等操作。选择使用哪一个通常取决于程序是否运行在多线程环境中,以及对性能的要求。
(4)注意点
- 记得Character要大写
- result.get(不是getkey)
- s.length()要有括号。?有时候有有时候没有,奇怪
(5)Code
class Solution {
public String frequencySort(String s) {
HashMap<Character,Integer> result = new HashMap<>();
for (int i = 0;i < s.length();i++){
if (result.containsKey(s.charAt(i))){
int tmp = result.get(s.charAt(i));
tmp++;
result.put(s.charAt(i),tmp);
}else{
result.put(s.charAt(i),1);
}
}
List<Character> list = new ArrayList<Character>(result.keySet());
Collections.sort(list, (a, b) -> result.get(b) - result.get(a));
StringBuffer sb = new StringBuffer();
int size = list.size();
for (int i = 0; i < size; i++) {
char c = list.get(i);
int frequency = result.get(c);
for (int j = 0; j < frequency; j++) {
sb.append(c);
}
}
return sb.toString();
}
}
总结:
- 状态:未完结 - 百分之80
- 学习2小时左右
- 困难:很多
- 待解决问题:第一题
- 今日收获:
- 来源:代码随想录