[路飞]_leetcode刷题_969. 煎饼排序

565 阅读2分钟

题目

969. 煎饼排序

给你一个整数数组 arr ,请使用 煎饼翻转 完成对数组的排序。

一次煎饼翻转的执行过程如下:

选择一个整数 k ,1 <= k <= arr.length 反转子数组 arr[0...k-1](下标从 0 开始) 例如,arr = [3,2,1,4] ,选择 k = 3 进行一次煎饼翻转,反转子数组 [3,2,1] ,得到 arr = [1,2,3,4] 。

以数组形式返回能使 arr 有序的煎饼翻转操作所对应的 k 值序列。任何将数组排序且翻转次数在 10 * arr.length 范围内的有效答案都将被判断为正确。

思路:

目前没有最优解的想法,只有暴力解的思路。每次翻转最大的数到最前面,再把最大的数翻转到后面它应改在的位置。

arr = [3,2,4,1]

第一轮,i=0arr = [3,2,4,1]

从数组前4项中找到最大的元素4,记录下标maxIndex=2k=maxIndex+1,翻转3,得到[4,2,3,1];
再翻转length-i=4,得到[1,3,2,4],这样就将前4项中最大的数翻转到了前4项的最后一位。

第二轮,i=1[1,3,2,4]

从数组前3项中找到最大的元素3,记录下标maxIndex=1k=maxIndex+1,翻转2,得到[3,1,2,4];
再翻转length-i=3,得到[2,1,3,4],这样就将前3项中最大的数翻转到了前3项的最后一位。

第三轮,i=2[2,1,3,4]

从数组前2项中找到最大的元素2,记录下标maxIndex=0k=maxIndex+1,翻转1,得到[2,1,3,4],显然这一步翻转没有意义,所以我们翻转时要加一个条件,如果是翻转1就不操作了。
再翻转length-i=2,得到[1,2,3,4],这样就将前2项中最大的数翻转到了前2项的最后一位。

其实这个时候数组已经排序成功了,但是我们的程序没有去检测是否已经有序,所以还会继续

第四轮,i=3[1,2,3,4]

从数组前1项中找到最大的元素1,记录下标maxIndex=0k=maxIndex+1,翻转1,根据之前分析,这一步要剔除,不做操作。
再反转length-i=1,即翻转1,也剔除。

最终得到数组[1,2,3,4],中间每一次翻转我们记录一下操作的k,即可得到最终的结果。

具体代码如下:

/**
 * @param {number[]} arr
 * @return {number[]}
 */
var pancakeSort = function(arr) {
    let resultArr = [];
    let len = arr.length;
    for(let i=0;i<len;i++){
        let maxIndex = 0;
        for(let j=0;j<arr.length-i;j++){
            if(arr[j]>arr[maxIndex]){
                maxIndex = j;
            }
        }
        if(maxIndex>0){
            reverse(arr,0,maxIndex)
            resultArr.push(maxIndex+1);
        }
        if(len-i>1){
            reverse(arr,0,len-1-i)
            resultArr.push(len-i);
        }
    }
    return resultArr;
};


function reverse(arr,i,j){
    while(i<j){
        [arr[i],arr[j]] = [arr[j],arr[i]]
        i++;
        j--;
    }
}

但是我对这个解法是不满意的,有以下几个问题。

  1. 如果数组本来就是有序的,我们这个解法仍然会去翻转很多次。但如果我们在每次翻转前去做一个数组有序的检测,其实时间复杂度更高了。
  2. 我们这个解法的步数不是最少的,怎样才能算出最少步数?
  3. 怎么可以算出数组元素交换次数最少的解法?比如翻转5,则需要将前4项都两两交换位置,翻转2,则只需要交换前2项。

题解

看了题解之后,有一个题解特别巧妙,大家可以一起学习一下

他先把目标数组arr处理了一下,用一个record数组按arr值的大小顺序把对应值的下标存下来。

比如arr = [3,2,4,1],处理后得到record = [empty,3,1,0,2]

这样record从前到后分别对应到就是数组1,2,3,4值的下标,那么record的的最后一项就是数组arr里最大的一项的下标

然后去判断record的最后一项在arr里的下标是不是最后一项,是的话代表位置是对的,不是的话就开始交换arr的元素,交换的同时记得把record也更新一下,

然后i--,去判断record倒数第二项在arr里的下标是不是倒数第二项

...

依次类推,即可交换完成,

目标是将record处理成[empty,0,1,2,3],这样对应的arr就是[1,2,3,4],排序完成

具体可以看代码:

/**
 * @param {number[]} arr
 * @return {number[]}
 */
var pancakeSort = function(arr) {
        let len = arr.length;
        let result = [];
        if (len < 2) return result;
        // 根据题目给定范围,数组元素的值是在1 到 len 之间的
        // 所以可以用另外一个数组记录每个元素所在的位置,下标为元素,值为元素所在原数组的位置
        let record = [];

        for (let i= 0; i < len; i++){
            record[arr[i]] = i;
        }

        // 这样record数组下标也就是有序的了,我们先从最大的元素开始,一个个的调整到合适的位置
        for (let i = len; i >= 1; i--){
            // 如果当前最大元素的在合适的位置,就不做处理,直接操作下一个
            if (i == record[i] + 1) continue;

            let curPosition = record[i]; // 当前最大元素所在位置

            // 每个元素需继续两步翻转:
            // 第一步:翻转到0位置;
            // 第二步:再翻转到当前最后的位置(i - 1)
            // 首先看是否在0的位置,是的话省去第一步
            if (curPosition == 0){
                result.push(i);
                swap(arr, record, 0, i - 1);
            }else {
                // 第一步:翻转到0位置
                result.push(record[i] + 1);
                swap(arr, record, 0, record[i]);
                i++; // 加回去,下次遍历再来,走第二步
            }
        }

        return result;
};


    function swap(arr,record, left, right){
        while (left < right){
            record[arr[left]] = right; // 更新位置记录
            record[arr[right]] = left;

            let temp = arr[left];
            arr[left] = arr[right];
            arr[right] = temp;

            left++;
            right--;
        }
    }