算法分析与设计 - 作业4
问题一
有一个序列是某个有序系列围绕着下标为 的元素()旋转得到的序列,使数组下标变为 ,如 123456 围绕着下标为 3 的元素旋转得到 456123,请为此序列编写元素查找算法,并分析你的算法性能。
发现序列以位置 为界,前后分别为有序状态。在有序部分内可以进行二分查找,于是考虑对于要查询的位置,首先判断它位于 中还是位于 中,然后在对应的有序部分内进行二分查找即可。
空间复杂度 级别,单次查询即为二分查找的时间复杂度,为 级别。
复制// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 1e5 + 10; //============================================================= int a[kN]; //============================================================= //============================================================= int main() { //freopen("1.txt", "r", stdin); // std::ios::sync_with_stdio(0), std::cin.tie(0); int n, k; std::cin >> n >> k; for (int i = 1, j = k + 1; i <= k; ++ i, ++ j) a[i] = j; for (int i = k + 1, j = 1; i <= n; ++ i, ++ j) a[i] = j; int q; std::cin >> q; while (q --) { int x, p = -1; std::cin >> x; if (x <= k) p = std::lower_bound(a + k + 1, a + n + 1, x) - a; else if (x <= n) p = std::lower_bound(a + 1, a + k + 1, x) - a; else p = -1; if ((x <= k && p == n + 1) || (k < x && x <= n && p == k + 1)) p = -1; std::cout << p << "\n"; } return 0; }
问题二
我们学习的二分查找算法是针对一维有序序列的,现假设有一个矩阵,其每一行每一列分别是从左到右、从上到下有序的,请为此矩阵编写元素查找算法,并分析你的算法性能。
手玩一下可知矩阵的形态类似于:
5 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9
则某种元素的出现次数可能为 级别,且出现位置可能并不连续,估计很难找到一个小于 的解法。
解法一
发现每一行都是有序的,则在每一行若有待查询元素,则它们一定构成一段连续的区间。于是考虑对每一行运行二分查找算法,找到待查询元素出现的区间即可。
并不需要额外的数据结构维护,空间复杂度 级别,时间复杂度 级别。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 2000 + 10; //============================================================= int a[kN][kN]; //============================================================= //============================================================= int main() { //freopen("1.txt", "r", stdin); // std::ios::sync_with_stdio(0), std::cin.tie(0); int n; std::cin >> n; for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= n; ++ j) { std::cin >> a[i][j]; } } int q; std::cin >> q; while (q --) { int x; std::cin >> x; for (int i = 1; i <= n; ++ i) { int l = std::lower_bound(a[i] + 1, a[i] + n + 1, x) - a[i]; int r = std::upper_bound(a[i] + 1, a[i] + n + 1, x) - a[i] - 1; if (l <= r) std::cout << "line " << i << " : [" << l << ", " << r << "] is element " << x <<".\n"; else std::cout << "line " << i << " has no element " << x << "!\n"; } } return 0; }
解法二
解法一没有使用每一列也为有序这一性质,这可不行!
通过观察可知,若每一列也为有序,则对于某元素 ,其在第 行与第 行中出现的位置 一定满足:
即有:
发现数列 和数列 均为非递减数列,分别代表第 行元素 最靠左和最靠右出现的位置。考虑通过求得这两个数列来确定待查询元素的位置。
以求得 为例,考虑倒序枚举每一行 ,在此过程中维护一个指针 指向当前行 出现的最靠左的位置:
- 当 时,初始化 ;否则当 时初始化 。
- 然后不断右移指针直至 ,则若第 行存在元素 ,则 。
求数列 同理,仅需维护指针 指向该行元素 最靠右的满足 的元素,并不断右移即可。
按照上述算法求得上述两数列后,若某行满足 ,说明该行存在元素 ,且所有元素 构成区间 ,否则不存在元素 。
空间复杂度 级别。发现上述过程中指针 在倒序枚举每行时是单调不降的,则右移操作至多会进行 次,又仅会枚举 行,则枚举次数为 级别。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 2000 + 10; //============================================================= int a[kN][kN], l[kN], r[kN]; //============================================================= //============================================================= int main() { // freopen("1.txt", "r", stdin); // std::ios::sync_with_stdio(0), std::cin.tie(0); int n; std::cin >> n; for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= n; ++ j) { std::cin >> a[i][j]; } } int q; std::cin >> q; while (q --) { int x; std::cin >> x; for (int i = n; i >= 1; -- i) { int pl = (i == n) ? 0 : l[i + 1]; while (pl <= n && a[i][pl] < x) ++ pl; l[i] = pl; int pr = (i == n) ? 0 : r[i + 1]; while (pr < n && a[i][pr + 1] <= x) ++ pr; r[i] = pr; } for (int i = 1; i <= n; ++ i) { if (l[i] <= r[i]) std::cout << "line " << i << " : [" << l[i] << ", " << r[i] << "] is element " << x <<".\n"; else std::cout << "line " << i << " has no element " << x << "!\n"; } } return 0; }
写在最后
问题二中解法二的时间复杂度分析是一种均摊时间复杂度分析。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!