第六节课:


题目一

在这里插入图片描述
当暴力递归该动态规划时,发现一些初始状态没有,可以回到原问题,将所需的初始状态推出来。另外,写出的递归代码一定要具有无后效性,即子问题的结果不会因为母问题结果而受到影响。
递归:大问题做决策1调小问题1、做决策2调小问题2…一定要保证大问题调小问题的所有影响,都体现在调小问题的参数上了,也就是要做到无后效性,大问题之前的状态不会影响到小问题。

原始串str,全部由小写字母组成、匹配串exp可以由小写字母、‘’、和‘.’组成,其中‘.’可以变成任意一个小写字母,但必须要变,‘’必须和前一个字符(包括‘.’)配合使用,可以将前一个字符变成任意个该字符(包括0个)。
f(str,exp,si,ei):str[si…]能否被exp[ei…]匹配。1) exp[ei+1]不是*–>如果当前位置不匹配,直接返回false,否则返回f(str,exp,si+1,ei+1);2) exp[ei+1]是*–>尝试将当前位置的字符和*变0…k个当前字符,后续只要有正确匹配的情况,当前结果就为true,否则为false。

在这里插入图片描述
代码实现:

bool isValid(string& s, string& e) {
	for (int i = 0; i < s.length(); i++) {
		if (s[i] < 'a' || s[i]>'z') {
			return false;
		}
	}
	for (int i = 0; i < e.length(); i++) {
		if (((e[i] < 'a' || e[i]>'z')&&(e[i]!='*'&&e[i]!='.')) || (i == 0 && e[i] == '*') || (i + 1 < e.length() && (e[i] == '*' && e[i + 1] == '*'))) {
			return false;
		}
	}

	return true;
}
//f(i,j)表示:s[i..]和e[j..]能否匹配
//e[j]永远不要是*
bool f(string& s, string& e, int i, int j) {
	if (j == e.length()) {
		return i == s.length();
	}
	if (i == s.length()) {
		if ((e.length() - j) % 2 == 1) {
			return false;
		}
		for (int k = e.length() - 1; k > j; k -= 2) {
			if (e[k] != '*' || e[k - 1] == '*') {
				return false;
			}
		}
		return true;
	}
	if (j == e.length() - 1 || e[j + 1] != '*') {
		if ((s[i] != e[j] && e[j] != '.')) {
			return false;
		}
		return f(s, e, i + 1, j + 1);
	}
	while (i<s.length()&&(s[i] == e[j] || e[j] == '.')) {
		if (f(s, e, i, j + 2)) {
			return true;
		}
		i++;
	}
	return f(s, e, i, j + 2);
}

bool isMatch(string& s, string& e) {
	if (!isValid(s, e)) {
		return false;
	}

	return f(s, e, 0, 0);
}

//改动态规划
bool isMatch2(string& s, string& e) {
	if (!isValid(s, e)) {
		return false;
	}
	vector<vector<bool>>dp(s.length() + 1, vector<bool>(e.length() + 1));
	dp[s.length()][e.length()] = true;//最后一列

	//最后一行
	//a*b*c*..
	for (int j = e.length() - 1; j >= 0; j--) {
		if ((e.length() - j) % 2 == 1) {
			dp[s.length()][j] = false;
		}
		else {
			if (e[j] != '*' && e[j + 1] == '*') {
				dp[s.length()][j] = true;
			}
			else {
				break;//剩余的全部为false
			}
		}
	}
	//倒数第二列:e的最后一个字符能否和s[i..]匹配
	//无后效性决定了最后一个字符如果为*,那么这个位置的值不会被使用,因此当作不是*处理
	//表是为递归返回值服务的,不会递归到的位置,表中也不需要填,最后一个字符是*的情况,就属于不用填的情况
	if (e[e.length() - 1] == s[s.length() - 1] || e[e.length() - 1] == '.') {
		dp[s.length() - 1][e.length() - 1] = true;
	}
	
	//从下往上,从右往左
	for (int i = dp.size() - 2; i >= 0; i--) {
		for (int j = dp[0].size() - 3; j >= 0; j--) {
			if (e[j + 1] != '*') {
				if ((s[i] != e[j] && e[j] != '.')) {
					dp[i][j] = false;
				}
				else {
					dp[i][j] = dp[i + 1][j + 1];
				}
			}
			else {
				int i1 = i;
				while (i1<s.length()&&(s[i1] == e[j] || e[j] == '.')) {
					if (dp[i1][j + 2]) {
						dp[i][j] = true;
						break;
					}
					i1++;
				}
				if (dp[i][j] == false) {
					dp[i][j] = dp[i1][j + 2];
				}
			}
		}
	}
	return dp[0][0];
}

题目二

在这里插入图片描述
如果是累加和可以构建单调性,但是异或和很难,两个小数异或可能很大,两个大数异或可能很小。
不好的方法是求对任意两个位置都求他们之间异或和,返回其中的最大值。需要用到双层循环,第二层循环难以优化的原因是,不知道哪个前缀异或和与当前的前缀异或和异或得到的结果最大,使用前缀数,可以解决这一问题。具体如下:
将每个位置的前缀和依次添加到前缀树中,当遍历到arr[i]时,前缀树中存在i-1个前缀和,我们想从中找到谁与presum[i]异或的值最大,可以在前缀树中,尽量往能使异或结果高位为1的路径走。

在这里插入图片描述
在这里插入图片描述
如果是32位有符号整数,怎么做?最高位符号位,如果是1,更想遇到的是1,为0,更想遇到的是0;其他位还是尽量使高位为1。因为如果负数只能和正数异或,由于负数是用补码存储的,所以高位的1,变成原码就是0,此时就做到了,让结果尽可能大;如果负数可以和负数异或,那么符号位变成0,高位尽可能为1,也让结果尽可能大。
代码实现:

//方法一:不好的方法
int maxEor(vector<int>& arr) {
	if (arr.size() == 0) {
		return 0;
	}
	vector<int>presum(arr.size());
	presum[0] = arr[0];
	int res = arr[0];
	//数据预处理
	//前缀异或和
	for (int i = 1; i < arr.size(); i++) {
		presum[i] = presum[i - 1] ^ arr[i];
		res = max(res, presum[i]);
	}

	//arr[i..j]的异或和=arr[0..j]的异或和^arr[0..i-1]的异或和
	//原因是两个相等的数异或为0
	for (int i = 1; i < arr.size(); i++) {
		for (int j = i; j >= 1; j--) {
			//第二层循环难以优化的原因是,不知道哪个前缀异或和与当前的前缀异或和异或得到的结果最大
			//使用前缀数,可以解决这一问题
			res = max(res, presum[i] ^ presum[j - 1]);
		}
	}
	return res;
}

//方法二
class Node {
public:
	vector<Node*> nexts;
	Node() {
		nexts.push_back(nullptr);
		nexts.push_back(nullptr);
	}
};

class NumTrie {
public:
	Node* h;
	NumTrie() {
		this->h = new Node;
	}
	void add(int num) {
		Node* cur = h;
		for (int i = 31; i >= 0; i--) {
			int path = ((num >> i) & 1);//回去i位的值0/1
			cur->nexts[path] = (cur->nexts[path] == nullptr ? new Node : cur->nexts[path]);
			cur = cur->nexts[path];
		}
	}
	int maxeor(int eor) {
		Node* cur = h;
		int res = 0;
		for (int i = 31; i >= 0; i--) {
			int path = ((eor >> i) & 1);
			int best = (i == 31 ? path : (path ^ 1));
			best = (cur->nexts[best] != nullptr ? best : (best ^ 1));
			res = (res | ((path ^ best) << i));
			cur = cur->nexts[best];//bug出在这里,少了更新cur
		}
		return res;
	}
};

int maxEor2(vector<int>& arr) {
	if (arr.size() == 0) {
		return 0;
	}
	NumTrie nt;
	nt.add(0);
	int res = arr[0];
	int sum = 0;
	for (int i = 0; i < arr.size(); i++) {
		sum ^= arr[i];
		res = max(res, nt.maxeor(sum));
		nt.add(sum);
	}
	return res;
}

题目三

在这里插入图片描述

在这里插入图片描述
尝试1:假设arr[l…r]范围内的每个气球都尝试最先被打爆f(l.r),这样会遇到问题:当假设其中一个值是最先被打爆时,调用的子问题没法获得最接近他且没被打爆的气球。大问题对子问题的所有影响都要体现在调用参数上。
在这里插入图片描述
尝试2:假设arr[l…r]范围内的每个气球都尝试最后被打爆f(l.r),潜台词:l-1位置和r+1位置的气球一定没爆,这两个位置会调用f(l.r),说明此时正在尝试l-1位置/r+1位置最后被打爆;假设当前位置来到了i,即该位置的气球最后被打爆,那么他就可以调f(l,i-1)和f(i+1,r),当前获得分数位f(l,i-1)+f(i+1,r)+lir位置上的分数

在这里插入图片描述
代码实现:

int f(vector<int>& arr, int l, int r) {
	if (l == r) {
		return arr[l] * arr[l - 1] * arr[r + 1];
	}
	int res = max(arr[l] * arr[l - 1] * arr[r + 1] + f(arr, l + 1, r), arr[r] * arr[l - 1] * arr[r + 1] + f(arr, l, r - 1));

	for (int i = l + 1; i < r; i++) {
		res = max(res, arr[i] * arr[l - 1] * arr[r + 1] + f(arr, l, i - 1) + f(arr, i + 1, r));
	}
	return res;
}

int maxScore(vector<int>& arr) {
	if (arr.size() == 0) {
		return 0;
	}
	arr.resize(arr.size() + 2, 1);
	for (int i = arr.size() - 2; i >= 0; i--) {
		arr[i + 1] = arr[i];
	}
	arr[0] = 1;

	return f(arr, 1, arr.size() - 2);
}


//改动态规划
int maxScore2(vector<int>& arr) {
	int len = arr.size();
	vector<vector<int>>dp(len - 1, vector<int>(len - 1));
	for (int i = 1; i < dp.size(); i++) {
		dp[i][i] = arr[i] * arr[i - 1] * arr[i + 1];
	}
	//从下往上,从左往右
	for (int i = dp.size() - 2; i > 0; i--) {
		for (int j = i + 1; j < dp.size(); j++) {
			dp[i][j] = max(arr[i] * arr[i - 1] * arr[j + 1] + dp[i + 1][j], arr[j] * arr[i - 1] * arr[j + 1] + dp[i][j - 1]);
			for (int k = i + 1; k < j; k++) {
				dp[i][j] = max(dp[i][j], arr[k] * arr[i - 1] * arr[j + 1] + dp[i][k - 1] + dp[k + 1][j]);
			}

		}
	}
	return dp[1][len - 2];
}

题目四

在这里插入图片描述
题目理解:数组的下标表示圆盘的大小,长度是圆盘的数量,下标对应的元素是该圆盘目前所在的位置。问给定数组表示的状态,是否为最优移动轨迹出现的状态,如果是返回是第几个状态,否则返回-1。
在这里插入图片描述
N层汉诺塔问题的最优解要走2^N-1步:

在这里插入图片描述
假设现在是i层汉诺塔问题,想要将1…i从from杆移到to杆,中间可以借助other杆;三步走:1) 1…i-1的圆盘从from杆挪到other杆;2) i圆盘从from杆挪到to杆;3) 1…i-1的圆盘从other杆挪到to杆;如果i盘的状态是from,说明第一步没走完、走了多少步却决于i-1层汉诺塔问题;如果i盘的状态是to,说明当前至少走了2^i-1+1,总步数取决于3)走了多少步;如果i是other状态,说明不是最优解的任何一步。

在这里插入图片描述

代码实现:

//把0..i的圆盘,从from全部挪到to
//返回arr[0..i]中的状态是最优解的第几步
//N层汉诺塔问题是要分解成N-1层...1层的问题的,因此arr中出现的状态也是某个小问题的状态
//时间复杂度:O(N),递归直走一侧,且每次i都要减一
int f(vector<int>& arr, int i, int from, int other, int to) {
	if (i == -1) {
		return 0;
	}
	if (arr[i] == other) {
		return -1;
	}
	//第一步没走完
	if (arr[i] == from) {
		return f(arr, i - 1, from, to, other);
	}
	//第三步完成的程度
	int rest = f(arr, i - 1, other, to, from);
	return rest == -1 ? -1 : rest + (1 << i);
}

int step(vector<int>& arr) {
	if (arr.size() == 0) {
		return -1;
	}
	return f(arr, arr.size() - 1, 1, 2, 3);
}


//改动态规划
int step2(vector<int>& arr) {
	if (arr.size() == 0) {
		return -1;
	}
	int from = 1;
	int mid = 2;
	int to = 3;
	int i = arr.size() - 1;
	int res = 0;
	int temp = 0;
	while (i >= 0) {
		if (arr[i] == mid) {
			return -1;
		}
		if (arr[i] == to) {
			res += (1 << i);
			temp = from;
			from = mid;
			mid = temp;
			i--;
		}
		else {
			temp = to;
			to = mid;
			mid = temp;
			i--;
		}
	}
	return res;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值