【算法与数据结构】【面试经典150题】【题46-题50】

题46:最小栈(中等)

复制代码
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:
MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。


示例 1:

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

思路:

复制代码
1.一共两个栈,第一个栈正常存储,其中一个存储当前入栈时的数字,对应的栈的最小值。
2.如果栈里面没有元素,此时插入,则最小值就是插入的值。
3.如果此时存在元素,则插入时,对比要插入的值和栈顶的值,去最小的,就是当前插入的元素对应的最小值

代码:

复制代码
class MinStack
{
public:
    stack<int> x_stack;
    stack<int> min_stack;

    MinStack()
    {
        min_stack.push(INT_MAX);
    }

    void push(int val)
    {
        x_stack.push(val);
        min_stack.push(min(min_stack.top(), val));
    }

    void pop()
    {
        x_stack.pop();
        min_stack.pop();
    }

    int top()
    {
        return x_stack.top();
    }

    int getMin()
    {
        return min_stack.top();
    }
};

题47:逆波兰表达式求值(中等)

复制代码
给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

逆波兰表达式:

逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。

平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

有效的算符为 '+'、'-'、'*' 和 '/' 。
每个操作数(运算对象)都可以是一个整数或者另一个表达式。
两个整数之间的除法总是 向零截断 。
表达式中不含除零运算。
输入是一个根据逆波兰表示法表示的算术表达式。
答案及所有中间计算结果可以用 32 位 整数表示。


示例 1:

输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:

输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:

输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
输出:22
解释:该算式转化为常见的中缀算术表达式为:
  ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

思路:

复制代码
思路:栈
遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中

代码:

复制代码
class Solution
{
public:
    int evalRPN(vector<string> &tokens)
    {
        stack<int> stk;
        int n = tokens.size();
        for (int i = 0; i < n; i++)
        {
            string &token = tokens[i];
            if (isNumber(token))
            {
                stk.push(atoi(token.c_str()));
            }
            else
            {
                int num2 = stk.top();
                stk.pop();
                int num1 = stk.top();
                stk.pop();
                switch (token[0])
                {
                case '+':
                    stk.push(num1 + num2);
                    break;
                case '-':
                    stk.push(num1 - num2);
                    break;
                case '*':
                    stk.push(num1 * num2);
                    break;
                case '/':
                    stk.push(num1 / num2);
                    break;
                }
            }
        }
        return stk.top();
    }

    bool isNumber(string &token)
    {
        return !(token == "+" || token == "-" || token == "*" || token == "/");
    }
};

题48:两数相加(中等)

复制代码
给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照逆序的方式存储的,并且每个节点只能存储一位数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:

输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:

输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]

提示:
每个链表中的节点数在范围 [1, 100] 内

思路:

复制代码
1.直接遍历两个链表,两个链表中同一位置的数字可以直接相加。
2.如果数字相加为10以下,则直接放入新的链表中
3.如果数字相加大于10,则标记一个进位为carry,
4.如果一个链表的下一个节点为nullptr,则将其值为0,知道两个链表都为空

代码:

复制代码
class Solution
{
public:
    ListNode *addTwoNumbers(ListNode *l1, ListNode *l2)
    {
        ListNode *head = nullptr, *tail = nullptr;
        int carry = 0;
        while (l1 || l2)
        {
            int n1 = l1 ? l1->val : 0;
            int n2 = l2 ? l2->val : 0;
            int sum = n1 + n2 + carry;
            if (!head)
            {
                head = tail = new ListNode(sum % 10);
            }
            else
            {
                tail->next = new ListNode(sum % 10);
                tail = tail->next;
            }
            carry = sum / 10;
            if (l1)
            {
                l1 = l1->next;
            }
            if (l2)
            {
                l2 = l2->next;
            }
        }
        if (carry > 0)
        {
            tail->next = new ListNode(carry);
        }
        return head;
    }
};

题49:合并两个有序链表(简单)

复制代码
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:

输入:l1 = [], l2 = []
输出:[]
示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

*/

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };

 */

思路:

复制代码
思路:
当 l1 和 l2 都不是空链表时,判断 l1 和 l2 哪一个链表的头节点的值更小,将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。
1.我们设定一个哨兵节点 prehead,这可以在最后让我们比较容易地返回合并后的链表。
我们维护一个 prev 指针,我们需要做的是调整它的 next 指针。
然后,我们重复以下过程,直到 l1 或者 l2 指向了 null 
2.如果 l1 当前节点的值小于等于 l2 ,我们就把 l1 当前的节点接在 prev 节点的后面同时将 l1 指针往后移一位。
否则,我们对 l2 做同样的操作。不管我们将哪一个元素接在了后面,我们都需要把 prev 向后移一位。
3.在循环终止的时候, l1 和 l2 至多有一个是非空的。由于输入的两个链表都是有序的,
所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。
这意味着我们只需要简单地将非空链表接在合并链表的后面,并返回合并链表即可。

代码:

复制代码
class Solution
{
public:
    ListNode *mergeTwoLists(ListNode *list1, ListNode *list2)
    {
        ListNode *preHead = new ListNode(-1);
        ListNode *prev = preHead;
        while (list1 != nullptr && list2 != nullptr)
        {
            if (list1->val < list2->val)
            {
                prev->next = list1;
                list1 = list1->next;
            }
            else
            {
                prev->next = list2;
                list2 = list2->next;
            }

            prev = prev->next;

        }

        prev->next = list1 == nullptr ? list2 : list1;

        return preHead ->next;

    }
};

题50:随机链表的复制(中等)

复制代码
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。

返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为  null 。
你的代码 只 接受原链表的头节点 head 作为传入参数。


输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
图如下:

思路:

复制代码
1.由于存在随机指针指向的节点,即当我们拷贝节点的时候,「当前节点的随机指针指向的节点」可能还没创建。
2.所以我们需要创建一个哈希表,用于存储每个节点的创建情况。
3.如果「当前节点的后继节点」和「当前节点的随机指针指向的节点」中存在没有创建的,
则立刻创建,并放入哈希表中。

代码:

复制代码
class Solution
{
public:
    unordered_map<Node *, Node *> cachedNode;

    Node *copyRandomList(Node *head)
    {
        if (head == nullptr)
        {
            return nullptr;
        }

        if (!cachedNode.count(head))
        {
            Node *newNode = new Node(head->val);
            cachedNode[head] = newNode;
            newNode->next = copyRandomList(head->next);
            newNode->random = copyRandomList(head->random);
        }
        return cachedNode[head];
    }
};
相关推荐
Chat_zhanggong34529 分钟前
主推NT98336BG作用有哪些?
嵌入式硬件·算法
Run_Teenage1 小时前
算法:线段树
算法
Westward-sun.1 小时前
YOLOv2算法全方位解析:从BatchNorm到聚类先验框的九大改进
算法·yolo·聚类
扶苏xw1 小时前
【离散化算法】
算法
码之气三段.1 小时前
Codeforces Round 1095 (Div. 2) 补题
算法
6Hzlia1 小时前
【Hot 100 刷题计划】 LeetCode 189. 轮转数组 | C++ 三次反转经典魔法 (O(1) 空间)
c++·算法·leetcode
wuweijianlove1 小时前
算法可扩展性建模与渐进性能分析的技术7
算法
许彰午1 小时前
CacheSQL:一个面向政务系统的内存缓存数据库中间件
java·数据库·缓存·中间件·面试·开源软件·政务
不会敲代码11 小时前
从 URL 到页面展示,还有哪些你忽略的底层细节?(DNS 与传输篇)
前端·面试
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题】【Java基础篇】第21题:HashMap和Hashtable的区别是什么
java·开发语言·面试·哈希算法·散列表·hash table