剑指offer(3-52) python3

题目顺序

剑指 Offer 03. 数组中重复的数字 简单

解法1:hash记录,如果重复直接退出,时间O(n),空间O(n)
解法2:鸽笼原理:判断i==(nums[i]=m),相等判断下一个数字,不相等则判断nums[i]==nums[m],出现重复,不相等调整nums[i]。时间O(n),空间O(1)
解法3:排序查找相同。时间O(nlogn),空间O(1)

hashmap = {}
for i in nums:
    if i in hashmap:
        return i
    hashmap[i]=1
for i in range(len(nums)):
     while nums[i] != i:
    	 if nums[nums[i]] == nums[i]:
	         return nums[i]
          nums[nums[i]] , nums[i] = nums[i] , nums[nums[i]]

剑指 Offer 04. 二维数组中的查找 简单

解法:站在右上角看,这个矩阵其实就像是一个Binary Search Tree。从右上角开始p,等于p返回,小于p在左边,大于p在下方。时间O(n*m),空间O(1)

if len(matrix)==0 or len(matrix[0])==0: return False
        row,col = 0,len(matrix[0])-1
        while row<len(matrix) and col>=0:
            if matrix[row][col]>target:
                col-=1
            elif matrix[row][col]<target:
                row+=1
            else:
                return True
        return False

剑指 Offer 05. 替换空格 简单

解法1:从后面开始,遇到空格替换,减少空间使用,可以先扩充空格*2个空间。时间O(len(s),空间O(len(s))
解法2:从前面开始,遇到空格替换。时间O(len(s)),空间O(len(s))

res = ""
for i in range(len(s)-1,-1,-1):
    if s[i]!=' ':
        res = s[i]+res
    else:
        res = '%20'+res
return res
return  s.replace(" ","%20")

剑指 Offer 06. 从尾到头打印链表 简单

解法:遍历链表,将每个值加入res,结果反转或是使用insert,使用栈先进后出的特性,全部读完后反向输出。时间空间O(n)

res = []
while head:
    res.append(head.val)
    head = head.next
return res[::-1]

剑指 Offer 07. 重建二叉树 中等

解法:根据先序划分左右子树,然后递归调用,时间空间O(n)

def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
	if not inorder:
	    return None
	root_val = preorder.pop(0)
	index_root = inorder.index(root_val)
	root = TreeNode(root_val)
	root.left = self.buildTree(preorder,inorder[:index_root])
	root.right = self.buildTree(preorder,inorder[index_root+1:])
	return root

剑指 Offer 09. 用两个栈实现队列 简单

解法:入栈保存在stack_in,出栈保存在stack_out,删除时将stack_in反向保存在out里。 时间空间O(n)

def __init__(self):
        # 入栈和出栈分别维护
        self.stack_in=[]
        self.stack_out=[]

    def appendTail(self, value: int) -> None:
        self.stack_in.append(value)

    def deleteHead(self) -> int:
        if self.stack_out==[]:
            if self.stack_in==[]:
                return -1
            else:
                while self.stack_in:
                    self.stack_out.append(self.stack_in.pop())
        return self.stack_out.pop()

剑指 Offer 10- I. 斐波那契数列 简单

解法:dp[i + 1] = dp[i] + dp[i - 1] dp[n],即斐波那契数列的第 n 个数字。
时间O(n) 空间O(1)

#Python 由于语言特性可以省去 sumsum 辅助变量和大数越界处理
a, b = 0, 1
for _ in range(n):
    a, b = b, a+b
return a % 1000000007

剑指 Offer 10- II. 青蛙跳台阶问题 简单

解法:dp[i ] = dp[i-2] + dp[i - 1] 。
时间O(n) 空间O(1)

    def numWays(self, n: int) -> int:
        a,b=1,1
        for _ in range(n):
            temp = (a+b)%1000000007
            a,b=b,temp
        return a

剑指 Offer 11. 旋转数组的最小数字 简单

解法:二分判断可能无序的情况(右边),如果顺序,最小点在m处r=m;如果乱序,最小点在[m+1,r]l=m+1;相等的时候,不确定状况,从右边下来保守。时间O(logn)空间O(1)

l, r =0,len(numbers)-1
while l<r:
    m = (l+r)//2
    if numbers[m]<numbers[r]:
        r=m
    elif numbers[m]>numbers[r]:
        l=m+1
    else:
        r-=1
return numbers[l]

剑指 Offer 12. 矩阵中的路径 中等

解法:DFS+递归,每次判断当前字母的合法性(索引越界,是否为当前查找值),使用递归判断下一个字母,直到长度为k=len(word)。时间O(3kmn)每次递归深度为k,每次3个(不走前路),因此每次时间为O(3k),可查找元素为m*n,空间O(k)

def dfs(i,j,k):
   	# 行、列越界,不相等
    if not 0<=i<row or not 0<=j<col or board[i][j]!=word[k]: return False
    if k==len(word)-1: return True
    tmp, board[i][j] = board[i][j], '/'
    # 在调用邻居时,保证当前不可访问
    res = dfs(i-1,j,k+1) or dfs(i+1,j,k+1) or dfs(i,j-1,k+1) or dfs(i,j+1,k+1) 
    board[i][j] = tmp
    return res

row,col = len(board),len(board[0])
for i in range(row):
	for j in range(col):
	    if dfs(i,j,0): return True
return False

剑指 Offer 13. 机器人的运动范围 中等

即使是k=0,也能走一步(0,0).首先将(0,0)保存,每一步只能向下或向右走,只需判断当前数位和是否合法。时空O(mn)

def digitsum(n):
	n = 0
    while(v):
        n+=v%10
        v = v//10
    return n
def movingCount(self, m: int, n: int, k: int) -> int:
    vis = set([(0, 0)])
    for i in range(m):
        for j in range(n):
            if ((i - 1, j) in vis or (i, j - 1) in vis) and digitsum(i) + digitsum(j) <= k:
                vis.add((i, j))
    return len(vis)

剑指 Offer 14- I. 剪绳子 中等

尽可能的剪出3和2,3的优先级最高.将绳子 .以相等的长度等分为多段 ,得到的乘积最大n=3a+b,b=0是,res=3a;b=1时,res=3a-1x2x2;b=2时,res=3ax2

if n<=3:
    return n-1
s = 1
while n>4:
    s*=3
    n-=3
return s*n

剑指 Offer 14- II. 剪绳子 II 中等

区别是大整数溢出,需要取余:快速幂求余

a,b,x,p,rem=n//3-1,n%3,3,1000000007,1
while a>0:  # (x^a-1^) % p
    # 奇数
    if a%2: rem = (rem*x)%p
    x = x**2%p
    a = a//2
if b==0:
    return (rem*3)%p
elif b==1:
    return (rem*4)%p
else:
    return (rem*6)%p

剑指 Offer 15. 二进制中1的个数

1&1=1 1&0=0,时间O(log2n),空间O(1)

res = 0
while n:
    res += n&1
    n>>=1
return res

剑指 Offer 16. 数值的整数次方 中等

1&1=1 1&0=0,时间O(log2n),空间O(1)

def myPow(self, x: float, n: int) -> float:
    if x==0:
        return 0
    if n<0: x,n=1/x,-n
       rem = 1
       while n>0:
           if n%2:rem *= x
           x *=x
           n//=2
       return rem

剑指 Offer 18. 删除链表的节点 简单

使用哑节点

def deleteNode(self, head: ListNode, val: int) -> ListNode:
    dummy = ListNode(float('inf'))
    dummy.next = head

    pre,p = dummy,dummy.next
    while p:
        if p.val == val:
            pre.next = p.next
            break
        pre,p = p,p.next
    return dummy.next

剑指 Offer 19. 正则表达式匹配 困难

当前在(i,j)主要考虑的是✳号:分为两种可能,匹配前面字符0次,转移(i,j+2);匹配一次,转移s[i]=p[j] and(i+1,j)

def isMatch(self, s: str, p: str) -> bool:
    memo = {}
    def dp(i,j):
        # 记在备忘录
        if (i,j) in memo: return memo[(i,j)]

        # j超出pattern时,若i此时也超出s,则成功,否则失败
        if j==len(p): return i==len(s)
        # j没有超出,i没有超出,现在匹配第一个是否成功(第一个有两种可能,‘.’或是‘s[i]’)
        first_match = i<len(s) and p[j] in [s[i],'.']
        
        # 判断‘*’的存在
        if j<=len(p)-2 and p[j+1]=='*':
            # 匹配0次(pj走两步,si不走)   或    匹配当前字符s[i]一次(si走一步,p不走)
            ans = dp(i,j+2) or (first_match and dp(i+1,j))
        else:
            # 没有‘*’,直接判断第一个字符
            ans = first_match and dp(i+1,j+1)
            
        memo[(i,j)]=ans
        return ans
    return dp(0,0)

剑指 Offer 20. 表示数值的字符串 中等

状态机states[i] ,其中 i 为所处状态, states[i]使用哈希表存储可转移至的状态

    def isNumber(self, s: str) -> bool:
        # states[i] ,其中 i 为所处状态, states[i]使用哈希表存储可转移至的状态
        # A[.[B]][e/EC]
        # 从开始可能的状态 ' ':继续作为开始, 's':e之前的符号, 'd':数字, '.':小数点
        # e之前的符号s:可转移状态有'd' '.'
        # A是小数点之前的整数:可转移状态有'd', '.' , 'e', ' '
        # B是小数点之后的数字:可转移状态有'd', 'e', ' '
        # 开始之后直接是小数点:可转移状态有'd'
        # e 可转移状态有's', 'd'
        # e之后的符号:可转移状态有'd'
        # C是e之后的数字:可转移状态有'd', , ' '
        # 结束空格,只能一直空格到末尾:转移状态' '
    
        states = [
            { ' ': 0, 's': 1, 'd': 2, '.': 4 }, # 0. start with 'blank'   空格开始
            { 'd': 2, '.': 4 } ,                # 1. 'sign' before 'e'    出现在e之前的符号
            { 'd': 2, '.': 3, 'e': 5, ' ': 8 }, # 2. 'digit' before 'dot'  出现在小数点之前的数字
            { 'd': 3, 'e': 5, ' ': 8 },         # 3. 'digit' after 'dot'   出现在小数点之后的数字
            { 'd': 3 },                         # 4. 'digit' after 'dot' (‘blank’ before 'dot')  空格后直接就是小数点,小数点之后的数字
            { 's': 6, 'd': 7 },                 # 5. 'e'   出现e
            { 'd': 7 },                         # 6. 'sign' after 'e'      e之后的符号
            { 'd': 7, ' ': 8 },                 # 7. 'digit' after 'e'     e之后的数字
            { ' ': 8 }                          # 8. end with 'blank'      结束空格符
        ]
        p = 0                           # start with state 0
        for c in s:
            # c是下一个转移状态
            if '0' <= c <= '9': t = 'd'  # digit
            elif c in "+-": t = 's'     # sign
            elif c in ".eE ": t = c     # dot, e, blank
            else: t = '?'               # unknown
            #  t不在哈希表 states[p]中,说明无法转移至下一状态,因此直接返回 False
            if t not in states[p]: return False
            p = states[p][t]
        return p in (2,3,7,8)

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 简单

左右两个指针,如果左为奇,则左指针前移;如果右为偶,则右指针后移;如果左指向数组元素为偶,右指向数组元素为奇,互相交换位置

def exchange(self, nums: List[int]) -> List[int]:
    i,j =0, len(nums)-1
    while i<j:
        # 找偶数
        if nums[i]&1==1:
            i+=1
        # 找奇数
        elif nums[j]&1==0 and i<j:
            j-=1
        else:
            nums[i],nums[j] = nums[j],nums[i] 
            i+=1
            j-=1
    return nums

剑指 Offer 22. 链表中倒数第k个节点 简单

双指针:指针p走k步;然后两个指针p,q一起走,p走到头时,q走了lenth-k步,即倒数第k个

    def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
        p,q=head,head
        while k:
            p = p.next
            k-=1
        while p:
            p, q = p.next, q.next
        return q

剑指 Offer 24. 反转链表 简单

新建尾结点q=None,从头遍历链表,每次将头结点指向尾结点,并将头结点指向下一个节点,尾结点更新为当前节点

    def reverseList(self, head: ListNode) -> ListNode:
        q = None
        while head:
            temp = head.next
            head.next = q
            q = head
            head = temp
        return q
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        dummy = ListNode(0)
        L = dummy
        while l1 and l2:
            if l1.val<=l2.val:
                L.next = l1
                l1=l1.next
            else:
                L.next = l2
                l2=l2.next
            L = L.next
        if l1:
            L.next = l1
        if l2:
            L.next = l2
        return dummy.next

剑指 Offer 25. 合并两个排序的链表 简单

哑节点
时空O(M+N)

    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        dummy = ListNode(0)
        L = dummy
        while l1 and l2:
            if l1.val<=l2.val:
                L.next = l1
                l1=l1.next
            else:
                L.next = l2
                l2=l2.next
            L = L.next
        if l1:
            L.next = l1
        if l2:
            L.next = l2
        return dummy.next

剑指 Offer 26. 树的子结构 中等

递归匹配,使用原函数找到第一个匹配点B的根节点;递归函数匹配B的左右子树
时间O(MN):先序遍历A树时间O(M),每次调用递归函数O(N)。空间O(M),空间栈占用的A的节点

    def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
        def Recur(A,B):
            # 当节点 B 为空:说明树 B 已匹配完成
            if not B:return True
            # 当节点A为空,B不为空或是A!=B,匹配错误,
            if not A or A.val!=B.val:return False
            # 匹配左右子树
            return Recur(A.left,B.left) and Recur(A.right,B.right)
       
        # 特例是第一次传参的A,B为空时错误.找到第一个匹配点后会调用
        return bool( A and B) and (Recur(A,B) or self.isSubStructure(A.left,B) or self.isSubStructure(A.right,B))

剑指 Offer 27. 二叉树的镜像 简单

使用队列,将每一层由左至右记录下来,并将每个节点的左右置换,时间O(n),空间O(最大行)

def mirrorTree(self, root: TreeNode) -> TreeNode:
	if not root:return root
	queue = collections.deque([root])
	while queue:
	   lenth = len(queue)
	   for i in range(lenth):
	       node = queue.popleft()
	       if node.left:
	           queue.append(node.left)
	       if node.right:
	           queue.append(node.right)
	       node.left, node.right = node.right, node.left
	return root

剑指 Offer 28. 对称的二叉树 简单

递归调用,每次判断左左-右右,左右-右左
时间O(N),最多调用N/2个节点;空间O(N),最差的链表状态占用空间O(N)

    def isSymmetric(self, root: TreeNode) -> bool:
        return self.check(root,root)
    def check(self,p,q):
        if not p and not q:return True
        elif p and q and p.val==q.val:
            return self.check(p.left,q.right) and self.check(p.right,q.left)
        else:
            return False

剑指 Offer 29. 顺时针打印矩阵 简单

按照顺时针打印,左->右(上)->下(右)->左(下)->上,判断是否到达边界
时间O(MN) 空间O(1)

	def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        if not matrix: return []
        left,right,up,down,res,i = 0,len(matrix[0])-1,0,len(matrix)-1,[],0
        while True:
            # left to right  上边界 up 加 1  up>down
            for i in range(left,right+1): res.append(matrix[up][i])
            up+=1
            if up>down: break
            # up to down  右边界 right减 1  right<left
            for i in range(up,down+1): res.append(matrix[i][right])
            right-=1
            if left>right: break
            # right to left  下边界 down减 1  up>down
            for i in range(right,left-1,-1): res.append(matrix[down][i])
            down-=1
            if up>down: break
            #  down to up 左边界 left加 1  right<left
            for i in range(down,up-1,-1): res.append(matrix[i][left])
            left+=1
            if left>right: break
        return res

剑指 Offer 30. 包含min函数的栈

使用min_stack存储当前最小值
时间O(1),空间O(N)

class MinStack:
    import math
    def __init__(self):
        """
        initialize your data structure here.
        """
        self.stack = []
        self.min_stack = [math.inf]

    def push(self, x: int) -> None:
        self.stack.append(x)
        self.min_stack.append(min(x,self.min_stack[-1]))

    def pop(self) -> None:
        if len(self.stack)==0:
            return None
        self.stack.pop()
        self.min_stack.pop()

    def top(self) -> int:
        return self.stack[-1]

    def min(self) -> int:
        return self.min_stack[-1]

剑指 Offer 31. 栈的压入、弹出序列 中等

使用一个栈模拟入栈出栈操作,如果栈顶是出栈元素,出栈并且出栈元素向后移动,否则入栈
时空O(N)

    def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
        stack = []
        i=0
        for num in pushed:
            stack.append(num)
            while stack and stack[-1]==popped[i]:
                stack.pop()
                i+=1
        return not stack

剑指 Offer 32 - I. 从上到下打印二叉树 中等

层次遍历
时间O(N) 空间O(N)

    def levelOrder(self, root: TreeNode) -> List[int]:
        if not root:return []
        queue = collections.deque([root])
        res = []
        while queue:
            node = queue.popleft()
            res.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        return res

剑指 Offer 32 - III. 从上到下打印二叉树 III 中等

层次遍历+记录行数
时间O(N) 空间O(N)

    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        if not root:return []
        h=0
        queue = collections.deque([root])
        res = []
        while queue:
            layer = []
            lenth = len(queue)
            for i in range(lenth):
                node = queue.popleft()
                layer.append(node.val)
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
            # 奇数层,逆序
            if h&1:
                res.append(layer[::-1])
            else:
                res.append(layer)
            h+=1
        return res

剑指 Offer 33. 二叉搜索树的后序遍历序列 中等

二叉搜索树:左<中<右,左子树始终小于root和右子树。整个树是float(‘inf’)的左子树,现将右子树以及根入栈,遇到左子树时,出栈并更新当前root。若左子树存在比root大的数,直接False
时空O(N)

    def verifyPostorder(self, postorder: List[int]) -> bool:
        root,stack=float('inf'),[]
        for i in range(len(postorder)-1,-1,-1):
            if postorder[i]>root:return False
            while stack and stack[-1]>postorder[i]:
                root = stack.pop()
            stack.append(postorder[i])
        return True

剑指 Offer 34. 二叉树中和为某一值的路径 中等

中序遍历,每次判断当前节点是否为叶子结点以及sum==0,成功记录路径;遍历其左右节点。遍历完成当前节点,路径删除
时空O(N)

    def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
        res,path = [],[]
        def dfs(node,s):
            if not node:return
            path.append(node.val)
            s -= node.val
            if s==0 and node.left==None and node.right==None:
                res.append(list(path))
            dfs(node.left,s)
            dfs(node.right,s)
            path.pop()
        dfs(root,sum)
        return res

剑指 Offer 35. 复杂链表的复制 中等

每个节点复制之后放在当前节点之后,复制random,最后拆分链表
时间O(N) 空间O(1)

    def copyRandomList(self, head: 'Node') -> 'Node':
        if not head: return head
        cur = head
        while cur:
            new_node = Node(cur.val,None,None)
            new_node.next = cur.next
            cur.next=new_node

            cur = new_node.next

        cur = head
        while cur:
            cur.next.random = cur.random.next if cur.random else None
            cur = cur.next.next

        old, new = head, head.next
        new_head = head.next
        while old:
            old.next = old.next.next
            new.next = new.next.next if new.next else None
            old, new = old.next, new.next
        return new_head

剑指 Offer 36. 二叉搜索树与双向链表 中等

Left:prev right:next 中序遍历最后是尾结点,记录头结点head以及前节点prev,更改头结点与尾结点的l,r,对节点node处理prev.right=node;node.left=prev 。解法1:使用递归。解法2:使用堆栈
时间O(N) 空间O(N)

	def treeToDoublyList(self, root: 'Node') -> 'Node':
		def dfs(node):
            if not node:return 
            dfs(node.left)
            if self.pre==None:
                # 头结点
                self.head = node
            else:
                node.left = self.pre
                self.pre.right = node
            self.pre = node
            dfs(node.right)


        if not root:return 
        self.pre = None
        dfs(root)
        self.head.left = self.pre
        self.pre.right = self.head
        return self.head
    def treeToDoublyList(self, root: 'Node') -> 'Node':
        if not root:return 
        stack = []
        pre=None
        while stack or root:
            if root:
                stack.append(root)
                root = root.left
            else:
                node = stack.pop()
                if pre==None:
                    head = node
                else:
                    pre.right = node
                node.left = pre
                pre = node
                root = node.right
        head.left=pre
        pre.right=head
        return head

剑指 Offer 37. 序列化二叉树 困难

层次遍历:正向反向
时间O(N) 空间O(N)

    def serialize(self, root):
        """Encodes a tree to a single string.
        
        :type root: TreeNode
        :rtype: str
        """
        if not root: return "[]"
        res=[]
        queue = collections.deque([root])
        while queue:
            node = queue.popleft()
            if node:
                res.append(str(node.val))
                queue.append(node.left)
                queue.append(node.right)
            else:
                res.append('null')
        return "["+','.join(res)+"]"
        

    def deserialize(self, data):
        """Decodes your encoded data to tree.
        
        :type data: str
        :rtype: TreeNode
        """
        if data=="[]":return None
        vals = data[1:-1].split(',')
        i=1
        root = TreeNode(vals[0])
        queue = collections.deque([root])
        while queue:
            node = queue.popleft()
            if vals[i]!='null':
                node.left = TreeNode(vals[i])
                queue.append(node.left)
            i+=1
            if vals[i]!='null':
                node.right = TreeNode(vals[i])
                queue.append(node.right)
            i+=1
        return root

剑指 Offer 38. 字符串的排列 中等

全排列,固定第x位,固定x+1位,一直到最后一位存储c.第x位[x,n-1],第x+1范围是[x+1,n-1]
时间O(N!) 空间O(N2)

    def permutation(self, s: str) -> List[str]:
        c, n, res = list(s), len(s), []
        def dfs(x):
            if x==n-1:
                res.append("".join(c))
            dic = set()
            for i in range(x,n):
                if c[i] in dic:continue
                dic.add(c[i])
                c[i], c[x] = c[x], c[i]
                dfs(x+1)
                c[i], c[x] = c[x], c[i]
        dfs(0)
        return res

剑指 Offer 39. 数组中出现次数超过一半的数字 简单

解法1:需要的数字出现次数多于一半 那么排序后必定在中间
时间O(NlogN) 空间O(1)

    def majorityElement(self, nums: List[int]) -> int:
        nums.sort()
        return nums[len(nums)//2]

解法2:摩尔投票,由于众数出现的次数超过数组长度的一半;若记 众数 的票数为 +1+1 ,非众数 的票数为 -1−1 ,则一定有所有数字的 票数和 > 0>0
时间O(N) 空间O(1)

    def majorityElement(self, nums: List[int]) -> int:
		votes=0
        for num in nums:
            if votes==0: x = num
            votes += 1 if num==x else -1
        count=0
        for num in nums:
            if num==x: count+=1
        return x if count>len(nums)//2 else 0

剑指 Offer 40. 最小的k个数 简单

解法:快速选择,类似于快速排序的分治法
时间O(N),最坏会退化到O(n^2) 空间O(N)

     def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        # 快速选择
        n = len(arr)
        if n==0 or k>n: return 
        if k==0: return []
        
        def quickselect(l,r):
            pivot = arr[l]
            low = l+1
            high = r
            while True:
                while low<=high and  arr[low]<=pivot:
                    low+=1
                while high>=low and arr[high]>=pivot:
                    high-=1
                if low>high:
                    break
                arr[low], arr[high] = arr[high], arr[low]
            arr[l], arr[high]=arr[high], arr[l]
            return high
        l, r = 0, len(arr)-1
        while l<=r:
            m = quickselect(l,r)
            if m==k:
                break
            elif m<k:
                l = m+1
            else:
                r = m-1
        return arr[:k]

剑指 Offer 41. 数据流中的中位数 困难

解法:插入数字的排序,使用A是小顶堆,保存较大的那一半,如果是奇数,多保存一个元素,B是大顶堆,保存较小的那一半。向A中插入元素,先在B里比较,然后较小一半的最大值放在A堆
插入时间O(logN) ,访问时间O(1)空间O(N)

class MedianFinder:

    def __init__(self):
        """
        initialize your data structure here.
        """
        self.A = []
        self.B = []

    def addNum(self, num: int) -> None:
        if len(self.A)==len(self.B):
            heappush(self.B,-num)
            heappush(self.A,-heappop(self.B))
        else:
            heappush(self.A,num)
            heappush(self.B,-heappop(self.A))

    def findMedian(self) -> float:
        return self.A[0]  if len(self.A)!=len(self.B)  else (self.A[0]-self.B[0])/2.0

剑指 Offer 42. 连续子数组的最大和 简单

解法:对于第i个数字要么自成一派,要么和前面的子数组和合并 dp[i] = Math.max(nums[i], nums[i] + dp[i - 1]);
插入时间O(N)空间O(1)

    def maxSubArray(self, nums: List[int]) -> int:
        res= nums[0]
        for i in range(1,len(nums)):
            if nums[i-1]>0:
                nums[i] += nums[i-1]
            res = max(res,nums[i])
        return res

剑指 Offer 45. 把数组排成最小的数 中等

解法:字符串的拼接规则,若x+y>y+x:则x>y;
插入时间O(NlogN)空间O(N)

    def minNumber(self, nums: List[int]) -> str:
        def sort_rule(x,y):
            a, b = x+y, y+x
            if a>b: return 1
            elif b>a: return -1
            else: return 0
        strs = [str(num) for num in nums]
        strs.sort(key = functools.cmp_to_key(sort_rule))
        return "".join(strs)

剑指 Offer 46. 把数字翻译成字符串 中等

解法:翻译方法增加点在于10-25之间两种状态,dp[i]:0-i之间的翻译方法。如果num_l[i-1], num_l[i]处于10-25,那当前的翻译方式由dp[i-1]和dp[i-2]得到。举例:122 指向最后一个2时,dp[2] = dp1+dp0
时间O(logN)时间复杂度是字符串的长度 空间O(logN)

    def translateNum(self, num: int) -> int:       
        num_l = list(map(int,str(num)))
        if len(num_l)<=1:
            return len(num_l)
        def helper(a: int, b:int) -> bool:
            x = a*10+b
            return x>=10 and x<=25
        # dp
        dp = [0 for _ in num_l]
        # base case
        dp[0] = 1
        dp[1] = 2 if helper(num_l[0],num_l[1]) else 1
        for i in range(2,len(num_l)):
            if helper(num_l[i-1], num_l[i]):
                dp[i] = dp[i-1] + dp[i-2]
            else:
                dp[i] = dp[i-1]
        return dp[-1]

剑指 Offer 46. 把数字翻译成字符串 中等

解法1:翻译方法增加点在于10-25之间两种状态,dp[i]:0-i之间的翻译方法。如果num_l[i-1], num_l[i]处于10-25,那当前的翻译方式由dp[i-1]和dp[i-2]得到。举例:122 指向最后一个2时,dp[2] = dp1+dp0
解法2:每次判断最后两位,如果最后两位处于10-25则有两个选择num/10+num/100进行递归;如果不是那只有一种选择num/10
时间O(N) 时间复杂度是字符串的长度 空间O(N)

    def translateNum(self, num: int) -> int:       
        num_l = list(map(int,str(num)))
        if len(num_l)<=1:
            return len(num_l)
        def helper(a: int, b:int) -> bool:
            x = a*10+b
            return x>=10 and x<=25
        # dp
        dp = [0 for _ in num_l]
        # base case
        dp[0] = 1
        dp[1] = 2 if helper(num_l[0],num_l[1]) else 1
        for i in range(2,len(num_l)):
            if helper(num_l[i-1], num_l[i]):
                dp[i] = dp[i-1] + dp[i-2]
            else:
                dp[i] = dp[i-1]
        return dp[-1]
    public int translateNum(int num) {
        if (num<=9) {return 1;}
        //获取输入数字的余数,然后递归的计算翻译方法
        int ba = num%100;
        //如果小于等于9或者大于等于26的时候,余数不能按照2位数字组合,比如56,只能拆分为5和6;反例25,可以拆分为2和5,也可以作为25一个整体进行翻译。
        if (ba<=9||ba>=26) {return translateNum(num/10);}
        // ba=[10, 25]时,既可以当做一个字母,也可以当做两个字母
        else  {return translateNum(num/10)+translateNum(num/100);}
    }
}

剑指 Offer 47. 礼物的最大价值 中等

** 解法:dp[i][j]=max(dp[i -1][j], dp[i][j - 1]) + grid[i - 1][j - 1],注意边界判断或是空间换时间减少边界判断 **
时间O(MN) 空间O(MN)

    def maxValue(self, grid: List[List[int]]) -> int:
        row, col = len(grid),len(grid[0])
        dp = [[0 for j in range(col+1)] for i in range(row+1)]
        for i in range(1,row+1):
            for j in range(1,col+1):
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1];
        return dp[row][col]

剑指 Offer 48. 最长不含重复字符的子字符串 中等

解法:滑动窗口
时间O(N) 空间O(N)

    def lengthOfLongestSubstring(self, s: str) -> int:
        if len(s)<=1:
            return len(s)
        windows = {}
        left, right, valid= 0, 0, 0
        lenth = 0
        while right<len(s):
            c = s[right]
            right+=1
            windows[c] = windows.get(c, 0) + 1
            # 判断左侧窗口是否要收缩:出现重复字符,left+1
            while windows[c]>1:
                d = s[left]
                left+=1
                # 进行窗口内数据的一系列更新
                windows[d] -= 1
            lenth = max(lenth,right-left)
        return lenth

剑指 Offer 49. 丑数 简单

解法:任意一个丑数都是由小于它的某一个丑数*2,3或者5得到的,所有丑数的排列,必定就是3个数组的合并结果然后去重得到的
时间O(N) 空间O(N)

    def nthUglyNumber(self, n: int) -> int:
        dp, a,b,c=[1]*n,0,0,0
        for i in range(1,n):
            n2,n3,n5 = dp[a]*2,dp[b]*3,dp[c]*5
            dp[i] = min(n2,n3,n5)
            if dp[i]==n2:a+=1
            if dp[i]==n3:b+=1
            if dp[i]==n5:c+=1
        return dp[n-1]

剑指 Offer 50. 第一个只出现一次的字符 简单

解法:Python 3.6 后,默认字典就是有序的,因此无需使用 OrderedDict()
时间O(N) 空间O(N)

    def firstUniqChar(self, s: str) -> str:
        dic = {}
        for c in s:
            dic[c] = not c in dic
        for c in s:
            if dic[c]: return c
        return ' '

剑指 Offer 51. 数组中的逆序对 困难

解法:如果左边部分的数组第l个元素大于右边第r个元素,那么左边数组将有(n1-l)个都大于右边第r个元素
时间O(NlogN) 空间O(N)

    def merge(self,arr1,arr2) -> List[int]:
        l,r=0,0
        n1,n2=len(arr1),len(arr2)
        res = []
        while l<n1 and r<n2:
            if arr1[l]>arr2[r]:
                self.ans+=(n1-l)
                res.append(arr2[r])
                r+=1
            else:
                res.append(arr1[l])
                l+=1
        return res+arr1[l:]+arr2[r:]

    def merge_sort(self,arr: List[int]) -> List[int]:
        n = len(arr)
        if n<=1: return arr
        left = self.merge_sort(arr[:n//2])
        right = self.merge_sort(arr[n//2:])
        return self.merge(left,right)

    def reversePairs(self, nums: List[int]) -> int:
        self.ans = 0
        self.merge_sort(nums)
        return self.ans

剑指 Offer 52. 两个链表的第一个公共节点 简单
解法:两个链表长度分别为L1+C、L2+C, C为公共部分的长度. 第一个人走了L1+C步后,回到第二个人起点走L2步;第2个人走了L2+C步后,回到第一个人起点走L1步。 当两个人走的步数都为L1+L2+C时就两个家伙就相爱了
时间O(L1+l2+c)空间O(1)

    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        p, q = headA, headB
        while p!=q:
            p = headB if p==None else p.next
            q = headA if q==None else q.next
        return p

剑指 Offer 56 - I. 数组中数字出现的次数 中等
解法:首先获取所有数字的xor,不相同的两个数字的最低位保存在diff,与diff一致类型保存在b,不同的保存在a
时间空间O(1)

    def singleNumbers(self, nums: List[int]) -> List[int]:
        res = 0
        for num in nums:
            res ^= num
        diff = res&(-res)
        
        a, b = 0, 0
        for num in nums:
            # 与差距最低位不一致
            if num&diff:
                a ^= num
            # 与差距最低位一致
            else:
                b ^= num
        return [a,b]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值