经典递归

news/2024/12/21 22:22:21/文章来源:https://www.cnblogs.com/sprinining/p/18448540

master公式

  • 所有子问题规模相同的递归才能用master公式:T(n)=a*T(n/b)+O(n^c),a、b、c为常数
  • 若log(b, a) < c,复杂度为O(n^c),log(b, a)是以b为底a的对数
  • 若log(b, a) > c,复杂度为O(n^log(b, a))
  • 若log(b, a) = c,复杂度为O(n^c * logn)
  • T(n)=2*T(n/2)+O(n*longn),时间复杂度为O(n*(logn)^2)

字符串的全部子序列

  • 时间复杂度 O(2^n * n)

  • 常规做法

#include <vector>
#include <iostream>
#include <unordered_set>using namespace std;class Solution {
public:vector<string> res;unordered_set<string> st;string path;void generate(string &s, int curIndex) {if (curIndex == s.length()) {// 去重if (st.find(path) != st.end()) return;res.emplace_back(path);st.emplace(path);return;}// curIndex 处选中path.append(1, s[curIndex]);// 递归处理下个位置generate(s, curIndex + 1);// 回溯,删除最后一个字符path.erase(path.length() - 1, 1);generate(s, curIndex + 1);}vector<string> generatePermutation(string s) {generate(s, 0);return res;}
};
  • 省去擦除
#include <vector>
#include <iostream>
#include <unordered_set>using namespace std;class Solution {
public:vector<string> res;unordered_set<string> st;string path;// size 为当前 path 中有效长度void generate(string &s, int curIndex, int size) {if (curIndex == s.length()) {// 只选取有效长度string str = path.substr(0, size);// 去重if (st.find(str) != st.end()) return;res.emplace_back(str);st.emplace(str);return;}path[size] = s[curIndex];// curIndex 处选中,path 的长度变为 size + 1generate(s, curIndex + 1, size + 1);// curIndex 处不选中,path 的长度还是 sizegenerate(s, curIndex + 1, size);}vector<string> generatePermutation(string s) {path.resize(s.length());generate(s, 0, 0);return res;}
};

90. 子集 II

  • 时间复杂度 O(2^n * n)

  • 相同的元素作为一段

#include <vector>
#include <iostream>
#include <unordered_set>
#include <algorithm>using namespace std;class Solution {
public:vector<vector<int>> res;vector<int> path;void generate(vector<int> &nums, int curIndex) {if (curIndex == nums.size()) {res.emplace_back(path);return;}int end = curIndex;while (end < nums.size() && nums[end] == nums[curIndex])end++;// 这一段相同的元素的个数int len = end - curIndex;// 选中 i 个这种元素for (int i = 0; i <= len; ++i) {for (int j = 0; j < i; ++j)path.emplace_back(nums[curIndex]);// 递归处理下一段generate(nums, curIndex + len);// 回溯for (int j = 0; j < i; ++j)path.pop_back();}}vector<vector<int>> subsetsWithDup(vector<int> &nums) {sort(nums.begin(), nums.end());generate(nums, 0);return res;}
};
  • 省去擦除
#include <vector>
#include <iostream>
#include <unordered_set>
#include <algorithm>using namespace std;class Solution {
public:vector<vector<int>> res;vector<int> path;// size 为当前 path 中有效长度void generate(vector<int> &nums, int curIndex, int size) {if (curIndex == nums.size()) {// 选取有效位置res.emplace_back(vector<int>(begin(path), begin(path) + size));return;}int end = curIndex;while (end < nums.size() && nums[end] == nums[curIndex])end++;// 这一段相同的元素的个数int len = end - curIndex;// 选中 i 个这种元素for (int i = 0; i <= len; ++i) {for (int j = 0; j < i; ++j)path[size + j] = nums[curIndex];// 递归处理下一段generate(nums, curIndex + len, size + i);}}vector<vector<int>> subsetsWithDup(vector<int> &nums) {sort(nums.begin(), nums.end());path.resize(nums.size());generate(nums, 0, 0);return res;}
};

46. 全排列

  • 时间复杂度 O(n! * n)

  • 按字典序输出

  • 用哈希表标记 nums 中某个位置是否已经加入到 path

#include <vector>using namespace std;class Solution {
public:vector<vector<int>> res;vector<int> path;vector<bool> entered;// path 中 [0, curIndex) 已经放入数据,现在往 curIndex 处放入所有可能void generate(vector<int> &nums, int curIndex) {if (curIndex == nums.size()) {res.emplace_back(path);return;}for (int i = 0; i < nums.size(); ++i) {// nums[i] 还没放入,就放入到 curIndex 位置if (!entered[nums[i] + 10]) {path[curIndex] = nums[i];// 标记nums[i] 已经放入entered[nums[i] + 10] = true;// 递归处理子问题,尝试 curIndex + 1 处所有的放入可能generate(nums, curIndex + 1);// 取消标记,再尝试在 curIndex 处放入其他还没使用过的数据entered[nums[i] + 10] = false;}}}// 按字典序输出vector<vector<int>> permute(vector<int> &nums) {entered.resize(21, false);path.resize(nums.size());generate(nums, 0);return res;}
};
  • 不按字典序
  • 把所有加入到 path 的元素移到 nums 中的左侧,当前位置从 nums 右侧元素中挑选
#include <vector>
#include <algorithm>using namespace std;class Solution {
public:vector<vector<int>> res;vector<int> path;// path 中 [0, curIndex) 已经放入数据,现在往 curIndex 处放入所有可能void generate(vector<int> &nums, int curIndex) {if (curIndex == nums.size()) {res.emplace_back(path);return;}// nums[left] 开始到末尾都是尚未使用过的元素,从中挑出一个使用,并且在 nums 中和 nums[left] 交换位置// 这样以来 nums 从开头到 nums[left] 就是已经使用过的元素int left = curIndex;for (int right = left; right < nums.size(); ++right) {path[curIndex] = nums[right];// 标记 nums[i] 已经放入swap(nums[left], nums[right]);// 递归处理子问题,尝试 curIndex + 1 处所有的放入可能generate(nums, curIndex + 1);// 取消标记,再尝试在curIndex处放入其他还没使用过的数据swap(nums[left], nums[right]);}}// 不按字典序输出,不使用 entered 标记元素是否使用过vector<vector<int>> permute(vector<int> &nums) {path.resize(nums.size());generate(nums, 0);return res;}
};
  • 省去 path 直接在 nums 中操作
// 以 curIndex 作为标记,nums[0, curIndex)是已经排好的,也就是使用过的元素
class Solution {
public:vector<vector<int>> res;void backtrack(vector<int> &nums, int curIndex) {if (curIndex == nums.size()) {res.emplace_back(nums);return;}// nums[0, curIndex)是已经排好的// 从 nums[curIndex, nums.size()-1] 中选一个 nums[i] 放到 nums[curIndex]for (int i = curIndex; i < nums.size(); ++i) {// nums[i] 放到 nums[curIndex]swap(nums[i], nums[curIndex]);// 继续递归填下一个数backtrack(nums, curIndex + 1);// 撤销操作swap(nums[i], nums[curIndex]);}}// 不按字典序输出vector<vector<int>> permute(vector<int> &nums) {backtrack(nums, 0);return res;}
};
// 把 nums 数组当作标记数组
class Solution {
public:vector<vector<int>> res;vector<int> output;// output 中 0 到 curIndex 已经放入数据,现在往 curIndex 处放入所有可能void backtrack(vector<int> &nums, int curIndex) {// output 已经放满,把当前排列添加到结果中if (curIndex == nums.size()) {res.emplace_back(output);return;}for (int i = 0; i < nums.size(); ++i) {// 已经被使用过的就跳过if (nums[i] == INT_MIN) continue;int temp = nums[i];// 标记nums[i] = INT_MIN;output.emplace_back(temp);// 继续递归填下一个数backtrack(nums, curIndex + 1);// 撤销操作output.pop_back();nums[i] = temp;}}// 不按字典序输出vector<vector<int>> permute(vector<int> &nums) {backtrack(nums, 0);return res;}
};

47. 全排列 II

  • 时间复杂度 O(n! * n)

  • 给定一个可包含重复数字的序列 nums ,按任意顺序返回所有不重复的全排列。

#include <vector>
#include <iostream>
#include <unordered_set>
#include <algorithm>using namespace std;class Solution {
public:vector<vector<int>> res;void backtrack(vector<int> &nums, int curIndex) {if (curIndex == nums.size()) {res.emplace_back(nums);return;}// 标记元素是否曾经放入 curIndexunordered_set<int> st;// nums[0, curIndex)是已经排好的// 从 nums[curIndex, nums.size()-1] 中选一个 nums[i] 放到 nums[curIndex]for (int i = curIndex; i < nums.size(); ++i) {// 避免生成重复的if (st.find(nums[i]) != st.end()) continue;st.emplace(nums[i]);// nums[i] 放到 nums[curIndex]swap(nums[i], nums[curIndex]);// 继续递归填下一个数backtrack(nums, curIndex + 1);// 撤销操作swap(nums[i], nums[curIndex]);}}vector<vector<int>> permuteUnique(vector<int> &nums) {backtrack(nums, 0);return res;}
};

用递归函数逆序栈

  • 时间复杂度 O(n^2)
#include <vector>
#include <iostream>
#include <unordered_set>
#include <algorithm>
#include <stack>using namespace std;// 栈底元素移除掉,上面的元素依次落下来,返回移除掉的栈底元素
int bottomOut(stack<int> &stack) {// 当前栈顶出栈int top = stack.top();stack.pop();// 栈空就返回唯一的栈顶元素if (stack.empty()) return top;// 栈不空,就取出栈底,其他按原来的顺序压栈int bottom = bottomOut(stack);stack.push(top);return bottom;
}void reverse(stack<int> &stack) {if (stack.empty()) return;int bottom = bottomOut(stack);reverse(stack);// 最先取出的栈底,最后入栈,从而实现逆序栈stack.push(bottom);
}int main() {stack<int> stack;stack.push(1);stack.push(2);stack.push(3);stack.push(4);stack.push(5);reverse(stack);while (!stack.empty()) {cout << stack.top() << endl;stack.pop();}return 0;
}

用递归函数排序栈

  • 时间复杂度 O(n^2)
#include <iostream>
#include <stack>
#include <cstdlib>
#include <climits>using namespace std;class Solution {
public:static void sort(stack<int> &stack) {int depth = getDepth(stack);while (depth > 0) {int max = getMax(stack, depth);int k = countOccurrences(stack, depth, max);moveToBottom(stack, depth, max, k);depth -= k;}}// 递归求栈深static int getDepth(stack<int> &stack) {if (stack.empty()) return 0;int top = stack.top();stack.pop();int depth = getDepth(stack) + 1;stack.push(top);return depth;}// 递归求栈中最大值static int getMax(stack<int> &stack, int depth) {if (depth == 0) return INT_MIN;int top = stack.top();stack.pop();int tempMax = getMax(stack, depth - 1);int finalMax = max(top, tempMax);stack.push(top);return finalMax;}// 递归求最大值出现次数static int countOccurrences(stack<int> &stack, int depth, int max) {if (depth == 0) return 0;int top = stack.top();stack.pop();int tempTimes = countOccurrences(stack, depth - 1, max);int finalTimes = tempTimes + (top == max ? 1 : 0);stack.push(top);return finalTimes;}static void moveToBottom(stack<int> &stack, int depth, int max, int k) {if (depth == 0) {// 把出现 k 次的最大值压入栈底for (int i = 0; i < k; i++) {stack.push(max);}} else {int top = stack.top();stack.pop();moveToBottom(stack, depth - 1, max, k);// 除了最大值,其他值按照原来的顺序入栈if (top != max) stack.push(top);}}static stack<int> randomStack(int n, int v) {stack<int> ans;for (int i = 0; i < n; i++)ans.push(rand() % v);return ans;}static bool isSorted(stack<int> &stack) {int pre = INT_MIN;while (!stack.empty()) {if (pre > stack.top()) return false;pre = stack.top();stack.pop();}return true;}static void test() {stack<int> test;test.push(1);test.push(5);test.push(4);test.push(5);test.push(3);test.push(2);test.push(3);test.push(1);test.push(4);test.push(2);sort(test);while (!test.empty()) {cout << test.top() << endl;test.pop();}// Random testint N = 20;int V = 20;int testTimes = 20000;cout << "Testing started" << endl;for (int i = 0; i < testTimes; i++) {int n = rand() % N;stack<int> stack = randomStack(n, V);sort(stack);if (!isSorted(stack)) {cout << "Error!" << endl;break;}}cout << "Testing ended" << endl;}
};int main() {Solution::test();return 0;
}

打印n层汉诺塔问题的最优移动轨迹

  • 时间复杂度 O(2^n)
  • 有三根杆子A,B,C。A杆上有 N 个 (N>1) 穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至 C 杆:
    1. 每次只能移动一个圆盘;
    2. 大盘不能叠在小盘上面。
  • n 层汉诺塔最少移动 2^n - 1 步
#include <iostream>
#include <stack>
#include <cstdlib>
#include <climits>
#include <string>using namespace std;class Solution {
public:static void hanoi(int n) {// 1. 把 n 层从 from 移动到 toif (n > 0) move(n, "左", "右", "中");}static void move(int i, const string &from, const string &to, const string &other) {if (i == 1) {cout << "移动圆盘 1 从 " << from << " 到 " << to << endl;} else {// 2. 先把 n - 1 层从 from 移动到 othermove(i - 1, from, other, to);// 3. 再把第 n 层的圆盘从 from 移动到最终目标 to 上,此时原来上面的 n - 1 层还在 other 上cout << "移动圆盘 " << i << " 从 " << from << " 到 " << to << endl;// 4. 把着 n - 1 层从 other 移到最终目标 to 上move(i - 1, other, to, from);}}
};int main() {int n = 3;Solution::hanoi(n);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/808732.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PM的正交调解法

1.PM的模拟调制过程 ​ PM信号是一种相位调制信号,其携带的信息保存在其信号的相位中,通过改变载波的相位来实现基带数据的传输。 其函数表达式如下: \[s(t) = A*cos(w_c*t + K_f*m(t)) \]其中: \(A\):表示载波幅度。 \(m(t)\):表示基带信号。 \(w_c\):表示载波信号角度增量…

监控室值班人员脱岗睡岗识别系统

监控室值班人员脱岗睡岗识别系统基于Yolov7深度学习神经网络算法,监控室值班人员脱岗睡岗识别系统可以7*24小时不间断自动监控总监控室值班人员是否在工位上(脱岗睡岗玩手机),若值班人员没有在总监控室系统则立即抓拍告警,算法鲁棒性强。监控室值班人员脱岗睡岗识别系统检…

监控室值班人员脱岗识别系统

监控室值班人员脱岗识别系统通过AI视觉技术,监控室值班人员脱岗识别系统实现企业总监控室值班人员脱岗、睡岗、玩手机等场景的AI识别,不需人为干预全天候自动识别。监控室值班人员脱岗识别系统通过建设视频监控智能识别功能赋能企业端,监控室值班人员脱岗识别系统对各个生产…

非煤矿山电子封条

非煤矿山电子封条 支持对接甘肃省矿山监管平台通过AI智能分析技术,非煤矿山电子封条可以实现矿山与甘肃省应急管理部门互联互通,非煤矿山电子封条 支持对接甘肃省矿山监管平台通过在关键地点(关键位置包括矿山主副井口、风井口、车辆出入口和调度室)安装摄像机,非煤矿山电…

甘肃省非煤矿山电子封条建设方案

甘肃省非煤矿山电子封条建设方案通过AI视频智能分析技术,甘肃省非煤矿山电子封条建设方案利用在煤矿风井口、煤矿调度室、煤矿主副井口、矿区车辆出入口重要位置安装摄像机,甘肃省非煤矿山电子封条建设方案对非煤矿区生产作业状态以及矿井出井入井人员数量变化、监控室人员睡…

校园食堂明厨亮灶监控分析系统

校园食堂明厨亮灶监控分析系统通过yolov5网络模型技术,校园食堂明厨亮灶监控分析系统针对校园餐厅后厨厨师穿戴及行为进行7*24小时不间断实时分析预警,如:不按要求戴口罩、不穿厨师帽、陌生人员进入后厨、厨师不穿厨师服、上班时间玩手机、老鼠识别等行为。校园食堂明厨亮灶…

工服智能监测预警系统

工服智能监测预警系统通过yolov8网络模型算法,工服智能监测预警系统对摄像机画面内出现的人员着装穿戴实时监测,当检测到现场人员未按要求穿戴工服工装则输出报警信息,通知后台值班人员及时处理。工服智能监测预警系统无需新增硬件,利用最新的深度学习技术,基于现场监控视…

ai皮带跑偏撕裂监测系统功能

ai皮带跑偏撕裂监测系统功能基于人工智能视觉技术,ai皮带跑偏撕裂监测系统功能自动识别现场监控画面中传送皮带撕裂、跑偏、偏移等情况,当ai皮带跑偏撕裂监测系统功能检测出皮带出现撕裂或者跑偏现象后,立即告警抓拍存档同步回传后台提醒及时处理,提高煤矿的智能化水平和人…

订单交易平台一

简介:订单交易平台核心的功能模块:认证模块,用户名密码 或 手机短信登录(60s有效)。角色管理,不同角色具有不同权限 和 展示不同菜单。 管理员,充值客户,下单客户管理,除了基本的增删改查以外,支持对客户可以分级,不同级别后续下单折扣不同。交易中心管理员可以给客…

学习“基于VirtualBox虚拟机安装Ubuntu图文教程”

安装过程中出现了很多问题第一次安装没有下载ubtun20.04 镜像 第二次安装不知道如何下载bzip2,导致一直安装增强功能 第三次在上B站搜索视频讲解后成功安装好了

wintools premium 24.10.1 注册分析

wintools premium 24.10.1 目录wintools premium 24.10.1NAGSCREEN_proc_472350check_472BF0pyps win32 程序,程序注册逻辑简单,关注窗口事件、控件id可快速定位到关键逻辑。 NAGSCREEN_proc_472350 INT_PTR __thiscall NAGSCREEN_proc_472350(void *this, HWND hDlg, UINT a…

项目部署二:服务器和环境配置

2.服务和环境配置 下面的配置和操作均在腾讯云服务器+CentOS 7.5的系统下进行。 2.1 MySQL安装服务端 yum install mariadb-server -y mariadb-server.x86_64 1:5.5.68-1.el7安装客户端 yum install mariadb -y 软件包 1:mariadb-5.5.68-1.el7.x86_64 已安装并且是最新版本服务…