题目

类型:贪心

image.png

解题思路

由于每次都对「某段前缀」进行整体翻转,并且规定了翻转次数在一定范围内的方案均为合法翻转方案,同时 arr 又是 1 到 n 的排列。可以很自然想到「冒泡排序」:每次确定未排序部分最右端的元素(最大值)。

假设下标 [k + 1, n - 1] 部分已有序,如果希望当前值 t 出现在某个位置 k 上,可以进行的操作为:

  • 如果当前值 t 已在 k 上,无须进行操作;
  • 如果当前值不在 k 上,根据当前值是否在数组头部(下标为 0)进行分情况讨论
    • 当前值在数组头部(下标为 0),直接将 [0, k] 部分进行翻转(将 k + 1 加入答案中),即可将当前值 t 放到位置 k 上;
    • 当前值不在数组头部(下标为 0),而是在 idx 位置上,需要先将 [0, idx] 部分进行翻转(将 idx + 1 加入答案中),这样使得当前值 t 出现数组头部(下标为 0),然后再将 [0, k] 部分进行翻转(将 k + 1 加入答案中),即可将当前值 t 放到位置 k 上。

翻转某个前缀的操作可使用「双指针」实现,复杂度为 O(n)。

代码

  1. class Solution {
  2. // 煎饼排序
  3. // 这道题有这么一个提示:
  4. // 任何将数组排序且翻转次数在 10 * arr.length 范围内的有效答案都将被判断为正确
  5. // 提示你:用任何一种解法,只要能正确排序就行
  6. // 使用冒泡排序的思想,每一次把最大的扔在后面,排序剩下的子数组
  7. // 这道题是1 - n全排列 ,每次最大的就是 n - 1 -> 1
  8. // 举例: 3 2 4 1
  9. // 先找到 4 的位置,(翻转)将 4 放到 第一个位置 => 4 2 3 1
  10. // (翻转) 0 - 4 =>1 3 2 4
  11. // 一次排序完成
  12. // 优化:比如 4 已经在它的位置上了,就不要再翻了
  13. public List<Integer> pancakeSort(int[] arr) {
  14. List<Integer> res = new LinkedList<>();
  15. // 当前要排序的子数组下标为: 0 - max(不包括max)
  16. // 同时,由于全排列的原因:max 也是最大数字
  17. // 如果只剩 1 的话就不用排了
  18. for (int max = arr.length; max > 1; max--) {
  19. // 先找到当前要排序的数字=>最大数在哪
  20. int maxNumIndex;
  21. for (maxNumIndex = 0; arr[maxNumIndex] != max; maxNumIndex++) ;
  22. if (maxNumIndex == max - 1) {
  23. // 如果已经排好了,就不要排了
  24. continue;
  25. }
  26. // 如果当前要排序的数字,在 0 这个位置就省去了第一段排序
  27. // 否则就要换到 0 这个位置
  28. if (maxNumIndex != 0) {
  29. reverse(arr, 0, maxNumIndex);
  30. res.add(maxNumIndex + 1);
  31. }
  32. // 然后将 整个子数组反转
  33. reverse(arr, 0, max - 1);
  34. res.add(max);
  35. }
  36. return res;
  37. }
  38. private void reverse(int[] arr, int start, int end) {
  39. // 翻转 start 到 end 的数组
  40. while (start < end) {
  41. int temp = arr[start];
  42. arr[start] = arr[end];
  43. arr[end] = temp;
  44. start++;
  45. end--;
  46. }
  47. }
  48. }