算法部分
1.Acwing 入门组每日一题
题目:数字反转
给定一个整数,请将该数各个位上数字反转得到一个新数。
新数也应满足整数的常见形式,即除非给定的原数为零,否则反转后得到的新数的最高位数字不应为零。
输入格式
输入共1行,1个整数N。
输出格式
输出共1行,1个整数表示反转后的新数。
数据范围
|N|≤109
输入样例:
123
输出样例:
321
输入样例:
-380
输出样例:
-83
题解:
简单的模拟题。
代码:
#include <iostream>
using namespace std;
int main(){
int n, ans = 0;
bool op = false;
cin >> n;
if(n < 0)
//使用op记录负数
n = -n, op = true;
while(n){
ans = ans * 10 + n % 10;
n /= 10;
}
if(op)
ans = -ans;
cout << ans << endl;
return 0;
}
2.排序
题目:
给定 n 个变量和 m 个不等式。其中 n 小于等于26,变量分别用前 n 的大写英文字母表示。
不等式之间具有传递性,即若 A>B 且 B>C ,则 A>C。
请从前往后遍历每对关系,每次遍历时判断:
如果能够确定全部关系且无矛盾,则结束循环,输出确定的次序;
如果发生矛盾,则结束循环,输出有矛盾;
如果循环结束时没有发生上述两种情况,则输出无定解。
输入格式
输入包含多组测试数据。
每组测试数据,第一行包含两个整数n和m。
接下来m行,每行包含一个不等式,不等式全部为小于关系。
当输入一行0 0时,表示输入终止。
输出格式
每组数据输出一个占一行的结果。
结果可能为下列三种之一:
如果可以确定两两之间的关系,则输出 “Sorted sequence determined after t relations: yyy…y.”,其中’t’指迭代次数,'yyy…y’是指升序排列的所有变量。
如果有矛盾,则输出: “Inconsistency found after t relations.”,其中’t’指迭代次数。
如果没有矛盾,且不能确定两两之间的关系,则输出 “Sorted sequence cannot be determined.”。
数据范围
2≤n≤26,变量只可能为大写字母A~Z。
输入样例1:
4 6
A<B
A<C
B<C
C<D
B<D
A<B
3 2
A<B
B<A
26 1
A<Z
0 0
输出样例1:
Sorted sequence determined after 4 relations: ABCD.
Inconsistency found after 2 relations.
Sorted sequence cannot be determined.
输入样例2:
6 6
A<F
B<D
C<E
F<D
D<E
E<F
0 0
输出样例2:
Inconsistency found after 6 relations.
输入样例3:
5 5
A<B
B<C
C<D
D<E
E<A
0 0
输出样例3:
Sorted sequence determined after 4 relations: ABCDE.
题解:
这种传递性的题目可以使用Floyd算法来求解,毕竟数据范围很小,O(n3)的复杂度能通过,使用Floyd算法能够根据已有不等式关系,得到所有传递得到的其他不等关系(传递性 A > B B > C 能够得到 A > C),使用二维数据g[i][j] 表示第 i 个元素和第 j 个元素的关系,若g[i][j]为 1,代表 i 元素小于 j 元素,所以,如果g[i][j] 和 g[j][i] 同时为1,那么是有冲突矛盾的,如果g[i][j] 和 g[j][i] 同时为0,那么代表这两个元素之间的关系是不能根据已有条件确定的。优化:可以根据已知条件二分来降低时间复杂度。
代码:
#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 30;
bool g[MAXN][MAXN];
int n, m;
//二元关系
typedef pair<int, int> PII;
PII arr[400];
int floyd(int mid){
memset(g, 0, sizeof(g));
for(int i = 1; i <= mid; i ++){
//根据输入的二元关系更新二维矩阵
g[arr[i].first][arr[i].second] = true;
}
//floyd算法
for(int k = 0; k < n; k ++)
for(int i = 0; i < n; i ++)
for(int j = 0; j < n; j ++)
g[i][j] |= (g[i][k] & g[k][j]);
//先看有没有冲突
for(int i = 0; i < n; i ++)
for(int j = 0; j < n; j ++)
if(i != j && g[i][j] && g[j][i])
return -1;
//然后看有没有不能确定的元素
//注意不能把这两个if放到同一个for里面写,因为一旦return 0 ,会影响到二分的正确性
//!!!我这里错了,debug了很久
for(int i = 0; i < n; i ++)
for(int j = 0; j < n; j ++)
if(i != j && !g[i][j] && !g[j][i])
return 0;
return 1;
}
int main(){
string s;
while(cin >> n && n){
cin >> m;
for(int i = 1; i <= m; i ++){
cin >> s;
//记录输入的二元关系
arr[i] = {s[0] - 'A', s[2] - 'A'};
}
int le = 1, ri = m, mid;
//二分求答案
while(le < ri){
mid = le + ri >> 1;
int flag = floyd(mid);
//flag为1 代表确定了所有关系 为-1代表有冲突,这两者都能够得到答案,所以mid需要减小
if(flag)
ri = mid;
//flag代表有元素的大小关系不能确定
else
le = mid + 1;
}
int flag = floyd(ri);
if(flag == 1){
printf("Sorted sequence determined after %d relations: ", ri);
//得到元素的大小关系
int cnt[30];
for(int i = 0; i < n; i ++){
int t = 0;
for(int j = 0; j < n; j ++){
if(i == j)
continue;
if(g[i][j])
++ t;
}
//第 i 行 有 t 个 1,代表有t个元素大于i,所以第n - t - 1个位置放i
cnt[n - t - 1] = i;
}
for(int i = 0; i < n; i ++)
cout << (char)(cnt[i] + 'A');
cout << '.' << endl;
}else if(flag == -1)
printf("Inconsistency found after %d relations.\n", ri);
else
printf("Sorted sequence cannot be determined.\n");
}
return 0;
}
3.队列的最大值
题目:
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1
示例 1:
输入:
[“MaxQueue”,“push_back”,“push_back”,“max_value”,“pop_front”,“max_value”]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例 2:
输入:
[“MaxQueue”,“pop_front”,“max_value”]
[[],[],[]]
输出: [null,-1,-1]
限制:
1 <= push_back,pop_front,max_value的总操作数 <= 10000
1 <= value <= 10^5
题解:
经典的单调队列又双叒来了,默写一遍框架,咳咳。
代码:
class MaxQueue {
public:
//普通的队列
queue<int> Q;
//返回max的单调队列
deque<int> DQ;
MaxQueue() {}
int max_value() {
if(DQ.empty())
return -1;
else
return DQ.front();
}
void push_back(int value) {
//将前面所有小于value的元素都pop
while(!DQ.empty() && DQ.back() < value)
DQ.pop_back();
//加入当前元素
DQ.push_back(value);
Q.push(value);
}
int pop_front() {
if(Q.empty())
return -1;
int t = Q.front();
Q.pop();
//如果出队的元素是队头,那么deque也要pop
if(DQ.front() == t)
DQ.pop_front();
return t;
}
};
4.最接近目标值的子序列和
题目:
给你一个整数数组 nums 和一个目标值 goal 。
你需要从 nums 中选出一个子序列,使子序列元素总和最接近 goal 。也就是说,如果子序列元素和为 sum ,你需要 最小化绝对差 abs(sum - goal) 。
返回 abs(sum - goal) 可能的 最小值 。
注意,数组的子序列是通过移除原始数组中的某些元素(可能全部或无)而形成的数组。
示例 1:
输入:nums = [5,-7,3,5], goal = 6
输出:0
解释:选择整个数组作为选出的子序列,元素和为 6 。
子序列和与目标值相等,所以绝对差为 0 。
示例 2:
输入:nums = [7,-9,15,-2], goal = -5
输出:1
解释:选出子序列 [7,-9,-2] ,元素和为 -4 。
绝对差为 abs(-4 - (-5)) = abs(1) = 1 ,是可能的最小值。
示例 3:
输入:nums = [1,2,3], goal = -7
输出:7
提示:
1 <= nums.length <= 40
-107 <= nums[i] <= 107
-109 <= goal <= 109
题解:
挺好的一题,暴搜 + 二分查找。题意就是在数组中挑选一些数字(子集),使得这些数字和与goal的差最小,本质是暴搜,暴搜也有技巧,数据范围只有40,但是纯暴搜240会超时,所以将数据分为两半,le 和 ri 来分别暴搜,le + ri 就是选出来的和,这样子就可以将复杂度降低为220,先将前一半暴搜后的结果加入数组中,后一半暴搜得到ri,然后去前一半的数组中寻找le,找到一个最接近答案的数字。注意我们可以把le数组排序,然后二分和goal比较。
代码:
class Solution {
public:
int minAbsDifference(vector<int>& nums, int goal) {
int n = nums.size() / 2, m = nums.size() - n, ans = INT_MAX;
//用来存储le暴搜的结果,有2^n个
vector<int> q(1 << n);
//使用位运算得到子集
for(int i = 0; i < (1 << n); i ++){
int t = 0;
for(int j = 0; j < n; j ++){
if((i >> j) & 1)
t += nums[j];
}
q[i] = t;
}
//排序
sort(q.begin(), q.end());
//ri也得到子集,每得到一个子集就二分去le数组中找最优解
for(int i = 0; i < (1 << m); i ++){
int t = 0;
for(int j = 0; j < m; j ++){
if((i >> j) & 1)
t += nums[j + n];
}
//二分找答案
int le = 0, ri = q.size() - 1, mid;
while(le < ri){
mid = le + ri >> 1;
if(q[mid] + t < goal)
le = mid + 1;
else
//保证q[ri] + t >= goal
ri = mid;
}
//在ri和ri - 1 中找答案
if(abs(t + q[ri] - goal) < ans)
ans = abs(t + q[ri] - goal);
if(ri)
if(abs(t + q[ri - 1] - goal) < ans)
ans = abs(t + q[ri - 1] - goal);
}
return ans;
}
};
5.袋子里最少数目的球
题目:
给你一个整数数组 nums ,其中 nums[i] 表示第 i 个袋子里球的数目。同时给你一个整数 maxOperations 。
你可以进行如下操作至多 maxOperations 次:
选择任意一个袋子,并将袋子里的球分到 2 个新的袋子中,每个袋子里都有 正整数 个球。
比方说,一个袋子里有 5 个球,你可以把它们分到两个新袋子里,分别有 1 个和 4 个球,或者分别有 2 个和 3 个球。
你的开销是单个袋子里球数目的 最大值 ,你想要 最小化 开销。
请你返回进行上述操作后的最小开销。
示例 1:
输入:nums = [9], maxOperations = 2
输出:3
解释:
- 将装有 9 个球的袋子分成装有 6 个和 3 个球的袋子。[9] -> [6,3] 。
- 将装有 6 个球的袋子分成装有 3 个和 3 个球的袋子。[6,3] -> [3,3,3] 。
装有最多球的袋子里装有 3 个球,所以开销为 3 并返回 3 。
示例 2:
输入:nums = [2,4,8,2], maxOperations = 4
输出:2
解释:
- 将装有 8 个球的袋子分成装有 4 个和 4 个球的袋子。[2,4,8,2] -> [2,4,4,4,2] 。
- 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,4,4,4,2] -> [2,2,2,4,4,2] 。
- 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,2,2,4,4,2] -> [2,2,2,2,2,4,2] 。
- 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,2,2,2,2,4,2] -> [2,2,2,2,2,2,2,2] 。
装有最多球的袋子里装有 2 个球,所以开销为 2 并返回 2 。
示例 3:
输入:nums = [7,17], maxOperations = 2
输出:7
提示:
1 <= nums.length <= 105
1 <= maxOperations, nums[i] <= 109
题解:
又是二分查找(有点做🤮了吧,hhh), 二分来找答案,check若为true则缩小答案,否则扩大答案。注意每个袋子中的球都是独立的,所以若第 i 个袋子有nums[i] 个球, 最终每一个袋子只允许有 len 个球,那么需要把第 i 个袋子操作 (nums[i] - 1) / len 次。
代码:
class Solution {
public:
int minimumSize(vector<int>& nums, int maxOperations) {
int le = 1, ri = 1e9, mid;
while(le < ri){
mid = le + ri >> 1;
if(check(mid, nums, maxOperations))
ri = mid;
else
le = mid + 1;
}
return ri;
}
bool check(int len, vector<int> nums, int k){
int cnt = 0;
for(int i : nums){
cnt += (i - 1) / len;
}
return cnt <= k;
}
};
书籍部分
算法竞赛.进阶指南 0x61最短路 floyd ✔