基础
1…Python的可变数据类型和不可变数据类型?
Python 中的可变数据类型有列表、字典和集合 ;不可变数据类型有整数、浮点数、字符串、元组和布尔值 。
可变对象可以在原来地址上修改元素,不可变则不行(即不能在自己身上增删改),若要修改可以使用对象拼接赋值给新的对象,总之不能修改自身
2.Python的内置数据结构?
列表,集合,元组,字典
list :有序可修改。通过索引进行查找、切片。使用方括号[ ]表示;
tuple :有序不可修改。可查询、切片操作。元组将多样的对象集合到一起,通过索引进行查找。用小括号( )表示;
set :集合,无序可修改,无重复,无索引 ,不能跟列表一样切片。自动去重。使用set([ ])表示;
dict:字典是一组key-value ,通过key查找,无顺序,用{ }表示。
3.字符串逆序
1 2 s[::-1 ] '' .join(reversed (str (n)) 其中reversed 返回迭代器对象
4.数值
没有最大整数def
5 .列表
lines=sorted(lines)
words.reverse()
原地翻转
ls.append(path[:])
:保存列表结果用这个。
ls.append(path)
,
ls.pop(0)
ls.sort()
原地排序
对这种二维列表去重
[[1, 2, 5], [1, 7], [1, 6, 1], [2, 6], [2, 1, 5], [7, 1]]
由于列表是不可哈希的类型,需要先将每个子列表转换为元组,再使用集合进行去重,最后再转换回列表。
为元组是可哈希的,可以作为集合的元素。然后,我们将这些元组放入集合中,集合会自动去重。
extend(iterable)
: 将可迭代对象中的元素添加到列表的末尾。
6.del 删除
在这个例子中,我们首先创建了一个列表[1, 2, 3]
并让变量a
指向它。然后,我们让变量b
也指向这个列表。当我们使用del a
时,我们只是删除了变量a
,而列表[1, 2, 3]
仍然存在,因为变量b
仍然指向它。
所以,当我们说*“删除的是变量而不是数据*”时,我们的意思是del
关键字删除的是变量(也就是数据的引用),而不是实际的数据对象。希望这个解释对你有所帮助!
1 2 3 a = [1 , 2 , 3 ] b = a del a
1 2 3 a = [0 , 2 , 2 , 3 ] a.remove(2 ) print (a)
7.字符串方法
1 2 3 4 s.strip() s=' ' .join(t) s.replace(' ' ,' ' ) ls=s.split()
8.if not, 真假
False
、None
、空字符串""
、数字0
、空列表[\]
、空字典{}
或空元组()
if not stack
: 当stack为NOne
9.is和== 的区别
is判断两个对象的内存地址是否相同 == 判断两个对象的值是否相同
10.字典
字典的键必须是不可变的 类型。不可以使用列表作为键。字典键的常见类型包括整数、浮点数、字符串、元组等不可变类型。
1 2 3 4 5 6 7 8 9 my_dict = {1 : 'one' , 2 : 'two' , 3 : 'three' } another_dict = {'apple' : 3 , 'banana' : 5 , 'orange' : 2 } yet_another_dict = {(1 , 2 ): 'tuple_key' , (3 , 4 ): 'another_tuple_key' } my_dict = {('John' , 25 ): 'value1' , ('Alice' , 30 ): 'value2' }
如何根据key进行排序?
在 sorted()
函数中指定要排序的元组列表和一个 key
参数,该参数用于指定排序的关键字。在这里,我们可以将 key
参数设置为 lambda x: x[0]
,以根据元组的第一个元素(即键)进行排序。
1 2 3 4 5 my_dict = {'apple' : 10 , 'orange' : 5 , 'banana' : 20 , 'grape' : 7 } sorted_dict = dict (sorted (my_dict.items(), key=lambda x: x[0 ])) print (sorted_dict)
lambda函数可以换:
1 2 3 4 def get_key (item ): return item[0 ]
方法
get(k,default)
11.推导
可以从一个数据序列构建另一个新的数据序列的结构体
1 2 3 4 5 6 7 8 9 10 11 names = ['Bob' ,'Tom' ,'alice' ,'Jerry' ,'Wendy' ,'Smith' ] new=[ name.upper() for name in names] multiples = [i for i in range (30 ) if i % 3 == 0 ] dic={name:len (name) for name in names} sett={name.upper() for name in names} tt=(name.lower() for name in names)
12.常用的模块,标准库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 import logginglogging.basicConfig(filename="test.log" ,level=logging.INFO) logging.debug('debug message' ) logging.info("info message" ) logging.warn('warn message' ) logging.error("error message" ) logging.critical('critical message' ) import osimport sysdir (os)help (sys)path='E:\\' os.name os.system('mkdir temp.foo' ) os.getcwd() os.listdir(path) os.path.getmtime(path) os.stat(path) import shutilshutil.copyfile('temp.txt' , 'temp.out' ) import globglob.glob('*py' ) print (sys.argv)sys.stdin import rere.findall(r'\bf[a-z]*' , 'which foot or hand fell fastest' ) re.sub(r'(\b[a-z]+) \1' , r'\1' , 'cat in the the hat' ) import mathmath.cos(math.pi/4 ) math.log(1024 ,2 ) import randomrandom.choice([1 ,2 ,3 ]) random.sample(range (100 ), 10 ) random.random() random.randrange(6 ) from urllib.request import urlopenfor line in urlopen('http://43.192.14.14' ): line=line.decode('utf-8' ) print (line) import requestsres=requests.get('http://42.193.14.14/' ) print (res.headers)import datetimecurrent_datetime=datetime.datetime.now() print (current_datetime)formatted_datetime = current_datetime.strftime("%Y-%m-%d %H:%M:%S" ) print (formatted_datetime)import timecurrent_time = time.time() print ("Current Time:" , current_time)formatted_time = time.ctime(current_time) print ("Formatted Time:" , formatted_time)print ("Start" )time.sleep(3 ) print ("End after 3 seconds" )import zlibs=b'witch which has which witches wrist watch' len (s)t=zlib.compress(s) len (t)zlib.crc32(s) import jsondata = { 'no' : 1 , 'name' : 'Runoob' , 'url' : 'http://www.runoob.com' } json_str=json.dumps(data) print (repr (data))print (json_str)
13.迭代器
迭代器是一个可以记住遍历位置的对象。
迭代器有两个基本的方法:iter() 和 next() 。
许多内置对象都是可迭代的,例如列表、元组、字符串等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class MyIterator : def __init__ (self,data ): self .data=data self .index=0 def __iter__ (self ): return self def __next__ (self ): if self .index<len (self .data): result = self .data[self .index] self .index += 1 return result else : raise StopIteration my_list=[1 ,2 ,3 ,4 ] my_custom_iterator = MyIterator(my_list) for element in my_custom_iterator: print (element)
14. map(),filter(),reduce()
1 2 3 length=list (map (lambda x:len (x),names)) small=list (filter (lambda x:len (x)>4 , names)) product=reduce(lambda x,y:x*y, nums, 1 )
15.遍历技巧
1 2 for k in {'a':1,'b':2} : print(k)
字典中遍历 时,关键字和对应的值可以使用 items() 方法同时解读出来:
1 2 3 knights = {'gallahad' : 'the pure' , 'robin' : 'the brave' } for k,v in knights.items(): print (k,v)
序列中遍历时,索引位置和对应值可以使用 enumerate() 函数同时得到:
1 2 for i,v in enumerate (names): print (i,v)
同时遍历两个或更多的序列,可以使用 zip() 组合:
1 2 3 4 questions = ['name' , 'quest' , 'favorite color' ] answers = ['lancelot' , 'the holy grail' , 'blue' ] for q,a in zip (questions,answers): print ("Que:{} Ans:{}" .format (q, a))
要按顺序遍历一个序列,使用 sorted() 函数返回一个已排序的序列,并不修改原值:
1 2 for f in sorted (set (names)): print (f)
16.输入输出
读写文件
1 2 3 4 5 6 7 8 f=open ('temp.txt' ,'w' ) f.write(' python open file \n nandwnowdoi' ) f.close() f=open ('temp.txt' ,'r' ) str =f.read()ls=f.readlines() print (str ,ls)
pickle 模块:基本的数据序列和反序列化。
1 2 3 4 5 6 7 8 9 10 11 import pickledata1 = {'a' : [1 , 2.0 , 3 , 4 +6j ], 'b' : ('string' , u'Unicode string' ), 'c' : None } pkl_file=open ('temp.pkl' ,'wb' ) pickle.dump(data1, pkl_file) pkl_file.close() pkl_file = open ('temp.pkl' , 'rb' ) data2=pickle.load(pkl_file) pkl_file.close()
17.嵌套函数,外部变量
1 2 3 4 5 6 7 8 9 10 11 def outer_function (): outer_variable = 10 def inner_function (): nonlocal outer_variable outer_variable = 20 inner_function() print (outer_variable) outer_function()
好避免在函数之间共享状态,而是通过函数参数和返回值来传递信息
但是res[] 列表可以
18.copy 拷贝:
深拷贝(deep copy)和浅拷贝(shallow copy)是两种不同的复制方式,它们主要针对复杂对象(例如嵌套的列表或字典)中的子对象的复制方式而言。
浅拷贝:只是复制了容器对象本身和对其元素(复杂对象)的引用。 即新对象修改元素会改变原始元素
res.append(path.copy())
和 res.append(path[:])
都用于向列表 res
中添加列表 path
的副本。
path.copy()
:创建列表的浅拷贝,会复制列表中的元素。如果元素是对象如列表,则不会复制对象本身内容。
实际上,对于包含整数、字符串这种的列表,copy
和[:]
效果一样,
深拷贝:创建一个对象,递归复制对象内所有元素,是完全独立的副本
path.deepcopy()
实现深拷贝
19.转换
bin
20.集合
1 2 3 s.add(x) s.remove( x ) s.clear()
21.循环
1 2 3 for i in range (6 ): print (i) i += 1
20.生成器
生成器(Generator)是一种特殊的迭代器,它允许按需生成值,
生成器使用 yield
关键字来实现暂停和恢复执行,允许函数在每次产生值后暂停并在下一次调用时继续执行。
定义生成器:
生成器函数:使用 yield
关键字的函数。
1 2 3 4 5 6 7 8 def my_generator (): yield 1 yield 2 yield 3 gen = my_generator() print (next (gen)) print (next (gen)) print (next (gen))
** 生成器表达式** : 类似于列表推导式,但使用圆括号。
1 2 3 4 gen = (x for x in range (1 , 4 )) print (next (gen)) print (next (gen)) print (next (gen))
yield和return:
相同点:都是返回程序的执行结果;
区别:yield返回结果但程序还没有结束,return返回结果表示程序已经结束执行
22.谈谈对不定⻓参数的理解?
在不确定需要传多少参数时使用,args表示位置参数,元组形式保存,kwargs表示关键字参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 def cache (func ): cached_results = {} def wrapper (*args, **kwargs ): key = (args, tuple (kwargs.items())) if key not in cached_results: print ('不在缓存中' ) cached_results[key] = func(*args, **kwargs) return cached_results[key] return wrapper @cache def add (x, y ): print ("Calculating result..." ) return x + y result1 = add(2 , 3 ) result2 = add(2 , 3 ) result3 = add(4 , 5 ) print (result1) print (result2) print (result3)
23.多线程 threading 模块
方法:
threading.Thread(target=modify_shared_resource)
:创建线程,指定启动执行函数
run()
: 用以表示线程活动的方法。
start()
:启动线程活动。
join([time])
: 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
isAlive()
: 返回线程是否活动的。
getName()
: 返回线程名。
setName()
: 设置线程名
threading.Lock()
threading.Semaphore(value=2)
threading.Event()
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import threadingimport timeexitFlag = 0 class MyThread (threading.Thread): def __init__ (self,threadID,name,delay ): threading.Thread.__init__(self ) self .threadID=threadID self .name = name self .delay = delay def run (self ): print ("开始线程:" + self .name) print_time(self .name, self .delay, 5 ) print ("退出线程:" + self .name) def print_time (threadName, delay, counter ): while counter: if exitFlag: threadName.exit() time.sleep(delay) print ("%s: %s" % (threadName, time.ctime(time.time()))) counter -= 1 thread1 = MyThread(1 , "Thread-1" , 1 ) thread2 = MyThread(2 , "Thread-2" , 2 ) thread1.start() thread2.start() thread1.join() thread2.join() print ("退出主线程" )
线程同步
(1) 互斥量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import threadingshared=0 mutex=threading.Lock() def modift_shared (): global shared with mutex: shared+=1 threads=[] for _ in range (5 ): thread=threading.Thread(target=modift_shared) thread.start() threads.append(thread) for thread in threads: thread.join() print ("Final shared resource value:" , shared)
(2)信号量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import threadingshared_resource = 0 semaphore = threading.Semaphore(value=2 ) def modify_shared_resource (): global shared_resource with semaphore: shared_resource += 1 threads = [] for _ in range (5 ): thread = threading.Thread(target=modify_shared_resource) thread.start() threads.append(thread) for thread in threads: thread.join() print ("Final shared resource value:" , shared_resource)
(3) 事件(Event):
事件用于线程之间的通信,一个线程可以等待事件的触发,而另一个线程可以触发事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import threadingevent = threading.Event() def wait_for_event (): print ("Thread 1 is waiting for the event." ) event.wait() print ("Thread 1 received the event." ) def set_event (): print ("Thread 2 is setting the event." ) event.set () thread1 = threading.Thread(target=wait_for_event) thread2 = threading.Thread(target=set_event) thread1.start() thread2.start() thread1.join() thread2.join()
(4).条件变量
(5).队列
24.with
with
语句是 Python 中用于简化资源管理的语法结构,通常用于对文件、网络连接、线程锁等资源进行管理。
25。collections
26. 面向对象
接口:
1 2 3 4 5 from abc import ABC, abstractmethod class Strategy (ABC ): @abstractmethod def issue (self, *params ): pass
数据结构和算法
1.二叉树、
1.1二叉搜索树
对于根节点,左子树中所有节点的值 < 根节点的值 < 右子树中所有节点的值。
任意节点的左、右子树也是二叉搜索树,即同样满足条件 1.
中序遍历是一个升序序列
1.2 完全二叉树
除最底层最右边的节点没充满
最后一个非叶子节点
parent=(i-1)//2
通过高度 计算结点个数
左子树高度>右子树高度:右子树是满二叉
左子树高度=右子树高度:左子树是满二叉
1.3 平衡二叉树
左右两个子树的高度差的绝对值不超过 1 。
1.4 遍历
计算每层平均值:DFS, BFS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def averageOfLevels (self, root: Optional [TreeNode] ) -> List [float ]: result=[] level_sum=[] level_size=[] def dfs (root,depth ): if not root: return if depth<len (level_size): level_size[depth]+=1 level_sum[depth]+=root.val else : level_size.append(1 ) level_sum.append(root.val) dfs(root.left,depth+1 ) dfs(root.right,depth+1 ) dfs(root,0 ) for i in range (len (level_sum)): result.append(level_sum[i] / level_size[i]) return result
1.5 路径
因为列表是可变对象,如果直接将 path
添加到 res
,后续对 path
的修改会影响已经添加到 res
中的路径。
使用 path.copy()
创建路径的副本,确保不同路径之间的独立性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def back_paths (root:TreeNode ): res=[] path=[root.val] def backtrace (node ): if not node.left and not node.right: res.append(path[:]) return if node.left: path.append(node.left.val) backtrace(node.left) path.pop() if node.right: path.append(node.right.val) backtrace(node.right) path.pop() backtrace(root) return res
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def hasPathSum ( root: [TreeNode], targetSum: int ) -> bool : res=[] def dfs (node,path ): if not node: return path.append(node.val) if sum (path)==targetSum: res.append(path.copy()) print (node.val,path) dfs(node.left,path) dfs(node.right,path) path.pop() dfs(root,[]) return len (res)>0
2.回溯法
简单来说,回溯算法采用了一种 「走不通就回退」 的算法思想。
回溯算法通常用简单的递归方法 来实现,在进行回溯过程中更可能会出现两种情况:
找到一个可能存在的正确答案;
在尝试了所有可能的分布方法之后宣布该问题没有答案
全排列
模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 res = [] path = [] def backtracking (nums,cond ): if 遇到边界条件: res.append(path[:]) return for i in range (len (nums)): path.append(nums[i]) backtracking(nums) path.pop() backtracking(nums,cond)
子集:
backtracking(nums, index):
函数代表的含义是:在选择 nums[index]
的情况下,递归选择剩下的元素。
剪枝
3.栈
3.1最小栈
:能在常数时间内检索到最小元素的栈。
使用一个 min
变量记录当前栈中的最小值确实是一种方法。然而,当元素被弹出时,如果弹出的正好是最小值,那么我们就需要重新遍历整个栈找到新的最小值。这样在 pop
操作的时候就会变得更加耗时。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class MinStack : def __init__ (self ): self .stack=[] self .min_stack=[] def push (self, val: int ) -> None : self .stack.append(val) if not self .min_stack or val<=self .min_stack[-1 ]: self .min_stack.append(val) def pop (self ) -> None : poped=self .stack.pop() if poped==self .min_stack[-1 ]: self .min_stack.pop() def top (self ) -> int : return self .stack[-1 ] def getMin (self ) -> int : return self .min_stack[-1 ]
3.2单调栈:
单调递增栈:
栈底到栈顶递增。
新元素入栈,要保持递增,需要弹出所有比他大的元素
可以快速找到它前面比它小的元素和后面比它小的元素。
求解某个元素的下一个更大元素(Next Greater Element): 在一个数组中,找到每个元素右边第一个比它大的元素。
例子:每日温度
1 2 输入: temperatures = [73 ,74 ,75 ,71 ,69 ,72 ,76 ,73 ] 输出: [1 ,1 ,4 ,2 ,1 ,1 ,0 ,0 ]
找到每个元素下一个比他大的元素, 单调递减栈。
*stack*=[0(73)]
,*stack*=[2(75),3(71),4(69)]
这种写法 好理解
模板
1 2 3 4 5 6 7 for i in nums: while stack and n>nums[i]: stack.pop() if not stack: break stack.apped()
4.递归和迭代
递归,函数调用,栈帧:
参数压栈, 接受返回值,
就类似栈帧, 把调用参数逐个压栈,
多个参数可视为一个元组压栈.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def maxDepth (self, root: Optional [TreeNode] ) -> int : if not root: return 0 stack=[(root,1 )] maxdp=0 while stack: node,depth=stack.pop(0 ) if depth>maxdp: maxdp=depth if node.right: stack.append((node.right,depth+1 )) if node.left: stack.append((node.left,depth+1 )) return maxdp
5.贪心算法
贪心算法基于当前情况,做出下一步的最优解。因此,总是逐步迭代,问题规模逐渐变小。贪心算法每次局部最优,但不一定达到全局最优。需要证明达到,或者达到近似。
6.指针
快慢指针:
如果存在循环 ,终会相遇。
双指针:
明确含义
一般用while语句
滑动窗口
7.动态规划
7.1 五部曲:
确定dp数组(dp table)以及下标的含义
确定递推公式
dp数组如何初始化:根据递推公式得到需要初始化的底层值
确定遍历顺序:不同遍历顺序?
举例推导dp数组:验证结果思路,然后才写代码
除了写循环,也可以写成递归
双层dp:
某一点的状态需要遍历,使用其他节点内容填充,
即:
1 2 3 for ele in list : for ....: dp[ele]=XXXX.
7.2案例
300. 最长递增子序列
718. 最长重复子数组
子集划分
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
问题:是否存在一个子集的和为 nums和的一半?
dp[i][j]
:前i个元素中是否存在一个子集和为j
dp[n][nums和/2]
:最终结果
dp[i][0]
:子集包含空集,和为0
状态转移:dp[i-1][(j)]
如果前i-1个元素存在子集和为j,那么前i个元素就存在子集和为j。 i元素不放入子集。
如果前i-1个元素存在子集和为j-nums[i]
,那么前i个元素也存在子集和为j。 i元素放入子集
如果前i-1个元素不存在子集和为j,且和不为j-nums[i]
,那么前i个元素也不存在和为j。
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] o r d p [ i − 1 ] [ j − n u m s [ i ] ] dp[i][j]=dp[i-1][j] \quad or\quad dp[i-1][j-nums[i]]
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] o r d p [ i − 1 ] [ j − n u m s [ i ] ]
1049. 最后一块石头的重量 II
~~方法一:
dp[i][j]
:前 i
个石头中选择若干个,使得它们的总重量不超过 j
时,可能的最大石头重量。
dp[n][sum//2]
: 前n个元素,选择若干个,在其和不超过sum//2情况下,的最大和
结果:sum-dp[n][sum//2]-dp[n][sum//2]
1 dp[i][j]=max (dp[i-1 ][j],dp[i-1 ][j-stone]+stone)
方法二:
dp[i]
:是否存在一些石头,重量和为i。
结果:找到最后一个true,即为最大重量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def lastStoneWeightII (stones ): total_weight = sum (stones) target = total_weight dp = [False ] * (target + 1 ) dp[0 ] = True for stone in stones: for i in range (target, stone - 1 , -1 ): dp[i] = dp[i] or dp[i - stone] for i in range (target, -1 , -1 ): if dp[i]: return total_weight - 2 * i
方法三:
dp[i]
:总重不超过i
的情况下,最大重量
1 dp[j] = max (dp[j], dp[j - stone] + stone)
不同的二叉搜索树: [1…n]构成的…个数
卡特兰数:
c ( n ) = ∑ k = 1 n + 1 c ( k − 1 ) ⋅ c ( n − k ) c(n)=\sum_{k=1}^{n+1} c(k-1) \cdot c(n-k)
c ( n ) = k = 1 ∑ n + 1 c ( k − 1 ) ⋅ c ( n − k )
判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
!()[https://raw.githubusercontent.com/ddongzi/ddongzi.github.io/main/image/markdown/20231220161833.png ]
回文子串:
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
dp
定义:
dp[i]
: s[i,j]
是否存在回文子串。布尔类型
状态转移(递归):
dp[i][j]= s[i]==s[j] and dp[i+1][j-1]
特殊值处理,分情况:
i==j
: True
j-i>=1
:True
返回值:
用res 计数
初始化:
dp[i][j]
:False
遍历顺序:
![[Pasted image 20231220175747.png]]
1 2 for i in range (n-1 ,-1 ,-1 ): for j in range (i,n):
换零钱
dp[i]
: 凑成i 元地最小硬币书
8.背包问题
背包问题是动态规划
8.1 01背包
问题描述:
有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次 ,求解将哪些物品装入背包里物品价值总和最大。
例子:背包最大重量为4。
重量
价值
物品0
1
15
物品1
3
20
物品2
4
30
二维dp数组01背包:
1. dp数组定义,下标定义:
dp[i][j]
表示从下标为[0-i]
的物品里任意取,放进容量为j的背包,价值总和最大是多少。
表示前i个物品,放进j容量背包地价值最大总和。
![[Pasted image 20231226231423.png]]
递推方式
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
3. 初始化
dp[i][0]=0
初始化
![[Pasted image 20231226231731.png]]
dp[0][j]
初始化
![[Pasted image 20231226232510.png]]
其余值初始化:
由递推公式知道,是由上一层推导出来的,递推方向向右下,所以其余初始值都被覆盖,设置0即可
![[Pasted image 20231226232948.png]]
确定遍历顺序
遍历顺序不影响公式推导,所需的数据都是固定左上角唯一的
![[Pasted image 20231226233928.png]]
举例推导dp数组
做动态规划的题目,最好的过程就是自己在纸上举一个例子把对应的dp数组的数值推导一下,然后在动手写代码!
![[Pasted image 20231226234414.png]]
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 limit=4 weights=[1 ,3 ,4 ] values=[15 ,20 ,30 ] m=len (weights) n=limit+1 dp=[[0 ]*n for _ in range (m)] for i in range (m): dp[i][0 ]=0 for j in range (1 ,n): if j>=weights[0 ]: dp[0 ][j]=values[0 ] for i in range (1 ,m): for j in range (1 ,n): dp[i][j]=dp[i-1 ][j] if j-weights[i]>=0 : dp[i][j]=max (dp[i][j],dp[i-1 ][j-weights[i]]+values [i]) print (dp)
一维dp数组(滚动数组)
在使用二维数组的时候,递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
其实可以发现如果把dp[i - 1]
那一层拷贝到dp[i]
上,不如只用一个一维数组了,只用dp[j]
(一维数组,也可以理解是一个滚动数组)。
这就是滚动数组的由来,需要满足的条件是上一层可以重复利用,直接拷贝到当前层。
本质是一层一层计算。
![[Pasted image 20231226232510.png]]
dp数组定义:
dp[j]
表示:容量为j的背包,所背的物品价值可以最大为dp[j]。
递推公式:
1 dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
初始化:
初始化为0, 右边通过左边地推而来
确定遍历顺序
1 2 3 4 5 for (int i = 0 ; i < weight.size(); i++) { for (int j = bagWeight; j >= weight[i]; j--) { dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } }
如果背包容量是正序遍历,显然dp[2]
中物品0被重复放了2次,不符合只放一次的背包。
1 2 dp[1] = dp[1 - weight[0]] + value[0] = 15 dp[2] = dp[2 - weight[0]] + value[0] = 30
如果背包容量是倒序遍历,每次取得状态不会和之前取得状态重合,这样每种物品就只取一次了。
1 2 dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp数组已经都初始化为0) dp[1] = dp[1 - weight[0]] + value[0] = 15
此外,不可以颠倒内外部循环。
5. 举例推导dp数组
![[Pasted image 20231227114952.png]]
8.2 完全背包:
完全背包和01背包问题唯一不同的地方就是,每种物品有无限件 。
01背包和完全背包唯一不同就是体现在遍历顺序上
8.分治:
拆解问题,尤其是常规加上额外条件时
9.数组
元素是删除不了的,只能覆盖
滑动窗口
双指针
10.堆
堆是一种特殊的数据结构,它是一个完全二叉树,且父节点的值小于或等于其子节点的值。
heapq
是 Python 标准库中的一个模块,提供了一个实现堆队列算法的模块。堆是一种特殊的数据结构,它是一个完全二叉树,且父节点的值小于或等于其子节点的值。在 Python 的 heapq
模块中,堆总是以列表的形式表示,且堆的第一个元素是最小(或最大)的元素。
heapq
提供了一些用于在堆上进行操作的函数,包括将列表转换为堆、向堆中插入元素、从堆中弹出最小(或最大)元素等操作。其中,最小堆可以用于解决一些优先级队列的问题,而最大堆则相反。
1 2 3 4 5 import heapqheap = [3 , 1 , 4 , 1 , 5 , 9 , 2 , 6 , 5 , 3 , 5 ] heapq.heapify(heap) min_element = heapq.heappop(heap)
大根堆:
将元素的值取相反数插入堆 max_heap = [-x for x in max_heap]
堆是一种数据结构,具有以下主要特点:
有序性:
堆是一个特殊的树形数据结构,通常是一个完全二叉树。
堆中的每个节点都有一个键值,且具有堆属性,即对于每个节点 i
,其父节点的键值要么小于等于 i
的键值(最大堆),要么大于等于 i
的键值(最小堆)。
堆的种类:
最大堆: 每个节点的值都大于或等于其子节点的值,根节点是最大的。
最小堆: 每个节点的值都小于或等于其子节点的值,根节点是最小的。
堆的表示:
堆通常使用数组来表示。在数组中,如果节点 i
存储了一个元素,则其左子节点为 (2*i + 1)
,右子节点为 (2*i + 2)
,父节点为 ((i-1)//2)
。
插入和删除操作的复杂度:
插入和删除堆中的元素的时间复杂度通常为 O(log n),其中 n 是堆中元素的数量。
插入操作:将新元素插入到堆的末尾,然后通过一系列交换操作维持堆属性。
删除操作:删除堆的根节点,将堆的最后一个元素移到根位置,然后通过一系列交换操作维持堆属性。
堆的应用:
堆常用于优先队列的实现,其中最小堆可以用于实现最小优先队列,而最大堆可以用于实现最大优先队列。
堆排序算法使用最大堆或最小堆来进行排序。
堆还可以用于实现一些高效的算法,如Dijkstra算法和Prim算法。
总体而言,堆是一种非常有效的数据结构,特别适用于需要快速查找最大或最小元素的场景。
11.排序算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 def selection_sort (nums ): i,j=0 ,0 for i in range (len (nums)): min_idx=i for j in range (i+1 ,len (nums)): if nums[j]<nums[min_idx]: min_idx=j nums[min_idx],nums[i]=nums[i],nums[min_idx] return nums def bubble_sort (nums ): i,j=0 ,0 n=len (nums) for i in range (n): for j in range (0 ,n-i-1 ): if nums[j]>nums[j+1 ]: nums[j],nums[j+1 ]=nums[j+1 ],nums[j] return nums def insert_sort (nums ): i,j=0 ,0 n=len (nums) for i in range (1 ,n): j=i-1 key=nums[i] while j>=0 and key<nums[j]: nums[j+1 ]=nums[j] j-=1 nums[j+1 ]=key return nums def quick_sort (nums ): if len (nums)<=1 : return nums base=nums[len (nums)//2 ] left=[x for x in nums if x<base] middle=[x for x in nums if x==base] right=[x for x in nums if x>base] return quick_sort(left)+middle+quick_sort(right) def merge_sort (arr ): if len (arr) > 1 : mid = len (arr) // 2 left_half = arr[:mid] right_half = arr[mid:] merge_sort(left_half) merge_sort(right_half) i = j = k = 0 while i < len (left_half) and j < len (right_half): if left_half[i] < right_half[j]: arr[k] = left_half[i] i += 1 else : arr[k] = right_half[j] j += 1 k += 1 while i < len (left_half): arr[k] = left_half[i] i += 1 k += 1 while j < len (right_half): arr[k] = right_half[j] j += 1 k += 1 arr = [64 , 34 , 25 , 12 , 22 , 11 , 90 ] merge_sort(arr) print ("归并排序结果:" , arr)res=quick_sort([5 ,2 ,8 ,1 ,4 ,3 ,6 ])
12.广度优先和深度优先搜索
BFS和DFS 都是图搜索算法。
二叉树是特殊的图结构。
深度优先:
特点:认准一个方向搜索,直到碰壁后(到达终点后)再换方向。换方向是撤销原路径,改为下一个节点。 换方向过程就是回溯。 使用栈(或者递归调用)维护访问的节点。
场景:找到一条路径,或者找到所有路径
代码框架:(实际就是回溯)
1 2 3 4 5 6 7 8 9 10 11 12 dfs(参数) { if (终止条件) { 存放结果; return ; } for (选择:本节点所连接的其他节点) { 处理节点; dfs(图,选择的节点); // 递归 回溯,撤销处理结果 } }
广度优先- :
特点:从起点出发,以起始点为中心一圈一圈进行搜。搜索到终点即为最短路径。 搜索方向为四周
场景:寻找最短路径, 使用队列来维护节点, 常使用visited[]
标记
![[Pasted image 20231226170753.png]]
![[Pasted image 20231226170825.png]]
代码框架:
使用队列实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from collections import deque dir = [(0 , 1 ), (1 , 0 ), (-1 , 0 ), (0 , -1 )] def bfs (grid, visited, x, y ): queue = deque() queue.append((x, y)) visited[x][y] = True while queue: curx, cury = queue.popleft() for dx, dy in dir : nextx, nexty = curx + dx, cury + dy if nextx < 0 or nextx >= len (grid) or nexty < 0 or nexty >= len (grid[0 ]): continue if not visited[nextx][nexty]: queue.append((nextx, nexty)) visited[nextx][nexty] = True
岛屿数量
深度优先
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def numIslands (self, grid: List [List [str ]] ) -> int : m, n = len (grid), len (grid[0 ]) visited = [[False ] * n for _ in range (m)] dirs = [(-1 , 0 ), (0 , 1 ), (1 , 0 ), (0 , -1 )] result = 0 def dfs (x, y ): for d in dirs: nextx = x + d[0 ] nexty = y + d[1 ] if nextx < 0 or nextx >= m or nexty < 0 or nexty >= n: continue if not visited[nextx][nexty] and grid[nextx][nexty] == '1' : visited[nextx][nexty] = True dfs(nextx, nexty) for i in range (m): for j in range (n): if not visited[i][j] and grid[i][j] == '1' : visited[i][j] = True result += 1 dfs(i, j) return result
广度优先
![[Pasted image 20231226175011.png]]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 def numIslands (self, grid: List [List [str ]] ) -> int : count=0 m, n = len (grid), len (grid[0 ]) directions = [(0 , 1 ), (1 , 0 ), (-1 , 0 ), (0 , -1 )] visited=[[False ]*n for _ in range (m)] def bfs (grid,visited,x,y ): queue=[] queue.append((x,y)) visited[x][y]=True while queue: curx,cury=queue.pop(0 ) for direct in directions: next_x=curx+direct[0 ] next_y=cury+direct[1 ] if next_x<0 or next_x>=m or next_y<0 or next_y>=n: continue if not visited[next_x][next_y] and grid[next_x][next_y]=='1' : visited[next_x][next_y]=True queue.append((next_x,next_y)) for i in range (m): for j in range (n): if not visited[i][j] and grid[i][j]=='1' : count+=1 bfs(grid,visited,i,j) return count
面试题
2.什么是堆?什么是栈?栈和堆的区别是什么?
数据结构:
堆:是一种无序的数据结构,可以看成完全二叉树。
栈:后进先出的数据结构,
系统内存:
堆 :没有特定的操作顺序,一般用于动态分配内存空间。
栈:一般用于存储局部变量、函数调用以及程序执行过程中的临时数据。
最大区别在于它们的内存分配方式不同,栈由系统自动分配和释放,而堆的分配和释放需要程序员手动控制。
3.简述数组,链表,队列,堆栈的区别?
数组、链表、队列和堆栈都是常见的数据结构,它们在存储和操作数据上有不同的特点。
数组(Array):
存储方式: 连续的内存块,通过索引直接访问元素。
插入和删除: 插入和删除元素可能需要移动其他元素。
适用场景: 适用于元素大小已知,且需要频繁随机访问的情况。
链表(Linked List):
存储方式: 非连续的内存块,通过指针相连。
插入和删除: 插入和删除元素相对较快,不需要移动其他元素。
适用场景: 适用于元素大小不固定,需要频繁插入和删除的情况。
队列(Queue):
操作方式: 先进先出(FIFO)。
插入和删除: 插入(enqueue)在队尾进行,删除(dequeue)在队头进行。
应用场景: 适用于需要按顺序处理的任务,如广度优先搜索。
栈(Stack):
操作方式: 后进先出(LIFO)。
插入和删除: 插入(push)和删除(pop)都在栈顶进行。
应用场景: 适用于需要后进先出顺序的场景,如深度优先搜索,函数调用栈等。
总结:
数组适用于需要随机访问的场景,但插入和删除较为耗时。
链表适用于频繁插入和删除的场景,但不支持随机访问。
队列适用于按顺序处理的场景,先进先出。
栈适用于后进先出的场景,如函数调用栈。
4.面向对象的三个特性是什么?
面向对象编程(Object-Oriented Programming,OOP)有三个主要的特性,通常被称为三大特性,它们是封装(Encapsulation)、继承(Inheritance)、多态(Polymorphism)。
封装(Encapsulation):
封装是指将数据和操作数据的方法捆绑在一起的概念。
封装通过将数据的内部细节隐藏起来,只暴露必要的接口,使得对象的使用者无需了解对象的内部实现细节。
封装有助于提高代码的可维护性,降低了对象的耦合度。
继承(Inheritance):
继承是指一个类(子类)可以继承另一个类(父类)的属性和方法。
继承可以减少代码的重复性,提高代码的可重用性。
子类可以通过继承扩展或修改父类的行为,实现了代码的扩展性和灵活性。
多态(Polymorphism):
多态是指一个对象可以以多种形态存在。
多态允许使用一个对象的通用接口来操作不同类型的对象 ,提高了代码的灵活性和可扩展性。
多态包括编译时多态(静态多态,例如方法的重载)和运行时多态(动态多态,例如方法的重写、接口的实现)。
不同的子类调用相同的父类,产生不同的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Animal : def make_sound (self ): pass class Dog (Animal ): def make_sound (self ): return "Bark" class Cat (Animal ): def make_sound (self ): return "Meow" def animal_sound (animal ): return animal.make_sound() dog = Dog() cat = Cat() print (animal_sound(dog)) print (animal_sound(cat))
这三个特性共同组成了面向对象编程的基础,它们使得程序更易于理解、维护和扩展。这些特性使得对象能够以更模块化、可重用的方式构建,并且有助于提高代码的质量和可维护性。
5. 什么是闭包?
闭包是指在一个函数内部定义另一个函数,并且这个内部函数能够访问外部函数的变量和参数,即使外部函数已经执行完毕,这些变量和参数仍然存在于内存中,并可以被内部函数使用。闭包常常用来实现一些高阶函数的功能,例如装饰器、回调函数等。运行时动态地改变或增强函数的行为。
工厂函数
1 2 3 4 5 6 7 8 def mutiply_by (factor ): def mutiply (x ): return x*factor return mutiply double=mutiply_by(2 ) triple=mutiply_by(3 ) print (double(2 ))print (triple(2 ))
#装饰器 是一种使用闭包的常见模式,它允许在不修改原始函数代码的情况下,向函数添加额外的功能。
可以用于记录日志,记录时间
1 2 3 4 5 6 7 8 9 10 def my_decorator (func ): def wrapper (): print ("Something is happening before the function is called." ) func() print ("Something is happening after the function is called." ) return wrapper @my_decorator def say_hello (): print ('hello' ) say_hello()
@staticmethod 装饰器:
1 2 3 4 5 6 7 class MyClass : @staticmethod def my_static_method (arg1, arg2 ): pass MyClass.my_static_method(value1, value2)
6. 匿名函数(lambda)/函数/闭包/对象在做实参时有什么区别?
函数:当函数作为参数传递时,仅传递其功能。
匿名函数:与函数类似,只有它们的功能作为参数传递。
闭包:当闭包作为参数传递时,函数和闭包内的数据都会被传递,从而可以同时传递功能和上下文。
对象:当实例对象作为参数传递时,它的方法和属性也会被传递。
7. 什么是进程、线程、协程、多进程、多线程?有什么区别?有什么使用场景?
概念
进程:操作系统中进行资源分配和调度的基本单位,一个进程可以包含多个线程。每个进程都有独立的内存空间。
线程:是操作系统能够进行运算调度的最小单位。一个进程可以包含多个线程,这些线程共享进程的内存空间和系统资源,但每个线程都拥有私有的信息。
协程:一个线程可以包含多个协程,协程通常是在单个线程中运行的,但线程是由操作系统调度的,而协程则是由程序主动控制的。协程是轻量级的,在单个线程中运行,因此不存在资源竞争,死锁问题,
多线程:指一个程序中创建多个线程,这些线程共享分配给进程的内存空间,可共享资源。
多进程:是指在操作系统中创建多个独立的进程,每个进程有自己独立的内存空间和系统资源。这些进程通过进程间通信(IPC)来共享数据和协作完成任务。
场景
进程:适用于需要进行大量计算或者需要充分利用CPU资源的场景,如并行计算、大数据处理等(cpu密集型任务)。
线程:适用于不需要独立内存空间的场景,如Web服务器、桌面应用程序等。
协程:适用于IO密集型的应用场景,如网络爬虫、异步编程等—》同样适用于线程。
8.什么是迭代器,为什么要使用它?什么是生成器,为什么要使用它?
拥有__iter__ ,next 魔法方法的对象就是迭代器,它是一个可以记住遍历的位置的对象。
生成器是特殊的迭代器,使用yield和next函数,有生成器表达式,生成器函数,都是为了节约内存。
区别:
在处理巨大的数据集合时,建议使用生成器来节省内存。
在遍历容器对象时,使用迭代器很方便。
9.什么是线程安全?
是多个线程同时访问共享数据时,不会出现任何问题或错误的特性。这意味着在一个多线程应用程序中,当多个线程同时访问共享资源(例如变量或对象)时,它们不会干扰彼此或产生无法预测的结果。
numpy
matplotlib
1 2 3 4 5 6 7 8 import matplotlibimport matplotlib.pyplot as pltimport numpy as npfig = plt.figure() ax = fig.add_subplot(111 ) ax.scatter(mat[:,1 ], mat[:,2 ],15.0 * np.array(labels), 15.0 *np.array(labels)) plt.show()
pandas
数据结构:
Series:带有索引的一组数据
环境
安装 Anaconda
从官网 下载 .sh
文件后,bash
运行即可
[Optional] 区分 Ubuntu 自带 python 和 anaconda 中的 python
Ubuntu22.04 中自带 python3,为方便起见,我们可以做一定的区分。在 ~/.bashrc
文件最后加入以下命令:
1 2 3 4 alias python3="/usr/bin/python3" # 给系统自带的python起一个别名,就叫python3 export PATH="/home/jason/anaconda3/bin:$PATH" # anaconda3中的python BASH
然后 source 一下让修改发生作用:
这样,输入 python3
时会自动替换为 /usr/bin/python3
,即使用系统自带的 python;而直接输入 python
则是用当前环境下的 python.
[Optional] 每次打开终端时,conda 会默认自动激活 base 环境,在命令提示符前有 (base)
标志。强迫症表示这太难看了,可以用以下命令禁止自动激活:
1 conda config --set auto_activate_base False