题目描述:
给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。
按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:
“123” “132” “213” “231” “312” “321” 给定 n 和 k,返回第 k 个排列。
说明:
给定 n 的范围是 [1, 9]。
给定 k 的范围是[1, n!]。
基本思路:
(1)回溯法,需要在回溯到第K个排列的时候终止,否则会超时。
(2) 数学法,因为数字都是从1开始的连续自然数,排列出现的次序可以直接推出来,例如对于n=4, k=15,找到k=15排列的过程。
1 + 对2,3,4的全排列 (3!个)
2 + 对1,3,4的全排列 (3!个) 3, 1 + 对2,4的全排列(2!个)
3 + 对1,2,4的全排列 (3!个)-------> 3, 2 + 对1,4的全排列(2!个)-------> 3, 2, 1 + 对4的全排列(1!个)-------> 3214
4 + 对1,2,3的全排列 (3!个) 3, 4 + 对1,2的全排列(2!个) 3, 2, 4 + 对1的全排列(1!个)
首先确定第k个排列的第一位的数字:
k = 14(从0开始计数)
index = k/(n-1)! = 2,说明第15个数的第一位是3
更新k = k-index*(n-1)! = 2
确定第二位:
k = 2
index = k / (n-2)! = 1, 说明第15个数的第二位是2
更新k
k = k - index*(n-2)! = 0
确定第三位:
k = 0
index = k / (n-3)! = 0, 说明第15个数的第三位是1
更新k
k = k - index*(n-3)! = 0
确定第四位:
k = 0
index = k / (n-4)! = 0, 说明第15个数的第四位是4
最终确定n=4时第15个数为3214
代码如下:
class Solution:
def getPermutation(self, n: int, k: int) -> str:
# num = [str(i) for i in range(1,n+1)]
# #print(num)
# res = []
# def backTravel(num,cur):
# if len(num)==0:
# res.append(cur)
# return
# #if len(res)>k:return
# for i in range(len(num)):
# if len(res)==k:
# break
# backTravel(num[:i]+num[i+1:],cur+num[i])
# a = backTravel(num,"")
# #print(res)
# return res[-1]
num = [str(i) for i in range(1,n+1)]
def factorial(n):
if n<=1:
return 1
else:
return n*factorial(n-1)
res = ""
k = k-1
while num:
a = factorial(len(num)-1)
index = k//a
#print(index)
value = num[index]
res += value
num.remove(value)
k = k-a*(index)
#print(k)
return res
题目描述:
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
基本思路:首先从左往后遍历,如果num[i]>num[i-1],那么说明从nums[i-1]出的右边开始重排列会是比当前这个更大的一个排列。首先我们需要找到右边第一个比num[i-1]大的数,然后将其与num[i-1]交换并将右边从小到大排列。
如果没有说明这个排列是最大的,就将其从小到大排列。
代码如下:
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
for i in range(len(nums)-1, 0, -1):
if nums[i] > nums[i-1]:
res_nums = nums[i:]
res_nums.sort()
nums[i:] = res_nums
for j in range(i, len(nums)):
if nums[j] > nums[i-1]:
nums[j], nums[i-1] = nums[i-1], nums[j]
break
return
nums.sort()
题目描述:给定一个 没有重复 数字的序列,返回其所有可能的全排列。
输入: [1,2,3]
输出:
[ [1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1] ]
思路分析:回溯算法
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
res = []
def dfs(cur,nums):
if len(nums)==0:
res.append(cur)
return
for i in range(len(nums)):
dfs(cur+[nums[i]],nums[:i]+nums[i+1:])
dfs([],nums)
return res
题目描述:给定一个可包含重复数字的序列,返回所有不重复的全排列。
思路分析:与前一题不同的是,这里需要返回所有不重复的全排列。有两种方法,第一种就是按照前一天得到所有全排列再去重。这里时间复杂度较高。第二种就是对前一题的回溯算法进行剪枝。
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
# res = set()
# def dfs(nums,cur):
# if len(nums)==0:
# res.add(tuple(cur))
# return
# for i in range(len(nums)):
# dfs(nums[:i]+nums[i+1:],cur+[nums[i]])
# dfs(nums,[])
# return list(res)
nums.sort()
self.res = []
self.recur(nums,[])
return self.res
def recur(self,nums,temp):
if nums == []:
self.res.append(temp)
return
for i in range(len(nums)):
if i>0 and nums[i] == nums[i-1]: # 防止构造重复结果
continue
self.recur(nums[:i]+nums[i+1:],temp+[nums[i]]) #防止重复利用
题目描述:给定一个字符串 s ,返回其通过重新排列组合后所有可能的回文字符串,并去除重复的组合。
如不能形成任何回文排列时,则返回一个空列表。
输入: “aabb” 输出: [“abba”, “baab”]
输入: “abc” 输出: []
思路分析:先用判断能否构成回文序列,不可以就直接返回空数组
可以的话看回文序列长度是否为奇数,为奇数的话把奇数字符查出来,
然后在map中把这个key对应个数减1,为偶数的话就不管
由于回文序列是两边对称的,所以我只计算一边的全排列,计算
完了之后再加上他的反序列即可,当然,有奇数字符就把它加到中间
代码如下:
class Solution:
def generatePalindromes(self, s: str) -> List[str]:
hashmap = {}
count = 0
for v in s:
hashmap[v] = hashmap.get(v,0) + 1
if hashmap[v]%2 != 0:count += 1
else:
count -= 1
if count>1:return []
if len(hashmap)==1:return [s]
num = []
mid = ""
for v in hashmap:
if hashmap[v]%2==0:
tem = [v]*(hashmap[v]//2)
num += tem
else:
if hashmap[v]>1:
num += [v]*((hashmap[v]-1)//2)
mid = v
res = []
def backTravel(num,cur):
if len(num)==0:
tem = "".join(cur)+mid
tem += "".join(cur[::-1])
res.append(tem)
return
for i in range(len(num)):
backTravel(num[:i]+num[i+1:],cur+[num[i]])
backTravel(num,[])
res = list(set(res))
return res
题目描述:给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
输入: candidates = [2,3,6,7], target = 7, 所求解集为: [ [7], [2,2,3] ]
**基本思路:**还是回溯算法,类似于全排列,只不过这里返回的条件变了,之前是数组为空,这里的条件是路径上的数之和为target 。
需要注意的是:因为数字可以无限制的重复被选取,因此分支有两条,一个是选下一个数字,一个是继续选择当前数字。
代码如下:
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
res = []
if not candidates:return []
candidates.sort()
n = len(candidates)
def dfs(i,cur_tem,target):
if target==0:
res.append(cur_tem)
return
if i==n or target<candidates[i]:
return
# 两条分支
dfs(i,cur_tem+[candidates[i]],target-candidates[i])
dfs(i+1,cur_tem,target)
dfs(0,[],target)
return res
题目描述:给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
思路分析:
这道题与上一问的区别在于:
第 1题:candidates 中的数字可以无限制重复被选取。
第 2 题:candidates 中的每个数字在每个组合中只能使用一次。
编码的不同在于下一层递归的起始索引不一样。
第 1 题:还从候选数组的当前索引值开始。
第 2 题:从候选数组的当前索引值的下一位开始。
相同之处:解集不能包含重复的组合。
为了使得解集不包含重复的组合。我们想一想,如何去掉一个数组中重复的元素,除了使用哈希表以外,我们还可以先对数组升序排序,重复的元素一定不是排好序以后的第 1 个元素和相同元素的第 1 个元素,简言之就是让树的同一层不会出现相同元素,根据这个思想,我们先对数组升序排序是有必要的。候选数组有序,对于在递归树中发现重复分支,进而“剪枝”也是有效的。
代码如下:
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
res = []
if not candidates:return []
candidates.sort()
n = len(candidates)
def dfs(candidates,cur_tem,target):
if target==0:
res.append(cur_tem)
return
if target<0:
return
for i in range(len(candidates)):
if candidates[i]>target:break
if i>0 and candidates[i]==candidates[i-1]:
continue
dfs(candidates[i+1:],cur_tem+[candidates[i]],target-candidates[i])
dfs(candidates,[],target)
return res
题目描述:给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
基本思路:回溯法,迭代法
代码如下:
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
#迭代
# n = len(nums)
# res = [[]]
# for i in nums:
# res = res + [[i]+num for num in res]
# return res
#回溯
n = len(nums)
res = []
def helper(i, tem):
res.append(tem)
for j in range(i,n):
helper(j+1,tem+[nums[j]])
helper(0, [])
return res
题目描述:给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
基本思路:与组合总数2类似,去重的方法类似
class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
res = []
nums.sort()
def helper(nums,tem):
res.append(tem)
for i in range(len(nums)):
if i>0 and nums[i]==nums[i-1]:
continue
helper(nums[i+1:],tem+[nums[i]])
helper(nums,[])
return res
子集2和组合总和2,相对于全排列2的区别在于,全排列中,子列顺序不同也算不同的子列。而子集和组合总和,只要子列里数字相同,就只能算法一个子列。所以可以看到,递归的时候
helper(nums[i+1:],tem+[nums[i]]) #子集,组合总和
helper(nums[:i]+nums[i+1:],tem+[nums[i]]) 全排列
就是说前者的选择列表是当前元素右边的数组
后者是除了当前元素的数组