文章目录
C++ 技术点
1. string类型使用find函数。
int index = s.find("@");
if (inde != string:npos){
xx
}
2. transform函数,将整个字符串做整体改变。
transform(s.begin(), s.end(), s.begin(), ::tolower);
多边三角形剖分的最低得分(dp思路,选不选问题)
- dp定义:dp[i][j]表示点 i 到点 j 的多边形的最小值。
- dp转移:转移方程的思路两种。选不选、选哪个。现在本题只有选哪个这个思路。因此
dp[i][j] = min(dp[i][j], dp[i,k]+dp[k,j]+cal(i,j,k))
。 - 枚举次序:求解dp[i][j]的时候是需要提前知道dp[i][k](k<j)这个更小的问题的,因此此时 j 是正序枚举;同样的对于dp[k][j]则是需要知道 k 才能知道 i 的,因此 i 是倒序枚举。
- dp初始值:在枚举次序分析以后,我们知道dp[i][i],dp[i][i+1] = 0, 其他的初始化为INT_MAX;
class Solution {
public:
int minScoreTriangulation(vector<int> &v) {
int n = v.size(), f[n][n];
memset(f, 0, sizeof(f));
for (int i = n - 3; i >= 0; --i)
for (int j = i + 2; j < n; ++j) {
f[i][j] = INT_MAX;
for (int k = i + 1; k < j; ++k)
f[i][j] = min(f[i][j], f[i][k] + f[k][j] + v[i] * v[j] * v[k]);
}
return f[0][n - 1];
}
};
移动石子到连续(思路)
比较有思维量的题目。首先我们需要思考下最终的结果是处于什么区间上的,也就是我们需要选在一个长度为 k 的区间最后将所有的石子搬运到这个区间上。
另外我们还需要思考,如何实现最大和最小的移动。
- 最大:以左右端点作为其中一个端点进行移动。这两个点之间的所有端点都是需要一次操作的。
- 最小:首先设计一个滑动窗,窗的两侧为实际存在的石子,并且左右两侧的石子的距离是小于 n 的最大距离。此时我们可以分情况讨论:
- 如果左右端点差为n,完全不需要动,返回0;
- 两个端点的距离差[stone[l], stone[r] ]恰好为n-1,石子数量也为n-1,也就是n-1个全部都是连续的。此时外面还剩一个,无论在什么位置,最多两次就可以连续。(认为右侧不动)
- 否则,填上中间的空挡即可。
class Solution {
public:
vector<int> numMovesStonesII(vector<int>& stones) {
int n = stones.size();
sort(stones.begin(), stones.end());
// max = max(stones[n-2] - stones[0] -1, stones[n-1]-stones[1]-1)
// min = 在一个滑动窗内 k个,因此需要n-k次
// 如果有n-1个是紧密相连的。那么 需要两次
if(stones[n-1]- stones[0]+1 == n){
return {0, 0};
}
int max_v = max(stones[n-2] - stones[0] +1, stones[n-1]-stones[1]+1)-(n-1);
int min_v = max_v;
int r = 1;
for (int l = 0;l<n;l++){
while(r < n && stones[r]-stones[l]+1 <= n){
r++;
}
r--;
// 左端点l到右端点r是连续的
if (r-l+1 == n-1 && stones[r]-stones[l]+1 == n-1){
min_v = min(min_v, 2);
}else{
min_v = min(min_v, n-(r-l+1));
}
}
return {min_v, max_v};
}
};
1027. 最长等差数列(动态规划)
最长子序列的问题很容易想到dp问题。选哪个问题
- dp定义:dp[i][j] 长度为 i 时,差值为 j 时的最大长度
- dp转移:dp[i][j] = max(dp[k][j]+(k,i)*maxValue, dp[i][j])
- dp初始值:0
- dp转移顺序:正序遍历 i ,反向遍历 k
class Solution {
public:
int longestArithSeqLength(vector<int> &a) {
int ans = 0, n = a.size(), f[n][1001];
memset(f, 0, sizeof(f));
for (int i = 1; i < n; ++i)
for (int j = i - 1; j >= 0; --j) {
int d = a[i] - a[j] + 500; // +500 防止出现负数
if (f[i][d] == 0) {
f[i][d] = f[j][d] + 1; // 默认的 1 在下面返回时加上
ans = max(ans, f[i][d]);
}
}
return ans + 1;
}
};
1105. 填充书架(动态规划)
选不选?选哪个? 选哪个问题
- dp定义:dp[i]表示前 i 本 书的最小高度。
- dp转移:dp[i] = min(dp[i], dp[j]+cal(j,i))。对于后 j 到 i 的书我们放在一个全新的书架上。
- dp状态:初始化为INT_MAX,但是dp[0] = 0;
class Solution {
public:
int minHeightShelves(vector<vector<int>>& books, int shelfWidth) {
// dp[i] 表示前i本书,总的高度
// dp[i] 选什么?当前层还是下一层? X
// 选哪个?从哪个开始放置在新的一层? ✅
int n = books.size();
vector<int> dp(n+1, INT_MAX);
dp[0] = 0; // 初始化为0
for (int i = 1;i<=n;i++){
int curmax = 0;
int curlength = 0;
for (int j = i;j>=1;j--){ // 选择某一个点,这个点之后都放在某一层
curlength += books[j-1][0];
// 超出了就停止,此时无法都放在一层
if(curlength > shelfWidth) break;
curmax = max(curmax, books[j-1][1]);
dp[i] = min(dp[i], dp[j-1]+curmax);
}
}
return dp[n];
}
};
1031 两个非重叠子数组的最大和
依然会有一点类似滑动窗口的思路,我们枚举second的起始位置 i ,可以表示firstLen的选取在[0,i]之间实现,second就是[i+1, i+secondLen]。 对于firsr 和 second可以交换前后的情况,我们可以分两种情况讨论即可。
class Solution {
public:
int help(vector<int>& nums, int firstLen, int secondLen) {
// dp[i] 表示前i个元素时,first数组的最大值,
// ans = dp[i]+sum(nums(i+1, i+secondLen))
int suml = accumulate(nums.begin(), nums.begin() + firstLen, 0);
int maxSumL = suml;
int sumr = accumulate(nums.begin() + firstLen, nums.begin() + firstLen + secondLen, 0);
int res = maxSumL + sumr;
// i表示second的结尾位置,j表示first的结尾位置
for (int i = firstLen + secondLen, j = firstLen; i < nums.size(); ++i, ++j) {
suml += nums[j] - nums[j - firstLen];
maxSumL = max(maxSumL, suml);
sumr += nums[i] - nums[i - secondLen];
res = max(res, maxSumL + sumr);
}
return res;
}
int maxSumTwoNoOverlap(vector<int>& nums, int firstLen, int secondLen) {
// 分两种情况分别讨论
return max(help(nums, firstLen, secondLen), help(nums, secondLen, firstLen));
}
};
1163.按字典序排在最后的子串(双指针,反证法)
这个题目的思路很独特,不知道更深层的思想是什么。首先有一个证明,答案一定是后缀字符串。只是我们要去选择这个开头的位置。因此我们可以比较不同的开头。
我们维护一个双指针,假设 i 是最佳的答案,我们尝试在 i 的后面寻找到一个 j 去证明 i 不是最好。
class Solution {
public:
string lastSubstring(string s) {
int n = s.size();
int i = 0;
int j = 1;
int k = 0;
// 假设 i 是最好的点,我们尝试找一个点j去推翻,如果找不到,就是最好的点。
// i表示当前最大的字串开始的位置,j是当前考虑的字串的位置,k是当前比较的位置
while( j + k < n) {
if (s[i + k] == s[j + k]) {
++k;
} else if (s[i + k] < s[j + k]) {
//因为i+k这个点不如j+k这个点,因此此时s[i+1,..,i+k]开头的的字串都不如s[j+1,..,j+k]开头的字串更好。都不考虑了,直接跳到新的点去论证。
i += k + 1;
k = 0;
// 如果i在j的位置以后了,我们更新j比较。前面的点都被推翻了。
if (i >= j) {
j = i + 1;
}
} else {
// s[i + k] > s[j + k] 我们需要找新的点去推翻,这个点就是j+k+1才可能
j += k + 1;
k = 0;
}
}
return s.substr(i);
}
};
1187 使数组严格递增(dp)
- dp[i][j] 表示对于前 i 个数字时候,替换了 j 次时的最后一个数字。这个定义比较奇特,没有直接定义所求内容。
- 转移方程:
- 如果arr[i] > dp[i-1][j] 那可以直接,dp[i][[j] = arr[i];
- 对于无法选择arr[i]或者不选择arr[i]的情况, 在arr2[j, end]里面寻找到第一个大于(upperbound)的 dp[i-1][j-1]的
- 初始:对于i= 0这个位置,dp[0][0] = arr[0], dp[0][1] = arr2[0], 其余的都初始化为int_MAX;
class Solution {
public:
int makeArrayIncreasing(vector<int>& arr1, vector<int>& arr2) {
// dp[i][j] 表示对于对于前i个arr1,替换j次时候的最小后缀;
// dp[i][j] = arr[i] 满足arr[i] > dp[i-1][j] 不进行替换
// dp[i][j] = min(dp[i][j], cal) 其中 cal是arr2中 index大于等于j,val严格大于dp[i-1][j-1]的最小值 j>0
// 初始化:dp[i][j] = INF dp[0][0] = arr[0] dp[0][1] = arr2[0]
sort(arr2.begin(), arr2.end());
arr2.erase(unique(arr2.begin(), arr2.end()), arr2.end());// 去重
int n = arr1.size();
int m = min((int)arr2.size(), n);
vector<vector<int>> dp(n, vector<int>(m+1, INT_MAX));
dp[0][0] = arr1[0];
dp[0][1] = arr2[0];
for(int i = 1;i<n;i++){
for(int j = 0;j<=min(i+1,m);j++){
if(arr1[i] > dp[i-1][j]) {
dp[i][j] = arr1[i];
}
if(j>0){
auto it = upper_bound(arr2.begin()+j-1, arr2.end(), dp[i-1][j-1]);
if(it != arr2.end()){
dp[i][j] = min(dp[i][j], *it);
}
}
if(dp[n-1][j] != INT_MAX) return j;
}
}
return -1;
}
};
1172. 餐盘栈 H (数据结构)
class DinnerPlates {
public:
// list 维护所有的stack,pq维护没有放满stack的index
vector<stack<int>> list;
priority_queue<int, vector<int>, greater<>> pq;
int cap = 0;
DinnerPlates(int capacity) {
cap = capacity;
}
void push(int val) {
// 首先删除可能已经空的尾部栈
while(!pq.empty() && pq.top() >= list.size()) pq.pop();
if (!pq.empty() ){
int stackIndex = pq.top();
pq.pop();
auto &stack = list[stackIndex];
stack.push(val);
}else{
// 如果所有的栈都满了,就新建一个栈
stack<int> newStack;
newStack.push(val);
if(newStack.size()!=cap){
// 有空间的栈,可以压入。
pq.push(list.size());
}
list.push_back(newStack);
}
}
int pop() {
if(list.size() == 0) return -1;
return popAtStack(list.size()-1);
}
int popAtStack(int index) {
if (index > list.size() || index<0 || list[index].empty()) return -1;
auto &stack = list[index];
int cur = stack.top();
stack.pop();
// 如果stack是最后一个,并且空了,开始清理队尾
if(index == list.size()-1 && stack.empty()){
while(!list.empty() && list[list.size()-1].empty()) list.pop_back();
}else{
// 空出来了空间,因此可以压入。
pq.push(index);
}
return cur;
}
};
1157. 子数组中占绝大多数的元素 (线段树)
class Node {
public:
// l,r维护了区间的两个端点,x是当前的众数,cnt是摩尔投票的结果
int l = 0, r = 0;
int x = 0, cnt = 0;
};
using pii = pair<int, int>;
class SegmentTree {
public:
SegmentTree(vector<int>& nums) {
this->nums = nums;
int n = nums.size();
tr.resize(n << 2);
for (int i = 0; i < tr.size(); ++i) {
tr[i] = new Node();
}
build(1, 1, n);
}
pii query(int u, int l, int r) {
// 当前点的区间小于查询区间, 直接返回
if (tr[u]->l >= l && tr[u]->r <= r) {
return {tr[u]->x, tr[u]->cnt};
}
int mid = (tr[u]->l + tr[u]->r) >> 1;
// 查询区间完整在左侧,直接查询左侧
if (r <= mid) {
return query(u << 1, l, r);
}
// 查询区间完整在右侧,直接查询右侧
if (l > mid) {
return query(u << 1 | 1, l, r);
}
// 分居两边
auto left = query(u << 1, l, r);
auto right = query(u << 1 | 1, l, r);
// 众数一样,直接合并
if (left.first == right.first) {
left.second += right.second;
// 否则按照摩尔投票,得到最多的
} else if (left.second >= right.second) {
left.second -= right.second;
} else {
right.second -= left.second;
left = right;
}
return left;
}
private:
vector<Node*> tr;
vector<int> nums;
void build(int u, int l, int r) {
tr[u]->l = l;
tr[u]->r = r;
if (l == r) {
tr[u]->x = nums[l - 1];
tr[u]->cnt = 1;
return;
}
int mid = (l + r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
void pushup(int u){
int l = u<<1;
int r = u<<1|1;
if(tr[l]->x == tr[r]->x){
tr[u]->x = tr[r]->x;
tr[u]->cnt = tr[l]->cnt + tr[r]->cnt;
}else if (tr[l]->cnt >= tr[r]->cnt){
tr[u]->x = tr[l]->x;
tr[u]->cnt = tr[l]->cnt - tr[r]->cnt;
}else{
tr[u]->x = tr[r]->x;
tr[u]->cnt = tr[r]->cnt - tr[l]->cnt;
}
}
};
class MajorityChecker {
public:
MajorityChecker(vector<int>& arr) {
tree = new SegmentTree(arr);
for (int i = 0; i < arr.size(); ++i) {
d[arr[i]].push_back(i);
}
}
int query(int left, int right, int threshold) {
// 得到区间内的众数
int x = tree->query(1, left + 1, right + 1).first;
// 二分查找,index 大于等于left的
auto l = lower_bound(d[x].begin(), d[x].end(), left);
// 二分查找,index大于等于riht的
auto r = upper_bound(d[x].begin(), d[x].end(), right);
return r - l >= threshold ? x : -1;
}
private:
unordered_map<int, vector<int>> d;
// 这里一定是一个地址
SegmentTree* tree;
};
/**
* Your MajorityChecker object will be instantiated and called as such:
* MajorityChecker* obj = new MajorityChecker(arr);
* int param_1 = obj->query(left,right,threshold);
*/