• 剑指Offer-3~9题


    3. 数组中重复的数字

    题目描述:

    在一个长度为 (n​) 的数组里的所有数字都在 (0​)(n-1​) 的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的,也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为 (7​) 的数组 ({2,3,1,0,2,5,3}​),那么对应的输出是第一个重复的数字 (2​)

    Input:
    {2, 3, 1, 0, 2, 5}
    
    Output:
    2
    

    思路:

    最直接的策略是使用哈希表,时间复杂度为 (O(n)),但是它提高时间效率是以一个大小为 (O(n)) 的哈希表为代价的,可以采用更好的方式使空间复杂度降为 (O(1)​)

    本题数组里的元素都在 (0)(n-1) 的范围内,我们从头到尾扫描数组的每个元素,将元素 (j = numbers[i]) 与下标为 (j) 的元素进行位置交换,如果 (numbers[j]) 已经等于 (j),则不用进行交换,数字 (j) 就是重复数字((i e j))。

    public class Solution {
        /**
        * numbers: 数组;length: 数组的长度;duplication[0]: 存放任意一个重复的数字
    	* 返回为 true 代表为数组中存在重复数字,false 代表没有
        */
        public boolean duplicate(int[] numbers,int length,int[] duplication) {
            for(int i = 0; i < length; i++) {
                while(i != numbers[i]) {
                    int j = numbers[i];
                    if(numbers[j] == j) {
                        //要替换的位置下标 j 上的元素已经为 j,说明已经存在重复元素
                        duplication[0] = j;
                        return true;
                    }
                    //进行位置上的元素替换
                    numbers[i] = numbers[j];
                    numbers[j] = j;
                }
            }
            return false;
        }
    }
    

    4. 二维数组中的查找

    题目描述:

    在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

    Consider the following matrix:
    [
      [1,   4,  7, 11, 15],
      [2,   5,  8, 12, 19],
      [3,   6,  9, 16, 22],
      [10, 13, 14, 17, 24],
      [18, 21, 23, 26, 30]
    ]
    
    Given target = 5, return true.
    Given target = 20, return false.
    

    思路:

    该二维数组中的一个数,小于它的数一定在其左边,大于它的数一定在其下边。因此,从右上角开始查找,就可以根据 (target) 和当前元素的大小关系来缩小查找区间,如果当前元素等于 (target),返回 (true);如果当前元素小于 (target),则下个位置左移;如果当前元素大于 (target),则下个位置下移;如果下个位置超出了数组边界范围,返回 (false)

    public class Solution {
        public boolean Find(int target, int [][] array) {
            if(array == null || array.length == 0 || array[0].length == 0) {
                return false;
            }
            
            int m = array.length, n = array[0].length;
            
            int i = 0, j = n - 1;
            while(i < m && j >= 0) {
                if(array[i][j] == target) {
                    return true;
                }else if(array[i][j] < target) {
                    i++;
                }else{
                    j--;
                }
            }
            return false;
        }
    }
    

    5. 替换空格

    题目描述:

    请实现一个函数,将一个字符串中的每个空格替换成 “%20”。例如,当字符串为 We Are Happy,则经过替换之后的字符串为 We%20Are%20Happy。

    思路:

    本题可以直接使用 (String)(replace()) 或者 (replaceAll()) 方法,但是该题的本意是希望在一个字符数组上实现该功能。

    public class Solution {
        public String replaceSpace(StringBuffer str) {
        	return str.toString().replace(" ", "%20");
        }
    }
    

    将字符数组扩充到能容纳替换后的字符串大小,使用指针 (p1) 指向原来的字符数组末端,指针 (p2) 指向扩充后的字符数组末端。指针 (p1) 向前移动,$p1 $ 遇到非空格字符,将该字符拷贝到 (p2) 位置,(p2) 向前移动一位;(p1) 遇到空格字符,(p2) 向前移动三位,(p2) 后续的三位放置 "%20";直达两指针相遇结束。

    public class Solution {
        public String replaceSpace(StringBuffer str) {
            int p1 = str.length() - 1;
            //增大数组空间
        	for(int i = 0; i <= p1; i++) {
                if(str.charAt(i) == ' ') {
                    str.append("  ");   
                }
            }
            
            int p2 = str.length() - 1;
            while(p1 < p2) {
                if(str.charAt(p1) != ' ') {
                    str.setCharAt(p2--, str.charAt(p1--));
                }else {
                    str.setCharAt(p2--, '0');
                    str.setCharAt(p2--, '2');
                    str.setCharAt(p2--, '%');
                    p1--;
                }
            }
            return str.toString();
        }
    }
    

    6. 从尾到头打印链表

    题目描述:

    输入一个链表,按链表从尾到头的顺序返回一个 (ArrayList)

    思路:

    思路一:

    使用递归的方式,逆序打印该链表。

    /**
    *    public class ListNode {
    *        int val;
    *        ListNode next = null;
    *
    *        ListNode(int val) {
    *            this.val = val;
    *        }
    *    }
    */
    import java.util.ArrayList;
    public class Solution {
        public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
            ArrayList<Integer> ret = new ArrayList<>();
            if(listNode == null) {
                return ret;
            }
            dfs(listNode, ret);
            return ret;
        }
        
        public void dfs(ListNode listNode, ArrayList<Integer> ret) {
            if(listNode.next != null) {
                dfs(listNode.next, ret);
            }
            ret.add(listNode.val);
        }
    }
    

    思路二:

    使用头插法的方式得到一个逆序的链表。

    头插法的头结点 (head) 是一个额外节点,这个节点不存储值,(head.next) 才是链表的第一个节点。当需要插入一个新的节点 (node),该节点的插入位置在 (head)(head.next) 之间,所以插入时 (node.next = head.next)(head.next =node)

    /**
    *    public class ListNode {
    *        int val;
    *        ListNode next = null;
    *
    *        ListNode(int val) {
    *            this.val = val;
    *        }
    *    }
    */
    import java.util.ArrayList;
    public class Solution {
        public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
            ListNode head = new ListNode(-1);
            while(listNode != null) {
                ListNode node = listNode.next;
                listNode.next = head.next;
                head.next = listNode;
                listNode = node;
            }
            ArrayList<Integer> ret = new ArrayList<>();
            while(head.next != null) {
                ret.add(head.next.val);
                head = head.next;
            }
            return ret;
        }
    }
    

    思路三:

    使用栈结构。

    /**
    *    public class ListNode {
    *        int val;
    *        ListNode next = null;
    *
    *        ListNode(int val) {
    *            this.val = val;
    *        }
    *    }
    */
    import java.util.*;
    public class Solution {
        public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
            Stack<Integer> stack = new Stack<>();
            while(listNode != null) {
                stack.push(listNode.val);
                listNode = listNode.next;
            }
            ArrayList<Integer> ret = new ArrayList<>();
            while(!stack.isEmpty()) {
                ret.add(stack.pop());
            }
            return ret;
        }
    }
    

    7. 重建二叉树

    题目描述:

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

    思路:

    可以从二叉树的前序遍历和中序遍历序列中确定根节点的值、左子树和右子树的范围。

    /**
     * Definition for binary tree
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode(int x) { val = x; }
     * }
     */
    public class Solution {
        public TreeNode reConstructBinaryTree(int[] pre,int [] in) {
            return buildSubTree(pre, in, 0, 0, pre.length);
        }
        
        //pStart 是子树在前序遍历数组的起始索引,iStart 是子树在中序遍历数组的起始索引,len 是子树节点数目(大小)
        public TreeNode buildSubTree(int[] pre, int[] in, int pStart, int iStart, int len) {
            if(len == 0) {
                return null;
            }
            
            //子树根节点
            int rootVal = pre[pStart];  
            TreeNode root = new TreeNode(rootVal);
            
            //寻找中序遍历数组中子树的根节点
            int index = iStart;
            while(in[index] != rootVal) {
                index++;
            }
            int lLen = index - iStart; //根节点左子树的大小
            int rLen = len - lLen - 1; //根节点右子树的大小
            
            //左右孩子节点
            TreeNode lNode = buildSubTree(pre, in, pStart + 1, iStart, lLen);
            TreeNode rNode = buildSubTree(pre, in, pStart + lLen + 1, iStart + lLen + 1, rLen);
            
            root.left = lNode;
            root.right = rNode;
            return root;
        }
    }
    

    8. 二叉树的下一个结点

    题目描述:

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

    思路:

    中序遍历顺序的下一个节点可以分两种情况讨论:

    • 如果一个节点的右子树不为空,那么该节点的下一个节点是其右子树的最左节点。例如:上图中的节点 (b) 的下一个节点为 (h), 节点 (a) 的下一个节点是 (f),节点 (e) 的下一个节点是 (i)
    • 如果一个节点的右子树为空,我们沿着父节点的指针往上遍历,如果遍历的某个节点是其父节点的左孩子节点,则该节点的父节点便是我们要寻找的下一个节点。例如:上图中的节点 (i) 的下一个节点为 (a),节点 (d) 的下一个节点为 (b)
    /*
    public class TreeLinkNode {
        int val;
        TreeLinkNode left = null;
        TreeLinkNode right = null;
        TreeLinkNode next = null;
    
        TreeLinkNode(int val) {
            this.val = val;
        }
    }
    */
    public class Solution {
        public TreeLinkNode GetNext(TreeLinkNode pNode){
            if(pNode == null) {
                return null;
            }
            
            //右子树不为空,寻找右子树最左端
            if(pNode.right != null) {
                TreeLinkNode node = pNode.right;
                while(node.left != null) {
                    node = node.left;
                }
                return node;
            }
            
            //右子树为空,往父节点方向向上寻找,直到找到某个节点是其父节点的左孩子
            if(pNode.right == null) {
                while(pNode.next != null) {
                    TreeLinkNode parent = pNode.next;
                    if(parent.left == pNode) {
                        return parent;
                    }
                    pNode = parent;
                }
            }
            return null;
        }
    }
    

    9. 用两个栈实现队列

    题目描述:

    用两个栈来实现一个队列,完成队列的 (Push)(Pop) 操作。 队列中的元素为 (int​) 类型。

    思路:

    使用两个栈 (in)(out)(in) 栈负责数据的入栈操作,(out) 栈负责数据的出栈操作。需要注意的是,(in) 栈的数据倾倒到 (out) 栈过程要一次性将 (in) 栈全部数据倒入。

    import java.util.Stack;
    
    public class Solution {
        Stack<Integer> stack1 = new Stack<Integer>();  //in 栈
        Stack<Integer> stack2 = new Stack<Integer>();  //out 栈
        
        public void push(int node) {
            stack1.push(node);  //入栈数据全部进入 in 栈
        }
        
        public int pop() {
            if(stack2.isEmpty()) {
                //out 栈为空,要将 in 栈全部数据一次性倾倒到 out 栈
                while(!stack1.isEmpty()) {
                    stack2.push(stack1.pop());
                }
            }
            return stack2.pop();
        }
    }
    

    参考

    1. https://cyc2018.github.io/CS-Notes/#/notes/%E5%89%91%E6%8C%87%20Offer%20%E9%A2%98%E8%A7%A3%20-%203~9
    2. 《剑指OFFER 名企面试官精讲典型编程题 第2版》
  • 相关阅读:
    【MySQL】Mysql模糊查询like提速优化
    mysql实现row_number()和row_number() over(partition by)
    mysql limit 分页优化
    js正则验证方法大全
    如何获取select下拉框中option选中的文本值
    js控制input框只能输入数字和一位小数点和小数点后面两位小数
    js页面 :函数名 is not defined
    算法名称 Alias Method
    解决mybatis报错Result Maps collection does not contain value for java.lang.Integer
    nginx的rewrite规则
  • 原文地址:https://www.cnblogs.com/zongmin/p/11612478.html
Copyright © 2020-2023  润新知