剑指offer——(一)二叉树

1.二叉树的镜像

操作给定的二叉树,将其变换为源二叉树的镜像。

思路:直接用先序遍历,左边和右边换一下,然后再分别遍历左子树和右子树。

public class Solution {
    public void Mirror(TreeNode root) {
        TreeNode node = null;
        if(root != null) {
            node = root.left;
            root.left = root.right;
            root.right = node;
            if(root.left != null) 
            	Mirror(root.left);
            if(root.right != null) 
            	Mirror(root.right);
        }
    }
}

2.二叉树的深度

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

递归写法:二叉树的深度即为左右字树深度较深的值再加1。

    // 递归写法
    public int TreeDepth(TreeNode root) {
        if (root == null)
            return 0;
        int left = TreeDepth(root.left);
        int right = TreeDepth(root.right);
        return Math.max(left, right) + 1;
    }
    */

非递归写法:层次遍历

	// 层次遍历
	public int TreeDepth(TreeNode root) {
		if (root == null)
			return 0;
		Queue<TreeNode> queue = new LinkedList<>();
		// 记录深度
		int depth = 0;
		// 记录每层的节点数
		int width = 0;
		// 记录当前扫描到第几个
		int cur = 0;
		queue.offer(root);
		while (!queue.isEmpty()) {
			width = queue.size();
			while (cur < width) {
				cur = 0;
				TreeNode node = queue.poll();
				if (node.left != null) {
					queue.offer(node.left);
				}
				if (node.right != null) {
					queue.offer(node.right);
				}
				cur ++;
			}
			depth ++;			
		}
		return depth;
	}

3.平衡二叉树

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

普通写法,得到深度,判断深度差

	public boolean IsBalanced_Solution(TreeNode root) {
        if (root == null) return true;
        if(Math.abs(getDepth(root.left) - getDepth(root.right)) > 1) {
            return false;
        }
        return IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right);
    }
    
    private int getDepth(TreeNode root) {
        if(root == null) return 0;
        return Math.max(getDepth(root.left), getDepth(root.right)) + 1;
    }

剪枝法,遇到不平衡就退出

	// 剪枝做法
    public boolean IsBalanced_Solution(TreeNode root) {
        if (root == null) return true;
        return getDepth(root) != -1;
    } 
    private int getDepth(TreeNode root) {
        if(root == null) return 0;
        int left = getDepth(root.left);
        if(left == -1) return -1;
        int right = getDepth(root.right);
        if(right == -1) return -1;
        return Math.abs(left - right) > 1 ? -1 : Math.max(left, right) + 1;
    }

4.把二叉树打印成多行

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

依然是层次遍历的思想,记录下当前层需要打印的个数和下一层需要打印的个数

	ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
        if (pRoot == null)
            return res;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(pRoot);
        ArrayList<Integer> layerList = new ArrayList<>();
        // 当前层需要打印的节点数
        int thisLayer = 0;
        // 下一层需要打印的节点数,初始值即为根节点
        int nextLayer = 1;
        while (!queue.isEmpty()) {
            TreeNode temp = queue.poll();
            layerList.add(temp.val);
            thisLayer++;
            if (temp.left != null) {
                queue.offer(temp.left);
            }
            if (temp.right != null) {
                queue.offer(temp.right);
            }
            if (thisLayer == nextLayer) {
            	// 记下下层需要打印的节点数
                nextLayer = queue.size();
                thisLayer = 0;
                res.add(layerList);
                layerList = new ArrayList<>();
            }
            
        }
        return res;
    }

5.之字形顺序打印二叉树

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

依然是层序遍历,设置一个标志位,标记是偶数行还是奇数行。

public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
        if (pRoot == null)
            return res;
        boolean isOdd = true;
        // oddStack存奇数层节点,evenStack存偶数层节点
        Stack<TreeNode> oddStack = new Stack<>();
        Stack<TreeNode> evenStack = new Stack<>();
        oddStack.add(pRoot);
        while (!oddStack.isEmpty() || !evenStack.isEmpty()) {
            // 如果是奇数行
            if (isOdd) {
                ArrayList<Integer> temp = new ArrayList<>();
                while (!oddStack.isEmpty()) {
                    TreeNode node = oddStack.pop();
                    temp.add(node.val);
                    if (node.left != null) {
                        evenStack.push(node.left);
                    }
                    if (node.right != null) {
                        evenStack.push(node.right);
                    }
                }
                res.add(temp);
                isOdd = false;
            } else {
                ArrayList<Integer> temp = new ArrayList<>();
                while (!evenStack.isEmpty()) {
                    TreeNode node = evenStack.pop();
                    temp.add(node.val);
                    if (node.right != null) {
                        oddStack.push(node.right);
                    }
                    if (node.left != null) {
                        oddStack.push(node.left);
                    }
                }
                res.add(temp);
                isOdd = true;
            }
            
        }
        return res;
    }

6.二叉树的下一个节点

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

分三种情况讨论。

public TreeLinkNode GetNext(TreeLinkNode pNode)
    {
        if (pNode == null)
            return null;
        // 若当前节点的右子树不为空,则返回右子树中的最左边的节点,为下一个节点
        if (pNode.right != null) {
            pNode = pNode.right;
            while (pNode.left != null) {
                pNode = pNode.left;
            }
            return pNode;
            // 当前节点的右子树为空,且当前节点是其父节点的左节点,则直接返回其父节点
        } else if (pNode.next != null && pNode.next.left == pNode) {
            return pNode.next;
            // 当前节点的右子树为空,且当前节点是其父节点的右节点,一直向上找,
            // 直到找到有一个节点的父节点的左节点是该节点的时候,返回其父节点
        } else if (pNode.next != null && pNode.next.right == pNode) {
            while (pNode.next != null && pNode.next.left != pNode) {
                pNode = pNode.next;
            }
            return pNode.next;
        } else {
            return null;
        }
        
    }

7.序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树

一定要注意sb在函数体里,因为每一步返回的sb都是往上层的sb中添加的。

	// 此题考查对递归的理解
    // sb对象一定要放在里面,这样每次递归产生的值才可以一点点放入最终的sb中
    String Serialize(TreeNode root) {
        StringBuffer sb = new StringBuffer();
        if (root == null) {
            sb.append("#,");
            return sb.toString();
        }
        sb.append(root.val + ",");
        sb.append(Serialize(root.left));
        sb.append(Serialize(root.right));
        return sb.toString();
    }
    
    int index = -1;
	TreeNode Deserialize(String str) {
		index++;
		int len = str.length();
		if (index >= len)
			return null;
		String[] strr = str.split(",");
		TreeNode node = null;
		if (!strr[index].equals("#")) {
			node = new TreeNode(Integer.valueOf(strr[index]));
			node.left = Deserialize(str);
			node.right = Deserialize(str);
		}
		return node;
    }

8.二叉搜索树的第K个结点

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

递归做法:直接中序遍历,找到第K个遍历到的结点。注意中序遍历的写法。

    // 递归实现
    int index = 0;
    TreeNode KthNode(TreeNode pRoot, int k)
    {
        if (pRoot != null) {
            TreeNode node = KthNode(pRoot.left, k);
            if (node != null) {
                return node;
            }
            index ++;
            if (index == k) {
                return pRoot;
            }
            node = KthNode(pRoot.right, k);
            if (node != null) {
                return node;
            }
        }
        return null;
    }

非递归做法:存放在栈中

// 非递归实现
    TreeNode KthNode(TreeNode pRoot, int k) {
        Stack<TreeNode> stack = new Stack<>();
        while (pRoot != null || !stack.isEmpty()) {
            if (pRoot != null) {
                stack.push(pRoot);
                pRoot = pRoot.left;
            } else {
                pRoot = stack.pop();
                k--;
                if (k == 0) {
                    return pRoot;
                }
                pRoot = pRoot.right;
            }
        }
        return null;
    }

9.重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

前序遍历和中序遍历构造二叉树。
方法一:
先序遍历第一个位置肯定是根节点,中序遍历的根节点位置在中间p,在p左边的肯定是node的左子树的中序数组,p右边的肯定是node的右子树的中序数组,记录下先序遍历和中序遍历的start和end,分左右递归调用即可。

public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        TreeNode root=reConstructBinaryTree(pre,0,pre.length-1,in,0,in.length-1);
        return root;
    }
    //前序遍历{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}
    private TreeNode reConstructBinaryTree(int [] pre,int startPre,int endPre,int [] in,int startIn,int endIn) {
          
        if(startPre>endPre||startIn>endIn)
            return null;
        TreeNode root=new TreeNode(pre[startPre]);
          
        for(int i=startIn;i<=endIn;i++)
        	// i的位置就记录下根节点的位置	
            if(in[i]==pre[startPre]){
            	// 先序遍历的左子树,即为startPre+1到循环中i移动的位置,就是startPre+i-startIn。
            	// 中序遍历的左子树,即为startIn到i-1。
                root.left=reConstructBinaryTree(pre,startPre+1,startPre+i-startIn,in,startIn,i-1);
              	// 先序遍历的右子树,即为左子树end+1(startPre+i-startIn+1)到endPre。
              	// 中序遍历的右子树,即为i+1到endIn。
                root.right=reConstructBinaryTree(pre,startPre+i-startIn+1,endPre,in,i+1,endIn);
                      break;
            }
                  
        return root;
    }

方法二,使用copyofRange函数。

// 先序序列的第一个必然是根节点,找到此节点在中序序列中对应的位置,则该位置左边必然是左子树,右边必然是右子数。通过copyofRange函数构造出左右子树的先序和中序序列,分别递归。
    
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        if(pre.length == 0 || in.length == 0 || pre.length != in.length)
            return null;
        TreeNode node = new TreeNode(pre[0]);
        for(int i = 0; i<pre.length; i++) {
            if(pre[0] == in[i]) {
                node.left = reConstructBinaryTree(Arrays.copyOfRange(pre,1,i+1),
                                                  Arrays.copyOfRange(in,0,i));
                node.right = reConstructBinaryTree(Arrays.copyOfRange(pre,i+1,pre.length),
                                                  Arrays.copyOfRange(in,i+1,in.length));
            }
        }
        return node;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值