当前位置: 首页 > news >正文

字符串与栈和队列-算法小结

字符串

双指针

反转字符串(双指针)

力扣题目链接

void reverseString(vector<char>& s) {
    for (int i = 0, j = s.size() - 1; i < s.size()/2; i++, j--) {
        swap(s[i],s[j]);
    }
}

反转字符串II

力扣题目链接
遍历字符串的过程中,只要让 i += (2 * k),i 每次移动 2 * k 就可以了,然后判断是否需要有反转的区间

    string reverseStr(string s, int k) {
        for(int i=0;i<s.size();i+=(2*k)){
            if(i+k <= s.size())
               {reverse(s.begin()+i,s.begin()+k+i);}
            else{
              reverse(s.begin()+i,s.end());
              }
        }
        return s;
    }

翻转字符串里的单词

我们将整个字符串都反转过来,那么单词的顺序指定是倒序了,只不过单词本身也倒序了,那么再把单词反转一下,单词不就正过来了

  • 移除多余空格
  • 将整个字符串反转
  • 将每个单词反转
    力扣题目链接
class Solution {
public:
    string reverseWords(string s) {
        reverse(s.begin(),s.end());
        int y=0;
        for(int i=0;i<=s.size();i++){
            if(s[i]==' '){
                reverse(s.begin()+y,s.begin()+i);
                y=i+1;
            }     
        }         
        reverse(s.begin()+y,s.end());
	    removeExtraSpaces(s);
        return s;    
    }


//移除元素(双指针)
     void removeExtraSpaces(string& s) {
        int slow = 0; 
        for (int i = 0; i < s.size(); ++i) { 
            if (s[i] != ' ') { 
                if (slow != 0) s[slow++] = ' '; 
                while (i < s.size() && s[i] != ' ') { 
                    s[slow++] = s[i++];
                }
            }
        }
        s.resize(slow); 
    }
}; 

实现 strStr()

力扣题目链接

class Solution {
public:
    int strStr(string haystack, string needle) {
        int n = haystack.size(), m = needle.size();
        for (int i = 0; i + m <= n; i++) {
            bool flag = true;
            for (int j = 0; j < m; j++) {
                if (haystack[i + j] != needle[j]) {
                    flag = false;
                    break;
                }
            }
            if (flag) {
               return i;
            }
        }
        return -1;
    }
};

KMP算法-CSDN博客

重复的子字符串

class Solution {
public:
    void getNext (int* next, const string& s){
        next[0] = -1;
        int j = -1;
        for(int i = 1;i < s.size(); i++){
            while(j >= 0 && s[i] != s[j + 1]) {
                j = next[j];
           }
            if(s[i] == s[j + 1]) {
                j++;
            }
            next[i] = j;
        }
    }
    bool repeatedSubstringPattern(string s) {
       if (s.size() == 0) {
            return false;
        }
        int next[s.size()];
        getNext(next, s);
        int n = s.size();
        if(next[n-1]!=-1 && n% (n- (next[n - 1] + 1))==0){
             return true;
        }
         return false;
    }
};

栈与队列

字符串去重

栈的目的,就是存放遍历过的元素,当遍历当前的这个元素的时候,去栈里看一下我们是不是遍历过相同数值的相邻元素

从栈里弹出的元素是倒序的,所以再对字符串进行反转一下

class Solution {
public:
    string removeDuplicates(string S) {
        stack<char> st;
        for (char s : S) {
            if (st.empty() || s != st.top()) {
                st.push(s);
            } else {
                st.pop(); 
            }
        }
        string result = "";
        while (!st.empty()) { 
            result += st.top();
            st.pop();
        }
        reverse (result.begin(), result.end());
        return result;

    }
};

可以拿字符串直接作为栈,这样省去了栈还要转为字符串的操作。

class Solution {
public:
    string removeDuplicates(string S) {
        string result;
        for(char s : S) {
            if(result.empty() || result.back() != s) {
                result.push_back(s);
            }
            else {
                result.pop_back();
            }
        }
        return result;
    }
};

逆波兰表达式求值

计算机可以利用栈来顺序处理,不需要考虑优先级了。也不用回退了, 所以后缀表达式对计算机来说是非常友好的

中缀:a * ( b + c ) - d   ->  ((a * ( b + c )) - d)

//将每个运算符左移到当前括号前 
前缀:- * a + b c d       <-  - (*(a +( b  c ))  d)        //波兰式 
//将每个运算符左移到当前括号后
后缀:a b c + * d -       <-   ((a ( b  c )+) *  d)-       //逆波兰式

2分钟秒杀-中缀表达式转前后缀表达式_哔哩哔哩_bilibili
【算法】数据结构中的栈有什么用?_哔哩哔哩_bilibili

在这里插入图片描述

优先级可以看为加上括号

bool isOperator(char c)
{
	return c == '+' || c == '-' || c == '*' || c == '/'||c=='^';

}

map <char, int> precedence = {
	{'+',1},
	{'-',1},
	{'*',2},
	{'/',2},
	{'^',3}
};

int applyop(int a, int b, char c)
{

	switch (c){
	case'+':return a + b; 
	case'-':return a - b; 
	case'*':return a * b; 
	case'/':return a / b;
	case'^':return pow(a, b); 

	}

}

void valute(stack<int>& val, stack<char>& ope)
{
	int val2 = val.top(); val.pop();
	int val1 = val.top(); val.pop();
	char ops = ope.top(); ope.pop();
	val.push(applyop(val1, val2, ops));
}


int compute(const string& input)
{
	stack<int>val;
	stack<char> ope;
	for (int i=0;i<input.length();i++)
	{
		char ch = input[i];
		if (isspace(ch))continue;
		else if (isdigit(ch)) {
			int num = 0;
			while (i < input.length() && isdigit(input[i])) {
				num = num * 10 + (input[i] - '0');
				i++;
			}
			val.push(num);
			i--;
		}
		else if (ch == '(') {
			ope.push(ch);
		}
		else if (ch == ')'){
			while (!ope.empty() && ope.top() != '(')
			{	
				valute(val, ope);
			}
			ope.pop();
		}
		else if (isOperator(ch)) {
			while (!ope.empty() && precedence[ope.top()] >= precedence[ch]) {

				valute(val, ope);
			}
			ope.push(ch);
		}
	}
	while (!ope.empty()) {
		valute(val, ope);
	}

	return val.top();
}
int main()
{
	string expression;
	cin >> expression;
	cout << compute(expression) << endl;
	return 0;
}

compute
遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
   
        stack<long long> st; 
        for (int i = 0; i < tokens.size(); i++) {
            if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
                long long num1 = st.top();
                st.pop();
                long long num2 = st.top();
                st.pop();
                if (tokens[i] == "+") st.push(num2 + num1);
                if (tokens[i] == "-") st.push(num2 - num1);
                if (tokens[i] == "*") st.push(num2 * num1);
                if (tokens[i] == "/") st.push(num2 / num1);
            } else {
                st.push(stoll(tokens[i]));
            }
        }

        long long result = st.top();
        st.pop(); // 把栈里最后一个元素弹出(其实不弹出也没事)
        return result;
    }
};

滑动窗口最大值

![[Pasted image 20250405163224.png]]
大顶堆每次只能弹出最大值,我们无法移除其他数值,这样就造成大顶堆维护的不是滑动窗口里面的数值了。所以不能用大顶堆。

此时我们需要一个队列,这个队列呢,放进去窗口里的元素,然后随着窗口的移动,队列也一进一出,每次移动之后,队列告诉我们里面的最大值是什么


class Solution {
    class MyQueue { //单调队列(从大到小)
        public:
        deque<int> que;
        void pop(int value){
            if(!que.empty()&&que.front()==value){
                que.pop_front();
            }
        }
        void push(int value){
            while(!que.empty()&&value>que.back()){
                que.pop_back();
            }
            que.push_back(value);
        }
         int front() {
            return que.front();
        }
    };
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue  n;
        vector<int>max_num;
        for(int i=0;i<k;i++){
           n.push(nums[i]);
        }
         max_num.push_back(n.front());
       for(int i=k;i<nums.size();i++){
          n.pop(nums[i-k]);  
          n.push(nums[i]);
          max_num.push_back(n.front());
        }
        return max_num;
    }
};

前 K 个高频元素

  1. 要统计元素出现频率
  2. 对频率排序
  3. 找出前K个高频元素

使用优先级队列来对部分频率进行排序

为什么不用快排呢, 使用快排要将map转换为vector的结构,然后对整个数组进行排序, 而这种场景下,我们其实只需要维护k个有序的序列就可以了,所以使用优先级队列是最优的

而且使用大顶堆就要把所有元素都进行排序,那能不能只排序k个元素呢?

所以我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。

class Solution {
class mycomparison {
    public:
    bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
       return lhs.second > rhs.second;
    }
};                
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int,int>map;
        for(int i=0;i<nums.size();i++){
                map[nums[i]]++;
        }
        priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
        for(auto m:map){
            pri_que.push(m);
            if(pri_que.size()>k){
                pri_que.pop();
            }
        }
      vector<int> result(k);
        for (int i = k - 1; i >= 0; i--) {
            result[i] = pri_que.top().first;
            pri_que.pop();
        }
        return result;
   }
};

相关文章:

  • 驱动开发硬核特训 · Day 10 (理论上篇):设备模型 ≈ 运行时的适配器机制
  • c++中的this
  • 用java代码如何存取数据库的blob字段
  • 02 - spring security基于配置文件及内存的账号密码
  • 设计模式 --- 访问者模式
  • 【LeetCode】算法详解#4 ---合并区间
  • 进程线程回顾
  • Pinia最基本用法
  • Nginx基础讲解
  • ros2-rviz2控制unity仿真的6关节机械臂,探索从仿真到实际应用的过程
  • 论文精度:HeightFormer:基于Transformer的体素高度预测在路边3D目标检测中的应用
  • flutter 桌面应用之右键菜单
  • 【Cursor 】Cursor 安装与配置指南:从零开始的高效开发之旅
  • QT6 源(16):存储 QT 里元对象的类信息的类 QMetaClassInfo 的类,只有两个成员函数 name()、value(),比元对象属性简单多了
  • 数据驱动,数字能量分析API助力手机号数据解读
  • RAG Fusion技术详解与应用场景
  • 趣味编程之Spring的白学之路
  • 二、Android Studio环境安装
  • [React] 如何用 Zustand 构建一个响应式 Enum Store?附 RTKQ 实战与 TS 架构落地
  • 波束形成(BF)从算法仿真到工程源码实现-第七节-关于波束10个基本概念
  • 专利申请全球领先!去年我国卫星导航与位置服务产值超5700亿元
  • 清雪车司机未拉手刹下车导致溜车被撞亡,事故调查报告发布
  • “三个集中”之后:图说浦东新区28次撤乡并镇
  • 泰山、华海、中路等山东险企综合成本率均超100%,承保业务均亏损
  • 张广智︱“编年事辑”:打开学人心路历程的窗户
  • 透视社会组织创新实践中的花开岭现象:与乡村发展的融合共进