LeetCode复习总结(动态规划,BFS,贪心算法等)

leetcode做了180道了,仅以此文记录那些刷题过程中遇到的坑与发现的新大陆,温故知新。

每道题都值得二刷,花时间仔细思考!立个Flag


'''
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
输入: ["eat", "tea", "tan", "ate", "nat", "bat"],
输出:[["ate","eat","tea"],  ["nat","tan"],  ["bat"]]
'''
#被惊艳到的一个想法,每个字母用一个质数表示,这样乘积的因子只有一种排列情况,如果值相当即属于异位词

'''
找到所有不重复子集
输入:[1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
这个代码好在
1.subsetsWithDup先执行就相当于init了,定义self.res等函数
2.list+[2],等价于list.append(2)
'''
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
	nums.sort()
	self.res = []
	self.helper(0, nums, [])
	return self.res

def helper(self, start, nums, tmp):
	self.res.append(tmp.copy())
	for i in range(start, len(nums)):
		if i > start and nums[i] == nums[i-1]:
			continue  # 一个for循环下不能有相同的情况,因为后续的会一样
		self.helper(i+1, nums, tmp+[nums[i]])
	return

'''
动态规划的一种思想,一般来讲,动态规划会有迭代格式,比如bp[i]=bp[i-1]+bp[i-2]等,这种时候总是要单独处理bp[0]、bp[1]等
这样不好,所以可以在bp前一二位直接补0、1等
这是leetcode91题解码方法,典型动态规划问题,记录下来主要说明补位思想
**************补位补的少,代码写的少,不用一直判断边界条件***********
'''
def numDecodings(self, s: str) -> int:
	s = '0' + s
	length = len(s)
	dp = [1] * (length)  # 补1占位

	for i in range(1, length):	# 由于补了位,从1开始
		c = 0
		single = s[i]  # 一位数
		if single != '0':
			c += dp[i - 1]
		double = s[i - 1:i + 1]  # 两位数
		if (not double.startswith('0')) and 0 < int(double) < 27:
			c += dp[i - 2]
		dp[i] = c
	return dp[-1]

'''
这个动态规划我是真服了
问题:给定一个字符串 S 和一个字符串 T,计算在 S 的子序列中 T 出现的个数。(例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是)
输入: S = "rabbbit", T = "rabbit"
输出: 3
一般二维打表动态规划求解,可是这个解决方案,真的,惊叹
通过逆向全搜索得到更新解
********逆向思想很重要,不止这一道题,正向有时候走不通,不妨思考下逆向,至于为什么逆向就得从原理来看了***********
'''
def numDistinct(self, s: str, t: str) -> int:
	ls, lt = len(s), len(t)
	dp = [1] + [0] * lt
	for i in range(ls):
		for k in range(lt - 1, -1, -1): #不能改为正向,dp数组要用上次外循环的
			if s[i] == t[k]:
				dp[k + 1] += dp[k]
	return dp[lt]

'''
BFS广度遍历思想
借助队列进行层次遍历
问题:给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:1.每次转换只能改变一个字母。2.转换过程中的中间单词必须是字典中的单词。
leetcode127题
注意:在把此题构成树结构时会导致前面若已经出现则后层不可能出现,因为是找最小值,可以利用此点进行剪枝与避重**************这个规律没想到,还是太弱了点
'''
def ladderLength(self, beginWord: str, endWord: str, wordList) -> int:
        # BFS层次遍历
        layer = 1  #层数为1
        if endWord not in wordList: return 0
        queue = [beginWord]
        flag = [False for _ in range(len(wordList))]
        while queue:
            queue2 = []
            layer += 1
            while len(queue) > 0:
                head = queue.pop(0)
                for i in range(len(wordList)):
                    if flag[i]:continue
                    per = wordList[i]
                    if self.canChange(head, per):
                        if per == endWord:
                            return layer
                        queue2.append(per)
                        flag[i]=True
            queue = queue2
        return 0

# 判断两个单词之间是否可以转换
def canChange(self, str1, str2):
	length1, length2 = len(str1), len(str2)
	flag = False
	if length1 != length2: return False
	for i in range(length1):
		if str1[i] != str2[i]:
			if flag:
				return False
			flag = True
	return True
'''
以上纯BFS做法,但是会存在超时情况:
第一种解决方案:双端BFS,本题目标在于找到最短,无所谓从begin到end或者从end到begin,可从中优化
第二种解决方案:set的效率远高于List

'''
贪心算法:o(n)级别解决问题,牛逼
leetcode135:老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
你需要按照以下要求,帮助老师给这些孩子分发糖果:
1.每个孩子至少分配到 1 个糖果。
2.相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?
例1:
	输入: [1,0,2]
	输出: 5
	解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。
例2:
	输入: [1,2,2]
	输出: 4
	解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。
		 第三个孩子只得到 1 颗糖果,这已满足上述两个条件。
'''
def candy(self, ratings: List[int]) -> int:
	# 贪心算法:从左往右,只要右边比左边大,则右边为左边+1;反过来,从右到左,若左比右大,则取两者最大值
	n=len(ratings)
	tmp =[1]*n
	for i in range(1,n):
		if ratings[i]>ratings[i-1]:
			tmp[i] = tmp[i-1]+1
	for i in range(n-2,-1,-1):
		if ratings[i]>ratings[i+1]:
			tmp[i]=max(tmp[i],tmp[i+1]+1)
	return sum(tmp)

'''
快慢指针
leedcode142:为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
'''
"""
	设从head需要走 a 步到达环的入口,如果环存在的话,
	再走 b 步可以再次到达该入口(即环的长度为b),
	如果存在环的话,上述描述的 快指针 fast 和 
	慢指针slow 必然会相遇,且此时slow走的路长
	小于 a + b(可以自行证明),设其为 a + x,
	当快慢指针相遇时,快指针已经至少走完一圈环了,
	不妨设相遇时走了完整的m圈(m >= 1),有:
	快指针走的路长为 a + mb + x
	慢指针走的路长为 a + x
	由于快指针fast 走的路长始终是慢指针的 2倍,所以:
	a + mb + x = 2(a + x)
	化简可得:
	a = mb - x  -------------  (*)
    当快指针与慢指针相遇时,由于 <性质1> 的存在,
    可以在链表的开头初始化一个新的指针,
    称其为 detection, 此时 detection 距离环的入口的距离为 a,
    
    此时令 detection 和 fast 每次走一步,
    会发现当各自走了 a 步之后,两个指针同时到达了环的入口,理由分别如下:
    
    detection不用说了,走了a步肯定到刚好到入口
    fast已经走过的距离为 a + mb + x,当再走 a 步之后,
    fast走过的总距离为 2a + mb + x,带入性质1的(*)式可得:
    2a + mb + x = a + 2mb,会发现,fast此时刚好走完了
    整整 2m 圈环,正好处于入口的位置。
    
    基于此,我们可以进行循环,直到 detection 和 
    fast 指向同一个对象,此时指向的对象恰好为环的入口。

"""
	if not head or not head.next:
		return None
	p1, p2 = head, head
	while p2 and p2.next:
		p1 = p1.next
		p2 = p2.next.next
		if p1==p2:
			print(p1.val)
			print(p2.val)
			p2 = head
			print(p2.val)
			while p1!=p2:
				p1 = p1.next
				p2 = p2.next
				print(p1.val,p2.val)
			return p1
	return None

'''
惊艳的思想:leetcode152	
给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是连续子数组。
'''
def maxProduct(self, nums: List[int]) -> int:
	'''
	问题实质简化成:双向搜索找最大
	发现其实把所有值连乘找最大即可,最多处理下0的问题
	-1 or 1 = -1; 1 or 1 = 1; 0 or 1 = 1实际就是0的时候取1,其余时候不变
	如[-2,0,1]如果做or 1处理得到[-2,0,1],不做处理得到[-2,0,0]
	'''
	B = nums[::-1]
	for i in range(1, len(nums)):
		nums[i] *= nums[i - 1] or 1
		B[i] *= B[i - 1] or 1
	return max(max(nums),max(B)) 	

 

你可能感兴趣的:(#,leetcode)