知识点:计数;排序;
因为数据量较小,可以直接暴力统计经过每个扇区的次数。然后选取此时最大的扇区即可。
(数据量较大时,可用线段树等区间查询技术来优化~)
class Solution {
public:
vector<int> mostVisited(int n, vector<int>& rounds) {
int cnt[101] = {0};
cnt[rounds[0]]++; // 因为只有round[0] 的起点需要计数,所以单独统计一下。
for(int i = 0, pos = 0; i+1 < rounds.size(); i++) {
int b = rounds[i];
int e = rounds[i+1];
while(b != e) {
if(++b > n) {
b = 1;
}
cnt[b]++;
}
}
vector<int> vec;
for(int i = 1; i <= n; i++) {
vec.push_back(i);
}
stable_sort(vec.begin(), vec.end(), [cnt](int l, int r) -> bool {
return cnt[l] > cnt[r];
});
for(int i = 1; i < n; i++) {
if(cnt[vec[i]] != cnt[vec[0]]) {
vec.resize(i);
break;
}
}
return vec;
}
};
知识点:贪心;排序
因为每轮选择中,Bob 总是选择最少的一堆。所以 Bob 得到最少的 n 堆是对其他两个人最有利的局面。
剩下的 2n 堆该如何分配呢?
每次选择最大的两堆,Alice 获得其中较大的,我获得其中较小的。
虽然这样选择,Alice 还是能获得剩余的里面最大的一堆,但是可以让剩余堆中的最大值最小。
class Solution {
public:
int maxCoins(vector<int>& piles) {
sort(piles.begin(), piles.end(), [](int l, int r) -> bool {
return l > r;
});
int anw = 0;
for(int i = 0; i < piles.size()/3; i++) {
anw += piles[i*2+1];
}
return anw;
}
};
知识点:思维题;瞎搞;
每操作一次,新增的 1 可能会有如下三种情况:
- 左右都是 0。此时该位置作为新增段独立存在。
- 仅有左边或者右边。此时该位置会将某个旧段的长度加 1。
- 左右都是 1。此时该位置会将两个旧段合并成一个新段。
我们现在维护一个字典 M,M 的 key 表示段的长度,value 表示在字符串中,长度为 key 的段的数量。初始时,M 中无记录。接下来,让我们看下上述三种情况分别对 M 造成了哪些变化。
- 情况一:新增了一个长度为 1 的段。M[1] += 1。
- 情况二:删除一个长度为 L 的段,增加一个长度为 L+1的段。M[L] -= 1;M[L+1] += 1;
- 情况三:删除两个长度分别为 X,Y的段,增加一个长度为 X+Y+1 的段。 M[X] -= 1;M[Y] -= 1;M[X+Y+1] += 1;
然后记录一下最后一次使 M[m] 不为零的操作即可。
另外,还有一个重要问题,如何确定被删除段的长度呢?
设有一个数组 link,当 arr[i] 为某个段的端点时,link[i] 才有意义,其值代表另一个端点的位置。
接下来,上述三种情况如何更新 link。
-
情况一:因为长度为 1,所以 link[i] = i;
-
情况二:加入新增点成为某个旧段的右端点; 则被删段长度为 (i-1) - link[i-1] + 1;link[link[i-1]] = i,link[i] = link[i-1]。 为左端点时同理,机智的老铁们可自行推导。
-
情况三:左删除段长度为:(i-1) - link[i-1] + 1; 有删除段长度为:link(i+1) - (i+1) + 1; 更新:link[link[i-1]], link[link[i+1]] = link[i+1], link[i-1]。
另外,实现时根本不需要M,因为我们只关心长度为 m 的段的数量~。
int link[100001] = {0};
class Solution {
public:
int findLatestStep(vector<int>& arr, int m) {
int cnt = 0;
memset(link, -1, sizeof(link));
int anw = -1;
for(int i = 0; i < arr.size(); i++) {
int pos = arr[i] - 1;
link[pos] = pos;
int L = pos, R = pos;
if(0 < pos && link[pos-1] != -1) {
if(pos-1 - link[pos-1] + 1 == m) {
cnt--;
}
L = link[pos-1];
}
if(pos+1 < arr.size() && link[pos+1] != -1) {
if(link[pos+1] - (pos+1) + 1 == m) {
cnt--;
}
R = link[pos+1];
}
link[L] = R;
link[R] = L;
if(R-L+1 == m) {
cnt++;
}
if(cnt > 0) {
anw = i+1;
}
}
return anw;
}
};
知识点:递归;记忆化;前缀和
设 f(L,R) 为在 stoneValue[L:R] 这一排石子上可获得最大分数;
显然,f(1,N) 即为答案。那么 f(1,N) 咋求呢?递归呀~
- 终止条件:显然 L == R 时,可直接获得答案,为 0。那就把 L == R 作为终止条件。
- 递进阶段:从 L 到 R 枚举分割策略,然后根据题意保留 f(L,i) + sum(L,i),或者 f(i+1,R) + sum(i+1, R)中的最大值。
- 回归阶段:利用递进阶段获得的最优解,更新f(L,R)。
另外,因为求解过程中,会产生重复的子问题,所以需要通过记忆化的方法避免重复计算。
class Solution {
public:
int64_t dp[501][501]; // 记忆化数组,用于避免重复计算
int64_t sum[501];
int64_t dfs(int L, int R) {
if(dp[L][R] != -1) { //已经计算过该子问题了,直接范围答案
return dp[L][R];
}
if(L == R) { // 终止条件,直接获得答案
dp[L][R] = 0;
} else {
//递进阶段,根据题意,求解最大值;
int64_t val = 0;
for(int i = L; i< R; i++) {
int64_t s1 = sum[i] - sum[L-1];
int64_t s2 = sum[R] - sum[i];
if(s1 < s2) { // 根据题意,只能取后半段
val = max(val, s1 + dfs(L, i));
} else if(s1 > s2){ // 根据题意,只能取前半段
val = max(val, s2 + dfs(i+1, R));
} else { // 相等时,可任意选择~
val = max(dfs(L, i), dfs(i+1, R)) + s1;
}
}
//回归阶段,更新答案
dp[L][R] = val;
}
return dp[L][R];
}
int stoneGameV(vector<int>& stoneValue) {
memset(dp, -1, sizeof(dp));
//出来一下前缀和
sum[0] = 0;
for(int i = 0; i < stoneValue.size(); i++) {
sum[i+1] = sum[i] + stoneValue[i];
}
return dfs(1, stoneValue.size());
}
};