今天的还是挺简单的
给你一个由大小写英文字母组成的字符串 s 。
一个整理好的字符串中,两个相邻字符 s[i] 和 s[i + 1] 不会同时满足下述条件:
请你将字符串整理好,每次你都可以从字符串中选出满足上述条件的 两个相邻 字符并删除,直到字符串整理好为止。
请返回整理好的 字符串 。题目保证在给出的约束条件下,测试样例对应的答案是唯一的。
注意:空字符串也属于整理好的字符串,尽管其中没有任何字符。
示例 1
输入:s = “leEeetcode”
输出:“leetcode”
解释:无论你第一次选的是 i = 1 还是 i = 2,都会使 “leEeetcode” 缩减为 “leetcode” 。
示例 2
输入:s = “abBAcC”
输出:""
解释:存在多种不同情况,但所有的情况都会导致相同的结果。例如:
“abBAcC” --> “aAcC” --> “cC” --> “”
“abBAcC” --> “abBA” --> “aA” --> “”
示例 3
输入:s = “s”
输出:“s”
提示:
1 <= s.length <= 100
s 只包含小写和大写英文字母
这个反之亦然真的sb 意思就是aA和Aa都会被消
这题就无脑模拟消消乐了
会从中间删除 所以用链表这个数据结构
由于我忘了STL的list的erase参数了 所以就手撕了
当找到某个字符的后面±32和它自己一样的话 那么就可以消除
就把这两个字符都删除了 删除的顺序不影响消消乐
我在比赛的时候 删完之后的指针是重头开始再找的 其实完全没必要
删完之后指针返回前面一个就好了 因为消消乐只会影响它前后两个字符
所以总的时间复杂度是O(n)的 而我比赛时候的代码是O(n^2)的 无所谓 无脑写对就行 怎么快怎么写
class Solution {
public:
struct node{
//熟练的链表节点
char val;
node* last;
node* next;
node(char c){
val=c;
last=nullptr;
next=nullptr;
}
};
node* prehead;
string makeGood(string s) {
prehead=new node(0);
node* p=prehead;
for(int i=0;i<s.size();i++){
//建双向链表
p->next=new node(s[i]);
p->next->last=p;
p=p->next;
}
bool flag=true; //不需要再消了
while(flag){
flag=false;
p=prehead->next; //从头开始 其实没必要
while(p){
if(p->next){
if(p->val-32==p->next->val||p->val+32==p->next->val){
//要消了
p->last->next=p->next->next;
if(p->next->next)p->next->next->last=p->last;
flag=true;
//其实这里写上p=p->last即可 上面的那句重头开始就不用写了
break;
}
}
p=p->next;
}
}
string ans="";
p=prehead->next;
while(p){
//看下还剩下什么
ans.push_back(p->val);
p=p->next;
}
return ans;
}
};
给你两个正整数 n 和 k,二进制字符串 Sn 的形成规则如下:
其中 + 表示串联操作,reverse(x) 返回反转 x 后得到的字符串,而 invert(x) 则会翻转 x 中的每一位(0 变为 1,而 1 变为 0)
例如,符合上述描述的序列的前 4 个字符串依次是:
请你返回 Sn 的 第 k 位字符 ,题目数据保证 k 一定在 Sn 长度范围以内。
示例 1
输入:n = 3, k = 1
输出:“0”
解释:S3 为 “0111001”,其第 1 位为 “0” 。
示例 2
输入:n = 4, k = 11
输出:“1”
解释:S4 为 “011100110110001”,其第 11 位为 “1” 。
示例 3
输入:n = 1, k = 1
输出:“0”
示例 4
输入:n = 2, k = 3
输出:“1”
提示
想了一下 极限把整个s20模拟出来 总长度也不过是2^20方以内
因为每次是把长度加倍然后+1
所以可以把整个sn模拟出来
这里不能模拟reverse操作 因为这个操作会把复杂度升到O(n^2)
+1然后前面顺序反转并把数字翻转 等价于:
总的时间复杂度是sn最后的长度 因为我们进行了那么多次push构造出来
class Solution {
public:
char findKthBit(int n, int k) {
string s="0";
for(int i=2;i<=n;i++) //递推
s=f(s);
return s[k-1]; //k位
}
string& f(string &s){
//递推的操作 写成函数逻辑清楚
s+="1";
for(int i=s.size()-2;i>=0;i--)
if(s[i]=='1')s.push_back('0');
else s.push_back('1');
return s;
}
};
给你一个数组 nums 和一个整数 target 。
请你返回 非空不重叠 子数组的最大数目,且每个子数组中数字和都为 target 。
示例 1
输入:nums = [1,1,1,1,1], target = 2
输出:2
解释:总共有 2 个不重叠子数组(加粗数字表示) [1,1,1,1,1] ,它们的和为目标值 2 。
示例 2
输入:nums = [-1,3,5,1,4,2,-9], target = 6
输出:2
解释:总共有 3 个子数组和为 6 。
([5,1], [4,2], [3,5,1,4,2,-9]) 但只有前 2 个是不重叠的。
示例 3
输入:nums = [-2,6,6,3,5,4,1,2,8], target = 10
输出:3
示例 4
输入:nums = [0,0,0], target = 0
输出:3
提示
观察示例2 知道题目的做法可以是把全部和为target的子数组都找出来 然后再筛掉重叠的
那么找子数组可以用前缀和的方式
假设以某个位置j为结尾的子数组 可能会有多个子数组i~j的和为target (因为数组有负数)
但是此时其实只需要拿到最近的i即可 也就是i越大越好 原因是因为贪心 区间尽可能短 那么能不重叠的区间就肯定多点
所以对于固定的位置j为结尾 假设其前缀和为x 那么只需要找到前缀和为y=x-target的位置i即可 这个i尽可能大 那么此时x-y==target
至于怎么找到对应的i 只需要以前缀和的值为键丢进hashmap就行了
把所有区间找到之后 也是一个经典的贪心问题
不重叠的选最多 直接对区间右侧排序 右侧一样的话就对长度排序
然后一路选就行了 证明略
总的时间复杂度为O(nlogn) 被排序卡了
不过应该有不排序的贪心方法
class Solution {
public:
int maxNonOverlapping(vector<int>& nums, int target) {
int n=nums.size();
unordered_map<int,vector<int>> mp; //某个前缀和的i的位置
vector<int> sum(n,0); //前缀和
sum[0]=nums[0];
mp[sum[0]].push_back(0);
for(int i=1;i<nums.size();i++){
sum[i]=sum[i-1]+nums[i];
mp[sum[i]].push_back(i); //记录位置
}
vector<pair<int,int>> v;
for(int i=0;i<sum.size();i++){
if(sum[i]==target)v.push_back({
0,i}); //区间符合
int l=-1;
for(auto j:mp[sum[i]-target]) //找到符合的区间
if(j<i)l=j;
else break;
if(l!=-1)v.push_back({
l+1,i});
}
sort(v.begin(),v.end(),[](pair<int,int> a,pair<int,int> b){
if(a.second!=b.second)return a.second<b.second;
return a.first>b.first;
}); //排序后贪心
int ans=0;
int now=-1;
for(int i=0;i<v.size();i++){
//按顺序加入答案
if(v[i].first>now){
ans++;
now=v[i].second;
}
}
return ans;
}
};
有一根长度为 n 个单位的木棍,棍上从 0 到 n 标记了若干位置。例如,长度为 6 的棍子可以标记如下:
给你一个整数数组 cuts ,其中 cuts[i] 表示你需要将棍子切开的位置。
你可以按顺序完成切割,也可以根据需要更改切割的顺序。
每次切割的成本都是当前要切割的棍子的长度,切棍子的总成本是历次切割成本的总和。对棍子进行切割将会把一根木棍分成两根较小的木棍(这两根木棍的长度和就是切割前木棍的长度)。请参阅第一个示例以获得更直观的解释。
返回切棍子的 最小总成本 。
示例 1
输入:n = 7, cuts = [1,3,4,5]
输出:16
解释:按 [1, 3, 4, 5] 的顺序切割的情况如下所示:
第一次切割长度为 7 的棍子,成本为 7 。第二次切割长度为 6 的棍子(即第一次切割得到的第二根棍子),第三次切割为长度 4 的棍子,最后切割长度为 3 的棍子。总成本为 7 + 6 + 4 + 3 = 20 。
而将切割顺序重新排列为 [3, 5, 1, 4] 后,总成本 = 16(如示例图中 7 + 4 + 3 + 2 = 16)。
示例 2
输入:n = 9, cuts = [5,6,1,4,2]
输出:22
解释:如果按给定的顺序切割,则总成本为 25 。总成本 <= 25 的切割顺序很多,例如,[4,6,5,2,1] 的总成本 = 22,是所有可能方案中成本最小的。
提示
挺经典的dp问题
这个区间问题 肯定需要枚举所有情况才能知道怎样更优(直觉
所以不会存在贪心的方法
每次只会切开两半 且当前切的花费是固定的 那么假如左右两部分的花费都知道了 所以就能算出这次切的总花费
所以本质就是不断在找分割点 假设了分开后的左右两部分都已知且最小 然后对比不同分割点 找最小的分割点
所以左右两部分是个规模更小的子问题 那么dp状态就可以设计为
dp[i][j]
表示cuts[i]
到cuts[j]
这个区间的最小切割花费
为什么ij不是表示点呢 因为n是1e6 枚举所有点会爆 而且也没必要 因为没有分割点的地方是不能切的 花费肯定为0
那么递推公式就显然得出是:
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+cost
此处的cost就是ij的长度 k为分割点
那么枚举的顺序当然是先枚举长度 因为大的区间需要两个小的已知区间
初始化状态也是很直接的 dp[i][i+1]=0
因为相邻的两个切割点围起来的区间 中间不能再切了 所以肯定花费是0
总的时间复杂度是填满所有状态 O(n^2)再乘上要枚举分割点的O(n)
所以总的是O(n^3) 排序的省略了
int dp[105][105];
class Solution {
public:
int minCost(int n, vector<int>& cuts) {
memset(dp,0x3f,sizeof(dp)); //初始化为未知的正无穷
cuts.push_back(0); //此处把答案的区间加上 实际无意义
cuts.push_back(n);
sort(cuts.begin(),cuts.end()); //先排序 把cuts数组变成线段坐标
for(int i=0;i<cuts.size()-1;i++)dp[i][i+1]=0; //初始化合法的为0
for(int len=2;len<cuts.size();len++) //枚举长度
for(int i=0;i<cuts.size()-len;i++) //枚举起点
for(int j=i+len,k=i+1;k<j;k++) //终点固定的 枚举分割点
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+cuts[j]-cuts[i]);
return dp[0][cuts.size()-1]; //答案的子问题也就是原问题
}
};