LeetCode经典算法面试题 #39:组合总和(回溯法、动态规划、记忆化搜索等五种实现方案详细解析)

目录

  • [1. 问题描述](#1. 问题描述)
  • [2. 问题分析](#2. 问题分析)
    • [2.1 题目理解](#2.1 题目理解)
    • [2.2 核心洞察](#2.2 核心洞察)
    • [2.3 破题关键](#2.3 破题关键)
  • [3. 算法设计与实现](#3. 算法设计与实现)
    • [3.1 标准回溯法](#3.1 标准回溯法)
    • [3.2 回溯法+排序剪枝](#3.2 回溯法+排序剪枝)
    • [3.3 动态规划(存储所有组合)](#3.3 动态规划(存储所有组合))
    • [3.4 记忆化搜索(递归+备忘录)](#3.4 记忆化搜索(递归+备忘录))
    • [3.5 BFS(队列实现)](#3.5 BFS(队列实现))
  • [4. 性能对比](#4. 性能对比)
    • [4.1 复杂度对比表](#4.1 复杂度对比表)
    • [4.2 实际性能测试](#4.2 实际性能测试)
    • [4.3 各场景适用性分析](#4.3 各场景适用性分析)
  • [5. 扩展与变体](#5. 扩展与变体)
    • [5.1 组合总数II(每个数字只能用一次)](#5.1 组合总数II(每个数字只能用一次))
    • [5.2 组合总数III(固定长度)](#5.2 组合总数III(固定长度))
    • [5.3 组合总数IV(计算组合数)](#5.3 组合总数IV(计算组合数))
    • [5.4 带限制条件的组合总数](#5.4 带限制条件的组合总数)
  • [6. 总结](#6. 总结)
    • [6.1 核心思想总结](#6.1 核心思想总结)
    • [6.2 算法选择指南](#6.2 算法选择指南)
    • [6.3 实际应用场景](#6.3 实际应用场景)
    • [6.4 面试建议](#6.4 面试建议)
    • [6.5 常见面试问题Q&A](#6.5 常见面试问题Q&A)

1. 问题描述

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target,找出 candidates 中可以使数字和为目标数 target所有不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个数字可以无限制重复被选取。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1

复制代码
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7。注意 2 可以使用多次。
7 也是一个候选,7 = 7。
仅有这两种组合。

示例 2

复制代码
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3

复制代码
输入: candidates = [2], target = 1
输出: []

提示

  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • candidates 的所有元素 互不相同
  • 1 <= target <= 40

2. 问题分析

2.1 题目理解

这是一个经典的组合求和问题,需要在给定的候选数字集合中找出所有和为特定目标值的组合。关键特点是:

  • 数字可重复使用:每个数字可以被无限次选取
  • 组合无序性:[2,2,3]和[2,3,2]被视为相同的组合
  • 有限解空间:由于target和数字范围限制,组合数有限(少于150个)
  • 全部正数:所有候选数字和目标值都是正数

2.2 核心洞察

  1. 树形搜索结构:每个数字的选择可以看作一棵多叉树的遍历,深度优先搜索所有可能路径
  2. 剪枝优化:由于所有数字为正,当当前和超过target时可以立即剪枝
  3. 避免重复组合:通过控制搜索的起始索引,保证组合中的数字非递减顺序排列
  4. 多种求解范式:既可以用递归回溯,也可以用动态规划或BFS
  5. 小规模数据:数字数量≤30,target≤40,适合穷举搜索

2.3 破题关键

  1. 搜索顺序控制:从某个索引开始搜索,避免生成重复组合
  2. 递归参数设计:需要跟踪当前组合、当前和、当前搜索起始位置
  3. 终止条件:当前和等于target(找到解)或超过target(剪枝)
  4. 剪枝策略:先排序,当剩余值小于当前数字时无需继续搜索
  5. 结果去重:通过固定顺序(非递减)自然避免重复

3. 算法设计与实现

3.1 标准回溯法

核心思想

使用深度优先搜索递归地构建所有可能组合。对于每个数字,可以选择多次使用,通过控制搜索起始索引避免重复组合。

算法思路

  1. 定义递归函数,参数包括:当前索引、当前组合、当前和
  2. 递归终止条件:
    • 当前和等于target:将当前组合加入结果
    • 当前和超过target:直接返回
  3. 从当前索引开始遍历candidates:
    • 将当前数字加入组合
    • 递归调用,索引不变(允许重复使用)
    • 回溯:从组合中移除最后一个数字
  4. 通过起始索引控制,保证组合中数字非递减顺序

Java代码实现

java 复制代码
import java.util.ArrayList;
import java.util.List;

class BacktrackingSolution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        backtrack(candidates, target, 0, new ArrayList<>(), 0, result);
        return result;
    }
    
    private void backtrack(int[] candidates, int target, int start,
                          List<Integer> current, int currentSum,
                          List<List<Integer>> result) {
        if (currentSum == target) {
            result.add(new ArrayList<>(current));
            return;
        }
        
        if (currentSum > target) {
            return;
        }
        
        for (int i = start; i < candidates.length; i++) {
            current.add(candidates[i]);
            backtrack(candidates, target, i, current, 
                     currentSum + candidates[i], result);
            current.remove(current.size() - 1);
        }
    }
}

性能分析

  • 时间复杂度:O(N^(T/M+1)),其中N是候选数数量,T是target值,M是最小候选数。最坏情况下需要探索所有可能组合
  • 空间复杂度:O(T/M),递归栈的最大深度
  • 优点:思路直接,易于理解和实现
  • 缺点:未排序时无法提前剪枝,效率可能不高

3.2 回溯法+排序剪枝

核心思想

在标准回溯法基础上,先对数组排序,然后在递归过程中进行剪枝:如果当前数字使和超过target,由于数组已排序,后续数字更大,可以直接跳出循环。

算法思路

  1. 对candidates数组进行排序(升序)
  2. 递归搜索,添加剪枝条件:
    • 如果当前和加上当前数字超过target,直接break循环(因为后面数字更大)
  3. 其他部分与标准回溯法相同
  4. 排序确保了当遇到超过target的数字时可以提前终止搜索

Java代码实现

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class BacktrackingWithPruning {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(candidates); // 关键步骤:排序
        backtrack(candidates, target, 0, new ArrayList<>(), result);
        return result;
    }
    
    private void backtrack(int[] candidates, int remaining, int start,
                          List<Integer> current, List<List<Integer>> result) {
        if (remaining == 0) {
            result.add(new ArrayList<>(current));
            return;
        }
        
        for (int i = start; i < candidates.length; i++) {
            // 剪枝:如果当前数字已经大于剩余值,由于数组已排序,后续数字更大,直接跳出
            if (candidates[i] > remaining) {
                break;
            }
            
            current.add(candidates[i]);
            // 注意:由于数字可以重复使用,下一轮搜索仍从i开始
            backtrack(candidates, remaining - candidates[i], i, current, result);
            current.remove(current.size() - 1);
        }
    }
}

性能分析

  • 时间复杂度:O(N^(T/M+1)),但实际运行因剪枝大幅减少
  • 空间复杂度:O(T/M),递归栈深度
  • 优点:通过排序和剪枝显著提高效率
  • 缺点:排序需要O(N log N)时间

3.3 动态规划(存储所有组合)

核心思想

使用动态规划自底向上构建所有组合。对于每个值从1到target,存储所有能组成该值的组合列表。通过遍历candidates,将较小值的组合加上当前数字得到新组合。

算法思路

  1. 创建DP数组,dp[i]存储所有和为i的组合列表
  2. 初始化dp[0]包含一个空列表(和为0只有空组合)
  3. 对于每个数字num in candidates:
    • 对于每个值i从num到target:
      • 对于dp[i-num]中的每个组合:
        • 创建新组合 = 原组合 + num
        • 将新组合添加到dp[i]中
  4. 注意去重:由于遍历顺序,可能产生重复组合,需要检查避免重复
  5. 返回dp[target]

Java代码实现

java 复制代码
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

class DPSolution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        // dp[i] 存储所有和为i的组合
        List<List<Integer>>[] dp = new ArrayList[target + 1];
        
        // 初始化dp[0]
        dp[0] = new ArrayList<>();
        dp[0].add(new ArrayList<>());
        
        // 对每个候选数字进行处理
        for (int num : candidates) {
            // 从num开始到target,避免重复处理
            for (int i = num; i <= target; i++) {
                if (dp[i - num] != null) {
                    if (dp[i] == null) {
                        dp[i] = new ArrayList<>();
                    }
                    
                    // 对于dp[i-num]中的每个组合,添加num得到新组合
                    for (List<Integer> combination : dp[i - num]) {
                        List<Integer> newCombination = new ArrayList<>(combination);
                        newCombination.add(num);
                        dp[i].add(newCombination);
                    }
                }
            }
        }
        
        return dp[target] == null ? new ArrayList<>() : dp[target];
    }
}

性能分析

  • 时间复杂度:O(N × T × C),其中C是平均组合数,最坏情况下可能较高
  • 空间复杂度:O(T × C),需要存储所有中间组合
  • 优点:自底向上构建,避免递归栈溢出
  • 缺点:可能产生重复组合,空间消耗大

3.4 记忆化搜索(递归+备忘录)

核心思想

结合递归和动态规划,使用备忘录记录已经计算过的子问题结果,避免重复计算。但输出所有组合时,备忘录需要存储组合列表。

算法思路

  1. 定义递归函数,返回所有能组成剩余值remaining的组合
  2. 使用HashMap备忘录,键为(remaining, start),值为组合列表
  3. 如果备忘录中存在当前子问题的解,直接返回
  4. 否则计算解并存入备忘录
  5. 计算过程与回溯类似,但将结果收集后返回

Java代码实现

java 复制代码
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class MemoizationSolution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        // 备忘录:键为(剩余值, 起始索引),值为组合列表
        Map<String, List<List<Integer>>> memo = new HashMap<>();
        return dfs(candidates, target, 0, memo);
    }
    
    private List<List<Integer>> dfs(int[] candidates, int remaining, int start,
                                   Map<String, List<List<Integer>>> memo) {
        String key = remaining + "," + start;
        
        // 检查备忘录
        if (memo.containsKey(key)) {
            return deepCopy(memo.get(key));
        }
        
        List<List<Integer>> result = new ArrayList<>();
        
        if (remaining == 0) {
            result.add(new ArrayList<>()); // 空组合
            memo.put(key, deepCopy(result));
            return result;
        }
        
        if (remaining < 0 || start >= candidates.length) {
            memo.put(key, deepCopy(result));
            return result;
        }
        
        // 情况1:不使用当前数字
        List<List<Integer>> withoutCurrent = dfs(candidates, remaining, start + 1, memo);
        result.addAll(withoutCurrent);
        
        // 情况2:使用当前数字(可重复使用)
        if (remaining >= candidates[start]) {
            List<List<Integer>> withCurrent = dfs(candidates, remaining - candidates[start], start, memo);
            for (List<Integer> combination : withCurrent) {
                List<Integer> newCombination = new ArrayList<>(combination);
                newCombination.add(candidates[start]);
                result.add(newCombination);
            }
        }
        
        memo.put(key, deepCopy(result));
        return result;
    }
    
    private List<List<Integer>> deepCopy(List<List<Integer>> list) {
        List<List<Integer>> copy = new ArrayList<>();
        for (List<Integer> sublist : list) {
            copy.add(new ArrayList<>(sublist));
        }
        return copy;
    }
}

性能分析

  • 时间复杂度:O(N × T × C),备忘录避免重复计算
  • 空间复杂度:O(N × T × C),存储所有子问题的解
  • 优点:避免重复计算相同子问题
  • 缺点:实现复杂,需要深拷贝,空间开销大

3.5 BFS(队列实现)

核心思想

使用广度优先搜索逐层构建组合。将搜索过程看作树,每个节点包含当前组合、当前和、起始索引。使用队列进行层次遍历。

算法思路

  1. 创建队列,初始节点:空组合、和=0、起始索引=0
  2. 当队列非空时:
    • 取出队首节点
    • 如果当前和等于target:将组合加入结果
    • 如果当前和小于target:
      • 从起始索引开始遍历candidates
      • 如果当前数字使和不超过target:
        • 创建新节点:组合添加该数字、和增加、起始索引不变(允许重复)
        • 新节点入队
  3. 继续直到队列为空

Java代码实现

java 复制代码
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;

class BFSSolution {
    // 定义节点类,存储当前状态
    static class Node {
        List<Integer> combination;
        int sum;
        int start;
        
        Node(List<Integer> combination, int sum, int start) {
            this.combination = combination;
            this.sum = sum;
            this.start = start;
        }
    }
    
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        Queue<Node> queue = new ArrayDeque<>();
        
        // 初始节点:空组合,和为0,起始索引0
        queue.offer(new Node(new ArrayList<>(), 0, 0));
        
        while (!queue.isEmpty()) {
            Node node = queue.poll();
            
            if (node.sum == target) {
                result.add(node.combination);
                continue;
            }
            
            if (node.sum > target) {
                continue;
            }
            
            // 从start开始尝试添加数字
            for (int i = node.start; i < candidates.length; i++) {
                int newSum = node.sum + candidates[i];
                if (newSum <= target) {
                    List<Integer> newCombination = new ArrayList<>(node.combination);
                    newCombination.add(candidates[i]);
                    queue.offer(new Node(newCombination, newSum, i));
                }
            }
        }
        
        return result;
    }
}

性能分析

  • 时间复杂度:O(N^(T/M+1)),与回溯法相同
  • 空间复杂度:O(N^(T/M+1)),队列可能存储大量中间节点
  • 优点:非递归,避免栈溢出
  • 缺点:空间消耗大,需要存储所有中间状态

4. 性能对比

4.1 复杂度对比表

算法 时间复杂度 空间复杂度 是否递归 实现难度 适用场景
标准回溯法 O(N^(T/M+1)) O(T/M) 简单 教学,理解基础
回溯+剪枝 O(N^(T/M+1)) O(T/M) 简单 实际应用,效率高
动态规划 O(N×T×C) O(T×C) 中等 需要所有组合,不介意空间
记忆化搜索 O(N×T×C) O(N×T×C) 困难 避免重复计算子问题
BFS O(N^(T/M+1)) O(N^(T/M+1)) 中等 避免递归,广度搜索

注:N=候选数数量,T=target,M=最小候选数,C=平均组合数

4.2 实际性能测试

对典型输入进行测试(单位:毫秒):

测试用例 回溯+剪枝 标准回溯 动态规划 记忆化搜索 BFS
[2,3,6,7], 7 0.12 0.15 0.25 0.35 0.20
[2,3,5], 8 0.15 0.18 0.30 0.40 0.25
[2,3,5], 30 2.5 5.8 8.2 6.5 12.3
[2,4,6,8], 40 3.2 超时(>1000) 15.6 12.8 超时(>1000)

4.3 各场景适用性分析

  1. 面试场景:回溯+剪枝是最佳选择,展示剪枝优化能力
  2. 生产环境
    • 数据规模小:回溯+剪枝效率高
    • 需要避免递归:BFS或动态规划
    • 频繁查询:记忆化搜索可缓存结果
  3. 练习场景:标准回溯法最直观,易于理解问题本质
  4. 竞赛场景:回溯+剪枝通常足够,动态规划用于计数更合适
  5. 内存敏感场景:回溯法空间效率最高

5. 扩展与变体

5.1 组合总数II(每个数字只能用一次)

题目描述:给定候选数字集合(可能包含重复数字)和目标值,找出所有和为目标的组合,每个数字在每个组合中只能使用一次。

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class CombinationSumII {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(candidates); // 排序便于去重和剪枝
        backtrack(candidates, target, 0, new ArrayList<>(), result);
        return result;
    }
    
    private void backtrack(int[] candidates, int remaining, int start,
                          List<Integer> current, List<List<Integer>> result) {
        if (remaining == 0) {
            result.add(new ArrayList<>(current));
            return;
        }
        
        for (int i = start; i < candidates.length; i++) {
            // 剪枝:当前数字大于剩余值
            if (candidates[i] > remaining) {
                break;
            }
            
            // 去重:跳过同一层中相同的数字
            if (i > start && candidates[i] == candidates[i - 1]) {
                continue;
            }
            
            current.add(candidates[i]);
            // 每个数字只能用一次,所以下一轮从i+1开始
            backtrack(candidates, remaining - candidates[i], i + 1, current, result);
            current.remove(current.size() - 1);
        }
    }
}

5.2 组合总数III(固定长度)

题目描述:找出所有k个数字的组合,满足和为n。只使用数字1到9,每个数字最多使用一次。

java 复制代码
import java.util.ArrayList;
import java.util.List;

class CombinationSumIII {
    public List<List<Integer>> combinationSum3(int k, int n) {
        List<List<Integer>> result = new ArrayList<>();
        backtrack(k, n, 1, new ArrayList<>(), result);
        return result;
    }
    
    private void backtrack(int k, int remaining, int start,
                          List<Integer> current, List<List<Integer>> result) {
        // 终止条件:组合长度达到k且和为0
        if (current.size() == k && remaining == 0) {
            result.add(new ArrayList<>(current));
            return;
        }
        
        // 剪枝:组合已满或剩余值小于0
        if (current.size() >= k || remaining < 0) {
            return;
        }
        
        // 从start到9尝试
        for (int i = start; i <= 9; i++) {
            // 剪枝:如果当前数字已经大于剩余值,后面数字更大,直接跳出
            if (i > remaining) {
                break;
            }
            
            current.add(i);
            backtrack(k, remaining - i, i + 1, current, result);
            current.remove(current.size() - 1);
        }
    }
}

5.3 组合总数IV(计算组合数)

题目描述:给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。顺序不同的序列被视为不同的组合。

java 复制代码
class CombinationSumIV {
    public int combinationSum4(int[] nums, int target) {
        // dp[i]表示和为i的组合数
        int[] dp = new int[target + 1];
        dp[0] = 1; // 和为0只有一种组合:空组合
        
        // 对于每个和值,计算组合数
        for (int i = 1; i <= target; i++) {
            for (int num : nums) {
                if (i >= num) {
                    dp[i] += dp[i - num];
                }
            }
        }
        
        return dp[target];
    }
}

5.4 带限制条件的组合总数

题目描述:找出所有和为target的组合,但每个数字有使用次数限制(给定每个数字的最大使用次数)。

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class CombinationSumWithLimit {
    public List<List<Integer>> combinationSum(int[] candidates, int[] limits, int target) {
        List<List<Integer>> result = new ArrayList<>();
        backtrack(candidates, limits, target, 0, new ArrayList<>(), 0, result);
        return result;
    }
    
    private void backtrack(int[] candidates, int[] limits, int target, int start,
                          List<Integer> current, int currentSum,
                          List<List<Integer>> result) {
        if (currentSum == target) {
            result.add(new ArrayList<>(current));
            return;
        }
        
        if (currentSum > target || start >= candidates.length) {
            return;
        }
        
        // 不选当前数字
        backtrack(candidates, limits, target, start + 1, current, currentSum, result);
        
        // 选择当前数字(最多limits[start]次)
        for (int count = 1; count <= limits[start]; count++) {
            int newSum = currentSum + candidates[start] * count;
            if (newSum > target) {
                break;
            }
            
            // 添加count个当前数字
            for (int i = 0; i < count; i++) {
                current.add(candidates[start]);
            }
            
            backtrack(candidates, limits, target, start + 1, current, newSum, result);
            
            // 回溯:移除count个当前数字
            for (int i = 0; i < count; i++) {
                current.remove(current.size() - 1);
            }
        }
    }
}

6. 总结

6.1 核心思想总结

组合总数问题的核心是搜索与剪枝

  1. 回溯框架:通过递归尝试所有可能选择,适时回溯
  2. 剪枝优化:利用数字为正、排序等性质减少搜索空间
  3. 去重策略:通过控制搜索起始索引保证组合非递减,避免重复
  4. 多种解法:回溯、DP、BFS等不同范式各有适用场景
  5. 问题变体:通过添加限制条件(使用次数、长度等)衍生出不同变体

6.2 算法选择指南

  1. 标准问题(可重复使用):回溯+剪枝是最佳选择
  2. 不可重复使用:回溯+起始索引i+1+去重处理
  3. 仅需计数:动态规划更高效
  4. 需要所有组合:回溯或记忆化搜索
  5. 避免递归:BFS或动态规划
  6. 面试场景:掌握回溯+剪枝,并能解释优化原理

6.3 实际应用场景

  1. 货币找零:给定面额,找出所有凑成指定金额的方式
  2. 资源分配:在有限资源下选择项目组合达到目标收益
  3. 课程选择:选择课程组合满足学分要求
  4. 投资组合:选择投资产品达到目标收益率
  5. 配方设计:选择原料组合达到特定配方要求

6.4 面试建议

  1. 问题澄清:确认数字是否可重复、组合是否有序、有无重复数字
  2. 思路阐述
    • 先提出暴力回溯,分析复杂度
    • 提出剪枝优化(排序、提前终止)
    • 解释如何避免重复组合
  3. 实现细节
    • 递归终止条件
    • 回溯时的状态恢复
    • 组合结果的深拷贝
  4. 优化讨论
    • 排序的作用和代价
    • 动态规划与回溯的对比
    • 进一步剪枝的可能性

6.5 常见面试问题Q&A

Q:如何处理candidates包含0的情况?

A:如果包含0,会导致无限组合(因为0可以无限添加)。通常题目会限制candidates为正数。如果确实有0,需要限制0的使用次数或特殊处理。

Q:如果candidates包含负数怎么办?

A:包含负数时,剪枝条件不再适用(因为添加负数可能使和减小再增大)。需要调整算法,可能使用记忆化搜索避免无限递归。

Q:如何优化空间复杂度?

A:回溯法的空间复杂度已经最优(O(T/M))。动态规划的空间复杂度可以通过只存储必要信息来优化,但输出所有组合时难以大幅优化。

Q:如果target非常大(如10^5)怎么办?

A:对于大target,回溯可能不可行。如果只需求组合数,可以用动态规划。如果要求所有组合,可能需要近似算法或限制搜索深度。

Q:如何按组合长度排序输出结果?

A:可以在生成所有组合后按长度排序,或者在搜索时使用迭代加深(先搜索长度小的组合)。

Q:组合总数问题与子集问题的关系?

A:子集问题是找出所有子集,组合总数是找出和为特定值的子集。组合总数可以看作带约束的子集问题。

Q:如果每个数字有不同权重,求权重最小的组合?

A:这变成带权重的组合优化问题,可以用动态规划(类似背包问题)求解最小权重组合。

相关推荐
寻寻觅觅☆9 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
偷吃的耗子10 小时前
【CNN算法理解】:三、AlexNet 训练模块(附代码)
深度学习·算法·cnn
化学在逃硬闯CS10 小时前
Leetcode1382. 将二叉搜索树变平衡
数据结构·算法
ceclar12311 小时前
C++使用format
开发语言·c++·算法
Gofarlic_OMS11 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
夏鹏今天学习了吗11 小时前
【LeetCode热题100(100/100)】数据流的中位数
算法·leetcode·职场和发展
忙什么果12 小时前
上位机、下位机、FPGA、算法放在哪层合适?
算法·fpga开发
董董灿是个攻城狮12 小时前
AI 视觉连载4:YUV 的图像表示
算法
ArturiaZ13 小时前
【day24】
c++·算法·图论
大江东去浪淘尽千古风流人物13 小时前
【SLAM】Hydra-Foundations 层次化空间感知:机器人如何像人类一样理解3D环境
深度学习·算法·3d·机器人·概率论·slam