leetcode刷题之树

介绍

树是一种抽象数据类型(ADT)或是实现这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。它是由 n(n>0) 个有限节点组成一个具有层次关系的集合。
它具有以下的特点:

  1. 每个节点都只有有限个子节点或无子节点;
  2. 没有父节点的节点称为根节点;
  3. 每一个非根节点有且只有一个父节点;
  4. 除了根节点外,每个子节点可以分为多个不相交的子树;
  5. 树里面没有环路。

目录

  • 二叉树
    1 递归执行前中后遍历
    2 非递归执行前中后遍历
    3 由遍历序列求二叉树
    4 完全二叉树
    5 节点最近公共祖先
  • 字典树
一、二叉树
  1. 递归执行前序,中序,后序遍历
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
#前序遍历
class Solution:
	ans = []
    def preorderTraversal(self, root: TreeNode) -> List[int]:
            if root == None:
                return
            else:
            	self.ans.append(root.val)
                preorderTraversal(root.left)
                preorderTraversal(root.right)
#中序遍历
class Solution:
	ans = []
    def inorderTraversal(self, root: TreeNode) -> List[int]:
            if root == None:
                return
            else:
                inorderTraversal(root.left)
                self.ans.append(root.val)
                inorderTraversal(root.right)
#后序遍历
class Solution:
	ans = []
    def postTraversal(self, root: TreeNode) -> List[int]:
            if root == None:
                return
            else:
                postorderTraversal(root.left)
                self.ans.append(root.val)
                postorderTraversal(root.right)
  1. 非递归执行前序,中序,后序遍历
这里介绍一个更加简洁易懂且通用的方式
其核心思想如下:
如果节点status = 1,则表示已经访问,将节点的值输出
如果节点status = 0,根据前序,中序,后序要求将该节点与左右子树以此入栈
class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        ans = []
        stack = [(0, root)]
        while stack:
            status, node = stack.pop()
            if not node: continue
            if status == 0:
            #中序遍历
                stack.append((0, node.right))
                stack.append((1, node))
                stack.append((0, node.left))
            #前序遍历
                stack.append((0, node.right))
                stack.append((0, node.left))
                stack.append((1, node))
            #后序遍历
                stack.append((1, node))    
                stack.append((0, node.right))
                stack.append((0, node.left))           
            else:
                ans.append(node.val)
        return ans
  1. 由遍历序列构造二叉树
    (1)从前序和中序遍历序列构造二叉树 从中序与后序遍历序列构造二叉树
根据一棵树的前序遍历与中序遍历构造二叉树。

注意:
你可以假设树中没有重复的元素。

例如,
前序遍历 preorder = [3,9,20,15,7] 中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
    3
   / \
  9  20
    /  \
   15   7
思路:首先来看前序遍历和中序遍历的性质
前序遍历:遍历顺序为 父节点 -> 左子节点 -> 右子节点
中序遍历:遍历顺序为 左子节点 -> 父节点 -> 右子节点
步骤解析:首先通过前序遍历获取根节点,然后根据中序遍历将树分为左子树和右子树,重复上述过程。
注意在每次遍历过程中,为了保持递归一致性,左子树,右子树的中序遍历容易知道,而前序遍历怎么
求呢?
根据前序遍历性质可知左子节点大于右子节点,所以中序遍历获得的左子树的前序遍历也一定大于出现在
右子树之前,假设根节点在中序遍历中的位置是mid,则[1:mid + 1]为左子树在前序遍历中对应的节点,
也就是[mid + 1:]为右子树在前序遍历中对应的节点。
class Solution(object):
    def buildTree(self, preorder, inorder):
        if len(inorder) == 0:
            return None
        # 前序遍历第一个值为根节点
        root = TreeNode(preorder[0])
        # 因为没有重复元素,所以可以直接根据值来查找根节点在中序遍历中的位置
        mid = inorder.index(preorder[0])
        # 构建左子树
        root.left = self.buildTree(preorder[1:mid+1], inorder[:mid])
        # 构建右子树
        root.right = self.buildTree(preorder[mid+1:], inorder[mid+1:])
   
        return root
(2)同理可求,从中序与后序遍历序列构造二叉树     
class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
        def buildTree(inorder, postorder):
            if not inorder: return None
            root = TreeNode(postorder[-1])
            mid = inorder.index(postorder[-1])
            root.left = buildTree(inorder[0:mid], postorder[: mid])
            root.right = buildTree(inorder[mid + 1:], postorder[mid: -1])   
            return root
        return buildTree(inorder, postorder)
  1. 完全二叉树
    说明:完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。

举例一:完全二叉树的节点个数
给出一个完全二叉树,求出该树的节点个数。

示例:
输入: 
    1
   / \
  2   3
 / \  /
4  5 6
输出: 6
class Solution:
    def countNodes(self, root: TreeNode) -> int:
        def helper(root):
            if not root: return 0
            left, right = 0, 0
            if root.left: left = helper(root.left)
            if root.right: right = helper(root.right)
            return left + right + 1
        return helper(root)
    #精简版如下:
 	def countNodes(self, root: TreeNode) -> int:
 		return countNodes(root.left) + countNodes(root.right) + 1 if root else 0
  1. 最近公共祖先
    给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
    百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
思路:首先遍历整棵树的所有节点,找到节点p和q,然后包含p或q的树在上传过程中返回值为True,最深节点满足以下几种情况:
(1)当前节点为p或者q,且其子树中包含留一个节点
(2)当前节点不包括p或者q,其子树同时包含p和q
当我们由下向上回溯的过程中,第一次遇到一个节点满足以上两种其中任意一种情况,则该节点就是目标节点
class Solution:
    judge = None
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        def helper(root):
            if not root: return False
            left, right = False, False
            if root.left: left = helper(root.left)
            if root.right: right = helper(root.right)
            #情况(1)
            if (root == p or root == q) and (left or right):
                self.judge = root
                return False
            #情况(2)
            if left and right:  
                self.judge = root
                return False
            if root == p or root == q or left or right: return True
            
        helper(root)
        return self.judge

在这里插入图片描述
二、字典树
概念摘要:字典树又称为单词查找树,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
前缀树的代码实现:

class Trie:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.lookup = {}

    def insert(self, word: str) -> None:
        """
        Inserts a word into the trie.
        """
        tree = self.lookup
        for a in word:
        	tree.setdefault(a, {})
            tree = tree[a]
        tree['#'] = "#"

    def search(self, word: str) -> bool:
        """
        Returns if the word is in the trie.
        """
        tree = self.lookup
        for a in word:
            if a not in tree.keys():
                return False
            tree = tree[a]
        if '#' in tree.keys():
            return True
        return False

    def startsWith(self, prefix: str) -> bool:
        """
        Returns if there is any word in the trie that starts with the given prefix.
        """
        tree = self.lookup
        for a in prefix:
            if a not in tree.keys():
                return False
            tree = tree[a]
        return True

举例二:添加单词与搜索单词

设计一个支持以下两种操作的数据结构:
void addWord(word)
bool search(word)


代码实现:
class WordDictionary:

    def __init__(self):
        self.word_dict = collections.defaultdict(list)

    def addWord(self, word):
        self.word_dict[len(word)] += [word]  #将单词长度作为键

    def search(self, word):
        length = len(word)
        def f(s, word):  #查找长度为length的集合里是否有word
            for i in range(length):
                if word[i] not in [s[i], '.']:
                    return False
            return True
        for w in self.word_dict[length]:
            if f(w, word):
                return True
        return False

举例三:恢复空格
哦,不!你不小心把一个长篇文章中的空格、标点都删掉了,并且大写也弄成了小写。像句子"I reset the computer. It still didn’t boot!“已经变成了"iresetthecomputeritstilldidntboot”。在处理标点符号和大小写之前,你得先把它断成词语。当然了,你有一本厚厚的词典dictionary,不过,有些词没在词典里。假设文章用sentence表示,设计一个算法,把文章断开,要求未识别的字符最少,返回未识别的字符数。

注意:本题相对原题稍作改动,只需返回未识别的字符数

class Solution {
	//----------------------------------前缀树类定义-------------------------------------------
    class Node{
        Node[] childs;
        boolean isWord;
        public Node(){
            childs = new Node[26];
            isWord = false;
        }
    }
    //----------------------------------构建前缀树---------------------------------------------
    public void buildTree(String word, Node root){
        Node temp = root;
        for(int i = 0; i<word.length(); i++){
            int index = word.charAt(i) - 'a';
            if(temp.childs[index] == null) temp.childs[index] = new Node();
            temp = temp.childs[index];
        }
        temp.isWord = true;
    }
    //------------------------------动态规划 + 前缀树--------------------------------------------
    public int respace(String[] dictionary, String sentence) {
        Node root = new Node();
        for(String s:dictionary){
            buildTree(s, root);
        }
        int len = sentence.length();
        int[] dp = new int[len + 1];
        //从后往前一次寻找子串对应的最小长度
        for(int i = len - 1; i>=0; i--){
            Node temp = root;
            dp[i] = len - i;//最不理想情况,当前子串不包含字典中单词
            for(int j = i; j<len; j++){
                int index = sentence.charAt(j) - 'a';
                //当前子串s前j个字符组成单词不在字典中,停止内循环
                if(temp.childs[index] == null) {dp[i] = Math.min(dp[i], j-i+1 + dp[j+1]); break;}
                //当前子串s前j个字符组成单词在字典中,s.substring(i, j+1) 对结果贡献为0
                if(temp.childs[index].isWord){dp[i] = Math.min(dp[i], dp[j+1]);}
                //当前子串s前j个字符在前缀树中,可以断句
                else{dp[i] = Math.min(dp[i], j-i+1 + dp[j+1]);}
                temp = temp.childs[index];
            }
        }
        return dp[0];

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
校园悬赏任务平台对字典管理、论坛管理、任务资讯任务资讯公告管理、接取用户管理、任务管理、任务咨询管理、任务收藏管理、任务评价管理、任务订单管理、发布用户管理、管理员管理等进行集中化处理。经过前面自己查阅的网络知识,加上自己在学校课堂上学习的知识,决定开发系统选择小程序模式这种高效率的模式完成系统功能开发。这种模式让操作员基于浏览器的方式进行网站访问,采用的主流的Java语言这种面向对象的语言进行校园悬赏任务平台程序的开发,在数据库的选择上面,选择功能强大的Mysql数据库进行数据的存放操作。校园悬赏任务平台的开发让用户查看任务信息变得容易,让管理员高效管理任务信息。 校园悬赏任务平台具有管理员角色,用户角色,这几个操作权限。 校园悬赏任务平台针对管理员设置的功能有:添加并管理各种类型信息,管理用户账户信息,管理任务信息,管理任务资讯公告信息等内容。 校园悬赏任务平台针对用户设置的功能有:查看并修改个人信息,查看任务信息,查看任务资讯公告信息等内容。 系统登录功能是程序必不可少的功能,在登录页面必填的数据有两项,一项就是账号,另一项数据就是密码,当管理员正确填写并提交这二者数据之后,管理员就可以进入系统后台功能操作区。项目管理页面提供的功能操作有:查看任务,删除任务操作,新增任务操作,修改任务操作。任务资讯公告信息管理页面提供的功能操作有:新增任务资讯公告,修改任务资讯公告,删除任务资讯公告操作。任务资讯公告类型管理页面显示所有任务资讯公告类型,在此页面既可以让管理员添加新的任务资讯公告信息类型,也能对已有的任务资讯公告类型信息执行编辑更新,失效的任务资讯公告类型信息也能让管理员快速删除。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值