leetcode回溯 (python)

1、 leetcode78 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)

from copy import deepcopy
class Solution(object):
    def subsets(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        ans=[]
        l = len(nums)
        if l == 0:
            return [[]] #注意这一步返回的是[[]]而不是[],[]是空列表,因此不会进入循环,而[[]]是一个元素为[]的非空列表(即[]长度为0,[[]]长度为1)
        for x in self.subsets(nums[:l-1]):
            ans.append(x)
            if x:
                ans.append([nums[l-1]]+x)
            else:
                ans.append([nums[l-1]])
        #ans.append([])
        print(ans)
        return ans

我的思想是对于每一个新元素,都在原有子集上添加该新元素即可。
其他思想有:
1、回溯法
贴一个C++的回溯代码

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> res;
        vector<int> item;
        res.push_back(item);
        generate(0, nums, item, res);
        return res;        
    }
private:
    void generate(int i, vector<int> &nums, vector<int> &item, vector<vector<int>> &res)
    {
        if(i >= nums.size())  return;
        item.push_back(nums[i]);
        res.push_back(item);
        generate(i + 1, nums, item, res);
        item.pop_back();
        generate(i + 1, nums, item, res);
    }
};

2、位运算

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        l = len(nums)
        res = []
        for i in range(2**l):
            subset = []
            for j in range(l):
                if i& (1<<j):#与nums中元素逐位与,当与的结果非零时,说明该元素在集合中
                    subset.append(nums[j])
            res.append(subset)
        return res

这道题按位运算也很精妙,1<<n表示2的N次方


2、leetcode90

class Solution(object):
    def subsetsWithDup(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """ 
        l = len(nums)
        if l == 0:
            return [[]]
        ans =[]
        for x in self.subsetsWithDup(nums[:l-1]):
            if x not in ans:
                ans.append(x)
            if x:
                if [nums[l-1]]+x not in ans:
                    ans.append(sorted([nums[l-1]]+x)) #排好序的列表
            else:
                if [nums[l-1]] not in ans :
                    ans.append([nums[l-1]]) 
        return ans

比较鸡贼==直接在第一种上面改,改成排好序的然后判断即可


3、leetcode40
首先贴一个标准的

class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        candidates.sort()
        res = []
        def helper(candidates, target, t):
            if target == 0:
                res.append(t)
                return

            for i, num in enumerate(candidates):
                if i > 0 and num == candidates[i-1]:
                    continue
                if target >= num:
                    helper(candidates[i+1:], target - num, t + [num])
                else:
                    break
        helper(candidates, target, [])
        return res

然后是我改了好久的。。。回溯好难

class Solution(object):
    def combinationSum2(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        if not candidates:
            return []
        candidates.sort()
        l = len(candidates)
        self.res = [[]]
        self.helper(0,0,target,candidates,[])
        return self.res[1:]
    def helper(self,i,s,target,nums,item):
        l = len(nums)
        if i >=l or s>target:
            return
        else:
            #print (item,i)
            s += nums[i]
            item.append(nums[i])
            #print (s,target,item)
            if s==target:
                c = copy.deepcopy(item)
                if c not in self.res:
                    self.res.append(c)
            self.helper(i+1,s,target,nums,item)
            item.pop()
            s -=nums[i]
            self.helper(i+1,s,target,nums,item)
        



4、leetcode22

import random
class Solution(object):
    def generateParenthesis(self, n):
        """
        :type n: int
        :rtype: List[str]
        """
        self.res=[]
        lbracket = n
        rbracket = n
        self.helper(1,"",lbracket,rbracket,n)
        return self.res
    def helper(self,i,item,lbraket,rbracket,n):
        if i>n*2+1:
            return
        left= 0
        right = 0
        print(i,item)
        for j in item:#不符合条件
            if right>left:
                return
            if j=="(":
                left +=1
            else:
                right +=1
        if left == n and right == n:
            self.res.append(item)
            return
        if lbraket>0 :
            self.helper(i+1,item+"(",lbraket-1,rbracket,n)
        if rbracket >0:
             self.helper(i+1,item+")",lbraket,rbracket-1,n)
        if lbraket<=0 and rbracket <=0:
            return

        

这个题一开始做的是input=[((()))],但是这么做会导致只能产生一种顺序的结果,也就是说,当选择了第一个 “(”后, 如果不选择位置1、2处的 “(”,在后面都不能选择“(”了,为了解决这个问题。我们选择记录左右括号的数目,加入回溯。算法步骤:
1、判断是否符合解,符合就将其加入
2、开始回溯:

  • 当左括号还有剩时,说明下一个位置可以选择加入左括号,并将左括号加入回溯,输入下一个位置为左括号时的全部结果。
  • 当后括号有剩时,如上。(注意很可能左右括号都有,因此一定是if 而不是elif),要在此位置上回溯所有可能。
  • 当左右都不剩时,返回。

5、leetcode51

#
# @lc app=leetcode.cn id=51 lang=python
#
# [51] N皇后
#
import copy
class Solution(object):
    def solveNQueens(self, n):
        """
        :type n: int
        :rtype: List[List[str]]
        """
        self.res = []
        item = []
        pre = []#记录一下已经占用的数组列数
        self.helper(0,pre,item,n)
        return self.res
    
    def helper(self,i,pre,item,n):
        if i>n or len(set(pre))!=len(pre):#如果pre中有重复元素,说明不满足
            return 
        #print(pre)
        m = list(enumerate(pre))
        if m:
            for x in range(len(m)):#判断对角线
                for y in range(x+1,len(m)):
                  #  print(m[x],m[y])
                    if m[x][1]-m[x][0] == m[y][1]-m[y][0] or m[x][1]+m[x][0] == m[y][1]+m[y][0]:
                        return
        if len(pre)==n and item not in self.res: #满足的解
            c = copy.deepcopy(item)
            self.res.append(c)
            return
        for j in range(n):
            tmp=""
            for x in range(n):
                tmp +='.'
            tmp =tmp[:j]+'Q'+tmp[j+1:]
            pre.append(j)
            item.append(tmp)
            self.helper(i+1,pre,item,n)
            item.pop()
            pre.pop()



对于我这种菜鸡果然还是太勉强了。。。虽然是自己写的,但是写的我这口气差点没过来。我趁着还能打字总结一下坑点吧。。

  • 首先对问题分析,分析如何递归,我们需要输出问题的所有解,因此res用来保存所有解,item保存当前解。但是对于当前str元素的选择,仍存在遍历(也就是说,item只能保存“xxxx”,还要对每列试探放入Q),因此我们选择遍历每列,同时设置tmp字符串加入item当前解中,每次递归完成后,对新列遍历,因此要还原tmp。
  • 其次是条件的编写。这里的条件是每行、每列、主副对角线不重复,由于我们是按行递归的,行必然不重复。设置pre=[]保存每次选择的列,如果入栈的数在之前出现过,说明列有重复,不满足,即返回。这样只剩下对对角线的处理。
  • 与(x,y)在一条主对角线的元素坐标(i,j)有这样的关系: x-i == y-j 也就是y-x==j-i
    与(x,y)在一条副对角线的元素坐标(i,j)有这样的关系: y+x==j+i
  • 基于以上两点,利用python中enumerate对象,处理可以很快得到结果。

我中间调试过程特别痛苦,写成了pre.remove(j),我也布吉岛自己为什么有些失了智:)


6、leetocode473
①先贴一个评论里看到的,比较暴力的方法(which 我也咩想到吧)

class Solution(object):
    def makesquare(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        nums.sort(reverse=True)#逆序减少回溯次数
        self.n = len(nums)
        self.l = sum(nums)/4
        if self.n<4 or sum(nums) % 4 !=0:
            return False
        return self.helper(0,nums,0,0,0,0) 

    def helper(self,i,nums,s1,s2,s3,s4):#s1\s2\s3\s4表示桶1234的和
        if i == self.n:
            if s1 == self.l and s2== self.l and s3 == self.l and s4 == self.l:
                return True
            return False
        if s1 > self.l or s2 > self.l or s3 > self.l or s4 > self.l:
            return False
        return self.helper(i+1,nums,s1+nums[i],s2,s3,s4) or  self.helper(i+1,nums,s1,s2+nums[i],s3,s4) or self.helper(i+1,nums,s1,s2,s3+nums[i],s4) or self.helper(i+1,nums,s1,s2,s3,s4+nums[i])
    
        

②这个是我自己的思路加上看了一下别人怎么做的 很搞笑的是python3死活超时 python就过了。。。python是真滴慢

class Solution(object):
	def makesquare(self, nums):
		self.l = sum(nums)/4
		self.n = len(nums)
		if self.n<4 or sum(nums) % 4 !=0:
			return False
		s = [0 for i in range(4)]
		used = [0 for i in range(len(nums))]
		nums.sort(reverse=True)
		return self.helper(0,nums,s,used) 
	def helper(self,i,nums,s,used):
		if i > self.n:
			return False
		if i == self.n:
			if s[0] == self.l and s[1]==self.l and s[2]==self.l and s[3]==self.l:
				return True
			return False
		if used[i]!=-1:	#这个数没有被用过
			for j in range(4):
				if s[j] + nums [i] > self.l: #是否可以将他放入j桶中
					continue #不进行回溯 进入下一个桶 剪枝1
				used[i] =-1
				s[j] += nums[i]
				#print(i,s,used)
				if self.helper(i+1,nums,s,used):
					return True
				used[i] = 0
				s[j] -=nums[i]
		return False


因此,我们可以总结

回溯问题的算法流程:

回溯算法最重要的就是回溯函数 回溯函数一般分为两个部分:

  • 判断是否是问题的解—>对是问题的解做处理或对非解处理,写在函数前面
  • 回溯过程 ,这个过程挺麻烦的,而且要依据不同的要求做不同处理,总的来说,回溯的核心是对这个元素选 or 不选 的问题。同时,回溯过程伴随一些问题的处理,比如说构造新的选择数组等等,这些就具体问题具体分析了。

我只做了几道题,还有很多没有处理过,但是实在不想写回溯了,过几天有机会再多做几道,总结更全面一点的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值