5483. 整理字符串
知识点:栈
将待整理字符串 s 的字符一次放到栈顶,每放入一个字符后就对栈进行检查:
- 栈是否有两个或更多元素
- 栈顶的两个元素是否是对应的大小写字符。
如果栈满足上述两个条件,则弹出栈顶的两个元素。
最后栈内的字符即为答案~
class Solution {
public:
string makeGood(string s) {
string stack;
for(int i = 0; i < s.size(); i++){
stack += s[i];
int n = stack.size();
if(n >= 2 && (stack[n-1] + 32 == stack[n-2] || stack[n-1] - 32 == stack[n-2])) {
stack.resize(n-2);
}
}
return stack;
}
};
5484. 找出第 N 个二进制字符串中的第 K 位
知识点:预处理
因为只会对前 20 个 S 进行询问,所以可以先预处理出S1,S2,S2 … S20。这样对于每次询问就可以O(1)的给出答案了~
// 全区变量,用于存储 S1,S2, ..., S20
std::vector<string> seq;
class Solution {
public:
string reverse(const string &str) {
string anw = str;
for(int i = str.size()/2; i >= 0; --i) {
std::swap(anw[i], anw[str.size()-i-1]);
}
return anw;
}
string invert(string s) {
for(int i = 0; i < s.size(); i++) {
if(s[i] == '1') {
s[i] = '0';
} else {
s[i] = '1';
}
}
return s;
}
Solution() {
// 如果seq 非空,说明已经处理过了。
if(seq.empty() == false) {
return;
}
// 按题目描述初始化 seq
seq.push_back("0");
for(int i = 1; i < 20; i++) {
seq.push_back(seq[i-1] + "1" + reverse(invert(seq[i-1])));
}
};
char findKthBit(int n, int k) {
// 以O(1)的时间复杂度给出答案
return seq[n-1][k-1];
}
};
5471. 和为目标值的最大数目不重叠非空子数组数目
知识点:动态规划
设 dp[i] 表示前 i 个数字中,满足要求的子数组个数。
当 i = 0 时,显然有 dp[0] = 0。
当 i > 0 时,有两种情况:
- 如果存在 j 使得 sum(nums[j] … nums[i-1]) 等于 target,那么:
- dp[i] 有可能为 dp[j-1] + 1。找到了一个以第 i 个数字为右端点的合法子数组。
- dp[i] 有可能为 dp[i-1]。以第 i 个数字为右端点的子数组虽然累加和为 target,但它太长了,被最优解嫌弃了。
- 综上,dp[i] = max(dp[i-1], dp[j-1] + 1)。
- 如果不存在这样的 j,那么 dp[i] 只能是 dp[i-1],因为第 i 个数组没有做出任何贡献。
还有一种情况,如果存在多个满足条件的 j 呢?显然要选最大的,因为 dp 是单调非降的,j 越大,则 dp[j] 也可能越大,则 dp[j-1] + 1 更有可能大于 dp[i-1]。
class Solution {
public:
unordered_map<int, int> pos_dict; // key 为 前缀和,value 为最大的位置
int dp[100001]; // 前 i 个数字中,合法子数组的最大数量
int maxNonOverlapping(vector<int>& nums, int target) {
dp[0] = 0;
pos_dict[0] = 0;
for(int i = 1, sum = 0; i <= nums.size(); i++) {
int data = nums[i-1];
sum += data;
// 寻找前缀和为 sum - target 的位置。
auto it = pos_dict.find(sum - target);
if(it == pos_dict.end()) {
// 没找到,则只有一种策略
dp[i] = dp[i-1];
} else {
// 找到了,选择一种更优的策略。
dp[i] = max(dp[i-1], dp[it->second] + 1);
}
// 更新一下 pos_dict。因为 value 是最大的位置。
pos_dict[sum] = i;
}
return dp[nums.size()];
}
};
5486. 切棍子的最小成本
知识点:动态规划
设 dp[L][R] 为切割以L,R为左右端点的木棍的最小成本。显然 L,R 的取值只能是 0,n,或者 cuts中的某个数。
如果不存在 i 满足 L < cuts[i] < R,那么dp[L][R] = 0,因为根本不用切了。
如果存在 i 满足上述条件,则 dp[L][R] = min(dp[L][cuts[i]] + dp[cuts[i]][R]) + R-L。
class Solution {
public:
// 为了方便实现,
// dp[l][r] 代表 切割以 cuts[l], cuts[r] 为左右端点的木棍的最小花费
int dp[103][103];
int dfs(int l, int r, int n, const vector<int>& cuts) {
// 保存已经计算过的答案,避免子问题的重复计算
if(dp[l][r] != -1) {
return dp[l][r];
}
// l+1 == r 时,说明不用再切了。
if(l+1 == r) {
dp[l][r] = 0;
return 0;
}
// 枚举切割的地方,记录最优解。
for(int i = l+1; i < r; i++) {
int cost = dfs(l, i, n, cuts) + dfs(i, r, n, cuts) + cuts[r] - cuts[l];;
if(dp[l][r] == -1 || dp[l][r] > cost) {
dp[l][r] = cost;
}
}
return dp[l][r];
}
int minCost(int n, vector<int>& cuts) {
memset(dp, -1, sizeof(dp));
cuts.push_back(0);
cuts.push_back(n);
sort(cuts.begin(), cuts.end());
// 向 cuts 中加入 0 和 n。
// 这不会影响答案,因为我们不会从这两处切割。
return dfs(0, cuts.size()-1, n, cuts);
}
};