2073. 买票需要的时间
有 n
个人前来排队买票,其中第 0
人站在队伍 最前方 ,第 (n - 1)
人站在队伍 最后方 。
给你一个下标从 0
开始的整数数组 tickets
,数组长度为 n
,其中第 i
人想要购买的票数为 tickets[i]
。
每个人买票都需要用掉 恰好 1 秒 。一个人 一次只能买一张票 ,如果需要购买更多票,他必须走到 队尾 重新排队(瞬间 发生,不计时间)。如果一个人没有剩下需要买的票,那他将会 离开 队伍。
返回位于位置 k
(下标从 0 开始)的人完成买票需要的时间(以秒为单位)。
数据范围
n == tickets.length
1 <= n <= 100
1 <= tickets[i] <= 100
0 <= k < n
分析
首先将第k个人移到尾部,并更新初始条件,此时只需要找每一轮剩下的人数,由于全都在第k个人前面,因此每一轮答案加上的权重就是剩下的人数,可以排序,每次找剩下的人中票数最小的人群,然后在答案一次性加上他们所贡献的时间并删除他们
代码
class Solution {
public:
int timeRequiredToBuy(vector<int>& tickets, int k) {
int n = tickets.size();
vector<int> tmp;
int res = 0;
res += k + 1;
for(int i = 0; i <= k; i ++ ) tickets[i] -- ;
int cnt = 0;
for(int i = 0; i < tickets.size(); i ++ ) {
tmp.push_back(tickets[i]);
}
int o = tickets[k];
sort(tmp.begin(), tmp.end());
for(int i = 0; i < tmp.size(); i ++ ) {
int last = 1, j = i + 1;
while(j < tmp.size() && tmp[j] == tmp[i]) {
last ++ ;
j ++ ;
}
res += (n - i) * (min(o, tmp[i]) - cnt); // 剩下的人数×轮数
cnt = max(cnt, min(tmp[i], o));
i = j - 1;
}
return res;
}
};
2320. 统计放置房子的方式数
一条街道上共有 n * 2
个 地块 ,街道的两侧各有 n
个地块。每一边的地块都按从 1
到 n
编号。每个地块上都可以放置一所房子。
现要求街道同一侧不能存在两所房子相邻的情况,请你计算并返回放置房屋的方式数目。由于答案可能很大,需要对 109 + 7
取余后再返回。
注意,如果一所房子放置在这条街某一侧上的第 i
个地块,不影响在另一侧的第 i
个地块放置房子。
数据范围
1 <= n <= 104
分析
状态机DP,令
d
p
[
i
]
[
1
]
dp[i][1]
dp[i][1]表示处理完前i个且防止房子在i列第一排的方案数,同理定义
d
p
[
i
]
[
0
]
dp[i][0]
dp[i][0]、
d
p
[
i
]
[
2
]
dp[i][2]
dp[i][2]、
d
p
[
i
]
[
3
]
dp[i][3]
dp[i][3]
状态转移如下:
- d p [ i ] [ 0 ] = ( d p [ i − 1 ] [ 0 ] + d p [ i − 1 ] [ 1 ] + d p [ i − 1 ] [ 2 ] + d p [ i − 1 ] [ 3 ] ) ; dp[i][0] = (dp[i - 1][0] + dp[i - 1][1] + dp[i - 1][2] + dp[i - 1][3]) ; dp[i][0]=(dp[i−1][0]+dp[i−1][1]+dp[i−1][2]+dp[i−1][3]);
- d p [ i ] [ 1 ] = ( d p [ i − 1 ] [ 0 ] + d p [ i − 1 ] [ 2 ] ) ; dp[i][1] = (dp[i - 1][0] + dp[i - 1][2]) ; dp[i][1]=(dp[i−1][0]+dp[i−1][2]);
- d p [ i ] [ 2 ] = ( d p [ i − 1 ] [ 0 ] + d p [ i − 1 ] [ 1 ] ) ; dp[i][2] = (dp[i - 1][0] + dp[i - 1][1]) ; dp[i][2]=(dp[i−1][0]+dp[i−1][1]);
- d p [ i ] [ 3 ] = d p [ i − 1 ] [ 0 ] ; dp[i][3] = dp[i - 1][0] ; dp[i][3]=dp[i−1][0];
代码
typedef unsigned long long ULL;
class Solution {
public:
const static int N = 1e4 + 5, mod = (int)1e9 + 7;
ULL dp[N][4];
int countHousePlacements(int n) {
dp[0][0] = 1;
for(int i = 1; i <= n; i ++ ) {
dp[i][0] = (dp[i - 1][0] % mod + dp[i - 1][1] % mod + dp[i - 1][2] % mod + dp[i - 1][3] % mod) % mod;
dp[i][1] = (dp[i - 1][0] % mod + dp[i - 1][2] % mod) % mod;
dp[i][2] = (dp[i - 1][0] % mod + dp[i - 1][1] % mod) % mod;
dp[i][3] = dp[i - 1][0] % mod;
}
ULL res = 0;
for(int i = 0; i < 4; i ++ ) {
res += dp[n][i];
res %= mod;
}
return res;
}
};
213. 打家劫舍 II
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
数据范围
1 <= nums.length <= 100
0 <= nums[i] <= 1000
分析
分两种情况讨论,对于第一个房子选择的情况,dp范围为1-n-1,对于第一个房子不选择的情况,dp范围为2-n,具体状态转移看代码
代码
class Solution {
public:
const static int N = 105;
int dp1[N][2], dp2[N][2];
int rob(vector<int>& nums) {
if(nums.size() == 1) return nums[0];
int n = nums.size();
for(int i = 1; i <= n - 1; i ++ ) { // 选择第一个
dp1[i][0] = max(dp1[i - 1][1], dp1[i - 1][0]);
dp1[i][1] = dp1[i - 1][0] + nums[i - 1];
}
for(int i = 2; i <= n; i ++ ) { // 不选择第一个
dp2[i][0] = max(dp2[i - 1][1], dp2[i - 1][0]);
dp2[i][1] = dp2[i - 1][0] + nums[i - 1];
}
int res = 0;
res = max(dp1[n - 1][0], max(dp1[n - 1][1], max(dp2[n][0], dp2[n][1])));
return res;
}
};
3186. 施咒的最大总伤害
一个魔法师有许多不同的咒语。
给你一个数组 power
,其中每个元素表示一个咒语的伤害值,可能会有多个咒语有相同的伤害值。
已知魔法师使用伤害值为 power[i]
的咒语时,他们就 不能 使用伤害为 power[i] - 2
,power[i] - 1
,power[i] + 1
或者 power[i] + 2
的咒语。
每个咒语最多只能被使用 一次 。
请你返回这个魔法师可以达到的伤害值之和的 最大值 。
数据范围
1 <= power.length <= 105
1 <= power[i] <= 109
分析
使用 m a p map map进行 d p dp dp,首先记录每个值出现的次数,然后 p r e pre pre数组记录比当前值小的数的值,状态转移同打家劫舍,也是个状态机DP,最后极限过
代码
typedef unsigned long long ULL;
class Solution {
public:
const static int N = 1e5 + 5;
map<ULL, ULL> pre;
map<ULL, ULL> dp[2];
map<ULL, ULL> mp;
ULL maximumTotalDamage(vector<int>& power) {
sort(power.begin(), power.end());
int n = power.size();
int last = -1;
ULL maxx = 0;
for(int i = 0; i < n; i ++ ) {
maxx = max(maxx, (ULL)power[i]);
int j = i;
while(j < n && power[j] == power[i]) {
mp[power[i]] ++ ;
j ++ ;
}
pre[power[i]] = last;
last = power[i];
i = j - 1;
}
for(auto [k, v] : mp) {
if(mp[k - 1] && mp[k - 2]) {
int last = pre[pre[pre[k]]];
dp[0][k] = max(dp[1][k - 1], dp[0][k - 1]);
dp[1][k] = max(dp[0][last], dp[1][last]) + v * k;
} else if(mp[k - 1] && !mp[k - 2]) {
int last = pre[pre[k]];
dp[0][k] = max(dp[0][k - 1], dp[1][k - 1]);
dp[1][k] = max(dp[0][last], dp[1][last]) + v * k;
} else if(!mp[k - 1] && mp[k - 2]) {
int last = pre[pre[k]];
dp[0][k] = max(dp[0][k - 2], dp[1][k - 2]);
dp[1][k] = max(dp[0][last], dp[1][last]) + v * k;
} else {
int last = pre[k];
dp[0][k] = max(dp[0][last], dp[1][last]);
dp[1][k] = max(dp[0][last], dp[1][last]) + v * k;
}
}
return max(dp[0][maxx], dp[1][maxx]);
}
};
2606. 找到最大开销的子字符串
给你一个字符串s
,一个字符 互不相同 的字符串 chars
和一个长度与 chars
相同的整数数组 vals
。
子字符串的开销 是一个子字符串中所有字符对应价值之和。空字符串的开销是 0 。
字符的价值 定义如下:
- 如果字符不在字符串
chars
中,那么它的价值是它在字母表中的位置(下标从1
开始)。- 比方说,
'a'
的价值为1
,'b'
的价值为2
,以此类推,'z'
的价值为26
。
- 比方说,
- 否则,如果这个字符在
chars
中的位置为i
,那么它的价值就是vals[i]
。
请你返回字符串s
的所有子字符串中的最大开销。
数据范围
1 <= s.length <= 105
s
只包含小写英文字母。1 <= chars.length <= 26
chars
只包含小写英文字母,且 互不相同 。vals.length == chars.length
-1000 <= vals[i] <= 1000
分析
令 d p [ i ] dp[i] dp[i]表示已 i i i为结尾子串的价值最大值,状态转移如下
- d p [ i ] = m a x ( 0 , d p [ i − 1 ] + k ) , k 为价值 dp[i]=max(0,dp[i-1]+k),k为价值 dp[i]=max(0,dp[i−1]+k),k为价值
代码
class Solution {
public:
map<char, int> pos;
const static int N = 1e5 + 5;
int dp[N];
int maximumCostSubstring(string s, string chars, vector<int>& vals) {
for(int i = 0; i < chars.size(); i ++ ) pos[chars[i]] = i;
int res = 0;
for(int i = 1; i <= s.size(); i ++ ) {
int k = 0;
if(pos.find(s[i - 1]) != pos.end()) k = vals[pos[s[i - 1]]];
else k = s[i - 1] - 'a' + 1;
dp[i] = max(0, dp[i - 1] + k);
res = max(res, dp[i]);
}
return res;
}
};