剑指offer在线编程(08-15)【10】

本文深入解析了二叉搜索树的第k个结点、数据流中的中位数、滑动窗口的最大值、矩阵中的路径以及机器人的运动范围等经典算法问题,提供了详细的解题思路与代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Date: 2019-08-15

1.  二叉搜索树的第k个结点   (考察知识点;栈和树)

题目描述

给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8)    中,按结点数值大小顺序第三小结点的值为4。

分析: 对于二叉搜索树(Binary Search tree),其有一个特性:其中序遍历结果就是一个非递减的序列。因此需要返回按节点数值大小顺序的第k小节点即是中序遍历结果中的第k个节点。所以实质上就是二叉搜索树的中序遍历结果。(在这里写中序遍历的时候,一般采用一个子函数直接返回中序遍历结果,我直接在返回结果函数中写中序遍历一直有问题,不知道是哪里的BUG!,下次记得避免就行!!)

中序遍历:左根右

第一个版本:注意的点在于:左右子树递归调用时,返回的是一个列表,所以采用extend函数,而不是append。

第二个版本:无需进行左右子树的append,直接递归到每个节点! (时间复杂度更小一些)

 

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    # 返回对应节点TreeNode
    def KthNode(self, pRoot, k):
        # write code here
        if not pRoot or not k:
            return None
        res = self.isorder(pRoot)
        return res[k-1] if 0<k<=len(res) else None
    
    def isorder(self, pRoot):
        res = []
        if not pRoot:
            return None
        if pRoot.left:
            res.extend(self.isorder(pRoot.left))  # 这里改成append,会出现错误,因为这里递归调用后是一个list结果
        res.append(pRoot) 
        if pRoot.right:
            res.extend(self.isorder(pRoot.right))  # 这里改成append,会出现错误,因为这里递归调用后是一个list结果
        return res
# -*- coding:utf-8 -*-  # version2 
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    # 返回对应节点TreeNode
    def KthNode(self, pRoot, k):
        self.res=[]
        self.dfs(pRoot)
        return self.res[k-1] if 0<k<=len(self.res) else None
    def dfs(self,root):
        if not root:return
        self.dfs(root.left)
        self.res.append(root)
        self.dfs(root.right)

2.  数据流中的中位数   ()

题目描述

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

分析:该题我的第一想法就是直接最数据求长度,然后根据长度计算是奇数个数的序列,还是偶数个数的序列!

注意点:在类下初始化了一个data列表,用作存数据流的实时更新,增添数据;在获取中位数的函数的形参,要把data写成形参传入(觉得应该是系统的问题吧???);分为奇数偶数情况的讨论!

# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.data=[]
    def Insert(self, num):
        # write code here
        self.data.append(num)
        self.data.sort()
    def GetMedian(self,data):
        # write code here
        lens = len(self.data)
        if lens %2 == 1:  # 奇数长度的序列
            return self.data[int(lens//2)]
        else:   # 偶数长度的序列
            return (self.data[lens//2] + self.data[lens//2-1]) / 2.0

牛客上的其他思路:供参考,没实现

1) 

思路:构建一棵"平衡二叉搜索树 "。

每个结点左子树均是小于等于其value的值,右子树均大于等于value值。每个子树均按其 “结点数” 调节平衡。 这样根节点一定是中间值中的一个。若结点数为奇数,则返回根节点的值;若结点个数为偶数,则再从根结点左子数或右子数中个数较多的子树中选出最大或最小值既可。

 


2)

参考:https://leetcode.com/problems/find-median-from-data-stream/discuss/74062/Short-simple-JavaC++Python-O(log-n)-+-O(1)
使用两个堆:

  • 大根堆:large保存大的半数的数据
  • 小根堆:small保存小的半数的数据
    获取中位数的时间复杂度为O(1),插入一个数据的时间复杂度为O(log(n))

技巧:

  1. 构造小根堆时,对数组元素取负值来构造
  2. heappush(heap,data),将deat放入大根堆中
  3. heapposh(heap),弹出heap中的最小值
  4. heappushpop(heap,data),将data放入大根堆中,再弹出堆heap的最小值
# -*- coding:utf-8 -*-  # 时间复杂度比我的list版本要写小一些
from heapq import *
class Solution: 
    def __init__(self):
        self.heaps = [], [] 
    def Insert(self, num):
        # write code here
        small, large = self.heaps
        heappush(small, -heappushpop(large, num)) #将num放入大根堆,并弹出大根堆的最小值,取反,放入大根堆small
        if len(large) < len(small):
            heappush(large, -heappop(small)) #弹出small中最小的值,取反,即最大的值,放入large 
    def GetMedian(self,ss):
        # write code here
        small,large = self.heaps
        if len(large) > len(small):
            return float(large[0])
        return (large[0] - small[0]) /2.0

3.  滑动窗口的最大值   (考察知识点:数组+队列)

题目描述

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

分析:该题我自己的思路s是:根据num 和size这两个形参,我们可以得到有len(num)-size+1个滑动窗口情况,最后需要返回每个窗口的最大值数组,则可以进行len(num)-size+1次遍历,获取相应的滑动窗口,然后获取该窗口最大值进行append到结果数组中!

# -*- coding:utf-8 -*-  利用python性质每次求固定size的最大值,时间复杂度O(n*size)
class Solution:
    def maxInWindows(self, num, size):
        # write code here
        if not num or not size:
            return []
        res = []
        for i in range(len(num)-size+1):
            res.append(max(num[i:i+size]))
        return res

看到有人有双向队列queue来实现的:(双向队列,queue存入num的位置(每个窗口符合条件的index集合),时间复杂度O(n) )

# -*- coding:utf-8 -*-  # 时间消耗在这些实例中时间,两个版本的差异不大,可能是由于示例小吧
class Solution: 
    def maxInWindows(self, num, size):
        # write code here
        if not num or not size:
            return []
        queue,res,i = [],[],0
        while size>0 and i<len(num):
            if len(queue)>0 and i-size+1 > queue[0]:#若最大值queue[0]位置过期 则弹出
                queue.pop(0)    
            while len(queue)>0 and num[queue[-1]]<num[i]: #每次弹出所有比num[i]的数字
                queue.pop()
            queue.append(i)
            if i>=size-1:
                res.append(num[queue[0]])
            i += 1
        return res

还有用最大堆来实现的,这里暂不阐述

4.  矩阵中的路径   (迷宫/N皇后问题的类似) (考察知识点:回溯法)  (Dif   多看 多看 多看!!!)

题目描述

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 a b c e s f c s a d e e 矩阵中包含一条字符串"bccced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

分析:首先找到字符串路径中第一个点在矩阵中的位置,然后寻找路径(建立find子函数),在子函数部分,首先判断路径知否走完,如果没有,则讨论右左上下的元素是否是当前字符串的下一字符,如果某一个是则相应移动并且path[1:],剩余待寻找的路径-1;如果都不满足,要么回溯然后跳到下一个方位进行判断,如果回溯到顶并且四个方位都行不通,则False.

# -*- coding:utf-8 -*-
class Solution:
    def hasPath(self, matrix, rows, cols, path):
        for i in range(rows):
            for j in range(cols):
                if matrix[i*cols+j]==path[0]: # 遍历矩阵,找到第一个和路径字符中第一个字符相等的i,j
                    if self.find(list(matrix),rows,cols,path[1:],i,j): # 从起点开始,上下左右一定能够吵到相应的路径,即返回True
                        return True
    def find(self,matrix,rows,cols,path,i,j):
        if not path:  #  在递归调用时,首先判断路径是否空了?注意:递归是path[1:],不断进行更新
            return True
        matrix[i*cols+j]='0'  # (下面会先一条路径去走,无路可走则返回False)
        if j+1<cols and matrix[i*cols+(j+1)]==path[0]:  # 如果右边可走,且相等,则向右移动一格
            return self.find(list(matrix),rows,cols,path[1:],i,j+1)
        elif j-1>=0 and matrix[i*cols+(j-1)]==path[0]:  # 如果左边可走,且相等,则向左移动一格
            return self.find(list(matrix),rows,cols,path[1:],i,j-1)
        if i+1<rows and matrix[(i+1)*cols+j]==path[0]:  # 如果下边可走,且相等,则向下移动一格
            return self.find(list(matrix),rows,cols,path[1:],i+1,j)
        if i-1>=0 and matrix[(i-1)*cols+j]==path[0]: # 如果上边可走,且相等,则向上移动一格
            return self.find(list(matrix),rows,cols,path[1:],i-1,j)
        else:
            return False

5.  机器人的运动范围  (考察知识点:数组)

题目描述

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

思路:将地图全部置1,遍历能够到达的点,将遍历的点置0并令计数+1.这个思路在找前后左右相连的点很有用

# -*- coding:utf-8 -*- 通过版本
class Solution:
    def __init__(self):
        self.count = 0
    def movingCount(self, threshold, rows, cols):
        # write code here
        arr = [[1 for i in range(cols)] for j in range(rows)]  # 先放一张全1矩阵,用于标记当前位置是否走过,0表示走过了该点
        self.findway(arr, 0, 0, threshold)  # 从0,0位置开始移动
        return self.count
    def findway(self, arr, i, j, k):
        if i < 0 or j < 0 or i >= len(arr) or j >= len(arr[0]):  # i,j不在矩阵范围内时,结束递归调用
            return
        tmpi = list(map(int, list(str(i))))  # 将i这一横坐标数字各个位数进行分割,然后求和得到横坐标的位数和。
        tmpj = list(map(int, list(str(j))))  # 将j这一纵坐标数字各个位数进行分割,然后求和得到纵坐标的位数之和
        if sum(tmpi) + sum(tmpj) > k or arr[i][j] != 1:  # 如果当前坐标的位数之和大于k,或者已经走过了,跳过该点,回退到上一点,寻找其他方向
            return
        arr[i][j] = 0  # 否则标记给点已走过,然后计数+1
        self.count += 1
        self.findway(arr, i + 1, j, k) # 然后从该点出发,进行上下左右移动的判断。
        self.findway(arr, i - 1, j, k)
        self.findway(arr, i, j + 1, k)
        self.findway(arr, i, j - 1, k)

以下的思路是一样的,实现上有点问题:

# -*- coding:utf-8 -*- 附上我的思路,后续在修改一下
class Solution:
    def movingCount(self, threshold, rows, cols):
        # write code here
        if not threshold:
            return 1
        if not rows or not cols:
            return 1
        i,j,step = 0,0,1
        return self.calstep(i, j, rows, cols, threshold, step)
    def calsum(self, i,j):
        return sum(list(int(_) for _ in str(i))) + sum(list(int(_) for _ in str(j)))
    def calstep(self, i, j, rows, cols, threshold, step):
        if j+1<cols and self.calsum(i,j+1)<=threshold:
            step += 1
            return self.calstep(i, j+1, rows, cols, threshold, step)
        elif j-1>=0 and self.calsum(i,j-1)<=threshold:
            step += 1
            return self.calstep(i, j-1, rows, cols, threshold, step)
        elif i+1<rows and self.calsum(i+1,j)<=threshold:
            step += 1
            return self.calstep(i+1, j, rows, cols, threshold, step)
        elif i-1>=0 and self.calsum(i-1,j)<=threshold:
            step += 1
            return self.calstep(i-1, j, rows, cols, threshold, step)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值