正题
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 范围内的有效答案都将被判断为正确。
解析:
什么是煎饼排序
首先了解一下煎饼排序的定义:饼排序是数量问题的口语术语,当刮刀可以插入堆叠中的任何点并用于翻转其上方的所有薄饼时,按照尺寸的顺序对无序堆叠的薄煎饼进行分类。 煎饼数是给定数量的煎饼所需的最小翻转数。 在这种形式下,问题首先由美国几何学家Jacob E. Goodman讨论过。它是排序问题的变体,其中唯一允许的操作是反转序列的某些前缀的元素。 与传统的排序算法不同,传统的排序算法试图以尽可能少的比较进行排序,目标是尽可能少地对序列进行排序。 该问题的一个变体涉及烧焦的煎饼,其中每个煎饼具有烧焦的一面,并且所有的煎饼必须另外在底部烧焦的一面。 -- 来自百度
说实话我们不必去考虑它如何定义的,只要去了解它的原理是什么样的,如何去实现的?
一张图带你了解:
从图中可以看出,所谓“翻煎饼”就是将 指定范围内的元素倒序
。经过多次反复的翻转之后,可以达到排序的目的了。
煎饼排序的原则是什么
了解煎饼排序的原则也是我们实现煎饼排序的基本。可以发现我们翻一次煎饼,就相当于将某一个元素放到了最上面,再翻一次的时候就可以翻到指定的位置。如上图,我想要把9翻到倒数第二的位置,那么我先在9的位置做一次翻转,9就到了最上面,然后再在倒数第二的位置做一次翻转,这样9就到了倒数第二的位置了。所以,我们可以发现,任何元素通过2次翻转都可以到达你想要的位置,这也是煎饼排序的底层逻辑。
实现煎饼排序
根据上面的原则,我们首先需要找到要排序的元素,再确定他要排序的位置,这样对这个元素,做两次翻转就可以了。
找到要排序的元素
第一个要排序的元素是最大的元素,第二个要排序的元素是第二大的元素。。。以此类推。通过代码,我们可以理解为我们要找第 n 大的元素, n 为已经排好序的元素个数 + 1.例如,我们要找第三大元素,那么也可以理解为已经排好了2个元素(2+1)。
设已经排好的元素个数为 let ready = 0
,那么我们只要在两次翻转过后执行 ready++
即可
实现找到第 ready + 1 大的元素:
let getMaxIndex = function (arr, ready) {
let maxIndex = 0
for (let index = 1 ; index < arr.length - ready; index++) {
maxIndex = arr[maxIndex] > arr[index] ? maxIndex : index
}
return maxIndex
}
实现两次翻转
通过双指针的方法实现数组的翻转,具体原理可以参考之前的文章:反转数组
翻转的方法:
let trans = function (arr, index) {
let left = 0
let right = index
while(left < right) {
arr[left] = arr[left] ^ arr[right]
arr[right] = arr[left] ^ arr[right]
arr[left] = arr[left] ^ arr[right]
left++
right--
}
return arr
}
实现两次翻转:
const maxIndex = getMaxIndex(arr, ready)
arr = trans(arr,maxIndex)
arr = trans(arr, arr.length - 1 - ready)
最终代码:
/**
* @param {number[]} arr
* @return {number[]}
*/
let getMaxIndex = function (arr, ready) {
let maxIndex = 0
for (let index = 1 ; index < arr.length - ready; index++) {
maxIndex = arr[maxIndex] > arr[index] ? maxIndex : index
}
return maxIndex
}
let trans = function (arr, index) {
let left = 0
let right = index
while(left < right) {
arr[left] = arr[left] ^ arr[right]
arr[right] = arr[left] ^ arr[right]
arr[left] = arr[left] ^ arr[right]
left++
right--
}
return arr
}
var pancakeSort = function(arr) {
let ready = 0
let res = []
while(ready < arr.length - 1) {
const maxIndex = getMaxIndex(arr, ready)
arr = trans(arr,maxIndex)
arr = trans(arr, arr.length - 1 - ready)
maxIndex > 0 ? res.push(maxIndex + 1) : ''
arr.length - 1 - ready > 0 ? res.push(arr.length - 1 - ready + 1) : ''
ready++
}
return res
};
期间有一点可以优化的地方:
maxIndex > 0 ? res.push(maxIndex + 1) : ''
arr.length - 1 - ready > 0 ? res.push(arr.length - 1 - ready + 1) : ''
即当需要翻转的 index = 0 的时候就不需要做翻转了。