题目
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=0,arr = [3,2,4,1]
从数组前4项中找到最大的元素4,记录下标maxIndex=2。k=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=1。k=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=0。k=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=0。k=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--;
}
}
但是我对这个解法是不满意的,有以下几个问题。
- 如果数组本来就是有序的,我们这个解法仍然会去翻转很多次。但如果我们在每次翻转前去做一个数组有序的检测,其实时间复杂度更高了。
- 我们这个解法的步数不是最少的,怎样才能算出最少步数?
- 怎么可以算出数组元素交换次数最少的解法?比如翻转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--;
}
}