嵌套类问题的递归解题套路

一,含有嵌套的表达式求值

题目:给你一个字符串类型的表达式,例如:3 + (3  - 2 * 4 * (1 + 1) ),请问答案是多少。

由简单到复杂,先看没有括号的情况下如何解决。

无括号

思路:

准备两个栈,让数字和运算符一一对应,通过指针或者迭代器来遍历识别数字和符号,如果符号栈顶已经是乘除,遇到数字。先弹出数字栈顶的数字与之运算,得到的结果又添加进数字栈中从而处理特殊运算符和优先级。

我的思路不一样,遇到符号就检查符号栈顶是不是乘除号,如果是就进行一次乘除运算。同时结束遍历后,还要检查一次。这些操作都是确保最后只有加减等最低级运算符。

上述过程中还要涉及翻转栈。

class NestedExpression
{
private:
	int ans;
	string str;
	void calculate();
	int getNum(string::iterator&);
public:
	NestedExpression(string input);
	int getAns();
};
void NestedExpression::calculate()
{
    auto current = str.begin();
    int num = 0;
    std::stack<int> num_stack;
    std::stack<char> ope_stack;

    // 辅助函数,用于执行一次乘除运算
    auto performMulDiv = [&]() {
        char op = ope_stack.top();
        ope_stack.pop();
        int right = num_stack.top();
        num_stack.pop();
        int left = num_stack.top();
        num_stack.pop();
        if (op == '*') {
            num_stack.push(left * right);
        } else if (op == '/') {
            num_stack.push(left / right);
        }
    };

    while (current != str.end())
    {
        if (isdigit(*current))
        {
            // 解析数字
            num = getNum(current);
            num_stack.push(num);
        }
        else if (*current == '+' || *current == '-' || *current == '*' || *current == '/')
        {
            // 只要遇到运算符,就检查栈顶,处理掉乘除符号,保证栈里面的优先级一样。
            if(!ope_stack.empty() && (ope_stack.top() == '*' || ope_stack.top() == '/'))
            {
                performMulDiv();
            }
            // 将当前运算符压入栈
            ope_stack.push(*current);
            current++;
        }
        else
        {
            // 忽略空格或其他无效字符
            current++;
        }
    }

    // 处理栈中剩余的乘除法
    while (!ope_stack.empty() && (ope_stack.top() == '*' || ope_stack.top() == '/'))
    {
        performMulDiv();
    }

    // 反转栈,以便正确处理加减法
    std::stack<int> num_stack_reversed;
    std::stack<char> ope_stack_reversed;
    while (!num_stack.empty())
    {
        num_stack_reversed.push(num_stack.top());
        num_stack.pop();
    }
    while (!ope_stack.empty())
    {
        ope_stack_reversed.push(ope_stack.top());
        ope_stack.pop();
    }

    // 处理加减法
    int result = num_stack_reversed.top();
    num_stack_reversed.pop();
    while (!ope_stack_reversed.empty())
    {
        char op = ope_stack_reversed.top();
        ope_stack_reversed.pop();
        int next = num_stack_reversed.top();
        num_stack_reversed.pop();
        if (op == '+')
        {
            result += next;
        }
        else if (op == '-')
        {
            result -= next;
        }
    }

    // 最终结果
    ans = result;
}

int NestedExpression::getNum(string::iterator& current)
{
    int num = 0;
    while (current != str.end() && isdigit(*current)) // 读取数字
    {
        num = num * 10 + (*current - '0');
        current++;
    }
    return num;
}

NestedExpression::NestedExpression(string input) : str(input),ans(0)
{
}

int NestedExpression::getAns()
{
	calculate();
	return ans;
}

后面继续拓展,如果表达式里有括号怎么办?

有括号

首先,我们需要知道左括号只会出现在运算符号的后面,实际上括号所包裹的就相当于一个数字。就等同于每次压入数字栈的数字。当我们遇到左括号时,就进入递归,函数返回的值就是那个num。递归函数遇到右括号就停止。

使用类的成员变量来充当记录访问字符串位置的索引。

class NestedExpressionWithBracket
{
private:
	int index;
	string str;
	int recursion();
public:
	int getAns();
	NestedExpressionWithBracket(string str);
};
int NestedExpressionWithBracket::recursion()
{
    int result = 0;
    int cur = 0;
    std::stack<int> nums;
    std::stack<char> opes;

    while (index != str.length() && str[index] != ')')
    {
        if (isdigit(str[index]))
        {
            cur = cur * 10 + (str[index] - '0');  // 处理多位数
        }
        else if (str[index] == ' ')
        {
            // 忽略空格
        }
		else if (str[index] == '(')
		{
			// 遇到左括号,递归计算括号内的表达式
			index++;
			cur = recursion();
		}
        else
        {
            // 处理当前数字
            nums.push(cur);
            cur = 0;

            // 处理栈顶的乘法和除法
            while (!opes.empty() && (opes.top() == '*' || opes.top() == '/'))
            {
                int right = nums.top();
                nums.pop();
                int left = nums.top();
                nums.pop();
                char ope = opes.top();
                opes.pop();
                if (ope == '*') nums.push(left * right);
                else nums.push(left / right);
            }

            // 将当前运算符压入栈
            opes.push(str[index]);
        }

        index++;
    }

    // 处理最后一个数字
    nums.push(cur);

    // 处理栈中剩余的乘法和除法
    while (!opes.empty() && (opes.top() == '*' || opes.top() == '/'))
    {
        int right = nums.top();
        nums.pop();
        int left = nums.top();
        nums.pop();
        char ope = opes.top();
        opes.pop();
        if (ope == '*') nums.push(left * right);
        else nums.push(left / right);
    }

    // 处理栈中剩余的加法和减法
    result = nums.top();
    nums.pop();
    while (!opes.empty())
    {
        char ope = opes.top();
        opes.pop();
        int last = nums.top();
        nums.pop();
        if (ope == '+') result += last;
        else if (ope == '-') result = last - result;
    }

    return result;
}

int NestedExpressionWithBracket::getAns()
{
	return recursion();
}

NestedExpressionWithBracket::NestedExpressionWithBracket(string str) : str(str),index(0)
{
}

二,含有嵌套的字符串解码

题目

链接:394. 字符串解码

还是由简单到复杂,如果没有嵌套怎么处理

1,没有嵌套

class StringDecoding
{
private:
	int index;
	string str;
	string decoded_str;
	void append(int, string&, string);
	void decode();
	
public:
	StringDecoding(string& s);
	string getAns();
};
//StringDecoding
void StringDecoding::append(int times, string& src, string add)
{
	for (int i = 0; i < times; i++)
	{
		src += add;
	}
}

void StringDecoding::decode()
{
	int num = 0;
	string temp;
	while (index != str.length())
	{
		if (isdigit(str[index]))
		{
			num = num * 10 + (str[index] - '0');
		}
		else
		{
			while (index != str.length()&&!isdigit(str[index]))
			{
				temp += str[index++];
			}
			
			append(num, decoded_str, temp);
			num = 0;
			temp.clear();
			continue;
		}
		index++;
	}
}

StringDecoding::StringDecoding(string& s) : str(s) ,index(0),decoded_str("")
{
}

string StringDecoding::getAns()
{
	decode();
	return decoded_str;
}

2,有嵌套的

我们把括号内的也当作一个字符,遇到左括号就调用递归,让重复的字符串等于递归结果

class StringDecoding
{
private:
	int index;
	string str;
	string decoded_str;
	void append(int, string&, string);
	void decode();
	string decodeWithBracket();
	
public:
	StringDecoding(string& s);
	string getAns();
	string getAnsWithBracket();
};
string StringDecoding::decodeWithBracket()
{
	int num = 0;
	string result = "";
	string temp = "";
	while (index != str.length())
	{
		if (isdigit(str[index]))
		{
			num = num * 10 + (str[index++] - '0');
		}
		else if (str[index] == ')')
		{
			append(num, result, temp);
			index++;
			break;
		}
		else
		{
			if (str[index] == '(')
			{
				index++;
				temp += decodeWithBracket();
			}
			else
			{
				while (index != str.length()&&!isdigit(str[index]))
				{
					if (str[index] == ')') break;
					temp += str[index++];
				}
			}
			if (num == 0) append(1, result, temp);
			else append(num, result, temp);
			num = 0;
			temp.clear();
		}
	}

	return result;
}

string StringDecoding::getAnsWithBracket()
{
	decoded_str = decodeWithBracket();
	return decoded_str;
}

需要注意的一点是

while (index != str.length()&&!isdigit(str[index]))
	{
		if (str[index] == ')') break;
		temp += str[index++];
	}

在解析获取需要重复的字符串时,特殊处理右括号。

3,带注释的代码

AI修改后的

// 定义一个名为StringDecoding的类,用于字符串解码操作
class StringDecoding {
private:
    string str;        // 待解码的原始字符串
    int index;         // 当前处理到的字符位置索引
    string decoded_str; // 解码后的字符串结果

    // 辅助函数:将字符串add追加到src中times次
    void append(int times, string& src, string add) {
        for (int i = 0; i < times; i++) {
            src += add; // 每次循环将add追加到src的末尾
        }
    }

public:
    // 构造函数:初始化原始字符串str、索引index和解码结果字符串decoded_str
    StringDecoding(string& s) : str(s), index(0), decoded_str("") {}

    // 主解码函数:不处理带括号的情况
    void decode() {
        int num = 0; // 用于存储当前解析到的数字倍数
        string temp; // 用于临时存储当前解析到的字符串片段
        while (index != str.length()) { // 当索引未到达字符串末尾时,继续解析
            if (isdigit(str[index])) { // 如果当前字符是数字
                num = num * 10 + (str[index] - '0'); // 更新num为当前解析到的数字倍数
                index++; // 移动到下一个字符
            } else { // 如果当前字符不是数字
                while (index != str.length() && !isdigit(str[index])) { // 继续读取直到遇到数字或字符串结束
                    temp += str[index++]; // 将当前字符加入temp
                }
                append(num, decoded_str, temp); // 将temp追加到decoded_str中num次
                num = 0; // 重置num为0,以便处理下一个数字倍数
                temp.clear(); // 清空temp,以便解析下一个字符串片段
            }
        }
    }

    // 解码函数:处理带括号的情况
    string decodeWithBracket() {
        int num = 0; // 用于存储当前解析到的数字倍数
        string result = ""; // 用于存储当前括号内的解码结果
        string temp = ""; // 用于临时存储当前解析到的字符串片段
        while (index != str.length()) { // 当索引未到达字符串末尾时,继续解析
            if (isdigit(str[index])) { // 如果当前字符是数字
                num = num * 10 + (str[index++] - '0'); // 更新num为当前解析到的数字倍数
            } else if (str[index] == ')') { // 如果当前字符是右括号
                append(num, result, temp); // 将temp追加到result中num次
                index++; // 移动到下一个字符
                break; // 遇到右括号,结束当前括号内的解析
            } else { // 如果当前字符不是数字且不是右括号
                if (str[index] == '(') { // 如果当前字符是左括号
                    index++; // 移动到下一个字符(即括号内的第一个字符)
                    temp += decodeWithBracket(); // 递归调用decodeWithBracket解析括号内的内容,并将结果加入temp
                } else { // 如果当前字符是普通字符
                    while (index != str.length() && !isdigit(str[index]) && str[index] != ')') { // 继续读取直到遇到数字、右括号或字符串结束
                        temp += str[index++]; // 将当前字符加入temp
                    }
                }
                if (num == 0) append(1, result, temp); // 如果num为0,说明没有倍数要求,直接将temp追加到result一次
                else append(num, result, temp); // 否则,将temp追加到result中num次
                num = 0; // 重置num为0,以便处理下一个数字倍数
                temp.clear(); // 清空temp,以便解析下一个字符串片段
            }
        }

        return result; // 返回括号内的解码结果
    }

    // 获取解码后的字符串结果(不处理带括号的情况)
    string getAns() {
        decode(); // 调用decode函数进行解码
        return decoded_str; // 返回解码后的字符串
    }

    // 获取解码后的字符串结果(处理带括号的情况)
    string getAnsWithBracket() {
        decoded_str = decodeWithBracket(); // 调用decodeWithBracket函数进行解码
        return decoded_str; // 返回解码后的字符串
    }
};

4,leetcode上测试结果

三,计算原子个数

题目

726. 原子的数量

跟上面的类似,遇到括号就调用递归函数,计算括号内的表达式,只不过上面返回的是字符串,这道题是以哈希表的形式存储的括号内的结果,没有采用全局变量,使用了引用传递来收集数据。

思路

还是从简单到复杂,先思考没有括号的情况,再在其基础上进行添加遇到括号怎么处理。

代码

//头文件
class CountOfAtoms
{
private:
	int index;
	string str;
	std::unordered_map<string, int> map;
	std::unordered_map<string, int> temp;
	void recursion();
	void recursionWithBracket(std::unordered_map<string,int>&);
public:
	CountOfAtoms();
	CountOfAtoms(string& s);
	~CountOfAtoms() = default;
	void printCount();
	void printCountWithBracket();
};
//具体函数实现
void CountOfAtoms::recursion()
{
	string name = "";
	int cnt = 1;
	while (index != str.length())
	{
		if (str[index] >= 'a'&&str[index] <= 'z')
		{
			name += str[index++];
		}

		//遇到大写字母就开始处理(清算)之前记录在name中的字符。统计这个原子的个数。
		else if (str[index] >= 'A' && str[index] <= 'Z')
		{
			if (map.find(name) == map.end()&&name != "")
			{
				map[name] = cnt;
			}
			else if(name != "") map[name] += cnt;

			name.clear();
			cnt = 1;
			name += str[index++];
		}
		else if (isdigit(str[index]))
		{
			int num = 0;
			while (index != str.length()&&isdigit(str[index]))
			{
				num = num * 10 + (str[index] - '0');
				index++;
			}
			cnt = num;

			if (map.find(name) == map.end()) map[name] = cnt;
			else map[name] += cnt;
			name.clear();
			cnt = 1;
		}
	}

	if (map.find(name) == map.end()&&name != "")
	{
		map[name] = cnt;
	}
	else if (name != "") map[name] += cnt;
	return;
}

void CountOfAtoms::recursionWithBracket(std::unordered_map<string,int>& nested_map)
{
	string name = "";
	int cnt = 1;
	while (index != str.length())
	{
		if (str[index] >= 'a'&&str[index] <= 'z')
		{
			name += str[index++];
		}

		//遇到大写字母就开始处理(清算)之前记录在name中的字符。统计这个原子的个数。
		else if (str[index] >= 'A' && str[index] <= 'Z')
		{
			if (nested_map.find(name) == nested_map.end()&&name != "")
			{
				nested_map[name] = cnt;
			}
			else if(name != "") nested_map[name] += cnt;

			name.clear();
			cnt = 1;
			name += str[index++];
		}
		else if (isdigit(str[index]))
		{
			int num = 0;
			while (index != str.length()&&isdigit(str[index]))
			{
				num = num * 10 + (str[index] - '0');
				index++;
			}
			cnt = num;

			if (nested_map.find(name) == nested_map.end()) nested_map[name] = cnt;
			else nested_map[name] += cnt;
			name.clear();
			cnt = 1;
		}
		else if (str[index] == '(')
		{
			//先处理前面的原子个数
			if (name != "" && nested_map.find(name) == nested_map.end())
			{
				nested_map[name] = cnt;
			}
			else if (name != "") nested_map[name] += cnt;
			cnt = 1;
			name.clear();

			// 遇到左括号,递归计算括号内的表达式
			std::unordered_map<string,int> sub_map;
			index++;
			recursionWithBracket(sub_map);
			int num = 0;
			while (index != str.length()&&isdigit(str[index]))
			{
				num = num * 10 + (str[index] - '0');
				index++;
			}

			for (const auto& it : sub_map)
			{
				if (nested_map.find(it.first) == nested_map.end()) nested_map[it.first] = it.second * num;
				else nested_map[it.first] += it.second * num;
			}
		}
		else if (str[index] == ')')
		{
			index++;
			if (name != "" && nested_map.find(name) == nested_map.end())
			{
				nested_map[name] = cnt;
			}
			else if (name != "") nested_map[name] += cnt;
			return;
		}
	}

	if (nested_map.find(name) == nested_map.end()&&name != "")
	{
		nested_map[name] = cnt;
	}
	else if (name != "") nested_map[name] += cnt;
	return;

}

CountOfAtoms::CountOfAtoms() : index(0), str("")
{
}

CountOfAtoms::CountOfAtoms(string& s) : index(0),str(s)
{
}

void CountOfAtoms::printCount()
{
	recursion();
	for (const auto& it : map)
	{
		cout << "Atom " << it.first << " number " << it.second << endl;
	}
	return;
}

void CountOfAtoms::printCountWithBracket()
{
	recursionWithBracket(map);
	for (const auto& it : map)
	{
		cout << "Atom " << it.first << " number " << it.second << endl;
	}
	return;
}

有点臃肿,其实很多重复部分可以单独封装成函数。

我让AI优化一下

#include <iostream>
#include <unordered_map>
#include <string>
#include <cctype>
#include <algorithm>
#include <stack>

class Solution {
public:
    std::string countOfAtoms(std::string formula) {
        std::stack<std::unordered_map<std::string, int>> st;
        st.push({});
        int i = 0;
        int n = formula.length();

        while (i < n) {
            if (std::isupper(formula[i])) {
                std::string atom;
                atom += formula[i++];
                while (i < n && std::islower(formula[i])) {
                    atom += formula[i++];
                }

                int count = 0;
                while (i < n && std::isdigit(formula[i])) {
                    count = count * 10 + (formula[i++] - '0');
                }
                if (count == 0) {
                    count = 1;
                }
                st.top()[atom] += count;
            } else if (formula[i] == '(') {
                st.push({});
                i++;
            } else if (formula[i] == ')') {
                i++;
                int count = 0;
                while (i < n && std::isdigit(formula[i])) {
                    count = count * 10 + (formula[i++] - '0');
                }
                if (count == 0) {
                    count = 1;
                }

                auto subMap = st.top();
                st.pop();
                for (const auto& pair : subMap) {
                    st.top()[pair.first] += pair.second * count;
                }
            }
        }

        auto finalMap = st.top();
        std::vector<std::pair<std::string, int>> sortedAtoms(finalMap.begin(), finalMap.end());
        std::sort(sortedAtoms.begin(), sortedAtoms.end(), [](const auto& a, const auto& b) {
            return a.first < b.first;
        });

        std::string result;
        for (const auto& pair : sortedAtoms) {
            result += pair.first;
            if (pair.second > 1) {
                result += std::to_string(pair.second);
            }
        }

        return result;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值