第4章 解决面试题的思路

参考:

  1. 所有offer题目的LeetCode链接及python实现
  2. github Target offer

4.2 画图让抽象问题形象化

面试题19:二叉树的镜像

题目:请完成一个函数,输入一个二叉树,该函数输出它的镜像。

  • 先前序遍历这棵树的每个结点,如果遍历到的结点有子结点,就交换它的两个子结点。
  • 当交换完所有非叶子结点的左右子结点之后,就得到了树的镜像。
# 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 invertTree(self, root):
        """
        :type root: TreeNode
        :rtype: TreeNode
        """
        ### 方法一:递归,最快
        # if not root:
        #     return root
        # root.left, root.right = root.right, root.left
        # if root.left:
        #     self.invertTree(root.left)
        # if root.right:
        #     self.invertTree(root.right)
        # return root
        ### 方法二:简易递归
        # if root:
        #     root.left, root.right = self.invertTree(root.right), self.invertTree(root.left)
        # return root
        ### 方法三:使用stack实现, 最慢
        stack = [root]
        while stack:
            r = stack.pop()
            if r:
                r.left, r.right = r.right, r.left
                stack.extend([r.left, r.right])
        return root

面试题20:顺时针打印矩阵

题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
例子

思路梳理

  • 从外圈到内圈的顺序依次打印;
  • 打印每一圈时,首先需要判断每一步开始坐标点是否满足小于行数的一半且小于列数的一半
    • 在矩阵中选取左上角为(start, start)的一圈作为我们分析的目标。
    • 循环继续的条件columns>startX×2 && rows>startY×2
  • 考虑如何打印一圈:
    • 把打印一圈分为四步:第一步从左到右打印一行,第二步从上到下打印一列,第三步从右到左打印一行,第四步从下到上打印一列。
    • 这四步的是否需要进行,取决于(start, start)与(endX,endY)的相对位置,判断后再执行。
    • 最后一圈中(如下图所示),可能出现仅能向右走一行,仅能向右走一行向下走一列,向右走一行向下走一列向左走一行,能走完整一圈,一共四种情况。其中只有能向右走一行必然发生,不必判断,剩余的都需要判断发生条件(根据本圈左上角起始坐标点与右下角终止坐标点的相对位置来判断)。
      打印最里面一圈
# -*- coding:utf-8 -*-
class Solution:
    def printMatrixInCircle(self, matrix, col, row, start):
        endX = col-1-start
        endY = row-1-start
        # 从左到右打印一行
        for i in range(start, endX+1):
            print(matrix[start][i])
        # 从上到下打印一行
        if start < endY:
            for j in range(start+1, endY+1):
                print(matrix[j][endX])
        # 从右到左打印一行
        if start < endX and start < endY:
            for k in range(endX-1, start-1, -1):
                print(matrix[endY][k])
        # 从下到上打印一行
        if start < endX and start < endY-1:
            for l in range(endY-1, start, -1):
                print(matrix[l][start])

    # matrix类型为二维列表,需要返回列表
    def printMatrix(self, matrix):
        # 矩阵为空
        if matrix == None:
            return
        row = len(matrix)
        col = len(matrix[0])
        start = 0
        while (row > start * 2) & (col > start * 2):
            self.printMatrixInCircle(matrix, col, row, start)
            start += 1

4.3 举例让抽象问题具体化

面试题21:包含min函数的栈

题目:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的min函数。在该栈中,调用min、push及pop的时间复杂度都是O(1)。

思路梳理

如果设置一个数值型变量保存最小值,则会出现问题:当该最小值出栈后,无法找到栈中剩余元素的次小元素;

  • 所以考虑构建一个辅助栈,依次将当前最小元素压入,最终最小元素会位于辅助栈中的栈顶,其余次小元素依次位于其他位置。
    注意:
# 必须加上“=”号,不然如果栈中压入过两次最小元素,弹出其中一个会导致辅助栈中的最小元素被弹出
        elif x <= self.minS[-1]:
class MinStack(object):
    def __init__(self):
        """
        initialize your data structure here.
        """
        self.val = []
        self.minS = []

    def push(self, x):
        """
        :type x: int
        :rtype: None
        """
        self.val.append(x)
        if not len(self.minS):
            self.minS.append(x)
        # 必须加上“=”号,不然如果栈中压入过两次最小元素,弹出其中一个会导致辅助栈中的最小元素被弹出
        elif x <= self.minS[-1]:
            self.minS.append(x)

    def pop(self):
        """
        :rtype: None
        """
            
        if len(self.minS) and self.val[-1] == self.minS[-1]:
            self.minS.pop()
        return self.val.pop()

    def top(self):
        """
        :rtype: int
        """
        return self.val[-1]

    def getMin(self):
        """
        :rtype: int
        """
        return self.minS[-1]

面试题22:栈的压入、弹出序列

题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列 1、2、3、4、5 是某栈的压栈序列,序列 4、5、3、2、1 是该压栈序列对应的一个弹出序列,但 4、3、5、1、2 就不可能是该压栈序列的弹出序列。

思路梳理

  • 建立一个辅助栈,把push序列的数字依次压入辅助栈;
  • 每次压入后,比较辅助栈的栈顶元素和pop序列的首元素是否相等,
    • 相等的话就推出pop序列的首元素和辅助栈的栈顶元素;
    • 若最后** pop序列为空**,则push序列可以对应于pop序列,返回True;否则,返回False。
class Solution(object):
    def validateStackSequences(self, pushed, popped):
        """
        :type pushed: List[int]
        :type popped: List[int]
        :rtype: bool
        """
        stack = []
        
        for push in pushed: 
            stack.append(push)           
            while stack and popped and stack[-1] == popped[0]:
                stack.pop()
                del popped[0]
                
        return popped==[]

面试题23:从上往下打印二叉树

题目:从上往下打印出二叉树的每个结点,同一层的结点按照从左到右的顺序打印。例如输入图4.5中的二叉树,则依次打印出8、6、10、5、7、9、11。
题目

思路梳理

按层遍历树的节点,本质上来说就是广度优先遍历二叉树,由根->儿子->孙子的顺序进行访问,符合“先入先出”的原则,所以采用队列的数据结构进行存储。

不管是广度优先遍历一个有向图还是一棵树,都要用到队列

  • 第一步我们把起始结点(对树而言是根结点)放入队列中。
  • 接下来每一次从队列的头部取出一个结点,遍历这个结点之后把从它能到达的结点(对树而言是子结点)都依次放入队列。
  • 重复这个遍历过程,直到队列中的结点全部被遍历为止。
class Solution:
    # 返回从上到下每个节点值列表,例:[1,2,3]
    def PrintFromTopToBottom(self, root):
        # write code here
        if not root:
            return []
        queue = [root]
        res = [root.val]
        while queue:
            r = queue.pop(0)
            if r.left:
                queue.append(r.left)
                res.extend([r.left.val])
            if r.right:
                queue.append(r.right)
                res.extend([r.right.val])
        return res

或者

class Solution:
    # 返回从上到下每个节点值列表,例:[1,2,3]
    def PrintFromTopToBottom(self, root):
        # write code here
        if not root:
            return []
        queue = [root]
        res = []
        while queue:
            r = queue.pop(0)
            res.extend([r.val])
            if r.left:
                queue.append(r.left)
            if r.right:
                queue.append(r.right)
        return res

面试题24:二叉搜索树的后序遍历序列

题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。

思路梳理

后序遍历数组中前面的数字可以分为两部分:

  • 第一部分是左子树结点的值,它们都比根结点的值
  • 第二部分是右子树结点的值,它们都比根结点的值
  1. 先找到根、左子树、右子树的节点列表;
  2. 根据规则得到了左子树,所以无需判断左边是否合理;
  3. 判断右边元素是否合法;
  4. 递归调用方法,判断左右子树是否合法。
  5. 返回左右子树结果与操作。
class Solution:
    def VerifySquenceOfBST(self, sequence):
    	# 判断特殊无效输入
        if not sequence:
            return False
        left = right = []
        root = sequence[-1]
        index = 0
        for i in range(len(sequence)-1):
            index = i
            if sequence[i] > root:
                break
        res = True
        right = sequence[index:-1]
            
        if right:
            for i in range(index+1, len(sequence)-1):
                if sequence[i] < root:
                    return False
        
        ri = True
        if index < len(sequence)-1:
            ri = self.VerifySquenceOfBST(sequence[index:-1])
        le = True
        if index:
            le = self.VerifySquenceOfBST(sequence[:index])
        return ri and le

面试题25:二叉树中和为某一值的路径

题目:输入一棵二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

思路梳理

递归函数:

  • 距离目标值还需要的值 保存为参数,传递给子方法。
  • 总体思路为深度优先遍历的递归。
class Solution:
    # 返回二维列表,内部每个列表表示找到的路径
    def FindPath(self, root, expectNumber):
        ret = []
        def dfs(root,sum_,tmp):
            if root:
                if root.left==None and root.right == None:
                    if root.val == sum_:
                        tmp.append(root.val)
                        ret.append(tmp[:])
                else:
                    tmp.append(root.val)
                    dfs(root.left,sum_-root.val,tmp[:])
                    dfs(root.right,sum_-root.val,tmp[:])
        dfs(root,expectNumber,[])
        return ret

方法二:
当用前序遍历的方式访问到某一结点时,我们把该结点添加到路径上,并累加该结点的值。

  1. 如果该结点为叶结点并且路径中结点值的和刚好等于输入的整数,则当前的路径符合要求,我们把它打印出来。
  2. 如果当前结点不是叶结点,则继续访问它的子结点。
  3. 当前结点访问结束后,递归函数将自动回到它的父结点。

因此我们在函数退出之前,要在路径上删除当前结点并减去当前结点的值,以确保返回父结点时路径刚好是从根结点到父结点的路径

  • 我们不难看出保存路径的数据结构实际上是一个,因为路径要与递归调用状态一致,而递归调用的本质就是一个压栈和出栈的过程。
    注意:需要设置一个变量 pre ,记录前一个访问的节点,防止重复访问右节点。
    见LeetCode113:
# 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 pathSum(self, root, sum):
        """
        :type root: TreeNode
        :type sum: int
        :rtype: List[List[int]]
        """
        if root is None:
            return 
        res = []
        stack = []
        sum1 = 0
        node = root
        pre = TreeNode(0)
        while node or stack:
            while node:
                stack.append(node)
                sum1 += node.val
                node = node.left
            node = stack[-1]
            # 是否符合返回值的条件
            if not node.left and not node.right and sum==sum1:
                res.append([i.val for i in stack])
            
            # 如果有右节点
            if node.right and node.right!=pre:
                node = node.right
                
            # 更新pre与其他变量
            else:
                pre = node
                sum1 -= node.val
                stack.pop()
                node = None
                
        return res            
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数字乡村和智慧农业的数字化转型是当前农业发展的新趋势,旨在通过应用数字技术,实现农业全流程的再造和全生命周期的管理服务。中国政府高度重视这一领域的发展,提出“数字中国”和“乡村振兴”战略,以提升国家治理能力,推动城乡融合发展。 数字乡村的建设面临乡村治理、基础设施、产业链条和公共服务等方面的问,需要分阶段实施《数字乡村发展战略纲要》来解决。农业数字化转型的需求包括满足市民对优质农产品的需求、解决产销对接问、形成优质优价机制、提高农业劳动力素质、打破信息孤岛、提高农业政策服务的精准度和有效性,以及解决农业融资难的问。 数字乡村建设的关键在于构建“1+3+4+1”工程,即以新技术、新要素、新商业、新农民、新文化、新农村为核心,推进数据融合,强化农业大数据的汇集功能。数字农业大数据解决方案以农业数字底图和数据资源为基础,通过可视化监管,实现区域农业的全面数字化管理。 数字农业大数据架构基于大数据、区块链、GIS和物联网技术,构建农业大数据中心、农业物联网平台和农村综合服务指挥决策平台三大基础平台。农业大数据中心汇聚各类涉农信息资源和业务数据,支持大数据应用。信息采集系统覆盖市、县、乡、村多级,形成高效的农业大数据信息采集体系。 农业物联网平台包括环境监测系统、视频监控系统、预警预报系统和智能控制系统,通过收集和监测数据,实现对农业环境和生产过程的智能化管理。综合服务指挥决策平台利用数据分析和GIS技术,为农业决策提供支持。 数字乡村建设包括三大服务平台:治理服务平台、民生服务平台和产业服务平台。治理服务平台通过大数据和AI技术,实现乡村治理的数字化;民生服务平台利用互联网技术,提供各类民生服务;产业服务平台融合政企关系,支持农业产业发展。 数字乡村的应用场景广泛,包括农业生产过程、农产品流通、农业管理和农村社会服务。农业生产管理系统利用AIoT技术,实现农业生产的标准化和智能化。农产品智慧流通管理系统和溯源管理系统提高流通效率和产品追溯能力。智慧农业管理通过互联网+农业,提升农业管理的科学性和效率。农村社会服务则通过数字化手段,提高农村地区的公共服务水平。 总体而言,数字乡村和智慧农业的建设,不仅能够提升农业生产效率和管理水平,还能够促进农村地区的社会经济发展,实现城乡融合发展,是推动中国农业现代化的重要途径。
1. 实现一个数组去重的函数 思路:使用对象来存储数组中的元素,遍历数组,若元素在对象中不存在,则存储到对象中,并将其推入新数组中。 2. 实现一个函数,判断一个字符串是否是回文字符串 思路:将字符串翻转,与原字符串比较是否相等。 3. 实现一个函数,可以将多维数组转化为一维数组 思路:使用递归来遍历多维数组,将每个元素推入新数组中,直到遍历完所有元素。 4. 实现一个函数,统计一个字符串中出现次数最多的字符 思路:使用对象来存储每个字符出现的次数,遍历字符串,将每个字符存储到对象中,找到出现次数最多的字符。 5. 实现一个函数,实现数组的冒泡排序 思路:使用双重循环遍历数组,比较相邻两个元素的大小,如果前者大于后者,则交换两个元素的位置,直到遍历完数组。 6. 实现一个函数,实现数组的快速排序 思路:选择数组中的一个元素作为基准点,将数组分为两个部分,一部分大于基准点,一部分小于基准点,递归处理两个部分。 7. 实现一个函数,实现数组的深拷贝 思路:使用递归遍历数组中的每个元素,判断元素类型,如果是对象或数组,则进行深拷贝,如果是基本类型,则直接复制。 8. 实现一个函数,实现函数的柯里化 思路:使用闭包保存参数,当参数个数达到预设值时,执行函数。 9. 实现一个函数,实现函数的节流 思路:使用定时器来控制函数执行的频率,每次函数执行时,清除定时器并重新设置一个定时器。 10. 实现一个函数,实现函数的防抖 思路:使用定时器来延迟函数执行,每次函数执行时,清除定时器并重新设置一个定时器。如果在定时器延迟时间内再次触发函数,则重新计时。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值