跳转至

技巧题

31. 下一个排列

难度:⭐️⭐️⭐️

整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。

例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。 整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。 类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。 而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。 给你一个整数数组 nums ,找出 nums 的下一个排列。

必须 原地 修改,只允许使用额外常数空间。

示例 1:

输入:nums = [1,2,3]
输出:[1,3,2]

示例 2:

输入:nums = [3,2,1]
输出:[1,2,3]

示例 3:

输入:nums = [1,1,5]
输出:[1,5,1]

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 100

解法一 两遍扫描

从后往前搜索,找到第一个降序序列(顶点 i + 1),第二遍搜索,倒序找到比顶点前一个元素小的元素交换,从 i 到数组末尾是降序的,将剩下的元素反转。

以 1 4 6 5 4 3 2 1 为例:

y
6 |         ○
5 |             ○
4 |     ○           ○
3 |                     ○
2 |                         ○
1 | ○                          ○
  ---------------------------------------- x
    0   1   2   3   4   5   6   7
一句话记忆:

找上坡 → 找稍大 → 交换 → 反转右侧。
折线图上就是:找到最后一个上升的起点,用右边比它稍大的数替换,再把右边从降序翻成升序。

时间复杂度:O(n)
空间复杂度:O(1)

两遍扫描
  public void nextPermutation(int[] nums) {
      // 1 4 6 5 4 3 2 1

      // 找到顶点元素的前一个元素 i
      int i = nums.length - 2;
      while (i >= 0 && nums[i] >= nums[i + 1]) {
          i--;
      }
      // 找到比 num[i] 更小的元素,反转
      if (i >= 0) {
          int j = nums.length - 1;
          while (j >= 0 && nums[i] >= nums[j]) {
              j--;
          }
          swap(nums, i, j);
      }
      // 反转剩下的元素
      int left = i + 1;
      int right = nums.length - 1;
      while (left < right) {
          swap(nums, left, right);
          left++;
          right--;
      }
  }

  private void swap(int[] nums, int i, int j) {
      int temp = nums[i];
      nums[i] = nums[j];
      nums[j] = temp;
  }

75.颜色分类

难度:⭐️⭐️

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

必须在不使用库内置的 sort 函数的情况下解决这个问题。

解法一 双指针

左右指针分别指向头尾,遇到 2 移动到数组尾部,遇到 0 移动到数组头部。

双指针
  public void sortColors(int[] nums) {
      int p0 = 0;
      int p2 = nums.length - 1;
      for (int i = 0; i <= p2; i++) {
          while (i <= p2 && nums[i] == 2) {
              int temp = nums[p2];
              nums[p2] = nums[i];
              nums[i] = temp;
              p2--;
          }
          if (nums[i] == 0) {
              int temp = nums[p0];
              nums[p0] = nums[i];
              nums[i] = temp;
              p0++;
          }
      }
  }

  // 另外一种简单的写法
  public void sortColors(int[] nums) {
      int left = 0;
      int right = nums.length - 1;
      for (int i = 0; i <= right; i++) {
          if (nums[i] == 0) {
              swap(nums, left, i);
              left++;
          } else if (nums[i] == 2) {
              swap(nums, i, right);
              right--;
              i--;
          }
      }
  }

解法二 计数排序

第一次循环计算每种颜色的数量,第二次循环根据数量分别设置值。

记录次数
    public void sortColors(int[] nums) {
      int red = 0;
      int white = 0;
      for (int n: nums) {
          if (n == 0) red++;
          if (n == 1) white++;
      }
      for (int i = 0; i < nums.length; i++) {
          if (i < red) {
              nums[i] = 0;
          } else if (i < red + white) {
              nums[i] = 1;
          } else {
              nums[i] = 2;
          }
      }
  }

解法三 荷兰国旗问题(三指针)

使用三个指针 left、right、cur,一次遍历完成。

三指针
  public void sortColors(int[] nums) {
      int left = 0;
      int right = nums.length - 1;
      int cur = 0;
      while (cur <= right) {
          if (nums[cur] == 0) {
              swap(nums, cur, left);
              cur++;
              left++;
          } else if (nums[cur] == 1) {
              cur++;
          } else {
              swap(nums, right, cur);
              right--;
          }
      }
  }

  private void swap(int[] nums, int x, int y) {
      int t = nums[x];
      nums[x] = nums[y];
      nums[y] = t;
  }

136.只出现一次的数字

难度:⭐️

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

解法一 异或

数字 n^n = 0,n^0 = n,将所有的数字异或一遍,结果就是只出现一次的数字。

异或
  public int singleNumber(int[] nums) {
      int ans = 0;
      for (int i = nums.length; i > 0; i--) {
          ans ^= nums[i-1];
      }
      return ans;
  }

169.多数元素

难度:⭐️

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

解法一 Boyer-Moore 投票算法

遍历数组,对每个数字进行投票,与自身数字相同的,票数+1,否则票数-1,票数归零后重新选择数字。则众数得到的票数一定>0。

投票算法
  public int majorityElement(int[] nums) {
      int candidate = nums[0];
      int votes = 1;
      for (int i = 1; i < nums.length; i++) {
          if (votes < 1) {
              candidate = nums[i];
              votes++;
          }
          if (nums[i] == candidate) {
              votes++;
          } else {
              votes--;
          }
      }
      return candidate;
  }

解法二 哈希表

对每个数字计数,取value最大的数。

哈希表
 public int majorityElement(int[] nums) {
      Map<Integer, Integer> map = new HashMap<>();
      for (int num : nums) {
          map.put(num, map.getOrDefault(num, 0) + 1);
      }
      for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
          if (entry.getValue() > nums.length / 2) {
              return entry.getKey();
          }
      }
      return -1;
  }

解法三 排序

对数组进行排序,排序后,最中间的位置一定是众数。

排序
  public int majorityElement(int[] nums) {
      Arrays.sort(nums);
      return nums[nums.length / 2];
  }

解法四 分治递归

将数组分成两半,分别求两边的众数,两边众数相同时,结果即为该值;否则计算刚刚算出来的2个数组中的众数的数量,取数量更多的数组。递归该过程,当数组只有一个元素时,递归结束。

排序
  public int majorityElement(int[] nums) {
      return majority(nums, 0, nums.length);
  }

  private int majority(int[] nums, int start, int end) {
      if (end - start == 1) {
          return nums[start];
      }
      int mid = start + (end - start) / 2;
      int left = majority(nums, start, mid);
      int right = majority(nums, mid, end);
      if (left == right) {
          return left;
      }
      int leftCount = count(nums, start, end, left);
      int rightCount = count(nums, start, end, right);
      if (leftCount < rightCount) {
          return right;
      } else {
          return left;
      }

  }

  private int count(int[] nums, int start, int end, int target) {
      int count = 0;
      for (int i = start; i < end; i++) {
          if (nums[i] == target) {
              count++;
          }
      }
      return count;
  }

287.寻找重复数

难度:⭐️⭐️⭐️

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。

你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

示例 1:

输入:nums = [1,3,4,2,2]
输出:2

示例 2:

输入:nums = [3,1,3,4,2]
输出:3

示例 3 :

输入:nums = [3,3,3,3,3]
输出:3

提示:

  • 1 <= n <= 105
  • nums.length == n + 1
  • 1 <= nums[i] <= n
  • nums 中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次

解法一 快慢指针,Floyd判圈法

和链表类似,通过快慢指针,先让快慢指针相遇,然后快指针回到头部,快慢指针以相同的速度走,相遇点即为环的入口。和链表的写法的差别是结束的终点一定会相遇,可以用do-while写法或者是while(true)的写法。

快慢指针
    public int findDuplicate(int[] nums) {
        int slow = nums[0];
        int fast = nums[0];
        do {
            slow = nums[slow];
            fast = nums[nums[fast]];
        } while (slow != fast);
        fast = nums[0];
        while (slow != fast) {
            slow = nums[slow];
            fast = nums[fast];
        }
        return slow;
    }