LeetCode之回溯(电话号码的字母组合,括号生成,全排列等等)

115 阅读1分钟

image.png

image.png

image.png 分析:首先说明使用回溯,通过题眼“可能的所有组合,排列等” 对于回溯: 回溯本身就是暴力穷举,通过递归实现,这里不要纠结回溯和递归的关系,回溯就是一个套路通过递归实现而已。 主要有三个点:

  1. 路径:这三题其中对应的ad,((())),[1,2,3]就是一条完整的路径
  2. 决策:即每一个可能的选择,第一题,a的决策可以为def任一,(的决策可以为(或者),1的决策可以为2,3
  3. 结束条件:结束条件为通常为路径的长度,是固定的,比如这三题中对应的分别是2,6,3

下面的公式便是所有回溯解的思想

void recall(res, array, singleRes) {
    // 结束条件
    if (singleRes.length == array.length) {
		res.add(singleRes);
    } else {
        // 这个for循环是指本次可选择的决策,理解这个很重要
	for(i,i<array.length,i++) {
		if(需要剪枝的情况) continue;

		// 决策生成路径
		singleRes.add(array[i]);
		recall(res, array, singleRes);

		// 撤销这一步,回到上一步	
		singleRes.remove(singleRes.length-1);										
	}
    }
}

回溯是暴力破解的过程,最终是生成一颗树,撤销是为了回到上一步的状态(表现为树的后序遍历),需要注意的是可能存在剪枝的情况也就是有些情况我们不需要,比如第二题,我们的不能以)开头且)不能大于(的,第三题是不能有重复的情况,所以我们需要进行剪枝 17 不需要剪枝

    public static List<String> letterCombinations(String digits) {
    	HashMap<Integer, String> map = new HashMap<>();
    	map.put(2, "abc");
    	map.put(3, "def");
    	map.put(4, "ghi");
    	map.put(5, "jkl");
    	map.put(6, "mno");
    	map.put(7, "pqrs");
    	map.put(8, "tuv");
    	map.put(9, "wxyz");
    	StringBuilder sb = new StringBuilder();
    	List<String> list = new LinkedList<>();
    	if ("".equals(digits)) {
    		return list;
    	}
    	return recall(digits,0,map,sb, list);
    	
    }

	private static List<String> recall(String digits, int i, HashMap<Integer, String> map, StringBuilder sb,
			List<String> list) {
		if (sb.length() == digits.length()) {
			list.add(sb.toString());
			return list;
		} else {
			int num = Integer.parseInt(digits.charAt(i)+"");
			String numStr = map.get(num);
			for (int j = 0; j < numStr.length(); j++) {
				sb.append(numStr.charAt(j));
				recall(digits, i+1, map, sb, list);
				sb.deleteCharAt(sb.length()-1);
			}
			
		}
		return list;
	}

22 需要剪枝,主要是()不能超过n数量以及)不能超过(

    public static List<String> generateParenthesis(int n) {
    	List<String> list = new LinkedList<String>();
    	recall(list, n, new StringBuilder(), 0, 0);
    	return list;
    }
	private static void recall(List<String> list, int n, StringBuilder sb, int left, int right) {
		if (sb.length() == n*2) {
			list.add(sb.toString());
			return;
		} else {
			for (int j = 0; j < 2; j++) {
				String str;
				if (j==0) {
					str = "(";
					left ++;
					if (left > n) continue;
				} else {
					str = ")";
					right ++;
					if (right > left || right > n) continue;
				}
				sb.append(str);
				recall(list, n, sb, left, right);
				sb.deleteCharAt(sb.length()-1);
				if (j==0) left--;
				else right--;
			}
		}
		
	}

46 存在剪枝的情况,不能有重复,注意结束时需要用新的list保存,否则浅拷贝会受影响

    public static List<List<Integer>> permute(int[] nums) {
    	List<List<Integer>> list = new LinkedList<>();
    	
    	List<Integer> res = new LinkedList<>();
    	recall(nums, list, res);
    	return list;
    }
	private static void recall(int[] nums, List<List<Integer>> list, List<Integer> res) {
		if (res.size() == nums.length) {
			list.add(new LinkedList<>(res));
			return;
		} else {
			for (int i = 0; i < nums.length; i++) {
				if (res.contains(nums[i])) continue;
				res.add(nums[i]);
				recall(nums, list, res);
				res.remove(res.lastIndexOf(nums[i]));
			}
		}
		
	}