力扣-回溯法

本文探讨了两种类型的组合问题:只能使用一次的组合及其变体,包括去重和排序;以及所有组合问题,涉及不同难度的DFS算法应用。讨论了如何通过DFS和数据结构优化求解含重复元素和不重复元素的子集、组合及组合总和问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类型一: 只能用一次

只能使用一次就是要不要的问题

1.如果数组里面的数字只能用一次,就是要不要的问题。
2.如果数组中包含重复数字,需要先排序,然后使用set才能去除重复组合
3.针对重复数字,可将其存入map中,这样要不要的O(2^n)问题就变成了O(n+1)问题,对于重复数字比较多的情况下,可改善情况。

40 组合总数II(只能用一次+含多个重复元素,dfs+map+set)(要几个0-n)

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

注意:解集不能包含重复的组合。

class Solution {
    //dfs:这就是要这个数,不要这个数的问题
    //因为compute递归函数中的combination仍然是一个容器,所以要这个数的时候还是需要回退
    //数组中存在重复元素,存在1 2 5,2 1 5这种组合,虽然1来自于不同的地方,但是仍然算是重复组合,解决这个问题采用了对candidates排序,这样最终的到是1 2 5、1 2 5,set会认为它们是重复的List,只保留一个。
    //第二次修改:
    //使用上面的技巧在100个1,target为30的时候会出现超时,问题出在相同值的处理上//相同值的问题:要不要当前节点(O(2^n)),而要几个此节点(O(n+1)),因此将相同值的要不要问题转换为要几个的问题
    Set<List<Integer>> res = new HashSet<>();//保存不重复的结果
    Map<Integer,Integer> map;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<Integer> nums = new ArrayList<>();
        map = new HashMap<>();
        for(int candidate:candidates){
            if(!map.containsKey(candidate)){
                nums.add(candidate);
            }
            map.put(candidate,map.getOrDefault(candidate,0)+1);
        }
        compute(nums,0,0,target,new ArrayList<Integer>());
        List<List<Integer>> list = new ArrayList<>(res);
        return list;
    }
    //要这个数字与不要这个数字的问题了
    //num:当前值,index:当前下标,target:目标值,combination:当前组合
    public void compute( List<Integer> nums, int num, int index,int target,List<Integer> combination){
        if(num > target) {
            return;
        }else if(num == target){
            for(Integer i : combination){
                System.out.print(i+" ");
            }
            System.out.println("");
            res.add(new ArrayList<>(combination));
            return;
        }
        if(index == nums.size()){//走到头
            return;
        }
        int value = nums.get(index);
        int freq = map.get(value);//当前值的个数
        for(int i=0;i<=freq;i++){//要i个
            for(int j = 0;j<i;j++){//向组合中添加i个
                combination.add(value);
            }
            compute(nums,num+value*i,index+1,target,combination);
            for(int j = 0;j<i;j++){//删除i个
                combination.remove(combination.size()-1);
            }
        }
    }
}
78 子集(只使用一次+不含重复元素,dfs)(要不要)
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

 
class Solution {
    //简单的要不要问题
    //而且没有重复元素
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {

        dfs(nums,0,new ArrayDeque<>());

        return res;
    }
    public void dfs(int[] nums, int index, Deque<Integer> path){
        if(index == nums.length){
            res.add(new ArrayList<>(path));
            return;
        }
        dfs(nums,index+1,path);//要这个元素
        path.offerLast(nums[index]);
        dfs(nums,index+1,path);//不要这个元素
        path.pollLast();
    }

}
90 子集II (只使用一次+含重复元素,dfs+排序)(要不要)
class Solution {
    //要这个元素,不要这个元素
    //去重使用set
    //元素顺序不一样:先对nums排序
    Set<List<Integer>> res = new HashSet<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        dfs(nums,0,new ArrayDeque<>());
        List<List<Integer>> list = new ArrayList<>(res);
        return list;
    }
    public void dfs(int[] nums, int index, Deque<Integer> path){
        if(index == nums.length){
            res.add(new ArrayList<>(path));
            return;
        }
        dfs(nums,index+1,path);
        path.offerLast(nums[index]);
        dfs(nums,index+1,path);
        path.pollLast();
    }

}

类型二:所有组合问题

77. 组合(所有组合(包含只使用一次的意思),dfs循环添加后面的元素)

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案
class Solution {
    //不是要不要的问题
    //所有组合问题:
    //每次可将index之后的元素加入到组合中,需要一个for循环将i添加到index中
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        if(k<0 || k>n) return null;
        dfs(n, k, 1, new ArrayList<Integer>());
        return res;
    }
    public void dfs(int n,int k,int index, List<Integer> combination){
        if(combination.size() == k){
            res.add(new ArrayList(combination));
            return;
        }
        for(int i=index;i<=n;i++){//循环的将每一个元素添加到组合中
            combination.add(i);
            dfs(n,k,i+1,combination);
            combination.remove(combination.size()-1);
        }
    }
}
另一种写法:要不要的写法

class Solution {
    //所有组合问题:
    //每次可将index之后的元素加入到组合中,需要一个for循环将i添加到index中
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        if(k<0 || k>n) return null;
        //开始的时候index初始赋值为0,dfs(n, k, 0, new ArrayList<Integer>())运行解答错误发现多了[[0,4],[0,3],[0,2],[0,1]],开头为0的组合,修改为1
        dfs(n, k, 1, new ArrayList<Integer>());
        return res;
    }
    //n:数组元素,k:目标值,num:当前值,index:数组索引,combination:当前组合数
    public void dfs(int n,int k,int index, List<Integer> combination){
        if(combination.size() == k){
            res.add(new ArrayList(combination));
            return;
        }
        if(index > n ) return;//数组界定
        
        //不要
        dfs(n,k,index+1,combination);
        //要
        combination.add(index);
        dfs(n,k,index+1,combination);
        combination.remove(combination.size()-1);
        // for(int i=index;i<=n;i++){//循环的将每一个元素添加到组合中
        //     combination.add(i);
        //     dfs(n,k,i+1,combination);
        //     combination.remove(combination.size()-1);
        // }
    }
}

216 组合总和II(只使用一次+所有组合+无重复元素,dfs循环添加后面的元素+剪枝)

也可以转换成要不要的问题,所有组合也是要不要的问题啊,那77可以转换吗

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

所有数字都是正整数。
解集不能包含重复的组合。 
class Solution {
    //不含重复元素的要不要问题+剪枝
    //额外的判断条件:当前组合中的值与target是否相等,大于target时,实行剪枝操作
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combinationSum3(int k, int n) {
        dfs(k,n,0,1,new ArrayDeque<>());
        return res;
    }
    public void dfs(int k, int n, int num, int index, Deque<Integer> path){//从1-9中选择k个数,相加之和为n,当前值为num,当前要走index
        if(num > n){//路径path的长度还小于k,num就已经大于n了,则返回
            return;
        }
        if(path.size() == k){//当前的路径等于k,返回
            if(num == n){
                res.add(new ArrayList<>(path));
            }
            return;
        }
        if(index == 10) return;
        //分两种情况:添加、不添加
        dfs(k,n,num,index+1,path);//不添加,//这次选择了i,下次应该从i+1开始,所以index=i+1
        path.offerLast(index);
        dfs(k,n,num+index,index+1,path);//这次选择了i,下次应该从i+1开始,所以index=i+1
        path.pollLast() ;
        // for(int i = index;i<=9;i++){//循环添加index后面的元素到路径中,分两种情况:添加、不添加
        //     dfs(k,n,num,i+1,path);//不添加,//这次选择了i,下次应该从i+1开始,所以index=i+1
        //     path.offerLast(i);
        //     dfs(k,n,num+i,i+1,path);//这次选择了i,下次应该从i+1开始,所以index=i+1
        //     path.pollLast();
        // }
    }
}
### LeetCode C++ 解题方案与示例代码 #### LRU缓存问题的C++实现 LRU(Least Recently Used)缓存是一种常见的数据结构设计问题,在LeetCode上也经常作为面试考察的重点之一。该问题的核心在于维护一个固定容量的数据存储空间,当达到容量上限时自动淘汰最久未使用的项。 下面展示了一个基于双向链表和哈希映射的高效C++实现方法: ```cpp #include <unordered_map> #include <list> class LRUCache { public: LRUCache(int capacity) : cap(capacity) {} int get(int key) { auto it = cache.find(key); if (it == cache.end()) return -1; // 将访问过的节点移到前面 lru.splice(lru.begin(), lru, it->second); return it->second->second; } void put(int key, int value) { auto it = cache.find(key); if (it != cache.end()) { // 更新已有键值对的位置到队首 lru.splice(lru.begin(), lru, it->second); it->second->second = value; return; } if (lru.size() >= cap) { // 移除最近最少使用的元素 int lastKey = lru.back().first; cache.erase(lastKey); lru.pop_back(); } lru.emplace_front(std::make_pair(key, value)); cache[key] = lru.begin(); } private: std::list<std::pair<int, int>> lru; // 双向链表用于记录顺序 std::unordered_map<int, std::list<std::pair<int, int>>::iterator> cache; // 哈希表快速查找 int cap; // 容量大小 }; ``` 上述代码实现了LRU缓存的功能[^3],其中`get`函数负责获取指定key对应的value,并将其移动至最近使用位置;而`put`函数则在更新或新增键值对的同时处理超出容量的情况。 --- #### 全排列问题的C++回溯算法实现 全排列问题是另一个经典算法题目,可以通过回溯法来解决。给定一组不重复的数字,返回其所有的可能排列组合。 下面是完整的C++代码实现: ```cpp #include <vector> using namespace std; void backtrack(vector<vector<int>>& res, vector<int>& path, vector<bool>& used, const vector<int>& nums) { if (path.size() == nums.size()) { res.push_back(path); // 当路径长度等于数组长度时保存结果 return; } for (int i = 0; i < nums.size(); ++i) { if (!used[i]) { // 如果当前数尚未被使用 used[i] = true; // 标记已使用状态 path.push_back(nums[i]); // 加入路径 backtrack(res, path, used, nums); // 进一步递归探索 path.pop_back(); // 回退操作 used[i] = false; // 恢复标记 } } } vector<vector<int>> permute(vector<int>& nums) { vector<vector<int>> result; vector<int> currentPath; vector<bool> visited(nums.size(), false); backtrack(result, currentPath, visited, nums); return result; } ``` 此代码片段定义了`permute`函数,它接收输入数组并返回所有可能的排列列表[^2]。核心逻辑由辅助函数`backtrack`完成,采用深度优先搜索的方式逐步构建每一种可能性。 --- #### 并行计算中的多线程优化 除了以上两个具体问题外,C++还擅长于高性能计算领域内的任务分配与资源管理。例如,可以借助标准库 `<thread>` 实现简单的并发执行模式以提升效率。以下是一段演示如何利用多线程加速矩阵乘法运算的例子: ```cpp #include <iostream> #include <vector> #include <thread> using namespace std; const int N = 100; // 矩阵维度 // 单一线程版本 void multiply(const vector<vector<int>>& A, const vector<vector<int>>& B, vector<vector<int>>& C, int rowStart, int rowEnd) { for (int i = rowStart; i < rowEnd; ++i) { for (int j = 0; j < N; ++j) { for (int k = 0; k < N; ++k) { C[i][j] += A[i][k] * B[k][j]; } } } } int main() { vector<thread> threads; vector<vector<int>> A(N, vector<int>(N)), B(N, vector<int>(N)), C(N, vector<int>(N, 0)); // 初始化A和B... int numThreads = thread::hardware_concurrency(); // 获取CPU支持的最大线程数量 for (int t = 0; t < numThreads; ++t) { int startRow = (N / numThreads) * t; int endRow = ((t == numThreads - 1) ? N : (N / numThreads) * (t + 1)); threads.emplace_back(multiply, ref(A), ref(B), ref(C), startRow, endRow); } for(auto& th : threads){ th.join(); } cout << "Matrix multiplication completed." << endl; return 0; } ``` 这段程序展示了如何将大尺寸矩阵分解成若干子区域分别交给不同线程独立计算[^1],从而显著缩短整体耗时。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值