刷题记录1---组合总和+全排列+子集+电话号码的字母组合+括号生成

前言

所有题目均来自力扣题库中的hot 100,之所以要记录在这里,只是方便后续复习

39.组合总和

题目:
给你一个 无重复元素 的整数数组 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
输出: []
解题思路:
【搜索回溯】任意选取数组成target,选取某个数val后,可变成任意选取数组成target-val,与之前相比只是target变了,解题思路没变,故联想起递归;递归要那递归的结束条件是什么呢,一个是target变为0即找到了目前列表,另一个是列表所有值都被遍历过了,那怎么判断target是否为0以及值是否被遍历完成,就需要在递归函数参数中带有target和遍历的位置index;当target变为0时我们要将已选择的值作为一个列表添加到最终结果中,那怎么记录已选择的值和最终结果呢,就需要在递归参数中带有已选中的值列表item和最终结果result;又因为每个位置都存在选中和被选中两种情况需要考虑,在位置index考虑被选中情况时,我们要将index的值添加到已选中的列表中后进行递归,为了不影响在index+1时且index没被选中的情况,我们需要在考虑被index被选中递归后,需要将index从已选中的值删除,因为已选中的值item会当作参数传入,整个过程公用一个,所以在添加后要回溯。此外要注意两个点:一个是将已选中值列表添加到最终结果时,要复制一个新的列表添加而不是直接添加item,因为在后续递归中item会被改变;另一个是考虑index被选中时,要判断target - nums[index]是否不小于0,不然不能选index,因为选了就超过target了
代码(python):

class Solution(object):
    def combinationSum(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        item = list()
        result = list()
        self.recursion(candidates, target, result, item, 0)
        return result

    # 定义递归函数 其中result为总结果,item为已选中的值的列表,index表示递归到第几个数的索引
    def recursion(self, candidates, target, result, item, index):
        # 当递归到最后最后一个数,则代表结束
        if index == len(candidates):
            return
        # 当target为0,则代表已经找到目标列表,将item复制添加到结果中,注意是复制,而不是直接添加item,因为item后续回溯会不停的更改
        if target == 0:
            result.append(list(item))
            return 
        # 不选择当前值的情况,只需index + 1 即可
        self.recursion(candidates, target, result, item, index + 1)
        # 选择当前值的情况,但是要满足target - 当前值大于等于0,不然不能选
        if target - candidates[index] >= 0:
            # 先将当前值添加到已选列表
            item.append(candidates[index])
            # 进行下一次递归,target变为target - 当前值,由于元素可以重复选,所以还是index,而不是index + 1
            self.recursion(candidates, target - candidates[index], result, item, index)
            # 递归完后要回溯,即将当前值再从已选列表中删除
            item.pop()

代码(java):

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        ArrayList<List<Integer>> results = new ArrayList<>();
        ArrayList<Integer> result = new ArrayList<>();
        test(candidates, target, results, result, 0);
        return results;
    }

    public void test(int[] candidates, int target, ArrayList<List<Integer>> results, ArrayList<Integer> result, int index){
        if (index == candidates.length){
            return;
        }

        if (target == 0){
            results.add(new ArrayList<Integer>(result));
            return;
        }

        test(candidates, target, results, result, index + 1);

        if (target - candidates[index] >= 0){
            result.add(candidates[index]);
            test(candidates, target - candidates[index], results, result, index);
            result.remove(result.size() - 1);
        }
    }
}

知识点:

  • python中列表删除最后一个元素可以用pop()方法,此外,del list[num]和list.remove(num)参数是具体的对象而不是索引
  • python和java中列表复制可以新建一个列表,将原列表当作参数传入即可

原题链接:组合总和

46.全排列

题目:
给定一个不含重复数字的数组 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]]
解题思路:
【搜索回溯】将某列表全排列,即先选取一个值后,其他值的列表再全排列,故联想到递归;递归结束的条件是无其他待选取值,此时我们要将已选取的值的列表添加到最终结果中,这就要求我们要在递归函数参数中带有最终结果result和已选取值的列表item;同时要知道有哪些待选取值,所以递归参数中要有待选取值的列表nums;我们遍历待选取值列表,假设选中该值,将该值添加到已选取列表,从待选取列表删除,然后递归选取其他值,但是递归后为了防止对下一次递归的影响,要回溯,将该值从已选取列表删除并添加到待选取列表原位置,对下一次递归会产生影响的原因是,已选取列表在递归过程中公用一个,在假设选取该值后我们要恢复到未选取该值而选取其他值的情况。
代码(python):

class Solution(object):
    def permute(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        results = []
        result = []

        self.test(nums, results, result)
        return results

    def test(self, nums,  results, result):

        if len(nums) == 0:
            results.append(list(result))
            return
     
        for val in nums:
            result.append(val)
            new_nums = list(nums)
            new_nums.remove(val)
            self.test(new_nums, results, result)
            result.pop()

代码(java):

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        // 将数组参数转为链表
        ArrayList<Integer> new_nums = new ArrayList<>();
        for(int i = 0; i < nums.length; i ++){
            new_nums.add(nums[i]);
        }
        ArrayList<Integer> item = new ArrayList<>();
        ArrayList<List<Integer>> result = new ArrayList<>();
        recursion(new_nums, result, item);
        return result;
    }

    public void recursion(ArrayList<Integer> nums, ArrayList<List<Integer>> result, ArrayList<Integer> item){
        // 递归结束条件,当待选取列表中没值时代表已经排完序,将已排序列表复制为新列表添加到最终结果中
        if (nums.size() == 0){
            result.add(new ArrayList(item));
            return;
        }
        // 遍历代选取列表中的每个值
        for (int i = 0; i < nums.size(); i ++){
            //假设选中该值,即将该值添加到已选取列表中,从待选取列表中删除
            int val = nums.get(i);
            item.add(val);
            nums.remove(i);
            //然后递归进行下一次选取;
            recursion(nums, result, item);
            // 递归完成后要回溯,即将该值从已选取列表中删除,且添加回待选取列表(注意要是原位置而不是末尾,因为改变位置后影响遍历结果)
            item.remove(item.size() -  1);
            nums.add(i, val);
        }
    }

}

知识点:

  • java中ArrayList.add(i, val)方法,若传入第一个参数i,可以添加到指定位置
  • python中list.insert(i, val)可以插入元素到某个索引

原题链接:全排列

78.子集

题目:
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]

解题思路:
【搜索回溯】对于列表中的每一个位置的值,我们考虑选取或不选取两种情况后,就变成后面其他值选取与否,考虑思路相同,故联想到递归;递归的结束的条件是我们遍历完所有待选取值列表了,故递归函数需要一个索引参数index;在到达结束条件后我们要将已选取值列表添加到最终结果中,故递归参数中要有已选取列表item和最终结果列表result,当然也要有待选取值列表;每个位置都有两种情况,选取和不选取,不选取即index + 1进行后面的递归即可;选取要将该值添加到已选取列表中,index+1继续进行后面位置的递归,但是递归完成后要回溯,即从已选取列表中删除该值,回溯的原因是已选取列表整个递归过程中公用一个,我们要恢复成未选取该值状态,防止对后面位置的值递归产生影响
代码(python):

class Solution(object):
    def subsets(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        result = list()
        item = list()
        self.recursion(nums, result, item, 0)
        return result
    
    def recursion(self, nums, result, item, index):
        # 递归结束条件,遍历完成,即索引到值列表长度,将已选取列表复制为新列表添加到最终结果中
        if index == len(nums):
            result.append(list(item))
            return
        # 不选取该值的情况,索引直接+1进行后面的递归即可
        self.recursion(nums, result, item, index + 1)
        # 选取该值的情况,将该值添加到已选取列表,然后索引+1继续递归,递归后要回溯即从已选取列表中删除该值
        item.append(nums[index])
        self.recursion(nums, result, item, index + 1)
        item.pop()

代码(java):

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        ArrayList<List<Integer>> results = new ArrayList<>();
        ArrayList<Integer> result = new ArrayList<>();
        test(nums, results, result, 0);
        return results;
    }

    public void test(int[] nums, ArrayList<List<Integer>> results, ArrayList<Integer> result, int index){
        if(index == nums.length){
            results.add(new ArrayList<Integer>(result));
            return;
        }

        result.add(nums[index]);
        test(nums, results, result, index + 1);
        result.remove(result.size() - 1);

        test(nums, results, result, index + 1);
    }
}

知识点:

原题链接:子集

17.电话号码的字母组合

题目:
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
电话号码的字母组合示意图

示例 1:
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]
示例 2:
输入:digits = “”
输出:[]
示例 3:
输入:digits = “2”
输出:[“a”,“b”,“c”]

解题思路:
【搜索回溯】对于参数中的每个数字,都有其对应的字符串,我们可以用一个map集合存储其此类对应关系;另外针对于每个位置index假设选择一个字符后,就转化成了index + 1后面的每个位置选一个字符当前字符拼接,即每个位置考虑的情况一样,所以联想到递归;那递归的中止条件是什么呢,就是每个位置都考虑完成了即index 到达了参数的长度;当递归中止时,我们要将此次组成的字符串复制一个新的字符串放到结果中;最重要的是每次递归后我们要进行回溯,即将次数选择的字符串从组成字符串中删除,因为选择了另一个字符串,之前选择的字符串当然要删掉,不然整个递归公用一个当前字符串存储值会产生干扰
代码(python):

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        if len(digits) == 0:
            return []
        rule = {"2":"abc", "3":"def", "4":"ghi", "5":"jkl", "6":"mno", "7":"pqrs", "8":"tuv", "9":"wxyz"}
        result = self.test(digits, rule)
        return result


    def test(self, digits, rule):
        result = []
        if len(digits) == 1:
            for s in rule[digits[0]]:
                result.append(s)
            return result
        string = rule[digits[0]]
        strings = self.test(digits[1:], rule)
        for s in string:
            for st in strings:
                result.append(s + st)
        return result

代码(java):

class Solution {
    // 定义map结构,用来存储数字 与 字母之间的关系
    public HashMap<Character, String> releationship = new HashMap<>();

    public List<String> letterCombinations(String digits) {
        // 定义结果集合
        ArrayList<String> result = new ArrayList<>();
        // 如果是空字符串,直接返回空集合即可
        if(digits.length() == 0){
            return result; 
        }
        // 将数字与字母之间的关系一一添加到map中
        releationship.put('2', "abc");
        releationship.put('3', "def");
        releationship.put('4', "ghi");
        releationship.put('5', "jkl");
        releationship.put('6', "mno");
        releationship.put('7', "pqrs");
        releationship.put('8', "tuv");
        releationship.put('9', "wxyz");
        // 调用搜索回溯方法
        test(digits, 0, "", result);
        return result;
    }

    public void test(String digits, int index, String curStr, ArrayList<String> result){
        //如果遍历位置index等于参数长度,说明搜索完成,即将当前字符串 复制一个添加到结果集合中 并return
        if(index == digits.length()){
            result.add(new String(curStr));
            return;
        }
        // 根据当前位置的数字获取其对应的字符串
        String mappingStr = releationship.get(digits.charAt(index));
        // 遍历获取的字符串,针对于每次遍历,
        for(int i = 0; i < mappingStr.length(); i ++){
            // 假设选取当前位置的字符拼到当前字符串上,
            curStr = curStr + Character.toString(mappingStr.charAt(i));
            // 然后index + 1并继续进行后面数字的搜索(递归)
            test(digits, index + 1, curStr, result);
            // 最后进行回溯,将拼接上的字符 从当前字符串上删除
            curStr = curStr.substring(0, curStr.length() - 1);
        }

    }
}

知识点:

  • java中字符串可以用string.substring(start, end)方法,截取一部分字符串代替删除某个字符,包含start但不包含end;而python中要删除末尾的字符也可以用截取的方式cur[0: len(cur) - 1]

原题链接:电话号码的字母组合

22.括号生成

题目:
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3
输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]
示例 2:
输入:n = 1
输出:[“()”]

解题思路:
【搜索回溯】此题可以转换为有n对括号,每个括号为一个字符,我们需要对此集合进行全排列,返回所有字符串集合,因此考虑到搜索回溯;但是并不能要随便排列,要符合有效这个规则,因此我们在每个位置考虑选取左括号或者右括号的时候,要遵守两个规则:当左括号数小于括号对数是才能选取左括号,以及当右括号数小于左括号数时才能选取右括号;那选取结束的中止条件是什么呢?当组成的字符串长度等于括号对数*2时即选取结束,将组成字符串复制并添加到结果集合中,另外由于要判断左右括号的数,所以在递归函数除了结果结合、当前字符串之外,还要左括号数、右括号数以及括号对数
代码(python):

class Solution(object):
    def generateParenthesis(self, n):
        """
        :type n: int
        :rtype: List[str]
        """
        # 定义结果集合
        result = list()
        # 调用搜索回溯方法
        self.test(result, "", 0, 0, n)
        return result

    def test(self, result, cur, left_num, right_num, n):
        # 如果当前字符串长度 等于 括号数,即完成搜索,将当前字符串复制添加到结果集合中
        if len(cur) == n *2:
            result.append("" + cur)
            return

        # 如果左括号数小于括号对数, 当前可选取 左括号
        if left_num < n:
            # 选取左括号,即拼接到当前字符串
            cur = cur + "("
            # 递归进行后续的选取
            self.test(result, cur, left_num + 1, right_num, n)
            # 回溯 将选取的左括从当前字符串中删除
            cur  = cur[0: len(cur) - 1]
            
        # 如果右括号数小于 左括号数, 当前可选区 右括号
        if right_num < left_num:
            # 选取右括号,即拼接到当前字符串
            cur = cur + ")"
            # 递归进行后续的选取
            self.test(result, cur, left_num, right_num + 1, n)
            # 回溯 将选取的右括号从当前字符串中删除
            cur  = cur[0: len(cur) - 1]

代码(java):

class Solution {
    public List<String> generateParenthesis(int n) {
        ArrayList<String> results = new ArrayList<String>();
        StringBuffer sb = new StringBuffer("");
        test(results, n, sb, 0, 0);
        return results;

    }

    public void test(ArrayList<String> results, int n, StringBuffer sb, int left_number, int right_number){
        if (sb.length() == n * 2){
            results.add(sb.toString());
            return;
        }

        if (left_number < n){
            sb = sb.append("(");
            test(results, n, sb, left_number + 1, right_number);
            sb.deleteCharAt(sb.length() - 1);
        }
        if (right_number < left_number){
            sb = sb.append(")");
            test(results, n, sb, left_number, right_number + 1);
            sb.deleteCharAt(sb.length() - 1);
        }
    }
}

知识点:

  • java中字符串可以用string.substring(start, end)方法,截取一部分字符串代替删除某个字符,包含start但不包含end;而python中要删除末尾的字符也可以用截取的方式cur[0: len(cur) - 1]

原题链接:括号生成

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值