排列问题概述

排列问题,主要看「有无重复元素」

  • 无重复元素,used数组只用来判断该数选了没有
  • 有重复元素,used数组还可以帮助去重

    46. 全排列

    需要一个used数组判断是否用过了
    或者使用list.containsstring.indexOf()判断path里有无当前字符

    1. class Solution {
    2. List<List<Integer>> res = new ArrayList<>();
    3. List<Integer> path = new ArrayList<>();
    4. public List<List<Integer>> permute(int[] nums) {
    5. backtrack(nums, new boolean[nums.length]);
    6. return res;
    7. }
    8. public void backtrack(int[] nums, boolean[] used) {
    9. if (path.size() == nums.length) {
    10. res.add(new ArrayList<>(path));
    11. return;
    12. }
    13. for (int i = 0; i < nums.length; ++i) {
    14. if (used[i]){
    15. continue;
    16. }
    17. path.add(nums[i]);
    18. used[i] = true;
    19. backtrack(nums, used);
    20. used[i] = false;
    21. path.remove(path.size() - 1);
    22. }
    23. }
    24. }

    47. 全排列 II

  • used[i] == true判断这个数用过了没,在组合问题中用的是startIndex

  • i > 0 && nums[i-1] == nums[i] && used[i-1] == false

    • used[i-1] == false,数层中去重
    • used[i-1] == true,也是可以的,但这是在树枝中去重,排列问题数层树枝都可以,但是组合问题只能在数层中去重

      1. class Solution {
      2. List<List<Integer>> res = new ArrayList<>();
      3. List<Integer> path = new ArrayList<>();
      4. public List<List<Integer>> permuteUnique(int[] nums) {
      5. Arrays.sort(nums);
      6. backtrack(nums, new boolean[nums.length]);
      7. return res;
      8. }
      9. public void backtrack(int[] nums, boolean[] used){
      10. if (path.size() == nums.length) {
      11. res.add(new ArrayList<>(path));
      12. return ;
      13. }
      14. for (int i = 0; i < nums.length; ++i) {
      15. if (i > 0 && nums[i-1] == nums[i] && used[i-1] == false){
      16. continue;
      17. }
      18. if (used[i] == true) {
      19. continue;
      20. }
      21. path.add(nums[i]);
      22. used[i] = true;
      23. backtrack(nums, used);
      24. used[i] = false;
      25. path.remove(path.size() - 1);
      26. }
      27. }
      28. }

332. 重新安排

  • 如何记录映射关系,构造什么样的数据结构

Map<出发机场, TreeMap<到达机场, 航班次数>> targets,首先使用TreeMap是为了添加元素默认升序,答案只需要一个升序最小的即可。为什么不使用TreeSet,是因为,TreeSet只能保存从该机场能出发到哪个机场,并不能表明机票是否使用过了,如果使用过了的机票不对航班次数置0,那么可能产生死循环,如图所示,会在JFK和NRT之间来回飞。我们将TreeMap的value作为一个标志位,如果机票用过了就置为0,否则为1。所以我们需要把机票信息处理成出发机场及到达机场的map。
image.png

  • 回溯函数是有返回值的,因为只要找到一条符合要求的路径就返回即可
  • for循环表示横向遍历,遍历从当前机场出发,到所有的目的地机场

    1. class Solution {
    2. Map<String, Map<String, Integer>> targets = new HashMap<>();
    3. Deque<String> res = new LinkedList<>();
    4. public List<String> findItinerary(List<List<String>> tickets) {
    5. for (List<String> ticket : tickets) {
    6. Map<String, Integer> temp;
    7. if (targets.containsKey(ticket.get(0))) {
    8. temp = targets.get(ticket.get(0));
    9. temp.put(ticket.get(1), temp.getOrDefault(ticket.get(1), 0) + 1);
    10. }else {
    11. temp = new TreeMap<>(); //升序map
    12. temp.put(ticket.get(1), 1);
    13. }
    14. targets.put(ticket.get(0), temp);
    15. }
    16. res.add("JFK");
    17. backtrack(tickets.size());
    18. return new ArrayList<>(res);
    19. }
    20. public boolean backtrack(int ticketsNum){
    21. if (res.size() == ticketsNum + 1) {
    22. return true;
    23. }
    24. String last = res.getLast();
    25. if (targets.containsKey(last)) {
    26. for (Map.Entry<String, Integer> target : targets.get(last).entrySet()) {
    27. int count = target.getValue();
    28. if (count > 0) {
    29. res.add(target.getKey());
    30. target.setValue(count - 1);
    31. if (backtrack(ticketsNum)) {
    32. return true;
    33. }
    34. target.setValue(count);
    35. res.removeLast();
    36. }
    37. }
    38. }
    39. return false;
    40. }
    41. }