刷题笔记

牛客网

关于树的解题思路

序列化二叉树

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

二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过
某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。

二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树

  • 解题思路
    序列化二叉树也就是二叉树的创建以及二叉树的遍历
    这道题就是二叉树的先序遍历+先序创建。
  • 如何就这道题进行二叉树的中序+二叉树的中序创建呢? 有思路的朋友可以帮一下忙
  public static String pre(TreeNode root){
        if(root==null)
            return "#";
        return root.val+"!"+pre(root.left)+"!"+pre(root.right);
    }
    public static TreeNode preBuildTree(String str){
        LinkedList<String> list=new LinkedList<>();
        if(str==null)
            return  null;
        String[] s=str.split("!");
        for(int i=0;i<s.length;i++)
            list.add(s[i]);
        return  preBuildTree(list);
    }
    public static TreeNode preBuildTree(LinkedList<String> list){
        String str=list.removeFirst();
        if(str.equals("#"))
            return null;
         TreeNode root=new TreeNode(Integer.valueOf(str));
           root.left=preBuildTree(list);
           root.right=preBuildTree(list);
           return root;
    }

二叉搜索树第K个节点

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

  • 解题思路
    二叉搜索树是二叉树+限定条件。即根点大于等于左子树,同时树节点小于等于右子树,并且每个子树也是这样。
    二叉搜索树有一个性质就是:中序遍历是一个按照从小到大顺序排列。
    第k个结点,也就是第k个排序。
      int cnt=0;
    TreeNode ret;
    TreeNode KthNode(TreeNode pRoot, int k)
    {
         orderselect( pRoot,  k);
         return  ret;
        
    }
    public void orderselect(TreeNode pRoot, int k){
        if(pRoot==null||cnt>=k)//等于的话可以少循环一次
            return ;
        orderselect(pRoot.left,k);
        cnt++;
        if(k==cnt)
            ret= pRoot;
         orderselect(pRoot.right,k); 
    }

把二叉树打印成多行

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

  • 解题思路
    这就是二叉树的层序遍历:也就是队列的思想
    static ArrayList<ArrayList<Integer> > Print(TreeNode pRoot){
        ArrayList<ArrayList<Integer> > lists= new  ArrayList<ArrayList<Integer> >();
        Queue<TreeNode> queue=new LinkedList<>();
        if(pRoot==null)
            return lists;
        queue.add(pRoot);
        while (!queue.isEmpty()){
            LinkedList<TreeNode> linkedList=new LinkedList<>();
            ArrayList<Integer> list=new ArrayList<>();
            while (!queue.isEmpty()){
                TreeNode treeNode=queue.poll();
                if(treeNode==null)
                    continue;
                linkedList.add(treeNode);
                list.add(treeNode.val);
            }
            TreeNode treeNode=linkedList.poll();
            while (treeNode!=null){
                    queue.add(treeNode.left);
                    queue.add(treeNode.right);
                treeNode=linkedList.poll();
            }
            lists.add(list);
        }
        return lists;
    }
//可以看出上面相当繁琐,原因在于不会使用标志位(cnt)
ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
    ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(pRoot);
    while (!queue.isEmpty()) {
        ArrayList<Integer> list = new ArrayList<>();
        int cnt = queue.size();
        while (cnt-- > 0) {
            TreeNode node = queue.poll();
            if (node == null)
                continue;
            list.add(node.val);
            queue.add(node.left);
            queue.add(node.right);
        }
        if (list.size() != 0)
            ret.add(list);
    }
    return ret;
    }

按照之字形顺序打印二叉树

  • 这个就是上面没有进行
 public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
            ArrayList<ArrayList<Integer> > ret=new ArrayList<ArrayList<Integer> >();
             Queue<TreeNode> queue=new LinkedList<TreeNode>();
        queue.add(pRoot);
        boolean flag=false;
        while(!queue.isEmpty()){
            int size=queue.size();
            ArrayList<Integer> list=new ArrayList<Integer>();
            while(size-->0){
                
                TreeNode p=queue.poll();
                if(p!=null){
                     list.add(p.val);
                queue.add(p.left);
                queue.add(p.right); 
                }            
            }
            //比上面一题,多了一个判断是否逆序
            if(flag)
                Collections.reverse(list);
            flag=!flag;
            
            if(list.size()>0)
                ret.add(list);
        }
        return ret;
    }

对称二叉树

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

  • 思路
    这种题说难也不难,说简单。想不到位又很难。
    其实就是一个节点的左儿子等于另一个节点的右儿子。
 boolean isSymmetrical(TreeNode pRoot)
    {
        if(pRoot==null)
            return true;
        return isSymmetrical(pRoot.left,pRoot.right);
        
    }
    public boolean isSymmetrical(TreeNode t1,TreeNode t2){
    //试着思考一下,为什么没有写if(t1.val==t2.val) return true;
        if(t1==null&&t2==null)
            return true;
        if(t1==null||t2==null)
            return false;
        if(t1.val!=t2.val)
            return false;
        return isSymmetrical(t1.left,t2.right)&&isSymmetrical(t1.right,t2.left);      
    }

二叉树下一个结点****

  • 题目

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

  • 思路
    由于中序遍历,需要根据其有没有右儿子来进行判断。如果有右儿子,那么输出右儿子最左边的节点,如果没有右儿子,往上找其父亲节点,直到父亲节点的左儿子是该节点。
 public TreeLinkNode GetNext(TreeLinkNode pNode)
    {
        if(pNode.right!=null)
        {
            TreeLinkNode p=pNode.right;
            while(p.left!=null)
                p=p.left;
            return p;
        }
        else{
          TreeLinkNode p=pNode;     
            while(p.next!=null){
                if(p.next.left==p)
                    return p.next;
                p=p.next;
               } 
 
            return null;//如果其父节点为空,那么就表示到了顶端了。证明这个节点是这棵树最右边的一个节点
        }

重建二叉树*********

  • 题目
    输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
  • 思路
    先序数组中的第一个元素,表示根节点。同时第一个元素在中序的位置的左边是左子树,右边是右子树。递归遍历便是从这里开始。
HashMap<Integer,Integer> list=new HashMap<>();
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        if(pre.length==0)
            return null;
        for(int i=0;i<in.length;i++)
            list.put(in[i],i);
        return reConstructBinaryTree(pre,0,pre.length-1,0);
    }
    public TreeNode reConstructBinaryTree(int[] pre,int preL,int preR,int inL){
        if(preL>preR)
            return null;
        TreeNode root =new TreeNode(pre[preL]);//先序的首位
        int inindex=list.get(pre[preL]);//找到先序首位在中序中的位置
        int lefttreesize=inindex-inL;//左子树的长度
        root.left=reConstructBinaryTree(pre,preL+1,preL+lefttreesize,inL);
        root.right=reConstructBinaryTree(pre,preL+lefttreesize+1,preR,inL+treesize+1);
        return root;
 
    }

从上往下打印二叉树

  • 题目
    从上往下打印出二叉树的每个节点,同层节点从左至右打印
  • 思路
    每次经过一个结点都会将其进入队列。也就是使用队列的思想。
 public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
    
        ArrayList<Integer> list =new ArrayList<>();
           if(root==null) 
              return list;
        Queue<TreeNode> queue=new LinkedList<>();
        queue.add(root);
        while(!queue.isEmpty()){
            TreeNode p=queue.poll();
            list.add(p.val);
            if(p.left!=null)
            queue.add(p.left);
             if(p.right!=null)
            queue.add(p.right);
        }
        return list;
    }

二叉搜索树的后序遍历序列

二叉树中和为某一值的路径

  • 题目
    输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
  • 思路
    跟着代码的思路走
 private ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        backtracking(root,target,new ArrayList<>());
            return ret;
    }
    public void backtracking(TreeNode root,int target,ArrayList<Integer> path){
        if(root==null||target<0)//优化
            return;
        path.add(root.val);
        target-=root.val;
        if(target==0&&root.left==null&&root.right==null){
            ret.add(new ArrayList<>(path));//每次add都是不会更改的
        }
        else
        {
            backtracking(root.left,target,path);
            backtracking(root.right,target,path);
        }
        path.remove(path.size()-1);//递归的精髓所在
    }

关于ret.add(new ArrayList<>(path))为什么这样使用?
ret.add(new ArrayList<>(path))

二叉搜索树与双向链表

  • 题目

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

  • 思路
    第一种方法就是下面代码块的方法。通过中序遍历,找到pre的位置。
  private TreeNode pre;
    private TreeNode head;
    public TreeNode Convert(TreeNode pRootOfTree) {
        inorder(pRootOfTree);
        return head;        
    }
    public void inorder(TreeNode root){
        if(root==null)
            return;
        inorder(root.left);
          root.left=pre;
        if(pre!=null)
            pre.right=root;
          
        pre=root;
        if(head==null)
            head=root;
        inorder(root.right);
    }

二叉树的深度

  • 题目

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

  • 思路
    有两种方法:第一种就是使用层次遍历找到左右节点都为空的情况。第二种使用递归(一行代码搞定太精妙了)。
  • 层次遍历能够解决的问题
    二叉树的最大高度、二叉树的最小高度、层次遍历一颗二叉树、之字形打印一颗二叉树。
//层次遍历
public int TreeDepth(TreeNode root) {
             if(root==null)
            return 0;
        Queue<TreeNode> queue=new LinkedList<>();
         queue.add(root);
        int k=0;
        while(!queue.isEmpty()){
            k++;
            int size=queue.size();
            while(size-->0){
            TreeNode p=queue.poll();               
            if(p.left!=null)
                queue.add(p.left);
            if(p.right!=null)
                queue.add(p.right);

        }   
    }
      return k;  
    }
    // 递归实现
     public int TreeDepth(TreeNode root) {
        return root==null?0:1+Math.max(TreeDepth(root.left),TreeDepth(root.right));    
    }

平衡二叉树

  • 题目

输入一棵二叉树,判断该二叉树是否是平衡二叉树。
在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树

  • 思路
    平衡二叉树的思想就是左子树和右子树的高度之差的绝对值小于等于1
 private boolean isbalance=true;
    public boolean IsBalanced_Solution(TreeNode root) {
        height(root);
        return isbalance;
    }
    public int height(TreeNode root){
        if(root==null||!isbalance)
            return 0;
        int left=height(root.left);
        int right=height(root.right);
        if(Math.abs(left-right)>1)
            isbalance=false;
        return 1+Math.max(left,right);
    }

关于链表的解题思路

两种倒数第k个结点

  • 题目

输入一个链表,输出该链表中倒数第k个结点。

  • 思路
    双指针,等到第一个指针跑出第k个,才启动第二个指针
 public ListNode FindKthToTail(ListNode head,int k) {
      int i=0;
        ListNode q=head;
        ListNode p=head;
        while(p!=null){
            if(i>=k){
                p=p.next;
                q=q.next;
            }
            else
                p=p.next;
            i++;
        }
        if(i>=k)
            return q;
        else
            return null;

    }

翻转链表

  • 题目

输入一个链表,反转链表后,输出新链表的表头。

  • 思路
    依次遍历链表,同时用头插法插入另外一个头结点中。
 public ListNode ReverseList(ListNode head) {
        if(head==null||head.next==null)
            return head;
        ListNode p=new ListNode(-1);
        ListNode q;
        while(head!=null){
            q=head.next;
            head.next=p.next;
            p.next=head;
            head=q;
        }
        return p.next;

    }
    // 以上是O(1)的空间复杂度
    //下面这是直接使用指针来计算
     public ListNode ReverseList(ListNode head) {
        if(head==null||head.next==null)
            return head;
        ListNode p=null;
        ListNode q;
        while(head!=null){
            q=head.next;
            head.next=p;
            p=head;
            head=q;
        }
        return p;
    }

合并两个排序的链表

  • 题目

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

  • 思路
    递归实现和遍历实现
//递归实现
public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1==null)
            return list2;
        if(list2==null)
            return list1;
        if(list1.val<list2.val){
            list1.next=Merge(list1.next,list2);
            return list1;
        }else{
            list2.next=Merge(list1,list2.next);
            return list2;
        }
 
    }
    // 遍历实现
    public ListNode Merge(ListNode list1,ListNode list2) {
        ListNode head=new ListNode(-1);
        ListNode p=head; 
        while(list1!=null&&list2!=null){
            if(list1.val<list2.val){
                p.next=list1;
                list1=list1.next;
           
            }else{
                 p.next=list2;
                list2=list2.next;
            }
               p=p.next;
        }
        if(list1!=null){
            p.next=list1;
        }
             
         if(list2!=null){
              p.next=list2;
         }
           
        
        return head.next;
    }

复杂链表的复制

  • 题目

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

  • 思路
    既然是实现链表的深拷贝,最好的做法就是new。以next为主线去查看其next指针和random指针,由于random指针是一个随机指向链表中的节点,说不用担心random的random指针指向哪里。但是这样的深拷贝其实很耗费内存,因为random指针本来是指向本链表中,结果都被new出来成为一个新的实例化对象。
 public RandomListNode Clone(RandomListNode pHead)
    {
        if(pHead==null)
            return pHead;
        RandomListNode head=new RandomListNode(pHead.label);
         RandomListNode temp=head;
        while(pHead.next!=null){
            temp.next=new RandomListNode(pHead.next.label);
            if(pHead.random!=null)
                temp.random=new RandomListNode(pHead.random.label);
            temp=temp.next;
            pHead=pHead.next;
        }
        return head;
        
    }

两个链表第一个公共结点

  • 题目

输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

  • 思路
    找出两个链表的公共结点,首先应该确定链表有没有环
 public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
         ListNode p=pHead1;
         ListNode q=pHead2;
        ListNode ret=null;
        while(p!=q ){//这个循环仅仅针对,链表无环。
            p=p==null?pHead2:p.next;
            q=q==null?pHead1:q.next;
        }
        return q;
    }

链表中环的入口

  • 题目

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

-思路
快慢指针,但是要注意空指针异常的问题。

  public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        if(pHead==null)
            return null;
        ListNode fast=pHead;
        ListNode slow=pHead;
        do{
            if(fast.next==null||fast.next.next==null)//避免空指针异常的情况
                return null;
            fast=fast.next.next;
            slow=slow.next;
        }while(slow!=fast);
        fast=pHead;
        while(fast!=slow){
            fast=fast.next;
              slow=slow.next;
        }
        return fast;
        
    }

删除链表中重复的结点

  • 题目

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

  • 思路
    将重复的节点进行删除。
   public static ListNode deleteDuplication(ListNode pHead)
    {
        if(pHead==null||pHead.next==null)
            return pHead;
        ListNode  head=new ListNode(-1);
        ListNode p=head;
        while(pHead!=null&&pHead.next!=null){//保证pHead在循环过后一定是一个没有重复的节点
            ListNode temp= pHead.next;
            if(temp.val==pHead.val){
                while(temp!=null&&temp.val==pHead.val){
                    temp=temp.next;
                }
            }else{               
                    p.next= pHead;
                    p=p.next;
                    p.next=null;
            }
              pHead=temp;
        }
        if(pHead!=null)
            p.next=pHead;
        return head.next;
    }

简单的递归实现

  public ListNode deleteDuplication(ListNode pHead)
    {
         if (pHead == null || pHead.next == null)
        return pHead;
    ListNode next = pHead.next;
    if (pHead.val == next.val) {
        while (next != null && pHead.val == next.val)
            next = next.next;
        return deleteDuplication(next);
    } else {
        pHead.next = deleteDuplication(pHead.next);
        return pHead;
    }
 
 
    }

栈的相关用法

栈的压入弹出序列

  • 题目

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

  • 思路
    将压入序列依次压入栈中,但是每次压入栈中都要是否与弹出序列进行比较。
  public boolean IsPopOrder(int [] pushA,int [] popA) {
      Stack<Integer> stack=new Stack<>();
        int cnt=0;
        for(int i=0;i<pushA.length;i++){
            stack.push(pushA[i]);
            while(cnt<=pushA.length&&!stack.isEmpty()&&stack.peek()==popA[cnt]){//cnt<=pushA.length也可以不写,加上这个是防止两个数组的长度不一致
                stack.pop();
                cnt++;
            }
        }
       if(stack.isEmpty())
           return true;
          else
           return false;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值