LC998+剑指 Offer II 083+084+085+086+087

最大二叉树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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值