算法沉淀——穷举、暴搜、深搜、回溯、剪枝综合练习一(leetcode真题剖析)

本文主要是介绍算法沉淀——穷举、暴搜、深搜、回溯、剪枝综合练习一(leetcode真题剖析),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述

算法沉淀——穷举、暴搜、深搜、回溯、剪枝综合练习一

  • 01.全排列
  • 02.子集
  • 03.找出所有子集的异或总和再求和
  • 04.全排列 II
  • 05.电话号码的字母组合

01.全排列

题目链接:https://leetcode.cn/problems/permutations/

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]] 

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

思路
这是一个典型的回溯问题,需要在每个位置上考虑所有可能情况,并确保不出现重复。通过深度优先搜索的方式,不断枚举每个数在当前位置的可能性,然后回溯到上一个状态,直到枚举完所有可能性得到正确的结果。

在这个问题中,可以通过一个递归函数 backtrack 和一个标记数组 visited 来实现全排列。递归函数的核心思想是在当前位置尝试放置每个数字,然后递归到下一层。同时,使用 visited 数组来判断某个数字是否已经在之前的位置出现过,以确保全排列的唯一性。

代码

class Solution {vector<vector<int>> ret;vector<int> path;bool cheak[7];
public:vector<vector<int>> permute(vector<int>& nums) {dfs(nums);return ret;}void dfs(vector<int>& nums){if(path.size()==nums.size()){ret.push_back(path);return;}for(int i=0;i<nums.size();++i){if(!cheak[i]){path.push_back(nums[i]);cheak[i]=true;dfs(nums);path.pop_back();cheak[i]=false;}}}
};

02.子集

题目链接:https://leetcode.cn/problems/subsets/

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

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

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:

输入:nums = [0]
输出:[[],[0]] 

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • nums 中的所有元素 互不相同

思路

这里我们我们可以有下面两种写法,我们结合代码解释

代码一

class Solution {vector<vector<int>> ret;vector<int> path;
public:vector<vector<int>> subsets(vector<int>& nums) {dfs(nums, 0);return ret;}void dfs(vector<int>& nums, int pos) {ret.push_back(path);for (int i = pos; i < nums.size(); ++i) {path.push_back(nums[i]);dfs(nums, i + 1);path.pop_back();}}
};

这段代码使用深度优先搜索(DFS)的思想。主要过程如下:

  1. dfs函数的一开始,将当前子集 path 加入结果集 ret
  2. 然后使用循环,从当前位置 pos 开始遍历数组 nums
  3. 对于每个元素,将其加入当前子集 path,然后递归调用 dfs 函数,继续生成包含当前元素的子集。
  4. 递归完成后,回溯,将刚刚加入的元素从当前子集 path 移出,继续循环遍历下一个元素。

这样,通过深度优先搜索,代码逐步生成了包含不同数量元素的所有子集。

代码二

class Solution {vector<vector<int>> ret;vector<int> path;
public:vector<vector<int>> subsets(vector<int>& nums) {dfs(nums, 0);return ret;}void dfs(vector<int>& nums, int pos) {if (pos == nums.size()) {ret.push_back(path);return;}path.push_back(nums[pos]);dfs(nums, pos + 1);path.pop_back();dfs(nums, pos + 1);}
};

这段代码同样使用深度优先搜索的思想。主要过程如下:

  1. dfs函数的一开始,检查当前位置 pos 是否等于数组 nums 的大小。如果相等,表示已经处理完所有元素,将当前子集 path 加入结果集 ret,然后返回结束当前 DFS 分支。
  2. 如果当前位置 pos 不等于数组大小,将当前元素加入子集 path,然后递归调用 dfs 函数,继续生成包含当前元素的子集。
  3. 递归完成后,回溯,将刚刚加入的元素从当前子集 path 移出。
  4. 继续递归调用 dfs 函数,生成不包含当前元素的子集。

这样,同样通过深度优先搜索,代码逐步生成了包含不同数量元素的所有子集。这种实现方式在递归的不同阶段进行了两次递归调用,一次是包含当前元素的情况,一次是不包含当前元素的情况。

03.找出所有子集的异或总和再求和

题目链接:https://leetcode.cn/problems/sum-of-all-subset-xor-totals/

一个数组的 异或总和 定义为数组中所有元素按位 XOR 的结果;如果数组为 ,则异或总和为 0

  • 例如,数组 [2,5,6]异或总和2 XOR 5 XOR 6 = 1

给你一个数组 nums ,请你求出 nums 中每个 子集异或总和 ,计算并返回这些值相加之

**注意:**在本题中,元素 相同 的不同子集应 多次 计数。

数组 a 是数组 b 的一个 子集 的前提条件是:从 b 删除几个(也可能不删除)元素能够得到 a

示例 1:

输入:nums = [1,3]
输出:6
解释:[1,3] 共有 4 个子集:
- 空子集的异或总和是 0 。
- [1] 的异或总和为 1 。
- [3] 的异或总和为 3 。
- [1,3] 的异或总和为 1 XOR 3 = 2 。
0 + 1 + 3 + 2 = 6

示例 2:

输入:nums = [5,1,6]
输出:28
解释:[5,1,6] 共有 8 个子集:
- 空子集的异或总和是 0 。
- [5] 的异或总和为 5 。
- [1] 的异或总和为 1 。
- [6] 的异或总和为 6 。
- [5,1] 的异或总和为 5 XOR 1 = 4 。
- [5,6] 的异或总和为 5 XOR 6 = 3 。
- [1,6] 的异或总和为 1 XOR 6 = 7 。
- [5,1,6] 的异或总和为 5 XOR 1 XOR 6 = 2 。
0 + 5 + 1 + 6 + 4 + 3 + 7 + 2 = 28

示例 3:

输入:nums = [3,4,5,6,7,8]
输出:480
解释:每个子集的全部异或总和值之和为 480 。

提示:

  • 1 <= nums.length <= 12
  • 1 <= nums[i] <= 20

思路

这一题实际上还是和上一题类似的子集问题,只不过我们将存下的数组变为了一个值,进数组变为子集累计异或的值,出临时数组变为异或相同值,也就是删除异或过的数。

代码

class Solution {int sum=0,path=0;
public:int subsetXORSum(vector<int>& nums) {dfs(nums,0);return sum;}void dfs(vector<int>& nums,int pos){sum+=path;for(int i=pos;i<nums.size();++i){path^=nums[i];dfs(nums,i+1);path^=nums[i];}}
};
  1. int sum = 0, path = 0;: 这两个类成员变量用于追踪异或和的总和 (sum) 和当前正在生成的子集异或和 (path)。
  2. int subsetXORSum(vector<int>& nums): 这是主函数,用于调用 DFS 函数,并返回最终的异或和结果。
  3. void dfs(vector<int>& nums, int pos): 这是深度优先搜索(DFS)函数,用于递归生成所有子集的异或和。
    • sum += path;: 在每一层递归中,将当前子集的异或和加到总和中。
    • for(int i = pos; i < nums.size(); ++i) {: 循环从当前位置 pos 开始遍历数组 nums
    • path ^= nums[i];: 将当前元素 nums[i] 异或到当前子集异或和中。
    • dfs(nums, i + 1);: 递归调用 DFS 函数,以 i + 1 为新的起点,生成包含当前元素的子集异或和。
    • path ^= nums[i];: 回溯,将刚刚异或的元素从当前子集异或和中移出,继续循环遍历下一个元素。
  4. 最终,sum 存储了所有子集的异或和总和,该值作为结果返回。

04.全排列 II

题目链接:https://leetcode.cn/problems/permutations-ii/

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

示例 1:

输入:nums = [1,1,2]
输出:
[[1,1,2],[1,2,1],[2,1,1]]

示例 2:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

提示:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

思路

这里要考虑全排列,在同一节点的所有组合中,相同的元素只能选择一次,所以这里我们要有一个标记元素是否使用的数组,在后面的主逻辑递归中,有两种写法,第一种是只关心“不合法”分支,第二种是只关心“合法”分支。

代码一

class Solution {vector<vector<int>> ret;vector<int> path;bool cheak[9];
public:vector<vector<int>> permuteUnique(vector<int>& nums) {sort(nums.begin(),nums.end());dfs(nums,0);return ret;}void dfs(vector<int>& nums,int pos){if(pos==nums.size()){ret.push_back(path);return;}for(int i=0;i<nums.size();++i){if(cheak[i]||(i!=0&&nums[i]==nums[i-1]&&cheak[i-1]==false))continue;path.push_back(nums[i]);cheak[i]=true;dfs(nums,pos+1);path.pop_back();cheak[i]=false;}}
};
  1. vector<vector<int>> ret;: 用于存储所有的全排列结果。

  2. vector<int> path;: 用于存储当前正在生成的排列。

  3. bool cheak[9];: 一个布尔数组,用于标记在当前生成排列的过程中哪些元素已经被选择。

  4. vector<vector<int>> permuteUnique(vector<int>& nums): 主函数,用于调用 DFS 函数,并返回最终的全排列结果。

  5. sort(nums.begin(), nums.end());: 在开始生成排列之前,先将输入数组排序,以便处理重复元素。

  6. void dfs(vector<int>& nums, int pos): 深度优先搜索函数,递归生成全排列。

    • if(pos == nums.size()): 如果当前位置 pos 等于数组的大小,表示已经处理完所有元素,将当前排列加入结果集。
      • ret.push_back(path);: 将当前排列加入结果集。
      • return;: 返回结束当前 DFS 分支。
    • for(int i = 0; i < nums.size(); ++i): 遍历数组中的每个元素。
      • if(cheak[i] || (i != 0 && nums[i] == nums[i-1] && cheak[i-1] == false)): 如果当前元素已经被选择(cheak[i] 为 true)或者当前元素与前一个元素相同,且前一个元素未被选择,则跳过当前循环。这是为了处理重复元素的情况,确保不会生成重复的排列。
        • continue;: 跳过当前循环,进入下一个循环。
      • path.push_back(nums[i]);: 将当前元素加入排列。
      • cheak[i] = true;: 将当前元素标记为已选择。
      • dfs(nums, pos + 1);: 递归调用 DFS 函数,继续生成下一个元素的排列。
      • path.pop_back();: 回溯,将刚刚加入的元素移出排列。
      • cheak[i] = false;: 回溯,将刚刚标记为已选择的元素重新标记为未选择。

    代码二

    class Solution {vector<vector<int>> ret;vector<int> path;bool cheak[9];
    public:vector<vector<int>> permuteUnique(vector<int>& nums) {sort(nums.begin(),nums.end());dfs(nums,0);return ret;}void dfs(vector<int>& nums,int pos){if(pos==nums.size()){ret.push_back(path);return;}for(int i=0;i<nums.size();++i){if(!cheak[i]&&(i==0||nums[i]!=nums[i-1]||cheak[i-1]==true)){path.push_back(nums[i]);cheak[i]=true;dfs(nums,pos+1);path.pop_back();cheak[i]=false;}}}
    };
    
    1. vector<vector<int>> ret;: 用于存储所有的全排列结果。
    2. vector<int> path;: 用于存储当前正在生成的排列。
    3. bool cheak[9];: 一个布尔数组,用于标记在当前生成排列的过程中哪些元素已经被选择。
    4. vector<vector<int>> permuteUnique(vector<int>& nums): 主函数,用于调用 DFS 函数,并返回最终的全排列结果。
    5. sort(nums.begin(), nums.end());: 在开始生成排列之前,先将输入数组排序,以便处理重复元素。
    6. void dfs(vector<int>& nums, int pos): 深度优先搜索函数,递归生成全排列。
      • if(pos == nums.size()): 如果当前位置 pos 等于数组的大小,表示已经处理完所有元素,将当前排列加入结果集。
        • ret.push_back(path);: 将当前排列加入结果集。
        • return;: 返回结束当前 DFS 分支。
      • for(int i = 0; i < nums.size(); ++i): 遍历数组中的每个元素。
        • if(!cheak[i] && (i == 0 || nums[i] != nums[i-1] || cheak[i-1] == true)): 如果当前元素未被选择且(是第一个元素或者与前一个元素不相同或者前一个元素已被选择),则进入循环。
          • path.push_back(nums[i]);: 将当前元素加入排列。
          • cheak[i] = true;: 将当前元素标记为已选择。
          • dfs(nums, pos + 1);: 递归调用 DFS 函数,继续生成下一个元素的排列。
          • path.pop_back();: 回溯,将刚刚加入的元素移出排列。
          • cheak[i] = false;: 回溯,将刚刚标记为已选择的元素重新标记为未选择。

05.电话号码的字母组合

题目链接:https://leetcode.cn/problems/letter-combinations-of-a-phone-number/

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"] 

提示:

  • 0 <= digits.length <= 4
  • digits[i] 是范围 ['2', '9'] 的一个数字。

思路

这里比前面的排列组合更为简单,只不过在这里我们需要额外创建一个哈希结构来建立数字和字母的映射关系。

代码

class Solution {string hash[10] = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs","tuv", "wxyz"};string path;vector<string> ret;
public:vector<string> letterCombinations(string digits) {if(!digits.size()) return ret;dfs(digits,0);return ret;}void dfs(string& digits,int pos){if(pos==digits.size()){ret.push_back(path);return;}for(auto ch:hash[digits[pos]-'0']){path+=ch;dfs(digits,pos+1);path.pop_back();}}
};
  1. string hash[10] = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs","tuv", "wxyz"};: 数字与对应的字母组合的映射关系,hash[2] 对应 “abc”,hash[3] 对应 “def”,以此类推。
  2. string path;: 用于存储当前正在生成的字母组合。
  3. vector<string> ret;: 用于存储所有的字母组合结果。
  4. vector<string> letterCombinations(string digits): 主函数,用于调用 DFS 函数,并返回最终的字母组合结果。
  5. if(!digits.size()) return ret;: 如果输入的数字序列为空,则直接返回空的结果集。
  6. void dfs(string& digits, int pos): 深度优先搜索函数,递归生成数字序列对应的所有字母组合。
    • if(pos == digits.size()): 如果当前位置 pos 等于数字序列的大小,表示已经处理完所有数字,将当前字母组合加入结果集。
      • ret.push_back(path);: 将当前字母组合加入结果集。
      • return;: 返回结束当前 DFS 分支。
    • for(auto ch : hash[digits[pos] - '0']): 遍历当前数字对应的所有字母。
      • path += ch;: 将当前字母加入字母组合。
      • dfs(digits, pos + 1);: 递归调用 DFS 函数,继续生成下一个数字对应的字母组合。
        )`: 主函数,用于调用 DFS 函数,并返回最终的字母组合结果。
  7. if(!digits.size()) return ret;: 如果输入的数字序列为空,则直接返回空的结果集。
  8. void dfs(string& digits, int pos): 深度优先搜索函数,递归生成数字序列对应的所有字母组合。
    • if(pos == digits.size()): 如果当前位置 pos 等于数字序列的大小,表示已经处理完所有数字,将当前字母组合加入结果集。
      • ret.push_back(path);: 将当前字母组合加入结果集。
      • return;: 返回结束当前 DFS 分支。
    • for(auto ch : hash[digits[pos] - '0']): 遍历当前数字对应的所有字母。
      • path += ch;: 将当前字母加入字母组合。
      • dfs(digits, pos + 1);: 递归调用 DFS 函数,继续生成下一个数字对应的字母组合。
      • path.pop_back();: 回溯,将刚刚加入的字母移出字母组合。

这篇关于算法沉淀——穷举、暴搜、深搜、回溯、剪枝综合练习一(leetcode真题剖析)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/732083

相关文章

Node.js 中 http 模块的深度剖析与实战应用小结

《Node.js中http模块的深度剖析与实战应用小结》本文详细介绍了Node.js中的http模块,从创建HTTP服务器、处理请求与响应,到获取请求参数,每个环节都通过代码示例进行解析,旨在帮... 目录Node.js 中 http 模块的深度剖析与实战应用一、引言二、创建 HTTP 服务器:基石搭建(一

哈希leetcode-1

目录 1前言 2.例题  2.1两数之和 2.2判断是否互为字符重排 2.3存在重复元素1 2.4存在重复元素2 2.5字母异位词分组 1前言 哈希表主要是适合于快速查找某个元素(O(1)) 当我们要频繁的查找某个元素,第一哈希表O(1),第二,二分O(log n) 一般可以分为语言自带的容器哈希和用数组模拟的简易哈希。 最简单的比如数组模拟字符存储,只要开26个c

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

usaco 1.3 Prime Cryptarithm(简单哈希表暴搜剪枝)

思路: 1. 用一个 hash[ ] 数组存放输入的数字,令 hash[ tmp ]=1 。 2. 一个自定义函数 check( ) ,检查各位是否为输入的数字。 3. 暴搜。第一行数从 100到999,第二行数从 10到99。 4. 剪枝。 代码: /*ID: who jayLANG: C++TASK: crypt1*/#include<stdio.h>bool h

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费