数据范围:,数组中任意元素的值:
要求:空间复杂度: ,时间复杂度:
[3,4,5,1,2]
1
[3,100,200,3]
3
import java.util.*; public class Solution { public int minNumberInRotateArray (int[] nums) { Integer min = Integer.MIN_VALUE; int start = 0; int end = nums.length - 1; return min(nums,start,end); } /** 递归+二分法实现,简练 */ public int min(int[] nums,int start ,int end){ while(start < end){ int mid = (start + end)/2; if(nums[mid] > nums[end]){ // 中间大于尾部时 比如2,1 start = mid + 1; }else if(nums[mid] < nums[end]){ //尾部大于中间时。比如1,2 end = mid; }else{ // 中间等于尾部时,比如1,2,2或者2,1,1 Integer minValue = min(nums,start,mid); Integer minValue2 = min(nums,mid + 1,end); return Math.min(minValue,minValue2); } } return nums[start]; } }
public int minNumberInRotateArray (int[] nums) { int m = minNumberInRotateArray2(nums); return nums[m]; } public int minNumberInRotateArray2 (int[] nums) { int s = 0,e = nums.length-1; int m = 0; while(s < e){ m = (s+e) / 2; if(nums[m] > nums[e]){ s = m+1; }else if(nums[m] < nums[e]){ e = m; }else{ e--; } } return s; } public int minNumberInRotateArray1 (int[] nums) { int m = minNumberInRotateArray1_1(nums,0,nums.length-1); return nums[m]; } public int minNumberInRotateArray1_1 (int[] nums,int s,int e) { if(s >= e) return s; int m = (s+e)/2; int n1 = minNumberInRotateArray1_1(nums,s,m); int n2 = minNumberInRotateArray1_1(nums,m+1,e); if(nums[n1] < nums[n2]) return n1; else return n2; }
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param nums int整型一维数组 * @return int整型 */ public int minNumberInRotateArray (int[] nums) { // write code here // 整个问题本是上是在找数组中唯一存在的极小值,采取二分法解决 // 算法的时间复杂度O(logN),额外空间复杂度O(1) int n = nums.length; if (n == 1) { // 如果只有一个元素 return nums[0]; } if (n == 2) { // 如果只有两个元素 return Math.min(nums[0], nums[1]); } // 确保有三个以上的元素 int l = 0; int r = n - 1; // 采用二分思想,任务是找到这样一个转折点[↗……↗(↘index)↗……↗],其特征是左边比它大,右边也比它大 int min = nums[l]; while (l < r) { // 因为数组非递减,且涉及到右划l // 所以采用一个变量min时刻与当前最左的l元素比较总不是坏事 min = Math.min(min, nums[l]); int mid = (l + r) / 2; if (nums[l] > nums[mid]) { // 如果mid处比最左处要小,即[↗……↗(↘index)↗……mid……↗],说明局部最小值一定在左半区(或mid本身) r = mid; } else if (nums[l] < nums[mid]) { // 如果mid处比最左处要大,即[↗……mid……↗(↘index)↗……↗],说明局部最小值一定在右半区(必不包括mid) l = mid + 1; } else { // 如果mid处与最左处相等,即[-->……mid……-->(↘index)↗……-->],或[-->……-->(↘index)↗……-->……mid……-->],在左还是在右不能确定 // 但是!至少说明l处一定不是极小值点,此时l往右移一位,缩小范围即可 l++; } } return Math.min(min, nums[l]); } }
import java.util.*; class Solution { public int minNumberInRotateArray (int[] nums) { int l = 0; int r = nums.length; while (l < r) { int mid = (l + r) / 2; if (nums[mid] > nums[r - 1]) { l = mid + 1; } else if (nums[mid] == nums[r - 1]) { r--; } else { r = mid; } } return nums[l]; } }大佬帮忙看看为什么右边取开区间不行, 取闭区间就能过
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param nums int整型一维数组 * @return int整型 */ public int minNumberInRotateArray (int[] nums) { // write code here if (nums.length == 0) return 0; //假设第一个元素是最小的 int min = nums[0]; for (int i = 1; i < nums.length; i++) { if (nums[i] < min) // 如果找到更小的元素,则更新最小值 min = nums[i]; } return min; } }
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param nums int整型一维数组 * @return int整型 */ public int minNumberInRotateArray (int[] nums) { // 这里可以将数组分解为两部分 // 这两部分均为 从小到大排序的数组,以 12345 为例 // 会有 34512、45123 等 // write code here // 先来判断一下数组为空的情况 if(nums.length == 0) { return 0; } // 使用二分法来解决 int start = 0; int end = nums.length - 1; // 这里是旋转数组,先来考虑第一种情况 // 当这里的第一个元素的值 比 最后一个元素的值小 // 对于上面的例子,可以断定这里的第一个值 就一定是 最小值 if(nums[start] < nums[end]) { return nums[start]; } // 之后通过 二分法来判断最小值 // 先来说明没有重复值的情况 // 不断的通过 左、右 侧加逼 // 最终会使得 start 在左侧部分的最大值(最后一个位置) 上 // 并且会使得 end 在右侧部分上达到其中的 最小值(这个就是我们需要的值) // 此时 start 和 end 就是一个相邻的关系,所以对于 while 设定这样一个判断条件 while(start != end-1) { int mid = (start + end) / 2; // 在这之中还需要处理多个重复数据的情况,并且处理三个指针重叠后的情况 // 如题例:1,0,1,1,1 if(nums[start] == nums[end] && nums[start] == nums[mid]) { // 这里定义一个方法专门来处理这个问题 return minNum(nums,start,end); } if(nums[mid] >= nums[start]) { start = mid; }else if(nums[mid] <= nums[end]){ end = mid; } } // 在完成上面的判断后,这里的 end 位置的值就是我们要找到的最小值 return nums[end]; } private int minNum(int[] nums, int start, int end) { // 这里需要注意的是,要将值设置为 当前重合处的值,不能设置为 0 !!! int re = nums[start]; for (int i=0; i < nums.length; i++) { if(re > nums[i]) { // 将这里的最小值记录下来并返回 re = nums[i]; } } return re; } }
public int minNumberInRotateArray (int[] nums) { // write code here if(nums.length <= 0){ return 0; } int rt = nums[0]; for(int info : nums){ if(rt > info){ rt = info; } } return rt; } 不考虑二分是不是更快?
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param nums int整型一维数组 * @return int整型 */ public int minNumberInRotateArray (int[] nums) { // write code here return binary(nums, 0, nums.length - 1); } public int binary(int[] nums, int left, int right) { if (left >= right || nums[left] < nums[right]) { return nums[left]; } int mid = left + (right - left) / 2; int leftNum = binary(nums, left, mid); int rightNum = binary(nums, mid + 1, right); return leftNum <= rightNum ? leftNum : rightNum; } }
public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param nums int整型一维数组 * @return int整型 */ public int minNumberInRotateArray (int[] nums) { // write code here a(nums,0,nums.length-1); return nums[0]; } public void a(int[] nums,int left,int right){ if(left>=right) return; int mid=(left+right)/2; a(nums,left,mid); a(nums,mid+1,right); b(nums,left,mid,right); } public void b(int[] nums,int left,int mid,int right){ if(nums[left]>nums[mid+1]){ nums[left]=nums[mid+1]; } } }
剑指Offer中有这道题目的分析。这是一道二分查找的变形的题目。
旋转之后的数组实际上可以划分成两个有序的子数组:前面子数组的大小都大于后面子数组中的元素
注意到实际上最小的元素就是两个子数组的分界线。本题目给出的数组一定程度上是排序的,因此我们试着用二分查找法寻找这个最小的元素。
思路:
(1)我们用两个指针left,right分别指向数组的第一个元素和最后一个元素。按照题目的旋转的规则,第一个元素应该是大于最后一个元素的(没有重复的元素)。
但是如果不是旋转,第一个元素肯定小于最后一个元素。
(2)找到数组的中间元素。
中间元素大于第一个元素,则中间元素位于前面的递增子数组,此时最小元素位于中间元素的后面。我们可以让第一个指针left指向中间元素。
移动之后,第一个指针仍然位于前面的递增数组中。
中间元素小于第一个元素,则中间元素位于后面的递增子数组,此时最小元素位于中间元素的前面。我们可以让第二个指针right指向中间元素。
移动之后,第二个指针仍然位于后面的递增数组中。
这样可以缩小寻找的范围。
(3)按照以上思路,第一个指针left总是指向前面递增数组的元素,第二个指针right总是指向后面递增的数组元素。
最终第一个指针将指向前面数组的最后一个元素,第二个指针指向后面数组中的第一个元素。
也就是说他们将指向两个相邻的元素,而第二个指针指向的刚好是最小的元素,这就是循环的结束条件。
到目前为止以上思路很耗的解决了没有重复数字的情况,这一道题目添加上了这一要求,有了重复数字。
因此这一道题目比上一道题目多了些特殊情况:
我们看一组例子:{1,0,1,1,1} 和 {1,1, 1,0,1} 都可以看成是递增排序数组{0,1,1,1,1}的旋转。
这种情况下我们无法继续用上一道题目的解法,去解决这道题目。因为在这两个数组中,第一个数字,最后一个数字,中间数字都是1。
第一种情况下,中间数字位于后面的子数组,第二种情况,中间数字位于前面的子数组。
因此当两个指针指向的数字和中间数字相同的时候,我们无法确定中间数字1是属于前面的子数组(绿色表示)还是属于后面的子数组(紫色表示)。
也就无法移动指针来缩小查找的范围。