中级提升班-7
题目三
大数被三整除
小Q得到一个神奇的数列: 1, 12, 123,…12345678910,1234567891011…。
并且小Q对于能否被3整除这个性质很感兴趣。
小Q现在希望你能帮他计算一下从数列的第l个到第r个(包含端点)有多少个数可以被3整除。
输入描述:
输入包括两个整数l和r(1 <= l <= r <= 1e9), 表示要求解的区间两端。
输出描述:
输出一个整数, 表示区间内能被3整除的数字个数。
示例1:
输入
2 5
输出
3
解题思路:判断一个数能不能被3整除,等价于一个数的每位之和能否被3整除。刚开始想打表,但发现数据
量是1e9,一维数组最多只能开到1e8.所以就纯暴力判断了,不过数据是有规律的,第一个数是1、第二个数
是12,第三个数是123,所以只用判断n(n+1)/2%3即可。因为数量太大了,所以用long long*
判断能否被3整除方法:
- 判断N % 3 == 0
- 将该数每位都加在一起得到的数能否被3整除
- 1+2+3+···+N能否被3整除,例如103能否被3整除按照方法2化为1+0+3能否被3整除,那么1+0+3可以同样化为103能否被3整除,则化简问题
int f(int l, int r) {
int res = 0;
for (int i = l; i <= r; i++) {
long temp = (((long)i * (long)(i + 1)) >> 1);
if (temp % 3 == 0) {
res++;
}
}
return res;
}
题目五
递归、完全二叉树CBT
求完全二叉树节点个数
法1:O(N),每个节点都搜索到,按照CBT方法
法2:
- 首先遍历到最左节点,得知CBT最大深度
- 从头节点开始递归,找到头节点的right孩子的最左边,若其也为最大深度,说明头节点的左树肯定为完全二叉树,左树+头节点个数为24 + 1 - 1,接下来,在头节点右树上递归
- 若头节点的右孩子没有达到最大高度,说明右树为完全二叉树,则右树 + 头节点的个数为2右树高度 - 1 + 1,接着在左树进行递归
- 下图为例子
- 时间复杂度为O(log2N) < O(N)
class TreeNode {
public:
int val;
TreeNode* left;
TreeNode* right;
TreeNode (int val) {
this->val = val;
this->left = nullptr;
this->right = nullptr;
}
};
bool isCBT(TreeNode* head) {
queue<TreeNode*> que;
que.push(head);
bool leaf = false;
while (!que.empty()) {
TreeNode* cur = que.front();
que.pop();
if ((leaf && (cur->left != nullptr || cur->right != nullptr)) || (cur->left == nullptr && cur->right != nullptr)) {
/*cout << "不是CBT" << endl;*/
return false;
}
if (cur->left != nullptr) {
que.push(cur->left);
}
if (cur->right != nullptr) {
que.push(cur->right);
}
else {
leaf = true;
}
}
/*cout << "是CBT" << endl;*/
return true;
}
int getHeight(TreeNode* head, int level) {
int height = level;
TreeNode* cur = head;
while (cur != nullptr) {
height++;
cur = cur->left;
}
return height - 1;
}
// level表示当前层数,height表示最大深度
int process(TreeNode* head, int level, int height) {
if (level == height) {
return 1;
}
if (getHeight(head->right, level + 1) == height) {
return (1 << (height - level)) + process(head->right, level + 1, height);
}
else {
return (1 << height - level - 1) + process(head->left, level + 1, height);
}
}
void Problem05_CompleteTreeNodeNumber(TreeNode* head) {
if (!isCBT(head)) {
return;
}
int height = 0;
TreeNode* cur = head;
while (cur != nullptr) {
height++;
cur = cur->left;
}
cout << process(head, 1, height) << endl;
}
题目二
给定一个整数数组A,长度为n,有 1 <= A[i] <= n,且对于[1,n]的整数,其
中部分整数会重复出现而部分不会出现。
实现算法找到[1,n]中所有未出现在A中的整数。
提示:尝试实现O(n)的时间复杂度和O(1)的空间复杂度(返回值不计入空间复
杂度)。
输入描述:
一行数字,全部为整数,空格分隔
A0 A1 A2 A3…
输出描述:
一行数字,全部为整数,空格分隔R0 R1 R2 R3…
示例1:
输入
1 3 4 3
输出
2
方法:若有N个数,则范围为1~N,则规定长度为N的数组,下标为0-N-1,因此,需要构造一个算法,使其数组i位置上放上值为i + 1的数。当cur来到i位置时,若该位置上不为i + 1,而为k,则跳到k - 1下标上,若k-1下标对应的值为k,则break,若不为k,而为m,则用k替换m,继续进行以上操作,停止后cur++。
void Problem02_PrintNoInArray(vector<int>& arr, int n) {
if (arr.size() < 1) {
return;
}
for (int cur = 0; cur < arr.size(); cur++) {
int value = arr[cur];
while (arr[value - 1] != value) {
int temp = arr[value - 1];
arr[value - 1] = value;
value = temp;
}
}
for (int i = 0; i < arr.size(); i++) {
if (arr[i] != i + 1) {
cout << i + 1 << " ";
}
}
}
题目四
递归+限制条件
CC里面有一个土豪很喜欢一位女直播Kiki唱歌,平时就经常给她点赞、送礼、私聊。最近CC直播平台在举行
中秋之星主播唱歌比赛,假设一开始该女主播的初始人气值为start, 能够晋升下一轮人气需要刚好达到end,
土豪给主播增加人气的可以采取的方法有:
a. 点赞 花费x C币,人气 + 2
b. 送礼 花费y C币,人气 * 2
c. 私聊 花费z C币,人气 - 2
其中 end 远大于start且end为偶数, 请写一个程序帮助土豪计算一下,最少花费多少C币就能帮助该主播
Kiki将人气刚好达到end,从而能够晋级下一轮?
输入描述:
第一行输入5个数据,分别为:x y z start end,每项数据以空格分开。
其中:0<x, y, z<=10000, 0<start, end<=1000000
输出描述:
需要花费的最少C币。
示例1:
输入
3 100 1 2 6
输出
6
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GLRsTWUr-1674911711639)(C:\Users\MSI-NB\Desktop\左程云\图片\中级\7\7.PNG)]
若利用此方法,思路正确,但它会存在当前cur的积分远远超过end既定值的情况而不停止,并且存在cur值为负数而不停止的情况,则当前循环不会停止。因此需要增加base case,增加平凡解。比如全用点赞达到的过程为一个平凡解,若超过该解,则不可能为最优解。针对这道题还需要一个base case,如果开始为a,需要达到积分为b,那么不可能使得cur达到2b的值,若达到2b的值,则肯定会经过b的值,那么就多次一举了。
// abc分别为花费的c币
// pre表示已经花费了多少C币
// limit1表示第二个basecase,不得大于end的两倍
// limit2表示一个平凡解,即为只用+2的方式到达end需要的C币数
int process(int a, int b, int c, int pre, int end, int cur, int limit1, int limit2) {
if (pre > limit1 || pre > limit2 || cur < 0) {
return INT32_MAX;
}
if (cur == end) {
return pre;
}
int mi = INT32_MAX;
int p1 = process(a, b, c,pre + a, end, cur + 2, limit1, limit2);
if (p1 != INT32_MAX) {
mi = p1;
}
int p2 = process(a, b, c, pre + b, end, cur * 2, limit1, limit2);
if (p2 != INT32_MAX) {
mi = min(p2, mi);
}
int p3 = process(a, b, c, pre + c, end, cur - 2, limit1, limit2);
if (p3 != INT32_MAX) {
mi = min(p3, mi);
}
return mi;
}
void kiki(int a, int b, int c, int start, int end) {
if (start > end) {
return;
}
cout << process(a, b, c, 0, end, start, 2 * end, (end - start) / 2 * a) << endl;
}
题目六
图、有序表
思路:
由最后一个任务开始(由于必须完成最后一项任务),逐步向前生成有序表。有序表分别表示所需天数及其奖励值。例如A项目中需要包括CED的数据,在加上A项目的数值后进行洗表(把天数增加但奖励值不增加的删除),当每个项目对应的有序表完成后,生成一个大表,并洗表,可以查出任何一个时长所获的最大奖励。
class Node {
public:
int times;
int revenue;
vector<Node*> nexts;
vector<Node*> parents;
map<int, int> mp;// 天数和奖励
Node(int times, int revenue) {
this->times = times;
this->revenue = revenue;
}
};
// 转换矩阵,将题目所给两个矩阵变成Node中的数值,将Node连起来
unordered_map<int, Node*> getNode(vector<int> revenue, vector<int> times, vector<vector<int>> dependents) {
// 首先将Node中天数和奖励给整完
int num = revenue.size();
unordered_map<int, Node*> umap;// 用来记录和查找Node
for (int i = 0; i < num; i++) {
umap.insert({ i, new Node(times[i], revenue[i]) });
}
// 记录Node各个完成顺序关系
for (int i = 0; i < num; i++) {
for (int j = 0; j < num; j++) {
if (dependents[i][j] == 1) { // 第i个活动完成后去做第j个
umap[i]->nexts.push_back(umap[j]);
umap[j]->parents.push_back(umap[i]);
}
}
}
return umap;
}
void process(Node* head, Node* cur, map<int, int>& money) {
if (head == cur) {
cur->mp.insert({ cur->times, cur->revenue });
money.insert({ cur->times, cur->revenue });
}
else {
for (Node* next : cur->nexts) {
for (pair<int, int> p : next->mp) {
cur->mp[cur->times + p.first] = cur->revenue + p.second;
}
}
for (pair<int, int> p : cur->mp) {
if (money.find(p.first) == money.end() || money[p.first] < p.second) {
money[p.first] = p.second;
}
}
}
}
// 主函数
void maxRevenue(int days, vector<int> revenue, vector<int> times, vector<vector<int>> dependents) {
unordered_map<int, Node*> umap = getNode(revenue, times, dependents);
// 由于最后一项活动必须参加,则首先寻找末尾节点,即nexts为空
Node* lastActivity = nullptr;
for (pair<int, Node*> it : umap) {
if (it.second->nexts.empty()) {
lastActivity = it.second;
break;
}
}
// 从最后一个活动开始,对他的父节点进行宽度优先遍历
queue<Node*> que;
unordered_set<Node*> uset;
Node* cur = nullptr;
que.push(lastActivity);
uset.insert(lastActivity);
map<int, int> money;
while (!que.empty()) {
cur = que.front();
que.pop();
process(lastActivity, cur, money);
for (auto p : cur->parents) {
/*if (!uset.count(p)) {
que.push(p);
uset.insert(p);
}*/
que.push(p);
}
}
auto iter = money.begin();
int pre = iter->second;
iter++;
while (iter != money.end()) {
if (iter->second <= pre) {
int temp = iter->first;
iter++;
money.erase(temp);
}
else {
pre = iter->second;
iter++;
}
}
iter = money.upper_bound(days);
iter--;
cout << iter->second << " " << iter->first << endl;
}
题目七
最长递增子序列问题
子序列和子串区别:子串要求连续,子序列不要求连续
传统方法:O(N2)
- dp[i]数组表示,子序列必须以i作为结尾情况下,最长的递增子序列长度
- dp数组前两个得自己填,后续i在遍历之前的arr时,找到比arr[i]小的index,找到index位置对应dp数组中值最大的那个个,选用其作为前缀
void MaxRevenue1(vector<int> arr) {
vector<int> dp(arr.size());
dp[0] = 1;
dp[1] = arr[1] > arr[0] ? 2 : 1;
for (int index = 2; index < dp.size(); index++) {
int maxIndex = -1;
int maxDp = INT32_MIN;
for (int i = 0; i < index; i++) {
if (arr[i] < arr[index]) {
if (dp[i] > maxDp) {
maxIndex = i;
maxDp = dp[i];
}
}
}
dp[index] = maxIndex == -1 ? 1 : maxDp + 1;
}
}
法2:O(NlogN)
- 建立dp数组和ends数组。其中ends数组第i个位置表示,所有i + 1长度的递增子序列中,最小的结尾值
- 如果当前遍历到的数比ends中任意一个大,则在ends原来最大的数后放上该数,dp中填入该数及其左边共有几个数
- 若当前数不比最大的大,则在ends中找到最接近大于等于该数的数,并更新该数
int MaxRevenue2(vector<int> arr) {
vector<int> ends(arr.size());
ends[0] = arr[0];
int e = 0;
for (int i = 1; i < arr.size(); i++) {
int index = -1;
int l = 0;
int r = e;
while (l <= r) {
int mid = l + ((r - l) >> 1);
if (ends[mid] >= arr[i]) {
r = mid - 1;
index = mid;
}
else {
l = mid + 1;
}
}
if (index == -1) {
ends[++e] = arr[i];
}
else {
ends[index] = arr[i];
}
}
/*cout << e + 1 << endl;*/
return e + 1;
}