剑指 Offer 11(排序篇1).旋转数组的最小数字

问题描述:

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

示例:

输入:[3,4,5,1,2]
输出:1
输入:[2,2,2,0,1]
输出:0

解题思路:

这个数组最开始的若干个元素搬到数组的末尾,旋转后的数组前部分都是按照大小排序好的。

1.直接遍历,线性查找

class Solution {
    public int minArray(int[] numbers) {
        for(int i = 0;i < numbers.length - 1;i++){
            if(numbers[i + 1] < numbers[i]){
                return numbers[i + 1];
            }
        }
        return numbers[0];
    }

}

2.利用二分查找实现

参考链接

二分查找(减治思想)
题目中给出的是半有序数组,虽然传统二分告诉我们二分只能用在有序数组中,但事实上,只要是可以减治的问题,仍然可以使用二分思想。

**思路:**数组中最特殊的位置是左边位置 left 和右边位置 right,将它们与中间位置 mid 的值进行比较,进而判断最小数字出现在哪里。

用左边位置 left 和中间位置 mid 的值进行比较是否可以?
举例:[3, 4, 5, 1, 2] 与 [1, 2, 3, 4, 5] ,此时,中间位置的值都比左边大,但最小值一个在后面,一个在前面,因此这种做法不能有效地减治。

用右边位置 right 和中间位置 mid 的值进行比较是否可以?
举例:[1, 2, 3, 4, 5]、[3, 4, 5, 1, 2]、[2, 3, 4, 5 ,1],用右边位置和中间位置的元素比较,可以进一步缩小搜索的范围。

补充说明:遇到 nums[mid] == nums[right] 的时候,不能草率地下定结论最小数字在哪一边,但是可以确定的是,把 right 舍弃掉,并不影响结果。

class Solution {
    public int minArray(int[] numbers) {
        //以以下三个旋转数组为例
        //3,4,5,0,1
        //2,2,2,0,1
        //2,3,4,5,1
        int left = 0;
        int right = numbers.length - 1;
        while(left <= right){
            int mid = (left + right) / 2;
            //当numbers[mid] == numbers[right]时,right位置的元素就可以不用考虑了
            if(numbers[mid] == numbers[right]){
                right--;
            }else if(numbers[mid] > numbers[right]){
                left = mid + 1;
            }else{
                right = mid;
            }
        }
        return numbers[left];
    }
}

3.利用排序,以下将使用冒泡和快速排序方法,对此题进行讲解!

img

冒泡排序O(N^2) 排序原理:

  1. 比较相邻的元素。如果前一个元素比后一个元素大,就交换这两个元素的位置。
  2. 对每一对相邻元素做同样的工作,从开始第一对元素到结尾的最后一对元素。最终最后位置的元素就是最大值。

image-20210827164750118

冒牌排序代码实现

//方式一:
class Solution {
    public int minArray(int[] numbers) {
        //冒泡排序
        int temp = 0;
        for(int i = 0;i < numbers.length;i++){
            for(int j = numbers.length - 1;j > i;j--){
               if(numbers[i] > numbers[j]){
                    temp = numbers[i];
                    numbers[i] = numbers[j];
                    numbers[j] = temp;
               }
            }
        }
        return numbers[0];
    }
}
//方式二:
class Solution {
    public int minArray(int[] numbers) {
        //冒泡排序
        int temp = 0;
        for(int i = numbers.length - 1;i > 0;i--){
            for(int j = 0;j < i;j++){
               if(numbers[i] < numbers[j]){
                    temp = numbers[i];
                    numbers[i] = numbers[j];
                    numbers[j] = temp;
               }
            }
        }
        return numbers[0];
    }
} 
//方式三:
class Solution {
    public int minArray(int[] numbers) {
        //冒泡排序
        int temp = 0;
        for(int i = 0;i < numbers.length;i++){
            for(int j = 0;j < numbers.length - i - 1;j++){
               if(numbers[j] > numbers[j + 1]){
                    temp = numbers[j];
                    numbers[j] = numbers[j + 1];
                    numbers[j + 1] = temp;
               }
            }
        }
        return numbers[0];
    }
}

快速排序排序原理:

  1. 首先设定一个分界值,通过该分界值将数组分成左右两部分;
  2. 将大于或等于分界值的数据放到到数组右边,小于分界值的数据放到数组的左边。此时左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值;
  3. 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
  4. 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左侧和右侧两个部分的数据排完序后,整个数组的排序也就完成了。

image-20210827204321861

切分原理:

把一个数组切分成两个子数组的基本思想:

1.找一个基准值,用两个指针分别指向数组的头部和尾部;

2.先从尾部向头部开始搜索一个比基准值小的元素,搜索到即停止,并记录指针的位置;

3.再从头部向尾部开始搜索一个比基准值大的元素,搜索到即停止,并记录指针的位置;

4.交换当前左边指针位置和右边指针位置的元素;

5.重复2,3,4步骤,直到左边指针的值大于右边指针的值停止。

image-20210827210811274

image-20210827210826070

image-20210827210848637

快速排序代码实现

class Solution {
    public int minArray(int[] numbers) {
        //设定left的初始位置为0,right的初始位置为数组最大索引处的
        int left = 0;
        int right = numbers.length - 1;
        quickSort(numbers,left,right);
        return numbers[0];
    }


    public static void quickSort(int[] numbers,int left, int right){
        //约束条件,当左指针超过右指针时,直接结束
        if (left > right){    // 结束条件
            return;
        }
        //初始化low和high的位置,以及第一个基准位
        int low = left;
        int high = right;
        int base = numbers[left];  //第一个位基准位
        int temp;
        //设定快速排序
        while (low < high){
            while (numbers[high] >= base && low < high){///找到小于基准位的数
                high--;
            }
            while (numbers[low] <= base && low < high){// 找到大于基准位的数
                low++;
            }

            if (low < high){                     // 交换
                temp = numbers[high];
                numbers[high] = numbers[low];
                numbers[low] = temp;
            }else{
                break;
            }
        }
        //
        numbers[left] = numbers[low];                 // 交换基准位和中间值
        //
        numbers[low] = base;
        quickSort(numbers,left,low-1);      //左边递归
        quickSort(numbers,low+1,right);       // 右边递归
    }   
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

原来如此呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值