《剑指offer》刷题——【查找和排序】面试题11:旋转数组的最小数字(java实现)

一、面试考点

1. 查找:

(1)顺序查找;
(2)二分查找:重点,必须能手写完整的代码(循环、递归)
(3)哈希表查找:考察重点在于对应的数据结构而不是算法

  • 优点:利用它能够在O(1)时间内查找某一元素,市销率最高的查找方式;
  • 缺点:需要额外的空间来实现哈希表

(4)二叉排序树查找:考察重点在于对应的数据结构而不是算法

  • 数据结构:二叉搜索树

2. 排序:

(1)面试经常会要求比较:插入排序、冒泡排序、归并排序、快速排序等不同算法的优劣;
(2)对各种排序算法的特点烂熟于心,能够从额外空间消耗、平均时间复杂度、最差时间复杂度等方面比较他们的优缺点
(3)面试经常会要求应聘者写:快排

3. 快排实现:

4. 员工年龄排序:

import java.util.Arrays;
 
public class AgeSort {
 
    public static void main(String[] args) {
 
        int[] ages = new int[] { 23, 45, 32, 43, 21, 24, 25, 23, 22, 22, 21 };
        System.out.println("原数组为:" + Arrays.toString(ages));
        AgeSort as = new AgeSort();
        as.sortAge(ages);
        System.out.println("排序后的数组为:" + Arrays.toString(ages));
    }
 
    private void sortAge(int[] ages) {
 
        if (ages == null || ages.length < 1) {
            return;
        }
        int oldAge = 80;
        int youngAge = 20;
 
        // 初始化一个odlAge+1的数组
        int[] timeOfAge = new int[oldAge + 1];
        // 将数组元素都置为0
        for (int i = 0; i < timeOfAge.length; i++) {
            timeOfAge[i] = 0;
        }
        // 某个年龄出现了多少次,就在timeOfAge数组对应年龄的位置设置多少次
        for (int j = 0; j < ages.length; j++) {
            int a = ages[j];
            timeOfAge[a]++;
        }
 
        int index = 0;
        for (int i = youngAge; i <= oldAge; i++) {// 按照年龄从小到大依次遍历timeOfAge
            for (int j = 0; j < timeOfAge[i]; j++) {// 在timeOfAge中取得各个年龄位置记录的出现次数
                ages[index] = i;// 将新数组从头设置出现的年龄,已经排好序
                index++;
            }
        }
    }
}

员工年龄(0-99),数组timeOfAge用来统计每个年龄出现的次数,某个年龄出现了多少次,就在数组age里设置几次该年龄,这就相当于给数组排序了,该方法用长度100的整数数组作为辅助空间换来 O(n) 的时间效率

二、题目描述

	把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的
	小元素,如:数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1

三、解题思路

1. 旋转数组特点:

(1)旋转之后的数组可以划分为两个排序的子数组:前面的子数组都大于或者等于后边子数组的元素
(2)最小的元素刚好是这两个子数组的分界线
(3)在排序的数组中可以使用二分查找法实现O(logn)

2. 使用二分查找法:

(1)两个指针,分别指向数组的首尾
(2)找到数组中间的元素:

  • 当它大于或者等于第一个指针指向的元素时,该中间元素位于前面的递增子数组,此时最小的元素应该位于该中间元素的后面,所以将第一个指针指向该中间元素;
  • 当它小于或者等于第二个指针指向的元素时,该中间元素位于后面的递增子数组,此时最小的元素应该位于该中间元素的前面,所以将第二个指针指向该中间元素;
  • 第一个指针总是指向前面递增数组的元素,第二个指针总是指向后面递增数组的元素,最终第一个指针将指向前面子数组的最后一个元素,第二指针会指向后面子数组的第一个元素
  • 循环条件:两个指针相邻,第二个指针指向的刚好是最小的元素

在这里插入图片描述

3. 代码实现:

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        //异常情况
        if(array==null || array.length<=0)
            return 0;
        int preIndex = 0;//第一个指针,总是指向前面递增子数组
        int endIndex = array.length-1;//第二个指针,总是指向后面递增子数组
        //如果把排序数组前面0个元素搬到最后面,即排序数组本身,此时数组中的第一个数字就是最小的数字,可以直接返回,因此mid初始化为第一个指针
        int mid = preIndex;//中间元素
        
        while(array[preIndex] >= array[endIndex]){
            //循环结束条件:若两个指针相邻,第二个指针指向的刚好是最小的元素
            if(endIndex - preIndex ==1){
                mid = endIndex;
                break;
            }
            //求中间元素
            mid = (preIndex + endIndex)>>1;
            //如果中间元素大于第一个指针指向元素,则最小元素肯定位于中间元素之后,让第一个指针指向中间元素
            if(array[mid] >= array[preIndex])
                preIndex = mid;
            //如果中间元素小于第二个指针指向元素,则最小元素肯定位于中间元素之前,让第二个指针指向中间元素
            else if(array[mid] <= array[endIndex])
                endIndex = mid;
        }
        return array[mid];
    
    }
}

4. 特殊情况:

(1)前面认为,在旋转数组中,由于是把递增数组排序数组前面的若干个数字搬到数组的后面,因此第一个数字总是大于或者等于最后一个数字
(2)特例:如果把排序数组的前面的0个元素搬到最后面,即排序数组本身,此时数组中的第一个数字就是最小的数字,可以直接返回,因此mid初始化为第一个指针
(3)特例:当两个指针指向的数字及它们中间的数字三者相同,无法判断中间的数字是位于前面的子数组还是后面的子数组,也就无法移动两个指针来缩小查找的范围。此时不得不采用顺序查找

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        //异常情况
        if(array==null || array.length<=0)
            return 0;
        int preIndex = 0;//第一个指针,总是指向前面递增子数组
        int endIndex = array.length-1;//第二个指针,总是指向后面递增子数组
        //如果把排序数组前面0个元素搬到最后面,即排序数组本身,此时数组中的第一个数字就是最小的数字,可以直接返回,因此mid初始化为第一个指针
        int mid = preIndex;//中间元素
        
        while(array[preIndex] >= array[endIndex]){
            //循环结束条件:若两个指针相邻,第二个指针指向的刚好是最小的元素
            if(endIndex - preIndex ==1){
                mid = endIndex;
                break;
            }
            //求中间元素
            mid = (preIndex + endIndex)>>1;
            
            
            //如果两个指针指向元素和中间元素,三个相等,则只能顺序查找
            if(array[preIndex]==array[endIndex] && array[mid]==array[preIndex])
                return minInOrder(array, preIndex, endIndex);
            
            
            //如果中间元素大于第一个指针指向元素,则最小元素肯定位于中间元素之后,让第一个指针指向中间元素
            if(array[mid] >= array[preIndex])
                preIndex = mid;
            //如果中间元素小于第二个指针指向元素,则最小元素肯定位于中间元素之前,让第二个指针指向中间元素
            else if(array[mid] <= array[endIndex])
                endIndex = mid;
        }
        return array[mid];
    
    }
    
    public int minInOrder(int[] array, int preIndex, int endIndex){
        int result = array[preIndex];//存最小元素
        //遍历两个指针之间元素
        for(int i = preIndex+1; i<=endIndex; i++){
            if(result > array[i])
                result = array[i];
        }
        return result;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值