【Leetcode刷题记录_C++】【分治法】

分治法

分治问题由“分”(divide)和“治”(conquer)两部分组成,通过把原问题分为子问题,再将子问题进行处理合并,从而实现对原问题的求解。我们在排序章节展示的归并排序就是典型的分治问题,其中“分”即为把大数组平均分成两个小数组,通过递归实现,最终我们会得到多个长度为 1 的子数组;“治”即为把已经排好序的两个小数组合成为一个排好序的大数组,从长度为 1 的子数组开始,最终合成一个大数组。

自上而下的分治可以和 memoization 结合,避免重复遍历相同的子问题。如果方便推导,也可以换用自下而上的动态规划方法求解。

表达式问题

241. 为运算表达式设计优先级

给定一个含有数字和运算符的字符串,为表达式添加括号,改变其运算优先级以求出不同的结果。你需要给出所有可能的组合的结果。有效的运算符号包含 +, - 以及 * 。

思路:
利用分治思想,我们可以把加括号转化为,对于每个运算符号,先执行处理两侧的数学表达式,再处理此运算符号(递归)。注意边界情况,即字符串内无运算符号,只有数字。(不断分为两个小表达式)

代码:

class Solution {
public:
	vector<int> Compute(string expression) {
		vector<int> re, m, n;
		string str;
		if (expression.size() % 2)
			expression = "+" + expression;
		if (expression.size() == 2)
			return { expression[1]};
		for (int i = 2; i < expression.size(); i += 2) {
			str.assign(expression.begin(), expression.begin() + i);
			m = Compute(str);
			str.assign(expression.begin() + i + 1, expression.end());
			n = Compute(str);
			for (int o = 0; o < m.size(); o++) {
				for (int j = 0; j < n.size(); j++) {
					if (expression[i] == '+')
						re.push_back(m[o] + n[j]);
					else if (expression[i] == '-')
						re.push_back(m[o] - n[j]);
					else
						re.push_back(m[o] * n[j]);
				}
			}
		}
		return re;
	}
	vector<int> diffWaysToCompute(string expression) {
		string str="";
		int n;
		str += expression[0] - '0';
		for (int i = 1; i < expression.size(); i++) {//先转换为1位数存储
			if (expression[i] >= '0') {
				n = str[str.size() - 1];
				if (n <= 9)
					str[str.size() - 1] = n * 10 + expression[i] - '0';
				else
					str += expression[i] - '0';
			}
			else
				str += expression[i];
		}
		return Compute(str);
	}
};

932. 漂亮数组

对于某些固定的 N,如果数组 A 是整数 1, 2, …, N 组成的排列,使得:

对于每个 i < j,都不存在 k 满足 i < k < j 使得 A[k] * 2 = A[i] + A[j]。

那么数组 A 是漂亮数组。

给定 N,返回任意漂亮数组 A(保证存在一个)。

思路:
题目要求不能让该等式成立:A[k] * 2 = A[i] + A[j],i < k < j,可知等式左边必为偶数,只要右边和为奇数即可保证不成立
可知 奇数 + 偶数 = 奇数,那就让A[i]和A[j]一个为奇数一个为偶数即可,不妨让A[i]为奇数,A[j]为偶数

对于从1到N的所有整数,奇数个数为 (N + 1) / 2,偶数个数为 N / 2
对于从1到N的所有整数,奇数个数为 (N + 1) / 2,偶数个数为 N / 2

如果[x1, x2, x3]是一个漂亮数组,则[k * x1 + b, k * x2 + b, k * x3 + b] 也一定是漂亮数组

对于从1到(N + 1)/2的所有整数x,得出其漂亮数组,并映射成1到N范围的所有奇数 2 * x - 1
对于从1到N/2的所有整数x,得出其漂亮数组,并映射成1到N范围的所有偶数 2 * x

一直到子问题规模为3

代码:

class Solution {
public:
	vector<int> beautifulArray(int n) {
		vector<int> a, b;
		if (n == 1)
			return { 1 };
		if (n == 2)
			return { 1,2 };
		if (n == 3)
			return { 1,3,2 };
		a = beautifulArray((n + 1) / 2);//n个数里有(n + 1) / 2个奇数
		for (int i = 0; i < a.size(); i++)//(n + 1) / 2个数映射成奇数
			a[i] = a[i] * 2 - 1;
		b = beautifulArray(n / 2);
		for (int i = 0; i < b.size(); i++)
			b[i] = b[i] * 2;
		a.insert(a.end(), b.begin(), b.end());//奇数在左,偶数在右
		return a;
	}
};

312. 戳气球

有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。

现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。

求所能获得硬币的最大数量。

思路:
由题,气球没有边界就是1,那么我们倒推,给两边加上1 的边界,每次在中间加上气球,表示被戳的气球,这样分析,我们设置左右边界为i,j,然后选取一个i、j的中间节点mid,总的价值就是:
val[i]×val[mid]×val[j]+solve(i,mid)+solve(mid,j)
简单来说,就是中间节点mid的气球在最后被戳破前,左右区间的气球已经被戳完了,直接相加就行,这样就能不断对区间细化为子问题,最后取最大值就行。

为避免重复,还可以用动态规划:
令 dp[i][j] 表示填满开区间 (i,j) 能得到的最多硬币数
在这里插入图片描述

for (int i = numsSize - 1; i >= 0; i--) {
    for (int j = i + 2; j <= numsSize + 1; j++) {
        for (int k = i + 1; k < j; k++) {
            int sum = val[i] * val[k] * val[j];
            sum += rec[i][k] + rec[k][j];
            rec[i][j] = fmax(rec[i][j], sum);
        }
    }
}

代码:

class Solution {
public:
	int maxCoins(vector<int>& nums) {
		vector<int> n{ 1 };
		n.insert(n.end(), nums.begin(), nums.end());
		n.push_back(1);
		vector<vector<int>> rec(n.size(), vector<int>(n.size(), 0));
		for (int i = n.size() - 1; i >= 0; i--) {//i倒序,保证每次计算用到的rec都是遍历过的
			for (int j = i + 2; j < n.size(); j++) {
				for (int k = i + 1; k < j; k++) {
					int sum = n[i] * n[k] * n[j];
					sum += rec[i][k] + rec[k][j];
					rec[i][j] = fmax(rec[i][j], sum);
				}
			}
		}
		return rec[0][n.size() - 1];
	}
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ycr的帐号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值