算法部分
1.Acwing 入门组每日一题
题目:明明的随机数
明明想在学校中请一些同学一起做一项问卷调查。
为了实验的客观性,他先用计算机生成了N个1到1000之间的随机整数,对于其中重复的数字,只保留一个,把其余相同的数去掉,不同的数对应着不同的学生的学号。
然后再把这些数从小到大排序,按照排好的顺序去找同学做调查。
请你协助明明完成“去重”与“排序”的工作。
输入格式
输入文件包含2行,第1行为1个正整数,表示所生成的随机数的个数:N 。
第2行有N个用空格隔开的正整数,为所产生的随机数。
输出格式
输出文件也是2行,第1行为1个正整数M,表示不相同的随机数的个数。
第2行为M个用空格隔开的正整数,为从小到大排好序的不相同的随机数。
数据范围
1≤N≤100
输入样例:
10
20 40 32 67 40 20 89 300 400 15
输出样例:
8
15 20 32 40 67 89 300 400
题解:
模拟题,使用set能快速求解,因为set默认排序并且去重了。
代码:
#include <iostream>
#include <set>
using namespace std;
int main(){
set<int> set;
int n, a;
cin >> n;
while(n --){
cin >> a;
set.insert(a);
}
cout << set.size() << endl;
for(int i : set)
cout << i << " ";
return 0;
}
2.Acwing 提高组每日一题
题目:乌龟棋
小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。
乌龟棋的棋盘只有一行,该行有 N 个格子,每个格子上一个分数(非负整数)。
棋盘第 1 格是唯一的起点,第 N 格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。
乌龟棋中共有 M 张爬行卡片,分成 4 种不同的类型(M 张卡片中不一定包含所有 4 种类型的卡片),每种类型的卡片上分别标有1、2、3、4 四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。
游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。
游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。
玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。
很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。
现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?
输入格式
输入文件的每行中两个数之间用一个空格隔开。
第 1 行 2 个正整数 N 和 M,分别表示棋盘格子数和爬行卡片数。
第 2 行 N 个非负整数,a1,a2,……,aN,其中 ai 表示棋盘第 i 个格子上的分数。
第 3 行 M 个整数,b1,b2,……,bM,表示 M 张爬行卡片上的数字。
输入数据保证到达终点时刚好用光 M 张爬行卡片。
输出格式
输出只有 1 行,包含 1 个整数,表示小明最多能得到的分数。
数据范围
1≤N≤350,
1≤M≤120,
0≤ai≤100,
1≤bi≤4,
每种爬行卡片的张数不会超过40。
输入样例:
9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1
输出样例:
73
题解:
这是一道动态规划题,很难想到,令dp[i][j][k][w] 为走过1、2、3、4卡片的数目,然后状态转移为:
dp[i][j][k][w] = max(dp[i - 1][j][k][w], dp[i][j][k][w]);
dp[i][j][k][w] = max(dp[i][j - 1][k][w], dp[i][j][k][w]);
dp[i][j][k][w] = max(dp[i][j][k - 1][w], dp[i][j][k][w]);
dp[i][j][k][w] = max(dp[i][j][k][w - 1], dp[i][j][k][w]);
代码:
#include <iostream>
using namespace std;
const int MAXN = 50;
int dp[MAXN][MAXN][MAXN][MAXN];
int arr[400];
int main(){
int n, m, a;
int cnt[5] = {
0};
cin >> n >> m;
for(int i = 0; i < n; i ++)
cin >> arr[i];
for(int i = 0; i < m; i ++)
//统计1、2、3、4卡牌出现的次数
cin >> a, cnt[a] ++;
for(int i = 0; i <= cnt[1]; i ++)
for(int j = 0; j <= cnt[2]; j ++)
for(int k = 0; k <= cnt[3]; k ++)
for(int w = 0; w <= cnt[4]; w ++){
//得到当前走到的位置
int tmp = i + 2 * j + 3 * k + 4 * w;
if(i)
dp[i][j][k][w] = max(dp[i - 1][j][k][w], dp[i][j][k][w]);
if(j)
dp[i][j][k][w] = max(dp[i][j - 1][k][w], dp[i][j][k][w]);
if(k)
dp[i][j][k][w] = max(dp[i][j][k - 1][w], dp[i][j][k][w]);
if(w)
dp[i][j][k][w] = max(dp[i][j][k][w - 1], dp[i][j][k][w]);
//还要加上当前位置的数值
dp[i][j][k][w] += arr[tmp];
}
cout << dp[cnt[1]][cnt[2]][cnt[3]][cnt[4]] << endl;
return 0;
}
3.LeetCode 每日一题
题目:K 个不同整数的子数组
给定一个正整数数组 A,如果 A 的某个子数组中不同整数的个数恰好为 K,则称 A 的这个连续、不一定独立的子数组为好子数组。(例如,[1,2,3,1,2] 中有 3 个不同的整数:1,2,以及 3。)返回 A 中好子数组的数目。
示例 1:
输入:A = [1,2,1,2,3], K = 2
输出:7
解释:恰好由 2 个不同整数组成的子数组:[1,2], [2,1], [1,2], [2,3], [1,2,1], [2,1,2], [1,2,1,2].
示例 2:
输入:A = [1,2,1,3,4], K = 3
输出:3
解释:恰好由 3 个不同整数组成的子数组:[1,2,1,3], [2,1,3], [1,3,4].
提示:
1 <= A.length <= 20000
1 <= A[i] <= A.length
1 <= K <= A.length
题解:
考察双指针,不过该题有一个右指针,两个左指针,两个左指针到右指针的区间分别指向子数组中有K个不同的数字和有K - 1 个不同数字,所以两个左指针的差值就是每一步要累加的答案了,具体思路也是挺简单的,见下代码。
代码:
class Solution {
public:
int subarraysWithKDistinct(vector<int>& A, int K) {
int n = A.size();
//cnt1、cnt2分别为两个区间内不同数字的个数
int le1 = 0, le2 = 0, ri = 0, cnt1 = 0, cnt2 = 0, ans = 0;
vector<int> nums1(n + 1, 0), nums2(n + 1, 0);
while(ri < n){
//遇到没有出现的数字,需要更新cnt1和cnt2
if(!nums1[A[ri]])
++ cnt1;
if(!nums2[A[ri]])
++ cnt2;
//更新各个数字出现的次数
nums1[A[ri]] ++;
nums2[A[ri]] ++;
//当区间内数字超过K,需要移动左指针缩小区间
while(cnt1 > K){
nums1[A[le1]] --;
//某个数字减为0,cnt需要减一
if(!nums1[A[le1]])
-- cnt1;
++ le1;
}
//当区间内数字超过K - 1,需要移动左指针缩小区间
while(cnt2 > K - 1){
nums2[A[le2]] --;
if(!nums2[A[le2]])
-- cnt2;
++ le2;
}
//le1和le2的差值就是每移动一次右指针的可行解,累加到答案中
ans += le2 - le1;
++ ri;
}
return ans;
}
};
4.移除石子的最大得分
题目:
你正在玩一个单人游戏,面前放置着大小分别为 a、b 和 c 的 三堆 石子。
每回合你都要从两个 不同的非空堆 中取出一颗石子,并在得分上加 1 分。当存在 两个或更多 的空堆时,游戏停止。
给你三个整数 a 、b 和 c ,返回可以得到的 最大分数 。
示例 1:
输入:a = 2, b = 4, c = 6
输出:6
解释:石子起始状态是 (2, 4, 6) ,最优的一组操作是:
- 从第一和第三堆取,石子状态现在是 (1, 4, 5)
- 从第一和第三堆取,石子状态现在是 (0, 4, 4)
- 从第二和第三堆取,石子状态现在是 (0, 3, 3)
- 从第二和第三堆取,石子状态现在是 (0, 2, 2)
- 从第二和第三堆取,石子状态现在是 (0, 1, 1)
- 从第二和第三堆取,石子状态现在是 (0, 0, 0)
总分:6 分 。
示例 2:
输入:a = 4, b = 4, c = 6
输出:7
解释:石子起始状态是 (4, 4, 6) ,最优的一组操作是:
- 从第一和第二堆取,石子状态现在是 (3, 3, 6)
- 从第一和第三堆取,石子状态现在是 (2, 3, 5)
- 从第一和第三堆取,石子状态现在是 (1, 3, 4)
- 从第一和第三堆取,石子状态现在是 (0, 3, 3)
- 从第二和第三堆取,石子状态现在是 (0, 2, 2)
- 从第二和第三堆取,石子状态现在是 (0, 1, 1)
- 从第二和第三堆取,石子状态现在是 (0, 0, 0)
总分:7 分 。
示例 3:
输入:a = 1, b = 8, c = 8
输出:8
解释:最优的一组操作是连续从第二和第三堆取 8 回合,直到将它们取空。
注意,由于第二和第三堆已经空了,游戏结束,不能继续从第一堆中取石子。
提示:
1 <= a, b, c <= 105
代码1:
//每次取最多的两堆进行合并,合并完成石头数-1 后在入队列,使用priority_queue
class Solution {
public:
int maximumScore(int a, int b, int c) {
priority_queue<int> Q;
Q.push(a);
Q.push(b);
Q.push(c);
int ans = 0;
while(Q.size() > 1){
int a = Q.top();
Q.pop();
int b = Q.top();
Q.pop();
++ ans;
a --; b --;
if(a)
Q.push(a);
if(b)
Q.push(b);
}
return ans;
}
};
代码2:
如果有一个堆的数量大于其余两个的和,那么无论如何三个堆都不可能都为空。反之,三个堆都可以尽可能为空,如果总数为奇数,则三个堆总共剩一个,如果为偶数,则三个堆全为空。
class Solution {
public:
int maximumScore(int a, int b, int c) {
if(a+b<=c)
return a+b;
else if(a+c<=b)
return a+c;
else if(b+c<=a)
return b+c;
return (a+b+c)/2;
}
};
书籍部分
PS.
- 要加强书籍的阅读
- 明天起至少写一道困难的算法题,行数很多的那种。