回溯法类型 OJ 题篇1

回溯法类型 OJ 题型

      • 1、组合总和
      • 2、全排列
      • 3、二叉树的路径
      • 4、电话号码的组合
      • 10.下一个排列
      • 6. 缺失的第一个正数

1、组合总和

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

candidates 中的数字可以无限制重复被选取。

示例1
输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]

示例2
输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]

思路: 回溯法法,如果这条路走不下去,就退回一级,重新选择路径,一直往下走,直到 target 等于 0 就添加到返回列表中,如果 target 小于 0 则返回

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        if(candidates.size()==0){
            return res;
        }
        combinationSum(candidates, target,0);
        return res;
    }
    void combinationSum(vector<int>& candidates,int target,int index){
        if(target < 0){
            return;
        }
        if(target == 0){
            res.push_back(temp);
        }
        for(int i = index;i<candidates.size();++i){
            temp.push_back(candidates[i]);
            combinationSum(candidates, target-candidates[i],i);
            temp.pop_back();
        }
    }
private:
    vector<vector<int>>res;
    vector<int>temp;
};

python 写法

    def combinationSum(candidates, target):
        length = len(candidates)
        res = []
        def combinationSum_in(target,index,maybe):
            if target<0:
                return
            if target==0:
                res.append(maybe)            
            for i in range(index,length):
                combinationSum_in(target-candidates[i],i,maybe+[candidates[i]])
        combinationSum_in(target,0,[])
        return res

如果 candidates 中的每个数字在每个组合中只能使用一次,该怎么修改代码?

所有数字(包括目标数)都是正整数,解集不能包含重复的组合

示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]

示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]

思路:同样也是回溯法,只不过每个元素只能使用一次,所以我们先得排序,按照升序或者降序的方式,在满足一个条件之后进行判断前一个元素和后一个元素是否相等,如果相等就跳过,以防重复

class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        if(candidates.empty()){
            return res;
        }
        sort(candidates.begin(),candidates.end());
        combinationSum2(candidates,target,0);
        return res;
    }
    void combinationSum2(vector<int>& candidates, int target,int index){
        if(target<0){
            return;
        }
        if(target == 0){
            res.push_back(temp);
            return;
        }
        for(int i = index;i<candidates.size();++i){
        	// 这个 if 语句还是挺难理解的,
        	// 目的防止在 candidates 中有重复的值被重复计算
        	// 而这个 if 语句在回溯的时候才会去判断,插入的时候不会判断
        	if(i>index && candidates[i] == candidates[i-1]){
                continue;
            }
            // 下面这个 if 语句加不加无所谓,加上程序跑的快
            // 如果当前的 candidates[i] 都已经大于 target 了
            // 就说明这条路肯定不行,需要退回去
 			if(candidates[i]>target){
                return;
            }
            temp.push_back(candidates[i]);
            combinationSum2(candidates,target-candidates[i],i+1);
            temp.pop_back();
        }
    }
private:
    vector<vector<int>>res;
    vector<int>temp;
};

这里还有一个注意事项,就是不能使用 set 函数对 candidates 进行去重,题目说的是 candidates 中的每个值都仅能使用一次,并不是说一个数字只能出现一次,这里还是有点绕的.

python 写法

def combinationSum2(candidates, target):
    c = sorted(candidates)
    res = []
    len_c = len(c)
    def dfs(target, index, path):
        if target == 0:
            res.append(path)
            return
        for i in range(index, len_c):
        	# 这个回溯就是满足了一个条件,那么i 一定大于 index
        	# 在刚开始的阶段 i == index
            if i>index and c[i] == c[i-1]:
                continue
            if c[i]>target:
                break
            dfs(target-c[i], i+1, path+[c[i]])
            # python 这里不是没有回退,而是上一行代码使用
            # 的是 path + [c[i]] 采用 + 的方法,返回去的
            # 话就直接减去了,没必要自定制的弹出
    dfs(target, 0, [])
    return res

2、全排列

给定一个没有重复数字的序列,返回其所有可能的全排列。

示例
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

解题思路:回溯法,在每一次的遍历都需要进行标记,如果访问了这个元素就标为 1,弹出这个元素就标 0 ,为的就是回溯的时候可以不重复

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<int>used(nums.size(),0);
        dfs(used,nums);
        return res;
    }
    void dfs(vector<int>&used,vector<int>& nums){
        if(temp.size() == nums.size()){
            res.push_back(temp);
            return;
        }
        for(int i = 0;i<nums.size();++i){
            if(used[i] !=0){
                continue;
            }
            temp.push_back(nums[i]);
            used[i] = 1;
            dfs(used,nums);
            temp.pop_back();
            used[i] = 0;
        }
    }
private:
    vector<vector<int>>res;
    vector<int>temp;
};

python 写法

def permute(nums):
    res = []
    def dfs(num, path):
        if not num:
            res.append(path)
            return
        for i in range(len(num)):
            dfs(num[:i]+num[i+1:], path+[num[i]])
    dfs(nums, [])
    return res 

另一种方法是在 python 中存在库函数

import itertools
def permute(nums: List[int]):
    return list(itertools.permutations(nums))

如果给定一个可包含重复数字的序列,要求返回所有不重复的全排列,代码应该如何修改?

示例:
输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]

可以增加一个变量,判断是否重复,重复的话就直接跳过

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<int>Isused(nums.size(),0);
        dfs(nums,Isused);
        return res;
    }
    void dfs(vector<int>&nums,vector<int>&Isused){
        if(nums.size() == temp.size()){
            res.push_back(temp);
            return;
        }
        int r = INT_MAX;
        for(int i = 0;i<nums.size();++i){
            if(Isused[i] != 0 || nums[i] ==r){
                continue;
            }
            r = nums[i];
            Isused[i] = 1;
            temp.push_back(nums[i]);
            dfs(nums,Isused);
            Isused[i] = 0;
            temp.pop_back();
        }
    }

private:
    vector<vector<int>>res;
    vector<int>temp;
};

python 版本

    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        res = []
        def dfs(nums,path):
            if not nums:
                res.append(path)
                return
            for i in range(len(nums)):
                if i>0 and nums[i]==nums[i-1]:
                    continue
                dfs(nums[:i]+nums[i+1:],path+[nums[i]])
        dfs(nums,[])
        return res

3、二叉树的路径

输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径

示例:
给定如下二叉树,以及目标和 sum = 22

回溯法类型 OJ 题篇1_第1张图片

返回:
[
[5,4,11,2],
[5,8,4,5]
]

参考代码

class Solution {
public:
    vector<vector<int>> pathSum(TreeNode* root, int sum) {
        dfs(root,sum,0);
        return res;
    }
    void dfs(TreeNode* root, int sum,int cur){
        if(root == nullptr){
            return;
        }
        path.push_back(root->val);
        cur+=root->val;
        if(root->left == nullptr && root->right == nullptr\
            && sum == cur){
            res.push_back(path);
        }
        dfs(root->left,sum,cur);
        dfs(root->right,sum,cur);
        path.pop_back();
    }
private:
    vector<vector<int>>res;
    vector<int>path;
};

4、电话号码的组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母
回溯法类型 OJ 题篇1_第2张图片

输入:“23”
输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].

回溯法

class Solution {
public:
    unordered_map<char,string>um{
        {'0'," "},{'1',"*"},{'2',"abc"},
        {'3',"def"},{'4',"ghi"},{'5',"jkl"},
        {'6',"mno"},{'7',"pqrs"},{'8',"tuv"},
        {'9',"wxyz"}
    };
    vector<string> letterCombinations(string digits) {
        vector<string>res;
        if(digits == ""){
            return res;
        }
        dfs(res,"",digits,0);
        return res;
    }
    void dfs(vector<string>&res,string str,\
        string& digits,int k){
      	// 满足条件则进行插入返回      
        if(str.size() == digits.size()){
            res.push_back(str);
            return;
        }
        // 取出对应的字符串.
        string temp = um[digits[k]];
        for(char e:temp){
            str +=e;
            dfs(res,str,digits,k+1);
            str.pop_back();
        }
        return;
    }
};

10.下一个排列

实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

必须原地修改,只允许使用额外常数空间。

示例
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

假设 我们的数1376542 ,我们第一要找的位置就是 3 ,把 3 和 4 进行交换,然后对3后面的数字进行翻转。

可以看出,需要改变的子数组的起始数为 从后往前第一个nums[i]>nums[i-1]的数k,即3
要把起始数k替换为从后往前第一个大于k的数,即4,交换两数变为1476532
翻转i之后的一半数组

有一种特殊情况:倒序遍历到0都没有碰到 nums[i]>nums[i-1] 的情况,直接翻转整个数组 最后结果就是:1423567

    def nextPermutation(self, nums):
        n = len(nums)
        i = j = n-1
        while i > 0 and nums[i-1] >= nums[i]:
            i -= 1  
        k = i - 1    # find the last "ascending" position
        if k>=0:
            while nums[j] <= nums[k]:
                j -= 1
            nums[k], nums[j] = nums[j], nums[k]  
        l, r = k+1, n-1  # reverse the second part
        while l < r:
            nums[l], nums[r] = nums[r], nums[l]
            l +=1 ; r -= 1

6. 缺失的第一个正数

给定一个未排序的整数数组,找出其中没有出现的最小的正整数。

示例1
输入: [1,2,0]
输出: 3

示例2
输入: [3,4,-1,1]
输出: 2

示例3
输入: [7,8,9,11,12]
输出: 1

思路:首先进行判空,如果为空返回1,然后找到最小的值,对最小的值进行分情况讨论,如果大于1,则返回1,如果小于等于1,就设置一个变量,赋初始化为1,从1开始判断它在不在数组中,如果在就一直加,如果不在就返回当前变量。

def firstMissingPositive(self, nums: List[int]) -> int:
    if not nums:
        return 1
    if min(nums)>1:
        return 1
    if min(nums)<=1:
        i = 1
        while i in nums:
            i+=1
        return i

或者这样也行,直接判断1在不在当前数组中,如果不在就返回1,就不用考虑空数组和最小值大于1的数组了。

    def firstMissingPositive(self, nums: List[int]) -> int:
        if 1 not in nums:
            return 1
        if min(nums)<=1:
            i = 1
            while i in nums:
                i+=1
            return i

当然也可是使用桶排序,先初始化 n 个桶,值都为True,对数组进行遍历,遇到大于n和小于1的数不用管,如果遍历的一个数在1到n之间,则把相应的桶设置为True,遍历完数组后,第一桶是True的就是返回的数字。

    def firstMissingPositive(self, nums):
        if 1 not in nums:
            return 1
        n = len(nums)
        bucket = [True]*(n+1)
        for i in range(n):
            if 0<nums[i]<=n:
                bucket[nums[i]] = False
        for i in range(1,n+1):
            if bucket[i]:
                return i
        return n+1

你可能感兴趣的:(惨痛的刷题记录)