【OJ算法训练】04__十月__栈

本文详细介绍了栈这一数据结构的定义、实现方式,包括顺序栈和链栈,并提供了C++和Python的实现示例。同时,文章通过三道LeetCode题目展示了栈在解决实际问题中的应用,如接雨水、行星碰撞和括号的分数计算。解题思路和代码实现清晰明了,帮助读者深入理解栈的运用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考资料,以下内容来自C/C++/Python大佬们:

https://www.cnblogs.com/lanhaicode/p/10453602.html

https://blog.youkuaiyun.com/zichen_ziqi/article/details/80807989

https://zhuanlan.zhihu.com/p/112316768

... ...

定义

1. 运算受限的线性存储结构,只能在一端进行插入和删除操作;

2. 允许操作的一端称为栈顶,不允许操作的一端称为栈底;

3. 栈遵循“先进后出”原则

实现方式

顺序栈

用一段连续的存储空间来存储栈中的数据元素,比较常见的是用数组来实现顺序栈。

顺序存储结构:

1.元素所占的存储空间必须连续(这里的连续是指的逻辑连续,而不是物理连续)

2.元素在存储空间的位置是按逻辑顺序存放的。

程序实现

C++标准库栈,需包含头文件: #include <stack>,定义为stack<int> s;

s.empty(); // 如果栈为空则返回true, 否则返回false
s.size();  // 返回栈中元素的个数
s.top();   // 返回栈顶元素, 但不删除该元素
s.pop();   // 弹出栈顶元素, 但不返回其值
s.push();  // 将元素压入栈顶

基于单链表的栈:

#include <iostream>
​
using namespace std;
​
template<class T>class Stack
{
private:
    struct Node
    {
        T data;
        Node *next;
    };
    Node *head;
    Node *p;
    int length;
 
public:
    Stack()
    {
        head = NULL;
        length = 0;
    }
    void push(T n) // 入栈
    {
        Node *q = new Node;
        q->data = n;
        if (head == NULL) {
            q->next = head;
            head = q;
            p = q;
        } else {
            q->next = p;
            p = q;
        }
        length++;
    }
 
    T pop() // 出栈并且将出栈的元素返回
    {
        if (length <= 0) {
            abort();
        }
        Node *q;
        T data;
        q = p;
        data = p->data;
        p = p->next;
        delete(q);
        length--;
        return data;
    }
    int size() // 返回元素个数
    {
        return length;
    }
    T top() // 返回栈顶元素
    {
        return p->data;
    }
    bool isEmpty() // 判断栈是不是空的
    {
        if (length == 0) {
            return true;
        } else {
            return false;
        }
    }
    void clear() // 清空栈中的所有元素
    {
        while (length > 0) {
            pop();
        }
    }
};
​
int main()
{
    Stack<char> s;
    s.push('a');
    s.push('b');
    s.push('c');
    while (!s.isEmpty()) {
        cout << s.pop() << endl;
    }
}

链栈

链式栈,将链表的头部作为栈顶,尾部作为栈底。避免在实现数据入栈出栈时做大量遍历链表的耗时操作。

入栈操作:将数据从链表的头部插入;

出栈操作:删除链表头部的首元结点。

程序实现

#include <iostream>
​
using namespace std;
​
template <class T>
struct chainStackNode
{
    T data;                  // 链式栈储存结点的数据
    chainStackNode<T>* next; // 链式栈指向下一结点的指针
};
​
template <class T>
class chainStack
{
private:
    chainStackNode<T>* top;     // 将链式栈的头部指针封装为私有成员
public:
    chainStack();               // 链式栈的构造函数--初始化栈
    ~chainStack();              // 析构函数
    bool Push(T newData);       // 栈的基本操作--进栈
    bool Pop(T& x);             // 出栈,以引用的方式返回出栈元素
    bool getTop(T& x);          // 以引用的方式返回栈顶元素
    bool isEmpty();             // 判断栈是否为空
    void printChainStackData(); // 打印链式栈的数据
};
​
template <class T>
inline chainStack<T>::chainStack()
{
    top = new chainStackNode<T>; // 创建一个新的结点
    top->next = NULL;            // 将top的next指针指向空
}
​
template <class T>
inline chainStack<T>::~chainStack()
{
    delete[] top;  // 释放top指针的空间,析构函数的作用-->回收空间
}
​
template <class T>
inline bool chainStack<T>::Push(T newData)
{
    chainStackNode<T>* newNode = new chainStackNode<T>;
    if (!newNode) {
        cout << "分配内存失败!" << endl;
        return false;
    }
    newNode->data = newData; // 修改指针,添加元素
    newNode->next = top->next;
    top->next = newNode;
    return true;
}
​
template <class T>
inline bool chainStack<T>::Pop(T& x)
{
    chainStackNode<T>* temporaryNode; // 创建一个临时指针指向删除结点
    if (isEmpty() == true) {
        cout << "该栈为空!" << endl;
        return false;
    }
    else
    {
        temporaryNode = top->next;
        x = temporaryNode->data; // 以引用返回
        top->next = temporaryNode->next;
        delete temporaryNode;    // 释放空间
        return true;
    }
}
​
template <class T>
inline bool chainStack<T>::isEmpty()
{
    if (top->next == NULL) { // top指针的下一结点是否为空,以此来判断是否为空
        return true;
    }
    else {
        return false;
    }
}
​
template <class T>
inline  bool chainStack<T>::getTop(T& x)
{
    if (isEmpty() == true) {
        return false;
    }
    else {
        x = top->next->data;
        return true;
    }
}
​
template <class T>
inline  void chainStack<T>::printChainStackData()
{
    chainStackNode<T>* pMove;
    pMove = top->next;
    while (pMove->next != NULL) {
        cout << "[" << pMove->data << "]->";
        pMove = pMove->next;
    }
    cout << "[" << pMove->data << "]" << endl;
}

​Python栈

分类

Python有专门的模块定义了栈

from pythonds.basic.stack import Stack
方式特点
Stack()创建一个空的新栈。 不需要参数,返回一个空栈
push(item)将一个新项添加到栈的顶部。需要 item 做参数,不返回任何内容
pop()从栈中删除顶部项。不需要参数并返回 item 。栈被修改
peek()从栈返回顶部项,不会删除它。不需要参数。 不修改栈
isEmpty()测试栈是否为空。不需要参数,返回布尔值
size()返回栈中的 item 数量。不需要参数,返回一个整数

程序实现

class Stack:
    #初始化栈
    def __init__(self):
        self.item = []
    #入栈
    def push(self,item):
        self.item.append(item)
    #出栈
    def pop(self):
        return self.item.pop()
    #返回栈顶元素
    def peek(self):
        return self.item[len(self.item)-1]
    #检查是否为空栈
    def isEmpty(self):
        return self.item == []
    #返回栈元素个数
    def size(self):
        return len(self.item)

LeetCode题目

42.接雨水

https://leetcode-cn.com/problems/trapping-rain-water/

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

img

解题思路

双指针法:两个下标left、right,分别位于输入数组的两端,向中间移动,边移动边计算。 使用max_left作为“开始...left”的最大值,使用max_right作为“right...结尾”的最大值。

作者:腐烂的橘子

链接:https://leetcode-cn.com/problems/trapping-rain-water/solution/dong-tai-gui-hua-shuang-zhi-zhen-tu-jie-by-ml-zimi/

Python3方法

# !/usr/bin/env python
# coding: utf-8
​
import unittest
from typing import List
​
​
class Solution:
    def trap(self, height: List[int]) -> int:
        # 边界条件
        if not height:
            return 0
        n = len(height)
​
        left, right = 0, n - 1  # 分别位于输入数组的两端
        max_left, max_right = height[0], height[n - 1]
        ans = 0
​
        while left <= right:
            max_left = max(height[left], max_left)
            max_right = max(height[right], max_right)
            if max_left < max_right:
                ans += max_left - height[left]
                left += 1
            else:
                ans += max_right - height[right]
                right -= 1
​
        return ans
​
​
class TestCase(unittest.TestCase):
​
    def test_01(self):
        height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
        expected_results = 6
        self.result = Solution()
        res = self.result.trap(height)
        self.assertEqual(res, expected_results)
​
    def test_02(self):
        height = [4, 2, 0, 3, 2, 5]
        expected_results = 9
        self.result = Solution()
        res = self.result.trap(height)
        self.assertEqual(res, expected_results)
​
​
if __name__ == '__main__':
    unittest.main()

C++方法

#include <iostream>
#include <stack>
#include <vector>
​
using namespace std;
​
class Solution {
public:
    int Trap(vector<int>& height) {
        int ans = 0;
        stack<int> stk;
        int n = height.size();
        for (int i = 0; i < n; ++i) {
            while (!stk.empty() && height[i] > height[stk.top()]) {
                int top = stk.top();
                stk.pop();
                if (stk.empty()) {
                    break;
                }
                int left = stk.top();
                int currWidth = i - left - 1;
                int currHeight = min(height[left], height[i]) - height[top];
                ans += currWidth * currHeight;
            }
            stk.push(i);
        }
        return ans;
    }
};
​
int main()
{
    Solution res;
​
    vector<int> nums = { 0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1 };
​
    cout << res.Trap(nums);
}

735.行星碰撞

https://leetcode-cn.com/problems/asteroid-collision/

给定一个整数数组 asteroids,表示在同一行的行星。

对于数组中的每一个元素,其绝对值表示行星的大小,正负表示行星的移动方向(正表示向右移动,负表示向左移动)。每一颗行星以相同的速度移动。

找出碰撞后剩下的所有行星。碰撞规则:两个行星相互碰撞,较小的行星会爆炸。如果两颗行星大小相同,则两颗行星都会爆炸。两颗移动方向相同的行星,永远不会发生碰撞。

解题思路

直接入栈:栈为空;栈顶元素为负数;当前元素为正数。

判断入栈:栈顶元素大于abs当前元素;栈顶元素等于abs当前元素;栈顶元素小于abs当前元素。

作者:清风Python

链接:https://leetcode-cn.com/problems/asteroid-collision/solution/735xing-xing-peng-zhuang-ji-yu-zhan-qu-f-xpd1/

Python3方法

# !/usr/bin/env python
# coding: utf-8
​
import unittest
from typing import List
​
​
class Solution:
    def asteroid_collision(self, asteroids: List[int]) -> List[int]:
        s, p = [], 0
        while p < len(asteroids):
            if not s or s[-1] < 0 or asteroids[p] > 0:
                s.append(asteroids[p])
            elif s[-1] <= -asteroids[p]:
                if s.pop() < -asteroids[p]:
                    continue
            p += 1
        return s
​
​
class TestCase(unittest.TestCase):
​
    def test_01(self):
        asteroids = [5, 10, -5]
        expected_results = [5, 10]
        self.result = Solution()
        res = self.result.asteroid_collision(asteroids)
        self.assertEqual(res, expected_results)
​
    def test_02(self):
        asteroids = [8, -8]
        expected_results = []
        self.result = Solution()
        res = self.result.asteroid_collision(asteroids)
        self.assertEqual(res, expected_results)
​
    def test_03(self):
        asteroids = [10, 2, -5]
        expected_results = [10]
        self.result = Solution()
        res = self.result.asteroid_collision(asteroids)
        self.assertEqual(res, expected_results)
​
    def test_04(self):
        asteroids = [-2, -1, 1, 2]
        expected_results = [-2, -1, 1, 2]
        self.result = Solution()
        res = self.result.asteroid_collision(asteroids)
        self.assertEqual(res, expected_results)
​
​
if __name__ == '__main__':
    unittest.main()

C++方法

#include <iostream>
#include <vector>

using namespace std;

class Solution {
public:
    vector<int> AsteroidCollision(vector<int>& asteroids) {
        vector<int> ans; // vector模拟栈 只做push_back、pop_back操作
        int n = asteroids.size();
        for (int i = 0; i < n; ++i) {
            if (ans.empty() || asteroids[i] > 0) {
                ans.push_back(asteroids[i]);
            } else {
                while (ans.size() && ans.back() > 0 && ans.back() < -asteroids[i]) {
                    ans.pop_back(); // 发生碰撞 并且正数绝对值较小 删除这个正数
                }
                if (ans.empty() || ans.back() < 0) {
                    ans.push_back(asteroids[i]);
                } else if (ans.back() == -asteroids[i]) {
                    ans.pop_back(); // 两者都删除
                }
            }
        }
        return ans;
    }
};

int main()
{
    Solution res;

    vector<int> asteroids = { 5, 10, -5 };
    vector<int> nums = res.AsteroidCollision(asteroids);

    cout << nums[0];
    for (unsigned int i = 1; i < nums.size(); i++) {
        cout << " " << nums[i];
    }
}

856.括号的分数

https://leetcode-cn.com/problems/score-of-parentheses/

给定一个平衡括号字符串 S,按下述规则计算该字符串的分数:

() 得 1 分;

AB 得 A + B 分,其中 A 和 B 是平衡括号字符串;

(A) 得 2 * A 分,其中 A 是平衡括号字符串。

解题思路

出栈给分,连续出栈第一次给分,重入栈再给分。

作者:Hypergeek

链接:https://leetcode-cn.com/problems/score-of-parentheses/solution/pythonzhan-by-louis-yuan-0whx/

Python3方法

# !/usr/bin/env python
# coding: utf-8

import unittest


class Solution:
    def score_of_parentheses(self, s: str) -> int:
        stack = []
        ans, flag = 0, False
        for char in s:
            if char == '(':
                stack.append('#')
                flag = True
            else:
                stack = stack[:-1]
                if flag:
                    ans += int(2 ** len(stack))
                    flag = False
        return ans


class TestCase(unittest.TestCase):

    def test_01(self):
        asteroids = '()'
        expected_results = 1
        self.result = Solution()
        res = self.result.score_of_parentheses(asteroids)
        self.assertEqual(res, expected_results)

    def test_02(self):
        asteroids = '(())'
        expected_results = 2
        self.result = Solution()
        res = self.result.score_of_parentheses(asteroids)
        self.assertEqual(res, expected_results)

    def test_03(self):
        asteroids = '()()'
        expected_results = 2
        self.result = Solution()
        res = self.result.score_of_parentheses(asteroids)
        self.assertEqual(res, expected_results)

    def test_04(self):
        asteroids = '(()(()))'
        expected_results = 6
        self.result = Solution()
        res = self.result.score_of_parentheses(asteroids)
        self.assertEqual(res, expected_results)


if __name__ == '__main__':
    unittest.main()

C++方法

#include <iostream>
#include <string>
#include <stack>

using namespace std;

int ScoreOfParentheses(string s)
{
    stack<int> score;
    for (char c : s) {
        if (c == '(') {
            score.push(0);
        }
        else {
            if (score.top() == 0) {
                score.pop();
                score.push(1);
            }
            else {
                int inScore = 0;
                while (score.top() != 0) {
                    inScore += score.top();
                    score.pop();
                }
                score.pop();
                score.push(inScore * 2);
            }
        }
    }
    int res = 0;
    while (!score.empty()) {
        res += score.top();
        score.pop();
    }
    return res;
}

int main()
{
    string str;
    while (cin >> str) {
        cout << ScoreOfParentheses(str) << endl;
    }
}

总结

十月的算法训练掐点完成了,算是承诺达成。

本次三道题为训练要求中“栈”的题目,首先整理了栈的知识点,同样从C++和Python两个方面展示代码实现方式。从三道leetcode题目练手,编码调试,再写测试用例,本地通过。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值