leetcode刷题日记之---排列问题

排列问题

  • 第k个排列
  • 下一个排列
  • 全排列
  • 全排列2
  • 回文排列2
  • 组合总和
  • 组合总和2
  • 子集1
  • 子集2
  • 总结1:

第k个排列

题目描述:

给出集合 [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

全排列2

题目描述:给定一个可包含重复数字的序列,返回所有不重复的全排列。

思路分析:与前一题不同的是,这里需要返回所有不重复的全排列。有两种方法,第一种就是按照前一天得到所有全排列再去重。这里时间复杂度较高。第二种就是对前一题的回溯算法进行剪枝。

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]]) #防止重复利用

回文排列2

题目描述:给定一个字符串 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

组合总和2

题目描述:给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。
说明:

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

输入: 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

子集1

题目描述:给定一组不含重复元素的整数数组 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

子集2

题目描述:给定一个可能包含重复元素的整数数组 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

总结1:

子集2和组合总和2,相对于全排列2的区别在于,全排列中,子列顺序不同也算不同的子列。而子集和组合总和,只要子列里数字相同,就只能算法一个子列。所以可以看到,递归的时候

 helper(nums[i+1:],tem+[nums[i]]) #子集,组合总和
 
 helper(nums[:i]+nums[i+1:],tem+[nums[i]]) 全排列

就是说前者的选择列表是当前元素右边的数组
后者是除了当前元素的数组

你可能感兴趣的:(leetcode刷题)