简洁开发
1 赋值多个变量的值
num_1, num_2 = 666, 999
2 合并join
slogan = ["今", "天", "你", "学", "python", "了", "么", "?"]
real_slogan = "".join(slogan)
3 enumerate() 函数来获取索引-数值对
4 列出当前目录下的所有文件和目录名
import os
files = [file for file in os.listdir(".")]
5 颠倒键值对
dict_1 = {1: "python", 2: "java"}
new_dict = {value:key for key, value in dict_1.items()}
输出:
{'python': 1, 'java': 2}
6 列表推导式
功能:生成符合条件的新列表
arrayList=[[1,2,3],[4,5,6],[7,8,9]]
arrayNew = [yy for yy in arrayList if yy[0]%2==1]
#新列表为该行第一个数为偶数。
7 map的使用
8 return 1 if true else 0
return count>1 一个判断式可以直接返回bool值,true/false
滑动窗口
-
从第一个元素开始滑动窗口并逐个元素地向右滑,并根据你所求解的问题调整窗口的长度。
-
在某些情况下窗口大小会保持恒定,在其它情况下窗口大小会增大或减小。
-
问题的输入是一种线性数据结构,例如链表,数组或字符串
-
可以处理大小为k的子数组的最大和 easy
-
带有k个不同字符的最长子字符串 medium
leetcode1 两数之和
暴力
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
n = len(nums)
for i in range(n):
for j in range(i + 1, n):
if nums[i] + nums[j] == target:
return [i, j]
return []
leetcode3. 无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列!
如何移动?
我们只要把队列的左边的元素移出就行了,直到满足题目要求!一直维持这样的队列,找出队列出现最长的长度时候,求出解!
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
if not s:return 0
lookup = set()
left, max_len, cur_len = 0,0,0
for i in range(len(s)):
cur_len += 1
while s[i] in lookup:
lookup.remove(s[left])
left += 1
cur_len -= 1
lookup.add(s[i])
max_len = max(max_len,cur_len)
return max_len
链表
leetcode 21. 合并两个有序链表
递归
class Solution:
def mergeTwoLists(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
#终止条件:当两个链表都为空时,表示我们对链表已合并完成。
#如何递归:我们判断 l1 和 l2 头结点哪个更小,然后较小结点的 next 指针指向其余结点的合并结果。(调用递归)
if not l1: return l2 # 终止条件,直到两个链表都空
if not l2: return l1
# 递归调用
if l1.val <= l2.val: #比较出 较小结点的 next 指针
l1.next = self.mergeTwoLists(l1.next,l2) #继续调用 指向下一个较小的节点
return l1
else:
l2.next = self.mergeTwoLists(l1,l2.next)
return l2
迭代
class Solution:
def mergeTwoLists(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
#首先,我们设定一个哨兵节点 prehead ,这可以在最后让我们比较容易地返回合并后的链表。我们维护一个 pre 指针,我们需要做的是调整它的 next 指针。
prehead = ListNode(-1)
pre = prehead
#如果 l1 当前节点的值小于等于 l2 ,我们就把 l1 当前的节点接在 prev 节点的后面同时将 l1 指针往后移一位。否则,我们对 l2 做同样的操作。不管我们将哪一个元素接在了后面,我们都需要把 prev 向后移一位。
while l1 and l2:
if l1.val<=l2.val:
pre.next = l1
l1 = l1.next
else:
pre.next = l2
l2 = l2.next
pre = pre.next
pre.next = l1 if l1 is not None else l2
return prehead.next
leetcode 86. 分隔链表
与上一题的迭代法非常相似
class Solution:
def partition(self, head: Optional[ListNode], x: int) -> Optional[ListNode]:
#建立2个链表,分别存放小于x的节点,与大于等于x的节点信息;
#遍历一遍后,记录2个链表最后链接上的信息,将左链表即小的尾部接上头部
#大于等于部分的链表尾部可能会成环,故将r.next = None
#dummy node
linklist_left, linklist_right = ListNode(-1), ListNode(-1)
#将要遍历的指针指向dummyy node
l, r = linklist_left, linklist_right
while head is not None:
if head.val < x:
l.next = head
l = l.next
else:
r.next = head
r = r.next
head = head.next
l.next = linklist_right.next # 将小的尾部链接上大于等于的头部
r.next = None # 防止链表成环
return linklist_left.next
leetcode 206. 反转链表
迭代
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
pre,cur = None,head
while cur:
nexxt = cur.next
cur.next = pre #反转cur.next
pre = cur
cur = nexxt #使pre cur都能前进
return pre
leetcode 92. 反转链表 II
迭代
class Solution:
def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
dummy = ListNode(0)
pre,l = dummy,head
dummy.next = head
#第 1 步:通过第一次遍历找到pre,left,rightright,succ
#第 2 步:待反转的区域反转;
#第 3 步:把 pre 的 next 指针指向反转以后的链表头节点(right),把反转以后的链表的尾节点(left)的 next 指针指向 succ。
for i in range(left-1):
pre = pre.next
l = l.next
r = l
for i in range(right - left):
r = r.next
succ = r.next
n1,n2 = l,l.next
for i in range(right - left):
nex = n2.next
n2.next = n1
n1 = n2
n2 = nex
pre.next = r
l.next = succ
return dummy.next
leetcode 19. 删除链表的倒数第 N 个结点
- 为什么需要哑节点
例如:链表 head = [1,2,3,4,5],需要删除倒数第 5 个结点,也就是第一个节点。按照算法逻辑,应该首先找到倒数第 6 个节点。但由于头节点不存在前驱节点,因此我们需要在删除头节点时进行特殊判断。
但是添加一个哑节点(dummy node),让它的next 指针指向链表的头节点。这样一来,头节点的前驱节点就是哑节点本身。
- 主要思想
我们首先从头节点开始对链表进行一次遍历,得到链表的长度 L。为了方便删除操作,添加一个哑结点dummy,我们可以从哑节点开始遍历,当遍历到第 L−n+1 个节点(L-n次)时,它的下一个节点就是我们需要删除的节点,这样我们只需要修改一次指针,就能完成删除操作。
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
def getLen(head: [ListNode]) -> int:
getLen = 0
while head:
getLen += 1
head = head.next
return getLen
L = getLen(head)
dummy = ListNode(0,head)
cur = dummy
for i in range(L-n):
cur = cur.next
cur.next = cur.next.next
return dummy.next
leetcode 24. 两两交换链表中的节点
三指针 迭代
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode(0)
dummy.next = head
temp = dummy
while temp.next and temp.next.next:
#标n1 n2的指针
node1 = temp.next
node2 = temp.next.next
#dummy指向2 2指向1 1指向3
temp.next = node2
node1.next = node2.next
node2.next = node1
#只用temp右移
temp = node1
return dummy.next
二指针或迭代器
二指针通常在排序过的数组或链表中搜索配对时很有用,需要查找满足某些约束的一组元素的问题
数组中的元素集是配对/三元组甚至子数组
leetcode 977
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。输入:nums = [-4,-1,0,3,10] 输出:[0,1,9,16,100] 解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
复杂度为O(nlogn)
def sortedSquares(self, nums: List[int]) -> List[int]:
return sorted(num * num for num in nums)
动态规划
leetcode 53. 最大子数组和
class Solution(object):
def maxSubArray(self, nums):
#思想是动态规划,nums[i-1]并不是数组前一项的意思,而是到前一项为止的最大子序和,和0比较是因为只要大于0,就可以相加构造最大子序和。如果小于0则相加为0,nums[i]=nums[i],相当于最大子序和又重新计算。其实是一边遍历一边计算最大序和
for i in range(1, len(nums)):
nums[i]= nums[i] + max(nums[i-1], 0)
return max(nums)
贪心算法
leetcode 455. 分发饼干
核心思想:为了尽可能满足最多数量的孩子,从贪心的角度考虑,应该按照孩子的胃口从小到大的顺序依次满足每个孩子,且对于每个孩子,应该选择可以满足这个孩子的胃口且尺寸最小的饼干
class Solution(object):
def findContentChildren(self, g, s):
g.sort()
s.sort()
i = j = count = 0
while i < len(g) and j < len(s):
if g[i]>s[j]:
j += 1
elif g[i] <= s[j]:
i += 1
j += 1
count += 1
return count
leetcode 121. 买卖股票的最佳时机
题目描述:一次股票交易包含买入和卖出,只进行一次交易,求最大收益。
核心思想:
只要记录前面的最小价格,将这个最小价格作为买入价格,然后将当前的价格作为售出价格,查看当前收益是不是最大收益。
class Solution(object):
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
#min_price = max(prices)
min_price = float('inf')
max_profit = 0
for i in range(len(prices)):
min_price = min(min_price,prices[i])
profit = prices[i] - min_price
max_profit = max(max_profit,profit)
return max_profit
leetcode 122. 买卖股票的最佳时机 II
class Solution(object):
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
#当访问到一个 prices[i] 且 prices[i] - prices[i-1] > 0,那么就把 prices[i] - prices[i-1] 添加到收益中。
maxProfit = 0
for i in range(1,len(prices)):
if prices[i] - prices[i-1] > 0:
maxProfit += prices[i] - prices[i-1]
return maxProfit
leetcode 605. 种花问题
flowerbed 数组中 1 表示已经种下了花朵。花朵之间至少需要一个单位的间隔,求解是否能种下 n 朵花。
贪心思想:
思路:遍历,能种花的情况是该位置为0,前后位置不为1
复杂度:时间复杂度O(n),空间复杂度O(1)
class Solution(object):
def canPlaceFlowers(self, flowerbed, n):
count = 0
#考虑只有1,2格的情况
if len(flowerbed) == 1:
if n == 0:
return True
elif n == 1 and flowerbed[0] == 0:
return True
else:
return False
if len(flowerbed) == 2:
if n == 0:
return True
elif n == 1 and flowerbed[0] == 0 and flowerbed[1] == 0:
return True
else:
return False
for i in range(0,len(flowerbed)):
if i == 0:
if flowerbed[i] == 0 and flowerbed[i+1] == 0:
flowerbed[i] = 1
count += 1
else:
continue
elif i < len(flowerbed)-1:
if flowerbed[i] == 0 and flowerbed[i-1] == 0 and flowerbed[i+1] == 0:
flowerbed[i] = 1
count += 1
else:
continue
elif i == len(flowerbed)-1:
if flowerbed[i] == 0 and flowerbed[i-1] == 0:
count += 1
#return count
return count>= n
防御式编程思想:在 flowerbed 数组两端各增加一个 0, 这样处理的好处在于不用考虑边界条件
这个方法真的好好啊555,好就好在我tmd想不到啊555
class Solution(object):
def canPlaceFlowers(self, flowerbed, n):
count = 0
tmp = [0] + flowerbed + [0]
for i in range(1,len(tmp)-1):
if tmp[i-1] == 0 and tmp[i] == 0 and tmp[i+1] == 0:
tmp[i] = 1
count += 1
return count>= n
leetcode 665 修改一个数成为非递减数组
这道题用的思想是贪心算法,你需要保证每次改动之后都对原序列的影响是最小的,换个说法就是每当出现逆序的时候,你应该思考到底是修改nums[i]好还是nums[i - 1]好
找数组中第一个 nums[i]>nums[i+1] 的 i 点,没找到直接true
找到了后,解决办法有两种:
1) nums[i] = nums[i+1]
2) nums[i+1] = nums[I]
更改完数组后若是非递减数列则true,否则false
class Solution(object):
def checkPossibility(self, nums):
count = 0
tmp = [float("-inf")] + nums + [float("inf")]
#找数组中第一个 nums[i]>nums[i+1] 的 i 点,没找到直接true
for i in range(1,len(tmp)-1):
if tmp[i] > tmp[i+1]:
#找到了后,nums[i]赋值为nums[i+1]
if tmp[i] > tmp[i+2]:
tmp[i] = tmp[i+1]
else:
tmp[i+1] = tmp[i]
break
else:
count += 1
if count == len(nums):
return True
count2 = 0
for i in range(1,len(tmp)-1):
if tmp[i] > tmp[i+1]:
return False
else:
count2 += 1
if count2 == len(nums):
return True
二分查找
剑指 Offer 53 - I. 在排序数组中查找数字 I
统计一个数字在排序数组中出现的次数。
#字典法
class Solution(object):
def search(self, nums, target):
dic = {}
for i in nums:
dic[i] = dic.get(i,0) + 1
if target not in dic.keys():
return 0
return dic[target]
#列表count方法
class Solution(object):
def search(self, nums, target):
return nums.count(target)
二叉树
二叉树介绍 https://blog.csdn.net/ycfxxr/article/details/122921489
先在开头总结一下,二叉树解题的思维模式分两类:
1、是否可以通过遍历一遍二叉树得到答案?如果可以,用一个 traverse 函数配合外部变量来实现,这叫「遍历」的思维模式。
2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值,这叫「分解问题」的思维模式。
无论使用哪种思维模式,你都需要思考:
如果单独抽出一个二叉树节点,它需要做什么事情?需要在什么时候(前/中/后序位置)做?其他的节点不用你操心,递归函数会帮你在所有节点上执行相同的操作。
递归
什么是递归呢?函数在运行时调用自己。
我们从中总结两个规律:
递归函数必须要有终止条件,否则会出错;
递归函数先不断调用自身,直到遇到终止条件后进行回溯,最终返回答案。
快排和归并
两个经典排序算法 快速排序 和 归并排序,对于它俩,你有什么理解?
如果你告诉我,快速排序就是个二叉树的前序遍历,归并排序就是个二叉树的后序遍历,那么我就知道你是个算法高手了。
快排
快速排序的原理如下:
-
从待排序列表中选取一个基准数据(通常选取第一个数据)。
-
将待排序列表中所有比基准数据小的元素都放到基准数据左边,所有比基准数据大的元素都放到基准数据右边(升序排列,降序反之)。用基准数据进行分割操作后,基准数据的位置就是它最终排序完成的位置,第一轮排序完成。
-
递归地对左右两个部分的数据进行快速排序。即在每个子列表中,选取基准,分割数据。直到被分割的数据只有一个或零个时,列表排序完成。
def quick_sort(array, start, end):
if start >= end:
return
mid_data, left, right = array[start], start, end
while left < right:
while array[right] >= mid_data and left < right:
right -= 1
array[left] = array[right]
while array[left] < mid_data and left < right:
left += 1
array[right] = array[left]
array[left] = mid_data
quick_sort(array, start, left-1)
quick_sort(array, left+1, end)
if __name__ == '__main__':
array = [10, 17, 50, 7, 30, 24, 27, 45, 15, 5, 36, 21]
quick_sort(array, 0, len(array)-1)
print(array)
前序中序后序
class TreeNode(object):
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
leetcode 144 二叉树的前序遍历
前序遍历首先访问根节点,然后遍历左子树,最后遍历右子树。
class Solution(object):
def preorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
#根->左->右 递归
if not root:
return []
return [root.val] + self.preorderTraversal(root.left) + self.preorderTraversal(root.right)
leetcode 94 二叉树的中序遍历
class Solution(object):
def inorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
#左->根->右 递归
if not root:
return []
return self.inorderTraversal(root.left) + [root.val] + self.inorderTraversal(root.right)
leetcode 145 二叉树的后序遍历
class Solution(object):
def postorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
#左->右->根 递归
if not root:
return []
return self.postorderTraversal(root.left) + self.postorderTraversal(root.right) + [root.val]
leetcode 102. 二叉树的层序遍历
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
广度优先搜索(BFS去父留子):
当队列里还有东西时
取出队头元素,查看该头元素的left和right
如果该left/right还没出现过,存入queue
class Solution(object):
def levelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
"""
if not root:
return []
res = []
queue = [root]
while queue:
length = len(queue)
level = []
for i in range(length):
node = queue.pop(0)
#queue都是去父留子
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
res.append(level)
return res
leetcode104. 二叉树的最大深度
深度优先 一棵二叉树的最大深度可以通过子树的最大深度推导出来,这就是分解问题计算答案的思路。
class Solution(object):
def maxDepth(self, root):
"""
:type root: TreeNode
:rtype: int
"""#递归
if not root:
return 0
else:
#整棵树的最大深度等于左右子树的最大深度取最大值,然后再加上根节点自己
left_max = self.maxDepth(root.left)
right_max = self.maxDepth(root.right)
return max(left_max,right_max) + 1
leetcode 226 翻转二叉树
深度优先 前序遍历
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if not root:
return None
print(root.val)
# 将当前节点的左右子树交换
root.left,root.right = root.right,root.left
# 递归交换当前节点的 左子树和右子树
self.invertTree(root.left)
self.invertTree(root.right)
# 函数返回时就表示当前这个节点,以及它的左右子树
# 都已经交换完了
return root
leetcode 101 对称二叉树
我们将根节点的左子树记做 left,右子树记做 right。比较 left 是否等于 right,不等的话直接返回就可以了。
如果相当,比较 left 的左节点和 right 的右节点,再比较 left 的右节点和 right 的左节点
递归函数的两个条件:
- 终止条件:left 和 right 不等,或者 left 和 right 都为空
- 递归的比较 left.left 和 right.right,递归比较 left.right 和 right.left
class Solution:
def isSymmetric(self, root: Optional[TreeNode]) -> bool:
#递归的比较 left.left 和 right.right,递归比较 left.right 和 right.left
if not root:
return True
def dfs(left,right):
#逻辑表达式取非后,or and交换
#递归的终止条件是两个节点都为空
if not (left or right):
return True
#两个节点中有一个为空:不对称
if not (left and right):
return False
#两个节点的值不相等:就不是对称二叉树
if left.val!=right.val:
return False
return dfs(left.left,right.right) and dfs(left.right,right.left)
# 用递归函数,比较左节点,右节点
return dfs(root.left,root.right)
回溯算法
leetcode46 全排列
我们定义的 backtrack 函数其实就像一个指针,在这棵树上游走,同时要正确维护每个节点的属性,每当走到树的底层叶子节点,其「路径」就是一个全排列。