剑指offer之每日6题 ----- 第一天

前言

本人算法基础比较薄弱,文章里的代码基本都是基于很多大神的或者书本里的代码,仅仅只作为自己学习使用,打牢基础。

原题链接:

1. 二维数组中的查找
题目描述

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

思路

由于这个二维数组是有序的,就可以用二分查找法来进行逐行查找,这样时间复杂度是O(nlogn)
有没有更有效的方法呢,我们可以画图来找一下规律,把二维数组画成一个矩形,举个例子来分析一下,在下面的数组中,去查找整数7是否存在

假如从第一个数字1开始与7比较,7>1,则可以往右或者往下走,那这样的话就比较麻烦了,如果我们可以只从一个方向走,不就很容易了?怎么才可以从一个方向走,那也就是说可以从一个角上选择数字来进行比较,有4个角可以选择,左上角也就是1,刚刚分析过了不行
右上角的数字特点是这一行中最大的,这一列中又是最小的,如果target这个数字是小于这个左上角的数字,那么target肯定是小于这一列中所有的数字,这样就可以把这一列都剔除掉,那么只能够向左移动,如果target大于这个左上角数字,那么target肯定是大于这一行所有的数字,这样可以把这一行数字都踢出去掉,也只能够向下移动了,不用再进行比较了。
左下角,特点是这一列中最大的,这一行中最小的,if target 小于这个左下角的数字,那么target肯定小于这一行的数字,所以可以把行剔除,向上走,同理的大于,把列剔除,向右走
右下角,行是最大的值,列也是最大值,也无法确定可以向哪个方向走。
所以可以选择右上角或者左下角数字,而且这样不用去比较数组中的每一个数字,就省去了很多时间。
时间复杂度为o(2n) 比二分查找要优一些

代码
public boolean Find(int target, int [][] array) {
        int row =0;
        int colum = array[0].length-1;
        while (row < array.length && colum >=0){
            if (target == array[row][colum]){
                return true;
            }else if (target > array[row][colum]){
                row++;
            }else{
                colum-- ;
            }
        }
        return false;
    }
2. 替换空格
题目描述

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

思路

首先要弄清楚,题目是可以是否可以创建新数组,如果可以,可以直接创建新数组,将之前的内容拷贝到新的数组中,并且把空格替换,如果需要在原来数组上进行修改,那么原来数组后面就需要有足够多的内存
假设是在原数组上进行替换,那么就可以从头到尾遍历字符串,然后遇见空格直接替换,把空格后面的字符都向后移动2个字符,那么这样的时间复杂度就是n的平方,假设字符串长度为n,那么对每个空格符来说,都需要向后移动o(n)个字符,那么对于o(n)个空格来说,就需要移动n的平方次;
那么有没有更好的方法?由于从前往后移动,后面的字符串会出现一个字符被多次移动的情况,所以效率才会比较低,怎么可以减少移动次数呢,可以试试从后往前移动字符,遇见空格就多移动两格,那么这样每个字符就只一移动了一次,时间复杂度也就是o(n)
首先需要知道新数组的长度,也就需要知道有多少个空格,新数组的长度就等于2*空格数,然后从旧数组中的最后一个字符,开始复制到新数组中,遇见空格,就替换,然后继续往前移动

代码
 public String replaceSpace(StringBuffer str) {
       int spaceNum = 0;

        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i) == ' '){
                spaceNum++;
            }
        }

        int indexOld = str.length() - 1;
        int newLength = str.length() + 2 * spaceNum;
        int indexNew = newLength -1;
        
        str.setLength(newLength);

        for (;indexOld >= 0 && indexOld<newLength;--indexOld){
            if (str.charAt(indexOld) == ' '){
                str.setCharAt(indexNew--,'0');
                str.setCharAt(indexNew--,'2');
                str.setCharAt(indexNew--,'%');
            }else{
                str.setCharAt(indexNew--,str.charAt(indexOld));
            }
        }

        return str.toString();
    }
3. 从尾到头打印链表
题目描述

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

思路

遍历链表的时候是从头到尾遍历,而题目要求是从尾到头输出链表,这个就是一个典型的“后进先出”,所以可以使用栈来实现。
另外递归的本质就是一个栈结构,所以也可以使用递归来进行遍历输出

  public class ListNode {
        int val;
        ListNode next = null;
        ListNode(int val) {
            this.val = val;
        }
    }
    
  //基于循环和栈实现的
  public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        Stack<Integer> stack = new Stack<>();

        while (listNode!=null){
            stack.push(listNode.val);
            listNode=listNode.next;
        }

        ArrayList<Integer> arrayList = new ArrayList<>();
        while (!stack.isEmpty()){
            arrayList.add(stack.pop());
        }
        return arrayList;
    }

//基于栈实现的
  public class Solution {
      ArrayList<Integer> arrayList = new ArrayList<>();
      public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
       if (listNode != null) {
            printListFromTailToHead(listNode.next);
            arrayList.add(listNode.val);
        }
        return arrayList;
    }
}
4.重建二叉树
题目描述

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

思路

考察二叉树的前序和中序遍历的特点,前序遍历先访问父节点,然后左节点,然后右节点,中序遍历是先访问左节点,后父节点,最后右节点,那么也就是说前序遍历的第一个数字肯定是根节点,然后我们可以找到父节点在中序遍历中的位置,然后在这个位置前半部分是这个父节点的左子节点,后半部分是这个父节点的右子节点,以此类推,可以重建二叉树。

  public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        TreeNode root =reConstructBinaryTree(pre,0,pre.length-1,in,0,in.length-1);
        return root;
    }
    
     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,i-startIn+startPre+1,endPre,in,i+1,endIn);;
            }
        }

        return root;
    }
5. 用两个栈实现队列
题目描述

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

思路

栈的特点是“后进先出”,队列的特点是“先进先出”
具体分析一下向这个队列中添加和删除元素的过程,比如说先插入a,插入到stack1中,然后在依次插入b元素和c元素,这样stack1中{a,b,c},此时需要删除一个元素,按照“先进先出”的规则,最先删除a,但是在stack1中栈顶元素是c,不是a,不能够直接删除,那么就可以把stack1中的元素,压入stack2中
这样stack2中就是{c,b,a},此时stack1是空的,而stack2的栈顶元素正好是a,就可以直接删除了,然后stack2的元素{c,b},可以直接弹出stack2中的元素了

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    
     public void push(int node) {
            stack1.push(node);
        }

     public int pop() {

        if (stack1.isEmpty() && stack2.isEmpty()){
            throw new RuntimeException("Queue is empty!");
        }
         if (stack2.isEmpty()){

            while (!stack1.isEmpty()){
               stack2.push(stack1.pop());
           }
       }

            return stack2.pop();
        }
}
6. 旋转数组的最小数字
题目描述

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

思路

最直观的的解法就是,遍历一遍数组,然后找出最小值,这样的解法的时间复杂度是o(n),这个普通的数组解法没有什么区别,而且题目中很多的信息都没有用到,比如说是一个非减排序的数组,也就是个递增数组,看到递增,就可以使用二分查找了,这个的时间复杂度是O(logn)比O(n)要小的多,原生的数组是递增的,但是旋转之后并不是了,但是可以发现旋转之后的数组其实是两个递增的子数组,而且这个分界线就是最小值.
和二分查找法一样,我们先用两个指针分别之前数组的第一个元素和最后一个元素,然后找到在中间位置的元素mid,如果mid大于或者等于第一个元素,那么mid是处在第一个子数组中,也就是说明最小值应该在mid的后面,所以可以将指向第一个元素的指针去指向mid位置,再继续寻找,如果mid是小于或者等于最后一个元素的,,那么也就是说明mid是位于第二个子数组中,那么最小值应该在mid的前面,就可以把第二个指针指向mid位置继续寻找
考虑一种特殊情况,那就是第一个指针,第二个指针和中间元素都相等的时候,就需要使用顺序排序了,二分查找就不适用了

  public int minNumberInRotateArray(int [] array) {
        if (array.length <= 0) {
            return 0;
        }

        int low = 0;
        int high = array.length - 1;
        int mid = 0;
        
        while (array[low] >= array[high]) {
            if (high - low == 1) {
                mid = high;
                break;
            }

            mid = (low + high) >> 1;
            
            //也就是三者相等的时候,则采取顺序排序
            if (array[low] == array[mid] && array[low] == array[high]){
                return MinInOrder(array,low,high);
            }
            if (array[mid] >= array[low]) {
                low = mid;
            } else if (array[mid] <= array[high]) {
                high = mid;
            }
        }
        return array[mid];
    }
    
     private int MinInOrder(int[] array, int low, int high) {

        int result = array[low];

        for (int i = low+1; i < high; ++i) {
            if (result > array[i]){
                result = array[i];
            }
        }

        return result;
    }

猜你喜欢

转载自blog.csdn.net/YuQing_Cat/article/details/85998875