当前位置: 首页 > news >正文

算法优化——以“LCR 080. 组合”为例

算法优化

算法优化,大体上几个思路:
1. 降低复杂度,比如从循环查找调整为二分查找或者用哈希表来查找,从需要排序到使用优先队列自动排序;
2. 减少重复运算。类似从暴力求解到动态规划;
3. 减少无效运算。如果某个前置条件决定后续所有结果都无效,那么达到前置条件位置时就跳出,避免后续的运算来耗时间耗内存。
下面,将以“LCR 080. 组合”为例来展示下算法优化过程,以及优化的结果。

问题描述

该问题具体描述如下:给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。以下是示例:在这里插入图片描述
显然,这是经典的回溯问题。下面,将从回溯法开始进行求解,并进行算法优化。

算法实现及优化

该问题是经典的回溯问题,那首先用回溯法进行求解

状态一(回溯法)

回溯法的基本原理,就是对当前位置进行循环,然后将当前位置对应的结果进行汇总,具体的不再展开,直接上代码:

class Solution {
public:vector<vector<int>> combine(vector<int> usableNums, int k) {if (k > usableNums.size())return {};vector<vector<int>> results;if (k == 1) {for (auto& num : usableNums) {results.push_back({num});}return results;}for (int i = 0; i < usableNums.size(); ++i) {vector<int> nextNums;for (int j = 0; j < usableNums.size(); ++j) {if (j != i && usableNums[i] < usableNums[j])nextNums.push_back(usableNums[j]);}vector<vector<int>> nextResults = combine(nextNums, k - 1);for (auto& tempResult : nextResults) {vector<int> currentResult = {usableNums[i]};currentResult.insert(currentResult.end(), tempResult.begin(),tempResult.end());results.push_back(currentResult);}}return results;}vector<vector<int>> combine(int n, int k) {vector<int> usableNums(n, 0);for (int i = 0; i < n; ++i) {usableNums[i] = i + 1;}return combine(usableNums, k);}
};

算法性能如下所示:
在这里插入图片描述
即对于给定的27个算例,通过时间为98ms,占用内存为42.07MB;

状态二

状态一中,长的组合是短的组合汇总而成,而短的组合又是由更短的组合汇总而来,因此不少相同的短组合会重新计算。比如,[1,3,4,5]和[2,3,4,5]都涉及到[3,4,5]这个短组合,就会算两次。
如果先从最短组合开始算起,就能避免这部分重复运算。因此,状态二先从短组合开始生成,接长为长组合。代码如下:

class Solution {
public:vector<vector<int>> combine(int n, int k) {if (k == 0)return {};vector<vector<int>> results;for (int i = 0; i < n; ++i) {results.push_back({i + 1});}for (int i = 2; i <= k; ++i) {vector<vector<int>> currentResults;for (int j = 0; j < results.size(); ++j) {for (int m = results[j].back() + 1; m <= n; ++m) {vector<int> currentList = results[j];currentList.push_back(m);currentResults.push_back(currentList);}}results = currentResults;}return results;}
};

算法性能如下所示:
在这里插入图片描述
即对于给定的27个算例,通过时间为834ms,占用内存为437.29MB;和状态一相比,时间和内存占用反而是前者的十倍,性能反而差了很多。
为什么会出现相反的效果呢?从代码来看,重复计算肯定已经避免了,能节省一部分时间和空间,但是无效计算增多了。比如,上面代码中循环变语句:for (int m = results[j].back() + 1; m <= n; ++m),循环后的结果,当前循环能产生有效的结果,但下一步或者后面几步,最后几个结果会被过滤掉,存在无效计算。

状态三

针对状态二中存在无效计算的问题,最关键的就是设置合理的循环边界。好的循环边界,既能覆盖所有有效结果,又能将无效计算降到最低。因此,根据每一步的计算实时调整边界。调整后代码如下:

class Solution {
public:vector<vector<int>> combine(int n, int k) {if (k == 0)return {};vector<vector<int>> results;for (int i = 0; i < n; ++i) {results.push_back({i + 1});}int restSize = k - 2;for (int i = 2; i <= k; ++i) {vector<vector<int>> currentResults;for (int j = 0; j < results.size(); ++j) {int endLoc = n - restSize;for (int m = results[j].back() + 1; m <= endLoc; ++m) {vector<int> currentList = results[j];currentList.push_back(m);currentResults.push_back(currentList);}}results = currentResults;restSize = k - i-1;}return results;}
};

算法性能如下图所示:
在这里插入图片描述
即对于给定的27个算例,通过时间为25ms,占用内存为24.05MB;和状态一和状态二相比,时间和内存占用都少了很多,算法性能明显提升。

状态四

那针对状态三,算法性能还有没有可能提升呢?经过检查发现,第一次循环还存在无效运算。调整循环的边界,还能减少运行时间。调整后代码如下:

class Solution {
public:vector<vector<int>> combine(int n, int k) {if (k == 0)return {};vector<vector<int>> results;int endLoc = n - (k - 1);for (int i = 0; i < endLoc; ++i) {results.push_back({i + 1});}for (int i = 2; i <= k; ++i) {vector<vector<int>> currentResults;endLoc = n - (k - i );for (int j = 0; j < results.size(); ++j) {for (int m = results[j].back() + 1; m <= endLoc; ++m) {vector<int> currentList = results[j];currentList.push_back(m);currentResults.push_back(currentList);}}results = currentResults;}return results;}
};

算法性能如下图所示:
在这里插入图片描述
即对于给定的27个算例,通过时间为15ms,占用内存为23.93MB;和优化前(状态一)相比,占用时间减少了85%,占用内存减少了44%,性能提升明显。

总结

针对已实现的过程,找出其中重复运算的部分以及无效运算的部分,能算法让算法性能明显提升。当然,如果需要更进一步的提升,可能需要更好的思路,或者更好的数据结构。

http://www.xdnf.cn/news/517159.html

相关文章:

  • React Native打包报错: Task :react-native-picker:verifyReleaseResources FAILE
  • IIS入门指南:原理、部署与实战
  • 电动车仪表上的数字怎么来的,想知道吗?
  • leetcode3546. 等和矩阵分割 I- medium
  • uniapp中的easycom工作机制
  • Flask快速入门和问答项目源码
  • ​在 ASP.NET 中,HTTP 处理程序(HttpHandler)是处理 HTTP 请求的核心组件​
  • 【Bluedroid】蓝牙HID DEVICE 报告发送与电源管理源码解析
  • Python 中 if 和 else 基础知识的详解和使用
  • 中级统计师-统计学基础知识-第四章 假设检验
  • 【老马】离线版金融敏感信息加解密组件开源项目 encryption-local
  • python打卡day29
  • spark数据处理练习题详解【下】
  • 【simulink】IEEE33节点系统潮流分析模型
  • 2025年全国青少年信息素养大赛C++小学全年级初赛试题
  • MyBatis框架(入门)
  • 【java多线程】线程间通信-利用wait和notify轮流按序打印奇数和偶数
  • 一文读懂-嵌入式Ubuntu平台
  • 基于Bootstrap 的网页html css 登录页制作成品
  • C++学习:六个月从基础到就业——C++17:if/switch初始化语句
  • MATLAB中的Switch语句讲解
  • LabVIEW双音信号互调失真测量
  • CSS 浮动(Float)及其应用
  • AUTOSAR图解==>AUTOSAR_SRS_TTCAN
  • CPU、核心、进程、线程、协程的定义及关系
  • 基于DeepSeek API构建带记忆的对话系统:技术实现详解
  • 网络的知识的一些概念
  • 代码案例分析
  • 文件IO之标准IO
  • AGI大模型(23):LangChain框架快速入门之LangChain介绍