开启我的LeetCode刷题日记:969. 煎饼排序

166 阅读1分钟

「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战

编程世界总是离不了算法

最近在看框架源码时,会有很多算法的实现逻辑,有时候会感到吃力

于是决定蹭着假期,加强算法和数据结构相关的知识

那怎么提升呢?

其实我知道算法这东西没有捷径,多写多练才能提升,于是我开启我的LeetCode刷题之旅

第一阶段目标是:200道,每天12

为了不乱,本系列文章目录分为三部分:

  1. 今日题目:xxx
  2. 我的思路
  3. 代码实现

今天题目: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 范围内的有效答案都将被判断为正确。

 

示例 1:

输入:[3,2,4,1] 输出:[4,2,4,3] 解释: 我们执行 4 次煎饼翻转,k 值分别为 4,2,4,和 3。 初始状态 arr = [3, 2, 4, 1] 第一次翻转后(k = 4):arr = [1, 4, 2, 3] 第二次翻转后(k = 2):arr = [4, 1, 2, 3] 第三次翻转后(k = 4):arr = [3, 2, 1, 4] 第四次翻转后(k = 3):arr = [1, 2, 3, 4],此时已完成排序。 示例 2:

输入:[1,2,3] 输出:[] 解释: 输入已经排序,因此不需要翻转任何内容。 请注意,其他可能的答案,如 [3,3] ,也将被判断为正确。  

我的思路

首先解读题目,翻译过来意思是说:有一个长度为n的数组arr,元素范围是1至n(元素不重复,可乱序),定义一次以 k 为参数的翻转操作是将下标为 [0, k-1] 的子数组进行reverse反转,求经过多少次翻转操作可以使得arr变成从1开始的增序数组,给出每次翻转的k序列(其中,每次翻转的k参数可为不超过n的任意正整数,k序列长度不超过 10*n)

对于这一题,直接看最终目标,变成一个从小到大排序的数组。先从翻转操作思考,每次翻转对数组有什么影响呢?可以发现翻转只影响了前k个元素,对后续的元素是无任何影响的(已经做好的煎饼就不用再加工了,再加工就糊了)。因此,我们应该从数组尾巴下手,而数组尾巴的元素是最大的元素,那么问题就转化为了:把数组中最大值依次放到数组末尾,即把熟了的鸡蛋饼放大末尾。看到这里,解决方案基本出来了,这就是冒泡排序阿,只是每次排序通过煎饼规则来弄。

那怎么进行煎饼规则的排序呢?假设现在已经知道了最大值的下标idx,要把它放到末尾下标end处,存在两种情况:

最大值已经是在末尾了,即idx == end,此时就不用再翻转了,已经达到了目的 最大值不在末尾处,即idx != end,此时就需要进行两次翻转了: 第一次翻转 [0, idx] 将最大值放到数组首位,此时 k=idx+1 第二次翻转 [0, end] 将最大值从首位放到 end 处,此时 k=end+1 如此从后向前依次寻找 [0, end] 范围内的最大值并进行翻转就可以煎出一串熟鸡蛋饼了 算法思路 从数组尾开始向前遍历,数组尾标记为 end 每次遍历时寻找下标为 [0, end] 的子数组中最大值对应的下标 idx 比较 idx 与 end 的排序关系: 二者相等,说明最大值已经在末尾,不进行操作 二者不相等,最大值不在末尾,进行两次翻转使得最大值排到末尾,翻转子数组分别为 [0, idx] 和 [0, end],并更新结果序列 直至 end 为第0项时返回结果

代码实现

/**
 * @param {number[]} arr
 * @return {number[]}
 */
var pancakeSort = function(arr) {
    // 翻转函数:将下标尾 [0,idx] 的数组进行翻转
    const reverse = (idx)=>{
        for(let i=0, j=idx; i<j; i++, j--){
            [arr[i], arr[j]] =  [arr[j], arr[i]]
        }
    }

    const n = arr.length;
    const ans = [];

    // 从尾开始,逐步将最大值放大末尾
    for(let end = n-1; end > 0; end--){

        // 寻找下标为[0, end]子数组里最大值的下标
        let idx = end;
        for( ; idx>0; idx--){
            if(arr[idx] === end+1){
                break
            }
        }

        // 只有当最大值下标不是在末尾时,才需要移动
        if(idx !== end){
            // 如果最大值下标不是在数组首位,则先将最大值翻转到数组首位
            if(idx > 0){
                ans.push(idx+1);
                reverse(idx)
            }
            // 然后再从尾翻转,从而使得最大值放到尾部
            ans.push(end+1);
            reverse(end);
        }
    }

    return ans
};


总结

实现方式其实有很多,这里仅供参考~

由于刚开始刷题,也不知道从哪里刷好,如果前辈们有好的建议,希望不吝赐教,感谢🌹