昨天有我詹的圣诞大战
可惜了,我一上午都在图书馆修改文档
没有看直播
中午回来看比赛和评论也是十分精彩啊
老詹受伤啊,格林打脏球啊
我是老詹球迷,但说实话我并不讨厌格林
虽然很多球迷骂他打球脏
我之前也有类似的想法
但是这不就是大家口口声声怀念的90年代打球的风格吗?
有身体对抗,虽然有些动作是不必要的附带动作,这个格林也确实有
实话说:格林是个讨人厌的敌人,但却是很受喜爱的队友
最后看到老詹受伤了
说实话,当时心揪了一下
因为到了这个年龄段,任何伤病都可能是致命的
即使老詹是个铁人
还好晚上看到伤势不大
突然觉得之前说的都在打脸
啊哈哈哈哈哈哈哈
47-全排列II
给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
思路:
这一题和上一题很类似,但是要比上一题有难度,因为给定的数列可能包含重复数字。大家都知道去重一直是个比较难的问题,在这道主体用回溯法解决的题目中更是如此了。在看我这道题的解法之前,大家可以先看看我写的上一篇文章,在那儿我会对无重复数字的全排列情况有比较详细的说明,方便后续大家的理解。
程小新同学:LeetCode46-全排列zhuanlan.zhihu.com
这一题我的思路大致如下:
- 首先用回溯法求得所有数字的排列情况(包括重复数字),这里面的情况就是全部情况组合了。具体算法就是我在上一篇文章中写的算法,大家可以过去看看参考
- 接下来就是对每一种情况进行查重分析了,如果该组合之前出现过了,则舍弃;反之则保留。关于这里面的查重分析,我用了两种方法。第一种是最暴力的,就是预先定义一个列表集合final_list=[],该集合专门用来保存每一种合乎要求的组合,当你得到一种新的组合情况new_answer后,可直接用if new_answer in final_list来判断。第二种方法就是设置一个字典,其中key值专门保存每一种情况组合,因为字典的key值是不重复的,刚好可以得到我们想要的无重复情况组合,而且字典的遍历也是挺快的。
方法一代码如下:
class Solution:
# 此题和46题也是一样,主要采用回溯法
# 本题的难度就是给定数组nums中可能包含重复数字,这个就比较头疼了
def permuteUnique(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
# 定义保存最后结果的列表集合
final_answer = []
# 获取给定nums数组的长度
nums_length = len(nums)
# 核心的递归函数
def back(nums=nums, answer=[]):
"""
:param answer: List[int]-->定义保存每一种情况组合的临时列表集合
:return: None
"""
# 如果临时结果集中的元素和给定数组nums长度相等,说明拿到了结果
if len(answer) == nums_length and answer not in final_answer:
final_answer.append(answer)
return
for index in range(len(nums)):
nums_copy = []
nums_copy.extend(nums)
nums_copy.pop(index)
back(nums_copy, answer + [nums[index]])
back()
return final_answer
if __name__ == "__main__":
nums = [1, 1, 2, 3]
result = Solution().permuteUnique(nums)
print(result)
这种类似于暴力法的方法,执行效率肯定是极差的,只有10%左右,主要是供大家思考的。
方法二代码如下:
class Solution:
# 此题和46题也是一样,主要采用回溯法
# 本题的难度就是给定数组nums中可能包含重复数字,这个就比较头疼了
# 我这儿的去重属于暴力检索法,就是把得到的每一种情况answer与保存结果集合final_answer比较,
# 如果answer不在final_answer中,则可以添加,否则舍去
def permuteUnique(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
# 定义一字典,其中key值用来保存组合情况,方便去重,不过这儿组合情况是用tuple保存,到最后还得转换
temp_answer = {}
# 定义保存最终结果的集合
final_answer = []
# 获取给定nums数组的长度
nums_length = len(nums)
nums.sort()
# 核心的递归函数
def back(nums=nums, answer=[]):
"""
:param answer: List[int]-->定义保存每一种情况组合的临时列表集合
:return: None
"""
# 如果临时结果集中的元素和给定数组nums长度相等,说明拿到了结果
if len(answer) == nums_length:
new_answer = tuple(answer)
temp_answer[new_answer] = 1
return
for index in range(len(nums)):
nums_copy = []
nums_copy.extend(nums)
nums_copy.pop(index)
back(nums_copy, answer + [nums[index]])
back()
for index in temp_answer.keys():
final_answer.append(list(index))
return final_answer
if __name__ == "__main__":
nums = [1, 2, 3]
result = Solution().permuteUnique(nums)
print(result)
该方法稍微高级一些,但是执行效率也是在20%左右。
我的这两种方法说实话都是挺辣鸡的,我有自知之明的,啊哈哈哈哈哈。但是在网上找了一些博客,上面写的方法说实话不是特别好理解,而且很多博主的思路和我的不一样,他们主要想的是如何进行两个数的交换,而我的突破口则是如何组合。如果有哪位大佬看到我这篇文章,对我写的方法中去重有更好的思路,还望不吝赐教啊,非常感激了!!!
2018/12/27 晚9:30 第二次修改
这篇文章今天上午刚发出去,就有笔友给我推荐了一种新的方法,特别受用,效率奇高。所以我迫不及待的想分享出来,在这儿也特别感谢这位笔友--吴垠,在这篇文章的下面回复中大家可以找到他,真的是太感谢了,谢谢谢谢!!!
这位笔友他给我提出的新思路就是:可以把这道题看作是不停地求next Permutation的问题,next Permutation是指求给定序列nums的下一个较大序列的意思,这其实也是之前的第31题,大家可以先看看我之前写的这篇文章,熟悉熟悉。
程小新同学:LeetCode31-下一个排列zhuanlan.zhihu.com
看完这篇文章之后,我就接着讲我的思路了
- 首先将给定的序列nums从小到大排序
- 然后对这个nums序列不停地用nextPermutation()函数,找到其下一个较大序列,并且用一集合final_answer保存其结果。
- 直到最后得到的序列恰好是从大到小排列的顺序,立即退出
大概流程可以用下图来表示了。
上面写的应该比较清楚了,怎么样,是不是有种恍然大悟的感悟,有些牛逼啊!
代码如下:
class Solution:
# 借鉴31题--下一个排列做,不停的递归
# 思路大概就是:首先将给定的序列nums从小到大排序,然后对这个nums序列不停地用nextPermutation()函数
# 找到其下一个较大序列,并且用一集合final_answer保存其结果
# 直到最后得到的序列恰好是从大到小排列的顺序,立即退出
def permuteUnique(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
# 如果给定nums序列的长度小于等于1,那没有排列的必要了
if len(nums) <= 1:
return [nums]
# 首先将nums序列按照从小到大的顺序排列。方便后面操作
nums.sort()
nums_copy = []
nums_copy.extend(nums)
# 定义保存最终结果的集合,初始元素就是排序后的nums序列
# 这儿可能会有人问了,为什么不直接用final_answer = [nums],我刚开始就是这样操作的,发现结果始终有错
# 你发现添加的元素经过下续while循环操作后变成了nums的逆序排列
# 至于其原因是什么我现在还不清楚
final_answer = [nums_copy]
while True:
# 每次取得nums序列的下一个较大序列,注意:nums序列经过self.nextPermutation()函数处理后,
# 顺序也会发生相应改变,所以我们才能每次直接调用nums序列
new_answer = []
new_answer.extend(self.nextPermutation(nums))
# 如果得到的下一个较大序列与初始的按从小到大排列的nums序列相同,说明初始序列是一个只有一个元素重复循环的序列
# 此时就不需要再次添加初始序列了,因为刚开始就添加了
if new_answer != nums_copy:
final_answer.append(new_answer)
# 取得nums序列的逆序排列
new_nums = []
new_nums.extend(nums)
new_nums.sort(reverse=True)
# 如果nums序列刚好是从大到小逆序排列,说明这就是最终的答案了。可以退出
if new_nums == nums:
break
return final_answer
# 该函数的作用:取得nums序列的下一个较大序列
def nextPermutation(self, nums):
index = len(nums) - 2
while index >= 0 and nums[index] >= nums[index + 1]:
index -= 1
if index >= 0:
self.swap(nums, index)
# 然后将index后面的值用sort()方法从小到大排列
nums_copy = []
nums_copy.extend(nums[index + 1:])
nums_copy.sort()
nums[index + 1:] = nums_copy
return nums
# 将找到的index下标值与相邻最大的值交换
def swap(self, nums, index):
for next_index in range(len(nums)-1, index, -1):
if nums[next_index] > nums[index]:
temp = nums[index]
nums[index] = nums[next_index]
nums[next_index] = temp
return
if __name__ == "__main__":
nums = [1, 1, 3, 4]
result = Solution().permuteUnique(nums)
print(result)
执行效率奇快,在90%以上。
要是大家还有什么奇思妙想,也请多多指教啊!!!