问题描述:
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [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.利用排序,以下将使用冒泡和快速排序方法,对此题进行讲解!
冒泡排序O(N^2) 排序原理:
- 比较相邻的元素。如果前一个元素比后一个元素大,就交换这两个元素的位置。
- 对每一对相邻元素做同样的工作,从开始第一对元素到结尾的最后一对元素。最终最后位置的元素就是最大值。
冒牌排序代码实现
//方式一:
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.交换当前左边指针位置和右边指针位置的元素;
5.重复2,3,4步骤,直到左边指针的值大于右边指针的值停止。
快速排序代码实现
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); // 右边递归
}
}