代码随想录|单调栈|02下一个更大元素I

leetcode:496. 下一个更大元素 I - 力扣(LeetCode)

题目

给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。

请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。

nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。

示例 1:

输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。
对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。

示例 2:
输入: nums1 = [2,4], nums2 = [1,2,3,4].
输出: [3,-1]
解释:
对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。
对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出-1 。

提示:

  • 1 <= nums1.length <= nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 10^4
  • nums1和nums2中所有整数 互不相同
  • nums1 中的所有整数同样出现在 nums2 中

思路

上来先用一个暴力:

class Solution
{
public:
    vector<int> nextGreaterElement(vector<int> &nums1, vector<int> &nums2)
    {
        int m = nums1.size(), n = nums2.size();
        vector<int> ans;
        unordered_map<int,int> umap;
        for(int i=0;i<n;i++)
        {
            umap[nums2[i]]=i;
        }


        for (int i = 0; i < m; i++)
        {
            bool flag=false;
            int start=umap[nums1[i]];
            for (int j = start+1; j < n; j++)
            {
                if (nums2[j] > nums1[i])
                {
                    ans.push_back(nums2[j]);
                    flag=true;
                    break;
                }
            }
            if(!flag)
            {
                ans.push_back(-1);
            }
            
        }
        return ans;
    }
};

两层循环应该不难理解,下面用单调栈来解决这个问题。

构建映射逻辑:key是nums1的元素值,value是nums1元素值对应的索引.

为什么需要构建映射逻辑,因为我们用nums2构建单调栈,但是在计算result的时候,是要看nums1的元素,所以在nums2中准备添加result时候,要看一下这个元素在不在nums1里面,如果在的话,就添加到相应的位置。

剩下的步骤就跟 每日温度 这个题一样。

// ===========================方法二:单调栈===========================
class Solution
{
public:
    vector<int> nextGreaterElement(vector<int> &nums1, vector<int> &nums2)
    {
        stack<int> st;
        vector<int> result(nums1.size(), -1); // 默认设为-1
        if (nums1.size() == 0)
            return result;
        // 构建映射逻辑
        unordered_map<int, int> umap;
        for (int i = 0; i < nums1.size(); i++)
        {
            umap[nums1[i]] = i;
        }
        // 用单调栈去模拟nums2
        st.push(0);
        for (int i = 1; i < nums2.size(); i++)
        {
            if (nums2[i] < nums2[st.top()])
            {
                st.push(i);
            }
            else if (nums2[i] == nums2[st.top()])
            {
                st.push(i);
            }
            else
            {
                while (!st.empty() && nums2[i] > nums2[st.top()])
                {
                    if (umap.count(nums2[st.top()]) > 0)
                    {
                        int index = umap[nums2[st.top()]];
                        result[index] = nums2[i];
                    }
                    st.pop();
                }
                st.push(i);
            }
        }
        return result;
    }
};

 

总结

这题的核心是:判断栈顶元素是否在nums1里出现过

参考资料

 代码随想录

<think>好的,用户现在问是否有必要学习如何设计列表以及用数组或链表实现栈或队列。首先,我需要理解他的问题背景。他之前提到跟着《代码随想录》学习时,感觉直接做题有困难,缺乏基础结构和算法的知识。现在他可能觉得这些底层实现的内容太基础,或者怀疑实际应用中的必要性。 接下来,我要分析他的需求。作为零基础或新手,用户可能认为直接调用现成的数据结构库更方便,不理解为何要自己实现。需要解释这些底层实现的重要性,比如加深理解数据结构原理、提升编程能力、面试考察等。 然后,我需要考虑用户可能的疑虑。例如,实际开发中确实很少自己实现这些结构,但学习过程中掌握原理能帮助更好地使用高级库。此外,算法面试中常考这些基础实现,如用数组实现队列,可能涉及循环队列等考点。 接下来,我需要组织回答结构。先明确回答有必要,再分点说明原因,包括掌握原理、应对面试、提升调试能力、培养工程思维等。然后,用具体例子说明如何学习,比如实现栈的不同方式,并给出学习建议,如分阶段学习和结合实践。 还要注意用户的体验,避免使用过于专业的术语,用易懂的例子和实际应用场景来说明。例如,解释栈的数组实现可能遇到的空间问题,或者链表实现指针操作的重要性,以及这些在解决实际问题时的帮助。 最后,总结建议,强调基础的重要性,并鼓励用户通过实践逐步掌握。可能还需要提及常见误区,如直接跳到底层实现,导致学习困难,建议循序渐进。</think>### **必须掌握的底层实现能力(以设计列表/栈/队列为例)** --- #### **一、直接价值:这四类实现是算法工程师的「细胞级技能」** 1. **LeetCode高频考点统计** | 题目 | 出现频率 | 考察核心 | |----------------------|----------|------------------------------| | 用数组实现队列 | ★★★★☆ | 循环数组设计 + 队列空满判断 | | 用链表实现栈 | ★★★☆☆ | 头插法 + 指针操作 | | 最小栈 (#155) | ★★★★★ | 同步辅助栈的应用 | | 设计循环双端队列(#641)| ★★★★☆ | 数组索引循环计算 | **典型面试场景**: - 字节跳动终面要求手写循环队列 - 亚马逊OA必考「用两个栈实现队列」 2. **实际工程中的隐形需求** - 阅读Redis源码时需要理解**双向链表实现列表**(adlist.h) - 学习Linux内核链表时需掌握**嵌入式链表设计**(list_head结构) --- #### **二、底层实现带来的六大核心能力提升** **1. 指针/引用操作敏感度训练** **案例**:链表实现栈时,每次`push()`操作本质是: ```python new_node = Node(val) new_node.next = self.top # 关键链接操作 self.top = new_node ``` **错误警示**:若交换最后两行顺序,会导致`self.top`丢失原栈顶节点。 **2. 数据结构特性深度认知** - **数组实现队列**的痛点演示: ```python class ArrayQueue: def __init__(self, capacity): self.items = [None] * capacity self.head = 0 # 队首指针 self.tail = 0 # 队尾指针 def enqueue(self, item): # 错误示范:未处理队列满的情况 self.items[self.tail] = item self.tail += 1 ``` 当`tail == len(items)`时继续插入会导致数组越界,此时需要**取模运算**实现循环队列: `self.tail = (self.tail + 1) % len(self.items)` **3. 时空复杂度评估能力** - 链表实现的栈: - `push()`: $O(1)$(头插法) - `pop()`: $O(1)$ - 动态数组实现的栈: - 均摊时间复杂度:$O(1)$(自动扩容时复制元素) **4. 边界条件处理能力**(以设计循环队列为例) ```python def isFull(self): # 关键判断:牺牲一个存储单元区分空满状态 return (self.tail + 1) % self.capacity == self.head def enqueue(self, value: int) -> bool: if self.isFull(): return False self.items[self.tail] = value self.tail = (self.tail + 1) % self.capacity return True ``` **调试重点**:当队列容量为5时,测试连续插入6个元素的报错机制。 **5. 抽象接口设计思维** - 标准栈接口设计规范: ```python class Stack: def __init__(self): self.items = [] def push(self, item): """将元素压入栈顶""" pass def pop(self): """弹出栈顶元素""" pass def peek(self): """查看栈顶元素不弹出""" pass def is_empty(self): """判断栈是否为空""" pass ``` 在实现时需严格遵循LIFO(后进先出)原则。 **6. 内存管理意识培养** - 链表实现时需注意**野指针**问题: ```c++ // C++ 链表节点删除错误示例 void pop() { Node* temp = top; top = top->next; // 必须手动释放内存 delete temp; } ``` 在Python中虽然无需手动释放,但理解这一过程对学习其他语言至关重要。 --- #### **三、实操训练路线图** **阶段1:基础实现(建议用时:3天)** | 实现目标 | 重点训练能力 | 推荐测试用例 | |--------------------|----------------------------|----------------------------------| | 数组实现栈 | 动态扩容策略 | 连续push超过初始容量 | | 链表实现队列 | 头尾指针协同 | 交替进行enqueue和dequeue操作 | | 双栈实现队列 | 数据迁移时机 | 大数据量下的性能对比 | **阶段2:进阶改造(建议用时:5天)** 1. **支持泛型**:使你的栈能存储多种数据类型(Python中通过类型提示实现) 2. **线程安全**:添加锁机制实现多线程安全(理解生产者-消费者模型基础) 3. **性能监控**:添加`get_operation_count()`方法统计操作次数 **阶段3:实战应用(建议用时:7天)** - **案例1**:用自实现的栈解决「括号匹配」(#20) - **案例2**:用自实现的队列完成「二叉树的层序遍历」(#102) - **案例3**:改造栈实现「获取当前栈最小值」功能(对标#155题) --- #### **四、常见认知误区纠正** **误区1**:「现代语言都有现成库,不需要自己实现」 - **现实情况**:在嵌入式开发中(如C语言),经常需要手写链表管理内存 - **典型案例**:Redis的快速列表(quicklist)就是双向链表+压缩列表的复合结构 **误区2**:「面试考这些只是为了刁难人」 - **字节跳动真题解析**: ```python # 要求实现一个支持get_min()的队列,所有操作时间复杂度O(1) class MinQueue: def __init__(self): self.main_queue = [] # 常规队列 self.min_queue = [] # 辅助队列 def push(self, val: int) -> None: self.main_queue.append(val) # 维护min_queue:移除比当前值大的元素 while self.min_queue and self.min_queue[-1] > val: self.min_queue.pop() self.min_queue.append(val) ``` 该题本质是「单调队列」的应用,必须理解队列底层实现才能解答。 --- ### **终极检验标准** 当你能在30分钟内完成以下任务,说明已掌握必要的基础实现能力: 1. 用数组实现支持自动扩容的循环队列 2. 用双向链表实现LRU缓存机制 (#146) 3. 解释Python中`collections.deque`的底层实现原理 **这些能力将成为你攻克动态规划、图算法等高级课题的基石**。坚持实践2周后,你会发现自己阅读《代码随想录》题解时,能快速定位到核心实现逻辑,而不是被基础操作绊住手脚。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值