刷题计划——栈算法(二)

921.使括号有效的最少添加(简单)

题目:

给定一个由 ‘(’ 和 ‘)’ 括号组成的字符串 S,我们需要添加最少的括号( ‘(’ 或是 ‘)’,可以在任何位置),以使得到的括号字符串有效。

从形式上讲,只有满足下面几点之一,括号字符串才是有效的:

它是一个空字符串,或者
它可以被写成 AB (A 与 B 连接), 其中 A 和 B 都是有效字符串,或者
它可以被写作 (A),其中 A 是有效字符串。
给定一个括号字符串,返回为使结果字符串有效而必须添加的最少括号数。

示例 1:
输入:"())"
输出:1

示例 2:
输入:"((("
输出:3

示例 3:
输入:"()"
输出:0

示例 4:
输入:"()))(("
输出:4

同“有效括号”这道题解法类似,同样使用栈的方法来存储’(‘字符,并且设立一个num 来记录需要添加的括号数量,当遇到’)’ 字符且栈不为空时出栈一次,为空时num 加一。 另外在遍历字符串结束后为解决’(’ 无法匹配的问题,需要查看栈中存储’(’ 的数量,将其与num 相加就是需要最终要添加的括号数量。

  • 设立st栈来存储 ( 字符,设立num来记录缺省的括号数量
  • 遍历字符串,当遇到字符 ( 时将其压栈
  • 当遇到 ) 字符时分为两种情况,st栈为空时num加一,st栈不为空时出栈一次
  • 字符串遍历结束将num与栈的大小相加
class Solution {
public:
    int minAddToMakeValid(string S) {
        stack<char> st;
        int num = 0;
        int len = S.length();
        for(int i = 0; i < len; i++){
            if(S[i] == '('){
                st.push(S[i]);
            }
            else{
                if(st.empty()){
                    ++num;
                }
                else{
                    st.pop();
                }
            }
        }
        num += st.size();
        return num;
    }
};

739.每日温度(中等)

题目:

根据每日气温 列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。

给定一个列表 
temperatures = [73, 74, 75, 71, 69, 72, 76, 73]
你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]

使用常规方法逐次查找下一次更大值的时间复杂度为O(N),我们为了提高该算法的时间复杂度使用栈的方法。

我们在算法中引入栈,该算法的核心思想如下:

从左到右遍历该数组,用栈来存储特定的数字下标,将结果存储在一个数组中,这样就使用栈完成了“定位”和存储。
从左向右看,当栈为空时将当前的数组下标压栈,当下一个数字大于栈顶的数字时,将栈顶的数字弹出,并且将目前的数组下标减去栈顶的数字,此结果就是下一个温度更高之间的相隔天数。

而当栈顶元素大于等于当前元素时,同样将该下标压栈,当遇到下一个“更高”温度的下标时再出栈进行计算。

总之一句话,该栈的作用就在于保存拥有一个温度的日期下标,与比它更高温度日期之间所经过的天数。

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& T) {
        stack<int> st;
        int len = T.size();
        vector<int> res(len, 0);
        for(int i = 0; i < len; ++i){
            while(!st.empty() && T[st.top()] < T[i]){
                int index = st.top(); st.pop();
                res[index] = i- index;
            }
            st.push(i);
        }

        return res;
    }
};

71.简化路径

题目:

以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。

在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (…) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。更多信息请参阅:Linux / Unix中的绝对路径 vs 相对路径

请注意,返回的规范路径必须始终以斜杠 / 开头,并且两个目录名之间必须只有一个斜杠 /。最后一个目录名(如果存在)不能以 / 结尾。此外,规范路径必须是表示绝对路径的最短字符串。

示例 1:
输入:"/home/"
输出:"/home"
解释:注意,最后一个目录名后面没有斜杠。

示例 2:

输入:"/../"
输出:"/"
解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。
示例 3:

输入:"/home//foo/"
输出:"/home/foo"
解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。
示例 4:

输入:"/a/./b/../../c/"
输出:"/c"
示例 5:

输入:"/a/../../b/../c//.//"
输出:"/c"
示例 6:

输入:"/a//bc/d//././/.."
输出:"/a/b/c"

典型的栈算法。总体而言分为三种情况,字符为 / 时,字符为 . 时,字符为其它时。

  • 设置一个存储string类型的栈,开始遍历字符串
  • 每次遍历前需要循环跳过斜杠,直到找到一个非斜杠字符
  • 分三种情况
  • 1.第一种是路径名为一个点,则直接跳过
  • 2.第二种路径名为两个点,此时计数器加1,表示要跳过的路径长度
  • 3.第三种是路径名为两个以上的点或者其他字符,此时再分两种情况,一种是计数器大于0时,跳过该路径,然后计数器减1;另一种是计数器为0时,此时直接在结果字符串中插入该路径名即可。
class Solution {
public:
    string simplifyPath(string path) {
        if(path.length() == 0) return "";
        stack<string> st;
        string tmp;
        path += "/";
        int now = 0;
        while(now < path.length() - 1)
        {
            while(path[now] == '/' && now < path.length() - 1) now ++;
            if(now >= path.length() - 1) break;
            if(path[now] == '.' && (path[now + 1] =='.' || path[now + 1] == '/'))
            {
                if(path[now + 1] == '.' && (now + 2 > path.length() - 1 || path[now + 2] == '/'))            
                {
                    if(!st.empty()) st.pop();
                    now = now + 2;
                    continue;
                }
                else 
                    if(path[now + 1] != '.')
                    {
                        now ++;
                        continue;
                    }
            }
            tmp = "";
            while(path[now] != '/')
            {
                tmp += path[now];
                now ++;
            }
            st.push(tmp);
        }
        tmp = "";
        if(st.empty()) return "/";
        while(!st.empty())
        {
            tmp = "/" + st.top() + tmp;
            st.pop();
        }
        return tmp;
    }
};

1019.链表中的下一个更大节点

题目:

给出一个以头节点 head 作为第一个节点的链表。链表中的节点分别编号为:node_1, node_2, node_3, … 。

每个节点都可能有下一个更大值(next larger value):对于 node_i,如果其 next_larger(node_i) 是 node_j.val,那么就有 j > i 且 node_j.val > node_i.val,而 j 是可能的选项中最小的那个。如果不存在这样的 j,那么下一个更大值为 0 。

返回整数答案数组 answer,其中 answer[i] = next_larger(node_{i+1}) 。

注意:在下面的示例中,诸如 [2,1,5] 这样的输入(不是输出)是链表的序列化表示,其头节点的值为 2,第二个节点值为 1,第三个节点值为 5 。

示例 1:

输入:[2,1,5]
输出:[5,5,0]
示例 2:

输入:[2,7,4,3,5]
输出:[7,0,5,5,0]
示例 3:

输入:[1,7,5,1,9,2,5,1]
输出:[7,9,9,9,0,5,0,0]

很明显,这道题可以通过两层循环来暴力破解,但这样就不符合使用栈的要求。

首先,我们设立一个存储链表节点数据的栈st, 这个栈用来存储一个节点以及之后小于它节点的数据,再设立一个存储结果的数组res,一个front指针,该指针作用之后会说到;遍历链表,同时将一个0插入数组res中用来占位,之后当栈为空并且当前链表的节点数值大于栈顶元素时,就要进行相关操作,反之继续将该链表数值压栈。

我们栈中存储的数据是要最终放到数组res中,因此如何定位相关数据对应的位置,这时front指针的作用就体现出来。 当要存储的位置不为0时,也就说明该位置已经有下一个最大的元素,此次front指针继续向数组左端运动;而当front前一处res的值为0时,说明该位置还未被发现它的下一个最大元素,此时将链表的当前节点数值赋给该front位置的res元素,之后出栈一次。

当链表遍历结束之后,res数组也就完成。总体时间复杂度为O(N)

  • 设立栈st, 数组res, 指示数组和链表对应位置的指针 i
  • 遍历链表
  • 每次遍历一个节点,就在数组res中添加一个元素,同时刷新front指针
  • 当栈不为空,栈顶元素大于等于当前链表元素大小时,将当前链表元素压入栈st
  • 否则,当数组res[front - 1] 不为0时,front 指针继续向左前进;当数组res[front - 1] 为0时,将res[front-1] 赋值为当前链表节点数值
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> nextLargerNodes(ListNode* head) {
        vector<int> res;
        ListNode *node = head;
        stack<int> st;
        int i = 0;
        while(node){
            res.push_back(0);
            int front = i;
            while(!st.empty() && st.top() < node->val){
                if(res[front - 1] == 0){
                    res[--front] = node->val;
                    st.pop();
                }
                else{
                    --front;
                }
            }
            st.push(node->val);
            i++;
            node = node->next;
        }

        return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值