首页 > 其他分享 >剑指offer_20230720

剑指offer_20230720

时间:2023-07-20 20:22:10浏览次数:35  
标签:offer 队列 双端 元素 PriorityQueue 插入 线程 20230720

剑指 Offer 59 - I. 滑动窗口的最大值

题目说明

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:

滑动窗口的位置 最大值


[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7

解题思路

本题的关键在于,后来的值是会对之前来的值造成影响的。具体的说就是,如果序列是[1, 2, 3],此时如果插入4,前面值之前记录的最大值都变成了4,之前维护的最大值也就没有意义了,可以舍弃。如果接下来又来了个3,依然不会对4及4前面的那些值造成影响,但是如果4出去之后最大值就不再是4了,因此要记录下来。

因此我们可以看出,我们需要维护的是一个单调递减的序列。且如果当队头就是最大值时,队列出队时辅助队列也需要poll。此外,如果当辅助队列的队尾和新来的值一样大时,也同样需要入队。因此我们需要对辅助队列的队尾队首都操作,我们需要一个双端队列。

此时还可以用一个技巧。因为同时需要对结果数组和给定数组进行定位,分别用 i 和 j 。实际上结尾数组相当于是从给定数组位置到达k时才开始计算,因此我们可以将i初始化为1 - k。当 i > 0之后,则用i - 1去和 deque.peek() 比较进行更新即可(出窗口的数字需要出队)

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int[] res = new int[nums.length - k + 1];
        Deque<Integer> deque = new LinkedList<Integer>();
        // i定位res, j定位nums
        for (int j = 0, i = 1 - k; j < nums.length; j++, i++) {
            // 针对出窗口元素进行处理
            if (i > 0 && nums[i - 1] == deque.peekFirst()) {
                deque.pollFirst();
            }
            // 入窗口,维护辅助队列单调性
            while (!deque.isEmpty() && deque.peekLast() < nums[j]) {
                deque.pollLast();
            }
            deque.offerLast(nums[j]);
            // 获得结果
            if(i >= 0) {
                res[i] = deque.peekFirst();
            }
        }
        return res;
    }
}

剑指 Offer 59 - II. 队列的最大值

题目说明

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。

若队列为空,pop_front 和 max_value 需要返回 -1

解题思路

和前一题基本的逻辑是相似的,同样是需要维护一个单调递减的辅助队列

区别在于出队的条件变了。之前是固定窗口大小,窗口内数字的数量是一定的。现在则是根据出队操作来触发

关于Queue和List

Queue

在 Java 中,我们可以使用 Queue 接口和它的实现类(如 LinkedList 或 PriorityQueue)来创建队列。插入操作通过 add()offer() 方法进行,删除操作通过 remove()poll() 方法进行。

addofferremovepoll 都用于元素的添加和删除。然而,他们在操作失败时的行为是有所不同的

addremove在操作失败时(如队列已满,队列为空)会抛出一个异常,offer失败时返回false,poll失败时会返回null。在实际情况中可以根据具体的需求来决定使用哪一种方法;

双端队列(Double Ended Queue)

Java 的 Deque 接口和它的实现类(如 ArrayDequeLinkedList)提供了双端队列的功能。

添加元素

  • addFirst(E e): 将指定元素插入此双端队列的开头。
  • addLast(E e): 将指定元素插入此双端队列的末尾。
  • offerFirst(E e): 在此双端队列的开头插入指定的元素。
  • offerLast(E e): 在此双端队列的末尾插入指定的元素。

删除元素

  • removeFirst(): 获取并移除此双端队列的第一个元素。
  • removeLast(): 获取并移除此双端队列的最后一个元素。
  • pollFirst(): 获取并移除此双端队列的第一个元素,若此双端队列为空,则返回 null。
  • pollLast(): 获取并移除此双端队列的最后一个元素,若此双端队列为空,则返回 null。

检索元素

  • getFirst(): 获取但不移除此双端队列的第一个元素。
  • getLast(): 获取但不移除此双端队列的最后一个元素。
  • peekFirst(): 获取但不移除此双端队列的第一个元素,若此双端队列为空,则返回 null。
  • peekLast(): 获取但不移除此双端队列的最后一个元素,若此双端队列为空,则返回 null。

LinkedList 和 ArrayList

  • ArrayList 是基于动态数组实现的,所以它在随机访问(通过索引查找)时非常快,时间复杂度为 O(1)。然而,由于需要大块连续的内存空间,当插入和删除元素时,可能需要移动其他元素以保持数组的连续,这使得其在插入和删除元素上的性能较差,尤其是在列表的中间或开头进行操作时,时间复杂度为 O(n)。

  • LinkedList 是基于双向链表实现的,所以它在元素的插入和删除(尤其是在列表的开头和结尾)上很快,时间复杂度为 O(1)。然而,由于需要遍历链表来查找元素,所以它在随机访问时的性能较差,时间复杂度为 O(n)。

因此,如果需要执行大量的随机访问操作,ArrayList 可能是更好的选择。如果你主要需要执行大量的插入和删除操作,那么 LinkedList 可能会更好。在实现队列时,由于队列主要在一端(队尾)添加元素,并从另一端(队头)删除元素,所以 LinkedList 通常是更好的选择。

优先队列(Priority Queue)

Java 的 PriorityQueue 类就是一个优先队列的实现。这个队列会根据元素的自然顺序或者比较器(Comparator)来决定出队顺序。

优先队列操作的具体方法和Queue是相同的

主要特性:

  • 元素排序PriorityQueue 中的元素默认会按照自然顺序(即数字从小到大、字母从 A 到 Z)进行排序。也就是说,默认情况下,优先级最小的元素最先出队。我们也可以通过提供一个 Comparator 对象来自定义元素的排序规则。

  • 非线程安全PriorityQueue 不是线程安全的。如果需要在多线程环境中使用优先队列,可以考虑使用 PriorityBlockingQueue

自定义比较器:

// 创建一个优先队列,然后按照字符串长度的升序进行排序
PriorityQueue<String> queue = new PriorityQueue<>(new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
});
PriorityQueue<String> queue = new PriorityQueue<>((s1, s2) -> s1.length() - s2.length());

关于线程安全

"线程安全"是多线程编程中的术语,如果代码是线程安全的,那么它在同时被多个线程访问时,其行为仍然是正确的。这意味着无论操作系统如何切换执行线程,结果都是预期的,不会出现数据竞争或者其他意料之外的结果。

而"非线程安全" 则是指在多线程环境下,一个对象的方法可能会受到其他线程方法的干扰,不能保证数据的完整性和一致性。结果可能取决于线程的调度顺序,这通常是我们不希望发生的。

举个例子,假设有两个线程同时对一个优先队列进行插入操作,由于 PriorityQueue 是非线程安全的,所以可能会出现问题。一个可能的情况是一个线程已经开始了插入操作,但在完成之前,操作系统切换到另一个线程,另一个线程也尝试插入元素。结果可能会导致队列中的某些元素状态不正确。

在处理这种情况的一种常见手法是使用锁来同步对共享资源的访问。Java提供了多种机制,例如synchronized关键字或者显式锁(java.util.concurrent.locks.Lock),可以用来同步对共享资源的访问,从而确保线程安全。

当然,如果你只在单线程环境下使用PriorityQueue,那么就不存在线程安全问题。

标签:offer,队列,双端,元素,PriorityQueue,插入,线程,20230720
From: https://www.cnblogs.com/xccx-0824/p/17569574.html

相关文章

  • 《剑指offer》编程在练评判
    下面是剑指offer书中的练习题在九度在线评判系统中的在线评测,非常适合大家练习。文章是转载。    目前国内外越来越多公司将在线机试的方式引入求职招聘中,或者通过各种在线比赛和比赛平台搜寻各类编程人才。在线编程练习可以培养求职者良好的编程习惯,提高编程水平,其自动判......
  • 《剑指offer》 对应的 在线测试地址
    《剑指Offer》面试题集收录汇总面试题1赋值运算符函数不适合在线模式面试题2实现Singleton模式不适合在线模式面试题3二维数组中的查找已收录面试题4替换空格已收录面试题5从头到尾打印链表已收录面试题6重建二叉树已收录面试题7用两个栈实现队列已收录面试......
  • 剑指 Offer 67. 把字符串转换成整数
    题目classSolution{public:intstrToInt(stringstr){if(str.size()==0)return0;#空字符串情况intcur=0;while(str[cur]=='')cur++;#直接从非空字符开始处理booloverflow=false;......
  • 剑指 Offer 20. 表示数值的字符串
    题目:#遇到数字:一定合法#遇到'.'且合法需要满足条件:之前没出现过'.',之前没出现过'e'#遇到'e'且合法需要满足条件:之前没出现过'e',之前出现过整数#遇到'+'或者'-'且合法需要满足条件:位于字符串第一位,或者紧跟在'e'之后classSolution{public:boolisNumber(st......
  • 剑指 Offer 58 - II. 左旋转字符串
    classSolution{public:stringreverseLeftWords(strings,intn){reverse(s.begin(),s.begin()+n);#反转用reverse而不是s.reversereverse(s.begin()+n,s.end());#这里用s.begin()+n而不是s.begin()+n+1,因为s.begin()是指向集......
  • 剑指 Offer 05. 替换空格
    classSolution{public:stringreplaceSpace(strings){intnumspace=0;for(inti=0;i<s.size();i++){if(s[i]==''){numspace++;}}intoldsize=s.size();s.r......
  • 剑指offer--链表
    第6题:链表中倒数最后k个结点题目描述输入一个长度为n的链表,设链表中的元素的值为\(a_i\),返回该链表中的第k个结点。如果该链表长度小于\(k\),请返回一个长度为0的链表思路双指针step1:准备一个快指针,从链表头开始,在链表上先走k步。step2:准备慢指针指向原始链表头,代......
  • 剑指offer_20230715
    剑指Offer67.把字符串转换成整数题目说明写一个函数StrToInt,实现把字符串转换成整数这个功能。不能使用atoi或者其他类似的库函数。首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。当我们寻找到的第一个非空字符为正或者负号时,则将该符......
  • 【剑指Offer】3、从尾到头打印链表
    【剑指Offer】3、从尾到头打印链表题目描述:输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。解题思路:(三种方法:借助栈、递归、列表的首位插入)从头到尾打印链表比较简单,从尾到头很自然的可以想到先将链表进行反转,然后再打印。但是,通常我们不希望改变原链表的结构,这是......
  • LeetCode 剑指 Offer 13. 机器人的运动范围
    题目链接:LeetCode剑指Offer13.机器人的运动范围题意:地上有一个m行n列的方格,从坐标[0,0]到坐标[m-1,n-1]。一个机器人从坐标[0,0]的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机......