Amber-Leedcode-Java-代码随想录打卡第十三天 | ● 239. 滑动窗口最大值● 347.前 K 个高频元素

● 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个点排序而不要求对整体排序,类似题的比较类似于

链接

451. 根据字符出现频率排序

给定一个字符串 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的初始化

  1. 空的ArrayList:

    List<String> list = new ArrayList<>();
    
  2. 带有初始容量的ArrayList(这可以提高性能,如果你知道将要存储的元素数量):

    List<String> list = new ArrayList<>(50); // 初始容量为50
    
  3. 从已有集合初始化ArrayList:

    Set<String> set = new HashSet<>();
    set.add("Apple");
    set.add("Banana");
    List<String> list = new ArrayList<>(set); // 使用Set初始化ArrayList
    
  4. 使用Arrays.asList初始化:

    List<String> list = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));
    

Array的初始化

  1. 声明时直接初始化:

    int[] array = new int[] {1, 2, 3, 4, 5};
    
  2. 声明并分配空间,然后初始化:

    int[] array = new int[5];
    array[0] = 1;
    array[1] = 2;
    array[2] = 3;
    array[3] = 4;
    array[4] = 5;
    
  3. 简化的初始化(在声明时不需要指定数组类型):

    int[] array = {1, 2, 3, 4, 5};
    
  4. 使用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的区别

StringBuilderStringBuffer在Java中都用于创建可修改的字符串,但它们之间有一些关键的区别:

  1. 同步性:

    • StringBuffer 是线程安全的,这意味着它的所有公共方法都是同步的,可以在多线程环境中安全使用。如果一个StringBuffer被多个线程同时访问,并且至少有一个线程修改了StringBuffer的内容,那么它仍然保持数据的一致性和完整性。
    • StringBuilder 不是线程安全的。它的方法没有被同步,因此它在单线程环境下的性能比StringBuffer要好,因为它避免了同步带来的性能开销。但是,在多线程环境中,对StringBuilder的操作可能不是安全的。
  2. 性能:

    • 由于StringBuffer中的方法是同步的,它在多线程操作中是安全的,但这也意味着它的性能通常会比StringBuilder慢,尤其是在单线程环境中。
    • StringBuilder通常比StringBuffer快,因为它避免了同步带来的开销。在单线程环境下,或者在多线程环境中对字符串操作不涉及多个线程共享同一StringBuilder实例的情况下,StringBuilder是一个更好的选择。
  3. 使用场景:

    • 如果需要在多线程环境中操作可变字符串,并且对线程安全有要求,应该使用StringBuffer
    • 如果是在单线程环境中操作可变字符串,或者虽然是多线程但没有共享StringBuilder实例的情况,推荐使用StringBuilder以获得更好的性能。

两者在API和功能上非常相似,都提供了添加、删除、插入和反转字符串等操作。选择使用哪一个通常取决于程序是否运行在多线程环境中,以及对性能的要求。

(4)注意点

  1. 记得Character要大写
  2.  result.get(不是getkey)
  3.  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();


    }
}

总结:

  1. 状态:未完结 - 百分之80
  2. 学习2小时左右
  3. 困难:很多
  4. 待解决问题:第一题
  5. 今日收获:
  6. 来源:代码随想录
  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值