练习题 Day7

剑指 Offer 59 - I. 滑动窗口的最大值

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

注意不用切片和max——每次取max无法降低重复操作

用一个队列维护最大值的index 最大长度为3,单调递减,队首为当前窗口的最大值

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7] 
解释: 
nums = [1, 3, -1, 6, -3, 3, 1, 2, 5, 3, 6, 7]
k = 3
queue, res = [], []
for i in range(len(nums)):
    while queue and (nums[i] > nums[queue[-1]]):  
        queue.pop()   # 如果nums[i]比之前的大,就把队列里小的pop掉
    queue.append(i)  # 加入queue
    if i - queue[0] >= k:      # 如果最大值不在窗口内了,则pop掉
        queue.pop(0)
    if i >= k-1:  
        res.append(nums[queue[0]])  # 队首是当前窗口的最大值
print(res)
[3, 6, 6, 6, 3, 3, 5, 5, 6, 7]

剑指 Offer 61. 扑克牌中的顺子

从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王(0)可以看成任意数字。

技巧点:没有重复 & 最大值-最小值 <= 4,就是顺子
[2,0,0,0,0]
[0,0,0,0,0]
[1,2,5,0,0]

[1,2,2,3,0] 有重复
[2,3,4,0,7] 极值>4

所以首先进行一步检查重复,如果出现非0的重复直接 return False,然后再检查极差

def isStraight(nums):
    d = dict()
    for num in nums:
        if num in d.keys() and num!=0:
            return False
        elif num!=0:
            d[num] = 1
    newnums = list(d.keys())
    if len(newnums) > 1 and  (max(newnums)-min(newnums) > 4):
        return False
    else:
        return True
isStraight([10,4,5,0,0])
False

剑指 Offer 62. 圆圈中最后剩下的数字

0,1,,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

  1. 基础版的可以理解为一个队列问题,每次把队首的移到队尾,如果移动了m次,pop就不再添加到队尾了
  2. 复杂情况下这样就超时了,结合数学知识理解:
    • 每次pop掉的索引是(i + m - 1) % len(a)
    • 递推公式 f(N,M)=(f(N−1,M)+M)%N
# 简单版——一个队列问题——超时
def lastRemaining(n, m):
    queue = list(range(n))
    while len(queue) > 1:
        for j in range(m):
            cur = queue.pop(0)
            if j < m-1:
                queue.append(cur)
        print(queue)
    return queue[0]
print(lastRemaining(5, 3))


# 结合数学逻辑,每次pop掉的索引是(i + m - 1) % len(a)——可以理解
def lastRemaining(n, m):
    queue = list(range(n))
    i = 0
    while len(queue) > 1:
        i = ( i + m - 1 ) % len(queue)
        queue.pop(i)
    return queue[0]
print(lastRemaining(5, 3))
print(lastRemaining(500, 300))


# 最佳解答——递推公式——其实使用了数学逻辑
def lastRemaining(n, m):
    # 递推公式 f(N,M)=(f(N−1,M)+M)%N
    # // f(n,m) = (f(n-1,m)+m) % n
    # // 我们把数字看做人,报到 m 的人,直接干掉
    # // n 代表的是当前人数,m 代表的是报数
    # // 假设一共有 11 人,最后那个胜利的人下标是 6
    # // 在下一轮 10 人的时候,胜利者的编号往前移动三格(假设m=3)
    # // 那假设我们有 10 人,胜利者下标为3,到 11 人的时候胜利者下标是 6
    # // 所以公式为 fn = (fn-1 + m) % n
    # // 如果只有一个人,那么下标为0
    last = 0
    # 往回推 推出最出最开始人数是n的时候last的index 
    for i in range(2, n+1):
        last = (last+m)%i
    return last
print(lastRemaining(500, 300))
[3, 4, 0, 1]
[1, 3, 4]
[1, 3]
[3]
3
3
335
335

剑指 Offer 63. 股票的最大利润

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

def maxprofit(prices):
    profit = 0
    minprice = prices[0]
    if len(prices)<2:
        return profit
    for i in range(1, len(prices)):
        if prices[i] < minprice:
            minprice = prices[i]
        elif prices[i] > minprice  + profit:
            profit = prices[i] - minprice
    return profit
maxprofit([7,1,5,3,6,4]),maxprofit([7,6,4,3,1])
(5, 0)

剑指 Offer 68 - 二叉树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

# 定义测试用例
t1 = TreeNode(5)
t2 = TreeNode(3)
t3 = TreeNode(9)
t1.left = t2; t1.right = t3
t4 = TreeNode(1)
t5 = TreeNode(4)
t6 = TreeNode(12)
t7 = TreeNode(18)
t2.left = t4; t2.right = t5; t3.right = t6;t6.right = t7
class Solution:
    # 1.如果是二叉搜索树,非常直接,题目给出pq一定有公共祖先
    def lowestCommonAncestor(self, root, p, q):
        if root.val > max(p.val, q.val):
            return self.lowestCommonAncestor(root.left, p, q)
        if root.val < min(p.val, q.val):
            return self.lowestCommonAncestor(root.right, p, q)
        else:
            return root
def lowestCommonAncestor(root, p, q):
    # 2.不是二叉搜索树,递归的时候有技巧
    if not root:
        return None
    if root == p or root == q:
        return root
    else:
        l1 = lowestCommonAncestor(root.left, p, q)
        l2 =  lowestCommonAncestor(root.right, p, q)
    if l1 and l2:
        return root
    elif l1 or l2:
        return l1 or l2
    else:
        return None
print(lowestCommonAncestor(t1,t4,t7).val)
5

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

每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序

[
 [1,   4,   7,   11,  15],
 [2,   5,   8,  12,  19],
 [3,   6,   9,  16,  22],
 [10,  13,  14, 17,  24],
 [18,  21,  23, 26, 30],
 [25,  28, 31, 33, 40]
]

逐行二分法,其实效率也还不错

注意点:

  • 如果行长度小于列长度——转置,保证遍历行min(m,n)次,每行二分查找时间复杂度为o(log(max(m,n))),总复杂度O(min(m,n)*log(max(m,n)))
class Solution:
    def findNumberIn2DArray(self, matrix, target):
        # 定义二分查找
        def binarySearch1(alist,item):
            if not alist:
                return False
            left, right = 0, len(alist) - 1
            while left <= right:
                if item > alist[right] or item < alist[left]:
                    return False
                mid = (right + left)//2
                if alist[mid] == item:
                    return True
                if alist[mid] < item:
                    left = mid+1
                if alist[mid] > item:
                    right = mid - 1
            return False
        
        # 如果是空,直接F
        if not len(matrix) or not len(matrix[0]):
            return False
        # 如果行长<列长——转置,从而实现只遍历min(m,n)次
        if len(matrix[0]) < len(matrix):  
            matrix = zip(*matrix)
        # 开始逐行二分:
        for row_list in matrix:
            if binarySearch1(row_list, target):  # 找到了
                return True
        return False

s7 = Solution()
matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30],[2,28,31,33,40]]
s7.findNumberIn2DArray(matrix, 17)
True

转化为图问题

时间复杂度 O(M+N)

这里的旋转之后用二叉搜索树,从左上角或者右下角开始搜索的想法,直呼好家伙!!!神仙思路

def findTarget(matrix, target):
    # 从左下角开始
    i, j = len(matrix)-1, 0

    while i >=0 and j < len(matrix[0]):
        cur = matrix[i][j]
        if cur < target:
            j += 1
        if cur > target:
            i -= 1
        if cur == target:
            return True
    return False  # 包括没找到、空值、等等各种情形

findTarget(matrix, 16)
True

剑指 Offer 07. 重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

前序遍历 preorder = [3,9,4,1,6,20,15,8,11,7]
中序遍历 inorder = [1,4,9,6,3,8,15,11,20,7]

自己的逻辑可以看出,这是一个递归问题

# Definition for a binary tree node.
class TreeNode:
     def __init__(self, x):
            self.val = x
            self.left = None
            self.right = None

def rebuild(preorder, inorder):
    n = len(preorder)
    if not n:
        return None
    root = TreeNode(preorder[0])
    i = inorder.index(preorder[0])
    root.left = rebuild(preorder[1:i+1],  inorder[:i])
    root.right = rebuild( preorder[i+1:], inorder[i+1:])
    return root

preorder = [3,9,4,1,6,20,15,8,11,7]
inorder = [1,4,9,6,3,8,15,11,20,7]
t1 = rebuild(preorder, inorder)
t1.val
3
# 将本层全部节点打印到一行,并将下一层全部节点加入队列,以此类推,即可分为多行打印。
def print_row(root):
    row_list = [root]
    res = []
    while row_list:
        row_res = []      
        for i in range(len(row_list)):
            cur = row_list.pop(0)
            row_res.append(cur.val)
            if cur.left:
                row_list.append(cur.left)
            if cur.right:
                row_list.append(cur.right)
        res.append(row_res)
    return res

print_row(t1)
[[3], [9, 20], [4, 6, 15, 7], [1, 8, 11]]

剑指 Offer 12. 矩阵中的路径

  • 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
  • 输出:true
[["A","B","C","E"],
["S","F","C","S"],
["A","D","E","E"]]

感觉是一个图的问题——DFS问题,类似骑士周游

  • 我的想法写的太复杂啦:
# 我的解答
def findPath(board, word):
    words = list(word)

    def dfs(location, k):
        if k >= len(words) or not board[location[0]][location[1]] or words[k] != board[location[0]][location[1]]:
            return False
        if k == len(words) - 1: 
            print(location)
            return True
        print(location)
        board[location[0]][location[1]] = None
        nexts = next_step(board, location)
        if not nexts:
            return False
        for _ in nexts:
            if dfs(_, k+1):
                return True
        board[location[0]][location[1]] = words[k]
        return False

    def next_step(matrix, location):
        res = []
        if not len(matrix) or not len(matrix[0]):
            return res
        m = len(matrix) - 1
        n = len(matrix[0]) - 1
        cur_x, cur_y = location[0], location[1]
        if cur_x >= 1:
            res.append([cur_x - 1, cur_y])
        if cur_y >= 1:
            res.append([cur_x, cur_y - 1])
        if cur_x <= m - 1:
            res.append([cur_x +1, cur_y])
        if cur_y <= n - 1:
            res.append([cur_x, cur_y + 1])
        return res

    def getlocation(matrix, word):
        res = []
        if not len(matrix) or not len(matrix[0]):
            return res
        for i in range(len(matrix)):
            for j in range(len(matrix[0])):
                if matrix[i][j] == word:
                    res.append([i, j])     
        return res

    start = getlocation(board, words[0])
    
    for s in start:
        if dfs(s, 0):
            return True
    return False


board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]]
findPath(board, 'ABCCEDFSA')
[0, 0]
[0, 1]
[0, 2]
[1, 2]
[2, 2]
[2, 1]
[1, 1]
[1, 0]
[2, 0]





True

标准答案真的清晰简单,好好学好好看:

  • 递归的时候一定想清楚函数()里面是什么更加简洁清晰,多想想在下手敲
  • 可以合并的内容尽量合并起来不要再写上面这样又臭又长的code啦
def exist(board, word):
    def dfs(i, j, k):
        # 结束条件
        if not 0<=i<len(board) or not 0<=j<len(board[0]) or board[i][j]!=word[k]:
            return False
        if k==len(word)-1:
            return True
        board[i][j] = None
        # 递归
        return 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)
    
    for i in range(len(board)):
        for j in range(len(board[0])):
            if dfs(i, j, 0):
                return True
    return False

board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]]
exist(board, 'ABCCEDFSA')
True