剑指offer-------链表、二叉树

本文总结了剑指Offer中关于链表和二叉树的题目,包括从尾到头打印链表、删除链表结点、寻找链表中环的入口、反转链表、合并排序链表、复杂链表复制、二叉搜索树转双向链表、序列化二叉树等。此外,还介绍了二叉树的重建、下一个节点、子结构判断、镜像、对称性检查、层次遍历以及二叉搜索树的相关问题,提供了解题思路和方法。
摘要由CSDN通过智能技术生成

剑指offer思路总结

 

1、链表

1.1 从尾到头打印链表

       题目概述:给出一个链表的表头,从尾到头反过来打印出每个结点的值。

(1)若可以改变链表

      思路:把链表反转,在遍历。

(2)不能改变链表

      思路:可以借助辅助栈的后进先出;

                 可以利用递归实现。

 

1.2 删除链表的结点

      题目一大致描述:给定一个单向链表的头结点和一个结点指针,要求在O(1)时间内删除该节点

      思路:因为要求是O(1),所以考虑先复制后结点的值,然后删除。需要注意头结点,尾结点情况。

   

      题目二大致描述:链表有序,删除链表中重复的结点

      思路:为了避免头结点可能被删除的情况,引进一个新的头结点,然后使用两个指针pre,cur进行即可。

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head == null)
            return null;
        ListNode newHead = new ListNode(-1);
        newHead.next = head;
        ListNode cur = head;   //当前节点
        ListNode pre = newHead; //前驱节点
        while(cur != null && cur.next != null){
            if(cur.val == cur.next.val){  //判断是否相同
                int a = cur.val;
                while(cur != null && cur.val == a){  //找出不相同的节点
                    cur = cur.next;
                }
                pre.next = cur;    
                // pre = cur;  如果多了这条,会出错,所以需要注意
            }else{
                pre = cur;  
                cur = cur.next;
            }
        }   
        return newHead.next;
    }
}

      题目三:去重复元素,但每一个只保留一次。

      思路:一次遍历,出现相同就跳一个指针。

 

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode current = head;
        while(current != null && current.next != null){
            if(current.val == current.next.val){
                current.next = current.next.next;
            }else {
                current = current.next;
            }
        }
        return head;
    }
}

1.3 链表中的倒数第k个结点

      题目大致概述:给出一个链表,输出链表中倒数第k个结点。

      思路双指针,一个先走k步,然后同时起步。

 

1.4 链表中环的入口

     题目概述:一个链表中包含环,找出环的入口

     思路: 双指针,一个每次走一步,一个每次走两步,当相遇时,第二个指针刚好比第一个走多了一个环大小的距离,然后一个指针从头节点重新开始走,相遇时的节点就是环的入口。

 

1.5 反转链表

    题目概述:给定一个链表,反转该链表。

    思路:双指法。一个指针记录为反转的链表头节点,一个是反转了的链表的头节点。

class Solution {
    public ListNode reverseList(ListNode head) {
         if(head == null || head.next == null)
             return head;
         ListNode pre = null;
         ListNode cur = head;
         while(cur != null){
             ListNode next = cur.next;
             cur.next = pre;
             pre = cur;
             cur = next;
         }
         return pre;
    }
}

1.6 合并两个排序的链表

    题目概述:给定俩个递增的链表,合并两个链表,使其依旧是递增排序。

    思路:递归法。

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
         if(l1 == null)
             return l2;
         if(l2 == null)
             return l1;
         ListNode head = null;
         if(l1.val > l2.val){
             head = l2;
             head.next = mergeTwoLists(l1 , l2.next);
         }else {
             head = l1;
             head.next = mergeTwoLists(l1.next , l2);
         }    
         return head;
               
    }
}

1.7 复杂链表的复制

     题目描述:给定一个复杂的链表,包括一个next指针和一个指向随意地方的指针。复制一份。

     思路:两次遍历法,第一次遍历是在原链表的每一个结点后复制一份新的结点;第二次是对随机指针的复制并分开链表。

class Solution {
    public Node copyRandomList(Node head) {
        if(head == null)
           return head;
        changeFirst(head);
        changeSecond(head);
        return changeThird(head);   
    }
    //进行next结点的复制
    private void changeFirst(Node head){
        Node root = head;
        while(root != null){
            Node node = new Node(0);
            node.val = root.val;
            node.next = root.next;
            root.next = node;
            root = node.next;
        }
    }
    //ramdom结点的复制
    private void changeSecond(Node head){
        Node root = head;
        while(root != null){
            Node node = root.next;
            if(root.random != null)
               node.random = root.random.next;
            root = node.next;   
        }
    }
    //进行拆分
    private Node changeThird(Node head){
        Node phead = head;
        Node nCloneHead = null;
        Node nCloneNode = null;
        if(phead != null){
            nCloneHead = nCloneNode = phead.next;
            phead.next = nCloneHead.next;
            phead = phead.next;
        }
        while (phead != null){
            nCloneNode.next = phead.next;
            nCloneNode = nCloneNode.next;
            phead.next = nCloneNode.next;
            phead = phead.next;
        }
        return nCloneHead;
    }
}

1.8 二叉搜索树与双向链表

     题目描述:给定一棵二叉搜索树,将二叉搜索树转成一棵排序的双向链表。不能创建新节点,只能调整树中结点指针的指向

     思路:利用递归法。                                                                                                                                

/**
*算法流程:
*dfs(cur): 递归法中序遍历;
*
*终止条件: 当节点 curcur 为空,代表越过叶节点,直接返回;
*递归左子树,即 dfs(cur.left) ;
*构建链表:
*当 prepre 为空时: 代表正在访问链表头节点,记为 headhead 。
*当 prepre 不为空时: 修改双向节点引用,即 pre.right = curpre.right=cur , cur.left = *precur.left=pre ;
*保存 curcur : 更新 pre = curpre=cur ,即节点 curcur 是后继节点的 prepre ;
*递归右子树,即 dfs(cur.left) ;
**/

class Solution {
    Node pre, head;
    public Node treeToDoublyList(Node root) {
        if(root == null) return null;
        dfs(root);
        head.left = pre;
        pre.right = head;
        return head;
    }
    void dfs(Node cur) {
        if(cur == null) return;
        dfs(cur.left);    //遍历左孩子

        //如果为第一个最左孩子,设置为头结点,否则更新
        if(pre != null) pre.right = cur;
        else head = cur;

        cur.left = pre;
        pre = cur;

        //递归右孩子
        dfs(cur.right);
    }
}

1.9  序列化二叉树

       题目概述:实现两个函数,分别用来序列化和反序列化二叉树

       思路:序列化时利用前序遍历,当遇到null ,用&表示;反序列化时,利用一个全局变量,构造即可。

public class Codec {
    private String results; // 定义一个成员变量存储序列化结果

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if (root == null) {
            return "[]";
        } // root为null直接返回空字符串即可

        // 创建一个队列存储每一个非null节点
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root); // 将root先放进去
        results = "[" + root.val;
        while(!queue.isEmpty()) {
            // 每次从队列中取出一个节点,根据其左右子节点是否为null进行字符串拼接
            TreeNode tmp = queue.poll();
            if (tmp.left != null) {
                queue.offer(tmp.left);
                results += "," + tmp.left.val;
            } else {
                results += ",null";
            }
            // 上面处理left,下面处理right
            if (tmp.right != null) {
                queue.offer(tmp.right);
                results += "," + tmp.right.val;
            } else {
                results += ",null";
            }
        }
        // 处理完成之后添加上结束符
        results += "]";
        return results;
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if (data.length() == 2) {
            return null;
        }

        // 去除收尾的中括号字符
        data = data.substring(1, data.length() - 1);
        // 利用逗号分隔符获取每一个节点的值
        String[] vals = data.split(",");
        // 定义队列存储每一个有效节点,为了构建其左右子节点
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        TreeNode root = new TreeNode(Integer.valueOf(vals[0]));
        queue.offer(root); // 第一个元素为root节点
        int i = 1; // 标记后续处理的节点值(包含null)
        while (!queue.isEmpty()) {
            TreeNode tmp = queue.poll();
            // 从队列中取出节点,然后根据是否为null,依次设置left和right
            // 如果不是null,则需要加入队列,后续需要处理此有效节点的左右节点
            if (vals[i].equals("null")) {
                tmp.left = null;
            } else {
                tmp.left = new TreeNode(Integer.valueOf(vals[i]));
                queue.offer(tmp.left);
            }
            i++;
            if (vals[i].equals("null")) {
                tmp.right = null;
            } else {
                tmp.right = new TreeNode(Integer.valueOf(vals[i]));
                queue.offer(tmp.right);
            }
            i++;
        }

        return root;
    }
}

 1.10  两个链表的第一个公共节点

       题目概述:输入两个链表,找出它们的第一个公共节点。

       思路:  采用路径法。也就是让两个指针同时走一遍两个链表,相遇时的点就是第一个公共节点。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
           ListNode list1 = headA;
           ListNode list2 = headB;
           while(list1 != list2){
               list1 = list1 == null ? headB : list1.next;
               list2 = list2 == null ? headA : list2.next;
           } 
           return list1;
    }
}

2、树

2.1 重建二叉树

      题目概述:输入某个二叉树的前序遍历和中序遍历的结果,重建该二叉树。

      思路:在二叉树的前序遍历序列中,第一个数字总是树的根结点的值。但在中序遍历序列中,根结点的值在序列的中间,左子树的结             点的值位于根结点的值的左边,而右子树的结点的值位于根结点的值的右边。因此我们需要扫描中序遍历序列,才能找到根结点的             值。
  

       
             分别找到了左、右子树的前序遍历序列和中序遍历序列,我们就可以用同样的方法分别去构建左右子树。换句话说,这是一个递归的过程。
 

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
         if(preorder.length == 0)
             return null;
         return reConstructBinaryTree(preorder , 0 , preorder.length - 1 ,
                       inorder , 0 , inorder.length - 1);
    }
    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++)
            if(in[i]==pre[startPre]){
                root.left=reConstructBinaryTree(pre , startPre+1, startPre+i-startIn, in, startIn, i - 1);
                root.right=reConstructBinaryTree(pre, startPre + i - startIn + 1,endPre, in, i + 1, endIn);
            }
        return root;
    }
}

2.2 二叉树的下一个节点

      题目概述:给定一棵二叉树和其中的一个节点,其中二叉树还包含一个指向父节点的指针,如何找出中序遍历的下一个节点。

      思路:分三种情况进行分析:

                 (1)如果给出的结点有右子树,那么它的下一个节点就是它的右子树的最左子结点;

                 (2)如果给出的结点没有右子树,如果该节点是它父节点的左子树,那么下一个节点就是父节点;

                 (3)如果给出的结点既没有右子树,也不是父节点的左子树,而是父节点的右子树,那么可以沿着指向父节点的指针一直向上遍历,直到找到一个是它父节点的左子结点,若没找到,则下一个节点为null。

 

2.3 树的子结构

     题目概述:输入两颗二叉树A和B,判断B是不是A的子结构。

     思路:递归法。先判断第一个节点是否相同,若相同,判断以该节点开始的树是否含有子结构;若不是,递归左右子树。

class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        if(A == null || B == null)
            return false;
        boolean result = false;
        if(A != null && B != null){
             if(A.val == B.val){
                 result = isTree(A , B);
             }
             if(!result){
                 result = isSubStructure(A.left , B);
             }
             if(!result){
                 result = isSubStructure(A.right , B);
             }
        }  
        return result;
        
    }
    private boolean isTree(TreeNode A , TreeNode B){
        if(B == null)
            return true;
        if(A == null)
            return false;    
        if(A.val == B.val){
            return isTree(A.left , B.left) && isTree(A.right , B.right);
        }    
        return false;
    }
}

2.4二叉树的镜像。

    题目概述:给定一颗二叉树,输出其镜像二叉树

    思路:利用递归法即可。

class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if(root == null)
           return null;
        TreeNode head = root;
        TreeNode temp = head.left;
        head.left = mirrorTree(root.right);
        head.right = mirrorTree(temp);
        return head;   
    }
}

2.5 对称二叉树。

    题目概述:给定一颗二叉树,判断这颗二叉树是否为对称二叉树。如果一颗树与他的镜像二叉树相同,则认为他是是一颗对称二叉树

   思路:利用递归遍历法。如果一颗二叉树为对称二叉树,那么他的前序遍历和后续遍历应该是相同的。

class Solution {
    public boolean isSymmetric(TreeNode root) {
        return isHashTree(root , root);
    }
    private boolean isHashTree(TreeNode root1 , TreeNode root2){
        if(root1 == null && root2 == null)
           return true;
        if(root1 == null || root2 == null)
           return false;
        if(root1.val != root2.val)
           return false;
        return isHashTree(root1.left , root2.right)
                && isHashTree(root1.right , root2.left);      
    }

}

2.6 从上到下打印二叉树

    题目一概述:给定一颗二叉树,同一层的节点从左到右打印(不分行)。

    思路:借助一个队列,每次遍历完队列的一个节点时,就将该节点不为空的节点进队列。

class Solution {
    public int[] levelOrder(TreeNode root) {
        if(root == null)
           return new int[]{};
        ArrayList<Integer> list = new ArrayList<>();   
        Queue<TreeNode> queue = new LinkedList<>();   
        queue.offer(root);
        while(!queue.isEmpty()){
           TreeNode node = queue.poll();
           list.add(node.val);
           if(node.left != null)
              queue.offer(node.left);
           if(node.right != null)
            queue.offer(node.right);   
        }
        int[] result = new int[list.size()];
        for(int i = 0; i < result.length; ++i){
            result[i] = list.get(i);
        } 
        return result;
    }
}

    题目二概述:给定一颗二叉树,同一层的节点从左到右打印(分行)。

    思路:和题目一一样,不过需要定义两个变量参数,一个记录当前行的节点数,一个记录下一行的节点数。

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        List<List<Integer>> res = new ArrayList<>();
        if(root != null) queue.add(root);
        while(!queue.isEmpty()) {
            List<Integer> tmp = new ArrayList<>();
            for(int i = queue.size(); i > 0; i--) {
                TreeNode node = queue.poll();
                tmp.add(node.val);
                if(node.left != null) queue.add(node.left);
                if(node.right != null) queue.add(node.right);
            }
            res.add(tmp);
        }
        return res;
    }
}

    题目三概述:给定一颗二叉树,之字形打印(分行)。

    思路:借助一个队列即可实现。

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
         Queue<TreeNode> queue = new LinkedList<>();
        List<List<Integer>> res = new ArrayList<>();
        if(root != null) queue.add(root);
        while(!queue.isEmpty()) {
            LinkedList<Integer> tmp = new LinkedList<>();
            for(int i = queue.size(); i > 0; i--) {
                TreeNode node = queue.poll();
                if(res.size() % 2 == 0) tmp.addLast(node.val); // 偶数层 -> 队列头部
                else tmp.addFirst(node.val); // 奇数层 -> 队列尾部
                if(node.left != null) queue.add(node.left);
                if(node.right != null) queue.add(node.right);
            }
            res.add(tmp);
        }
        return res;
    }
}

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

    题目概述:给定一个数组,判断该数组是否为二叉搜索树的后序遍历结果。

    思路:递归,后序遍历的最后一个元素为根节点。可以根据根节点找出其左子树,右子树,然后分别对其子树进行递归判断即可。

class Solution {
    public boolean verifyPostorder(int[] postorder) {
        return recur(postorder, 0, postorder.length - 1);
    }
    boolean recur(int[] postorder, int i, int j) {
        if(i >= j) return true;
        int p = i;
        while(postorder[p] < postorder[j]) p++;
        int m = p;
        while(postorder[p] > postorder[j]) p++;
        return p == j && recur(postorder, i, m - 1) && recur(postorder, m, j - 1);
    }
}

2.8 二叉树中和为某一值得路径

    题目概述:给定一个二叉树和一个整数,打印出二叉树中和为节点值得所有路径。(路径就是从根节点到叶子节点形成的路径 )

    思路可以采用递归方法,利用树的前序遍历。


class Solution {
    List<Integer> list = new ArrayList<>();
    List<List<Integer>> lists = new ArrayList<>();
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        if(root == null){
            return lists;
        }
        pathS(root,sum);
        return lists;
        
    }

    public void pathS(TreeNode root, int sum){
        if(root == null){
            return;
        }
        list.add(root.val);
        sum -= root.val;
        if(root.left == null && root.right == null && sum == 0){  //符合条件,记录下来
            lists.add(new ArrayList(list));
            list.remove(list.size()-1);    //移除list的最后一个元素,回朔上一步
            return;
        }
        pathS(root.left,sum);
        pathS(root.right,sum);
        list.remove(list.size()-1);   
    }
}

2.9 二叉搜索树的第k大节点

      题目概述:给定一个二叉搜索树,找出其中第k大的节点。

      思路:(1) 可以利用二叉树的中序遍历(不过是先从右子节点开始遍历);

                 (2)也可以借助栈进行深度遍历,到k为止。

class Solution {
    int res, k;
    public int kthLargest(TreeNode root, int k) {
        this.k = k;
        dfs(root);
        return res;
    }
    void dfs(TreeNode root) {
        if(root == null) return;
        dfs(root.right);
        if(k == 0) return;
        if(--k == 0) res = root.val;
        dfs(root.left);
    }
}

2.10 二叉树的深度

     题目一概述:二叉树的深度。

     思路:直接采用递归法就可以搞定。

class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null)
            return 0;
        int leftDe = maxDepth(root.left);  //递归计算左子树高度
        int rightDe = maxDepth(root.right); //递归计算右子树高度
        return leftDe > rightDe ? leftDe + 1 : rightDe + 1;    
    }
}

     题目二概述:判断一棵是否为平衡二叉树

     思路递归,但是注意重复节点的遍历。

class Solution {
    public boolean isBalanced(TreeNode root) {
        if(root == null)   
           return true;
        return depth(root) != -1;   
    }

    private int depth(TreeNode root){
        if(root == null)
           return 0;
        int leftDepth = depth(root.left);  //递归左子树
        if(leftDepth == -1){
            return -1;
        }  
        int rightDepth = depth(root.right); //递归右子树
        if(rightDepth == -1){
            return -1;
        } 
        //若子树不满足平衡条件,直接返回 -1 ,否则返回该子树的深度
        return Math.abs(leftDepth - rightDepth) > 1 ? -1 : Math.max(leftDepth , rightDepth) + 1;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值