刷算法题(C++)

堆栈

压栈出栈序列

  • 问题:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。
  • 解法:采用模拟方法,为序列 pushV 构造一个,为序列 popV 构造一个队列。不断压栈,直到遇到(位于队首的)当前需要出栈的元素;然后不断出栈,直到需要继续压栈。
#include <vector>
class Solution {
   
  public:

    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
   
        int fst = 0;
        int len = pushV.size();
        vector<int> stack;

        int i = 0;
        while (i != len) {
   
            if (pushV[i] != popV[fst]) {
   	// (压栈后的)栈顶不是预期的出栈队首
                stack.push_back(pushV[i]);	// 那么实际压栈
            } else {
   
                fst++;	// 否则将它压栈并出栈(stack 不变,出栈队列移除队首)
                while (!stack.empty() && stack.back() == popV[fst]) {
   
                    stack.pop_back();	// 然后不断出栈
                    fst++;
                }
            }

            i++;	// 继续压栈
        }

        return stack.empty();	// 为空,那么模拟顺利
    }
};

最大堆

  • 问题:请你实现一个堆 (大根堆)。操作:push x:将 x 加入堆。保证x为int型整数。不输出任何内容。top:输出堆顶元素。若堆为空,输出 “empty” (不含引号)。pop:输出堆顶元素,且弹出堆顶。若堆为空,输出 “empty” (不含引号)。
  • 解法:直接使用 <algorithm> 里的接口(默认:最大堆),可以自定义 comp 运算,但要注意它被用于堆的从上到下的重建扫描(最大堆需要定义严格小于
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main() {
   
    int n;
    cin >> n;

    auto lt = [](int x, int y) -> bool {
   
        return x < y;
    };

    vector<int> maxheap;
    make_heap(maxheap.begin(), maxheap.end(), lt);	// 首先初始化堆(默认最大堆)

    for(int i=0;i<n;i++){
   
        string str;
        int val;

        cin >> str;
        if(str == "push"){
   
            cin >> val;
            maxheap.push_back(val);	// 堆尾插入,向上到根的比较
            push_heap(maxheap.begin(), maxheap.end(), lt);	
        }
        else{
   
            if(maxheap.empty())
                cout << "empty" << endl;
            else{
   
                if(str == "top")
                    cout << maxheap[0] << endl;
                else{
   
                    cout << maxheap[0] << endl;
                    pop_heap(maxheap.begin(), maxheap.end(), lt);
                    maxheap.pop_back();	// 堆首移到堆尾,从上到下的扫描
                }
            }
        }
    }
}

柱状图中的最大矩形

  • 问题:给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1。求在该柱状图中,能够勾勒出来的矩形的最大面积。
  • 解法:对于每个柱子,用动态规划分别求出左边、右边更高柱子的数量;然后计算以该柱子的高度,所能找到的最大矩形;最后扫描一遍获得全局的最大矩形。注意,左邻居比自己低,那么状态转移时立即的;但是如果左邻居比自己高,那么该左邻居的左侧连续更高柱子,仅仅是本柱子的子集,需要继续和更左边的柱子做比较,直到遇到比自己更低的。这里隐含了一个贪心策略:如果一个(固定的宽度区间)矩形没有完全覆盖其宽度上的任意柱子,那么扩大该矩形的高度,必然可以获得更大面积的矩形;因此最大面积的矩形必然完全覆盖了某个柱子
  • 另一种算法:基于单调递增栈(从栈底到栈顶的值不断递增)构造 Left[i]Right[i],其结构为 stack<pair<int, int>>(索引 + 高度),在将 (i, heights[i]) 入栈时把栈里更高的那些都出栈(剩下的栈顶 (j, heights[j]) 就是最近的比自己低的),其栈顶记录了比本柱子更低的最近的左右柱子索引。
class Solution {
   
public:
    int largestRectangleArea(vector<int>& heights) {
   
        int len = heights.size();
        vector<int> Left(len, 0);
        vector<int> Right(len, 0);

        for (int i = 1; i < len; i++) {
   
            int j = 1;
            while (i - j >= 0 && heights[i - j] >= heights[i]) {
   
                Left[i] += Left[i - j] + 1; // 左边连续的更高柱子的数量
                j = Left[i] + 1; // 更左边的依旧可能更高的柱子
            }
        }

        for (int i = len - 2; i >= 0; i--) {
   
            int j = 1;
            while (i + j < len && heights[i + j] >= heights[i]) {
   
                Right[i] += Right[i + j] + 1; // 右边连续的更高柱子的数量
                j = Right[i] + 1; // 更右边的依旧可能更高的柱子
            }
        }

        int maxArea = INT_MIN;
        for (int i = 0; i < len; i++) {
   
            int Area = heights[i] * (1 + Left[i] + Right[i]); // 使用该柱子的高度
            maxArea = max(maxArea, Area);
        }

        return maxArea;
    }
};

矩阵中的最大矩形

  • 问题:给定一个仅包含 01 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
  • 解法:考虑矩阵中固定右下角的一族矩形,它们的极大面积等于该族矩形最右侧边的左侧连续 1 数量的柱状图的最大矩形面积。因为矩阵是二进制的,因此只需扫描它的每一行,容易求出这种 1 的计数。然后把每一列视为柱状图,使用严格单调递增栈计算出某个柱子的左/右连续更高柱子的数量,从而计算出以这一列中的某点作为右下角的最大面积,最终找出全局最优解。
class Solution {
   
public:
    int MaxArea(vector<int>& hist) {
   
        int len = hist.size();

        stack<pair<int, int>> stk; // 严格单调递增栈
        stk.push({
   -1, INT_MIN});   // 避免空栈(高度为负无穷)
        vector<int> Left(len, 0);

        for (int i = 0; i < len; i++) {
    // 注意 i=0 也要压栈,虽然 Left[0]=0 平凡
            int h = hist[i];
            while (stk.top().second >= h) {
   
                stk.pop(); // 移除它们,确保严格递增
            }

            Left[i] = (i - stk.top().first) - 1; // 和最近的更小值的距离,还需减 1
            stk.push({
   i, hist[i]});
        }

        stack<pair<int, int>>().swap(stk); // 因为 stack 没有 clear() 接口
        stk.push({
   len, INT_MIN}); // 避免空栈(高度为负无穷)
        vector<int> Right(len, 0);

        for (int i = len - 1; i >= 0; i--) {
    // 注意 i=len-1 也要压栈,虽然 Right[len-1]=0 平凡
            int h = hist[i];
            while (stk.top().second >= h) {
   
                stk.pop(); // 移除它们,确保严格递增
            }

            Right[i] = (stk.top().first - i) - 1; // 和最近的更小值的距离,还需减 1
            stk.push({
   i, hist[i]});
        }

        int res = INT_MIN;
        for (int i = 0; i < len; i++) {
   
            res = max(res, hist[i] * (1 + Left[i] + Right[i]));
        }

        return res;
    }

    int maximalRectangle(vector<vector<char>>& matrix) {
   
        int rows = matrix.size();
        int cols = matrix[0].size();

        // 记录值是 '1' 的点,其向左边连续的 '1' 数量(含自己)
        vector<vector<int>> LeftOne(cols, vector<int>(rows, 0));
        for (int i = 0; i < rows; i++) {
   
            int idx0 = -1;

            for (int j = 0; j < cols; j++) {
   
                char e = matrix[i][j];
                if (e == '0') {
   
                    idx0 = j; // 左侧最近的 '0' 索引
                } else {
   
                    LeftOne[j][i] = j - idx0; // 按列存储,第 j 列的柱状图
                }
            }
        }

        // 现在 LeftOne 的每一列都是一个柱状图,求其最大矩形面积
        int res = INT_MIN;
        for (auto& hist : LeftOne) {
   
            res = max(res, MaxArea(hist));
        }

        return res;
    }
};

哈希表

常数时间的增删和随机访问

  • 问题:实现RandomizedSet 类:

    • RandomizedSet() 初始化 RandomizedSet 对象。
    • bool insert(int val) 当元素 val 不存在时,向集合中插入该项,并返回 true ;否则,返回 false
    • bool remove(int val) 当元素 val 存在时,从集合中移除该项,并返回 true ;否则,返回 false
    • int getRandom() 随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有相同的概率被返回。

    你必须实现类的所有函数,并满足每个函数的平均时间复杂度为 O(1)

  • 解法:有序数组vector)提供了常数时间的随机访问,无序容器unordered_set, unordered_map)提供了常数时间的插入/删除。结合两者:使用 vector 管理集合元素,使用 map 管理元素的存储位置。

    1. 插入:简单的 push_backinsert 即可;
    2. 删除:首先根据 map 把末尾元素和待删除元素交换(避免后续元素的大幅移动),然后再删除它,记得更新索引;
    3. 访问:先查询 map 获得索引,然后再检索 vector 获得元素。
class RandomizedSet {
   
    unordered_map<int, int> T; // 存储 val - idx,用于快速增删
    vector<int> D;             // 存储 val,用于随机访问

public:
    RandomizedSet() {
   
        T.clear();
        D.clear();
    }

    bool insert(int val) {
   
        if (T.find(val) == T.end()) {
   
            T.insert({
   val, D.size()});
            D.push_back(val);
            return true;
        }

        return false;
    }

    bool remove(int val) {
   
        if (T.find(val) != T.end()) {
   
            T[D.back()] = T[val];
            swap(D[T[val]], D.back()); // 较待删除元素和末尾交换
            D.pop_back();              // 然后删除末尾
            T.erase(val);
            return true;
        }

        return false;
    }

    int getRandom() {
   
        int n = D.size();
        int rd = rand() % n;
        return D[rd];
    }
};

最长连续序列

  • 问题:给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
  • 解法:使用 sort 可以获得平均意义下的拟线性复杂度。为了获得严格线性的复杂度,需要使用哈希表。关键在于:使用哈希表检索出连续序列的开头元素,从而确保每个元素仅被至多访问两次。
class Solution {
   
public:
    int longestConsecutive(vector<int>& nums) {
   
        unordered_set<int> S(nums.begin(), nums.end()); // 哈希表

        int res = 0;
        for (const int e : S) {
    // 使用 (auto e: nums) 将会很慢!为何?
            if (S.find(e - 1) != S.end()) // 本元素不是某连续序列的最小值
                continue;

            int cnt = 1;
            while (S.find(e + cnt) != S.end()) {
    // 每个元素至多执行这里一次,从而 O(n)
                cnt++;
            }
            res = max(res, cnt);
        }

        return res;
    }
};

随机链表的深复制

  • 问题:给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。构造这个链表的深拷贝。 深拷贝应该正好由 n全新节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
  • 解法:首先扫描一遍原始链表,构造出各自独立的新节点(不处理其指针),使用映射 map<Node*, Node*> 记录原始节点到新节点的对应关系。然后,重新扫描一遍原始链表,处理其对应新节点中的指针。
/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;

    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
   
public:
    Node* copyRandomList(Node* head) {
   
        unordered_map<Node*, Node*> nodes; // 哈希表,记录新旧节点的对应关系

        Node* ptr = head;
        if (ptr == nullptr) // 不必复制,立即返回
            return ptr;

        Node* res = new Node(ptr->val); // 表头
        nodes.insert({
   ptr, res});       // ori - new
        ptr = ptr->next;

        while (ptr != nullptr) {
   
            Node* nn = new Node(ptr->val);
            nodes.insert({
   ptr, nn});
            ptr = ptr->next;
        }

        for (auto& p : nodes) {
   
            if (p.first->next != nullptr) // 使用 ori->next 查询 new->next
                p.second->next = nodes[p.first->next];
            if (p.first->random != nullptr) // 同理
                p.second->random = nodes[p.first->random];
        }

        return res;
    }
};

深度优先搜索

岛屿数量

  • 问题:给一个01矩阵,1代表是陆地,0代表海洋, 如果两个1相邻,那么这两个1属于同一个岛。我们只考虑上下左右为相邻。岛屿:相邻陆地可以组成一个岛屿(相邻:上下左右)。 判断岛屿个数。
  • 解法:采用深度优先,遇到一个岛屿,则把它的陆地完全清理掉;继续找出下一个岛屿(与清理掉的岛屿不相连),直到找出全部岛屿。
#include <vector>
class Solution {
   
  public:

    void dfs(vector<vector<char>>& grid, int x, int y) {
   
        int n = grid.size();
        int m = grid[0].size();

#define check(X,Y) (((X)>=0 && (X)<n && (Y)>=0 && (Y)<m) && (grid[X][Y] == '1'))    // 先判断索引范围,然后再检测表格的值(从前向后执行)

        if (check(x, y - 1)) {
   
            grid[x][y - 1] = '0';
            dfs(grid, x, y - 1);
        }

        if (check(x, y + 1)) {
   
            grid[x][y + 1] = '0';
            dfs(grid, x, y + 1);
        }

        if (check(x - 1, y)) {
   
            grid[x - 1][y] = '0';
            dfs(grid, x - 1, y);
        }

        if (check(x + 1, y)) {
   
            grid[x + 1][y] = '0';
            dfs(grid, x + 1, y);
        }
    }

    int solve(vector<vector<char>>& grid) {
   
        int n = grid.size();
        int m = grid[0].size();

        int cnt = 0;
        for (int i = 0; i < n; i++) {
   
            for (int j = 0; j < m; j++) {
   
                if (grid[i][j] == '1') {
   
                    grid[i][j] = '0';
                    cnt++;

                    dfs(grid, i, j); // 深度优先,把这个岛消除
                }
            }
        }

        return cnt;
    }
};

素因子的最小和

  • 问题:第一行一个正整数 n,代表
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值