Java回溯知识点(含面试大厂题和源码)

时间:2024-03-28 10:04:03

回溯算法是一种通过遍历所有可能的候选解来寻找所有解的算法,如果候选解被确认不是一个解(或至少不是最后一个解),回溯算法会通过在上一步进行一些变化来丢弃这个解,即“回溯”并尝试另一个候选解。回溯法通常用递归方法来实现,在解决排列、组合、选择问题时非常有效。

回溯算法的核心要点:

  1. 路径:也就是已经做出的选择。
  2. 选择列表:也就是你当前可以做的选择。
  3. 结束条件:也就是到达决策树底层,无法再做出选择的条件。

回溯算法的框架:

回溯算法的基本形式大致如下:

void backtrack(路径, 选择列表) {
    if (满足结束条件) {
        result.add(路径);
        return;
    }

    for (选择 : 选择列表) {
        做选择;
        backtrack(路径, 选择列表);
        撤销选择;
    }
}

回溯算法的关键点:

  • 递归前做选择:将当前节点加入路径中,同时将其从选择列表移除。
  • 递归:递归前的选择导向下一层决策树。
  • 递归后撤销选择:将之前做的选择撤销,以回溯至上一步,从而尝试下一个选项。

回溯算法的应用场景:

  • 排列问题:要求输出一个集合的所有排列方式,比如全排列问题。
  • 组合问题:要求输出满足条件的所有组合方式,比如组合总和问题。
  • 选择问题:从集合中选出满足条件的所有子集,比如子集问题、分割问题。

优化技巧:

  • 剪枝:在遍历过程中,对于明显不会得到正确解的情况直接跳过,减少不必要的计算。
  • 使用合适的数据结构:比如使用数组、链表或是集合等,根据问题场景选用最适合的数据结构,以便于添加、删除操作,优化时间复杂度。

回溯算法通过尝试分步的方式去解决一个问题,在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答时,它将取消上一步甚至是上几步的计算,再通过其他的可能的分步解答再次尝试寻找问题的答案。回溯法非常适合用来解决由多个步骤组成的问题,每个步骤有多个选项,通过尝试每个选项并回退到上一个步骤,最终找到解决问题的方法。在软件开发面试中,经常会遇到需要编写源码来解决问题的算法题。这里我将为你提供三道面试中常见的算法题及其Java实现,这些题目不仅考察基本编程能力,还考察对特定算法和数据结构的理解和应用。

1. 全排列

题目描述:给定一个没有重复数字的序列,返回其所有可能的全排列。

示例

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

Java解法

public List<List<Integer>> permute(int[] nums) {
    List<List<Integer>> list = new ArrayList<>();
    backtrack(list, new ArrayList<>(), nums);
    return list;
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums){
    if(tempList.size() == nums.length){
        list.add(new ArrayList<>(tempList));
    } else{
        for(int i = 0; i < nums.length; i++){
            if(tempList.contains(nums[i])) continue; // element already exists, skip
            tempList.add(nums[i]);
            backtrack(list, tempList, nums);
            tempList.remove(tempList.size() - 1);
        }
    }
}

2. 合并K个排序链表

题目描述:合并 k 个排序链表,返回合并后的排序链表。

示例

输入:
[
  1->4->5,
  1->3->4,
  2->6
]
输出: 1->1->2->3->4->4->5->6

Java解法

public ListNode mergeKLists(ListNode[] lists) {
    if(lists.length == 0) return null;
    PriorityQueue<ListNode> queue = new PriorityQueue<>(lists.length, Comparator.comparingInt(a -> a.val));
    ListNode dummy = new ListNode(0);
    ListNode tail = dummy;
    for(ListNode node : lists)
        if(node != null)
            queue.add(node);
    while(!queue.isEmpty()){
        tail.next = queue.poll();
        tail = tail.next;
        if(tail.next != null)
            queue.add(tail.next);
    }
    return dummy.next;
}

3. 最长连续序列

题目描述:给定一个未排序的整数数组,找出最长连续序列的长度。

示例

输入: [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4]。长度为 4。

Java解法

public int longestConsecutive(int[] nums) {
    Set<Integer> numSet = new HashSet<>();
    for (int num : nums) {
        numSet.add(num);
    }
    int longestStreak = 0;
    for (int num : numSet) {
        if (!numSet.contains(num - 1)) {
            int currentNum = num;
            int currentStreak = 1;
            while (numSet.contains(currentNum + 1)) {
                currentNum += 1;
                currentStreak += 1;
            }
            longestStreak = Math.max(longestStreak, currentStreak);
        }
    }
    return longestStreak;
}

这三道题目分别覆盖了回溯算法、优先队列和哈希表的使用,是面试中常见的算法题类型。理解这些解法的关键思想和代码实现,对准备软件开发面试大有帮助。