最大二叉树II
最大树 定义:一棵树,并满足:其中每个节点的值都大于其子树中的任何其他值。
给你最大树的根节点 root 和一个整数 val 。
分析:
首先题目的意思是最大树 root 是根据特定的规则构造出来的,即给定的 root 其实对应一个具体的 nums,题目要求是将 val 追加到 nums 的尾部,然后再对得到的 nums 运用相同规则的构造,返回重新构造的最大树头结点。
根据构造规则,若有下标 i < j,则 nums[i] 必然在nums[j] 水平线的左边,而 val 又是追加在原有 nums 的结尾。因此其最终位置分如下两种情况:
-
val 为新 nums 中的最大值,同时 val 又是追加在原有 nums 的结尾,此时将原有的 root 挂在 val 对应节点的左子树即可,新树的根节点为 val 对应节点;
-
否则,只需要不断在 root 的右子树中找目标位置(反证法可以知,val 必不可能出现在任一非右位置,否则可推断出在 val 右边仍有元素,这与 val 位于 nums 的结尾位置冲突)。假设目标位置的父节点为 prev,目标位置的原节点为 cur,根据构造规则可知 prev.right = node 且 node.left = cur,新树的根节点不变。
class Solution {
public TreeNode insertIntoMaxTree(TreeNode root, int val) {
TreeNode node = new TreeNode(val);
TreeNode prev = null, cur = root;
while (cur != null && cur.val > val) {
prev = cur; cur = cur.right;
}
if (prev == null) {
node.left = cur;
return node;
} else {
prev.right = node;
node.left = cur;
return root;
}
}
}
没有重复元素集合的全排列
给定一个不含重复数字的整数数组 nums ,返回其 所有可能的全排列 。可以 按任意顺序 返回答案。
分析:
使用回溯法。这个问题可以看作有 n 个排列成一行的空格,需要从左往右依此填入题目给定的 n 个数,每个数只能使用一次。
定义递归函数backtrack(first,output) 表示从左往右填到第first 个位置,当前排列为output。 那么整个递归函数分为两个情况:
- 如果first=n,说明我们已经填完了 n 个位置,找到了一个可行的解,将output 放入答案数组中,递归结束。
- 如果first<n,要考虑这第first 个位置要填哪个数。根据题目要求肯定不能填已经填过的数,因此很容易想到的一个处理手段是定义一个标记数组vis 来标记已经填过的数,那么在填第 first 个数的时候我们遍历题目给定的 n 个数,如果这个数没有被标记过,就尝试填入并将其标记,继续尝试填下一个位置,即调用函数backtrack(first+1,output)。回溯的时候要撤销这一个位置填的数以及标记,并继续尝试其他没被标记过的数。
想要不使用标记数组也是可以的:可以将题目给定的 n 个数的数组nums 划分成左右两个部分,左边的表示已经填过的数,右边表示待填的数,我们在回溯的时候只要动态维护这个数组即可。
具体来说,假设我们已经填到第first 个位置,那么nums 数组中[0,first−1] 是已填过的数的集合,[first,n−1] 是待填的数的集合。我们肯定是尝试用[first,n−1] 里的数去填第 first 个数,假设待填的数的下标为 i,那么填完以后我们将第 i 个数和第first 个数交换,即能使得在填第first+1 个数的时候nums 数组的 [0,first] 部分为已填过的数,[first+1,n−1] 为待填的数,回溯的时候交换回来即能完成撤销操作。
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> output = new ArrayList<Integer>();
for (int num : nums) {
output.add(num);
}
int n = nums.length;
backtrack(n, output, res, 0);
return res;
}
public void backtrack(int n, List<Integer> output, List<List<Integer>> res, int first) {
// 所有数都填完了
if (first == n) {
res.add(new ArrayList<Integer>(output));
}
for (int i = first; i < n; i++) {
// 动态维护数组
Collections.swap(output, first, i);
// 继续递归填下一个数
backtrack(n, output, res, first + 1);
// 撤销操作
Collections.swap(output, first, i);
}
}
}
含有重复元素集合的全排列组合
给定一个可包含重复数字的整数集合 nums ,按任意顺序 返回它所有不重复的全排列。
分析:
与上一题思路一样,只不过多了一步剪枝。当遍历[first,n-1]位置的数时,如果当前数与前一个数字相同则跳过该数字,防止出现重复的答案。
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
int n = nums.length;
Arrays.sort(nums);
List<Integer> output = new ArrayList<>();
for (int num : nums) output.add(num);
backtrack(n ,output, res, 0);
return res;
}
public void backtrack(int n, List<Integer> output, List<List<Integer>> res, int first){
if (first == n){
res.add(new ArrayList<>(output));
return;
}
for (int i = first; i < n; i++) {
if (i>0 && Objects.equals(output.get(i - 1), output.get(i))) continue;
Collections.swap(output, first, i);
backtrack(n, output, res, first+1);
Collections.swap(output, first, i);
}
}
}
生成匹配的括号
正整数 n 代表生成括号的对数,请设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
输入:n = 3
输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]
分析:
使用回溯法。定义递归函数backtrack(int n, String s, int l, int r),n代表需要生成括号的对数,s是当前生成的字符串,l是字符串中左括号的个数,r是当前右括号的字数。可以确定的是s的第一个括号一定是左括号,所以s初始化为"(",l=1,r=0. 当s的长度等于n的2倍,且l == r时说明当前s是一个可行的组合,将s添加到答案中。剪枝:当l+r>2n或者r>l时,当前s是一个错误的解,直接返回。否则继续去递归backtrack(n,s,l+1,r), s += “(” 和 backtrack(n,s,l,r+1), s += “)”。
class Solution {
List<String> res = new ArrayList<>();
public List<String> generateParenthesis(int n) {
String s = "(";
backtrack(n, s, 1 , 0);
return res;
}
public void backtrack(int n, String s, int l, int r) {
if (s.length() == 2 * n && l == r) {
res.add(new String(s));
return;
}
if (r > l || l + r > 2 * n) return;
s += "(";
backtrack(n, s, l + 1, r);
s = s.substring(0, s.length() - 1);
s += ")";
backtrack(n, s, l, r + 1);
}
}
分割回文子字符串
给定一个字符串 s ,请将 s 分割成一些子串,使每个子串都是 回文串 ,返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
分析:
使用回溯法穷举所有的可能性。定义递归函数backtrack(n, list, s),n是当前分割的位置(表示下标n之前的字符串都以分割完成),list是含有回文字符串的集合,s是输入的要分割的字符串。如果n == s的长度,则代表分割完成,把list加入到答案集合中。否则从n开始寻找从n开始的回文字符串,如果发现下标[n,i]是一个回文字符串,则将其添加到list中,然后递归的调用backtrack(i+1, list, s), 寻找完成后记得回溯。
import java.util.*;
class Solution {
List<List<String>> res = new ArrayList<>();
public String[][] partition(String s) {
List<String> lis = new ArrayList<>();
backtrack(0, lis, s);
String[][] ans = new String[res.size()][];
for (int i = 0; i < ans.length; i++) {
int cols = res.get(i).size();
ans[i] = new String[cols];
for (int j = 0; j < cols; j++) {
ans[i][j] = res.get(i).get(j);
}
}
return ans;
}
public boolean isHui(String s){
int l = 0 ,r = s.length()-1;
while (l<r){
if (s.charAt(l) != s.charAt(r)) return false;
l++;
r--;
}
return true;
}
public void backtrack(int n, List<String> list, String s){
if (n == s.length()){
res.add(new ArrayList<>(list));
return;
}
for (int i = n; i < s.length(); i++) {
if (isHui(s.substring(n, i+1))){
list.add(s.substring(n, i+1));
backtrack(i+1, list, s);
list.remove(list.size()-1);
}
}
}
}
复原IP
给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 IP 地址 。你可以按任何顺序返回答案。
有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。
例如:“0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效 IP 地址。
分析:
与上一题类似也是回溯法穷举加判断。dfs(s, n, ip),可以理解为对s的一个分割问题,n是当前分割到的位置,ip里面是已经分割好的满足条件的地址。当n==s的长度且ip的大小为4是表示当前是一个可行解。
import java.util.ArrayList;
import java.util.List;
class Solution {
List<String> ans = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
dfs(s, 0, new ArrayList<>());
return ans;
}
public void dfs(String s, int n, List<String> ip){
if (n == s.length() && ip.size()==4){
StringBuilder sb = new StringBuilder();
for (String i : ip){
sb.append(i).append('.');
}
ans.add(new String(sb.deleteCharAt(sb.length()-1)).toString());
return;
}
if (ip.size()>4) return;
for (int i = n; i < s.length(); i++) {
if (isIp(s.substring(n, i+1))){
ip.add(s.substring(n, i+1));
dfs(s, i+1, ip);
ip.remove(ip.size()-1);
}
}
}
public boolean isIp(String s){
if (s.length()>=1 && s.length()<=3){
if (s.length()>1 && s.charAt(0) == '0') return false;
if (Integer.parseInt(s)>=0 && Integer.parseInt(s)<=255) return true;
}
return false;
}
}