剑指offer专项突破快刷(二)

89 阅读8分钟

微信图片_20230512152603.jpg

考察以下两点:

  • 二叉树的遍历、序列化
  • 二叉搜索树

剑指 Offer II 047. 二叉树剪枝

class Solution {
    public TreeNode pruneTree(TreeNode root) {
        if (root == null) {
            return root;
        }
        TreeNode left = null;
        if (root.left != null) {
            left = pruneTree(root.left);
        }
        TreeNode right = null;
        if (root.right != null) {
            right = pruneTree(root.right);
        }
        if (root.val == 0 && left == null && right == null) {
            return null;
        }
        root.left = left;
        root.right = right;
        return root;
    }
}

剑指 Offer II 048. 序列化与反序列化二叉树

// 前序遍历
public class Codec {
    StringBuilder sb = new StringBuilder();
    String NULL = "#";
    String SPLIT = ",";

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        serializeTree(root);
        return sb.toString();
    }

    public void serializeTree(TreeNode root) {
        if (root == null) {
            sb.append(NULL).append(SPLIT);
            return;
        }

        sb.append(root.val).append(SPLIT);
        serializeTree(root.left);
        serializeTree(root.right);
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        String[] nodeArray = data.split(SPLIT);
        LinkedList<String> nodeList = new LinkedList<>();
        for (String node : nodeArray) {
            nodeList.add(node);
        }
        return deserialize(nodeList);
    }

    public TreeNode deserialize(LinkedList<String> nodes) {
        if (nodes.isEmpty()) {
            return null;
        }

        String first = nodes.removeFirst();
        if (NULL.equals(first)) {
            return null;
        }

        TreeNode head = new TreeNode(Integer.parseInt(first));
        head.left = deserialize(nodes);
        head.right = deserialize(nodes);
        return head;
    }
}

剑指 Offer II 049. 从根节点到叶节点的路径数字之和


//深度优先遍历
class Solution {
    public int sumNumbers(TreeNode root) {
        return dfs(root, 0);
    }

    public int dfs(TreeNode root, int preSum) {
        if (root == null) {
            return 0;
        }
        int sum = preSum * 10 + root.val;
        if (root.left == null && root.right == null) {
            return sum;
        } else {
            return dfs(root.left, sum) + dfs(root.right, sum);
        }
    }
}
//leetcode submit region end(Prohibit modification and deletion)
// 广度优先
class Solution {
    public int sumNumbers(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return bfs(root);
    }

    public int bfs(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        Queue<Integer> nums = new LinkedList<>();
        queue.offer(root);
        nums.add(0);

        int sum = 0;
        while (!queue.isEmpty()) {
            TreeNode cur = queue.poll();
            int val = cur.val + nums.poll() * 10;
            if (cur.left != null) {
                queue.offer(cur.left);
                nums.offer(val);
            }
            if (cur.right != null) {
                queue.offer(cur.right);
                nums.offer(val);
            }
            if (cur.left == null && cur.right == null) {
                // 找到根节点了
                sum += val;
            }
        }
        return sum;
    }
}

剑指 Offer II 050. 向下的路径节点之和

// 回溯,把所有可能的路径都列出来
class Solution {
    public int pathSum(TreeNode root, int targetSum) {
        if (root == null) {
            return 0;
        }

        int ret = rootSum(root, targetSum);
        ret += pathSum(root.left, targetSum);
        ret += pathSum(root.right, targetSum);
        return ret;
    }

    public int rootSum(TreeNode root, long targetSum) {
        int ret = 0;

        if (root == null) {
            return 0;
        }
        int val = root.val;
        if (val == targetSum) {
            ret++;
        }

        ret += rootSum(root.left, targetSum - val);
        ret += rootSum(root.right, targetSum - val);
        return ret;
    }
}
// 前缀和
class Solution {
    public int pathSum(TreeNode root, int targetSum) {

        if (root == null) {
            return 0;
        }
        Map<Long, Integer> map = new HashMap<>();
        // 0也是一种可能性
        map.put(0L, 1);
        return dfs(root, map, 0, targetSum);
    }

    public int dfs(TreeNode root, Map<Long, Integer> prefix, long curr, int targetSum) {
        if (root == null) {
            return 0;
        }

        curr += root.val;

        int ret = prefix.getOrDefault(curr - targetSum, 0);
        prefix.put(curr, prefix.getOrDefault(curr, 0) + 1);

        ret += dfs(root.left, prefix, curr, targetSum);
        ret += dfs(root.right, prefix, curr, targetSum);

        prefix.put(curr, prefix.getOrDefault(curr, 0) - 1);

        return ret;
    }
}

剑指 Offer II 051. 节点之和最大的路径

class Solution {
    int max = Integer.MIN_VALUE;

    public int maxPathSum(TreeNode root) {
        maxRoot(root);
        return max;
    }

    // 经过当前节点的最长路径
    public int maxRoot(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftMax = Math.max(maxRoot(root.left), 0);
        int rightMax = Math.max(maxRoot(root.right), 0);
        int priceNewpath = root.val + leftMax + rightMax;
        max = Math.max(max, priceNewpath);
        return root.val + Math.max(leftMax, rightMax);
    }
}

剑指 Offer II 052. 展平二叉搜索树

// 方法一:先保存中序遍历二叉搜索树的结果
class Solution {
    public TreeNode increasingBST(TreeNode root) {
        TreeNode p = new TreeNode(-1);
        TreeNode pre = p;
        List<Integer> ans = new ArrayList<>();
        dfs(root, ans);
        for (int v : ans) {
            p.right = new TreeNode(v);
            p = p.right;
        }
        return pre.right;
    }

    public void dfs(TreeNode root, List<Integer> ans) {
        if (root == null) {
            return;
        }
        dfs(root.left, ans);
        ans.add(root.val);
        dfs(root.right, ans);
    }
}
// 方法二:用一个全局的node,不断的改变node的指向
class Solution {
    private TreeNode resNode;

    public TreeNode increasingBST(TreeNode root) {
        TreeNode dummyNode = new TreeNode(-1);
        resNode = dummyNode;
        inorder(root);
        return dummyNode.right;
    }

    public void inorder(TreeNode node) {
        if (node == null) {
            return;
        }
        inorder(node.left);

        // 在中序遍历的过程中修改节点指向
        resNode.right = node;
        node.left = null;
        resNode = node;

        inorder(node.right);
    }
}

剑指 Offer II 053. 二叉搜索树中的中序后继

// 方法一:记录pre,要记住中序遍历的迭代写法
class Solution {
    public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
        Deque<TreeNode> stack = new ArrayDeque<TreeNode>();
        TreeNode prev = null, curr = root;
        while (!stack.isEmpty() || curr != null) {
            while (curr != null) {
                stack.push(curr);
                curr = curr.left;
            }
            curr = stack.pop();
            if (prev == p) {
                return curr;
            }
            prev = curr;
            curr = curr.right;
        }
        return null;
    }
}

// 方法二:使用搜索树的性质
class Solution {
    public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
        TreeNode successor = null;
        // p有右子树,那要寻找右子树里最左的,就是中序后继
        if (p.right != null) {
            successor = p.right;
            while (successor.left != null) {
                successor = successor.left;
            }
            return successor;
        }
        // p没有右子树,p可能是树中的一个节点,也可能是树的最后一个节点
        TreeNode node = root;
        while (node != null) {
            // node比p大,说明后继肯定出现在node的左子树或者自己
            if (node.val > p.val) {
                successor = node;
                node = node.left;
            } else {
                // node比p小,肯定是在右子树
                // node等于p,那也是在右子树
                node = node.right;
            }
        }
        return successor;
    }
}

剑指 Offer II 054. 所有大于等于节点的值之和

class Solution {
    int sum = 0;

    public TreeNode convertBST(TreeNode root) {
        // 大于等于节点的值肯定出现在自己和右子树
        // 如果按照右、中、左来遍历,那比自己大的元素都在自己之前遍历过了
        TreeNode head = root;
        dfs(root);
        return head;
    }

    public void dfs(TreeNode node) {
        if (node == null) {
            return;
        }
        dfs(node.right);
        int val = node.val;
        sum += val;
        node.val = sum;
        dfs(node.left);
    }
}

剑指 Offer II 055. 二叉搜索树迭代器

class BSTIterator {

    TreeNode cur = null;
    TreeNode resNode = null;

    public BSTIterator(TreeNode root) {
        cur = new TreeNode(-1);
        resNode = cur;
        dfs(root);
    }

    public void dfs(TreeNode node) {
        if (node == null) {
            return;
        }
        dfs(node.left);
        resNode.right = node;
        node.left = null;
        resNode = node;
        dfs(node.right);
    }

    public int next() {
        TreeNode next = cur.right;
        cur = cur.right;
        return next.val;
    }

    public boolean hasNext() {
        return cur != null && cur.right != null;
    }
}

剑指 Offer II 056. 二叉搜索树中两个节点之和

class Solution {
    public boolean findTarget(TreeNode root, int k) {
        List<Integer> vals = new ArrayList<>();
        dfs(root, vals);

        int left = 0;
        int right = vals.size() - 1;
        while (left < right) {
            int sum = vals.get(left) + vals.get(right);
            if (sum == k) {
                return true;
            } else if (sum < k) {
                left++;
            } else {
                right--;
            }
        }
        return false;
    }

    public void dfs(TreeNode root, List<Integer> vals) {
        if (root == null) {
            return;
        }
        dfs(root.left, vals);
        vals.add(root.val);
        dfs(root.right, vals);
    }
}

剑指 Offer II 057. 值和下标之差都在给定的范围内

剑指 Offer II 058. 日程表

微信图片_20230526100228.jpg

剑指 Offer II 059. 数据流的第 K 大数值

class KthLargest {
    int k;
    PriorityQueue<Integer> queue;

    public KthLargest(int k, int[] nums) {
        this.k = k;
        // [4, 5, 8, 2] 3,5,10,9,4
        // 顺序,从小到大
        this.queue = new PriorityQueue<>();

        if (nums.length > 0) {
            for (int num : nums) {
                add(queue, num);
            }
        }
    }

    private void add(PriorityQueue<Integer> queue, int val) {
        if (queue.isEmpty() || queue.size() < k) {
            queue.offer(val);
            return;
        }
        int head = queue.peek();
        if (val > head) {
            queue.poll();
            queue.offer(val);
        }
    }

    public int add(int val) {
        add(queue, val);

        // 返回队头
        return queue.size() < k ? -1 : queue.peek();
    }
}

剑指 Offer II 060. 出现频率最高的 k 个数字

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }

        // 从大到小排
        PriorityQueue<Model> queue = new PriorityQueue<>((o1, o2) -> {
            return o1.count - o2.count;
        });

        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            if (queue.size() < k) {
                queue.offer(new Model(entry.getKey(), entry.getValue()));
                continue;
            }
            int head = queue.peek().count;
            if (entry.getValue() > head) {
                queue.poll();
                queue.offer(new Model(entry.getKey(), entry.getValue()));
            }
        }
        int size = queue.size();
        int[] ans = new int[size];
        for (int i = 0; i < size; i++) {
            ans[i] = queue.poll().val;
        }
        return ans;
    }

    public class Model {
        int val;
        int count;

        public Model(int val, int count) {
            this.val = val;
            this.count = count;
        }
    }
}

剑指 Offer II 061. 和最小的 k 个数对

简单,懒得写。

二分查找

微信图片_20230512152606.jpg

剑指 Offer II 068. 查找插入位置

class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        // 找到它或者它的左边界
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return nums[left] < target ? left + 1 : left;
    }
}

二分也可以用来比较相邻位置。

剑指 Offer II 069. 山峰数组的顶部

class Solution {
    public int peakIndexInMountainArray(int[] arr) {
        // 找到峰顶
        int left = 0;
        int right = arr.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] > arr[mid + 1]) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return right;
    }
}

剑指 Offer II 070. 排序数组中只出现一次的数字

class Solution {
    public int singleNonDuplicate(int[] nums) {
        int low = 0, high = nums.length - 1;
        while (low < high) {
            int mid = (high - low) / 2 + low;
            if (nums[mid] == nums[mid ^ 1]) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }
        return nums[low];
    }
}
class Solution {
    public int singleNonDuplicate(int[] nums) {
        int low = 0, high = nums.length - 1;
        while (low < high) {
            int mid = (high - low) / 2 + low;
            mid -= mid & 1;
            if (nums[mid] == nums[mid + 1]) {
                low = mid + 2;
            } else {
                high = mid;
            }
        }
        return nums[low];
    }
}

剑指 Offer II 071. 按权重生成随机数

class Solution {
    int[] preSums;
    int sum = 0;
    Random random;

    public Solution(int[] w) {
        int n = w.length;
        preSums = new int[n];
        random = new Random();
        for (int i = 0; i < n; i++) {
            sum += w[i];
            preSums[i] = sum;
        }
    }

    public int pickIndex() {
        return find(preSums, random.nextInt(sum) + 1);
    }

    public int find(int[] nums, int target) {
        int left = 0, right = nums.length;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] >= target) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
}

剑指 Offer II 072. 求平方根

class Solution {
    public int mySqrt(int x) {
        // 找到左边界
        int left = 0;
        int right = x;
        int ans = 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            long sqr = (long) mid * mid;
            if (sqr <= x) {
                ans = mid;
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return ans;
    }
}

剑指 Offer II 073. 狒狒吃香蕉

class Solution {
    public int minEatingSpeed(int[] piles, int h) {
        // 速度越快,花费的时间越少,所以是一个倒序数组
        int left = 1;
        int right = 0;
        for (int pile : piles) {
            right = Math.max(right, pile);
        }
        int ans = -1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            int cost = cost(piles, mid);

            if (cost > h) {
                // 花费的时间比当前要求的多,加快速度
                left = mid + 1;
            } else {
                ans = mid;
                // 花费的时间比当前要求的少,减慢速度
                right = mid - 1;
            }
        }
        return ans;
    }

    public int cost(int[] piles, int k) {
        int cost = 0;
        for (int pile : piles) {
            cost += pile / k;
            if (pile % k > 0) {
                cost++;
            }
        }
        return cost;
    }
}

排序

微信图片_20230516095658.jpg

剑指 Offer II 074. 合并区间

class Solution {
    public int[][] merge(int[][] intervals) {
        // 升序排序
        Arrays.sort(intervals, (o1, o2) -> {
            // 第一个元素相等,第二个元素升序
            if (o1[0] == o2[0]) {
                return o2[1] - o2[1];
            }
            return o1[0] - o2[0];
        });
        LinkedList<int[]> merged = new LinkedList<>();
        merged.add(intervals[0]);
        for (int i = 1; i < intervals.length; i++) {
            int[] cur = intervals[i];
            int[] last = merged.getLast();
            // 不相交,是另一个区间了
            if (cur[0] > last[1]) {
                merged.add(cur);
            } else {
                int val = Math.max(cur[1], last[1]);
                last[1] = val;
                merged.set(merged.size() - 1, last);
            }
        }
        int[][] ans = new int[merged.size()][2];
        for (int i = 0; i < merged.size(); i++) {
            ans[i] = merged.get(i);
        }
        return ans;
    }
}

剑指 Offer II 075. 数组相对排序

剑指 Offer II 076. 数组中的第 k 大的数字

剑指 Offer II 077. 链表排序

剑指 Offer II 078. 合并排序链表

回溯

微信图片_20230526100307.jpg

剑指 Offer II 079. 所有子集

class Solution {
    List<List<Integer>> ans = null;

    public List<List<Integer>> subsets(int[] nums) {
        ans = new ArrayList<>();
        backtrace(0, nums, new LinkedList<>());
        return ans;
    }

    public void backtrace(int idx, int[] nums, LinkedList<Integer> path) {
        ans.add(new ArrayList<>(path));

        for (int i = idx; i < nums.length; i++) {
            path.add(nums[i]);
            backtrace(i + 1, nums, path);
            path.removeLast();
        }
    }
}

剑指 Offer II 080. 含有 k 个元素的组合

//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    List<List<Integer>> ans = null;

    public List<List<Integer>> combine(int n, int k) {
        ans = new ArrayList<>();
        backtrace(k, 1, n, new LinkedList<>());
        return ans;
    }

    public void backtrace(int k, int idx, int n, LinkedList<Integer> path) {
        if (path.size() == k) {
            ans.add(new ArrayList<>(path));
        }

        for (int i = idx; i <= n; i++) {
            path.add(i);
            backtrace(k, i + 1, n, path);
            path.removeLast();
        }
    }
}
//leetcode submit region end(Prohibit modification and deletion)

剑指 Offer II 081. 允许重复选择元素的组合

class Solution {
    List<List<Integer>> ans;

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        ans = new ArrayList<>();
        backtrace(0, candidates, target, 0, new LinkedList<Integer>());
        return ans;
    }

    public void backtrace(int idx, int[] candidates, int target, int sum, LinkedList<Integer> path) {
        if (sum == target) {
            ans.add(new ArrayList<Integer>(path));
            return;
        }
        // 已经超过目标,不需要再加了
        if (sum > target) {
            return;
        }
        for (int i = idx; i < candidates.length; i++) {
            path.add(candidates[i]);
            sum += candidates[i];
            backtrace(i, candidates, target, sum, path);
            path.removeLast();
            sum -= candidates[i];
        }
    }
}

剑指 Offer II 082. 含有重复元素集合的组合

class Solution {
    List<List<Integer>> ans;
    boolean[] visited;

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        ans = new ArrayList<>();
        visited = new boolean[candidates.length];

        // 必须排序后才能用i和i-1的比较来剪枝
        Arrays.sort(candidates);
        backtrace(candidates, target, 0, 0, new LinkedList<>());
        return ans;
    }

    public void backtrace(int[] candidates, int target, int sum, int idx, LinkedList<Integer> path) {
        if (sum == target) {
            ans.add(new ArrayList<>(path));
            return;
        }
        if (sum > target) {
            return;
        }

        for (int i = idx; i < candidates.length; i++) {
            if (i > 0 && !visited[i - 1] && candidates[i] == candidates[i - 1]) {
                continue;
            }
            visited[i] = true;
            path.add(candidates[i]);
            sum += candidates[i];
            backtrace(candidates, target, sum, i + 1, path);
            sum -= candidates[i];
            path.removeLast();
            visited[i] = false;
        }
    }
}

剑指 Offer II 083. 没有重复元素集合的全排列

class Solution {
    List<List<Integer>> ans;
    boolean[] used;

    public List<List<Integer>> permute(int[] nums) {
        ans = new ArrayList<>();
        used = new boolean[nums.length];
        backtrace(nums, new LinkedList<Integer>());
        return ans;
    }

    public void backtrace(int[] nums, LinkedList<Integer> path) {
        if (nums.length == path.size()) {
            ans.add(new ArrayList<Integer>(path));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (used[i]) {
                continue;
            }
            path.add(nums[i]);
            used[i] = true;
            backtrace(nums, path);
            path.removeLast();
            used[i] = false;
        }
    }
}

剑指 Offer II 084. 含有重复元素集合的全排列

//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    List<List<Integer>> ans;
    boolean[] used;

    public List<List<Integer>> permuteUnique(int[] nums) {
        ans = new ArrayList<>();
        used = new boolean[nums.length];
        Arrays.sort(nums);
        backtrace(nums, new LinkedList<Integer>());
        return ans;
    }

    public void backtrace(int[] nums, LinkedList<Integer> path) {
        if (nums.length == path.size()) {
            ans.add(new ArrayList<Integer>(path));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (used[i]) {
                continue;
            }
            if (i > 0 && !used[i - 1] && nums[i - 1] == nums[i]) {
                continue;
            }
            path.add(nums[i]);
            used[i] = true;
            backtrace(nums, path);
            path.removeLast();
            used[i] = false;
        }
    }
}
//leetcode submit region end(Prohibit modification and deletion)

剑指 Offer II 085. 生成匹配的括号

//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    List<String> ans;

    public List<String> generateParenthesis(int n) {
        // 右括号的数量不能超过左括号
        // 当左右括号的数量相等并且等于n的时候,就是有效的括号
        ans = new ArrayList<>();
        backtrace(0, 0, n, new StringBuilder());
        return ans;
    }

    public void backtrace(int left, int right, int n, StringBuilder path) {
        if (right > left || left > n || right > n) {
            return;
        }
        if (right == left && left == n) {
            ans.add(path.toString());
            return;
        }
        path.append("(");
        left++;
        backtrace(left, right, n, path);
        left--;
        path.deleteCharAt(path.length() - 1);

        path.append(")");
        right++;
        backtrace(left, right, n, path);
        right--;
        path.deleteCharAt(path.length() - 1);
    }
}
//leetcode submit region end(Prohibit modification and deletion)

剑指 Offer II 086. 分割回文子字符串

class Solution {
    boolean[][] f;
    List<List<String>> tmp = new ArrayList<List<String>>();
    List<String> ans = new ArrayList<String>();
    int n;

    public String[][] partition(String s) {
        n = s.length();
        f = new boolean[n][n];
        for (int i = 0; i < n; ++i) {
            Arrays.fill(f[i], true);
        }

        for (int i = n - 1; i >= 0; --i) {
            for (int j = i + 1; j < n; ++j) {
                f[i][j] = (s.charAt(i) == s.charAt(j)) && f[i + 1][j - 1];
            }
        }

        dfs(s, 0);

        int rows = tmp.size();
        String[][] ret = new String[rows][];
        for (int i = 0; i < rows; ++i) {
            int cols = tmp.get(i).size();
            ret[i] = new String[cols];
            for (int j = 0; j < cols; ++j) {
                ret[i][j] = tmp.get(i).get(j);
            }
        }
        return ret;
    }

    public void dfs(String s, int i) {
        if (i == n) {
            tmp.add(new ArrayList<String>(ans));
            return;
        }
        for (int j = i; j < n; ++j) {
            if (f[i][j]) {
                ans.add(s.substring(i, j + 1));
                dfs(s, j + 1);
                ans.remove(ans.size() - 1);
            }
        }
    }
}

剑指 Offer II 087. 复原 IP

class Solution {
    List<String> ans;
    int n;

    public List<String> restoreIpAddresses(String s) {
        ans = new ArrayList<>();
        n = s.length();
        backtrace(s, 0, new LinkedList<String>());
        return ans;
    }

    public void backtrace(String s, int i, LinkedList<String> tmp) {
        if (tmp.size() > 4 || i > n) {
            return;
        }
        if (tmp.size() == 4 && i == n) {
            StringBuilder sb = new StringBuilder();
            for (String ss : tmp) {
                sb.append(ss).append(".");
            }
            sb.deleteCharAt(sb.length() - 1);
            ans.add(sb.toString());
            return;
        }
        for (int j = i; j < n; j++) {
            if (isSubCode(s, i, j)) {
                tmp.add(s.substring(i, j + 1));
                backtrace(s, j + 1, tmp);
                tmp.removeLast();
            }
        }
    }

    public boolean isSubCode(String s, int i, int j) {
        if (j - i > 2) {
            return false;
        }
        if (s.charAt(i) == '0' && j > i) {
            return false;
        }
        int val = Integer.parseInt(s.substring(i, j + 1));
        return val >= 0 && val <= 255;
    }
}

动态规划

微信图片_20230526100214.jpg

剑指 Offer II 088. 爬楼梯的最少成本

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        // f(i)为到达该位置所需要的花费
        int len = cost.length;
        int[] f = new int[len + 1];
        for (int i = 2; i <= len; i++) {
            f[i] = Math.min(f[i - 2] + cost[i - 2], f[i - 1] + cost[i - 1]);
        }
        return f[len];
    }
}

剑指 Offer II 089. 房屋偷盗

class Solution {
    public int rob(int[] nums) {
        int len = nums.length;
        if (len == 1) {
            return nums[0];
        }
        // 偷完第i家所偷到的最大金额
        int[] f = new int[len];
        f[0] = nums[0];
        f[1] = Math.max(nums[0], nums[1]);
        int max = Math.max(f[0], f[1]);
        for (int i = 2; i < len; i++) {
            f[i] = Math.max(f[i - 2] + nums[i], f[i - 1]);
            max = Math.max(max, f[i]);
        }
        return max;
    }
}

剑指 Offer II 090. 环形房屋偷盗

class Solution {
    public int rob(int[] nums) {
        int length = nums.length;
        if (length == 1) {
            return nums[0];
        } else if (length == 2) {
            return Math.max(nums[0], nums[1]);
        }
        return Math.max(robRange(nums, 0, length - 2), robRange(nums, 1, length - 1));
    }

    public int robRange(int[] nums, int start, int end) {
        int first = nums[start], second = Math.max(nums[start], nums[start + 1]);
        for (int i = start + 2; i <= end; i++) {
            int temp = second;
            second = Math.max(first + nums[i], second);
            first = temp;
        }
        return second;
    }
}

剑指 Offer II 091. 粉刷房子

class Solution {
    public int minCost(int[][] costs) {
        // dp[i] 第i个房子选择第j个颜色后的最少花费
        // dp[i][j] = costs[i][j] + Math.min(costs[i][k]...);
        int len = costs.length;
        int[][] dp = new int[len][3];
        dp[0][0] = costs[0][0];
        dp[0][1] = costs[0][1];
        dp[0][2] = costs[0][2];
        for (int i = 1; i < len; i++) {
            dp[i][0] = Math.min(dp[i - 1][1], dp[i - 1][2]) + costs[i][0];
            dp[i][1] = Math.min(dp[i - 1][0], dp[i - 1][2]) + costs[i][1];
            dp[i][2] = Math.min(dp[i - 1][0], dp[i - 1][1]) + costs[i][2];
        }
        return Math.min(dp[len - 1][0], Math.min(dp[len - 1][1], dp[len - 1][2]));
    }
}

剑指 Offer II 092. 翻转字符

class Solution {
    public int minFlipsMonoIncr(String s) {
        //dp[i][2] 使i位翻转过后变成递增子串的最小翻转次数
        int len = s.length();
        int[][] dp = new int[len][2];
        if (s.charAt(0) == '0') {
            dp[0][1] = 1;
        } else {
            dp[0][0] = 1;
        }
        for (int i = 1; i < len; i++) {
            if (s.charAt(i) == '0') {
                // 无需翻转,并且不能由dp[i-1][1]得来
                dp[i][0] = dp[i - 1][0];
                // 必须翻转
                dp[i][1] = Math.min(dp[i - 1][0], dp[i - 1][1]) + 1;
            } else {
                // 无需翻转
                dp[i][1] = Math.min(dp[i - 1][0], dp[i - 1][1]);
                // 必须翻转,且只能由dp[i - 1][0]得来
                dp[i][0] = dp[i - 1][0] + 1;
            }
        }
        return Math.min(dp[len - 1][0], dp[len - 1][1]);
    }
}

剑指 Offer II 093. 最长斐波那契数列

class Solution {
    public int lenLongestFibSubseq(int[] arr) {
        // 这个问题的关键是要记录三个元素,k,j,i,只用两个元素是无法确定斐波那契数列的
        int n = arr.length;
        int[][] dp = new int[n][n];

        // 先把元素暂存起来,方便快速查找
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < n; i++) {
            map.put(arr[i], i);
        }

        int max = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i - 1; j >= 0 && arr[j] * 2 > arr[i]; j--) {
                int k = map.getOrDefault(arr[i] - arr[j], -1);
                if (k >= 0) {
                    // 要使得一个斐波那契数列成立,必须最少3个元素
                    dp[j][i] = Math.max(dp[k][j] + 1, 3);
                }
                max = Math.max(max, dp[j][i]);
            }
        }
        return max;
    }
}

剑指 Offer II 094. 最少回文分割

剑指 Offer II 095. 最长公共子序列

剑指 Offer II 096. 字符串交织

剑指 Offer II 097. 子序列的数目

剑指 Offer II 098. 路径的数目

class Solution {
    public int uniquePaths(int m, int n) {
        // 只能向下或向右移动
        // dp[i][j] = dp[i-1][j] + dp[i][j-1]

        int[][] dp = new int[m][n];
        // 给第一列赋值
        for (int i = 0; i < m; i++) {
            dp[i][0] = 1;
        }

        // 给第一行赋值
        for (int j = 0; j < n; j++) {
            dp[0][j] = 1;
        }

        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }

        return dp[m - 1][n - 1];
    }
}

剑指 Offer II 099. 最小路径之和

class Solution {
    public int minPathSum(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;

        int[][] dp = new int[m][n];
        // 给第一列赋值
        dp[0][0] = grid[0][0];
        for (int i = 1; i < m; i++) {
            dp[i][0] = dp[i - 1][0] + grid[i][0];
        }
        for (int j = 1; j < n; j++) {
            dp[0][j] = dp[0][j - 1] + grid[0][j];
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
            }
        }
        return dp[m - 1][n - 1];
    }
}

剑指 Offer II 100. 三角形中最小路径之和

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        int m = triangle.size();
        if(m == 1){
            return triangle.get(0).get(0);
        }

        int[][] dp = new int[m][];
        dp[0] = new int[1];
        dp[0][0] = triangle.get(0).get(0);

        int min = Integer.MAX_VALUE;
        for (int i = 1; i < m; i++) {
            dp[i] = new int[i + 1];
            for (int j = 0; j <= i; j++) {
                if (j == 0) {
                    dp[i][j] = dp[i - 1][j];
                } else if (j == i) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    dp[i][j] = Math.min(dp[i - 1][j], dp[i - 1][j - 1]);
                }
                dp[i][j] += triangle.get(i).get(j);

                if (i == m - 1) {
                    min = Math.min(dp[i][j], min);
                }
            }
        }
        return min;
    }
}

剑指 Offer II 101. 分割等和子集 剑指 Offer II 102. 加减的目标值


//动态规划的解法
class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        int diff = sum - target;
        // 说明无法得到target
        if (diff < 0 || diff % 2 != 0) {
            return 0;
        }
        // 问题转化为:在nums中选取N个元素使其总和为w,共有多少方案
        int m = nums.length;
        int W = diff / 2;
        int[][] dp = new int[m + 1][W + 1];
        dp[0][0] = 1;
        for (int i = 1; i <= m; i++) {
            for (int w = 0; w <= W; w++) {
                dp[i][w] = dp[i - 1][w];
                if (w >= nums[i - 1]) {
                    // 达到这个容量的方案数
                    dp[i][w] += dp[i - 1][w - nums[i - 1]];
                }
            }
        }
        return dp[m][W];
    }
}
//leetcode submit region end(Prohibit modification and deletion)
// 回溯法
class Solution {
    int total = 0;

    public int findTargetSumWays(int[] nums, int target) {
        backtrace(nums, target, 0, 0);
        return total;
    }

    public void backtrace(int[] nums, int target, int idx, int sum) {
        // 到最后了,需要退出
        if (idx == nums.length) {
            if (sum == target) {
                total++;
            }
            return;
        }
        backtrace(nums, target, idx + 1, sum + nums[idx]);
        backtrace(nums, target, idx + 1, sum - nums[idx]);
    }
}

剑指 Offer II 103. 最少的硬币数目

public class Solution {
    public int coinChange(int[] coins, int amount) {
        int max = amount + 1;
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, max);
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {
            for (int j = 0; j < coins.length; j++) {
                if (coins[j] <= i) {
                    dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
}

剑指 Offer II 104. 排列的数目