题目一
题目分析:
两个字符串所含字符的种类和数量都一样是否就一定是旋变串呢?不是!比如: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;
}