第七节课:


题目一

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
题目分析:

在这里插入图片描述
在这里插入图片描述
两个字符串所含字符的种类和数量都一样是否就一定是旋变串呢?不是!比如:abcd和cadb,abcd第一刀有三种切法,生成的第一个交换组不论换不换都不能生成cadb。1) 第一个交换组是a、bcd,想要c在第一个位置,则必须交换,但是交换之后a就只能位于最后了,显然不能生成cadb;2) 1 第一个交换组是ab、cd,想要c在第一个位置,则必须交换,但是交换之后d就只能位于a的前面了,显然不能生成cadb;3) 第一个交换组是abc、d,想要b位于最后,则必须交换,但是交换之后c就不能位于第一个了,显然不能生成cadb。因此无论怎么交换abcd均不能生成cadb,所以他们不互为旋变串。
在这里插入图片描述

范围上尝试:s1[l1…r1]与s2[l1…r2]是否为旋变串,三个参量,因为如果想要是旋变串长度必须相同,所以可以减少一个参数,变成s1从l1位置开始、s2从l2位置开始,长度为k的子串是否为旋变串(参数的数量尽量少)。可能性按照第一刀的切法分类:假设第一刀切在s1的i位置,将s1分成左1和右1两个部分,s2的第一刀切在j位置,s2切完之后分为左2和右2两个部分;想要s1和s2互为旋变串,则必须左1和左2互为旋变串并且右1和右2互为旋变串,或者左1和右2互为旋变串并且右1和左2互为旋变串,也就是说切完一刀之后要么对应部分直接互为旋变串,或者交换之后互为旋变串。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
改动态规划:
在这里插入图片描述
代码实现:

//返回s1从l1位置开始,s2从l2位置开始,长度为size的子串是否互为旋变串
bool f(string& s1, string& s2, int l1, int l2, int size) {
	if (size == 1) {
		return s1[l1] == s2[l2];
	}
	//s1[l1..l1+i]为左1;s1[l1+i+1..l1+size-1]为右1
	//不交换:s2[l2..l2+i]为左2;s2[l2+i+1..l2+size-1]为右2
	//交换:s2[l2 + size - i - 1..]左2;s2[l2..]为右2
	for (int i = 0; i < size - 1; i++) {
		//子过程的size都比母过程的size小,在改动态规划时,当前层之和下面层有关,而与本层无关
		//可以从下往上填
		if ((f(s1, s2, l1, l2, i + 1) && f(s1, s2, l1 + i + 1, l2 + i + 1, size - i - 1))
			|| (f(s1, s2, l1, l2 + size - i - 1, i + 1) && f(s1, s2, l1 + i + 1, l2, size - i - 1))) {
			return true;
		}
	}
	return false;
}

bool sameChars(string& s1, string& s2) {
	vector<int>charNum(26);
	for (int i = 0; i < s1.length(); i++) {
		charNum[s1[i] - 'a']++;
		charNum[s2[i] - 'a']--;
	}
	for (int i = 0; i < 26; i++) {
		if (charNum[i] != 0) {
			return false;
		}
	}
	return true;
}

bool isVariation(string& s1, string& s2) {
	if (s1.length() == 0 || s2.length() == 0 || s1.length() != s2.length()) {
		return false;
	}
	if (!sameChars(s1, s2)) {
		return false;
	}

	//长度相同,字符种类相同,数量相同
	return f(s1, s2, 0, 0, s1.length());
}


//改动态规划
//动态规划是一个三维表,底座是长度为len的正方形,高是len+1
bool isVariation2(string& s1, string& s2) {
	if (s1.length() == 0 || s2.length() == 0 || s1.length() != s2.length()) {
		return false;
	}
	if (!sameChars(s1, s2)) {
		return false;
	}

	//长度相同,字符种类相同,数量相同
	int len = s1.length();
	vector<vector<vector<int>>>dp(len, vector<vector<int>>(len, vector<int>(len + 1)));//dp[i][j][0]不用

	//第0层不需要
	//填写第1层
	for (int i = 0; i < len; i++) {
		for (int j = 0; j < len; j++) {
			dp[i][j][1] = s1[i] == s2[j];
		}
	}

	for (int k = 2; k <= len; k++) {
		//k能大于从i/j位置到最后的长度,三维表有的位置是用不到的
		//**这种约束不是本题特有的,由暴力递归改动态规划时,一定要递归哪些状态永远碰不到,**
		//**在填表时,这些位置碰都不要碰**
		for (int i = 0; i < len && k <= len - i; i++) {
			for (int j = 0; j < len && k <= len - j; j++) {
				for (int s = 0; s < k - 1; s++) {
					if ((dp[i][j][s + 1] && dp[i + s + 1][j + s + 1][k - s - 1])
						|| (dp[i][j + k - s - 1][s + 1] && dp[i + s + 1][j][k - s - 1])) {
						dp[i][j][k] = true;
					}
				}
			}
		}
	}
	return dp[0][0][len];
}

题目二

在这里插入图片描述
还钱的思路:首先建立s2的词频表,并且用变量all表示还有多好字符没有包含;双指针l、r,开始时l和r位于s2的0位置,r右移,对应字符的频率减1,直到以l为起始位置r为终止位置的s子串刚好把换完s1的所有欠账,即all=0,此时s1[l…r]就是以l为起点的最短子串;l右移,对应字符的频率加1,如果此时all仍为0,说明以当前l所在位置为起始位置包含s2所有字符的最小长度就是l到r这一段;重复上述过程,直到r来到最后位置,并且当前l到r不能包含s2的所有字符为止。
在这里插入图片描述
代码实现:

int minLen(string& s1, string& s2) {
	if (s1.length() == 0 || s2.length() == 0 || s1.length() < s2.length()) {
		return 0;//无效值
	}
	int l = 0;
	int r = 0;
	int all = 0;
	
	//建立词频表
	vector<int>chars(26);//默认输入全为小写字母
	for (int i = 0; i < s2.length(); i++) {
		chars[s2[i] - 'a']++;
		all++;
	}
	int res = INT_MAX;
	chars[s1[0] - 'a']--;
	if (chars[s1[0] - 'a'] >= 0) {
		all--;
	}
	while (l <= r&&r<s1.length()-1) {
		if (all == 0) {
			res = min(res, r - l + 1);
			chars[s1[l] - 'a']++;
			if (chars[s1[l] - 'a'] > 0) {
				all++;
			}
			l++;
		}
		else {
			r++;
			chars[s1[r] - 'a']--;
			if (chars[s1[r] - 'a'] >= 0) {
				all--;
			}
		}
	}
	return res == INT_MAX ? 0 : res;
}

题目三

在这里插入图片描述
题目分析:一个黑盒,黑盒中存放的每一条数据包含key、value、调用的频率和最近调用时间,黑盒中封装两个方法,set:添加/修改key对应的value、get查询key对应的value,要求时间复杂度都是O(1)。
在这里插入图片描述
二维双向链表:将频率相同的数据(节点)用双向链表组织,同时用一个map记录每一个节点的地址(为了在查询数据时,可以直接找到,而不用遍历链表);不同频率的链表(当成一个节点)也用双向链表组织,当链表为空时,要销毁链表(节点);当缓存超出容量时,所要删除的数据就是二维双向链表的第一个元素(大链表的第一个节点(是个链表)的第一个节点)

在这里插入图片描述

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

//数据节点
class Node {
public:
	int key;
	int value;
	int nodeNum;//当前节点操作次数
	Node* pre;
	Node* next;
	Node(int key, int value) {
		this->key = key;
		this->value = value;
		this->nodeNum = 1;//新建算依次操作
		this->pre = nullptr;
		this->next = nullptr;
	}
};

//链表节点
class ListNode {
public:
	int listNum;//当前链表存放的节点的操作次数
	ListNode* pre;
	ListNode* next;
	Node* dh;//该调用频率的链表的伪头节点
	Node* dt;//该调用频率的链表的伪尾节点

	ListNode(int listNum) {
		this->listNum = listNum;
		this->pre = nullptr;
		this->next = nullptr;
		this->dh = new Node(0, 0);
		this->dt = new Node(0, 0);
		//头尾相连
		dh->next = dt;
		dt->pre = dh;
	}
	~ListNode() {
		delete dh;
		dh = nullptr;
		delete dt;
		dt = nullptr;
	}
};

class LFU {
private:
	int K;//最多存放多少条数据
	int cur;//当前已经存放的数据数

	ListNode* dhead;//伪头链表
	ListNode* dtail;//伪尾链表

	unordered_map<int, Node*>nodeMp;//key对应的Node地址
	unordered_map<int, ListNode*>listMp;//调用频率对应的ListNode地址
public:
	LFU(int K) {
		this->K = K;
		this->cur = 0;
		this->dhead = nullptr;
		
		dhead = new ListNode(0);
		dtail = new ListNode(INT_MAX);
		
		dhead->next = dtail;
		dtail->pre = dhead;

		listMp.insert({ 0,dhead });
		listMp.insert({ INT_MAX,dtail });
	}
	~LFU() {
		delete dhead;
		dhead = nullptr;
		delete dtail;
		dtail = nullptr;
	}
	void set(int key, int value) {
		if (K == 0) {
			return;
		}

		if (nodeMp.find(key) != nodeMp.end()) {//当前的key在缓存中,修改对应的value
			Node* curNode = nodeMp[key];//正在操作的节点
			curNode->value = value;//修改当前节点
			moveNode(curNode);//将修改value之后的curNode移到争取的链表中
		}
		else {//当前的key不在缓存中,插入key
			if (cur == K) {//缓存已满,先进行删除
				deleteNode();
				cur--;
			}

			Node* curNode = new Node(key, value);//当前节点
			ListNode* curList = (listMp.find(1) != listMp.end() ? listMp[1] : new ListNode(1));//当前链表
			nodeMp.insert({ key,curNode });
			
			//将节点插入链表尾
			curNode->next = curList->dt;
			curNode->pre = curList->dt->pre;
			curList->dt->pre->next = curNode;
			curList->dt->pre = curNode;
			
			//当前链表如果是新创建的,则要添加入大链表中
			if (dhead->next != curList) {
				listMp.insert({ 1, curList });
				curList->next = dhead->next;
				curList->pre = dhead;
				dhead->next->pre = curList;
				dhead->next = curList;
			}
			cur++;
		}
	}


	int get(int key) {
		if (K == 0) {
			return INT_MAX;
		}
		if (nodeMp.find(key) == nodeMp.end()) {
			return INT_MAX;//查找的key不存在,返回无效值
		}

		//操作次数+1,并挪到正确的链表中
		Node* curNode = nodeMp[key];
		moveNode(curNode);

		return nodeMp[key]->value;
	}

private:
	//给我一个节点,将他的操作次数加一,并将他从当前链表移除、移到正确的链表中
	void moveNode(Node*curNode) {
		ListNode* curList = listMp[curNode->nodeNum];//正在操作的链表
		
		curNode->nodeNum++;//修改当前节点的操作次数

		//修改当前链表
		curNode->next->pre = curNode->pre;
		curNode->pre->next = curNode->next;

		//将当前节点放到对应频率的链表中
		ListNode* newList = listMp.find(curNode->nodeNum) != listMp.end() ? listMp[curNode->nodeNum] : new ListNode(curNode->nodeNum);
		if (listMp.find(curNode->nodeNum) == listMp.end()) {//如果要加入的链表不存在,则要新建
			//将创建的链表加入到大链表中
			ListNode* host = curList->next;
			newList->next = host;
			newList->pre = host->pre;

			host->pre->next = newList;
			host->pre = newList;
			listMp.insert({ curNode->nodeNum ,newList });
		}
		//将当前节点存入当前链表中
		curNode->next = newList->dt;
		curNode->pre = newList->dt->pre;

		newList->dt->pre->next = curNode;
		newList->dt->pre = curNode;

		//如果修改之后当前链表只剩下伪头、尾节点,就删除当前链表
		if (curList->dh->next == curList->dt) {
			//在删除之前要将大链表修改好
			curList->next->pre = curList->pre;
			curList->pre->next = curList->next;

			listMp.erase(curList->listNum);

			delete curList;
			curList = nullptr;
		}
	}

	//从缓存中删除一个节点
	void deleteNode() {
		Node* deNode = dhead->next->dh->next;
		dhead->next->dh->next = deNode->next;
		deNode->next->pre = dhead->next->dh;
		nodeMp.erase(deNode->key);

		delete deNode;
		deNode = nullptr;

		//删除节点之后当前链表为空,则将链表删除
		if (dhead->next->dh == dhead->next->dt) {
			ListNode* deList = dhead->next;
			deList->next->pre = dhead;
			deList->pre->next = deList->next;
			listMp.erase(deList->listNum);

			delete deList;
			deList = nullptr;
		}
	}
};

题目四

在这里插入图片描述
题目分析:

在这里插入图片描述
时间复杂度:O(N),额外空间复杂度:O(1)
将有数组的元素减去距离数组对应元素,得到纯能值数组(存放在油数组中,因此不占用空间复杂度),纯能值数组中的元素表示,从当前站到下一站,净剩多少能量。
判断是否为良好出发点,只需判断从选定的出发点开始,累加一圈,看中途是否有小于0的值,如果有,则不是,否则是。但是这种方法的时间复杂度是O(N^2)。
在这里插入图片描述

显然,如果纯能值数组的元素均小于0,则全部是非良好出发点;选择任意一个大于0的元素为联通区的头;联通区:[H,I)表示从H位置到I位置是联通的,包括H,不包括I;rest:通过联通区还剩多少能量,初始值为5;need:当前节点接到联通区的头上至少需要多少能量,初始值为0;从联通区的尾向外扩,直到再扩rest就小于0了为止,如果不到一圈,则说明当前的头不是良好出发点;接下来,让头往外扩,如果当前节点的大于等于need,则可以将当前节点接到头上,变成新的头,如果rest重新大于0了,让尾往外扩,如果当前节点的小于need,则当前节点不是良好出发点(更新need);当来到的节点进入到联通区的开边界,并且仍然没有一个出发点是良好出发点时,后续节点肯定不是良好出发。

在这里插入图片描述
在这里插入图片描述

当来到的节点进入到联通区的开边界,并且仍然没有一个出发点是良好出发点时,后续节点肯定不是良好出发。证明:
在这里插入图片描述
当联通区中存在良好出发点时:头往外扩,当其他节点可以连接到良好出发点,那么他也是良好出发点
在这里插入图片描述
代码实现:

vector<bool> isGoodStarts(vector<int>& oils, vector<int>& dis) {
	//修改oils为纯能值数组
	//不占用额外空间复杂度
	int len = oils.size();
	vector<bool>res(len);
	int index = -1;
	int rest = 0;
	int need = 0;
	for (int i = 0; i < len; i++) {
		oils[i] = oils[i] - dis[i];
		if (oils[i] >= 0) {
			index = i;
			rest = oils[i];
		}
	}
	if (index == -1) {
		return res;
	}
	//方法一:可以从每一个节点作为出发点,加一圈,只要其中有小于0的时候,说明该出发点不是良好出发点
	//时间复杂度O(N^2)

	//左神的方法:
	//联通区:[h,t)
	int h = index;//联通区的头
	int t = (index + 1) % len;//联通区的尾
	int first = h;//判断的第一个节点,他与第一个良好出发点之间的节点已经判断为都不是良好出发点
	while (h != t && index != t) {
		if (rest >= -oils[t]) {
			//剩余的能量大于等于当前尾节点的纯能值,尾结点可以向外扩
			rest = rest + oils[t];
			t = (t + 1) % len;
		}
		else {
			//剩余的能量小于当前尾节点的纯能值,尾结点不能向外扩
			//说明当前的头节点不是良好出发点
			res[h] = false;
			if (oils[((h - 1) % len < 0 ? (h - 1) % len + len : (h - 1) % len)] >= need) {
				//C++中负数取模还是负数,因此要加上len
				//尝试头节点往外扩
				h = ((h - 1) % len < 0 ? (h - 1) % len + len : (h - 1) % len);
				rest = rest + (oils[h] - need);
				need = 0;
			}
			else {
				//如果头节点不能往外扩,则当前来到的节点不是良好出发点
				index = ((h - 1) % len < 0 ? (h - 1) % len + len : (h - 1) % len);
				while (oils[index] < need&&index!=t) {
					res[index] = false;
					need = need - oils[index];
					index = ((index - 1) % len < 0 ? (index - 1) % len + len : (index - 1) % len);
				}
				h = index;
				rest += oils[index] - need;
				need = 0;
			}
		}
	}
	if (h == t&&rest>=-oils[t]) {
		//当前的头是良好出发点
		//其他节点只要能接到头上就是良好出发点
		res[h] = true;
		int sum = 0;
		int i = (h - 1) % len;
		if (i < 0) {
			i = i + len;
		}
		for (; i != (first + 1) % len; ) {
			sum += oils[i];
			if (sum >= 0) {
				res[i] = true;
			}
			i = (i - 1) % len;
			if (i < 0) {
				i = i + len;
			}
		}
	}

	return res;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值