前言
所有题目均来自力扣题库中的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]
原题链接:括号生成