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

从回溯到记忆化搜索再到递推

从回溯到记忆化搜索再到递推

一、回溯(递归暴力搜索)

回溯就是尝试所有可能的选择

遇到一个问题时,递归地做选择,直到到达终点,然后回到上一步换一种选择继续试。

  • 特点:不管有没有用过之前的计算结果,每次都重新算一遍。
  • 优点:简单,直观。
  • 缺点:效率极低,重复计算,指数级爆炸。

形象理解:在一棵决策树上,暴力走遍所有路径。


二、记忆化搜索(递归 + 记忆)

记忆化搜索是在回溯的基础上优化的。

就是在递归的时候,把算过的子问题结果记下来,下次遇到同样的子问题就直接用,不用再算。

  • 特点:还是递归,但是每个子问题只算一次。
  • 优点:大量避免重复计算,时间复杂度从指数级降到多项式级。
  • 核心思想:记录状态剪枝

形象理解:在走决策树的过程中,遇到同一棵子树,直接抄答案。


三、递推(动态规划,自底向上)

递推是去掉递归的做法。

明确每一个子问题的定义(也叫做状态),

然后根据小的状态一步步算出大的状态,按一定顺序推进。

  • 特点:不需要函数调用栈,节省开销。
  • 优点:效率最高,容易进行空间优化。
  • 核心思想:找到状态转移关系控制计算顺序

形象理解:像搭积木一样,一块块从底部搭上去。

题目1:打家劫舍(House Robber)

  1. 递归(回溯)

    思路:对第 i 间房可“偷”或“不偷”,暴力枚举所有方案,时间复杂度 O(2^n)。

    int rob(int[] nums, int i) {if (i >= nums.length) return 0;return Math.max(nums[i] + rob(nums, i + 2), rob(nums, i + 1));
    }
  2. 记忆化搜索

    在递归基础上用 memo[i] 缓存已算结果,避免重复调用,时间复杂度降为 O(n)。

    int[] memo;
    int rob(int[] nums, int i) {if (i >= nums.length) return 0;if (memo[i] != -1) return memo[i];int res = Math.max(nums[i] + rob(nums, i + 2), rob(nums, i + 1));return memo[i] = res;
    }
  3. 自底向上 DP

    定义 dp[i] 为从第 i 间房到末尾能偷到的最大金额,则:

    dp[n] = dp[n+1] = 0
    for (i = n-1; i >= 0; i--) {dp[i] = Math.max(nums[i] + dp[i+2], dp[i+1]);
    }

    最终返回 dp[0]


题目2:目标和(Target Sum)

  1. 递归(回溯)

    对每个元素选择“+”或“-”,暴力枚举,时间 O(2^n)。

    int findTargetSumWays(int[] nums, int target) {return dfs(nums, 0, 0, target);
    }
    int dfs(int[] nums, int i, int sum, int target) {if (i == nums.length) return sum == target ? 1 : 0;return dfs(nums, i + 1, sum + nums[i], target)+ dfs(nums, i + 1, sum - nums[i], target);
    }
  2. 记忆化搜索

    缓存 (i, sum) 状态到 Map 或二维数组,避免重复,复杂度 O(n·sum)。

    Map<String,Integer> memo;
    int dfs(int[] nums, int i, int sum, int target) {String key = i + "," + sum;if (i == nums.length) return sum == target ? 1 : 0;if (memo.containsKey(key)) return memo.get(key);int ways = dfs(nums, i + 1, sum + nums[i], target)+ dfs(nums, i + 1, sum - nums[i], target);memo.put(key, ways);return ways;
    }
  3. 自底向上 DP(子集和转化)

    令 P = (sum(nums) + target) / 2,统计凑成 P 的子集方案数,定义 dp[j] 为和为 j 的方案数:

    
    dp[0] = 1
    for (int num : nums) {for (int j = P; j >= num; j--) {dp[j] += dp[j - num];}
    }

    最后返回 dp[P]


题目3:零钱兑换 II(Coin Change II)

  1. 递归(回溯)

    枚举每种面额取与不取,时间复杂度指数级。

    int change(int amount, int[] coins) {return dfs(coins, amount, 0);
    }
    int dfs(int[] coins, int rem, int i) {if (rem == 0) return 1;if (i == coins.length || rem < 0) return 0;return dfs(coins, rem - coins[i], i)+ dfs(coins, rem, i + 1);
    }
  2. 记忆化搜索

    memo[i][rem] 缓存子问题,时间降为 O(n·amount)。

    int[][] memo;
    int dfs(int[] coins, int rem, int i) {if (rem == 0) return 1;if (i == coins.length || rem < 0) return 0;if (memo[i][rem] != -1) return memo[i][rem];int ans = dfs(coins, rem - coins[i], i)+ dfs(coins, rem, i + 1);return memo[i][rem] = ans;
    }
  3. 自底向上 DP

    定义 dp[j] 为凑成金额 j 的组合数:

    dp[0] = 1
    for (int coin : coins) {for (int j = coin; j <= amount; j++) {dp[j] += dp[j - coin];}
    }

    返回 dp[amount]


题目4:最长公共子序列(LCS)

  1. 递归(暴力)

    int lcs(String s1, String s2, int i, int j) {if (i == s1.length() || j == s2.length()) return 0;if (s1.charAt(i) == s2.charAt(j)) {return 1 + lcs(s1, s2, i + 1, j + 1);}return Math.max(lcs(s1, s2, i + 1, j),lcs(s1, s2, i, j + 1));
    }
  2. 记忆化搜索

    int[][] memo;
    int lcs(String s1, String s2, int i, int j) {if (i == s1.length() || j == s2.length()) return 0;if (memo[i][j] != -1) return memo[i][j];int res;if (s1.charAt(i) == s2.charAt(j)) {res = 1 + lcs(s1, s2, i + 1, j + 1);} else {res = Math.max(lcs(s1, s2, i + 1, j),lcs(s1, s2, i, j + 1));}return memo[i][j] = res;
    }
  3. 自底向上 DP

    for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if (s1.charAt(i) == s2.charAt(j)) {dp[i+1][j+1] = dp[i][j] + 1;} else {dp[i+1][j+1] = Math.max(dp[i][j+1], dp[i+1][j]);}}
    }

题目5:编辑距离(Edit Distance)

  1. 递归(暴力)

    int edit(String w1, String w2, int i, int j) {if (i == 0) return j;if (j == 0) return i;if (w1.charAt(i - 1) == w2.charAt(j - 1)) {return edit(w1, w2, i - 1, j - 1);}return 1 + Math.min(Math.min(edit(w1, w2, i - 1, j),   // 删除edit(w1, w2, i, j - 1)),  // 插入edit(w1, w2, i - 1, j - 1)         // 替换);
    }
  2. 记忆化搜索

    int[][] memo;
    int edit(String w1, String w2, int i, int j) {if (i == 0) return j;if (j == 0) return i;if (memo[i][j] != -1) return memo[i][j];int res;if (w1.charAt(i - 1) == w2.charAt(j - 1)) {res = edit(w1, w2, i - 1, j - 1);} else {res = 1 + Math.min(Math.min(edit(w1, w2, i - 1, j),edit(w1, w2, i, j - 1)),edit(w1, w2, i - 1, j - 1));}return memo[i][j] = res;
    }
  3. 自底向上 DP

    for (int i = 0; i <= m; i++) dp[i][0] = i;for (int j = 0; j <= n; j++) dp[0][j] = j;for (int i = 1; i <= m; i++) {for (int j = 1; j <= n; j++) {if (w1.charAt(i - 1) == w2.charAt(j - 1)) {dp[i][j] = dp[i - 1][j - 1];} else {dp[i][j] = 1 + Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]),dp[i - 1][j - 1]);}}
    }

题目6:最长递增子序列(LIS)

  1. 递归(暴力)

    
    int lis(int[] nums, int prev, int i) {if (i == nums.length) return 0;int taken = nums[i] > prev ? 1 + lis(nums, nums[i], i + 1) : 0;int notTaken = lis(nums, prev, i + 1);return Math.max(taken, notTaken);
    }
  2. 记忆化搜索

    
    int[] memo;
    int lis(int[] nums, int i) {if (memo[i] != -1) return memo[i];int max = 1;for (int j = 0; j < i; j++) {if (nums[j] < nums[i]) {max = Math.max(max, lis(nums, j) + 1);}}return memo[i] = max;
    }
  3. 自底向上 DP

    for (int i = 0; i < n; i++) {dp[i] = 1;for (int j = 0; j < i; j++) {if (nums[j] < nums[i]) {dp[i] = Math.max(dp[i], dp[j] + 1);}}
    }
方法特点效率难度
回溯全部暴力尝试,不记结果最低最低
记忆化搜索递归 + 记录,避免重复中等中等
递推(动态规划)明确状态,自底向上推进最高略高

相关文章:

  • 树莓派学习专题<11>:使用V4L2驱动获取摄像头数据--启动/停止数据流,数据捕获,缓存释放
  • Web前渗透
  • Win11 配置 Git 绑定 Github 账号的方法与问题汇总
  • flask返回文件的同时返回其他参数
  • 【密码学——基础理论与应用】李子臣编著 第七章 公钥密码 课后习题
  • ubuntu扩展逻辑卷并调整文件系统大小步骤
  • “RS232转Profinet,开启“变频器工业版绝绝子!”
  • Step1X-Edit: A practical framework for general image editing
  • 28-29【动手学深度学习】批量归一化 + ResNet
  • 《深入浅出Git:从版本控制原理到高效协作实战》​
  • uniapp-商城-42-shop 后台管理 分包
  • VRRP与BFD在冗余设计中的核心区别:从“备用网关”到“毫秒级故障检测”
  • [ACTF2020 新生赛]Upload
  • 大模型API密钥的环境变量配置(大模型API KEY管理)(将密钥存储在环境变量)(python-dotenv)(密钥管理)
  • 编译原理实验 之 Tiny C语言编译程序实验 语法分析
  • 李臻20242817_安全文件传输系统项目报告_第9周
  • w~嵌入式C语言~合集4
  • flask uri 怎么统一加前缀
  • 【Web API系列】深入解析 Web Service Worker 中的 WindowClient 接口:原理、实践与进阶应用
  • Spring框架的ObjectProvider用法
  • 上海市委常委会传达学习总书记重要讲话精神,研究张江科学城建设等事项
  • 加拿大警方:已确认有9人在温哥华驾车撞人事件中遇难
  • “爱泼斯坦案”关键证人弗吉尼亚·朱弗雷自杀身亡
  • 金正恩出席朝鲜人民军海军驱逐舰入水仪式
  • 内蒙古纪检干部刘占波履新呼和浩特,曾参与涉煤腐败倒查20年工作
  • 滁州一交通事故责任认定引质疑:民警和司法鉴定人被处罚,已中止诉讼