左神算法与数据结构——中级提升班-7

中级提升班-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整除方法:

  1. 判断N % 3 == 0
  2. 将该数每位都加在一起得到的数能否被3整除
  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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值