目录
1.10 查找链表的中间节点(如果中间有两个节点,返回第二个)
1.12 判断是否是回文链表(时间复杂度O(n)和空间复杂度都是O(1))
1.19 重排链表(1-2-3-4-5 》 1-5-2-4-3)
2.27 两数之和 II - 输入有序数组(和为target的两个数的下标,下标从1开始)
2.28 找到所有数组中消失的数字(1-n的n个数,有重复的)
3.38 只有两个键的键盘(每次要么复制要么粘贴出n个A的最小次数)
0 笔试题目整理
0.1 字节 7.7 (AC一道,90%一道,30%一道)
动态规划
回溯(超时)
8.10 帮豪哥
最长递增子序列的长度
求数组中可以构成递增子序列的最长长度
#最长递增子数组的长度
#dp[i]表示到i为止范围内的最长递增序列长度
def function(arr):
if len(arr)==0:
return 0
dp=[1]*len(arr)
for i in range(len(arr)):
for j in range(i-1,-1,-1): #比较前面的所有元素,看是可以递增
if arr[i]>arr[j]:
dp[i]=max(dp[i],dp[j]+1)
res=max(dp)
return res
举重
两两一组的条件,体重较小的不低于90%较大的,问有多少种组合
arr=[1,1,1,1,1]
arr.sort()
res=0
for i in range(len(arr)-1):
j=i+1
while j<len(arr) and arr[j]/float(arr[i])<0.9:
j+=1
res+=len(arr)-j
print (res)
8.11 拼多多(东哥)字节(豪哥)第四题都是图
1 求数组中,方差最小的三个数
排好序,一定是挨着的三个数
arr=[10,-1,0,1,3]
arr.sort()
ave1=sum(arr[:3])/3
total=sum([(arr[j]-ave1)**2 for j in range(3)])/3
for i in range(len(arr)-2):
ave=sum(arr[i:i+3])/3
f=sum([(arr[j]-ave)**2 for j in range(i,i+3)])/3
total=min(total,f)
print(round(total,2))
2 给定N和S,求由N个递增的整数组成的和为S的组合,有多少个
回溯
N=3
S=10
res=[]
nums=[i for i in range(1,S)]
def dfs(temp,target,nums,num):
if num==N:
if target==0:
res.append(temp[:])
return
for i in range(len(nums)):
if nums[i]<=target:
dfs(temp+[nums[i]],target-nums[i],nums[i+1:],num+1) #不可重复使用,所以传入下一次递归的是nums[i+1:]
dfs([],S,nums,0)
print (res)
1 加解密码
arr=[1,1,1,0,1,0,0,1,1,0]
K=4
N=7
#length=len(arr)-K+1
res=[]
for i in range(N):
if not res:
res.append(arr[i])
elif i<K: #前k位,都是从res的第1个开始异或
t=arr[i]
for j in res:
t^=j
res.append(t)
else: #超过第k位的话,比如第5位,就是和第2 3 4 位异或(从第2位开始了)
# 比如第6位,就是和第 3 4 5 位异或(从第3位开始了)
t=arr[i]
for j in res[i-K+1:]: #从第i-k+1开始
t^=j
res.append(t)
print(res)
4 LeetCode135发糖果
def candy(self, ratings):
"""
:type ratings: List[int]
:rtype: int
"""
#两次遍历,先从左到右,保证右边的都大于左边的,
#再从右到左,保证左边的都大于右边的,同时要满足第一次的条件,所以要取符合条件的最大值
temp=[1 for i in range(len(ratings))]
for i in range(1,len(ratings)):
if ratings[i]>ratings[i-1]:
temp[i]=temp[i-1]+1
for j in range(len(ratings)-2,-1,-1):
if ratings[j]>ratings[j+1]:
temp[j]=max(temp[j],temp[j+1]+1)
return sum(temp)
1 链表类
1.0 创建链表
将list转换为链表形式(VIVO提前批,写错了)
class Linknode(object):
def __init__(self,val=None,next=None):
self.val = val
self.next = next
def list_2_linknode(array):
pnode=Linknode(-1)
p=pnode
for s in range(arry):
p.next=Linknode(i)
p=p.next
return pnode
"""
tem_node = Linknode()
node = Linknode()
for i in array:
#记得是判定val是否有值,并且用一个node记住头节点,然后返回的是头节点
if not tem_node.val:
tem_node.val =i #当时错写成node.next=i node=node.next
node = tem_node
else:
tem_node.next = Linknode(i)
tem_node = tem_node.next
return node
"""
1.1 反转链表
增加两个指针,pre每次保存前一个,next保存后一个(防止断开),反转顺序,phead.next->pre,然后pre和phead依次后移(pre=phead, phead=next),最后终止条件是pHead=None,返回前一个节点pre
def ReverseList(self, pHead):
# write code here
if not pHead:
return None
pre=None
pnext=None
while pHead:
pnext=pHead.next
pHead.next=pre
pre=pHead
pHead=pnext
return pre #此时pHead=None.返回的是前一个节点
#递归方法:
"""
if not head or not head.next:
return head
p=self.reverseList(head.next)
head.next.next=head
head.next=None
return p
"""
#头插法(依次将后面节点移到头节点)
if not head or not head.next:
return head
dummy=ListNode(-1)
dummy.next=head
pre=dummy
p=dummy.next
while p.next:
temp=p.next
p.next=temp.next
temp.next=pre.next
pre.next=temp
return dummy.next
1.2 从尾到头打印链表
思路一:从头到尾依次打印并保存,逆序返回
思路二:递归实现,每次都先递归到下一个节点,当前值最后保存,则保存的结果就是从最后一个节点向前的顺序
递归时,两个关键因素:终止条件,递归过程的语句顺序(此题中,先依次调用本函数,再添加元素,注意语句顺序)
#method 1:
def printListFromTailToHead(self, listNode):
# write code here
a=[]
while listNode:
a.append(listNode.val)
listNode=listNode.next
return a[::-1]
#method 2:
def __init__(self):
self.res=[]
def printListFromTailToHead(self, listNode):
if listNode:
self.printListFromTailToHead(listNode.next)
self.res.append(listNode.val)
return self.res
#or:
def printListFromTailToHead(self, listNode):
if not listNode:
return []
return self.printListFromTailToHead(listNode.next)+[listNode.val]
1.3 查找链表中倒数第k个节点
思路一:一次遍历找到总长度,然后再遍历len-k+1步(时间复杂度较高)
思路二:快慢指针方法(两个指针,快的先走,慢的相隔k-1步再走,当快的走到尾时,慢的刚好在倒数第k个,返回慢的)
考虑特殊情况:空链表、k为0、链表长度小于k
快慢指针还可解决:查找链表中间节点(快的一次走两步,慢的一次走一步,快的到尾时,返回慢的)
def FindKthToTail(self, head, k):
# write code here
if not head or k==0:
return None
pbehind=head
for i in range(k-1):
if head.next: #注意此处判断条件
head=head.next
else: #考虑长度小于k的情况
return None
while head.next: #注意此处判断条件
head=head.next
pbehind=pbehind.next
return pbehind
1.4 合并两个递增的有序链表
思路一:非递归,新建一个列表,并记录表头。在都不为空的情况下,顺次比较两个链表的表头,每次取出较小的为新链表的下一个节点,然后较小的节点后移。每比较一次,新链表的记录都后移一次(否则只会在表头操作)。遍历结束后,如果有非空的,则直接添加。最后返回新链表的记录表头。
思路二:递归
class Solution:
# 返回合并后列表
def Merge(self, pHead1, pHead2):
#递归方法:
if not pHead1:
return pHead2
if not pHead2:
return pHead1
if pHead1.val<pHead2.val:
merge=pHead1
merge.next=self.Merge(pHead1.next,pHead2)
else:
merge=pHead2
merge.next=self.Merge(pHead1,pHead2.next)
return merge
"""
非递归方法:
pnew=ListNode(-1)#任意指定一个头为-1的链表
res=pnew #保存该链表的头,因为后面pnew会一直后移,需要记录表头位置
while pHead1 and pHead2:
if pHead1.val<pHead2.val:
pnew.next=pHead1
pHead1=pHead1.next
else:
pnew.next=pHead2
pHead2=pHead2.next
pnew=pnew.next #每次都要后移当前节点,否则会一直在第一个-1节点
if pHead1:
pnew.next=pHead1
if pHead2:
pnew.next=pHead2
return res.next #因为头节点是-1,从下一个开始返回
"""
1.5 复杂链表的复制(带一个random)
带random,可能在前面也可能在后面,不能直接复制。三步:
每个节点后面都插入一个该节点的复制;将复制节点的random链接(原节点的random.next);拆分,返回所有复制节点
def Clone(self, pHead):
# write code here
#递归方法:
if not pHead:
return None
clonehead=RandomListNode(pHead.label)
clonehead.random=pHead.random
clonehead.next=self.Clone(pHead.next)
return clonehead
"""
非递归
#1 复制原链表
pclone=pHead#避免破坏原链表表头
while pclone:
copy=RandomListNode(pclone.label)
pnext=pclone.next
pclone.next=copy
copy.next=pnext
pclone=pnext
#2 复制random
dummy=pHead
while dummy:
clone=dummy.next
if dummy.random:
clone.random=dummy.random.next
dummy=clone.next
#3 返回复制链表(偶数项)
dummy=pHead
copyNode=pHead.next
while dummy:
copynode=dummy.next
dummynext=copynode.next
if dummynext:
copynode.next=dummynext.next
else:
copynode.next=None
dummy=dummynext
return copyNode
"""
1.6 两个链表的第一个公共节点
思路一:先遍历得到两个长度差step,然后长链表先走step,然后两个一起走并比较是否相等,返回第一个相等的节点
思路二:分别遍历并用两个栈分布存储,然后从栈顶开始一次pop,返回最后一个相等的点
def FindFirstCommonNode(self, pHead1, pHead2):
# write code here
#method 1
#先分别遍历得到长度
length1,length2=0,0
pHead11=pHead1
pHead22=pHead2
while pHead11:
length1+=1
pHead11=pHead11.next
while pHead22:
length2+=1
pHead22=pHead22.next
if length2>=length1:
longhead=pHead2
shorthead=pHead1
else:
longhead=pHead1
shorthead=pHead2
step=abs(length2-length1)
#长链表先走,直到两个长度相等,然后同时走,
for i in range(step):
longhead=longhead.next
while longhead and shorthead:
if longhead==shorthead:
return longhead
longhead=longhead.next
shorthead=shorthead.next
"""
method 2
stack1=[]
stack2=[]
while pHead1:
stack1.append(pHead1)
pHead1=pHead1.next
while pHead2:
stack2.append(pHead2)
pHead2=pHead2.next
temp=None
while stack1 and stack2 and stack1[-1]==stack2[-1]:
temp=stack1[-1] #存储每一对相等的点
stack1.pop()
stack2.pop()
return temp
"""
1.7 链表环的入口节点
快慢指针,快一次走两步,慢一次走一步,直到第一次相遇;
假设环有n个节点,第一次相遇时,慢的走了a,快的走了2a,则有a+n=2a ,a=n,即慢的走了n步;
此时再让快的回到头,两个一起走每次都为一步,设x为头距入口的距离,y是入口距慢的距离,x+y=n, 所以当快走x步到达入口时,慢的走了x=n-y步,即一周又退回到入口处,两个刚好相遇
#判断是否有环:快慢指针,如果相遇,则有环
def hasCycle(self, head):
"""
:type head: ListNode
:rtype: bool
"""
f,s=head,head
while f and f.next:
f=f.next.next
s=s.next
if f==s:
return True
return False
#查找环的入口
def EntryNodeOfLoop(self, pHead):
# write code here
if not pHead or not pHead.next or not pHead.next.next:
return None
slow=pHead.next
fast=pHead.next.next
while fast != slow:
if not fast.next or not fast.next.next: #要判断,如果有None则说明没有环
return None
slow=slow.next
fast=fast.next.next
#slow刚好走了n步(n:环的节点个数)
# fast退回头结点,两个同时走,每次一步,直到再相遇
fast=pHead
while fast!=slow:
fast=fast.next
slow=slow.next
return fast
1.8 删除链表中的重复节点(1-2-2-3—>1-3)
对于链表操作,主要是确定next,建立一个链表res=ListNode(-1),然后定义指向链表头的指针pre=res,通过操作pre指定next,指针pre依次后移,最后返回建立起res完整链表,最后返回的是res(因为pre一直在变,是操作指针)
#递归方法
#method 1
def deleteDuplication(self, pHead):
# write code here
if pHead==None or pHead.next==None:
return pHead
if pHead.val==pHead.next.val:
temp=pHead.next
while temp!=None and temp.val==pHead.val:
temp=temp.next
return self.deleteDuplication(temp)
else:
temp=pHead.next
pHead.next=self.deleteDuplication(temp)
return pHead
"""
# 非递归方法
if not head or not head.next:
return head
dummy=ListNode(-1)
dummy.next=head
p=dummy
while head and head.next:
if head.next.val==head.val:
val=head.val
while head and head.val==val:
head=head.next
p.next=head #只记录,不后移
else:
p=head #只有不重复时候,才会后移并记录
head=head.next
if head:p.next=head
return dummy.next
"""
#重复节点,保留一个 1-1-2-3-3(1-2-3)
def deleteDuplicates(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
Head=ListNode(0)
p=Head
while head !=None and head.next!=None:
if head.val==head.next.val:
p.next=head #甭管重复与否,先记录
temp=head.val
while head and head.val==temp:
head=head.next
else:
p.next=head
head=head.next
p=p.next #每次都会后移
p.next=head
return Head.next
"""
#递归方法
if not head or not head.next:
return head
head.next=self.deleteDuplicates(head.next)
if head.val==head.next.val:
head=head.next
return head
"""
1.9 删除链表节点O(1)
将该节点的下一个节点值复制到该节点上,然后跳过下一个节点,指向node.next.next
def deleteNode(self,phead, node):
"""
:type node: ListNode
:rtype: void Do not return anything, modify node in-place instead.
"""
#若是尾节点,需要从头遍历
if node.next==None:
while phead.next!=node:
phead=phead.next
phead.next=None
else:#非尾节点
node.val=node.next.val #复制下个节点值到当前节点
node.next=node.next.next #跳过下个节点
1.10 查找链表的中间节点(如果中间有两个节点,返回第二个)
快慢指针。重点是判断条件。
def middleNode(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
fast=head
slow=head
#当下一个非空,下下一个空,也要再走一步,slow就是第二个中间节点了
while fast!=None and fast.next!=None:
fast=fast.next.next
slow=slow.next
return slow
1.11 删除指定值的节点
遍历,如果非val,则加入p,head后移,p也后移,否则head后移不加入p。
特殊情况:(1,2,5,6)val=6的情况,最后几个元素是val值,则p加入的最后一个元素是5,但是5的next还是6,所以结果还是1256,所以遍历结束后,指定p.next为head(此时head为空)或None
def removeElements(self, head, val):
"""
:type head: ListNode
:type val: int
:rtype: ListNode
"""
H=ListNode(0)
p=H
while head:
if head.val!=val:
p.next=head
head=head.next
p=p.next
else:
head=head.next
p.next=head #特殊情况,最后几个元素是val
return H.next
1.12 判断是否是回文链表(时间复杂度O(n)和空间复杂度都是O(1))
回文:翻转后和原来一样。空间复杂度是O(1),说明不能建立新的复制链表了,只能原地操作,唯有原地反转了。如果都反转则无法比较了。所以,只反转后半部分,然后和前半部分进行逐一比较。快慢指针找到中间节点->反转后半部分->前后比较
def isPalindrome(self, head):
"""
:type head: ListNode
:rtype: bool
"""
fast,slow=head,head
while fast and fast.next:
fast=fast.next.next
slow=slow.next
mid=slow
pre=None
while mid:
nex=mid.next
mid.next=pre
pre=mid
mid=nex
#pre是后半部分反转后的头节点
while pre:
if pre.val!=head.val: #比较的值是否一样,记得加val!!!
return False
pre=pre.next
head=head.next
return True
1.13 链表排序
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
思路一:先遍历一遍并用数组记录链表的所有值,对数组排序,再遍历一遍以数组值将每个节点重新重新赋值
思路二:归并算法:快慢指针,查找中间节点;合并两个子链表。
def sortList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
"""
#归并法:快慢指针每次找到中间节点,合并两个子链表
if not head or not head.next:
return head
fast,slow=head,head
pre=None
while fast and fast.next:
pre=slow #保存最终中间节点的前一个值,并赋值None,实现分割
slow=slow.next
fast=fast.next.next
pre.next=None #将head实现分割
left=self.sortList(head)
right=self.sortList(slow)
return self.merge(left,right)
def merge(self,left,right):
if not left:
return right
if not right:
return left
if left.val<right.val:
res=left
res.next=self.merge(left.next,right)
else:
res=right
res.next=self.merge(left,right.next)
return res
"""
#用链表存储所以节点值,然后排序,并给链表从头重新赋值,但是空间复杂度是O(n)
res=[]
q=head
while q:
res.append(q.val)
q=q.next
res.sort()
q=head
leng=len(res)
for i in range(leng):
q.val=res[i]
q=q.next
return head
1.14 链表插入排序
新建链表,指向head,每次比较时,先取出当前节点,然后从头遍历新链表找到插入的位置,然后将当前节点插入到该位置,当前点继续后移。
def insertionSortList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
"""
if not head or not head.next:
return head
dummy=ListNode(-1)
q=head
while q:
temp=q.next
pre=dummy
while pre.next and pre.next.val<q.val:
pre=pre.next
q.next=pre.next
pre.next=q
q=temp
return dummy.next
"""
#不额外开辟新的链表空间,直接指向当前链表
if head is None or head.next is None:
return head
dummy=ListNode(0)
cur=dummy.next=head
while cur.next:
if cur.val>cur.next.val:
pre=dummy
while pre.next.val<cur.next.val:
pre=pre.next
m=cur.next
cur.next=m.next
m.next=pre.next
pre.next=m
else:
cur=cur.next
return dummy.next
1.15 删除倒数第n个节点
考察求倒数第n+1个节点
思路一:两次遍历:先遍历一遍,得到总长度,然后从头遍历直接找到倒数第n+1的节点,删除
思路二:快慢指针,先找到倒数第n个节点(fast和slow相隔n-1步),并记录倒数第n+1节点,然后删除;或者fast和slow相隔n步,则可以直接找到倒数第n+1个节点)
def removeNthFromEnd(self, head, n):
"""
:type head: ListNode
:type n: int
:rtype: ListNode
"""
#fast和slow相隔n步,直接找到倒数第n+1个节点(注意特殊情况
#n有可能是头节点,此时fast刚好是None)
if not head:
return None
fast,slow=head,head
#假设n有效,不判断n是否超出长度
#fast先走n-1步,找到倒数第n个节点;走n步,
#找到倒数n节点的前一个节点
for i in range(n):
fast=fast.next
if i<n-1 and fast==None: #判断n是否有效
return None
if fast==None: #刚好是要删除头结点
head=head.next
return head
else: #找到倒数第n+1个节点
while fast.next:
fast=fast.next
slow=slow.next
slow.next=slow.next.next
return head
"""
fast和slow相隔n-1步,直接找到slow是倒数第n个节点,此时要记录倒数第n+1节点
if not head:
return None
fast,slow=head,head
for i in range(n-1):
fast=fast.next
if fast==None:
return None
dummy=ListNode(-1)
dummy.next=head
q=dummy
#slow刚好找到倒数第n个节点
#q比slow还慢一步,刚好是slow前面的节点
while fast.next:
fast=fast.next
q=q.next
slow=slow.next
temp=slow
q.next=temp.next
return dummy.next
"""
1.16 两两交换链表的节点
记录当前节点,下一个节点,两个反转,然后后移。注意第一个节点要和第四个节点连接(当第三个是非空时)第三个为空时则和第三个节点相连。否则会断开
def swapPairs(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if not head or not head.next:
return head
pre=head
p=pre.next
head=head.next
while p and pre:
temp=p.next
p.next=pre
if not temp:
pre.next=temp
return head
#注意,第一个和第四个连接,只有第四个是空时和第三个连接
pre.next=temp if temp.next== None else temp.next
pre=temp
p=temp.next
return head
"""
#递归
if not head or not head.next:
return head
nex=head.next
head.next=self.swapPairs(nex.next)
nex.next=head
return nex
"""
1.17 奇偶链表(index,偶数连偶数,接奇数连奇数)
分别定义偶数头节点和奇数头节点,依次后移,最后偶数最后一个节点接奇数的头结点
def oddEvenList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if not head or not head.next:
return head
dummy=head
dummy_odd=head.next
peven=dummy
podd=dummy_odd
while podd and podd.next:
peven.next=podd.next
peven=peven.next
podd.next=peven.next
podd=podd.next
if podd: #偶数个节点的情况,需要将最后一个偶数节点的next指定为空
podd.next=None
peven.next=dummy_odd #最后一个偶数节点,接奇数头结点
return dummy
1.18 反转链表II(反转第m-n的节点)
思路一:普通原地反转,找到并记录几个关键节点方便后续连接(第m,m-1,n,n+1)
思路二:头插法反转更容易理解。新建头结点,向后遍历,依次将p.next插入至头结点的后面。
def reverseBetween(self, head, m, n):
"""
:type head: ListNode
:type m: int
:type n: int
:rtype: ListNode
"""
#头插法,简单明了:
if not head or not head.next:
return head
dummy=ListNode(-1)
dummy.next=head
p=dummy
#先移动m步,找到头结点pre:
for i in range(m):
pre=p
p=p.next
#每次移动,都插入到头结点后面
for j in range(n-m): #总是通不过,发现竟然写成了m-n
temp=p.next
p.next=temp.next
temp.next=pre.next
pre.next=temp
return pre
"""
#普通做法,反转第m-n的链表,再拼接
dummy=ListNode(-1)
dummy.next=head
pm=dummy
for i in range(m):
front=pm #记录第n-1个节点
pm=pm.next
pre=pm #记录第n个节点
pnewpre=None
for j in range(n-m+1): #要多走一步,pm指向第n+1个节点
nex=pm.next
pm.next=pnewpre
pnewpre=pm
pm=nex
front.next=pnewpre
pre.next=pm
return dummy.next
"""
1.19 重排链表(1-2-3-4-5 》 1-5-2-4-3)
思路:三步走,快慢指针找到中间节点,反转后半部分,前后两部分合并
def reorderList(self, head):
"""
:type head: ListNode
:rtype: None Do not return anything, modify head in-place instead.
"""
if not head or not head.next:
return head
#1 快慢指针,找到中间节点
fast=slow=head
while fast and fast.next:
slow=slow.next
fast=fast.next.next
#2 将后半部分逆序,头插法
head_back=ListNode(-1)
head_back.next=slow.next #后半部分的头结点,保证后半部分小于前半部分,方便合并
p=head_back.next
pre=head_back
while p and p.next:
temp=p.next
p.next=temp.next
temp.next=pre.next
pre.next=temp
#后半部分是head_back.next,已经逆序
slow.next=None #将前半部分封死
#3 两段合并
#1-2-3-Null
#4-5-null
#1-2-3-null
#4-null
head2=head_back.next
head1=head
while head2:
temp2=head2.next
temp1=head1.next
head1.next=head2
head2.next=temp1
head2=temp2
head1=temp1
2 数组
2.1 二维数组的查找
数组左->右,上->下都是递增排列,判断一个整数是否在数组内
思路:以右上角为基准,这样每次判断没有重叠区域。与target进行比较。小于target,row+=1; 大于target,col-=1
def Find(self, target, array):
# write code here
rows=len(array)
cols=len(array[0])
row=0
col=cols-1
while row <rows and col >=0:
if array[row][col]==target:
return True
elif array[row][col]<target:
row+=1
else:
col-=1
return False
2.2 旋转数组的最小值查找
旋转数组:给定一个递增的数组将前n个元素移到末尾,查找最小值。
思路:考察二分查找。有序排列中查找,是二分查找的典型特征。注意特殊情况空数组或只有一个元素
arr [mid]<arr [right] : 分界点在左面或刚好等于mid: right=mid
arr [mid]>arr [right] : 分界点在右面 left=mid+1 不可能等于mid,因为right比mid还小
(假设原数组是递增的,不考虑相等的情况:相等情况比较复杂10111 或者11101,都有可能)
def minNumberInRotateArray(self, rotateArray):
leng=len(rotateArray)
left=0
right=leng-1
if leng==1:
return rotateArray[0]
elif leng==0:
return 0
else:
while left <right:
mid=(left+right)/2
if rotateArray[mid]<rotateArray[right]:
right=mid # 45123,有可能刚好是mid
else:
left=mid+1 #right比mid小,所以不可能是mid
return rotateArray[left]
2.3 调整数组的顺序,使奇数在前偶数在后(有无稳定性)
可扩展性问题,类似:整除3的在前,负数在前...
思路一:一次遍历,两个额外数组空间,分别存储奇数和偶数,最后相加返回,空间复杂度O(n)
思路二:(不具有稳定性,会改变相对位置)
前后指针,left condition:[一个头指针,一直往后走,若碰到应该排在后面的元素,停]
&right condition: [一个尾指针,从后往前走,若碰到应该排在前面的元素,停],交换两个元素。
循环条件:保证头指针<尾指针思路三:具有稳定性,不会改变相对位置
类比插入排序算法:依次从头遍历,若遇到偶数,直接插入不变;若遇到奇数:若前一个是偶数,则前面依次交换,将奇数插入到最后一个奇数后。
#无稳定性
def reorderoddeven(array):
head=0
end=len(array)-1
while head<end:
while head<end and array[head]%2!=0:#leftcondition
head+=1
while head<end and array[end]%2==0:#rightcondition
end-=1
if head<end:
#交换
array[head],array[end]=array[end],array[head]
print(array)
#具有稳定性
def reOrderArray(self, lst):
leng=len(lst)
if leng==1:
return lst
for i in range(1,leng):
if lst[i]%2!=0:
temp=lst[i]
j=i
while j>0 and lst[j-1] %2==0:
lst[j]=lst[j-1]
j-=1
lst[j]=temp
return lst
2.4 数组中出现次数超过一半的数字
思路一:用字典记录每个元素出现的次数,空间复杂度O(n)一次遍历
思路二:出现次数比其他所有元素出现次数之和还要多,每次重复出现加一,不相同减一,为零时,记录新的元素,count置为一。最后记录的元素就是次数超过一半长度的元素。
(要检验其存在性)
def MoreThanHalfNum_Solution(self, numbers):
#method 1:字典记录次数
l=len(numbers)
d={}
for i in numbers:
if i not in d:
d[i]=1
else:
d[i]+=1
if 2*d[i]>l:
return i
return 0
#method 2:
count=1
index=0
leng=len(numbers)
for i in range(1,leng):
if numbers[i]==numbers[index]:
count+=1
else:
if count==0:
index=i
count=1
else:
count-=1
#检验长度是否符合要求
times=0
for i in range(leng):
if numbers[i]==numbers[index]:
times+=1
if 2*times<=leng:
return 0
return numbers[index]
2.5 子数组的最大和
思路:动态规划
若当前和小于零,则从当前元素重新开始累加;否则直接累加,每次更新最大值。
(注意maxnum和cursum需要从第一个元素开始,不可maxnum=0:防止出现全负的情况)
def FindGreatestSumOfSubArray(self, array):
# write code here
if not array:return None
cursum=array[0] #不可从0开始
maxnum=cursum #不可从0开始
#或cursum=0
#maxnum=array[0]
for i in range(1,len(array)):
if cursum<=0:
cursum=array[i]
else:
cursum+=array[i]
if cursum>maxnum:
maxnum=cursum
return maxnum
2.6 子矩阵的最大和
有正有负的矩阵。子矩阵,一定是某行和紧邻他的连续几行。先压缩,以每一行为首行,然后依次合并后续的每一行,每一次合并后,都利用2.5中求子数组的最大和的思想,来求合并后的最大和。二维矩阵压缩为一维数组->求子数组的最大和。
def maxsubmatrix(matrix):
num=len(matrix)
maxSubMatrix = 0 #有正有负,可以从零开始
for i in range(num):
array = [0] * num #i行到j行之间同列的相加
for j in range(i,num):
for q in range(num): #j每次加一array都会继承前面的,只有i变化时才重置array
array[q] += matrix[j][q]
"""
temp = array[0]
ans = array[0]
for k in range(1,num):
temp= max(temp+array[k],array[k])
ans = max(temp,ans)
"""
ans=FindGreatestSumOfSubArray(array) #对之前合并的行,最大连续和
if maxSubMatrix < ans:
maxSubMatrix = ans
print(maxSubMatrix)
2.7 把数组排成最小的数
构造一种新的比较方式:用字符组合int(x+y)与int(y+x)比较前后位置关系。从头开始遍历,每次遍历时依次和后面的比较,根据比较结果,如果后面的放前面更小,就调换位置。
def PrintMinNumber(self, numbers):
# write code here
if not numbers:
return ''
num=list(map(str,numbers))
leng=len(num)
for i in range(leng):
for j in range(i+1,leng):
if self.need_inverse(num[i],num[j]):
num[i],num[j]=num[j],num[i]
return ''.join(num)
def need_inverse(self,x,y):
if int(x+y)<int(y+x):
return False
else:
return True
2.8 数组中的逆序对
思路一:先排序,从最小的开始作为当前元素,查找后面和当前元素形成逆序对的个数:从头遍历原数组,每次遍历都加一,直到和当前元素相等,然后从原数组中删除当前元素,再以第二小作为当前元素,继续从头遍历,删除...O(n2)
思路二:归并思想。归并过程中,每次统计前后逆序对,从头开始比较,如果left>right,则左边剩下的mid-left个元素都与right逆序,count+=mid-left。边比较边排序,每次返回排好序的子数组。
(原归并算法中,left=self.merge(data[:mid]),right=self.merge()...可以写在里面的排序子函数外面,每次递归调用自身,返回子函数。但这里最后返回的是计数,不是排好序的数组,所以上述left right的递归调用要放到子函数里面,使得调用子函数一次性返回排好序的数组和完成计数,最后子函数外面直接返回计数。)
global 变量,要指定变量的初始值,子函数继续调用时,要再次global声明
#归并方法:
def InversePairs(self, data):
# write code here
#count=0
global count
count=0
def MergeSort(lists):
global count
if len(lists) <= 1:
return lists
num = int( len(lists)/2 )
left = MergeSort(lists[:num])
right = MergeSort(lists[num:])
r, l=0, 0
result=[]
while l<len(left) and r<len(right):
if left[l] < right[r]:
result.append(left[l])
l += 1
else:
result.append(right[r])
r += 1
count += len(left)-l
#从l,r=,0,头开始比较,如果left[l]比rght[r]大,那么left中l右面的都和right[r]构成逆序对
result += right[r:]
result += left[l:]
return result
MergeSort(data)
return count%1000000007
# O(n2)排序-遍历-删除方法
sort_data=sorted(data)
count=0
for x in sort_data:
j=0
for j in range(len(data)):
if data[j]!=x:
count+=1
else:
break
del data[j] #从原数组中删除,避免重复计算
return count
2.9 查找排序数组中某个数出现的次数
排序数组+查找--->二分法特性。
先查找第一个出现的位置:若mid<k:left=mid+1;若mid>k:right=mid-1;若mid==k :if mid-1!=k: 返回mid, 否则right=mid-1
查找最后一个出现的位置:...
(循环写法,递归写法)
扩展:
def GetNumberOfK(self, data, k):
# write code here
leng=len(data)
start=0
end=leng-1
i=self.Getfirstk(data,start,end,k)
j=self.Getlastk(data,start,end,k)
if i==-1&j==-1:
return 0
else:
return j-i+1
def Getfirstk(self,data,start,end,k):
#循环写法
mid=(start+end)//2
while start<=end: #有等于的情况!
if data[mid]==k:
if (mid>0 and data[mid-1]!=k) or mid==0:
return mid
else:
end=mid-1
elif data[mid]<k:
start=mid+1
else:
end=mid-1
mid=(start+end)//2
return -1 #考虑没有出现的情况
def Getlastk(self,data,start,end,k):
#递归写法
mid=(start+end)//2
leng=len(data)
if start>end:
return -1
if data[mid]==k:
if (mid<leng-1 and data[mid+1]!=k) or mid==leng-1:
return mid
else:
start=mid+1
elif data[mid]<k:
start=mid+1
else:
end=mid-1
return self.Getlastk(data,start,end,k)
2.10 数组中只出现一次的两个数(其他都出现两次)
异或运算:任何数异或0都等于本身;任何数异或本身都等于0
数组从前到后依次异或的结果就是这两个数异或的结果,肯定不为零。看异或结果为1的那一位是第几位,根据该位是否为1将原数组分为两组,则每组各包含一个数,然后在每个组内一次异或。O(n)
def FindNumsAppearOnce(self, array):
# write code here
num=0
for arr in array:
num^=arr
index=0
while (num&1)==0:
num=num>>1
index+=1
num1=0
num2=0
for arr in array:
temp=arr>>index
if (temp&1)==0:
num1^=arr
else:
num2^=arr
return [num1,num2]
2.11 数组中重复的数
0~n-1范围内的n个数字。若无重复,本该arr[i]==i。利用该特点,查找重复。若不相等,则将其交换到本来的位置。
def duplicate(self, numbers, duplication):
# write code here
n=len(numbers)
for i in range(n):
if numbers[i]<0 or numbers[i]>=n:
return False
for j in range(n):
while numbers[j]!=j:
if numbers[j]==numbers[numbers[j]]:
duplication[0]=numbers[j]
return True
#交换位置
temp=numbers[j]
numbers[j]=numbers[temp]
numbers[temp]=temp
return False
2.12 有序数组的平方
问题描述:给定一个按非递减顺序排序的整数数组
A
,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。思路一:遍历一遍求平方,然后排序,复杂度sort-O(nlogn)
思路二:排序数组——双指针:从两边开始遍历查找最大值
def sortedSquares(self, A):
"""
:type A: List[int]
:rtype: List[int]
"""
#双指针
leng=len(A)
res=[0]*leng #从后向前赋最大值
left=0
right=leng-1
index=leng-1
while left<=right: #记得加上等号,指向同一个也要输出
l=A[left]*A[left]
r=A[right]*A[right]
if l<r:
res[index]=r
right-=1
else:
res[index]=l
left+=1
index-=1
return res
"""
#简单,时间复杂度sort-o(nlogn)
res=[]
for num in A:
res.append(num*num)
return sorted(res)
"""
2.13 翻转图像
描述:
给定一个二进制矩阵
A
,我们想先水平翻转图像,然后反转图像并返回结果。水平翻转图片就是将图片的每一行都进行翻转,即逆序。例如,水平翻转
[1, 1, 0]
的结果是[0, 1, 1]
。反转图片的意思是图片中的
0
全部被1
替换,1
全部被0
替换。例如,反转[0, 1, 1]
的结果是[1, 0, 0]
思路:
一次遍历完成。反转可以用1-x或者 位运算与1异或 :x^1
def flipAndInvertImage(self, A):
"""
:type A: List[List[int]]
:rtype: List[List[int]]
"""
"""
#超简洁
res=[[1-col for col in raw[::-1]] for raw in A]
return res
"""
raws=len(A)
cols=len(A[0])
for i in range(raws):
#A[i]=map(lambda x:1-x, A[i]) #慢,不需要先遍历,应该合并
left=0
right=cols-1
while left<=right:
if left!=right:
A[i][left]=1-A[i][left]
A[i][right]=1-A[i][right]
A[i][left],A[i][right]=A[i][right],A[i][left]
else: #指向同一个数的时候,只翻转一次,避免翻转两次
A[i][left]=1-A[i][left]
left+=1
right-=1
return A
2.14 按奇偶排序数组 II
给定一个非负整数数组
A
, A 中一半整数是奇数,一半整数是偶数。对数组进行排序,以便当A[i]
为奇数时,i
也是奇数;当A[i]
为偶数时,i
也是偶数。可以返回任何满足上述条件的数组作为答案。思路一:遍历一遍,分别找到奇数和偶数,然后合并
思路二:双指针,原地。一个负责检查偶数index,一个负责奇数,遇到不符合的,就检查另一个直到找到不符合的,然后交换。
def sortArrayByParityII(self, A):
"""
:type A: List[int]
:rtype: List[int]
"""
#双指针,一个检查偶数index,一个检查奇数index,步长都是2
leng=len(A)
if leng<=1:
return A
j=1
for i in range(0,leng,2):
if A[i]&1 !=0:
while j<leng and A[j] &1!=0:
j+=2
A[i],A[j]=A[j],A[i]
return A
2.15 斐波那契数列
f(N)=f(N-1)+f(N-2)
递归-时间和空间复杂度高;非递归(循环)
注意:for i in range(2,2):空循环,不会执行
def fib(self, N):
"""
:type N: int
:rtype: int
"""
#非递归,循环方法:
f0=0
f1=1
b=f0+f1
if N<2:
return N
f0=f1
f1=b
for i in range(2,N): #注意:for i in range(2,2):空循环,不会执行
b=f0+f1
f0=f1
f1=b
return b
"""
#递归方法:
if N==0:
return 0
elif N==1:
return 1
return self.fib(N-1)+self.fib(N-2)
"""
2.16 查找常用字符
在每个list中都出现的字符。
思路一:用第一组元素作参照,计算每个元素出现次数,并与其它字符串对比,返回每个字符的最小共同出现次数,然后构造list。
思路二:以一组作为参照,逐个遍历元素并检验是否存在于其他组中,若存在就删除remove(一次只删除一个),防止重复检验。
a=b=[1,2,3] 浅复制,一个变都变,用的同一个地址。
a=copy.deepcopy(b):深复制,形成新的单独地址。
def commonChars(self, A):
"""
:type A: List[str]
:rtype: List[str]
"""
#思路一:
leng=len(A)
A = [list(word) for word in A]
#必须转换为list,否则AttributeError: 'str' object has no attribute 'remove'
res=[]
A0=A[0]
uniq=list(set(A0))
for x in uniq:
times=A0.count(x)
for j in A[1:]:
times=min(times,j.count(x))
if times==0:
break
if times>0:
res+=[x]*times
return res
"""
#思路二
leng=len(A)
A = [list(word) for word in A]
#必须转换为list,否则AttributeError: 'str' object has no attribute 'remove'
res=[]
A0=A[0]
for x in A0:
flag=True
for j in range(1,leng):
if x not in A[j]:
flag=False
break
else:
A[j].remove(x)
if flag == True:
res.append(x)
else:
continue
return res
"""
2.17 杨辉三角
思路:动态规划。注意特殊情况和规律开始的行(第三行开始,要特殊处理前两行)
#不必单独考虑0 1 情况,更简洁
res=[]
for i in range(numRows):
now=[1]*(i+1)
if i >1:
for j in range(1,i):
now[j]=pre[j-1]+pre[j]
res.append(now)
pre=now
return res
"""
res=[[1],[1,1]]
if numRows==0:
return []
elif numRows==1:
return [[1]]
elif numRows==2:
return res
else:
for i in range(2,numRows):
temp=[1]*(i+1)
for j in range(1,i):
temp[j]=res[i-1][j-1]+res[i-1][j]
res.append(temp)
return res
"""
2.18查询后的偶数和
思路:遇到这种动态变化,但是需要每次都求和或者其他重复性的操作,要避免每次的重复操作,时间复杂度太高。
开始求出总和,然后根据每次的变化,动态加减变化,避免重复操作。动态思想。
每一步都要执行的操作,重复操作,要避免!!!动态变化增删
def sumEvenAfterQueries(self, A, queries):
"""
:type A: List[int]
:type queries: List[List[int]]
:rtype: List[int]
"""
#开始就统计和,然后每次动态增减,避免每次都重复求和
res=[]
sumnum=sum(list(filter(lambda x: x%2==0, A)))
for i in range(len(queries)):
index=queries[i][1]
val=queries[i][0]
if A[index]%2==0:
sumnum-=A[index]
A[index]+=val
if A[index]%2==0:
sumnum+=A[index]
res.append(sumnum)
return res
"""
#每次都求和,会超时
res=[]
for i in range(len(queries)):
index=queries[i][1]
val=queries[i][0]
A[index]+=val
sumnum=sum(list(filter(lambda x: x%2==0, A)))
res.append(sumnum)
return res
"""
2.19 重塑矩阵
reshape为r*c
思路一:将二维矩阵降为一维,然后没隔c个元素取出
思路二:先初始化指定shape,然后遍历赋值,col每到c就置零,并raw+=1
but:
[[1,2],[3,4]]
4
1
解答错误:[[4],[4],[4],[4]]?原因:
res=[[1]*c]*r ,当c=1时,各行都会同时赋值:
a=[[1]]*3
a[2][0]=0
print(a) :[[0], [0], [0]]避免类似赋值,用for:
res=[[1 for i in range(c)] for _ in range(r)]
def matrixReshape(self, nums, r, c):
"""
:type nums: List[List[int]]
:type r: int
:type c: int
:rtype: List[List[int]]
"""
raws=len(nums)
cols=len(nums[0])
if raws*cols!=r*c:
return nums
#思路一:将二维数组降为1维,每隔c个取出
#nums_1d=[i for raw in nums for i in raw]
nums_1d=[]
for i in range(raws):
nums_1d+=nums[i]
res=[]
temp=0
for raw in range(r):
res.append(nums_1d[temp:temp+c])
temp+=c
return res
"""
#思路二,初始化新矩阵,然后遍历赋值
res=[[1]*c]*r
col=0
raw=0
for i in range(raws):
for j in range(cols):
res[raw][col]=nums[i][j]
col+=1
if col==c:
col=0
raw+=1
return res
"""
2.20 托普利茨矩阵
思路:只需判断:前行中除最后一个元素外剩余的元素完全等于后行中除第一个元素外剩余的元素。
def isToeplitzMatrix(self, matrix):
"""
:type matrix: List[List[int]]
:rtype: bool
"""
#只需判断:前行中除最后一个元素外剩余的元素完全等于后行中除第一个元素外剩余的元素。
raws=len(matrix)
cols=len(matrix[0])
for i in range(1,raws):
if matrix[i][1:]!=matrix[i-1][:-1]:
return False
return True
2.21 移除数组中的元素(指定值)
双指针。(可以一前一后也可以都在前面符合条件再移动)原地删除,返回的是最终数组的长度。
def removeElement(self, nums, val):
"""
:type nums: List[int]
:type val: int
:rtype: int
"""
leng=len(nums)
#思路一:双指针,一前一后,注意特殊情况[2] 3 ,此时返回1而非0
l=0
r=leng-1
if leng==0:
return 0
while l<r:
while l<r and nums[l]!=val:
l+=1
while l<r and nums[r]==val:
r-=1
nums[l],nums[r]=nums[r],nums[l]
if nums[r]!=val:
return r+1
return r
"""
#思路二:双指针,但不是一前一后,而是都从前开始,一个先移动,另一个符合赋值条件后再移动
index=0
for i in range(leng):
if nums[i]!=val:
nums[index]=nums[i]
index+=1
return index
"""
2.22 移动零
移动到末尾,并保证其余元素相对位置不变(稳定性)
思路一:稳定性——插入思想,太慢了
思路二:双指针,头,最后将尾元素赋值为0
思路三:pop-append(0),最快
def moveZeroes(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
"""
#插入排序思想,最慢,O(nlog(n))
leng=len(nums)
if leng<=0:
return nums
for i in range(1, leng):
if nums[i]!=0:
temp=nums[i]
j=i
while j >0 and nums[j-1]==0:
nums[j],nums[j-1]=nums[j-1],nums[j]
j-=1
nums[j]=temp
return nums
#双-头指针O(n)
leng=len(nums)
if leng<=0:
return nums
index=0
for i in range(leng):
if nums[i]!=0:
nums[index]=nums[i]
index+=1
if index!=leng:
nums[index:]=[0]*(leng-index)
"""
#pop,尾部填加0,O(n)
leng=len(nums)
if leng<=0:
return nums
for i in range(leng-1,-1,-1):
if nums[i]==0:
nums.pop(i)
nums.append(0)
#2020.12月重做:
def moveZeroes(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
j=0
for i in range(len(nums)):
if nums[i]!=0:
if i>j:
nums[j]=nums[i]
nums[i]=0
j+=1
"""
#插入排序思想,每次把i前的0移到当前位置,复杂度O(n*n)
for i in range(1,len(nums)):
if nums[i]!=0:
temp=nums[i]
j=i
while j>0 and nums[j-1]==0:
nums[j],nums[j-1]=nums[j-1],nums[j]
j-=1
nums[j]=temp
"""
"""
#冒泡排序思想,每次把0交换到最后面,复杂度O(n*n)
for i in range(len(nums)):
for j in range(1,len(nums)-i):
if nums[j-1]==0:
nums[j],nums[j-1]=nums[j-1],nums[j]
"""
"""
#双指针,把右侧的不为零的,依次赋值给左边,复杂度O(n)
left=0
right=0
while right<len(nums):
if nums[right]!=0:
nums[left]=nums[right]
left+=1
right+=1
for i in range(left,len(nums)):
nums[i]=0
"""
2.23 买卖股票的最佳时机 I 只能一次交易
动态规划:
前i天的最大收益 = max{前i-1天的最大收益,第i天的价格-前i-1天中的最小价格}
low记录最小值索引,每次求当前值和最小值的差值,并记录最大值
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
leng=len(prices)
maxnum=0
low=0
for i in range(1,leng):
if prices[i]<prices[low]:
low=i
maxnum=max(maxnum,prices[i]-prices[low])
return maxnum
2.24 买卖股票的最佳时机 II 可以多次交易
"[7, 1, 5, 6]
第二天买入,第四天卖出,收益最大(6-1),所以一般人可能会想,怎么判断不是第三天就卖出了呢? 这里就把问题复杂化了,根据题目的意思,当天卖出以后,当天还可以买入,所以其实可以第三天卖出,第三天买入,第四天又卖出((5-1)+ (6-5) === 6 - 1)。所以算法可以直接简化为只要今天比昨天大,就卖出。"连续递增部分的累加和。
越觉得复杂的,可能思路越简单,只是没有找到规律。大道至简。
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
profit=0
differ=0
for i in range(1,len(prices)):
differ=prices[i]-prices[i-1]
if differ>0:
profit+=differ
return profit
2.25 最大连续1的个数
def findMaxConsecutiveOnes(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
#思路一:记录当前连续数以及最大数
leng=len(nums)
count=0
temp=0
for i in range(leng):
if nums[i]==1:
temp+=1
else:
count=max(count,temp)
temp=0
count=max(count,temp)# 考虑[1]的情况
return count
"""
# 思路二:字符串思想,以0切分的最大子串长度
return max(len(substr) for substr in ''.join([str(x) for x in nums]).split('0'))
"""
2.26 缺失数字
求和做差;相同的数出现两次,想到异或
def missingNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
"""
#思路一:数学方法,求和,相减
n=len(nums)
sumnum=n*(n+1)/2
subsum=0
for num in nums:
subsum+=num
return sumnum-subsum
"""
#思路二:异或
leng=len(nums)
sumnum=leng
for i in range(leng):
sumnum^=nums[i]
sumnum^=i
return sumnum
2.27 两数之和 II - 输入有序数组(和为target的两个数的下标,下标从1开始)
def twoSum(self, nums, target):
"""
:type numbers: List[int]
:type target: int
:rtype: List[int]
"""
leng=len(nums)
"""
#双指针
leng=len(nums)
l=0
r=leng-1
while l<r:
if nums[l]+nums[r]<target:
l+=1
elif nums[l]+nums[r]>target:
r-=1
else:
return [l+1,r+1]
"""
#字典查找
d={}
for i in range(leng):
another=target-nums[i]
if nums[i] in d:
return [d[nums[i]]+1,i+1]
d[another]=i #记录index为i 的 另一半
2.28 找到所有数组中消失的数字(1-n的n个数,有重复的)
字典统计出现次数,最简单;
原地移动,使下标i+1(1-n 从1开始的数字)和value一一对应,所以每次交换当前index和index为value-1位置的值(如果相等,则说明改值是重复的,不再交换),直到和下标i+1相等。排列完成后,遍历出和下标不对应的值即为没出现的值。
def findDisappearedNumbers(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
#
leng=len(nums)
for i in range(leng):
while nums[i]!=i+1:
temp=nums[i]
if nums[temp-1]==nums[i]:
break
nums[temp-1],nums[i]=nums[i],nums[temp-1]
res=[]
for j in range(leng):
if nums[j]!=j+1:
res.append(j+1)
return res
2.29 公平的糖果交换
交换一组元素,使两边和相等
计算差值,再查找每个元素可交换的另一半是否在另一个数组里(set查找比list更快)
def fairCandySwap(self, A, B):
"""
:type A: List[int]
:type B: List[int]
:rtype: List[int]
"""
differ=(sum(A)-sum(B))//2
setb=set(B)
for i in range(len(A)):
if A[i]-differ in setb:
return [A[i],A[i]-differ]
2.30 数组的度
字典的value还可以是list !!! 统计并查找首尾的位置时,方便简单!
用字典统计每个元素出现的indexs 组成数组,数组长度就是度,数组首位就是第一次出现和最后一次出现的index位置
d[num][0]——和二维数组形式一样
def findShortestSubArray(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
d={}
leng=len(nums)
for i in range(leng):
if nums[i] not in d:
d[nums[i]]=[i]
else:
d[nums[i]].append(i)
degree=0
for num in d:
degree=max(degree,len(d[num]))
res=leng
for num in d:
if len(d[num])==degree:
res=min(res,d[num][-1]-d[num][0]+1)
return res
2.31 删除排序数组中的重复项
描述:给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度
思路:双头指针——快慢指针。
def removeDuplicates(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
leng=len(nums)
if leng<=1:
return leng
s=0
f=1
while f <leng:
temp=nums[s]
if nums[f]!=temp:
s+=1
nums[s]=nums[f]
f+=1
return s+1
2.32 合并两个有序数组
思路一:将nums2插入到后面,然后整体排序,O(NlogN)
思路二:双指针。从尾部开始合并。从后向前遍历,找到最大的放到最后面。nums1的长度是m+n, m个有效元素,后面n个是无效0元素。
def merge(self, nums1, m, nums2, n):
"""
:type nums1: List[int]
:type m: int
:type nums2: List[int]
:type n: int
:rtype: None Do not return anything, modify nums1 in-place instead.
"""
"""
#思路一:将nums2插入到后面,然后排序
i=m
for num in nums2:
nums1[i]=num
i+=1
#nums1[m:] = nums2
nums1.sort()
"""
#思路二:双指针
index=m+n-1
i=m-1
j=n-1
while i >=0 and j>=0:
if nums1[i]>nums2[j]:
nums1[index]=nums1[i]
i-=1
else:
nums1[index]=nums2[j]
j-=1
index-=1
while i>=0:
nums1[index]=nums1[i]
index-=1
i-=1
while j>=0:
nums1[index]=nums2[j]
index-=1
j-=1
2.33 搜索插入位置
找到target在一个排序数组中的index或者是应该插入的位置
遍历,二分。
二分查找容易出现死循环。条件判断非常重要。
while low < high:
mid=(high-low)//2
low=mid+1 ,high=mid.
def searchInsert(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
"""
#一:单独考虑首尾情况,不够简洁
leng=len(nums)
if target<=nums[0]:
return 0
elif target>nums[-1]:
return leng
else:
for i in range(1,leng):
if target>nums[i-1] and target<=nums[i]:
return i
break
#二:减少了条件判断,简洁
leng=len(nums)
i=0
while i<leng and nums[i]<target:
i+=1
return i
"""
#三:二分查找
l=0
r=len(nums)-1
while l<r:
mid=l+(r-l)//2
if nums[mid]<target:
l=mid+1 #注意,l=mid+1 !!!
elif nums[mid]>target:
r=mid
else:
return mid
if nums[l]<target: #特殊:target插入到最后的情况
return l+1
return l
2.34 将数组分成和相等的三个部分
描述:给定一个整数数组
A
,只有我们可以将其划分为三个和相等的非空部分时才返回true
,否则返回false
。思路:首先,不能被3整除的一定是false;统计连续子数组的和,若找到两个子数组的和与三分之一总和相等,则一定true。也可以双指针从两边开始统计,也可以从前到后依次统计
def canThreePartsEqualSum(self, A):
"""
:type A: List[int]
:rtype: bool
"""
leng=len(A)
sumnum=sum(A)
if sumnum%3!=0:
return False
one_third=sumnum//3
count=0
sub_sum=0
i=0
for i in range(leng):
sub_sum+=A[i]
if sub_sum==one_third:
count+=1
sub_sum=0
if count==2:
return True
break
return False
2.35 1比特与2比特字符
只与末尾元素0前面的连续的1的个数有关系。 奇数时一定是两比特,偶数时一定是一比特。总结规律很重要。
count=0 #统计末位0前面的连续的1的个数
leng=len(bits)
index=leng-2 #直接从倒数第二个开始查看
while index>=0 and bits[index]==1:
count+=1
index-=1
#查看连续的1的个数是奇数还是偶数
if count%2==0: #偶数个1时,最后一个0是一比特字符
return True
else: #奇数个1时,最后一个0一定与其前面的1构成两比特字符
return False
2.36 复写零
思路:复制原数组,遍历。改变原数组的值,用其下标控制长度。
def duplicateZeros(self, arr):
"""
:type arr: List[int]
:rtype: None Do not return anything, modify arr in-place instead.
"""
leng=len(arr)
copy=arr[:] #注意是深复制
i=0
j=0
while j <leng:
#当前一个是零,那么arr在该位置也要置零,copy下标此时不变
if i>0 and copy[i-1]==0:
arr[j]=0
copy[i-1]=1 #记得将前一个0替换为其他
else:
#当前一个不是0,直接赋值给arr,copy下标继续前进
arr[j]=copy[i]
i+=1
j+=1 #arr下标每次都前进
2.37 大样本统计
审题! count[k]表示整数k的个数! 重新建立新数组内存会超限
难在求中位数的地方: 奇数个元素:如5 中位数是下标为5/2的元素,是第3个数,所以是count>=num/2+1
偶数个的时候,是中间两个数的平均数:分别找到count>=num/2 和count>=num/2+1的两个数
def sampleStats(self, count):
"""
:type count: List[int]
:rtype: List[float]
"""
leng=len(count)
ct=[]
res=[]
for i in range(leng): #第一个非零的就是最小数
if count[i] !=0:
break
res.append(round(i,5))
for j in range(leng-1,-1,-1):#最后一个非零的是最大数
if count[j] !=0:
break
res.append(round(j,5))
sumnum=0
num=0
for i in range(leng):
sumnum+=count[i]*i
if count[i]!=0:
num+=count[i]
res.append(round(float(sumnum)/num,5)) #平均数
#求中位数,太复杂了
mid=0
if num%2==0: #个数为偶数
s=0
for i in range(leng):
s+=count[i]
if s>=num/2:
mid+=i
break
s=0
for i in range(leng):
s+=count[i]
if s>=num/2+1:
mid+=i
break
mid=round(mid/2.0,5)
else: #个数为奇数
s=0
for j in range(leng):
s+=count[j]
if s>=num/2+1:
mid=round(j,5)
break
res.append(mid)
maxnum=max(count)
c=count.index(maxnum) #众数
res.append(round(c,5))
return res
2.38 山脉数组中查找目标值(二分查找典型)
对称的,有峰有谷。二分先找到波峰,然后先二分左面找指定值(保证index最小),然后二分右面(防止左面找不到)
def findInMountainArray(self, target: int, mountain_arr: 'MountainArray') -> int:
n = mountain_arr.length()
left, right = 1, n-2
while left < right:
mid = (left+right)//2
if mountain_arr.get(mid) > mountain_arr.get(mid+1):
right = mid
else:
left = mid+1
top = left
left, right = 0, top
while left <= right:
mid = (left+right)//2
cur = mountain_arr.get(mid)
if cur == target:
return mid
elif cur > target:
right = mid - 1
else:
left = mid + 1
left, right = top, n-1
while left <= right:
mid = (left+right)//2
cur = mountain_arr.get(mid)
if cur == target:
return mid
elif cur < target:
right = mid - 1
else:
left = mid + 1
return -1
2.39 分糖果 II
重要的是找到规律。循环对n个元素操作,i+=1, i%num
class Solution(object):
def distributeCandies(self, cand, num):
"""
:type candies: int
:type num_people: int
:rtype: List[int]
"""
res=[0]*num
s=0 #查看当前和
t=1
while s<=cand:
flag=True #记录本次循环内是否已经break
for i in range(num):
res[i]+=t
s+=t #求和
if s>cand:
flag=False
break
t+=1 #糖果,每次+1
if not flag: #跳出循环
break
res[i]+=cand-s #最后一个人的糖果数,要减去多出来的
return res
"""
def distributeCandies(self, candies: int, num_people: int) -> List[int]:
ans = [0]*num_people
cur = 1
i = 0
while candies > 0:
temp = min(candies, cur)
ans[i] += temp
candies -= temp
cur += 1
i += 1
i %= num_people
return ans
"""
2.40 二叉树寻路
重要的是找到规律(从个例开始梳理,走1-2个找到普遍规律)
每次都要用到的函数,拿出来单独定义(查找行的函数,查找父节点的函数)
查找行的函数可以一步搞定: n=len(bin(label)) 二进制表示的长度
class Solution(object):
def pathInZigZagTree(self, label):
"""
:type label: int
:rtype: List[int]
"""
res=[]
while label!=1: #每次更新节点,直到根节点
res.append(label)
label=self.dfs(label) #更新为其父节点
res.append(label)
return res[::-1]
#查找父节点
def dfs(self,x):
n=self.findn(x)
newlabel=(2**(n-1))-(x-2**(n-1))//2-1
return newlabel
#查找label所在的行
def findn(self,num):
for i in range(0,num+1):
if num>=2**(i-1) and num<2**i:
return int(i)
3 动态规划
递归是从问题的结果倒推,直到问题的规模缩小到寻常。 那么动态规划就是从寻常入手, 逐步扩大规模到最优子结构。
保存临时值的可以是 变量,数组或二维数组
3.1 爬楼梯
状态转移方程:f [i]=f [i-1] +f [ i-2]
def climbStairs(self, n):
"""
:type n: int
:rtype: int
"""
"""
#递归方法,时间超限
if n==1:
return 1
if n==2:
return 2
return self.climbStairs(n-1)+self.climbStairs(n-2)
"""
if n==1:
return 1
elif n==2:
return 2
else:
one=1
two=2
for i in range(3,n+1):
temp=one+two
one=two
two=temp
return temp
3.2 解码方法1
dp[i]存储以第 i 个元素结尾的方法数量
状态转移方程: dp[i]=dp[i-1]+g * dp[i-2](当两位数满足时g=1)临界条件:dp[0]=1,dp[1]=0/1
stupid error:
开始if 条件放在了循坏外面:error: one two has refered before...
将two命名为s, 与输入s冲突了: intergal has no __gettiem__....
def numDecodings(self, s):
"""
:type s: str
:rtype: int
"""
#从前向后,或者从后向前都可以,dp[i]表示以第i个元素结尾的方法数量
#dp[i]=dp[i-1]+g*dp[i-2](当两位数满足时g=1)
leng=len(s)
dp=[0]*(leng+1)
dp[0]=1
dp[1]=1 if s[0]!='0' else 0
for i in range(2,leng+1):
one=int(s[i-1:i])
two=int(s[i-2:i])
if two>=10 and two<=26:
dp[i]=dp[i-2]
if one>0 and one<10:
dp[i]+=dp[i-1]
#否则 one=0,没有组合,则为0
return dp[leng]
3.3 解码方法 2
思路:第i位分情况讨论,在每种情况内再细分
1 特殊情况,*:
单独分解,9种,9*dp[i-1];
考虑前一位,如果是1,则组合起来有11,12...19,九种;如果前一位是2,组合21,22...26,六种;如果前一位是*,则有11,12...26共15种,15*dp[i-2]
2 特殊情况,0:
单独分解,0;
考虑前一位,如果是1或2,组合有一种,dp[i-2];如果前一位是*,则可以有10/20两种,2*dp[i-2];其他,0
3 其他情况1~9:
单独分解,1种;
考虑前一位,如果是* &该位<=6,两种,2*dp[i-2];如果是*&该位>6,一种,dp[i-2];
(通过97%,太长的超出时间限制)
def numDecodings(self, s):
"""
:type s: str
:rtype: int
"""
leng=len(s)
dp=[0 for i in range(leng+1)]
dp[0]=1
if s[0]=='0':
dp[1]=0
elif s[0]=="*":
dp[1]=9
else:
dp[1]=1
for i in range(2,leng+1):
if s[i-1]=="*":
dp[i]+=9*dp[i-1]
if s[i-2]=='1': #找了一下午错,发现把s写成了dp,dp[i-2]=='1' :
dp[i]+=9*dp[i-2]
elif s[i-2]=='2':
dp[i]+=6*dp[i-2]
elif s[i-2]=="*":
dp[i]+=15*dp[i-2] #"**"联合解码,11,12,13....26,共15种
else:
continue
elif s[i-1]=="0":
#不能单独解码,直接考虑两个组合
if s[i-2]=="1" or s[i-2]=="2":
dp[i]+=dp[i-2]
elif s[i-2]=="*":
dp[i]+=2*dp[i-2]
else:
continue #不能组合,则为0,如1003,编码方式为0
else:
dp[i]+=dp[i-1]
if s[i-2]=="1" or (s[i-2]=="2" and s[i-1]<='6'): #s[i-1]<'6',字符和字符比较,不能和整数6比较
dp[i]+=dp[i-2]
elif s[i-2]=="*" :
if s[i-1]<='6':
dp[i]+=2*dp[i-2] #此时*有1 2两种可能
else: #此时*只能为1(1*7,18+1=19)
dp[i]+=dp[i-2]
else:
continue
return dp[leng]%1000000007
3.4 除数博弈
找规律: 最后谁拿偶数2,谁赢。
奇数减除数,一定是偶数。偶数减除数,可奇可偶。所以如果爱丽丝拿到了奇数,留给bobo的一定是偶数,鲍勃会想办法继续留给爱丽丝奇数,最后鲍勃一定赢。反之亦然。
def divisorGame(self, N):
"""
:type N: int
:rtype: bool
"""
#奇数减除数,一定是偶数。偶数减除数,可奇可偶。最后谁拿到2,谁赢。
if N&1==0:
return True
else:
return False
3.5 区域和检索 - 数组不可变
会多次调用! 每次都逐一求和太慢了
用一个数组保存子数组和:从头到index结尾的子数组。每次求和时直接相减即可
动态规划思想:保存临时值,由此求解。
def __init__(self, nums):
"""
:type nums: List[int]
"""
self.nums=nums
leng=len(self.nums)
#将以i结尾的和存储到一维数组中
if leng>0:
self.sums=[nums[0]]
for i in range(1,leng):
self.sums.append(self.nums[i]+self.sums[i-1])
def sumRange(self, i, j):
"""
:type i: int
:type j: int
:rtype: int
"""
return (self.sums[j]-self.sums[i-1] ) if i !=0 else self.sums[j]
3.6 买卖股票的最佳时机
时刻保存一个最小值和最大差值,比较 最大差值 与(当前值-最小值),更新最大差值
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
leng=len(prices)
maxnum=0
low=0
for i in range(1,leng):
if prices[i]<prices[low]:
low=i
maxnum=max(maxnum,prices[i]-prices[low])
return maxnum
3.7 最大子序和
状态转移方程: f[i]=nums[i] ,if f[i-1] <=0(任何数加一个负数都会减小,所以从当前数开始累加)
else: f[i]= f[i-1]+nums[i]
def findmaximumsubarraysum(self,nums):
if nums==[]:
return None
cur=nums[0]
maxnum=cur
leng=len(nums)
for i in range(1,leng):
if cur<0:
cur=nums[i]
else:
cur+=nums[i]
maxnum=max(cur,maxnum)
return maxnum
3.8 打家劫舍
错误思路:开始以为设置两个前后指针,间隔累加求最大,但是,3113这种情况没考虑到,就是完全可以跳两个累加。
状态转移方程: f[i]= max( f[i-2] , f [i-3] )+nums[i-1] 取第i个元素的所有方法的最大值,是间隔一个和间隔两个的最大值,加上当前元素值
注意最后返回的是dp[i] 与dp[i-1] 的最大值
def rob(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if nums==[]:
return 0
leng=len(nums)
if leng==1:
return nums[0]
else:
#dp[i]保存的是取第i个元素时的全局最大值(一定要加上第i个的所有方法的最大值)
dp=[0]*(leng+1)
dp[0]=0
dp[1]=nums[0]
dp[2]=nums[1]
for i in range(3,leng+1):
dp[i]=max(dp[i-2],dp[i-3])+nums[i-1] #间隔一个和间隔两个的最大值
return max(dp[leng],dp[leng-1]) #全局最大值存在于末尾两个之中
"""
#dp[i]保存的是当前元素结尾的全局最大值(全局,有可能是i-1,不一定取第i个)
dp=[0]*(leng)
dp[0]=nums[0]
dp[1]=max(nums[1],nums[0])
for i in range(2,leng):
dp[i]=max(dp[i-2]+nums[i],dp[i-1])
return dp[-1]
"""
3.9 使用最小花费爬楼梯
def minCostClimbingStairs(self, cost):
"""
:type cost: List[int]
:rtype: int
"""
leng=len(cost)
dp=[0]*(leng+1)
dp[0]=0
dp[1]=0
for i in range(2,len(cost)+1):
dp[i]=min(cost[i-1]+dp[i-1],cost[i-2]+dp[i-2])
return dp[leng]
3.10 比特位计数
动态规划,重在如何用到已经求解的子问题的解。
这里求n的1的个数,那如何利用比n小的? 右移,或者n&(n-1),都会获得比n小的数。然后再找关系,建立状态转移方程。
def countBits(self, num):
"""
:type num: int
:rtype: List[int]
"""
#位运算:与运算&
#i&(i-1)相当于把i的二进制表示形式的最右边的1去掉了,
#那么用已经计算过的i&(i-1)的个数再加上1就ok
dp=[0]*(num+1)
#dp[0]=0
for i in range(1,num+1):
dp[i]=dp[i&(i-1)]+1
return dp
"""
#右移:
#如果是偶数,右移一位,1的个数仍然相同;如果是奇数,右移一位再加1
#判断奇偶:奇:i&1==1, 偶:i&1==0
dp=[0]*(num+1)
for i in range(1,num+1):
dp[i]=dp[i>>1]+(i&1)
return dp
"""
3.11最小路径和
最先想到的,就是用二维数组,dp[i][j]保存走到 每一个元素时的最小和,最后返回的是dp[ -1] [ -1]。
状态转移方程: dp[i][j]=min( dp[ i-1] [j],dp[ i] [j-1])+grid[i][j]
边界条件:第一行、第一列。如果简化边界条件,up left要设为无穷大(求最大路径和时,设为0)
一维数组表示:
def minPathSum(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
rows=len(grid)
cols=len(grid[0])
#dp=[[0 for j in range(cols)] for i in range(rows)]
dp=[0]*cols #方法三
for i in range(rows):
for j in range(cols):
"""
#方法一:二维数组
if i==0:
if j==0:
dp[i][j]=grid[i][j]
else:
dp[i][j]=dp[i][j-1]+grid[i][j]
else:
if j==0:
dp[i][j]=dp[i-1][j]+grid[i][j]
else:
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j]
#方法二:简化判断条件:
#要求最小值,所以i==0或j==0时设为无穷大才会取前一个数
up=float('inf') #i=0时,设为无穷大
left=float('inf')#j=0时,设为无穷大
if i>0:
up=dp[i-1][j]
if j>0:
left=dp[i][j-1]
minnum=min(up,left)+grid[i][j]
if i==0 and j==0: #第一个要指定
minnum=grid[i][j]
dp[i][j]=minnum
"""
#方法三:一维数组
up=float('inf') #i=0时,设为无穷大
left=float('inf')#j=0时,设为无穷大
if i>0:
up=dp[j]
if j>0:
left=dp[j-1]
minnum=min(up,left)+grid[i][j]
if i==0 and j==0: #第一个要指定
minnum=grid[i][j]
dp[j]=minnum
return dp[-1]
3.12 三角形最小路径和
思路一:二维数组,自顶向下,状态转移方程:dp[i][j]=min( dp[ i-1] [j],dp[ i-1] [j-1])+triangle[i][j]
思路二:自底向上,可以用一维数组表示,每次更新时dp用的是前一层的相应位置的最小值,完成后变成本层的dp
def minimumTotal(self, triangle):
"""
:type triangle: List[List[int]]
:rtype: int
"""
"""
#方法一,自上向下
rows=len( triangle)
#dp= triangle
dp=[[0 for j in range(rows)] for i in range(rows)]#每次只比较i-1 j 和 i-1 j-1,所以其他cols设为零并不影响
for i in range(rows):
for j in range(i+1):
#简化判断条件,要求最小值,所以i==0或j==0时设为无穷大才会取前一个数
up=float('inf') #i=0时,设为无穷大
left=float('inf')#j=0时,设为无穷大
if i>0 and j<i: #判断条件,j需要小于i,否则dp[i-1][j]会越界
up=dp[i-1][j]
if j>0:
left=dp[i-1][j-1]
minnum=min(up,left)+ triangle[i][j]
if i==0 and j==0:
minnum= triangle[i][j]
dp[i][j]=minnum
return min(dp[-1])
方法二:自下向上
#在原矩阵上操作
rows=len(triangle)
for i in range(rows-2,-1,-1):
for j in range(i+1): #注意,每一行的cols都不相同
triangle[i][j]+=min(triangle[i+1][j],triangle[i+1][j+1])
return triangle[0][0]
"""
#方法三:自下向上,一维数组记录
#只使用一维数组记录每一层的最小值
rows=len( triangle)
dp=[0]*(rows+1) #在最下面多赋值一行
for i in range(rows-1,-1,-1):
for j in range(i+1):
#这里的dp[j] 使用的时候默认是上一层的,赋值之后变成当前层
dp[j]=min(dp[j+1],dp[j])+ triangle[i][j]
return dp[0]
! 3.13 最长公共子序列问题(类)
状态转移方程:
def minDistance(self, s1, s2):
"""
:type word1: str
:type word2: str
:rtype: int
"""
leng1=len(s1)
leng2=len(s2)
#dp[i][j] 表示字符串1前i个字符,字符串2前j个字符的最长公共子序列
dp=[[0 for i in range(leng2+1)] for j in range(leng1+1)]
#dp保存的是s1前i个元素和s2前j个元素的最长公共子序列的长度
for i in range(1,leng1+1):
for j in range(1,leng2+1):
if s1[i-1]==s2[j-1]:
dp[i][j]=dp[i-1][j-1]+1 #如果相同,那么公共子序列长度又多了一个
else:
dp[i][j]=max(dp[i-1][j],dp[i][j-1]) #如果不同,是(i-1 j) 与(i,j-1)的最大值
return dp[-1][-1]
3.14 两个字符串的最小ASCII删除和
dp [ i] [ j] 代表s1 前 i 个元素和 s2 前 j 个元素匹配所需删除的最小和。
状态转移方程:如果s1[i] 和s2 [j] 相等,则不删除,dp[i][j]=dp[i-1] [j-1],如果不等:dp[i][j]=min( 删除s1 第i个元素的和,删除s2第j个的和)。每次删除元素,总和加上该元素的ord .
def minimumDeleteSum(self, s1, s2):
"""
:type s1: str
:type s2: str
:rtype: int
"""
leng1=len(s1)
leng2=len(s2)
#dp[i][j] 表示字符串1前i个字符,字符串2前j个字符达到完全一致需要删除的最少ASCII和
dp=[[0 for i in range(leng2+1)] for j in range(leng1+1)]
"""
for i in range(1,leng1+1): #dp[i][0] 行,存储s1前i个元素和空字符串匹配所需删除的ascii和
dp[i][0]+=dp[i-1][0]+ord(s1[i-1])
for j in range(1,leng2+1): #列,存储s2前j个元素和空字符匹配所需删除的值
dp[0][j]+=dp[0][j-1]+ord(s2[j-1])
for i in range(1,leng1+1):
for j in range(1,leng2+1):
if s1[i-1]==s2[j-1]:
dp[i][j]=dp[i-1][j-1]
else:
del_s1=dp[i-1][j]+ord(s1[i-1]) #s1删除第i个元素来匹配
del_s2=dp[i][j-1]+ord(s2[j-1]) #s2删除第j个元素来匹配
dp[i][j]=min(del_s1,del_s2)
return dp[-1][-1]
"""
#方法二:统计最长公共子序列的ascii的和,最后返回其余ascii总和
#此时dp[i][j]表示s1前i个和s2前j个元素的最长公共子序列的ASCII和
for i in range(1,leng1+1):
for j in range(1,leng2+1):
if s1[i-1]==s2[j-1]:
dp[i][j]=dp[i-1][j-1]+ord(s1[i-1])
else:
del_s1=dp[i-1][j] #s1删除第i个元素的最长公共子序列
del_s2=dp[i][j-1] #s2删除第j个元素的最长公共子序列
dp[i][j]=max(del_s1,del_s2)
sum1=sum(map(ord,s1))-dp[-1][-1]
sum2=sum(map(ord,s2))-dp[-1][-1]
return sum1+sum2
3.15 两个字符串的删除操作
解法与上题类似
方法一:直接法,统计不同的元素个数。状态转移方程:如果s1[i] 和s2 [j] 相等,则不删除,dp[i][j]=dp[i-1] [j-1],如果不等:dp[i][j]=min( 删除s1 第i个元素的和,删除s2第j个的和).每删除一个元素,总步数加1
方法二:间接法。统计最长公共子序列,返回其余的不同元素的个数。
def minDistance(self, s1, s2):
"""
:type word1: str
:type word2: str
:rtype: int
"""
leng1=len(s1)
leng2=len(s2)
#dp[i][j] 表示字符串1前i个字符,字符串2前j个字符达到完全一致需要的步数
dp=[[0 for i in range(leng2+1)] for j in range(leng1+1)]
"""
#方法一:直接统计删除的元素个数
for i in range(1,leng1+1): #dp[i][0] 行,存储s1前i个元素和空字符串匹配所需操作的步数
dp[i][0]+=dp[i-1][0]+1
for j in range(1,leng2+1): #列,存储s2前j个元素和空字符匹配所需的步数
dp[0][j]+=dp[0][j-1]+1
for i in range(1,leng1+1):
for j in range(1,leng2+1):
if s1[i-1]==s2[j-1]:
dp[i][j]=dp[i-1][j-1]
else:
del_s1=dp[i-1][j]+1 #s1删除第i个元素来匹配,所以步数加一
del_s2=dp[i][j-1]+1 #s2删除第j个元素来匹配
dp[i][j]=min(del_s1,del_s2)
return dp[-1][-1]
"""
#方法二,间接方法,统计相同的个数,即最长公共子序列,最后用总元素个数减去相同的元素个数即可
#dp保存的是s1前i个元素和s2前j个元素的最长公共子序列的长度
for i in range(1,leng1+1):
for j in range(1,leng2+1):
if s1[i-1]==s2[j-1]:
dp[i][j]=dp[i-1][j-1]+1 #如果相同,那么公共子序列长度又多了一个
else:
dp[i][j]=max(dp[i-1][j],dp[i][j-1])
return leng1+leng2-2*dp[-1][-1] #返回的是除公共子序列外,所有不相同的元素个数
3.16 等差数列划分
所有连续的子数组中,组成等差的子数组的总和。比如12347,就是1234中的等差子数组,3个(比123多2个)
12345,6个(比1234多3个)
方法一:动态规划,dp[i]代表以A[i]结尾的比A[i-1]结尾的多的数量,最后总和就是总量,状态转移方程:dp[i]=dp[i-1]+1
方法二:计算等差的最长连续个数,每个连续子数组的等差子数组=(n-1)(n-2)/2 , 123:1, 1234:3
def numberOfArithmeticSlices(self, A):
"""
:type A: List[int]
:rtype: int
"""
"""
#方法一:动态规划,dp[i]代表以A[i]为结尾的比A[i-1]结尾的多的个数
leng=len(A)
dp=[0]*leng
for i in range(2,leng):
if A[i]-A[i-1]==(A[i-1]-A[i-2]):
dp[i]=dp[i-1]+1 #增加一个
return sum(dp)
"""
#方法二:遍历一遍,记录连续组成等差子数组的元素个数n1,n2...ni, 如12356789,记录3(123),5(56789)
#每个ni中有(ni-2)(ni-1)/2个等差子数组(ni=3:1个,ni=4:3个),求和
count=2
sumnum=0
leng=len(A)
for i in range(2,leng):
if A[i]-A[i-1]==(A[i-1]-A[i-2]):
count+=1
else:
sumnum+=(count-2)*(count-1)/2
count=2
sumnum+=((count-2)*(count-1)/2) #特殊情况:末尾是连续的等差子数组,最后记得加上
return sumnum
3.17 不同路径
方法一:动态规划,dp[i][j]表示走到grid[i][j]处的路径总和,返回dp[-1][-1]
方法二:组合问题,一共要走m+n-2步,从中挑出m-1步向下走。C(m-1,m+n-2)
def uniquePaths(self, m, n):
"""
:type m: int
:type n: int
:rtype: int
"""
dp=[[0 for j in range(m)] for i in range(n) ]
for i in range(n):
for j in range(m):
if i==0 or j==0: #第一行和第一列都是1种方法
dp[i][j]=1
else:
dp[i][j]=dp[i-1][j]+dp[i][j-1]
return dp[-1][-1]
! (递归)3.18 大礼包
递归+动态规划。 res=min(res, self.f( newneeds)) 不适用礼包和使用后的最小值。使用不知道使用哪个也不知道使用几个,遍历最好。
遍历大礼包,并判断大礼包是否符合要求,计算不使用大礼包的情况和使用的情况,取最小值,返回。每次计算使用后的新的needs物品数量,递归求解。
我的错误思路:想到用dp[i]记录i个大礼包的使用个数,但每一次都要计算不适用或者使用并比较,而且找不到状态转移方程。递归则解决了重复使用大礼包的问题(因为每次递归都从第一个大礼包开始遍历)
def shoppingOffers(self, price, special, needs):
"""
:type price: List[int]
:type special: List[List[int]]
:type needs: List[int]
:rtype: int
"""
#统计不适用大礼包的总价
n_gifts=len(needs)
res=sum([needs[i]*price[i] for i in range(n_gifts)])
#遍历每一个大礼包
for sp in special:
"""
#替换检查函数
newneeds=[needs[i]-sp[i] for i in range(len(needs))]
if min(newneeds)>=0:
"""
if self.check(sp,needs): #排除物品超界的情况
#统计使用大礼包后的新的物品数量
newneeds=[needs[i]-sp[i] for i in range(len(needs))]
#递归求解新物品需求下的价格
left=self.shoppingOffers(price,special,newneeds)+sp[-1]
#比较不适用大礼包和使用后的价格,取最小值
res=min(res,left)
return res #递归返回最小值
def check(self,sp,needs):
#检查礼包是否符合情况(数量是否超限)
n=len(needs)
for x in range(n):
if sp[x]>needs[x]:
return False #不会选择该礼包
return True
3.19 下降路径最小和
思路:状态转移方程(dp[i][j]=min(dp[i-1][ j], dp[i-1[ j-1], dp[i-1][ j+1])+A[j][j] )
dp[i][j]表示以A[i][j]结尾的所有路径中的最小和,最后返回最后一行的最小值。
边界条件:j=0,j=leng-1,i=0.
若要简化判断边界的条件,就要先预设好所有的边界。则可以省略多个 if 边界判断情况。
def minFallingPathSum(self, A):
"""
:type A: List[List[int]]
:rtype: int
"""
"""
rows=len(A)
cols=len(A[0])
dp=[[0 for j in range(cols)] for i in range(rows)]
dp[0]=A[0]
for i in range(1,rows):
for j in range(cols):
up=dp[i-1][j]
right=float('inf')
left=float('inf')
if j<cols-1:
right=dp[i-1][j+1]
if j>0:
left=dp[i-1][j-1]
dp[i][j]=min(min(up,right),left)+A[i][j]
return min(dp[-1])
"""
#简化判断条件:
#需要规定第一行和第一列
rows=len(A)
cols=len(A[0])
dp=[[0 for j in range(cols)] for i in range(rows)]
dp[0]=A[0] #边界1 :i==0
for i in range(1,rows):
dp[i][0]=min(dp[i-1][:2])+A[i][0] #边界2:每行的第一个元素都是上一行的前两个元素最小值加上A[i][0]
for j in range(1,cols):
dp[i][j]=min(dp[i-1][j-1:j+2])+A[i][j] #边界3:j=leng-1,不考虑,切片操作,可以超出上界,但不可低于下界。
return min(dp[-1])
3.20 整数拆分(乘积最大)
小于3的数,可以不必拆分(2:1*1,3:1*2,4:2*2,4可拆可不拆)
大于4的数,拆分后乘积一定更大(5:2*3,6:3*3)
所以,拆分后的因子一定是由1,2,3组成的。动态规划。
def integerBreak(self, n):
"""
:type n: int
:rtype: int
"""
#拆分后的因子是固定的;1,2,3
if n<=3:
return n-1
dp=[0]*(n+1)
dp[1]=1
dp[2]=2
dp[3]=3
for i in range(4,n+1):
for j in range(1,i/2+1): #对称的,只计算一半,记得i/2要+1
dp[i]=max(dp[i],dp[j]*dp[i-j])
return dp[n]
3.21完全平方数(拆分成若干个完全平方数之和,个数最少)
状态转移方程:dp[i]=min(i, dp[i-j^2]+1) 其中,j^2<i,从1遍历。
f(n) = 1 + min{ f(n-1^2), f(n-2^2), f(n-3^2), f(n-4^2), ... , f(n-k^2) //(k为满足k^2<=n的最大的k)
def numSquares(self, n):
"""
:type n: int
:rtype: int
"""
dp=[0]*(n+1)
dp[1]=1
for i in range(1,n+1):
dp[i]=i #假设由i个1组成,此时最长
j=1
while j*j<=i: #必须有等号:当i本身为完全平方数时,返回1
dp[i]=min(dp[i],dp[i-j*j]+1)
j+=1
return dp[n]
3.22 买卖股票的最佳时机含手续费
只有卖出的时候才付fee
思路:两个数组的动态规划。
两个数组,一个保存第i天有股票的情况的最大值dp1,一个保存第i天没有股票的情况dp2。最后返回的是最后一天没有股票的最大值。
两个状态转移方程:
dp1[i]=max(dp1[i-1],dp2[i-1]-prices[i])#前一天没有股票今天有,收益为前一天的收益-今天的股票价格price[i]
dp2[i]=max(dp2[i-1],dp1[i-1]+prices[i]-fee)#前一天有今天卖了的收益,要减去手续费
def maxProfit(self, prices, fee):
"""
:type prices: List[int]
:type fee: int
:rtype: int
"""
#两个数组,一个保存第i天有股票的情况dp1,一个保存第i天没有股票的情况dp2
leng=len(prices)
dp1=[0]*leng
dp2=[0]*leng
dp1[0]=-prices[0] #边界:第一天就有股票;第一天没有股票所以dp2[0]=0
for i in range(1,leng):
dp1[i]=max(dp1[i-1],dp2[i-1]-prices[i])#前一天没有股票今天有,收益为前一天的收益-今天的股票价格price[i]
dp2[i]=max(dp2[i-1],dp1[i-1]+prices[i]-fee)#前一天有今天卖了的收益,要减去手续费
return dp2[-1]#最后一天一定没有股票了
3.23 最佳买卖股票时机含冷冻期
原理同上
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
#还是两个数组的动态规划,一个保存第i天有股票的情况dp1,一个保存第i天没有股票的情况dp2
if not prices:
return 0
leng=len(prices)
dp1=[0]*leng
dp2=[0]*leng
dp1[0]=-prices[0] #边界:第一天就有股票;第一天没有股票所以dp2[0]=0
for i in range(1,leng):
dp2[i]=max(dp2[i-1],dp1[i-1]+prices[i])#前一天有今天卖了的收益
temp=0
if i>=2:
temp=dp2[i-2] #前两天没有股票,前一天冷冻,的收益
dp1[i]=max(dp1[i-1],temp-prices[i]) #前两天没有股票今天有,收益为前一天的收益-今天的股票价格price[i]
return dp2[-1]#最后一天一定没有股票了
3.24 最低票价
思路:dp记录全年每一天的消费365天,第i天的消费,如果第i天是旅行日:则是三种情况中的最小的一种min(dp[i-1]+cost[0] , dp[i-7]+cost[1] , dp[i-30]+cost[2]),如果不是旅行日,直接等于前一天的消费
def mincostTickets(self, days, costs):
"""
:type days: List[int]
:type costs: List[int]
:rtype: int
"""
dp=[0]*366
k=0
for i in range(1,366):
if i==days[k]:
dp[i]=dp[i-1]+costs[0] #购买一天的通行证
dp[i]=min(dp[i],dp[max(0,i-7)]+costs[1]) #七天的通行证
dp[i]=min(dp[i],dp[max(0,i-30)]+costs[2]) #三十天的通行证
k+=1
if k==len(days):
return dp[i]
else:
dp[i]=dp[i-1] #不旅行的情况,不消费
return dp[-1]
3.25 最长数对链
思路:按照第二个元素值进行排序(其顺序就是逐个判断当前数对能否插入的顺序)
def findLongestChain(self, pairs):
"""
:type pairs: List[List[int]]
:rtype: int
"""
"""
#方法一:动态规划,dp[i]表示以第i个结尾的数对的最大长度
leng=len(pairs)
dp=[1]*leng
pairs.sort(key=lambda x:x[1]) #按第二个元素值进行排序
res=0
for i in range(1,leng):
for j in range(i):
if pairs[i][0]>pairs[j][1]:#表示i可以插入到j后面
dp[i]=max(dp[i],dp[j]+1)
res=max(res,dp[i])
return res
"""
#方法二:循环比较
leng=len(pairs)
pairs.sort(key=lambda x:x[1])#按第二个元素值进行排序
last=-1
res=0
for i in range(leng):
if last==-1 or last[1]<pairs[i][0]: #如果pairs[i-1][1]<pairs[i][0],则pairs[i]可以插入
res+=1
last=pairs[i]
return res
3.26 删除与获得点数
思路:相邻的不能取——打家劫舍问题
1,1000依次排列,记录每个值出现的次数,取的话+times*i,不能取相邻的。
状态转移方程:dp[i]=max( dp[ i-1], dp[ i-2]+ times[i]*i )
def deleteAndEarn(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
#转换为打家劫舍问题,1,1000依次排列,记录每个值出现的次数,取的话+times*i,不能取相邻的
leng=len(nums)
times=[0]*1001
dp=[0]*1001
for i in range(leng):
times[nums[i]]+=1
dp[1]=times[1]
for j in range(2,1001):
dp[j]=max(dp[j-2]+j*times[j],dp[j-1]) #前一个或前两个加当前值*times
return dp[-1]
3.27 判断子序列
s是否是t的子序列,s在t中的相对位置不能变。
思路一:转换为最长公共子序列问题(时间超限)
思路二:遍历比较(#要保证相对位置不变,index要保证从上一个相等的位置开始。所以用while而不能用for循环)
def isSubsequence(self, s, t):
"""
:type s: str
:type t: str
:rtype: bool
"""
"""
#思路,最长公共子序列问题,如果最长公共子序列长度恰好等于短序列长度,则短序列一定是长的子序列
#时间超限
leng1=len(s)
leng2=len(t)
dp=[[0 for i in range(leng1+1) ] for j in range(leng2+1)]
for i in range(1,leng2+1):
for j in range(1,leng1+1):
if s[j-1]==t[i-1]:
dp[i][j]=dp[i-1][j-1]+1
else:
dp[i][j]=max(dp[i-1][j],dp[i][j-1])
return True if dp[-1][-1]==leng1 else False
"""
leng1=len(s)
leng2=len(t)
count=0
j=0
for i in range(leng1):
while j<leng2: #要保证相对位置不变,index要保证从上一个相等的位置开始。所以用while而不能用for循环
if s[i]==t[j]:
count+=1
j+=1
break
j+=1
return True if count==leng1 else False
背包问题
关键:
初始化条件! 状态转移方程!
完全背包与01背包: 详解
3.28 世界杯门票(牛客5月模拟)
类似于大礼包问题,递归求解只过了30%,时间超限。网上看了讨论区的一个帖子用动态规划做的,非常巧妙,试验了一下,时间复杂度大大降低。
dp[i] 记录买i张票所用的最低价格,返回dp[N]. while m>=0: 考虑尝试所有的套餐
状态转移方程:
if i>=y: 如果球票数y不足所需球票i,那么考虑购买i-y张的最低价格+该套餐价格x
dp[i]=min(dp[i],dp[i-y]+x)else: 如果球票数y大于所需球票i,那么考虑直接购买该套餐x
dp[i]=min(dp[i],x)
"""
shcemes:m种套餐,[价格,包含的球票数]
k:单张票价
N:人数
"""
#方法一:递归
def price(schemes,k,N):
res=k*N
m=len(schemes)
for i in range(m):
if schemes[i][1]<=N:
new_N=N-schemes[i][1]
temp=price(schemes,k,new_N)+schemes[i][0]
res=min(res,temp)
return res
#方法二:动态规划
def dp(schemes,k,N):
m=len(schemes)-1
dp=[0]*(N+1) #买i张票的最低价格
for i in range(N+1):
dp[i]=i*k #不购买套餐的单价票
while m>=0:
for i in range(1,N+1):
x=schemes[m][0]#套餐m价格
y=schemes[m][1]#套餐m中票的数量
if i>=y:
dp[i]=min(dp[i],dp[i-y]+x)
else:
dp[i]=min(dp[i],x)
m-=1
return dp[N]
3.29 数星星
笨方法:每次遍历所有点,判断x坐标和y坐标是否在范围内,复杂度n^m
动态规划:
给定了范围n<100000,先统计出以每个点的作为右下角的矩形内有多少点(坐标系原点位于左上角),然后求给定矩形内的点=右下角-左下角-右上角+左上角(重复减的)
复杂度:先遍历一次100000*100000, 然后m个矩形每次计算O(1)。
学习了
#数星星
#方法一:遍历,判断
def slow_method(place,ask):
m=len(ask) #m个矩形[a1,b1,a2,b2]
n=len(place) #已有的点的坐标[x,y]
res=[]
for j in range(m):
count=0
for i in range(n):
if abs(place[i][0]-ask[j][0])+abs(place[i][0]-ask[j][2])==abs(ask[j][0]-ask[j][2]):
if abs(place[i][1]-ask[j][1])+abs(place[i][1]-ask[j][3])==abs(ask[j][1]-ask[j][3]):
count+=1
res.append(count)
return res
#复杂度 n^m
#方法二:动态规划
def dp(place,ask):
length=100000
mp=[[0 for i in range(length)] for j in range(length) ] #记录出现的点
num=[[0 for i in range(length)] for j in range(length) ] #记录每个点左上方的矩形内的点的个数
m=len(ask) #m个矩形[a1,b1,a2,b2]
n=len(place) #已有的点的坐标[x,y]
for plac in place:
mp[plac[0]][plac[1]]+=1
#统计每个点左上方矩形内的点
for i in range(1,length):
for j in range(1,length):
num[i][j]=num[i-1][j]+num[i][j-1]-num[i-1][j-1]+mp[i][j] #记得加上本点mp
#计算每个矩形内的点的个数
res=[]
for as in ask:
a1=as[0]
b1=as[1]
a2=as[2]
b2=as[3]
res.append( num[a2][b2]-num[a1-1][b2]-num[a2][b1-1]+num[a1-1][b1-1]) #注意坐标
return res
3.30 零钱兑换
背包问题:
完全背包(每个背包可取的次数不限)01背包(只可以取一次)
res[i]表示金额为i时所需的硬币个数,返回 res[amount]
初始化:res=[float('inf')]* (amount+1) ,但是res[0]需要初始化为0(coins=i,个数为0+1)
状态转移方程: res=min(res[i],res[i-coin]+1) , if :j-coin存在。不存在的话就是初始值无穷大代表不能凑够。
def coinChange(self, coins, amount):
"""
:type coins: List[int]
:type amount: int
:rtype: int
"""
#硬币无限次取,完全背包问题
res = [float('inf') for _ in range(amount + 1)] #存储的是,金额为i时的最少硬币数
res[0]=0 #第一个元素res[0]需要为0
for i in range(1, amount + 1):
for c in coins:
if i - c >= 0: #第一个元素res[0]需要为0,i=c,恰好为0+1=1个
res[i]=min(res[i],res[i - c]+1)
if res[amount] == float('inf'):
return -1
else:
return res[amount]
3.31 计算各个位数不同的数字个数
思路一:排列组合问题,n位数,第一位有9种选择,第二位9种,第三位8...
思路二:动态规划,状态转移方程:dp[i] =dp[i-1]+( dp[i-1]-dp[i-2])*(10-(i-1)) (第二项:i-1位的数量,每个数后面可以再加11-i种,因为已经占用了i-1个,还有10-(i-1)=11-i 个)
def countNumbersWithUniqueDigits(self, n):
"""
:type n: int
:rtype: int
"""
#思路二:动态规划
if n==1:return 10
if n==0:return 1
dp=[0]*(n+1)
dp[0]=1
dp[1]=10
for i in range(2,n+1):
dp[i]=dp[i-1]+(dp[i-1]-dp[i-2])*(11-i)
return dp[-1]
"""
#思路一:排列组合问题 第一位9种(除0),第二位9种,第三位8种,第n位11-n种
dp=[0]*(n+1)
if n==0:return 1
if n==1:return 10
dp[0]=1
dp[1]=9 #第一位有9种选择
sumnum=10
for i in range(2,n+1):
dp[i]=dp[i-1]*(11-i) #i位数的数量
sumnum+=dp[i]
return sumnum
"""
3.32 最长回文子序列
dp[i][j] 记录s[i...j]范围内的最长回文序列长度。
def longestPalindromeSubseq(self, s):
"""
:type s: str
:rtype: int
"""
leng=len(s)
dp=[[0]*leng for i in range(leng)]
dp[0][0]=1
for j in range(1,leng):
dp[j][j]=1
for i in range(j-1,-1,-1):
if s[i]==s[j]:
dp[i][j]=dp[i+1][j-1]+2
else:
dp[i][j]=max(dp[i+1][j],dp[i][j-1])
return dp[0][-1]
3.33 丑数(判断)
如果能被2整除就一直除,能被3/5整除就一直除3/5,最后结果为1说明是丑数。注意0的判断,否则死循环超时。
def isUgly(self, num):
"""
:type num: int
:rtype: bool
"""
if num<1:return False
#if num==1:return True
while num%2==0:
num=num/2
while num%3==0:
num=num/3
while num%5==0:
num=num/5
return True if num==1 else False
3.34 丑数 II(求第n个丑数)
三指针法,依次移动与2/3/5相乘的指针index。下一个丑数是三个乘积的最小值。如果命中,则移动对应指针到下一个。
def nthUglyNumber(self, n):
"""
:type n: int
:rtype: int
"""
#三指针。所有的丑叔都是由丑叔与2/3/5的乘积得到,下一个丑数就是乘积最小的
dp=[1]
two,three,five=0,0,0 #记录各自相乘的index位置
for i in range(1,n):
minnum=min(min(2*dp[two],3*dp[three]),5*dp[five])
dp.append(minnum)
if minnum==2*dp[two]:
two+=1
if minnum==3*dp[three]:
three+=1
if minnum==5*dp[five]:
five+=1
return dp[-1]
3.35 预测赢家
偶数长度时,先手可以决定留给后手的位置,奇数位还是偶数位index,所以先手一定赢。
奇数个时,没有规律,动态规划。dp[i][j]表示在nums[i:j]先手比后手多赢的最大分数。
状态转移方程: dp[i][j]=max(nums[i]-dp[i+1][j],nums[j]-dp[i][j-1]) #如果玩家1拿nums[i],玩家2就会拿剩下的最大值,反之玩家拿nums[j]同理。i=j时,只拿nums[i],后手不拿。
由状态方程可以看到,每个位置与下方和左方有关,所以应该由右下到左上求解。决定了遍历方向为逆向。
状态方程决定遍历方向
def PredictTheWinner(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
leng=len(nums)
if leng==1:return True
if leng%2==0: #如果是偶数个,玩家1可以控制让玩家2拿的位置
return True
#偶数个,要具体分析 dp[i][j]表示nums[i:j]范围内的先手赢的最大差值
dp=[[0 for _ in range(leng)] for i in range(leng)]
for i in range(leng-1,-1,-1):
for j in range(i,leng):
if i==j:
dp[i][i]=nums[i] #nums[i]只一个元素
continue
#上方和左方有关,所以从右下到左上遍历赋值
dp[i][j]=max(nums[i]-dp[i+1][j],nums[j]-dp[i][j-1])
#如果玩家1拿nums[i],玩家2就会拿剩下的最大值,反之拿nums[j]同理
return dp[0][-1]>=0
3.36 最长重复子数组
与最长公共子序列有区别,这个要求连续的子数组。最长公共子序列不要求连续。dp表示A以i结尾B以j结尾的最大长度。
状态转移方程:dp[i][j]=dp[i-1][j-1]+1 ,if A[i-1]==B[j-1].否则dp[i][j]=0
最后返回的是数组中的最大值。。
def findLength(self, A, B):
"""
:type A: List[int]
:type B: List[int]
:rtype: int
"""
#与最长公共子序列有区别,这个要求连续
lA=len(A)
lB=len(B)
dp=[[0]*(lB+1) for _ in range(lA+1)]
res=0
for i in range(1,lA+1):
for j in range(1,lB+1):
if A[i-1]==B[j-1]:
dp[i][j]=dp[i-1][j-1]+1
res=max(res,dp[i][j])
return res
3.37 视频拼接
def videoStitching( clips, T):
"""
:type clips: List[List[int]]
:type T: int
:rtype: int
"""
#一维数组
#dp[i] 记录 时间T为i时的最小clips数,返回dp[T]
dp=[T+1]*(T+1)
dp[0]=0 #初始化中,第一个元素单独初始化。(0,2)可以组成T=2
for i in range(1,T+1):
for clip in clips:
if clip[0]<=i and clip[1]>=i: #该片段可以用到
dp[i]=min(dp[i],dp[clip[0]]+1) #已有的小时间clip[0]的个数+1该片段
print(dp)
3.38 只有两个键的键盘(每次要么复制要么粘贴出n个A的最小次数)
列出前几个就会发现,与其因子有关系。并非奇数都为i次,如9=3*3,直到dp[3]+3即可
状态转移方程:dp[i]=min(dp[i],dp[j]+i/j)
def minSteps(self, n):
"""
:type n: int
:rtype: int
"""
#思路:与其因子有关。状态转移方程:dp[i]=min(dp[i],dp[j]+i/j)
dp=[0]*(n+1)
for i in range(2,n+1):
dp[i]=i
for j in range(1,i): #可以缩小范围:int(i**0.5)
#为什么不是(1,int(i**0.5)+1)?因为取后半部分,i/j会更小,例如18,j=9才是最小方案,j=2或3都不够小
if i %j==0:
dp[i]=min(dp[i],dp[j]+i/j)
return dp[n]
!3.39 最长的斐波那契子序列的长度(动态规划+双指针)
子序列-可以不连续
三个数,很难想到用哪两个数去构造动态规划。用后两个数,每次用双指针遍历0到i-1范围内,是否存在Al+Ar=Ai。
动态规划+双指针
def lenLongestFibSubseq(self, A):
"""
:type A: List[int]
:rtype: int
"""
n=len(A)
#dp[i][j]表示以A[i]和[j]结尾的最长斐波那契子序列长度,用后两个元素构造动态规划
#每次查看0到i-1范围,双指针遍历所有情况。
dp=[[2]*n for _ in range(n)] #初始元素设为2,一旦满足+1=3
res=0 #记录最大长度
for i in range(1,n):
l=0
r=i-1
while l<r: #双指针遍历0到i-1范围内的所有可能
sumnum=A[l]+A[r]
if sumnum==A[i]:
dp[r][i]=max(dp[r][i],dp[l][r]+1) #每次取最大
res=max(dp[r][i],res)
r-=1
l+=1
elif sumnum<A[i]:# 太小,l右移
l+=1
else: #太大,r左移
r-=1
return res
3.40 最长上升子序列
dp[i]表示以 nums[i] 结尾的子序列的最大长度。
动态转移方程:dp[i]=max(dp[i],dp[j]+1) if dp[i] >dp[j] ,j:遍历所有小于i的值的。如果当前元素 i大于前面的任意元素 j,则最大长度为前面的长度+1.
def lengthOfLIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
leng=len(nums)
if leng==1:
return 1
dp=[1]*leng
res=0
for i in range(leng):
for j in range(i):
if nums[i]>nums[j]:
dp[i]=max(dp[i],dp[j]+1)
res=max(res,dp[i])
return res
3.41 单词拆分
思路一二:动态规划+指针
思路三:完全背包问题。字典内的物品使用次数不限,放在里层循环(01背包,物品外层循环)
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: bool
"""
#完全背包问题
leng=len(s)
maxleng=0
for dic in wordDict:
maxleng=max(maxleng,len(dic))
dp=[False]*(leng+1)#dp[i]表示第i个字符结尾的字符串能否被拆分
dp[0]=True
for i in range(1,leng+1):# leng+1不是leng
for word in wordDict:
#字典里的字符相当于物品
lengs=len(word)
#如果可拆分
if i-lengs>=0 and s[i-lengs:i]==word:
dp[i]=(dp[i] or dp[i-lengs])
return dp[-1]
"""
#思路二:动态规划+指针
leng=len(s)
maxleng=0
for dic in wordDict:
maxleng=max(maxleng,len(dic))
dp=[0]*leng
#以i结尾的字符串是否可以被拆分,1代表可以
for i in range(leng):
#一个指针从当前index向前遍历,考虑0的情况。
p=i
while p>=0 and i-p<=maxleng:
if (dp[p]==1 and s[p+1:i+1] in wordDict) or (p==0 and s[p:i+1] in wordDict):
#如果p!=0,每次判断要从上一个可拆分的下一个字符开始判断是否在字典内 s[p+1:i+1]
dp[i]=1
break
p-=1
return dp[-1]==1
"""
"""
#思路一
dp[i]表示以i-1结尾的字符串可以被拆分
leng=len(s)
maxleng=0
for dic in wordDict:
maxleng=max(maxleng,len(dic))
dp=[0]
for i in range(1,leng+1):
for j in dp:
#从之前标注的可以被拆分的字符开始搜索
if i-j<=maxleng and s[j:i] in wordDict:#长度小于字典中字符串的最大长度并且存在于字典中,表明可以被拆分
dp.append(i)#添加可以被拆分的字符的index
break
return dp[-1]==leng#如果可以被拆分的最长字符是最后一个字符,则为true
"""
3.42 目标和(通过加减号实现目标和的方案数)
转换为 和为target的子集个数
01背包问题(从nums里面取,最终和恰好为target的取法总数;礼物的总重量恰好为weight的最大价值)
sum(p)-sum(N)=S -> liang两边同时加sum(p)+sum(N),sum(p)=(target+sum)/2 ->总和为sum(p)的子集个数
01背包:礼物在外面循环,里面从大到小循环。
dp[i]表示target为0,1...target的方案数,返回dp[-1]
#外面循环,依次表示:考虑nums[0]刚好取所有target的方案数,考虑nums[1]...到考虑nums[-1]的target的方案数
#经典01背包
import sys
v,n=map(int,sys.stdin.readline().strip().split())
a=[]
b=[]
for i in range(n):
vo,va=map(int,sys.stdin.readline().strip().split())
a.append(vo)
b.append(va)
dp=[0]*(v+1)
for i in range(n):
bound=max(v-sum(a[i:]),a[i])
for j in range(v,bound-1,-1):
dp[j]=max(dp[j],dp[j-a[i]]+b[i])
print dp[-1]
def findTargetSumWays(self, nums, S):
"""
:type nums: List[int]
:type S: int
:rtype: int
"""
sumnum=sum(nums)
#如果不存在这样的子集或者S大于总和,返回0
if (sumnum+S)%2!=0 or S>sumnum: return 0
target=(sumnum+S)/2
dp=[0]*(target+1)#表示目标值为i的方案个数,初始化为0,因为全都没有考虑num
dp[0]=1 #该元素等于target,方案为1个(或者target=0,全都不取的方案,1个)
#01背包问题,礼物放在外面循环,里面从大到小循环
#外面循环,依次表示:考虑nums[0]刚好取所有target的方案数,考虑nums[1]...到考虑nums[-1]的target的方案数
for num in nums:
for j in range(target,num-1,-1):
dp[j]+=dp[j-num]
return dp[target]
3.43 分割等和子集
思路同上一题:查找和为half的子集有多少个。
01背包,dp[i]表示和为i的子集的个数,外面物品循环,里面由half到nums[i] 逆向循环。
def canPartition(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
"""
half=sum(nums)/2
if sum(nums)%2!=0:
return False
leng=len(nums)
dp=[0]*(half+1)#表示子集和为i时的划分方法数
dp[0]=1 #存在恰好等于half的数,一种
#01背包,物品循环在外面,里面由大到小循环
for i in range(leng):
for j in range(half,nums[i]-1,-1):
dp[j]+=dp[j-nums[i]] #j等于j-nums[i]的子集个数
return dp[half]!=0
"""
half=sum(nums)/2
if sum(nums)%2!=0:
return False
leng=len(nums)
dp=[False]*(half+1)#表示子集和为i时是否可以划分
dp[0]=True #存在恰好等于half的数,可以划分
#01背包,物品循环在外面,里面由大到小循环
for i in range(leng):
for j in range(half,nums[i]-1,-1):
dp[j]=(dp[j] or dp[j-nums[i]]) #如果存在j-nums[i]可以划分,那么j肯定也可以划分
return dp[half]
3.44 组合总和 Ⅳ
背包问题,因为可以重复使用,所以使完全背包:物品循环在里面,外面是价值循环
因为可以到达当前分数的所有情况,等于所有,还差一步就能到达当前分数的情况总和
def combinationSum4(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
leng=len(nums)
#完全背包问题,可以重复使用,物品里面循环
dp=[0]*(target+1)
dp[0]=1
for i in range(1,target+1):
for num in nums:
if i>=num:
dp[i]+=dp[i-num]
return dp[target]
3.45 拼车(字典动态存储)
还是没有理解透彻题目。用一个字典记录每个起点的人数,终点如何记录?终点减去人数就能减掉之前已经下车的人数
def carPooling(self, trips, capacity):
"""
:type trips: List[List[int]]
:type capacity: int
:rtype: bool
"""
#统计每个起点的人数,终点也作为起点(减去人数)
"""
d={}
for n,s,e in trips:
d[s]=0
d[e]=0
"""
d=collections.defaultdict(int)
for n,s,e in trips:
d[s]+=n
d[e]-=n
temp=0
for i in sorted(d):
temp+=d[i]
if temp >capacity:
return False
return True
3.46 填充书架
动态规划,每次向前搜索看能否合并,合并后更新当前层高度,然后状态转移方程 f[i]=min(f[i],f[j-1]+cur_h)
不必拘泥于哪个方向。动态规划不一定全部初始化,每次临时初始化 append
class Solution(object):
def minHeightShelves(self, books, width):
"""
:type books: List[List[int]]
:type shelf_width: int
:rtype: int
"""
f=[books[0][1]]
for i in range(1,len(books)):
f.append(f[i-1]+books[i][1])
cur_w=books[i][0]
cur_h=books[i][1]
#每次从当前开始向前查找,看能否合并
for j in range(i-1,-1,-1):
cur_w+=books[j][0]
if cur_w>width:
break
cur_h=max(books[j][1],cur_h) #若可以合并,则高度就是最大高度
if j==0:
f[i]=min(f[i],cur_h)
else:
f[i]=min(f[i],f[j-1]+cur_h) #合并后,高度是合并截止的前一个元素加上合并的高度 f[j-1]+cur_h
return f[-1]
3.47 摆动序列
def wiggleMaxLength(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
#2 动态规划
if len(nums)==1:
return 1
if not nums:
return 0
nums1=[nums[0]]
for i in range(1,len(nums)): #去掉重复元素
if nums[i]!=nums[i-1]:
nums1+=[nums[i]]
if len(nums1)==1: #避免dp[1]index out of range
return 1
dp=[1]*len(nums1)
dp[1]=2
for i in range(2,len(nums1)):
if (nums1[i]-nums1[i-1])*(nums1[i-1]-nums1[i-2])<0:
dp[i]=dp[i-1]+1
else:
dp[i]=dp[i-1]
return dp[i]
"""
#1 假设前i个元素的最长序列的状态,判断num[i+1]和nums[i]的大小,动态规划
up=1 #序列的最后两个元素递增
down=1 #序列的最后两个元素递减
if len(nums)==1:
return 1
if not nums:
return 0
for i in range(1,len(nums)):
if nums[i]>nums[i-1]:
up=down+1
elif nums[i]<nums[i-1]:
down=up+1
else:
continue
return max(up,down)
"""
4 栈和队列
4.1 滑动窗口最大值
双端队列
保持队列(头->尾)为递减的, 不满足的则pop, 则最大值永远为队头的元素,即可加入结果的vector
1、首先将滑动窗口范围内的值一个一个插入队列尾部, 放入规则: (1)保持对头的为最大值 (2)若将要插入的值大于队列中前一个值, 则这前一个值一定不是当前滑动窗口的最大值,将其弹出, 直到这个插入的值大于队列中前一个值为止
2、此时第一个滑动窗口的最大值一定为队头,取出将其放入结果数组中
3、继续将nums的下一个值push,若发现队头元素不在滑动窗口的范围中了,则将队头元素弹出 (*存入队列的值为数的index,即可方便的判断队头元素(最先遍历的元素)是否在滑动窗口的范围中) */
def maxSlidingWindow(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
#最后返回的数组长度为 len(nums)-k+1
leng=len(nums)
two_que=[] #双端链表或队列或栈,保存的是最大值的index
res=[] #保存每个窗口内的最大值,共leng-k+1个元素
left=0
#双端链表,由大到小排列,保存index,更新过期index
for i in range(leng):
#保持栈元素从大到小排列,遇到比当前值小的,直接出栈(因为窗口内不再需要比当前还小值的了)
while two_que !=[] and nums[two_que[-1]]<=nums[i]:
two_que.pop()
#将该元素插入到从大到小的对应位置
two_que.append(i)
if i-k==two_que[0]: #更新过期最大值。i-k刚好是当前窗口的前一个过期的元素left
two_que.pop(0)
if i>=k-1: #从第一个完整的窗口开始,每次保存栈底最大值
res.append(nums[two_que[0]])
return res
4.2 最大值减去最小值小于或等于num的子数组数量
描述:给定数组arr和整数num,共返回有多少个子数组满足如下情况:子数组中的最大值减去最小值小于或等于num。要求,时间复杂度O(N)。
思路:双端队列,滑动窗口
两个要点:滑动窗口思想,用一个窗口表示一个连续的子数组,那么该窗口内:
- 一个连续子数组,如果满足条件:Max-Min<=num,那么窗口内的子数组也一定满足(子数组中Max一定更小,min一定更大,差一定更小)。
- 如果该连续子数组不满足,即Max-Min<=num,那么窗口right继续右移的话(left不变),Max只会更大,Min只会更小,更不可能满足条件。此时就是满足条件下的以left起始的最长连续子数组,共right-left个子数组(right此时已经不满足)
思路:用一个滑动窗口代表连续的子数组,left遍历,每次遍历时,right不断右扩,直到不满足条件。由以上两点可知,此时就是以left为起始点的满足条件的最长的子数组,长度为right-left。
每次判断满足条件与否的方法如题4.1:用两个双端队列分别保存当前窗口的最大值和最小值,记得更新left是否过期,做差。然后left右移,进行下一次判断。
def find_num_of_subarray(arr,num):
leng=len(arr)
left=0
right=0
if leng==0 or arr is None:
return 0
qmax=[]#存储窗口内的最大值
qmin=[]#存储窗口内的最小值
res=0
while left<leng:#窗口左侧依次右移
while right<leng:
#存储最大值
while qmax and arr[qmax[-1]]<=arr[right]:
qmax.pop()
qmax.append(right)
#存储最小值
while qmin and arr[qmin[-1]]>=arr[right]:
qmin.pop()
qmin.append(right)
#直到不满足条件,right不能再扩张
if arr[qmax[0]]-arr[qmin[0]] > num:
break
right+=1
#更新两个双端队列的过期最大值
if left==qmax[0]:
qmax.pop(0)
if left==qmin[0]
qmin.pop(0)
#此时以left开始的满足条件的连续子数组个数: right-left
res+=right-left
left+=1
return res
4.3 不同字符的最小子序列
见题解1,非常详细,去重+保持原序列——>用栈存储,每次根据条件依次出栈或压入栈
当栈非空、栈顶元素的字典序大于当前元素、且栈顶元素在后面还会重复出现,就弹出栈顶元素。如果当前元素已经存在于栈中,跳过。
def smallestSubsequence(self, text):
"""
:type text: str
:rtype: str
"""
leng=len(text)
stack=[]
for i in range(leng):
if text[i] in stack:
continue
#当栈非空,栈顶元素的字典序大于当前元素,且栈顶元素在后面还会重复出现,就弹出栈顶元素
while stack and ord(text[i])<ord(stack[-1]) and text.find(stack[-1],i)!=-1:
stack.pop()
stack.append(text[i])
return "".join(stack)
4.4 解析布尔表达式
有操作符和bool值,每个操作符有作用范围在括号内,用两个栈存储操作符和bool值,为了找到操作范围左括号也要入栈。
遇到右括号,则开始出栈直到左括号,操作符就在该范围内作用,作用完成后要将该结果加入bool栈,继续操作。
class Solution(object):
def parseBoolExpr(self, expression):
"""
:type expression: str
:rtype: bool
"""
symbol=[] #记录操作符 & | !
stack=[]#记录bool值和左括号
s=["&","!","|"]
for c in expression:
if c=="(":
stack.append(c)
elif c=="t":
stack.append(True)
elif c=="f":
stack.append(False)
elif c==",":
continue
elif c in s:
symbol.append(c)
else: #碰到右括号,则开始出栈执行操作,直到碰到左括号
op=symbol.pop()
temp=stack[-1]
while stack and stack[-1]!="(":
if op=="&":
temp=temp & stack.pop()
elif op=="|":
temp=temp | stack.pop()
elif op=="!":
temp=not stack.pop()
stack.pop() #将左括号删除
stack.append(temp)
return stack[0]
4.5 用数组建立一个固定大小的栈
append 实现push(); pop 实现pop()
class arraystack:
def __init__(self,size): # 开始怎么调都不成功,init 竟然写成了int
self.stackData=[]
self.len=size
def push(self,obj):
if len(self.stackData)==self.len:
raise Exception("The stack is full")
else:
self.stackData.append(obj)
def pop(self):
if len(self.stackData)==0:
raise Exception("The stack is empty")
else:
value=self.stackData.pop()
return value
def peek(self):
if len(self.stackData)==0:
raise Exception("The stack is empty")
else:
value=self.stackData[-1]
return value
4.6 用数组建立一个固定大小的队列
设置start 和end 指针控制队列的头尾,如果end和start 到size-1了(length=size),则跳回0位置
class arrayqueue:
def __init__(self,size):
self.queue=[]
self.lenn=size
self.end=0
self.start=0
length=0 #在def之外,是类变量
def push(self,obj):
if self.length==self.lenn:
raise Exception("The queue is full")
else:
self.length+=1
self.queue.append(obj)
self.end=self.nextIndex(self.lenn,self.end)
def poll(self):
if self.length==0:
raise Exception("The queue is empty")
else:
self.length-=1
return self.queue.pop(self.start)
self.start=self.nextIndex(self.lenn,self.start)
def nextIndex(self,size,size2):
return 0 if size2 == size-1 else size2+1
4.7 设计一个有getmin功能的栈
时间复杂度为O(1)
1 建立两个栈,连续压入最小值(若元素比当前min大,则min栈压入min栈的栈顶元素),弹出时一起弹出
class getmin1:
def __init__(self):
self.data=[]
self.minstack=[]
def push(self,obj):
self.data.append(obj)
if (len(self.minstack)==0)or obj<self.getmin():
self.minstack.append(obj)
else:
self.minstack.append(self.getmin())
def pop(self):
if len(self.minstack)==0:
raise Exception("The stack is empty")
self.minstack.pop()
return self.data.pop()
def getmin(self):
if len(self.minstack)==0:
raise Exception("The stack is empty")
else:
return self.minstack[-1]
2 只压入比当前栈顶更小或相等的值,返回的时候,如果min和data相等,则min栈也弹出,否则不弹
class getmin2:
def __init__(self):
self.data=[]
self.minstack=[]
def push(self,obj):
self.data.append(obj)
if (len(self.minstack)==0)or obj<=self.getmin():
self.minstack.append(obj)
def pop(self):
if len(self.minstack)==0:
raise Exception("The stack is empty")
value2=self.getmin()
value1=self.data.pop()
if value1==value2:
self.minstack.pop()
return value1
def getmin(self):
if len(self.minstack)==0:
raise Exception("The stack is empty")
else:
return self.minstack[-1]
4.8 用队列实现栈功能
用两个队列,pop时候,每次依次倒入另一个队列直到剩一个元素,则返回剩下的那个,然后交换这两个队列
class queue_stack:
def __init__(self):
self.queue=[]
self.helpp=[]
def push(self,obj):
self.queue.append(obj)
def pop(self):
while len(self.queue)!=1:
self.helpp.append(self.queue.pop(0))# pop(0)-取首元素
re=self.queue.pop()
self.queue,self.helpp=self.helpp,self.queue
return re
def peek(self):
while len(self.queue)!=1:
self.helpp.append(self.queue.pop(0))
re=self.queue.pop()
self.helpp.append(re)
self.queue,self.helpp=self.helpp,self.queue
return re
python转换的时候,a,b=b,a 则实现java/c的swap功能;pop(0)元素则删除并返回队首元素
4.9 用栈实现队列功能
用两个栈,stack负责push,helpp栈负责pop:每次从stack全部倒入help栈(help 栈为空时才能倒入,并且一次性全部倒入,不空时直接从辅助栈pop),然后从help栈里pop
class stack_queue:
def __init__(self):
self.stack=[]
self.helpp=[]
def push(self,obj):
self.stack.append(obj)
def pop(self):
if len(self.helpp)==0:
while len(self.stack)!=0:
self.helpp.append(self.stack.pop())
re=self.helpp.pop()
return re
def peek(self):
if len(self.helpp)==0:
while len(self.stack)!=0:
self.helpp.append(self.stack.pop())
re=self.helpp[-1]
return
4.10 用一个栈实现另一个栈的排序
不断将原栈栈顶元素弹出并向辅助栈压入:
若辅助栈为空或者辅助栈栈顶元素大于当前元素,则直接压入辅助栈;
若小于当前元素,则将辅助栈元素依次弹出并压入原栈,直到栈顶元素大于当前元素,然后将当前元素压入辅助栈。
得到的辅助栈排序从栈顶到栈底是升序排列的(栈顶是插入删除操作的一端),然后依次弹出压入原栈实现降序排列。
def sortstack(stack):
stack1=[]
while stack:
temp=stack.pop()
while stack1 and stack1[-1]<temp:
stack.append(stack1.pop())
stack1.append(temp)
while stack1:
stack.append(stack1.pop())
print(stack)
stack=[2,1,5,3,4]
sortstack(stack)
#[1, 2, 3, 4, 5]
5 回溯(DFS & BFS)
5.1 字母大小写全排列
回溯算法:
DFS:每次有多个选择,递归调用把可能都写出来,最后逐级返回找到所有方案。一个全局变量存储方案,一个临时变量存储单个方案,只有到结尾时才将临时变量加入全局
BFS:按照距离,把当前距离下的可能 加入所有现存的方案中。
def letterCasePermutation(self, S):
"""
:type S: str
:rtype: List[str]
"""
leng=len(S)
if leng==0:
return [""]
"""
#DFS 深度优先
res=[]
def dfs(index,temp):
if index>=leng:
res.append(temp)
return
if S[index].isdigit():#数字,直接加入
dfs(index+1,temp+S[index])
elif S[index].islower():#小写
dfs(index+1,temp+S[index])
dfs(index+1,temp+S[index].upper())
else:#大写
dfs(index+1,temp+S[index])
dfs(index+1,temp+S[index].lower())
dfs(0,"")
return res
"""
res=[""]
#BFS 宽度优先
#每次准备一个临时数组,遍历res目前的所有项,然后每一项后面加入大小写两种,然后添加到temp里面,用temp覆盖res
import copy
for i,x in enumerate(S):
if x.isdigit():
for index,item in enumerate(res):
res[index]+=(x)
elif x.islower():
temp=[]
for index,item in enumerate(res):
temp.append(item+(x))
temp.append(item+(x.upper()))
res=copy.deepcopy(temp)
else:
temp=[]
for index,item in enumerate(res):
temp.append(item+(x))
temp.append(item+(x.lower()))
res=copy.deepcopy(temp)
return res
5.2 二进制手表
def readBinaryWatch(self, num):
"""
:type num: int
:rtype: List[str]
"""
#暴力搜索
res=[]
for i in range(12):
for j in range(60):
if bin(i).count("1")+bin(j).count("1")==num:
#bin函数,将数字转换为二进制形式,计算1的个数
res.append(str(i)+":"+str(j).rjust(2,"0"))
#rjust(width,str),右对齐,长度小于width,用str填充左边;如果字符串长度大于width,返回原字符串
return res
5.3 子集
回溯:临时变量+全局变量。
temp到达指定长度才加入全局变量res中(如大小写全排列),或者,每次都加入(如此题,子集长度不一,每一个长度都是一个子集)。
temp是临时变量,会变动,所以每次加入全局变量时,要深复制再添加,否则一变都变。深复制两种方法:import copy: copy.deepcopy(temp) 或者 res.append( temp[ : ] ) 即可(temp是可变对象列表,而数值或字符串是不可变对象深浅复制都一样)
重点是回溯时如何改变临时变量。这里每次递归后,临时变量栈删除栈顶此次循环走的路径,然后进入下一次循环尝试其他路径。temp.pop( )一定要加在循环里面,这样表示的是当前重新选择,删除此次循环走的路径即还没尝试走,然后进入下一次循环。
def subsets(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
#深度优先,先到达最深,然后回溯,临时变量删除最后存储的值
leng=len(nums)
res=[]
temp=[]
def dfs(nums,index,temp,res):
res.append(temp[:])#注意,temp[:]是深复制,否则一变之前的temp都变
if index>=leng: #回溯条件
return
for i in range(index,leng): #遍历,回溯时才会遍历其他未加入的路径
"""
temp.append(nums[i])
dfs(nums,i+1,temp,res)
temp.pop() #回溯,临时变量栈删除栈顶此次循环走的路径,然后进入下一次循环尝试其他路径
"""
dfs(nums,i+1,temp+[nums[i]],res)
dfs(nums,0,temp,res)
return res
5.4 子集 II
相比于上题,有重复元素:增加判断,重复则直接跳过。为了方便重复判断nums[i]==nums[i-1],先对nums排序,使重复的元素相邻。
def subsetsWithDup(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
leng=len(nums)
res=[]
def dfs(nums,temp,index):
res.append(temp[:])
if index==leng:
return
for i in range(index,leng):
if i>index and nums[i]==nums[i-1]: #如果与前一元素重复,直接跳过
continue
dfs(nums,temp+[nums[i]],i+1)
nums.sort() #便于递归内的重复判断
dfs(nums,[],0)
return res
5.5 全排列
每种排列方式的长度都等于原长,所以res添加加temp是遍历到最后才执行
思路一:临时变量,每次遍历加入当前子集的一个元素(for循环),然后子集剔除该元素(nums[:i]+nums[i+1:])进入下次递归,保证不会重复加入同一元素。当temp长度达到要求,res添加。
思路二:全排列方法:固定一个数的位置(left),然后从下一位数再开始全排列(递归过程)...直到最后一位数,模拟手动全排列的过程。
每次遍历交换当前元素与后面元素的顺序,然后对后面子集递归进行该操作,直到子集为空,将交换顺序的nums添加到res,回溯时要恢复交换前的原顺序,所以dfs()下面再次加入交换。用index控制剩余子集的起始点
def permute(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
#方法一:遍历剔除当前集合的一个元素并加入temp中,递归对剩余子集进行该操作,直到temp长度为leng
leng=len(nums)
res=[]
def dfs(nums,temp):
if len(temp)==leng:
res.append(temp[:])
return
for i in range(len(nums)):
dfs(nums[:i]+nums[i+1:],temp+[nums[i]])
dfs(nums,[])
return res
"""
#方法二:排列方法,每次通过与后面元素交换进行全排列,递归对剩下的子集同样操作,直到子集为空
leng=len(nums)
res=[]
def dfs(index,leng):
if index>=leng:
res.append(nums[:]) #list是可变对象,要深复制!!!
cur=index
for i in range(index,leng):
nums[cur],nums[i]=nums[i],nums[cur]
dfs(index+1,leng)
nums[cur],nums[i]=nums[i],nums[cur] #回溯时要保证恢复交换前原状,才能进行后面的交换
dfs(0,leng)
return res
"""
5.6 全排列 II(有重复元素)
先将数组排列,然后按照上题方法一,依次向temp里添加元素并递归,但遇到重复值需要跳过。(11123,temp第一个位置添加第一个1即可,回溯的时候如果添加后面的任意一个1,情况都出现过已经重复了)
def permuteUnique(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
#遍历剔除当前集合的一个元素并加入temp中,递归对剩余子集进行该操作,直到temp长度为leng
#先将nums排序,然后相邻值重复的时候,跳过递归,因为一定重复了
leng=len(nums)
res=[]
def dfs(nums,temp):
if len(temp)==leng:
res.append(temp[:])
return
for i in range(len(nums)):
#先对nums排序,重复的相邻,若遇到与前一个重复的,则结果一样直接跳过
if i>0 and nums[i]==nums[i-1]:
continue
dfs(nums[:i]+nums[i+1:],temp+[nums[i]])
nums.sort()#记得要先排序把重复值放在一起,方便函数内的判断
dfs(nums,[])
return res
#字符串的全排列:同上,不过字符是不可变对象,不必深复制
def Permutation(self, ss):
# write code here
res=[]
leng=len(ss)
if ss=="":
return []
def dfs(temp,ss):
if len(temp)==leng:
res.append(temp)
return
for i in range(len(ss)):
if i>0 and ss[i]==ss[i-1]: #重复的直接跳过
continue
dfs(temp+ss[i],ss[:i]+ss[i+1:])
dfs("",ss)
return res
5.7 括号生成
给定n表示括号对数,求总排列可能
每个位置都有两种选择,但选择)的时候有限制,当前左括号数要大于右括号数
def generateParenthesis(self, n):
"""
:type n: int
:rtype: List[str]
"""
res=[]
leng=2*n
#用index控制总数
def dfs(index,temp):
if index>=leng:
res.append(temp)
return
if temp.count("(")<n: #左括号有上限
dfs(index+1,temp+'(')
if temp.count("(")>temp.count(")"):#右括号一定小于左括号的时候才能加
dfs(index+1,temp+")")
dfs(0,"")
return res
"""
#每个位置都有两种选择,但选择)的时候有限制,当前左括号数要大于右括号数
res=[]
left,right=0,0
def dfs(left,right,temp):
if left==n and right==n:
res.append(temp)
return
if left<n: #左括号有上限
dfs(left+1,right,temp+'(')
if right<left:#右括号一定小于左括号的时候才能加
dfs(left,right+1,temp+")")
dfs(left,right,"")
"""
5.8 组合
组合,不考虑排列的顺序,元素相同顺序不同其实算一种组合(如12与21)。所以递归进行下一步选择时,i元素之前的元素因为已经选择过了,所以去掉,不再重复选择。dfs(nums[i+1:],temp+[nums[i])
而全排列的时候,会考虑顺序,所以还要考虑之前已经选择过的元素,,所以是dfs(nums[:i]+nums[i+1:],temp+[nums[i])。
def combine(self, n, k):
"""
:type n: int
:type k: int
:rtype: List[List[int]]
"""
nums=[i for i in range(1,n+1)]
res=[]
def dfs(nums,temp):
if len(temp)==k:
res.append(temp[:])
return
for i in range(len(nums)):
dfs(nums[i+1:],temp+[nums[i]])
#组合,不考虑排列的顺序,元素相同的顺序不同算一种组合(如12与21)。所以递归进行下一步选择时,i元素之前的元素因为已经选择过了,所以去掉,不再重复选择。
#而全排列的时候,还要考虑之前的元素,会考虑顺序,所以是dfs(nums[:i]+nums[i+1:],temp+[nums[i])。
dfs(nums,[])
return res
5.9 组合总和(每个元素可多次取)
组合不考虑重复,为了剔除重复,下次递归的时候就不再考虑已经选过的元素,但是可以考虑当前元素,所以是nums[i:]。
def combinationSum(self, nums, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
"""
#方法三:按照排列方法寻找,最后终止条件时检查是否重复并添加
res=[]
leng=len(nums)
def dfs(target,temp):
if target==0 and sorted(temp) not in res: #最后一步检查是否存在重复
res.append(sorted(temp))
return
for i in range(leng): #每次都从头遍历,不考虑重复
if target-nums[i]>=0:
dfs(target-nums[i],temp+[nums[i]])
dfs(target,[])
return res
#方法一,改变nums,剔除之前选择的元素
def dfs(target,temp,nums):
if target==0:
res.append(temp[:])
return
for i in range(len(nums)):
if target-nums[i]>=0:
dfs(target-nums[i],temp+[nums[i]],nums[i:]) #改变下次遍历的数组nums来剔除已经遍历过的元素
dfs(target,[],nums)
return res
#方法二:引入index控制不选择之前的元素
def dfs(target,temp,index):
if target==0:
res.append(temp[:])
return
for i in range(index,leng):
if target-nums[i]>=0:
#递归运算,将i传递至下一次运算是为了避免结果重复
dfs(target-nums[i],temp+[nums[i]],i)
dfs(target,[],0)
return res
"""
#方法四:动态规划目标和问题
#
leng=len(nums)
res=[]
#完全背包问题,物品循环在里面
dp=[0]*(target+1) #dp[i]表示目标和为i的总组合数量
dp[0]=1
d={} #d[i]存储目标和为i时的所有组合,最终返回的是d[target]
for i in range(1,target+1): #从1到target遍历
d[i]=[] #初始化
for num in nums: #遍历所有元素
if i-num==0: #如果num刚好等于i,那么直接加上该元素
d[i].append([num])
dp[i]+=1
elif i-num>0 and dp[i-num]!=0: #如果target为i-num的组合数不为0,那么考虑加上num
#加上num刚好等于i,所以就将i-num的所有组合都加上num就是target为i的所有组合
for _ in range(dp[i-num]):
if sorted(d[i-num][_]+[num]) not in d[i]: #每次添加前先检查并去重,保证排好序
d[i].append(sorted(d[i-num][_]+[num]))
dp[i]+=1
return d[target]
5.10 组合总和 II(每个元素只取一次)
每个元素只使用一次,尽管传入下一次的是nums[i+1:]不再考虑i和之前的,但是也会出现重复。
如[10,1,2,7,6,1,5],8 会出现[1,2,5] 和[2,1,5],是因为后面也出现了与前面重复的1,无法排除掉思路:可以先排好序跳过重复元素即可;或者在递归终止时判断重复
def combinationSum2(self, nums, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
#每个元素只使用一次,尽管传入下一次的是nums[i+1:]不再考虑i和之前的,但是也会出现重复。
#如[10,1,2,7,6,1,5],8 会出现[1,2,5] 和[2,1,5],是因为后面也出现了与前面重复的1,1无法排除掉
#方法一:先对nums排好序,然后遇到重复的跳过达到去重目的
leng=len(nums)
res=[]
def dfs(temp,target,nums):
if target==0:
res.append(temp[:])
for i in range(len(nums)):
if i>0 and nums[i]==nums[i-1]: #去重,如果重复,直接跳过
continue
if nums[i]<=target:
dfs(temp+[nums[i]],target-nums[i],nums[i+1:]) #不可重复使用,所以传入下一次递归的是nums[i+1:]
nums.sort() #使重复的相邻,便于判断重复
dfs([],target,nums)
return res
"""
#方法二:
直接求出所有情况,终止条件时判断重复与否
leng=len(nums)
res=[]
def dfs(temp,target,nums):
if target==0 and sorted(temp) not in res:
res.append(sorted(temp[:]))
for i in range(len(nums)):
if nums[i]<=target:
dfs(temp+[nums[i]],target-nums[i],nums[i+1:]) #不可重复使用,所以传入下一次递归的是nums[i+1:]
dfs([],target,nums)
return res
"""
5.11 组合总和 III
递归终止条件:target==0 and temp个数为k(两个参数:target、temp)
参数传递:不能重复nums[i+1:]
def combinationSum3(self, k, n):
"""
:type k: int
:type n: int
:rtype: List[List[int]]
"""
res=[]
def dfs(index,n,temp):
if len(temp)==k and n==0: #递归终止条件
res.append(temp[:])#深复制
return
for i in range(index,10):
if i<=n:
dfs(i+1,n-i,temp+[i]) #给下次传入的是i+1,不考虑i及之前的,为了去重
dfs(1,n,[])
return res
5.12 格雷编码
def grayCode(self, n):
"""
:type n: int
:rtype: List[int]
"""
res=[]
for i in range(2**n):
res.append(i^(i>>1))
return res
5.13 分割回文串
从头开始尝试分割点,如果左侧是回文的,递归对右侧进行判断。终止条件是字符串为空,每次传递的是右侧字符串和temp.
def partition(self, s):
"""
:type s: str
:rtype: List[List[str]]
"""
#从第一个元素开始每个分割点开始判断,如果是回文,对剩下的继续递归判断
res=[]
def dfs(s,temp):
if not s:
res.append(temp[:])
return
for i in range(len(s)):
temp2=s[:i+1]
if temp2==temp2[::-1]:
dfs(s[i+1:],temp+[temp2])
dfs(s,[])
return res
5.14 优美的排列
回溯的特点:每个位置有多种选择(满足条件才可以选),有终止条件(和为多少,长度达到多少),注意函数的向下次递归传入的参数变化(不能使用已经用过的元素,则nums[:i]+nums[i+1:],可以重复使用当前值,nums[i:])
变量第一次出现如果在函数外面,默认为全局变量,函数内部使用时会作为全局变量处理(list是可变对象,可以改变全局变量,如果是不可变对象字符或数字,函数内部引用会出错无法修改:has referenced before......)
class Solution(object):
def __init__(self):
self.count=0
def countArrangement(self, N):
"""
:type N: int
:rtype: int
"""
res=[]
#count=0
def dfs(nums,index,temp):
if index==(N+1):
#res.append(temp[:])
self.count+=1
return
for i in range(len(nums)):
if nums[i]%index==0 or index%nums[i]==0:
dfs(nums[:i]+nums[i+1:],index+1,temp+[nums[i]])
nums=[i for i in range(1,N+1)]
dfs(nums,1,[])
#return len(res)
return self.count
5.15 电话号码的字母组合
组合问题,考虑去重,下次传递的参数不包含之前已经遍历的
def letterCombinations(self, digits):
"""
:type digits: str
:rtype: List[str]
"""
d={
2:"abc",
3:"def",
4:"ghi",
5:"jkl",
6:"mno",
7:"pqrs",
8:"tuv",
9:"wxyz"
}
res=[]
leng=len(digits)
if leng==0:
return res
def dfs(digits,temp):
if len(temp)==leng:
res.append(temp)
return
for i in range(len(digits)):
for j in d[int(digits[i])]:
dfs(digits[i+1:],temp+j) #组合问题,不考虑顺序,所以传给下次递归的参数要去掉之前已经遍历过的
dfs(digits,"")
return res
5.16 第k个排列
将list快速变为str: "".join(str(i) for i in list)
def getPermutation(self, n, k):
"""
:type n: int
:type k: int
:rtype: str
"""
nums=[str(i) for i in range(1,n+1)]
res=""
for i in range(1,n+1):
index=(k-1)/self.fac(n-i)
res+=nums[index]
k-=index*self.fac(n-i)
nums.pop(index) #pop是删除索引对应的元素,remove是删除指定元素
return res
def fac(self,n):
result=1
if n==0:
return 1
for i in range(1,n+1):
result*=i
return result
"""
#递归,会超时,只返回第k个即可,没办法停止递归
res=[]
nums=[ i for i in range(1,n+1)]
def dfs(temp,nums,count):
if len(temp)==n:
res.append(temp[:])
count+=1
return
for i in range(len(nums)):
dfs(temp+[nums[i]],nums[:i]+nums[i+1:],count)
dfs([],nums,0)
return "".join(str(s) for s in res[-1])
"""
5.17复原IP地址
回溯,按位选择分割点。每次分割判断条件:长度小于4且小于256,并且长度大于1的时候第一位不可为0 如010。终止条件:已经选好前三段,第四段不为空且第四段符合上述判断条件。
def restoreIpAddresses(self, s):
"""
:type s: str
:rtype: List[str]
"""
leng=len(s)
res=[]
def dfs(s,temp,count):
if count==3:
#考虑大于1位的情况第一位数不能为0,如"0.1.0.010"
if len(s)==1 or (len(s)>1 and int(s[0])!=0 and int(s[:])<256):
temp+=s[:]
res.append(temp)
return
for i in range(1,len(s)):
#不能取到len(s),要给第四段留下至少一个值,否则为空,int(s[0])无法判断
if i>3 or int(s[:i])>256 or (i>1 and int(s[0])==0):
continue
dfs(s[i:],temp+s[:i]+".",count+1)
dfs(s,"",0)
return res
5.18 单词搜索
回溯思想:
递归终止条件,用word的index辅助判断,当index为leng时一定全部找到了;用一个数组表示方向来遍历周围元素; 用一个数组来标记是否可行,每次判断完该位置所有的周围元素,才恢复标记状态
如果在函数里面定义函数,那么最里层函数遇到的变量都当做全局变量处理,所以如果全局是不可变对象如字符串,里面递归引用时会出错;如果是可变对象,尤其是temp,递归终止添加到全局变量时要深复制添加。
如果函数外面定义函数,引用时,要传入所有的所需变量!注意变量顺序的一致性。
递归时,到达终止条件,一定要return, return空或者return True/False, 否则会继续执行下面语句不会终止。
回溯有两种:找到所有可能(类似于全排列或者组合,此时用一个全局变量保存所有可能),判断(只需返回True or False,找到一种可能即可,所以递归终止时不需要全局变量,要返回True/False,每次递归时用的是 if dfs() :可以及时判断,避免穷尽所有)
越界判断最好放在递归终止判断的下面,不要放在每次递归的地方,考虑只有一个元素的时候,那么周围所有都越界了,没办法递归了。
def exist(self, board, word):
"""
:type board: List[List[str]]
:type word: str
:rtype: bool
"""
rows=len(board)
cols=len(board[0])
visited=[[True for i in range(cols)] for j in range(rows)] #标记是否可行
res=[] #记录所有路径数量
d=[[0,-1],[0,1],[1,0],[-1,0]] #表示四个方向
#方法一:用res记录所有路径数量,穷尽所有可能,大规模时 会超时
leng=len(word)
#如果在函数里面定义函数,那么最里层函数遇到的变量都当做全局变量处理,所以如果全局是不可变对象如字符串,里面递归引用时会出错;如果是可变对象,尤其是temp,递归终止添加到全局变量时要深复制添加。
def dfs(i,j,visited,word,count):
if count==leng: #已经找到word
res.append(1) #结束后,一定要返回return 否则会继续执行
return
if not (i>=0 and i<rows) or not (j>=0 and j<cols): #已经遍历过或者与word不相等
return
if visited[i][j] and board[i][j]==word[count]:
visited[i][j]=False
for x in range(4):
new_i=i+d[x][0]
new_j=j+d[x][1]
dfs(new_i,new_j,visited,word,count+1)
#只给res添加而不if判断,那么会使函数一直穷尽下去找到所有可能,超出时间限制
visited[i][j]=True #恢复标记不能放在循环里面,应该是遍历完所有周围的,才能恢复。
return
#遍历所有元素作为开头的元素,一旦找到则直接return True,节省时间。
for i in range(rows):
for j in range(cols):
res=[]
dfs(i,j,visited,word,0)
if len(res)!=0:
return True
return False
"""
#方法二,函数外部定义,递归时每次增加判断,所以是贪心,只需找到一个就返回True
for i in range(rows):
for j in range(cols):
if self.dfs(i,j,rows,cols,visited,board,word,0,d):
return True
return False
def dfs(self,i,j,rows,cols,visited,board,word,count,d): #rows或者cols可以不传入,每次用len(board)代替
#注意三个if的顺序:首先如果找到,返回true,其次判断下标是否越界,最后判断是否已经访问过或者是否相等
if count==len(word): #已经找到word
return True
if not (i>=0 and i<rows) or not (j>=0 and j<cols): #越界情况
return False
if not visited[i][j] or board[i][j]!=word[count]: #已经遍历过或者与word不相等
return False
visited[i][j]=False #先标记
for x in range(4):
new_i=i+d[x][0]
new_j=j+d[x][1]
if self.dfs(new_i,new_j,rows,cols,visited,board,word,count+1,d):
return True
visited[i][j]=True #恢复标记不能放在循环里面,应该是遍历完所有周围的,才能恢复。
return False #如果递归没有找到从当前元素出发的路径,返回false
"""
"""
#方法三,函数内部定义函数
#如果在函数里面定义函数,那么最里层函数遇到的变量都当做全局变量处理,所以如果全局是不可变对象如字符串,里面递归引用时会出错;如果是可变对象,尤其是temp,递归终止添加到全局变量时要深复制添加。
def dfs(i,j,visited,word,count):
if count==leng: #已经找到word
return True
if visited[i][j] and board[i][j]==word[count]:
#排除[["a]] "a"的情况,只有一个元素且相当,第一次判断就相等了,没有周围符合条件,会错误的返回False
if leng==1:
return True
visited[i][j]=False
for x in range(4):
new_i=i+d[x][0]
new_j=j+d[x][1]
if new_i>=0 and new_i<rows and new_j>=0 and new_j<cols:
if dfs(new_i,new_j,visited,word,count+1):
return True
visited[i][j]=True #恢复标记不能放在循环里面,应该是遍历完所有周围的,才能恢复。
return False #如果递归没有找到从当前元素出发的路径,返回false
#遍历所有元素作为开头的元素,一旦找到则直接return True,节省时间。
for i in range(rows):
for j in range(cols):
if dfs(i,j,visited,word,0):
return True
return False
"""
5.19 累加数
类似于分割回文串,但是每次递归时有两个条件:首先判断位数大于一时首位不可为零,其次,前两个数之和要等于当前数,想到了用一个栈来存储每次符合条件的数,回溯之和要恢复res栈。
判断类型回溯,只返回True or False,所以终止条件应该是return True
字符串里面如果是数字时,操作一定要注意转换int,不然相加减判断无效;另,字符串空判断num==[ ]无效。
def isAdditiveNumber(self, num):
"""
:type num: str
:rtype: bool
"""
leng=len(num)
res=[]
def dfs(num,count): #count 记录当前找到的数的个数
#print(count,num,res)
if count>=3 and len(num)==0: #错误:num==[]这里num是字符串,所以要用num=""或len==0
return True
for i in range(1,len(num)+1):
if i>1 and num[0]=="0": #开始写的是if num[0]=="0",忽略一位的时候可以是0, 如101
continue
if count<2: #当个数不足三个,直接压入栈中
res.append(int(num[:i])) #忘记转int
if dfs(num[i:],count+1): #错误:本层忘记回溯了,直接添加完也要回溯,同下面
return True
res.pop(-1)
else: #当个数足够两个,那么就开始判断和是否与当前数相等
if res[-1]+res[-2]==int(num[:i]): #错误:忘记字符串,直接相加比较了,没有转int
res.append(int(num[:i]))
if dfs(num[i:],count+1):
return True
res.pop(-1) #每次回溯完成后要恢复原栈
return False
return dfs(num,0)
5.20 将数组拆分成斐波那契序列
忽略了整数的上限,要读清楚每一个条件,剩下的同上题,最后返回的是全局变量res
def splitIntoFibonacci(self, S):
"""
:type S: str
:rtype: List[int]
"""
#与累加和不同的是,这里每个数有上限,不大于2**31-1
res=[]
num=S
def dfs(num,count): #函数判断是否存在这一的一组数,并用栈记录
#print(count,num,res)
if count>=3 and len(num)==0:
return True
for i in range(1,len(num)+1):
if int(num[:i])>2**31-1:
break
if i>1 and num[0]=="0":
continue
if count<2:
res.append(int(num[:i]))
if dfs(num[i:],count+1):
return True
res.pop(-1)
else:
if res[-1]+res[-2]==int(num[:i]):
res.append(int(num[:i]))
if dfs(num[i:],count+1):
return True
res.pop(-1) #每次回溯完成后要恢复原栈
return False
if dfs(num,0): #如果函数返回True,说明找到,直接返回栈内元素
return res
else:
return []
5.21 N皇后
n*n的矩阵,放置的所有方式,不能处在 同一行、同一列或同一斜线上
思路:回溯,每次即遍历行又遍历列,复杂度高,简化为一维。matrix[i]表示index为i的行中所放置的列的位置,所以每次递归只选择列即可。
能简单就简单,回溯里面最好只有一个for循环,两个的话,考虑能否简化为一维
def solveNQueens(self, n):
"""
:type n: int
:rtype: List[List[str]]
"""
#不能处在 同一行、同一列或同一斜线上
#d=[[-1,-1],[-1,1],[1,1],[1,-1]]用方向判断对角线太慢,对角线元素,row+col=constant,row-col=constant
#temp记录Q坐标位置[[x1,y1],[x2,y2],...[]]
res=[]
matrix=[2*n]*n #matrix[i]表示第i行放置的列的位置,方便判断是否有效
def dfs(row,temp):
if row==n: #遍历完最后一行了
res.append(temp[:])
return
for col in range(n):
if isvalid(row,col,matrix): #如果有效,则row行放置位置为col列
matrix[row]=col
temp.append([row,col]) #添加坐标
dfs(row+1,temp)
temp.pop(-1) #回溯,删除坐标
matrix[row]=2*n #回溯,第row行的col恢复
def isvalid(row,col,matrix): #判断是否有效:同行/列、对角线
for i in range(row):
if col==matrix[i] or col+row==i+matrix[i] or row-col==i-matrix[i]:
return False
return True
dfs(0,[])
#输出指定结果
ret=[]
for l in res:
Map=[["."] * n for _ in range(n)]
for h in l:
Map[h[0]][h[1]]="Q"
t=[]
for _ in Map:
t.append("".join(_))
ret.append(t)
return ret
5.22 不同路径 III
回溯终止条件:走到出口且零被全部遍历。所以,一个标记数组记录是否检查过了,一个count记录遍历的零的数量。
剩下的就是判断是否越界、是否遍历过、是否是障碍物,是的话return。
def uniquePathsIII(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
rows=len(grid)
cols=len(grid[0])
d=[[0,-1],[0,1],[-1,0],[1,0]]
num_obstacle=0
start=[]
#一次遍历找到-1的个数和起点坐标
for row in range(rows):
for col in range(cols):
if grid[row][col]==-1:
num_obstacle+=1
elif grid[row][col]==1:
start=[row,col]
marked=[[True]*cols for _ in range(rows)] #标记是否可行
num_zero=rows*cols-num_obstacle-2 #求出零的个数,终止条件要用
self.res=0 #记录路径个数
def dfs(i,j,grid,marked,count):
#先判断越界,否则grid[i][j]会index out of range
if not (i>=0 and i<rows) or not (j>=0 and j<cols) or not marked[i][j] or grid[i][j]==-1:
return
#递归终止条件:走到出口2 且 0被全部遍历
if grid[i][j]==2 and count==num_zero+1:
self.res+=1
return
marked[i][j]=False
for x in d:
dfs(i+x[0],j+x[1],grid,marked,count+1)
marked[i][j]=True #for循环之外,因为是判断完周围所有的才恢复该位置的状态
dfs(start[0],start[1],grid,marked,0)
return self.res
5.23 不同路径 II
可以用回溯,但是会超时。用动态规划判断,二维数组dp[i][j]表示走到第i行第j列时的路径数量,为了防止越界,多增加一行和一列,初始化为0。转移方程:dp[i][j]=dp[i-1][j]+dp[i][j-1]
也可以简化为一维数组,dp[j] 表示第 j 列的路径数量,一个for循环从上到下遍历所有行,更新dp:转移方程
if j>0:dp[j]+=dp[j-1]
def uniquePathsWithObstacles(self, obstacleGrid):
"""
:type obstacleGrid: List[List[int]]
:rtype: int
"""
#动态规划,二维数组
nums=obstacleGrid
rows=len(nums)
cols=len(nums[0])
dp=[[0]* (cols+1) for _ in range(rows+1)] #表示走到第i行第j列时的路径数量,为了防止越界,多增加一行和一列
for i in range(1,rows+1):
for j in range(1,cols+1): #从1开始,防止越界,且第一行的上一行和第一列的左列都是0,不影响计算dp[i][j]=dp[i-1][j]+dp[i][j-1]
if nums[i-1][j-1]==1:
dp[i][j]=0
elif i==1 and j==1: #起点单独判断设为1
dp[i][j]=1
else:
dp[i][j]=dp[i-1][j]+dp[i][j-1]
return dp[-1][-1]
"""
#动态规划,一维数组
nums=obstacleGrid
rows=len(nums)
cols=len(nums[0])
dp=[0]*cols
dp[0]=1
for i in range(rows):
for j in range(cols):
if nums[i][j]==1:
dp[j]=0
continue
if j>0:
dp[j]+=dp[j-1]
#else: #避免start开始就是1,没有路径的情况
#dp[j]=dp[j]
return dp[-1]
"""
5.24 解数独
挨个位置试1-10所有可能,关键是判断是否有效。行、列、自己的块内不能重复。
[3*(row//3)+i//3] [3*(col//3)+i%3] 3x3块内表示方法
def solveSudoku(self, board):
"""
:type board: List[List[str]]
:rtype: None Do not return anything, modify board in-place instead.
"""
self.solve(board)
return board
def solve(self,board):
for i in range(9):
for j in range(9):
if board[i][j]==".":
for s in range(1,10):
if self.is_valid(i,j,str(s),board):
board[i][j]=str(s)
if self.solve(board):
return True
board[i][j]='.'
return False #放在for循环外面表示,1到10都试过了,还是不行,return False
return True #表示已经试过了最后一个位置
def is_valid(self,row,col,s,board):
for i in range(9):
if board[i][col]==s:return False
if board[row][i]==s:return False
if board[3*(row//3)+i//3][3*(col//3)+i%3]==s:return False
return True
5.25 贴纸拼词
回溯+动态规划。dp[i]表示拼出字符串 i 所需的最少贴纸数量。每次更新字符串。
用数组统计字符出现的频率: ord(s) 字符s 的Unicode编码值
mp=[[0]*26 for i in range(leng)]
#统计出每个sticker的每个字母的出现频率
for i in range(leng):
for s in stickers[i]:
mp[i][ord(s)-ord('a')]+=1
def minStickers(self, stickers, target):
"""
:type stickers: List[str]
:type target: str
:rtype: int
"""
leng=len(stickers)
mp=[[0]*26 for i in range(leng)]
#统计出每个sticker的每个字母的出现频率
for i in range(leng):
for s in stickers[i]:
mp[i][ord(s)-ord('a')]+=1
d={} #d[i]表示拼出字母i所需最少数量,返回d[target]
d[""]=0
def dfs(d,stickers,target):
if target in d:
return d[target]
#统计target中各字母出现的频率
t=[0]*26
for s in target:
t[ord(s)-ord('a')]+=1
ans=sys.maxint
#更新target
for i in range(leng):
if mp[i][ord(target[0])-ord('a')]==0: #如果首字母不在该sticker中,直接跳过
continue
s=""
for j in range(26):
if t[j]>mp[i][j]:
s+=chr(j+ord("a"))*(t[j]-mp[i][j])
temp=dfs(d,stickers,s)
if temp!=-1: #只有temp!=-1才表示可以拼出来
ans=min(ans,temp+1)
d[target]=-1 if ans==sys.maxint else ans
return d[target] #从最后开始依次返回值给temp
return dfs(d,stickers,target)
5.26 活字印刷
求排列(不能简单的sort然后去重,AAB与ABA会被去掉),还要排除重复的。
def numTilePossibilities(self, tiles):
"""
:type tiles: str
:rtype: int
"""
"""
#回溯枚举所有可能,超时
res=[]
leng=len(tiles)
def dfs(tiles,temp):
tem=[s for s in temp]
tem="".join(s for s in tem)
if tem not in res:
res.append(tem)
if tiles=="":
return
for i in range(len(tiles)):
dfs(tiles[:i]+tiles[i+1:],temp+tiles[i])
dfs(tiles,"")
return len(res)-1
"""
#统计每个字母出现的频率
mp=[0]*26
for s in tiles:
mp[ord(s)-ord("A")]+=1 #全部都是大写字母,所以是A
def dfs(tiles,mp):
res=0
for i in range(26):
if mp[i]==0:
continue
mp[i]-=1
res+=1
temp=dfs(tiles,mp)
res+=temp
mp[i]+=1 #回溯的时候要恢复
return res
return dfs(tiles,mp)
6 DFS & BFS
6.1 二叉树的深度
按层次遍历,每经过一层,深度加1,更新下一层节点。直到下一层节点为空,是最大深度了。
def TreeDepth(self, pRoot):
# write code here
if not pRoot:
return 0
q=[]
res=[]
q.append(pRoot)
count=0
while q:
temp=[] #记录下一层的元素
leng=len(q)
for i in range(leng): #遍历当前层的所有元素
r=q[i]
if r.left:
temp.append(r.left)
if r.right:
temp.append(r.right)
q=temp #将下一层元素赋给q
count+=1 #没遍历完一层,加1
return count
6.2 N叉树的最大深度
class Node(object):
def __init__(self, val, children):
self.val = val
self.children = children
class Solution(object):
def maxDepth(self, root):
"""
:type root: Node
:rtype: int
"""
if not root:
return 0
count=0
stack=[root]
while stack:
temp=[]
leng=len(stack)
for i in range(leng):
if stack[i].children:
temp.extend(stack[i].children) #要变为一维数组,所以用extend非append
count+=1
stack=temp
return count
6.3 二进制矩阵中的最短路径
注意:每个位置,有八种选择! 共边或共对角线!回溯超时
按层次遍历,类似于求二叉树的深度,此题中,下一层相当于该层所有节点的周围八个节点。
终止条件是:最先到达右下角节点,最先到达的,一定是最短的!
def shortestPathBinaryMatrix(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
#类似于求二叉树的深度,按层次遍历
n, m = len(grid), len(grid[0])
visited = [[0] * m for _ in range(n)]
if grid[0][0] == 1 or grid[-1][-1] == 1:
return -1
if n == m == 1:
return 1
stack = [[0, 0]]
result = 1
dx, dy = [1, 1, 0, -1, -1, -1, 0, 1], [0, 1, 1, 1, 0, -1, -1, -1]
while stack:
tmp_stack = [] #记录下一层的节点
for x, y in stack: #遍历当前层的所有节点
for i in range(8):
x_ = x + dx[i]
y_ = y + dy[i]
if x_ < 0 or x_ >= n or y_ < 0 or y_ >= m or grid[x_][y_] == 1 or visited[x_][y_]:
#越界或者阻塞或者已经遍历过,跳过
continue
if x_ == n - 1 and y_ == m - 1:
#最先到达最后节点的一定是最短的,返回
return result + 1
tmp_stack.append([x_, y_])
visited[x_][y_] = 1 #标记
stack = tmp_stack #遍历完一层,直接将下一层temp赋值给stack
result += 1 #每走一层,加1
return -1
"""
#回溯超时
rows=len(grid)
cols=len(grid[0])
dire=[[1,0],[0,1],[1,1],[-1,0],[0,-1],[-1,-1],[-1,1],[1,-1]] #八个方向!
mark=[[True]*cols for i in range(rows)] #标记是否走过
res=[]
def dfs(i,j,grid,mark,count):
if i==rows-1 and j==cols-1:#到达终点,添加此条路径的长度
res.append(count)
return
if i<0 or i>=rows or j<0 or j>=cols or (not mark[i][j]) or grid[i][j]==1:
#超出边界或者阻塞或者已经走过了,回溯到上一级
return
mark[i][j]=False #标记
for d in dire: #递归检查其余八个位置,路径长度count+1
dfs(i+d[0],j+d[1],grid,mark,count+1)
mark[i][j]=True #回溯时要恢复标记
dfs(0,0,grid,mark,1)
if grid[rows-1][cols-1]==1 or grid[0][0]==1:
return -1
return min(res) if len(res)!=0 else -1
"""
6.4 . 将有序数组转换为二叉搜索树
- 平衡:对于每个根节点,左右子树高度差不超过1;
- 二叉搜索树:对于每个节点,其左子树值<此节点值,右子树>此节点值
分治算法,每次找到子序列的中间值,递归解决
分治:可以用下标作为递归的传入参数进行分隔,也可以用原数组切片分隔
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def sortedArrayToBST(self, nums):
"""
:type nums: List[int]
:rtype: TreeNode
"""
#左闭右开,保持所有都一致
if not nums:
return None
leng=len(nums)
mid=leng/2
root=TreeNode(nums[mid])
nums1=nums[0:mid]
nums2=nums[mid+1:leng]
root.left=self.sortedArrayToBST(nums1)
root.right=self.sortedArrayToBST(nums2)
return root
"""
#左闭右闭
leng=len(nums)-1
mid=left+(right-left)/2
def build_tree(nums,left,right):
if left>right:
return None
mid=left+(right-left)/2
root=TreeNode(nums[mid])
if left==right:
return root
root.left=build_tree(nums,left,mid-1)
root.right=build_tree(nums,mid+1,right)
return root
return build_tree(nums,0,leng)
"""
6.5 叶子相似的树
树问题:递归解决。
深度优先遍历,保证了叶子节点从左到右的顺序。分别遍历出然后比较
def leafSimilar(self, root1, root2):
"""
:type root1: TreeNode
:type root2: TreeNode
:rtype: bool
"""
def dfs(root,res):
if not root:
return res
if not root.left and not root.right:
res.append(root.val)
if root.left:
dfs(root.left,res)
if root.right:
dfs(root.right,res)
return res
res1=dfs(root1,[])
res2=dfs(root2,[])
if res1==res2:
return True
else:
return False
6.6 二叉树的所有路径
找所有路径:回溯. 递归终止条件:到达叶子节点
元素之间连接的快速实现方法:"->".join()
def binaryTreePaths(self, root):
"""
:type root: TreeNode
:rtype: List[str]
"""
res=[]
def dfs(root,temp):
if not root:
return
if not root.left and not root.right:
temp+=str(root.val)
res.append(temp)
return
temp+=str(root.val)
if root.left:
dfs(root.left,temp+"->")
if root.right:
dfs(root.right,temp+"->")
dfs(root,"")
return res
6.7 递增顺序查找树
先中序遍历,得到从小到大的排列顺序数组,然后重新建树;
中序遍历: middle(root.left) ; res.append(root.val) ; middle(root.right)
def increasingBST(self, root):
"""
:type root: TreeNode
:rtype: TreeNode
"""
#中序遍历+重建树
def middleBFS(root):
if not root:
return
middleBFS(root.left)
res.append(root.val) #中序遍历
middleBFS(root.right)
#建树
res=[]
middleBFS(root)
head=TreeNode(res.pop(0))
r=head
while res:
head.right=TreeNode(res.pop(0))
head=head.right
return r
6.8 相同的树
分治,递归。
def isSameTree(self, p, q):
"""
:type p: TreeNode
:type q: TreeNode
:rtype: bool
"""
#递归
if (not p and not q):
return True
elif (p and q and p.val==q.val):
return self.isSameTree(p.left,q.left) and self.isSameTree(p.right,q.right)
else:
return False
"""
#按层次遍历,比较
if not p and not q:
return True
if not p or not q:
return False
stack1=[p]
stack2=[q]
while stack1 or stack2:
temp1=[]
temp2=[]
for s1,s2 in zip(stack1,stack2):
if s1.val==s2.val:
if s1.left and s2.left:
temp1.append(s1.left)
temp2.append(s2.left)
elif not s1.left and not s2.left:
if s1.right and s2.right:
temp1.append(s1.right)
temp2.append(s2.right)
elif not s1.right and not s2.right:
continue
else:
return False
else:
return False
else:
return False
stack1=temp1
stack2=temp2
return True
"""
6.9 员工的重要性
类似于N叉树,递归求解
def getImportance(self, employees, idd):
"""
:type employees: Employee
:type id: int
:rtype: int
"""
"""
#方法一:递归
res=0
for em in employees:
if em.id==idd:
res+=em.importance
if em.subordinates:
for i in em.subordinates:
res+=self.getImportance(employees,i)
return res
"""
#方法二:用栈作为中间存储单元
d={}
for em in employees:
d[em.id]=em
stack=[]
res=0
for em in employees:
if em.id==idd:
stack.extend(em.subordinates)
res+=em.importance
while stack:
h=stack.pop()
res+=d[h].importance
stack.extend(d[h].subordinates)
return res
6.10图像渲染
方法一:回溯,类似拼写单词路径,超时
方法二:类似二进制矩阵路径,按层次遍历,用栈存储下一层即四个方向的元素
def floodFill(self, image, sr, sc, newColor):
"""
:type image: List[List[int]]
:type sr: int
:type sc: int
:type newColor: int
:rtype: List[List[int]]
"""
#按层次遍历,用栈存储下一层次的节点
rows=len(image)
cols=len(image[0])
color=image[sr][sc]
stack=[[sr,sc]]
d=[[0,0],[1,0],[-1,0],[0,1],[0,-1]] #考虑到起始节点,所有增加【0,0】使其被标记
mark=[[True]*cols for i in range(rows)]
while stack:
temp=[] #要放在for外面,对存储所有节点的下一层
for i,j in stack:
for s in d:
if self.valid(i+s[0],j+s[1],image,color,mark):
image[i+s[0]][j+s[1]]=newColor
temp.append([i+s[0],j+s[1]])
mark[i+s[0]][j+s[1]]=False
stack=temp
return image
def valid(self,i,j,grid,color,mark): #定义边界条件/是否标记/值是否相等
if i>=0 and i<len(grid) and j>=0 and j<len(grid[0]) and grid[i][j]==color and mark[i][j]:
return True
else:
return False
"""
#回溯,超时
d=[[1,0],[-1,0],[0,1],[0,-1]]
rows=len(image)
cols=len(image[0])
mark=[[True]*cols for i in range(rows)]
color=image[sr][sc]
def dfs(grid,mark,i,j):
if i<0 or i>=rows or j<0 or j>=cols: #判断出界
return
if not mark or grid[i][j]!=color: #判断标记和值
return
grid[i][j]=newColor
mark[i][j]=False #每次回溯不用恢复mark,永远标记
for s in d:
dfs(grid,mark,i+s[0],j+s[1])
dfs(image,mark,sr,sc)
return image
"""
6.11 牛牛游玩记
入口出口路径问题:BFS,类似于6.3,用栈存储下一步的所有节点。每走完一层,count加一,深度遍历,一定是最短的。
切记要记得标记,否则重复遍历会超时;多个入口一个出口多个时候,反向将多个入口当做出口,将出口当做入口。
#读取数据
import sys
n=int(sys.stdin.readline().strip())
mp=[]
for i in range(n):
value=sys.stdin.readline().strip()
mp.append(value)
#获得出口和入口坐标以及障碍物标注
inlet=[]
outlet=[]
mark=[[0]*n for h in range(n)]
for i in range(n):
for j in range(n):
if mp[i][j]=="*":
outlet.extend([i,j])
elif mp[i][j]=="@":
inlet.extend([i,j])
mark[i][j]=1
elif mp[i][j]=="#":
mark[i][j]=-1
else :
continue
#开始按层次搜索深度遍历,一定是最短的
dx=[0,0,1,-1]
dy=[1,-1,0,0]
count=1
flag=[[True]*n for h in range(n)]
stack=[]
stack.append(outlet)
find=False
while stack:
temp=[]
for i,j in stack:
for x,y in zip(dx,dy):
x_=i+x
y_=j+y
if not (x_>=0 and x_<n) or not ( y_>=0 and y_<n) or mark[x_][y_]==-1:
continue
if not flag[x_][y_]:
continue
if mark[x_][y_]==1:
print count
find=True
break
temp.append([x_,y_])
flag[x_][y_]=False
if find:
break
count+=1
stack=temp
if find:
break
6.12 平衡二叉树
求树的最大深度递归方法:max(dfs(root.left),dfs(root.right))+1
平衡,要求每个节点的左右子树的深度差都不能超过1
def isBalanced(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
def dfs(root): #定义求树的最大深度
if not root:
return 0
return max(dfs(root.left),dfs(root.right))+1
if not root:
return True
left=dfs(root.left)
right=dfs(root.right)
if abs(right-left)>1:
return False
return self.isBalanced(root.left) and self.isBalanced(root.right)
6.13 对称二叉树
按层次遍历,每次判断下一层节点是否对称
或者每层多加一个list2添加val值,这样直接看list2是否对称list2[:]==list2[::-1]
def isSymmetric(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
#方法一:递归方法
def dfs(root1,root2):
if not root1 and not root2:
return True
elif not root1 or not root2:
return False
else:
if root1.val!=root2.val:
return False
else:
return dfs(root1.right,root2.left) and dfs(root1.left,root2.right) #注意传入参数,对称递归
return dfs(root,root)
"""
#方法二:层次遍历
if not root:
return True
#常规的按层次遍历会筛掉None, 这样会容易遗漏一种情况,就是3 None, None 3 也是对称的
stack=[]
stack.append(root)
while stack:
temp=[]
for i in range(len(stack)):
if stack[i]: #无论左右子树空不空,都添加进来
temp.append(stack[i].left)
temp.append(stack[i].right)
else:
continue
if temp:
leng=len(temp)
for i in range(leng/2+1): #看下一层节点是否对称
if temp[i] and temp[leng-1-i] and temp[i].val!=temp[leng-1-i].val: #如果都为非空且值不相等
return False
if (temp[i] and not temp[leng-1-i]) or (not temp[i] and temp[leng-1-i]): #如果一个空一个非空
return False
stack=temp
return True
"""
6.14 路径总和
递归,每次更新sum值。
深度优先。栈存储一对数值:左右子节点,减去该节点值当前剩余sum值。每次pop() 出栈,实现深度优先
def hasPathSum(self, root, summ):
"""
:type root: TreeNode
:type sum: int
:rtype: bool
"""
#方法一:非递归,深度优先,每次存储左右子节点和sum对应减去其值的结果
if not root:
return False
stack=[root]
arr=[summ-root.val]
while stack:
p=stack.pop() #每次拿最后一个,栈结构,后进先出
cursum=arr.pop()
if not p.left and not p.right and cursum==0:
return True
if p.left:
stack.append(p.left)
arr.append(cursum-p.left.val)
if p.right:
stack.append(p.right)
arr.append(cursum-p.right.val)
return False
"""
#方法二:递归
def dfs(root,sumnum):
if not root.left and not root.right and sumnum==root.val:
return True
if root.left:
if dfs(root.left,sumnum-root.val):
return True
if root.right:
if dfs(root.right,sumnum-root.val):
return True
return False
if not root:
return False
return dfs(root,summ)
"""
6.15 路径总和 II
回溯法,每次更新sum,终止条件:到达叶子节点且sum==该节点值
找到所有路径:回溯或者BFS(记录到达该节点的路径上的所有值),需要新的结构 (node,[ val1,val2,...node.val])
def pathSum(self, root, summ):
"""
:type root: TreeNode
:type sum: int
:rtype: List[List[int]]
"""
#找到最短或者只一条满足条件的路径->深度优先遍历DFS(层次遍历);
#找到所有满足条件的路径->回溯,或者广度优先遍历BFS(记忆每一个路径上的数,累计记忆)
#新的结构 [(Node,[node1.val,node2.val,...])] #set里面节点与节点前路径上的所有值
if not root:
return []
stack=[(root,[root.val])]
res=[]
for i,j in stack:
if not i.left and not i.right and sum(j)==summ:
res.append(j)
if i.left:
copy_j=j[:]
copy_j.append(i.left.val)
stack.append((i.left,copy_j))
if i.right:
copy_j=j[:]
copy_j.append(i.right.val)
stack.append((i.right,copy_j))
return res
"""
#回溯
res=[]
def dfs(root,temp,sumnum):
if not root.left and not root.right and sumnum==root.val:
temp.append(root.val)
res.append(temp[:])
return
if root.left:
dfs(root.left,temp+[root.val],sumnum-root.val)
if root.right:
dfs(root.right,temp+[root.val],sumnum-root.val)
if not root:
return []
dfs(root,[],summ)
return res
"""
6.16 二叉树的最小深度
二叉树的最小深度,深度优先遍历,第一次遇到叶子节点就返回深度
class Solution(object):
def minDepth(self, root):
"""
:type root: TreeNode
:rtype: int
"""
if not root:
return 0
stack=[root]
count=1
while stack:
leng=len(stack)
temp=[]
for i in range(leng):
if not stack[i].right and not stack[i].left:
return count
if stack[i].left:
temp.append(stack[i].left)
if stack[i].right:
temp.append(stack[i].right)
stack=temp
count+=1
6.17 有序链表转换二叉搜索树
class Solution(object):
def sortedListToBST(self, head):
"""
:type head: ListNode
:rtype: TreeNode
"""
#将链表转换为数组,变成 108.有序数组转换为二叉搜索树
l=[]
while head:
l.append(head.val)
head=head.next
#构建搜索树
def search(arr):
if not arr:
return None
leng=len(arr)
mid=leng/2
root=TreeNode(arr[mid])
root.left=search(arr[0:mid])
root.right=search(arr[mid+1:leng])
return root
return search(l)
6.18 找树左下角的值
深度遍历(按层次),记录上一行的值,最后返回第一个
class Solution(object):
def findBottomLeftValue(self, root):
"""
:type root: TreeNode
:rtype: int
"""
#求最大深度一样,不过每次要记录上一行,才能得到最后一行
if not root:
return None
stack=[root]
while stack:
#last=stack #记录上一行
temp=[]
leng=len(stack)
for i in range(leng):
if i==0:
res=stack[i].val #用一个变量记录每行的第一个值
if stack[i].left:
temp.append(stack[i].left)
if stack[i].right:
temp.append(stack[i].right)
stack=temp
return res
6.19 在二叉树中分配硬币
拿一颗子树分析,找到规律,递归解决。每个子节点与其父节点的交换量为:abs(硬币数-1),所以每棵树的移动量为L+R,多出来的要传递给上一棵树。
class Solution(object):
def distributeCoins(self, root):
"""
:type root: TreeNode
:rtype: int
"""
res=[0]
def behind_find(root):
if not root:
return 0
left=behind_find(root.left)
right=behind_find(root.right)
diff=left+right+root.val-1 #每棵树的过载量,要返回
res[0]+=abs(diff) #每棵树的移动量
return diff
behind_find(root)
return res[0]
6.20 二叉树展开为链表
先序遍历出由上到下的顺序,存入栈中,再依次出栈建立连接
class Solution(object):
def flatten(self, root):
"""
:type root: TreeNode
:rtype: None Do not return anything, modify root in-place instead.
"""
#先序遍历存入栈
res=[]
def first_find(root):
if not root:
return None
res.append(root)
first_find(root.left)
first_find(root.right)
first_find(root)
#栈弹出的顺序就是从下到上的顺序,依次建立连接
if root: #非空
right_last=res.pop()
while res:
R=res.pop()
R.left=None
R.right=right_last
right_last=R
root=right_last #根节点赋给root
6.21 从中序与后序(前序)遍历序列构造二叉树
重构二叉树:前序/后序+中序(必须有中序才可以)
后序+中序:从后序中pop()出最后一个为根
前序+中序:从前序中pop(0)出第一个为根
注意切片中,后序和前序的右子树都是i开始而非i+1(因为已经pop出一个元素了)
class Solution(object):
#后序+中序
def buildTree(self, inorder, postorder):
"""
:type inorder: List[int]
:type postorder: List[int]
:rtype: TreeNode
"""
if not inorder:
return
rot=postorder.pop()
i=inorder.index(rot)
root=TreeNode(rot)
root.left=self.buildTree(inorder[:i],postorder[:i])
root.right=self.buildTree(inorder[i+1:],postorder[i:]) #postorder不是从i+1开始
return root
#前序+中序
def buildTree(self, preorder, inorder):
"""
:type preorder: List[int]
:type inorder: List[int]
:rtype: TreeNode
"""
if not preorder:
return
rot=preorder.pop(0)
i=inorder.index(rot)
root=TreeNode(rot)
root.left=self.buildTree(preorder[:i],inorder[:i])
root.right=self.buildTree(preorder[i:],inorder[i+1:])
return root
6.22 地图分析(广度优先)
典型的广度优先遍历(层次遍历),下一层指该层所有元素的周围四个位置,表示距离+1
先存储所有的陆地,然后类似于按层次遍历,遍历每层中每个元素的周围四个位置,若为海洋则置1并添加到下一层中。每遍历一层相当于距离加1。直到没有下一层,表示没有海洋了。
class Solution(object):
def maxDistance(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
num_1=0
dic_1=[]
n=len(grid)
for i in range(n):
for j in range(n):
if grid[i][j]==1:
num_1+=1
dic_1.append([i,j])
if num_1==n**2 or num_1==0:
return -1
res=-1 #从-1开始的
while dic_1:
l=len(dic_1)
for i in range(l):
temp=dic_1.pop()
#pop()出错,必须pop(0)?因为下面会append下一层元素,直接pop()是下一层,当前层需要从前向后pop(0)
for dx,dy in [[0,1],[0,-1],[1,0],[-1,0]]:
nx=temp[0]+dx
ny=temp[1]+dy
if 0<=nx<n and 0<=ny<n and grid[nx][ny]==0:
grid[nx][ny]=1 #将海洋置标记为陆地,为了后面继续查找
dic_1.append([nx,ny])
res+=1
return res
总结
1 位运算总结:
i&(i-1)相当于把i的二进制表示形式的最右边的1去掉;
判断奇偶:奇:i&1==1, 偶:i&1==0;
右移,n>>1相当于地板除
2 ord(‘a’)=97 求字符的ascii码
3 sum(map(ord,s1)): map(f, list)将list每个元素带入函数f中
4 map+lambda: map 对list中每一个元素执行lambda函数
5 大礼包的使用问题。每个都可以无限次重复使用,不知道使用谁也不知道使用几次——递归+遍历,每次遍历都可以从第一个开始,考虑了重复情况。
6 python 切片操作,可以超出上界,但不可低于下界。。list[num1:num2]。num2可以超界,默认取到最大,num1不可以取负值(会变为空数组)
7 for 循环多个变量:zip :for i,j in zip(a,b):
8 动态规划难点:dp[i] or dp[i][j] 表示的是什么,即如何构建动态规划变量(以第i个元素结尾的值?坐标为ij的值?);还有就是动态转移方程,如何利用前面的子问题解;最后,注意初始值的设定,初始化要根据动态转移方程边界条件来决定!9 背包问题:0-1背包:物品循环在外面(要么dp[i] 要么dp[i-1],里面由大到小循环,见3.42);完全背包:物品循环在里面dp[i]可以多次使用,见3.41 、3.43。
10 深浅复制问题:
copy.deepcopy(),完全复制出一个新的个体,与原来独立;
copy.copy():如果复制的是不可变对象(数字,字符串,元组),等于赋值”的情况一样,对象的id值与浅复制原来的值相同;如果是可变对象(列表,元组),分情况:改变复杂子对象,也会改变,无复杂子对象,不会变。参考
11 空数组问题。res=[ ] for i,x in enumerate(res): x+'y'(不能实现,空列表不能与字符相加) 修改:res=[ ""] ,表示空字符串列表,可以相加。
12 判断字符还是数字 str.isalpha() str.isdigit() 判断大小写 :s.islower() str.isupper() 大小写转换 :s.lower() s.upper()
13 bin(num).count("1"),数字转换为二进制并计算1的个数. str.rjust(width,0)用0由左向右填充使不足width的字符串长度增加为width.
14 TypeError: can only concatenate list (not "int") to list:
列表加入元素时候,不能直接[ ]+b[2],要以列表形式加入:[ ]+[ b[ 2]]
15 回溯的特点:
每个位置有多种选择(满足条件才可以选),有终止条件(和为多少,长度达到多少),注意函数的向下次递归传入的参数变化(排列问题:不能使用已经用过的元素,则nums[:i]+nums[i+1:],可以重复使用当前值,nums[i:]);组合问题,不考虑顺序,要剔除重复,所以传给下次递归的参数要去掉之前已经遍历过的 nums[ i+1:]
16 回溯有两种:
找到所有可能(类似于全排列或者组合,此时用一个全局变量保存所有可能),判断(类似于单词搜索3.18,只需返回True or False,找到一种可能即可,所以递归终止时不需要全局变量,要返回True/False,每次递归时用的是 if dfs() :可以及时判断,避免穷尽所有)
17 string.find(str,beg=0,end=len(strding)), find返回下标beg开始的与str重复的元素的下标。find( "a",1)从下标1开始查找
18 nx2的二维数组操作:for x,y in stack: 会依次遍历所有元素,每次x y取s[0] s[1];stack.sort() 按照每个元素s的s[0]进行排序
19 入口出口路径问题:BFS,类似于6.11,用栈存储下一步的所有节点。每走完一层,count加一深度优先遍历(按层次遍历)用栈存储下一层的节点,直到栈空,6.3、6.10 深度遍历,一定是最短的。
20 牛客模拟6月,第一题犯了傻屌错误:
if s in ss: count+=1
else: maxnum=max(maxnum,couunt) count+=1
maxnum 如果只在不符合条件的时候更新,那么还应该在所有循环结束后再更新一下,忘记了,过了40% 唉
21 BFS 与 DFS区别:
找到最短或者只一条满足条件的路径->深度优先遍历DFS(层次遍历);
找到所有满足条件的路径->回溯,或者广度优先遍历BFS(记忆每一个路径上的数,累计记忆)如6.15 需要新的结构 [(Node,[node1.val,node2.val,...])] #set里面节点与节点前路径上的所有值,如6.2222 字典初始化方法:d=collections.defaultdict(int) 有头有尾,区间重叠,考虑用字典统计 d[s]+=n,d[e]-=n
23 循环对n个元素操作,i+=1, i%num, list[i]+cand
24 一串字符的操作布尔,用两个栈分别存储操作符和值,出栈到括号停止,就可以找到操作符的作用范围 4.4