旋转数组

旋转数组

给定一个数组,将数组中的元素向右移动 k个位置,其中 k是非负数。

示例 1:

输入: [1,2,3,4,5,6,7]k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5] 向右旋转 3 步: [5,6,7,1,2,3,4]

输入: [-1,-100,3,99]k = 2
输出: [3,99,-1,-100]
解释:
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]

方法一:暴力

public void rotate(int[] nums, int k) {
    int n = nums.length;
    k %= n;
    for (int i = 0; i < k; i++) {
        int temp = nums[n - 1];
        for (int j = n - 2; j >= 0; j--) {
            nums[j + 1] = nums[j];
        }
        nums[0] = temp;
    }
}

方法二:使用额外空间

public void rotate(int[] nums, int k) {
    int n = nums.length;
    int[] temp = new int[n];
    for (int i = 0; i < n; i++) {
        temp[(i + k) % n] = nums[i];
    }
    for (int i = 0; i < n; i++) {
        nums[i] = temp[i];
    }
}
public void rotate(int[] nums, int k) {
    int n = nums.length;
    k %= n;
    int[] temp = new int[n];
    for (int i = 0; i < k; i++) {
        temp[i] = nums[n - k + i];
    }
    for (int i = k; i < n; i++) {
        temp[i] = nums[i - k];
    }
    System.arraycopy(temp, 0, nums, 0, n);
}

方法三:环状替换

public void rotate(int[] nums, int k) {
    int n=nums.length;
    int count=0;//记录已替换的个数,做循环出口
    for (int i = 0; count < n; i++) {
        int pre=nums[i];//保存替换前的值
        int index=i;//i表示每轮替换开始的下标,index用来在内存循环中定位应该替换的位置
        do{
            int newIndex=(index+k)%n;//记录当前的位置,index待会更新为它
            int temp=nums[newIndex];//记录当前元素的值,pre待会更新为它
            nums[newIndex]=pre;
            pre=temp;
            index=newIndex;
            count++;
        } while (index!=i);//形成了一个环,就再从i+1开始
    }
}

方法四:三次反转
这个方法基于这个事实:当我们旋转数组 k 次,k%n 个尾部元素会被移动到头部,剩下的元素会被向后移动。

在这个方法中,我们首先将所有元素反转。然后反转前 k 个元素,再反转后面 n-kn−k 个元素,就能得到想要的结果。

假设 n=7 且 k=3

原始数组 : 1 2 3 4 5 6 7
反转所有数字后 : 7 6 5 4 3 2 1
反转前 k 个数字后 : 5 6 7 4 3 2 1
反转后 n-k 个数字后 : 5 6 7 1 2 3 4

public void rotate(int[] nums, int k) {
    int n=nums.length;
    reverse(nums,0,n-1);
    reverse(nums,0,k%n-1);
    reverse(nums,k%n,n-1);
}

public void reverse(int[] nums,int start,int end){
    while (start<end){
        int temp=nums[start];
        nums[start]=nums[end];
        nums[end]=temp;
        start++;
        end--;
    }
}

寻找旋转排序数组中的最小值

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

请找出其中最小的元素。

假设数组中不存在重复元素。

示例 1:

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

示例 2:

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

旋转数组左边的所有元素都比右边的大
以最后一个元素为参考值num

  • 如果nums[mid]<num,说明在右边,缩小右指针范围
  • 如果nums[mid]>num,说明在左边,缩小左指针范围
public int findMin(int[] nums) {
    return binarySearch(nums,0,nums.length-1);
}

public int binarySearch(int[] nums,int start,int end){
    if(start<end){
        int mid=(start+end)/2;
        if(nums[mid]<nums[end]){//右边
            return binarySearch(nums,start,mid);//由于最小元素在右边,所以是mid,不能-1
        } else {//左边
            return binarySearch(nums,mid+1,end);
        }
    }
    return nums[start];
}

非递归写法

public int findMin(int[] nums) {
    int left = 0;
    int right = nums.length - 1;
    while (left < right) {
        int mid = (left + right) / 2;
        if (nums[mid] < nums[right]) {//< 或者<=都行
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    return nums[left];//left或者right都行,因为循环结束时left=right
}

不能用left做参考值的原因:

  • 1 2,nums[mid]=nums[l]
  • 2 1, nums[mid]=nums[l]
    可以改为求最大值,最小值即为最大值后面的一个位置
public int findMin(int[] nums) {
    int l = 0, r = nums.length - 1;
    while (l < r) {
        int mid = (l + r + 1) / 2;
        if (nums[mid] >= nums[l]) {
            l = mid;
        } else {
            r = mid - 1;
        }
    }
    return nums[(l + 1) % nums.length];
}

寻找旋转排序数组中的最小值 II

可能存在重复元素

示例 1:

输入: [1,3,5]
输出: 1

示例 2:

输入: [2,2,2,0,1]
输出: 0

不同之处:可能出现重复元素,出现时right--

public int findMin(int[] nums) {
    int left = 0;
    int right = nums.length - 1;
    while (left < right) {
        int mid = (left + right) / 2;
        if (nums[mid] < nums[right]) {
            right = mid;
        } else if (nums[mid] > nums[right]){
            left = mid + 1;
        } else {
            right--;
        }
    }
    return nums[left];
}

不能再采用求最大值后一面一个位置,存在这种情况[3,1,3,3]

搜索旋转排序数组

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1

数组中不存在重复的元素。

示例 1:

输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4

示例 2:

输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1

二分搜索的时候查看当前 mid 为分割位置分割出来的两个部分 [left, mid] 和 [mid + 1, right] 哪个部分是有序的,并根据有序的那个部分确定我们该如何改变二分搜索的上下界,因为我们能够根据有序的那部分判断出 target 在不在这个部分:

  • 如果 [left mid - 1] 是有序数组,且 target 在其中,则我们应该将搜索范围缩小至 [left, mid - 1],否则在 [mid + 1, right] 中寻找。
  • 如果 [mid, right] 是有序数组,且 target在其中,则我们应该将搜索范围缩小至 [mid + 1, right],否则在 [left, mid - 1] 中寻找。
public int search(int[] nums, int target)  {
    int left = 0;
    int right = nums.length - 1;
    while (left <= right) {
        int mid = (left + right) / 2;
        if(nums[mid]==target){
            return mid;
        }
        if(nums[mid]>=nums[left]){//mid的左边有序
            if(target>=nums[left]&&target<nums[mid]){//target在其中
                right=mid-1;
            }else {
                left=mid+1;
            }
        }else {
            if(target>nums[mid]&&target<=nums[right]){
                left=mid+1;
            } else {
                right=mid-1;
            }
        }
    }
    return -1;
}

搜索旋转排序数组 II

可能有重复元素

编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false

示例 1:

输入: nums = [2,5,6,0,0,1,2], target = 0
输出: true

示例 2:

输入: nums = [2,5,6,0,0,1,2], target = 3
输出: false

10111 和 111011 这种。此种情况下 nums[start] == nums[mid],分不清到底是前面有序还是后面有序,此时 start++ 即可。相当于去掉一个重复的干扰项。

和查找最小数字类似,重复元素导致nums[mid] == nums[left]时,无法判断是左部有序还是右部有序,可以left++,移除重复的元素

public boolean search(int[] nums, int target) {
    int left = 0;
    int right = nums.length - 1;
    while (left <= right) {
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            return true;
        }
        if (nums[mid] == nums[left]) {
            left++;
        } else if (nums[mid] > nums[left]) {//mid的左边有序
            if (target >= nums[left] && target < nums[mid]) {//target在其中
                right = mid - 1;
            } else{
                left = mid + 1;
            }
        } else {
            if (target > nums[mid] && target <= nums[right]) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
    }
    return false;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351