【LeetCode】剑指Offer

剑指Offer

文章目录

    • 剑指Offer
        • ⭐[剑指 Offer 03. 数组中重复的数字](https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/)
        • [剑指 Offer 04. 二维数组中的查找](https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/)
        • [剑指 Offer 05. 替换空格](https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/)
        • [剑指 Offer 06. 从尾到头打印链表](https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/)
        • [剑指 Offer 07. 重建二叉树](https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/)
        • ⭐[剑指 Offer 09. 用两个栈实现队列](https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/)
        • ⭐⭐[剑指 Offer 10- I. 斐波那契数列](https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/)
        • [剑指 Offer 10- II. 青蛙跳台阶问题](https://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/)
        • ⭐⭐[剑指 Offer 11. 旋转数组的最小数字](https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/)
        • ⭐[剑指 Offer 12. 矩阵中的路径](https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/)
        • ⭐⭐[剑指 Offer 13. 机器人的运动范围](https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/)
        • ⭐⭐[剑指 Offer 14- I. 剪绳子](https://leetcode-cn.com/problems/jian-sheng-zi-lcof/)
        • ⭐[剑指 Offer 14- II. 剪绳子 II](https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/)
        • [剑指 Offer 15. 二进制中1的个数](https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/)
        • ⭐⭐[剑指 Offer 16. 数值的整数次方](https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/)
        • ⭐⭐[剑指 Offer 17. 打印从1到最大的n位数](https://leetcode-cn.com/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/)
        • [剑指 Offer 18. 删除链表的节点](https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof/)
        • [剑指 Offer 20. 表示数值的字符串](https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/)
        • [剑指 Offer 21. 调整数组顺序使奇数位于偶数前面](https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/)
        • [剑指 Offer 22. 链表中倒数第k个节点](https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/)
        • ⭐[剑指 Offer 24. 反转链表](https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/)
        • [剑指 Offer 25. 合并两个排序的链表](https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/)
        • ⭐⭐⭐[剑指 Offer 26. 树的子结构](https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/)
        • ⭐⭐[剑指 Offer 27. 二叉树的镜像](https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof/)
        • [剑指 Offer 28. 对称的二叉树](https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/)
        • ⭐[剑指 Offer 29. 顺时针打印矩阵](https://leetcode-cn.com/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/)
        • [剑指 Offer 30. 包含min函数的栈](https://leetcode-cn.com/problems/bao-han-minhan-shu-de-zhan-lcof/)
        • ⭐[剑指 Offer 31. 栈的压入、弹出序列](https://leetcode-cn.com/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/)
        • [面试题32 - I. 从上到下打印二叉树](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/)
        • [剑指 Offer 32 - II. 从上到下打印二叉树 II](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/)
        • [剑指 Offer 32 - III. 从上到下打印二叉树 III](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/)
        • ⭐⭐⭐[剑指 Offer 33. 二叉搜索树的后序遍历序列](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/)
        • ⭐⭐[剑指 Offer 34. 二叉树中和为某一值的路径](https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/)
        • [剑指 Offer 35. 复杂链表的复制](https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/)
        • [剑指 Offer 37. 序列化二叉树](https://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof/)
        • ⭐[剑指 Offer 38. 字符串的排列](https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/)
        • [剑指 Offer 39. 数组中出现次数超过一半的数字](https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/)
        • ⭐[剑指 Offer 40. 最小的k个数](https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/)
        • [剑指 Offer 42. 连续子数组的最大和](https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/)
        • [剑指 Offer 46. 把数字翻译成字符串](https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/)
        • [剑指 Offer 47. 礼物的最大价值](https://leetcode-cn.com/problems/li-wu-de-zui-da-jie-zhi-lcof/)
        • ⭐⭐⭐[剑指 Offer 48. 最长不含重复字符的子字符串](https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/)
        • ⭐⭐[剑指 Offer 49. 丑数](https://leetcode-cn.com/problems/chou-shu-lcof/)
        • [剑指 Offer 50. 第一个只出现一次的字符](https://leetcode-cn.com/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/)
        • [剑指 Offer 52. 两个链表的第一个公共节点](https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/)
        • ⭐[剑指 Offer 53 - I. 在排序数组中查找数字 I](https://leetcode-cn.com/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/)
        • ⭐[剑指 Offer 53 - II. 0~n-1中缺失的数字](https://leetcode-cn.com/problems/que-shi-de-shu-zi-lcof/)
        • [剑指 Offer 54. 二叉搜索树的第k大节点](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/)
        • [剑指 Offer 55 - I. 二叉树的深度](https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof/)
        • ⭐⭐[剑指 Offer 55 - II. 平衡二叉树](https://leetcode-cn.com/problems/ping-heng-er-cha-shu-lcof/)
        • ⭐⭐[剑指 Offer 56 - I. 数组中数字出现的次数](https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/)
        • ⭐⭐[剑指 Offer 56 - II. 数组中数字出现的次数 II](https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/)
        • [剑指 Offer 57. 和为s的两个数字](https://leetcode-cn.com/problems/he-wei-sde-liang-ge-shu-zi-lcof/)
        • ⭐⭐⭐[剑指 Offer 57 - II. 和为s的连续正数序列](https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/)
        • [剑指 Offer 58 - I. 翻转单词顺序](https://leetcode-cn.com/problems/fan-zhuan-dan-ci-shun-xu-lcof/)
        • [剑指 Offer 58 - II. 左旋转字符串](https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/)
        • [剑指 Offer 59 - II. 队列的最大值](https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/)
        • ⭐[剑指 Offer 60. n个骰子的点数](https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof/)
        • ⭐⭐[剑指 Offer 61. 扑克牌中的顺子](https://leetcode-cn.com/problems/bu-ke-pai-zhong-de-shun-zi-lcof/)
        • ⭐⭐[剑指 Offer 62. 圆圈中最后剩下的数字](https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/)
        • [剑指 Offer 63. 股票的最大利润](https://leetcode-cn.com/problems/gu-piao-de-zui-da-li-run-lcof/)
        • [剑指 Offer 64. 求1+2+…+n](https://leetcode-cn.com/problems/qiu-12n-lcof/)
        • [剑指 Offer 65. 不用加减乘除做加法](https://leetcode-cn.com/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/)
        • [剑指 Offer 66. 构建乘积数组](https://leetcode-cn.com/problems/gou-jian-cheng-ji-shu-zu-lcof/)
        • [剑指 Offer 68 - I. 二叉搜索树的最近公共祖先](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/)
        • [剑指 Offer 68 - II. 二叉树的最近公共祖先](https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/)

⭐剑指 Offer 03. 数组中重复的数字

找出数组中重复的数字。在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

// 如果用set或者map的话
// Time:O(n)
// Space:O(n)

// 值:	0 2 1 3 3
// 下标: 0 1 2 3 4
// 对于每个数字nums[i]如果这个数字 nums[i] 不等于其 下标 i的话,则将这个数字放到 nums[i]这个下标上,如果交换的时候发现两者相同则直接返回。
class Solution {
    public int findRepeatNumber(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            while (nums[i] != i) {
                if (nums[i] == nums[nums[i]]) {
                    return nums[i];
                }
                swap(nums, i, nums[i]);
            }
        }
        return -1;
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}
// Time:O(n)
// Space:O(1)

剑指 Offer 04. 二维数组中的查找

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if (matrix == null || matrix.length == 0) return false;
        int m = matrix.length, n = matrix[0].length;
        int row = 0, col = n - 1;
        while (row < m && col >= 0) {
            if (matrix[row][col] == target) {
                return true;
            } else if (matrix[row][col] < target) {
                row++;
            } else {
                col--;
            }
        }
        return false;
    }
}
// Time:O(m+n)
// Space:O(1)

剑指 Offer 05. 替换空格

难度简单211

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

class Solution {
    public String replaceSpace(String s) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == ' ') {
                sb.append("%20");
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }
}
// Time:O(n)
// Space:O(n)

剑指 Offer 06. 从尾到头打印链表

难度简单234

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

// 1. 反转链表,然后正常放入值
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public int[] reversePrint(ListNode head) {
        ListNode dummy = new ListNode(0);
        ListNode p = head;
        int len = 0;
        while (p != null) {
            len++;
            ListNode temp = p.next;
            p.next = dummy.next;
            dummy.next = p;
            p = temp;
        }
        int[] res = new int[len];
        int i = 0;
        while (dummy.next != null) {
            res[i++] = dummy.next.val;
            dummy = dummy.next;
        }
        return res;
    }
}

// 2. 第一个值放到最后一个位置,第二个值放到倒数第二个位置,以此类推
class Solution {
    public int[] reversePrint(ListNode head) {
        int len = 0;
        ListNode p = head;
        while (p != null) {
            len++;
            p = p.next;
        }
        int[] res = new int[len];
        p = head;
        for (int i = len - 1; i >= 0; i--) {
            res[i] = p.val;
            p = p.next;
        }
        return res;
    }
}
// Time:O(n)
// Space:O(n)

剑指 Offer 07. 重建二叉树

难度中等663

输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。

假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return helper(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
    }

    private TreeNode helper (int[] preorder, int pre_left, int pre_right, int[] inorder, int in_left, int in_right) {
        if (pre_left > pre_right || in_left > in_right) return null;
        TreeNode root = new TreeNode(preorder[pre_left]);
        int ind = 0;
        for (int i = in_left; i <= in_right; i++) {
            if (inorder[i] == preorder[pre_left]) {
                ind = i;
                break;
            }
        }
        int left_size = ind - 1 - in_left + 1, right_size = in_right - (ind + 1) + 1;
        root.left = helper(preorder, pre_left + 1, pre_left + left_size, inorder, in_left, in_left + left_size - 1);
        root.right = helper(preorder, pre_right - right_size + 1, pre_right, inorder, in_right - right_size + 1, in_right);
        return root;
    }
}
// Time: O(n+nlgn)->O(nlgn)
// Space:O(lgn)

⭐剑指 Offer 09. 用两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTaildeleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

class CQueue {
    private Deque<Integer> stack1;
    private Deque<Integer> stack2;


    public CQueue() {
        stack1 = new LinkedList<>();
        stack2 = new LinkedList<>();
    }
    
    public void appendTail(int value) {
        stack1.push(value);
    }
    
    public int deleteHead() {
        if (stack2.isEmpty()) {
            while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());
            }
        }
        if (stack2.isEmpty()) {
            return -1;
        } else {
            return stack2.pop();
        }
    }
}

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue obj = new CQueue();
 * obj.appendTail(value);
 * int param_2 = obj.deleteHead();
 */

⭐⭐剑指 Offer 10- I. 斐波那契数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:

F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.

斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

class Solution {
    public int fib(int n) {
        if (n == 0 || n == 1) return n;
        int a = 0, b = 1;
        for (int i = 2; i <= n; i++) {
            int c = (a + b) % 1000000007;
            a = b;
            b = c;
        }
        return b;
    }
}

矩阵快速幂

class Solution {
    static final int MOD = 1000000007;

    public int fib(int n) {
        if (n < 2) {
            return n;
        }
        int[][] q = {{1, 1}, {1, 0}};
        int[][] res = pow(q, n - 1);
        return res[0][0];
    }

    public int[][] pow(int[][] a, int n) {
        int[][] ret = {{1, 0}, {0, 1}};
        while (n > 0) {
            if ((n & 1) == 1) {
                ret = multiply(ret, a);
            }
            n >>= 1;	// n /= 2
            a = multiply(a, a);
        }
        return ret;
    }

    public int[][] multiply(int[][] a, int[][] b) {
        int[][] c = new int[2][2];
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                c[i][j] = (int) (((long) a[i][0] * b[0][j] + (long) a[i][1] * b[1][j]) % MOD);
            }
        }
        return c;
    }
}

剑指 Offer 10- II. 青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

class Solution {
    public int numWays(int n) {
        if (n == 0 || n == 1) return 1;
        int[] dp = new int[n + 1];
        int a = 1, b = 1, c = 2;
        for (int i = 2; i <= n; i++) {
            c = (a + b) % 1000000007;
            a = b;
            b = c;
        }
        return c;
    }
}

⭐⭐剑指 Offer 11. 旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2][1,2,3,4,5] 的一次旋转,该数组的最小值为1。

// 注意不能用mid与left进行比较进行区间缩小
class Solution {
    public int minArray(int[] numbers) {
        int len = numbers.length;
        int left = 0, right = len - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (numbers[mid] > numbers[right]) {
                // [mid + 1, right]
                left = mid + 1;
            } else if (numbers[mid] < numbers[right]) {
                // [left, mid]
                right = mid;
            } else {
                // 若想等,只能将right缩小,无法确定具体区间
	// 示例一 [1, 0, 1, 1, 1][1,0,1,1,1] :旋转点 x = 1x=1 ,因此 m=2,m=2 在 右排序数组 中。
	// 示例二 [1, 1, 1, 0, 1][1,1,1,0,1] :旋转点 x = 3x=3 ,因此 m=2,m=2 在 左排序数组 中。
                right--;
            }
        }
        return numbers[left];
    }
}
// Time:O(lgn)
// Space:O(1)

⭐剑指 Offer 12. 矩阵中的路径

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

class Solution {
    public boolean exist(char[][] board, String word) {
        if (board == null || board.length == 0 || board[0].length == 0) return false;
        int m = board.length, n = board[0].length;
        boolean[][] visited = new boolean[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (dfs(visited, board, i, j, word, 0)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean dfs(boolean[][] visited, char[][] board, int i, int j, String word, int index) {
        if ((i < 0 || i >= board.length) || (j < 0 || j >= board[0].length) || visited[i][j] || board[i][j] != word.charAt(index))
            return false;
        if (index == word.length() - 1) {
            return true;
        }
        visited[i][j] = true;
        boolean find = dfs(visited, board, i + 1, j, word, index + 1)
                || dfs(visited, board, i - 1, j, word, index + 1)
                || dfs(visited, board, i, j + 1, word, index + 1)
                || dfs(visited, board, i, j - 1, word, index + 1);
        visited[i][j] = false;
        return find;
    }
}

⭐⭐剑指 Offer 13. 机器人的运动范围

难度中等436

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

class Solution {
    public int movingCount(int m, int n, int k) {
        if (m == 0 || n == 0) return 0;
        boolean[][] vis = new boolean[m][n];
        return dfs(vis, 0, 0, k);
    }

    private int dfs(boolean[][] vis, int i, int j, int k) {
        if ((i < 0 || i >= vis.length) || (j < 0 || j >= vis[0].length) || vis[i][j] || (i / 10 + i % 10 + j / 10 + j % 10) > k) return 0;
        vis[i][j] = true;
        return 1 + dfs(vis, i + 1, j, k) + dfs(vis, i, j + 1, k);
    }
}

⭐⭐剑指 Offer 14- I. 剪绳子

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0]*k[1]*...*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

动态规划

class Solution {
    public int cuttingRope(int n) {
        // dp[i] 表示长度为i至少剪一刀的最大乘积
        int[] dp = new int[n + 1];
        dp[2] = 1;
        for (int i = 3; i <= n; i++) {
            for (int j = 1; j <= i - 1; j++) {
                dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
            }
        }
        return dp[n];
    }
}

贪心

算法流程:
n ≤ 3 n \leq 3 n3 时,按照规则应不切分,但由于题目要求必须剪成 m>1 段,因此必须剪出一段长度为 1 的绳子,即返回 n - 1 。
n > 3 n n>3n n>3n 时,求 n 除以 3 的 整数部分 a 和 余数部分bb (即 n = 3a+b ),并分为以下三种情况:
当 b = 0 时,直接返回 3 a 3^a 3a
当 b = 1 时,要将一个 1 + 31+3 转换为 2+22+2,因此返回 3 a − 1 × 4 3^{a-1} \times 4 3a1×4
当 b = 2 时,返回 3 a × 2 3^a \times 2 3a×2

class Solution {
    public int cuttingRope(int n) {
        if (n <= 3) return n - 1;
        int res = 1;
        while (n > 4) {
            res = res * 3;
            n -= 3;
        }
        res *= n;
        return res;
    }
}

⭐剑指 Offer 14- II. 剪绳子 II

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m - 1] 。请问 k[0]*k[1]*...*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

// 1. dp
class Solution {
    public int cuttingRope(int n) {
        // dp[i] 表示总长度为 i 能够得到的最大乘积
        // 最后一段可以长度 j 为 1 , 2, ..., i - 2
        // 对于前面长度为 i - j的话可以剪,也可以不剪
        // 那么结果就是 max(dp[i], max(j * dp[i - j], j * (i - j)))
        // 也就是 1 * dp[i - 1], 2 * d[i - 2], ... , (i - 2) * dp[2]
        int[] dp = new int[n + 1];
        dp[2] = 1;
        for (int i = 3; i <= n; i++) {
            for (int j = 1; j <= i - 2; j++) {
                dp[i] = Math.max(dp[i], Math.max(j * dp[i - j], j * (i - j)));
            }
        }
        return dp[n];
    }
}
class Solution {
    public int cuttingRope(int n) {
        if (n <= 3) return n - 1;
        long res = 1;
        while(n > 4){
            res = res * 3 % 1000000007;
            n -= 3;
        }
        return (int)(res * n % 1000000007);
    }
}

剑指 Offer 15. 二进制中1的个数

难度简单215

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为 汉明重量).)。

class Solution {
    // 1. 检查第i位时,将n与2^i次方进行与运算,如果与完之后等于2^i,说明该位置是1,如果与完之后等于0,说明该位置是0
    // 1 0 0 1 0
    // 0 0 0 1 0
    public int hammingWeight(int n) {
        int cnt = 0;
        for (int i = 0; i < 32; i++) {
            if ((n & (1 << i)) != 0) cnt++;
        }
        return cnt;
    }

    // 2. 每次检查最低位是不是1,是则计数,每次判断完最低位之后都要右移一位
    public int hammingWeight_1(int n) {
        int cnt = 0;
        for (int i = 0; i < 32; i++) {
            if ((n & 1) == 1) cnt++;
            n = n >> 1;
        }
        return cnt;
    }

    // 3. 通过n & (n-1)来将n最低位的1置为0,直到n等于0为止
    public int hammingWeight_2(int n) {
        int cnt = 0;
        while (n != 0) {
            n = n & (n - 1);
            cnt++;
        }
        return cnt;
    }
}

⭐⭐剑指 Offer 16. 数值的整数次方

难度中等254

实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,x^n)。不得使用库函数,同时不需要考虑大数问题。

快速幂 + 递归

class Solution {
    public double myPow(double x, int n) {
        if(n == 0) return 1;
        if(n == 1) return x;
        if(n == -1) return 1 / x;
        // n / 2 + n / 2 + n % 2
        double half = myPow(x, n / 2);
        double mod = myPow(x, n % 2);
        return half * half * mod;
    }
}
// Time:O(lgn)
// Space:O(lgn)

快速幂 + 迭代

class Solution {
    public double myPow(double x, int n) {
        if (n == 0) return 1.0;
        long b = n;	// 负数最小值的绝对值超出int范围,所以用更大容量防止溢出
        if (b < 0) {
            x = 1 / x;
            b = -b;
        }
        double res = 1.0;
        while (b > 0) {
            if ((b & 1) == 1) res *= x;
            x *= x;
            b /= 2;
        }
        return res;
    }
}
// Time:O(lgn)
// Space:O(1)

⭐⭐剑指 Offer 17. 打印从1到最大的n位数

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

class Solution {
    public int[] printNumbers(int n) {
        // n = 1, res = 1-9
        // n = 2, res = 1-99
        // n    , res = 1 - 10 ^ n - 1
        int len = (int) Math.pow(10, n) - 1;
        int[] res = new int[len];
        for (int i = 0; i < len; i++) {
            res[i] = i + 1;
        }
        return res;
    }
}

如果是大数,转为n位0-9的全排列问题

class Solution {
    char[] nums = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
    int[] res;
    int idx;
    StringBuilder path = new StringBuilder();

    public int[] printNumbers(int n) {
        int len = (int) Math.pow(10, n) - 1;
        res = new int[len];
        idx = 0;
        dfs(0, n);
        return res;
    }

    private void dfs(int cur, int n) {
        if (cur == n) {
            int curNum = Integer.parseInt(path.toString());
            if (curNum != 0) {
                res[idx++] = curNum;
            }
            return;
        }
        for (char num : nums) {
            path.append(num);
            dfs(cur + 1, n);
            path.deleteCharAt(path.length() - 1);
        }
    }
}

剑指 Offer 18. 删除链表的节点

难度简单192

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
// 迭代
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode pre = dummy;
        while (pre.next.val != val) {
            pre = pre.next;
        }
        // pre delete_node pre.next.next
        pre.next = pre.next.next;
        return dummy.next;
    }
}

// 递归
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        if (head == null) return head;
        if (head.val == val) {
            ListNode newHead = head.next;
            head.next = null;
            return newHead;
        }
        head.next = deleteNode(head.next, val);
        return head;
    }
}

剑指 Offer 20. 表示数值的字符串

难度中等286

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。

数值(按顺序)可以分成以下几个部分:

  1. 若干空格
  2. 一个 小数 或者 整数
  3. (可选)一个 'e''E' ,后面跟着一个 整数
  4. 若干空格

小数(按顺序)可以分成以下几个部分:

  1. (可选)一个符号字符('+''-'
  2. 下述格式之一:
    1. 至少一位数字,后面跟着一个点 '.'
    2. 至少一位数字,后面跟着一个点 '.' ,后面再跟着至少一位数字
    3. 一个点 '.' ,后面跟着至少一位数字

整数(按顺序)可以分成以下几个部分:

  1. (可选)一个符号字符('+''-'
  2. 至少一位数字

部分数值列举如下:

  • ["+100", "5e2", "-123", "3.1416", "-1E-16", "0123"]

部分非数值列举如下:

  • ["12e", "1a3.14", "1.2.3", "+-5", "12e+5.4"]
// ‘.’出现正确情况:只出现一次,且在e的前面
// ‘e’出现正确情况:只出现一次,且出现前有数字
// ‘+’‘-’出现正确情况:只能在开头和e后一位
class Solution {
    public boolean isNumber(String s) {
        if (s == null || s.length() == 0) return false;
        //去掉首位空格
        s = s.trim();
        boolean numFlag = false;
        boolean dotFlag = false;
        boolean eFlag = false;
        for (int i = 0; i < s.length(); i++) {
            //判定为数字,则标记numFlag
            if (s.charAt(i) >= '0' && s.charAt(i) <= '9') {
                numFlag = true;
                //判定为.  需要没出现过.并且没出现过e
            } else if (s.charAt(i) == '.' && !dotFlag && !eFlag) {
                dotFlag = true;
                //判定为e,需要没出现过e,并且出过数字了
            } else if ((s.charAt(i) == 'e' || s.charAt(i) == 'E') && !eFlag && numFlag) {
                eFlag = true;
                numFlag = false;//为了避免123e这种请求,出现e之后就标志为false
                //判定为+-符号,只能出现在第一位或者紧接e后面
            } else if ((s.charAt(i) == '+' || s.charAt(i) == '-') && (i == 0 || s.charAt(i - 1) == 'e' || s.charAt(i - 1) == 'E')) {

                //其他情况,都是非法的
            } else {
                return false;
            }
        }
        return numFlag;
    }
}

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

难度简单194

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分。

class Solution {
    public int[] exchange(int[] nums) {
        int len = nums.length;
        int left = 0, right = len - 1;
        while (left < right) {
            while ((nums[left] & 1) == 1 && left < right) left++;
            while ((nums[right] & 1) == 0 && left < right) right--;
            if (left < right) {
                swap(nums, left, right);
            }
        }
        return nums;
    }

    private void swap(int[] nums, int left, int right) {
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
    }
}

剑指 Offer 22. 链表中倒数第k个节点

难度简单323

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。

例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode fast = new ListNode(0);
        fast.next = head;
        ListNode slow = fast;
        while (k != 0) {
            fast = fast.next;
            k--;
        }
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow.next;
    }
}

⭐剑指 Offer 24. 反转链表

难度简单373

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

// 1. 迭代,头插法
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode dummy = new ListNode(0);
        ListNode p = head;
        while (p != null) {
            ListNode temp = p.next;
            p.next = dummy.next;
            dummy.next = p;
            p = temp;
        }
        return dummy.next;
    }
}

// 2. 递归
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode newHead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}

剑指 Offer 25. 合并两个排序的链表

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);
        ListNode pre = dummy;
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                pre.next = l1;
                l1 = l1.next;
            } else {
                pre.next = l2;
                l2 = l2.next;
            }
            pre = pre.next;
        }
        if (l1 != null) {
            pre.next = l1;
        }
        if (l2 != null) {
            pre.next = l2;
        }
        return dummy.next;
    }
}

⭐⭐⭐剑指 Offer 26. 树的子结构

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)

B是A的子结构, 即 A中有出现和B相同的结构和节点值。

class Solution {
    // 1. 先序遍历树 A 中的每个节点,判断 B 是不是该子树下的子结构
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        if (A == null || B == null) return false;
        return helper(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B);
    }

    // 2. 判断以 A 为根节点的子树 是否包含树 B
    public boolean helper(TreeNode A, TreeNode B) {
        // B 扫描完了
        if (B == null) return true;
        // A 扫描完了,但是 B 还没扫描完
        if (A == null) return false;
        if (A.val != B.val) return false;
        return helper(A.left, B.left) && helper(A.right, B.right);
    }
}

⭐⭐剑指 Offer 27. 二叉树的镜像

请完成一个函数,输入一个二叉树,该函数输出它的镜像。

class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if (root == null) return null;
        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;
        mirrorTree(root.left);
        mirrorTree(root.right);
        return root;
    }
}

剑指 Offer 28. 对称的二叉树

难度简单283

请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isSymmetric(TreeNode root) {
        if (root == null) return true;
        return helper(root.left, root.right);
    }

    private boolean helper(TreeNode A, TreeNode B) {
        if (A == null && B == null) return true;
        if (A == null || B == null) return false;
        return A.val == B.val && helper(A.left, B.right) && helper(A.right, B.left);
    }
}

⭐剑指 Offer 29. 顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if (matrix == null || matrix.length == 0) return new int[0];
        int m = matrix.length, n = matrix[0].length;
        int len = m * n;
        int[] res = new int[len];
        int[][] directions = new int[][]{{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
        boolean[][] visited = new boolean[m][n];
        int i = 0, j = 0, dir = 0;
        // 碰到边界 或 碰到访问过的 转向
        for (int ind = 0; ind < len ;ind++) {
            res[ind] = matrix[i][j];
            visited[i][j] = true;
            int next_i = i + directions[dir][0], next_j = j + directions[dir][1];
            if (next_i < 0 || next_i >= m
                    || next_j < 0 ||next_j >= n
                    || visited[next_i][next_j]) {
                dir = (dir + 1) % 4;
            }
            i += directions[dir][0];
            j += directions[dir][1];
        }
        return res;
    }
}
// Time:O(MN)
// Space:O(MN)	-> visited数组

// 2. ⭐⭐⭐
class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if (matrix == null || matrix.length == 0) return new int[0];
        int m = matrix.length, n = matrix[0].length;
        int[] res = new int[m * n];
        int idx = 0;
        int l = 0, r = n - 1, t = 0, b = m - 1;
        while (true) {
            // 上边界,从左到右
            for (int j = l; j <= r; j++) res[idx++] = matrix[t][j];
            if (++t > b) break;
            // 右边界,从上到下
            for (int i = t; i <= b; i++) res[idx++] = matrix[i][r];
            if (--r < l) break;
            // 下边界,从右到左
            for (int j = r; j >= l; j--) res[idx++] = matrix[b][j];
            if (--b < t) break;
            // 左边界,从下到上
            for (int i = b; i >= t; i--) res[idx++] = matrix[i][l];
            if (++l > r) break;
        }
        return res;
    }
}
// Time:O(MN)
// Space:O(1)

剑指 Offer 30. 包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。

class MinStack {

    Node dummy;

    /** initialize your data structure here. */
    public MinStack() {
        dummy = new Node(0, 0);
    }

    public void push(int x) {
        if (dummy.next == null) {
            dummy.next = new Node(x, x);
        } else {
            Node node = new Node(x, Math.min(x, dummy.next.minVal));
            node.next = dummy.next;
            dummy.next = node;
        }
    }

    public void pop() {
        dummy.next = dummy.next.next;
    }

    public int top() {
        return dummy.next.val;
    }

    public int min() {
        return dummy.next.minVal;
    }

    private class Node {
        int val;
        int minVal;
        Node next;

        private Node(int val, int minVal) {
            this(val, minVal, null);
        }

        private Node(int val, int minVal, Node next) {
            this.val = val;
            this.minVal = minVal;
            this.next = next;
        }
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.min();
 */

⭐剑指 Offer 31. 栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。

class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
        Deque<Integer> stack = new LinkedList<>();
        int idx = 0, len = popped.length;
        for (int val : pushed) {
            stack.push(val);
            // // 出栈操作: 每次入栈后,循环判断 “栈顶元素 == 弹出序列的当前元素” 是否成立,将符合弹出序列顺序的栈顶元素全部弹出。
            while (idx < len && !stack.isEmpty() && stack.peek() == popped[idx]) {
                stack.pop();
                idx++;
            }
        }
        return idx == len;
    }
}

面试题32 - I. 从上到下打印二叉树

从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] levelOrder(TreeNode root) {
        if (root == null) return new int[0];
        List<Integer> res = new ArrayList<>();
        Deque<TreeNode> queue = new LinkedList<>();
        TreeNode p = root;
        queue.offer(p);
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                p = queue.poll();
                res.add(p.val);
                if (p.left != null) queue.offer(p.left);
                if (p.right != null) queue.offer(p.right);
            }
        }
        int n = res.size();
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            ans[i] = res.get(i);
        }
        return ans;
    }
}

剑指 Offer 32 - II. 从上到下打印二叉树 II

难度简单176

从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList();
        if (root == null) return res;
        Deque<TreeNode> queue = new LinkedList<>();
        TreeNode p = root;
        queue.offer(p);
        while (!queue.isEmpty()) {
            int size = queue.size();
            List<Integer> level = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                p = queue.poll();
                level.add(p.val);
                if (p.left != null) queue.offer(p.left);
                if (p.right != null) queue.offer(p.right);
            }
            res.add(level);
        }
        return res;
    }
}

剑指 Offer 32 - III. 从上到下打印二叉树 III

难度中等178

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList();
        if (root == null) return res;
        Deque<TreeNode> queue = new LinkedList<>();
        int depth = 0;
        TreeNode p = root;
        queue.offer(p);
        while (!queue.isEmpty()) {
            int size = queue.size();
            depth++;
            List<Integer> level = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                p = queue.poll();
                level.add(p.val);
                if (p.left != null) queue.offer(p.left);
                if (p.right != null) queue.offer(p.right);
            }
            if ((depth & 1) == 0) Collections.reverse(level);
            res.add(level);
        }
        return res;
    }
}

⭐⭐⭐剑指 Offer 33. 二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

class Solution {
    public boolean verifyPostorder(int[] postorder) {
        return helper(postorder, 0, postorder.length - 1);
    }

    private boolean helper(int[] postorder, int left, int right) {
        if (left >= right) return true;	// 结点数小于等于1肯定是对的
        int i = left;
        while (postorder[i] < postorder[right]) {
            i++;
        }
        int m = i;
        // [left m - 1] [m, right - 1] right
        while (postorder[i] > postorder[right]) {
            i++;
        }
        return i == right && helper(postorder, left, m - 1) && helper(postorder, m, right - 1);
    }
}

⭐⭐剑指 Offer 34. 二叉树中和为某一值的路径

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

叶子节点 是指没有子节点的节点。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();

    public List<List<Integer>> pathSum(TreeNode root, int target) {
        if (root == null) return res;
        dfs(root, target);
        return res;
    }

    private void dfs(TreeNode root, int target) {
        if (root == null) return;
        path.add(root.val);
        target -= root.val;
        if (root.left == null && root.right == null && target == 0) {
            res.add(new ArrayList<>(path));
        }
        dfs(root.left, target);
        dfs(root.right, target);
        path.remove(path.size() - 1);
        target += root.val;
    }
}

剑指 Offer 35. 复杂链表的复制

难度中等416

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
// 1. 哈希表
// Time:O(n)
// Space:O(n)
class Solution {
    public Node copyRandomList(Node head) {
        Node p = head;
        Map<Node, Node> map = new HashMap<>();
        while (p != null) {
            Node node = new Node(p.val);
            map.put(p, node);
            p = p.next;
        }
        p = head;
        while (p != null) {
            map.get(p).next = map.get(p.next);
            map.get(p).random = map.get(p.random);
            p = p.next;
        }
        return map.get(head);
    }
}

// 2. 拼接 + 拆分
// Time:O(n)
// Space:O(1)
class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) return null;
        Node p = head;
        // 建立 next
        while (p != null) {
            Node temp = p.next;
            Node p_copy = new Node(p.val);
            p_copy.next = p.next;
            p.next = p_copy;
            p = temp;
        }
        // 建立 random
        p = head;
        while (p != null) {
            if (p.random != null) p.next.random = p.random.next;
            p = p.next.next;
        }
        Node res = head.next;
        Node pre = head;
        p = pre.next;
        // 拆分
        while (p.next != null) {
            pre.next = pre.next.next;
            p.next = p.next.next;
            pre = pre.next;
            p = p.next;
        }
        pre.next = null;
        return res;
    }
}

剑指 Offer 37. 序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树。

你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

Solution1. dfs

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {
    public static final String NULL_VAL = "#";

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if (root == null) return NULL_VAL;
        return root.val + " " + serialize(root.left) + " " + serialize(root.right);
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        Deque<String> queue = new LinkedList<>(Arrays.asList(data.split(" ")));
        return dfs(queue);
    }

    private TreeNode dfs(Deque<String> queue) {
        String str = queue.poll();
        if (NULL_VAL.equals(str)) return null;
        TreeNode root = new TreeNode(Integer.parseInt(str));
        root.left = dfs(queue);
        root.right = dfs(queue);
        return root;
    }
}

// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));

Solution2. bfs

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {
    public static final String NULL_VAL = "#";

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if (root == null) return NULL_VAL;
        StringBuilder sb = new StringBuilder();
        Deque<TreeNode> queue = new LinkedList<>();
        TreeNode p = root;
        queue.offer(p);
        while (!queue.isEmpty()) {
            p = queue.poll();
            if (p != null) {
                sb.append(p.val + " ");
                queue.offer(p.left);
                queue.offer(p.right);
            } else {
                sb.append(NULL_VAL + " ");
            }
        }
        return sb.toString();
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        Deque<String> strs = new LinkedList<>(Arrays.asList(data.split(" ")));
        Deque<TreeNode> queue = new LinkedList<>();
        String str = strs.poll();
        if (NULL_VAL.equals(str)) return null;
        TreeNode root = new TreeNode(Integer.parseInt(str));
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode cur = queue.poll();
            String left = strs.poll();
            String right = strs.poll();
            if (!NULL_VAL.equals(left)) {
                cur.left = new TreeNode(Integer.parseInt(left));
                queue.offer(cur.left);
            }
            if (!NULL_VAL.equals(right)) {
                cur.right = new TreeNode(Integer.parseInt(right));
                queue.offer(cur.right);
            }
        }
        return root;
    }
}

// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));

⭐剑指 Offer 38. 字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。

你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

// dfs + 剪枝
class Solution {
    List<String> res;
    StringBuilder path;
    boolean[] visited;

    public String[] permutation(String s) {
        int len = s.length();
        res = new ArrayList<>();
        path = new StringBuilder();
        visited = new boolean[len];
        char[] chars = s.toCharArray();
        Arrays.sort(chars);
        dfs(0, chars);
        return res.toArray(new String[0]);
    }

    private void dfs(int cnt, char[] chars) {
        if (cnt == chars.length) {
            res.add(new String(path));
            return;
        }
        for (int i = 0; i < chars.length; i++) {
            if (visited[i] == true) continue;
            if (i > 0 && !visited[i - 1] && chars[i] == chars[i - 1]) continue;
            path.append(chars[i]);
            visited[i] = true;
            dfs(cnt + 1, chars);
            path.deleteCharAt(path.length() - 1);
            visited[i] = false;
        }
    }
}

剑指 Offer 39. 数组中出现次数超过一半的数字

难度简单245

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

// hashmap
class Solution {
    public int majorityElement(int[] nums) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        int res = 0, max_cnt = 0;
        for (int key : map.keySet()) {
            int cnt = map.get(key);
            if (cnt > max_cnt) {
                res = key;
                max_cnt = cnt;
            }
        }
        return res;
    }
}
// 摩尔计数法
class Solution {
    public int majorityElement(int[] nums) {
        // 通过计数器cnt来判断是否要还候选人
        int cnt = 0;
        Integer candidate = null;
        for (int num : nums) {
            if (cnt == 0) {
                candidate = num;
                cnt++;
                continue;
            }
            if (num == candidate) {
                cnt++;
            } else {
                cnt--;
            }

        }
        return candidate;
    }
}

⭐剑指 Offer 40. 最小的k个数

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

  1. 优先队列(大根堆)
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if (arr == null || arr.length == 0 || k == 0) return new int[0];
        PriorityQueue<Integer> queue = new PriorityQueue<>((o1, o2) -> o2 - o1);
        for (int num : arr) {
            // queue.offer(num);
            // if (queue.size() > k) {
            //     queue.poll();
            // }
            // **剪枝:29ms -> 17ms**
            // 如果queue不满,直接插入
            if (queue.size() < k) {
                queue.offer(num);
            } else if (num < queue.peek()) {
                // 满了则要判断是否要插入重新堆化
                // 如果新数字大于根结点,则跳过不用考虑,否则需要插入
                queue.poll();
                queue.offer(num);
            }
        }
        int[] res = new int[queue.size()];
        int i = 0;
        while (!queue.isEmpty()) {
            res[i++] = queue.poll();
        }
        return res;
    }
}
  1. 快速选择
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if (arr == null || arr.length == 0 || k == 0) return new int[0];
        // 前k个最小元素,也就是快速选择后,找到下标为k-1的元素,[0, k-1]即所求
        quickSelect(arr, 0, arr.length - 1, k - 1);
        // int[] res = new int[k];
        // System.arraycopy(arr, 0, res, 0, k);
        // return res;
        // Arrays.copyOf调用了System.arraycopy
        return Arrays.copyOf(arr, k);
    }

    private void quickSelect(int[] arr, int left, int right, int k) {
        int mid = partition(arr, left, right);
        if (mid == k) {
            return;
        } else if (mid < k) {
            quickSelect(arr, mid + 1, right ,k);
        } else {
            quickSelect(arr, left, mid - 1, k);
        }
    }

    private int partition(int[] arr, int left, int right) {
        int random_index = (int) (left + Math.random() * (right - left + 1));
        int temp = nums[left];
        nums[left] = nums[random_index];
        nums[random_index] = temp;
        // 加了上面的交换之后速度可能有提升,该题中不加已经超过100%
        int pivot = arr[left];
        while (left < right) {
            while (left < right && arr[right] >= pivot) {
                right--;
            }
            arr[left] = arr[right];
            while (left < right && arr[left] <= pivot) {
                left++;
            }
            arr[right] = arr[left];
        }
        arr[left] = pivot;
        return left;
    }
}

剑指 Offer 42. 连续子数组的最大和

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)。

// dp
class Solution {
    public int maxSubArray(int[] nums) {
        int len = nums.length;
        int[] dp = new int[len];
        dp[0] = nums[0];
        int res = dp[0];
        for (int i = 1; i < len; i++) {
            dp[i] = Math.max(nums[i], nums[i] + dp[i - 1]);
            res = Math.max(res, dp[i]);
        }
        return res;
    }
}
// 空间压缩
class Solution {
    public int maxSubArray(int[] nums) {
        int len = nums.length;
        int cur = nums[0];
        int res = cur;
        for (int i = 1; i < len; i++) {
            cur = Math.max(nums[i], nums[i] + cur);
            res = Math.max(res, cur);
        }
        return res;
    }
}

剑指 Offer 46. 把数字翻译成字符串

难度中等371

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

class Solution {
    public int translateNum(int num) {
        if (num >= 0 && num <= 9) return 1;
        String s = String.valueOf(num);
        int len = s.length();
        int[] dp = new int[len];
        dp[0] = 1;
        if (Integer.parseInt(s.substring(0, 2)) <= 25) {
            dp[1] = 1 + 1;
        } else {
            dp[1] = 1;
        }
        for (int i = 2; i < len; i++) {
            if (Integer.parseInt(s.substring(i - 1, i + 1)) <= 25 && s.charAt(i - 1) != '0') {
                dp[i] = dp[i - 1] + dp[i - 2];
            } else {
                dp[i] = dp[i - 1];
            }  
        }
        return dp[len - 1];
    }
}
// 空间优化
class Solution {
    public int translateNum(int num) {
        if (num >= 0 && num <= 9) return 1;
        String s = String.valueOf(num);
        int len = s.length();
        int a = 1, b = 1, c = 1;
        if (Integer.parseInt(s.substring(0, 2)) <= 25) {
            b = 1 + 1;
        } else {
            b = 1;
        }
        for (int i = 2; i < len; i++) {
            int temp = b;
            if (Integer.parseInt(s.substring(i - 1, i + 1)) <= 25 && s.charAt(i - 1) != '0') {
                c = a + b;
            } else {
                c = b;
            }
            b = c;
            a = temp; 
        }
        return b;
    }
}

剑指 Offer 47. 礼物的最大价值

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

class Solution {
    public int maxValue(int[][] grid) {
        if (grid == null || grid.length == 0) return 0;
        int m = grid.length, n = grid[0].length;
        int[][] dp = new int[m][n];
        dp[0][0] = grid[0][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++) {
            dp[i][0] = dp[i - 1][0] + grid[i][0];
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
            }
        }
        return dp[m - 1][n - 1];
    }
}

⭐⭐⭐剑指 Offer 48. 最长不含重复字符的子字符串

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

  1. 双指针+滑动窗口
/*
1.将字符串中的字符取出,使用HashMap存取对应字符。key对应字符,value对应字符下标。用Hashmap来判断字符串中字符是否重复

2.设Left为区间左端点下标,right为区间右端点下标。即区间长度=right-left+1;

3.区间左端点left不动,让right这个右端点往后遍历,如果发现准备放入map的字符已经在Hashmap中存在,那么只需要将左端点更新到在map已经存在的字符的右边一个位置,即已存在字符的下标+1。 比如aba,左端点下标开始是0,第二个a准备存入map时,发现map中已经存在a了,所以将区间左端点下标更新为第一个a的右边位置,即第一个a的下标+1。子串ab,因此变成子串 ba。

至于为什么左端点要更新到已存在字符的右边一个位置,因为关键字是最长子串。如 aba,遍历字符串aba,准备放入第二个a时发现map已经存在了字符a,那么为了保证题意的最长子串。由于子串ab,和子串ba的长度显然都是2,二者虽然长度一样。但ab子串不可能再变长(ab子串右边有一个a了,限制了子串的成长)。而ba子串向后面遍历字符串还有可能接像c,d等其他字符,子串长度成长空间更大,显然符合求最长子串的题意。要选出更长的子串,Left必须更新。

4.注意:left = Math.max(left,map.get(s.charAt(i))+1); 之所以这里要取最大值,而不是直接让left = map.get(s.charAt(i))+1
是因为 存在 abba这种回文情况,如果left = map.get(s.charAt(i))+1 ,当第二个b存入map时,Left更新为2。当第二个a,准备存入map时,如果不取最大值,会导致本来是left本来是2,又更新变成为1,出现了left往回退的情况。而left = Math.max(left,map.get(s.charAt(i))+1)可以避免这个情况。
另外由于是HashMap的原因, map.get(s.charAt(i))的值如果存在,肯定是前一次相同字符存储好的下标。 所以每次对新的字符判断map是否存在后, 无论是否更新left,也都要在map中更新每次新字符对应的新下标。 比如abba,首先子串为ab,后面子串更新为ba后,b的下标更新为2而不是1,a的下标更新为3,而不是1.如果不更新新字符对应下标 比如abbabc这种情况,后面遍历到第三个b时,本来left要更新到第二个b的后面也就第二个a的位置,但因为没在map中将第二个b的位置替换为第一个b的位置,而导致出错。

作者:unique_cat
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/javasi-lu-xiang-jie-xiao-bai-jiu-xing-by-wfbf/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character, Integer> map = new HashMap<>();
        int ans = 0;
        for (int left = 0, right = 0; right < s.length(); right++) {
            if (map.containsKey(s.charAt(right))) {
                left = Math.max(left, map.get(s.charAt(right)) + 1);
            }
            ans = Math.max(ans, right - left + 1);
            map.put(s.charAt(right), right);
        }
        return ans;
    }
}

// 优化:用数组代替map,map的作用就是记录当前这个窗口中什么时候出现了这个新的重复的元素,那么每次记录下每个元素的下标就可以了
class Solution {
    public int lengthOfLongestSubstring(String s) {
        int[] last = new int[128];
        for (int i = 0; i < 128; i++) {
            last[i] = -1;
        }
        int ans = 0;
        for (int left = 0, right = 0; right < s.length(); right++) {
            if (last[s.charAt(right)] != -1) {
                left = Math.max(left, last[s.charAt(right)] + 1);
            }
            ans = Math.max(ans, right - left + 1);
            last[s.charAt(right)] = right;   
        }
        return ans;
    }
}
  1. 动态规划
dp[j]表示以下标为j字符结尾的不含重复字符的子字符串
设dp[i] == dp[j],i < j
1.i < 0, 说明j左边没有元素和当前元素相同,dp[j] = dp[j - 1] + 1
2. i > 0
  2.1 dp[j - 1]>len((i, j]) = j - i,说明s[i]在以j-1为结尾的结果里面,dp[j] = j - i
  2.2 dp[j - 1]<=len((i, j]) = j - i,说明s[i]在以j-1为结尾的结果外面,dp[j] = dp[j - 1] + 1
class Solution {
    public int lengthOfLongestSubstring(String s) {
        if (s == null || s.length() == 0) return 0;
        int n = s.length();
        int[] dp = new int[n];
        Map<Character, Integer> map = new HashMap<>();
        dp[0] = 1;
        map.put(s.charAt(0), 0);
        int res = 1;
        for (int i = 1; i < n; i++) {
            if (!map.containsKey(s.charAt(i))) {
                dp[i] = Math.max(dp[i], 1 + dp[i - 1]);
            } else {
                if (map.get(s.charAt(i)) < i - dp[i - 1]) {
                    dp[i] = Math.max(dp[i], 1 + dp[i - 1]);
                } else {
                    dp[i] = Math.max(dp[i], i - map.get(s.charAt(i)));
                }
            }
            res = Math.max(res, dp[i]);
            map.put(s.charAt(i), i);
        }
        return res;
    }
}
// 优化1:每次dp[i]只依赖于dp[i-1],所以可以不用数组,用两个变量
// 优化2:对于map只是用来记录上一次元素出现的位置,可以不用map用数组

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int res = 0, last = 0, len = s.length();
        Map<Character, Integer> map = new HashMap<>();
        for (int i = 0; i < len; i++) {
            char c = s.charAt(i);
            last = Math.min(i - map.getOrDefault(c, -1), last + 1);
            res = Math.max(res, last);
            map.put(c, i);
        }
        return res;
    }
}

⭐⭐剑指 Offer 49. 丑数

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

// 三指针
// 第一个丑数是1,以后的丑数都是基于前面的小丑数分别乘2,3,5构成的。
// 我们每次添加进去一个当前计算出来个三个丑数的最小的一个,并且是谁计算的,谁指针就后移一位。
class Solution {
    public int nthUglyNumber(int n) {
        int a = 0, b = 0, c = 0;
        int[] res = new int[n];
        res[0] = 1;
        for (int i = 1; i < n; i++) {
            int val2 = res[a] * 2, val3 = res[b] * 3, val5 = res[c] * 5;
            int minVal = Math.min(Math.min(val2, val3), val5);
            res[i] = minVal;
            if (minVal == val2) a++;
            if (minVal == val3) b++;
            if (minVal == val5) c++;
        }
        return res[n - 1];
    }
}

剑指 Offer 50. 第一个只出现一次的字符

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。

// hashmap存储个数,两次遍历字符串
// Time:O(n)
// Space:O(n)
class Solution {
    public char firstUniqChar(String s) {
        Map<Character, Integer> map = new HashMap<>();
        for (char c : s.toCharArray()) {
            map.put(c, map.getOrDefault(c, 0) + 1);
        }
        for (char c : s.toCharArray()) {
            if (map.get(c) == 1) {
                return c;
            }
        }
        return ' ';
    }
}
// 执行用时:25 ms, 在所有 Java 提交中击败了35.82% 的用户 
// 内存消耗:41.6 MB, 在所有 Java 提交中击败了15.97% 的用户 
// 不用计数,只判断是否只出现1次
class Solution {
    public char firstUniqChar(String s) {
        Map<Character, Boolean> map = new HashMap<>();
        for (char c : s.toCharArray()) {
            // 只有0->1时才返回true
            // 其他情况都是false
            map.put(c, !map.containsKey(c));
        }
        for (char c : s.toCharArray()) {
            if (map.get(c)) {
                return c;
            }
        }
        return ' ';
    }
}
// 执行用时: 21 ms , 在所有 Java 提交中击败了 50.96% 的用户 
// 内存消耗: 41.5 MB , 在所有 Java 提交中击败了 18.06% 的用户

剑指 Offer 52. 两个链表的第一个公共节点

输入两个链表,找出它们的第一个公共节点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
// 常规思路,让长的链表先走多出来的部分,然后一起走
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) return null;
        int lenA = 0, lenB = 0;
        ListNode pA = headA, pB = headB;
        while (pA != null) {
            lenA++;
            pA = pA.next;
        }
        while (pB != null) {
            lenB++;
            pB = pB.next;
        }
        int minus = Math.abs(lenA - lenB);
        if (lenA < lenB) {
            pA = headB;
            pB = headA;
        } else {
            pA = headA;
            pB = headB;
        }
        // pA指向较长的链 长度多 minus
        for (int i = 0; i < minus; i++) {
            pA = pA.next;
        }
        while (pA != pB) {
            pA = pA.next;
            pB = pB.next;
        }
        return pA;
    }
}
// 非常规思路,走完自己的再走对方的
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode pA = headA, pB = headB;
        while (pA != pB) {
            pA = pA != null ? pA.next : headB;
            pB = pB != null ? pB.next : headA;
        }
        return pA;
    }
}

// 还可以先记录一个链表的结点,然后遍历另一个链表,判断是否结点是否存在

⭐剑指 Offer 53 - I. 在排序数组中查找数字 I

统计一个数字在排序数组中出现的次数。

// 1. 遍历 O(n) O(1)
class Solution {
    public int search(int[] nums, int target) {
        int cnt = 0;
        for (int num : nums) {
            if (num == target) {
                cnt++;
            }
        }
        return cnt;
    }
}
// 2. 二分法,找第一个target,找最后一个target O(lgn) O(1)
class Solution {
    public int search(int[] nums, int target) {
        if (nums == null || nums.length == 0) return 0;
        int first = findFirst(nums, target);
        if (first == -1) return 0;
        int last = findLast(nums, target);
        return last - first + 1;
    }

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

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

⭐剑指 Offer 53 - II. 0~n-1中缺失的数字

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

// 1. 暴力法
class Solution {
    public int missingNumber(int[] nums) {
        int len = nums.length;
        // [0, len]
        for (int i = 0; i < len; i++) {
            if (nums[i] != i) {
                return i;
            }
        }
        return len;
    }
}

// 2. 二分法
// 如果当前位置,下标和数字不同,说明在之前某个位置(包含当前位置)发生了错位
class Solution {
    public int missingNumber(int[] nums) {
        int len = nums.length;
        int left = 0, right = len - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] != mid) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        // left最后的位置依然不错位,说明在数组外发生了错位,及len处
        if (nums[left] == left) {
            return len;
        } else {
            return left;
        }
    }
}

剑指 Offer 54. 二叉搜索树的第k大节点

给定一棵二叉搜索树,请找出其中第 k 大的节点的值。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int kthLargest(TreeNode root, int k) {
        int cnt = 0;
        Deque<TreeNode> stack = new LinkedList<>();
        TreeNode p = root;
        while (!stack.isEmpty() || p != null) {
            if (p != null) {
                stack.push(p);
                p = p.right;
            } else {
                p = stack.pop();
                if (++cnt == k) {
                    return p.val;
                }
                p = p.left;
            }
        }
        return -1;
    }
}

剑指 Offer 55 - I. 二叉树的深度

难度简单164

输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
// 递归
class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) return 0;
        return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
    }
}
// 迭代,层序遍历
class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) return 0;
        Deque<TreeNode> queue = new LinkedList<>();
        int depth = 0;
        TreeNode p = root;
        queue.offer(p);
        while (!queue.isEmpty()) {
            int size = queue.size();
            depth++;
            for (int i = 0; i < size; i++) {
                p = queue.poll();
                if (p.left != null) queue.offer(p.left);
                if (p.right != null) queue.offer(p.right);
            }
        }
        return depth;
    }
}

⭐⭐剑指 Offer 55 - II. 平衡二叉树

输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
// 自顶向下递归
// O(nlgn) O(n)
class Solution {
    public boolean isBalanced(TreeNode root) {
        if (root == null) return true;
        int leftDepth = getDepth(root.left), rightDepth = getDepth(root.right);
        return Math.abs(leftDepth - rightDepth) <= 1 && isBalanced(root.left) && isBalanced(root.right);
    }

    private int getDepth(TreeNode root) {
        if (root == null) return 0;
        return 1 + Math.max(getDepth(root.left), getDepth(root.right));
    }
}
// 自底向上递归
// O(n) O(n)
// 对于某个结点,判断以其为根的树是否平衡,平衡返回高度,否则返回-1
class Solution {
    public boolean isBalanced(TreeNode root) {
        return getDepth(root) >= 0;
    }

    private int getDepth(TreeNode root) {
        if (root == null) return 0;
        int leftDepth = getDepth(root.left), rightDepth = getDepth(root.right);
        if (leftDepth == -1 || rightDepth == -1 || Math.abs(leftDepth - rightDepth) > 1) {
            return -1;
        } else {
            return Math.max(leftDepth, rightDepth) + 1;
        }
    }
}

⭐⭐剑指 Offer 56 - I. 数组中数字出现的次数

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

class Solution {
    public int[] singleNumbers(int[] nums) {
        int x = 0;
        for (int num : nums) {
            x ^= num;
        }
        // x = a ^ b
        // 由于 a 和 b不同所以x不为0,假设x为 0100 0010
        // 那么1的位置表明a和b这两个该位上不同,即该位一个为0一个为1
        // 因此可以根据该为0还是1分为两组
        // 每组里面都有若干相等的数加上a/b
        int idx = 0;
        while ((num & 1) == 0) {
            num >>= 1;
            idx++;
        }
        int a = 0, b = 0;
        for (int num : nums) {
            if ((num >> idx) & 1 == 0) {
                a ^= num;
            } else {
                b ^= num;
            }
        }
        return new int[]{a, b};
    }
}

⭐⭐剑指 Offer 56 - II. 数组中数字出现的次数 II

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

class Solution {
    public int singleNumber(int[] nums) {
        // 将所有数字二进制位对应加起来
        // 如果该位为3,说明结果该位为0,否则结果该位为1
        int len = nums.length;
        int[] bit_sum = new int[32];
        for (int num : nums) {
            for (int j = 0; j < 32; j++) {
                // num & 1 = 1说明num最低位为1,即num & 1表示最低位数字
                bit_sum[j] += (num & 1);
                num >>= 1;
            }
        }
        int res = 0;
        for (int i = 31; i >= 0; i--) {
            res <<= 1;
            if (bit_sum[i] % 3 == 1) {
                res = (res | 1);
            }  
        }
        return res;
    }
}

剑指 Offer 57. 和为s的两个数字

输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。

// 1. 暴力法 O(n^2) O(1)
// 2. map存num对应的下标,每次遍历到新数字看看能否找到对应剩余数字,将新数字加入到map中 O(n) O(n)
// 考虑到数字有序,可以进一步优化
// 3. 二分 暴力法的优化 O(nlgn) O(1)
// 4. 双指针
// 1. 暴力法 超时
// 2. map空间换时间
class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        int[] res= new int[2];
        for (int i = 0; i < nums.length; i++) {
            if (map.containsKey(target - nums[i])) {
                res[0] = nums[i];
                res[1] = target - nums[i];
            }
            map.put(nums[i], i);
        }
        return res;
    }
}
// 3. 二分法
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] res = new int[2];
        for (int i = 0; i < nums.length - 1; i++) {
            int find = target - nums[i];
            int left = i + 1, right = nums.length - 1;
            while (left < right) {
                int mid = left + (right - left) / 2;
                if (nums[mid] < find) {
                    left = mid + 1;
                } else {
                    right = mid;
                }
            }
            if (nums[left] == find) {
                return new int[]{nums[i], find};
            }
        }
        return res;
    }
}
// 4. 双指针
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] res = new int[2];
        int left = 0, right = nums.length - 1;
        while (left < right) {
            int sum = nums[left] + nums[right];
            if (sum == target) {
                return new int[]{nums[left], nums[right]};
            } else if (sum > target) {
                right--;
            } else {
                left++;
            }
        }
        return res;
    }
}

⭐⭐⭐剑指 Offer 57 - II. 和为s的连续正数序列

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。

序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

// 双指针1
class Solution {
    public int[][] findContinuousSequence(int target) {
        // [a, b] 由于连续,所以这个范围的求和可以直接用求和公式
        // sum = (b - a + 1) (a + b) / 2,注意double和int转换
        List<int[]> res = new ArrayList<>();
        int left = 1, right = 1;
        // while (right < target) {
        // 此处的判断条件可以优化,不一定要遍历完
        // 当 right == target / 2 + 1的时候就是最后可能的右边界
        while (right <= target / 2 + 1) {
            int sum = (right - left + 1) * (right + left) / 2;
            if (sum == target) {
                int[] temp = new int[right - left + 1];
                for (int i = 0; i < right - left + 1; i++) {
                    temp[i] = i + left;
                }
                res.add(temp);
                left++;
            } else if (sum < target) {
                right++;
            } else {
                left++;
            }
        }
        return res.toArray(new int[0][]);
    }
}
// 双指针2
class Solution {
    public int[][] findContinuousSequence(int target) {
        List<int[]> list = new ArrayList<>();
        //区间是(1, 2, 3, ..., target - 1)
        //套滑动窗口模板,l是窗口左边界,r是窗口右边界,窗口中的值[l, r]一定是连续值。
        //当窗口中数字和小于target时,r右移; 大于target时,l右移; 等于target时就获得了一个解
        for (int l = 1, r = 1, sum = 0; r <= target / 2 + 1; r++) {
            sum += r;
            while (sum > target) {
                sum -= l++;
            }
            if (sum == target) {
                int[] temp = new int[r - l + 1];
                for (int i = 0; i < temp.length; i++) {
                    temp[i] = l + i;
                }
                list.add(temp);
            }
        }
        return list.toArray(new int[0][]);
    }
}

剑指 Offer 58 - I. 翻转单词顺序

输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. “,则输出"student. a am I”。

// 用split划分
class Solution {
    public String reverseWords(String s) {
        s = s.trim();
        String[] splits = s.split(" +");
        StringBuilder sb = new StringBuilder();
        for (int i = splits.length - 1; i >= 0; i--) {
            sb.append(splits[i]);
            if (i != 0) sb.append(" ");
        }
        return sb.toString();
    }
}
// 双指针,从后往前寻找单词,j指向单词结尾,i指向单词开头。遍历完空格之后重新设置j为空格前一个字符
class Solution {
    public String reverseWords(String s) {
        s = s.trim();
        int len = s.length();
        int j = len - 1, i = j;
        StringBuilder sb = new StringBuilder();
        while (i >= 0) {
            while (i >= 0 && s.charAt(i) != ' ') i--;
            // [i + 1, j + 1)
            sb.append(s.substring(i + 1, j + 1));
            sb.append(" ");
            while (i >= 0 && s.charAt(i) == ' ') i--;
            j = i;
        }
        return sb.toString().trim();
    }
}

剑指 Offer 58 - II. 左旋转字符串

难度简单193

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

// O(n) O(n)
class Solution {
    public String reverseLeftWords(String s, int n) {
        StringBuilder sb = new StringBuilder();
        // [0, n) [n:)
        sb.append(s.substring(n));
        sb.append(s.substring(0, n));
        return sb.toString();
    }
}

// O(n) O(1)
// abc defg
// cba gfed	分别逆序
// defg abc 整体逆序
class Solution {
    public String reverseLeftWords(String s, int n) {
        int len = s.length();
        StringBuilder sb = new StringBuilder(s);
        reverse(sb, 0, n- 1);
        reverse(sb, n, len - 1);
        reverse(sb, 0, len - 1);
        return sb.toString();
    }

    private void reverse(StringBuilder sb, int left, int right) {
        while (left < right) {
            char temp = sb.charAt(left);
            sb.setCharAt(left, sb.charAt(right));
            sb.setCharAt(right, temp);
            left++;
            right--;
        }
    }
}

剑指 Offer 59 - II. 队列的最大值

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_valuepush_backpop_front均摊时间复杂度都是O(1)。

若队列为空,pop_frontmax_value 需要返回 -1

class MaxQueue {
    Deque<Integer> queue;		// 正常queue进行增减元素
    Deque<Integer> desc_stack;	// 单调递减栈,当前队列中的元素递减排列

    public MaxQueue() {
        queue = new LinkedList<>();
        desc_stack = new LinkedList<>();
    }
    
    public int max_value() {
        return desc_stack.isEmpty() ? -1 : desc_stack.peekFirst();
    }
    
    public void push_back(int value) {
        queue.offerLast(value);
        while (!desc_stack.isEmpty() && value > desc_stack.peekLast()) {
            desc_stack.pollLast();
        }
        desc_stack.offerLast(value);
    }
    
    public int pop_front() {
        if (queue.isEmpty()) return -1;
        // int frontVal = queue.pollFirst();
        // if (frontVal == desc_stack.peekFirst()) {
        //    desc_stack.pollFirst();
        // }
        // return frontVal;
        //最好用equals来进行Integer值的判断
        if (queue.peekFirst().equals(desc_stack.peekFirst())) {
            desc_stack.pollFirst();
        }
        return queue.pollFirst();
    }
}

/**
 * Your MaxQueue object will be instantiated and called as such:
 * MaxQueue obj = new MaxQueue();
 * int param_1 = obj.max_value();
 * obj.push_back(value);
 * int param_3 = obj.pop_front();
 */

⭐剑指 Offer 60. n个骰子的点数

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。

你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。

class Solution {
    public double[] dicesProbability(int n) {
        double[] res = new double[6 * n - n + 1];
        double all = Math.pow(6, n);
        int[][] dp = new int[n + 1][6 * n + 1];
        // dp[n][s] 表示 n 个骰子总和为 s 的排列数
        // dp[n][s] <- dp[n-1][s-1] + dp[n-1][s-2] + dp[n-1][s-6]
        for (int j = 1; j <= 6; j++) {
            dp[1][j] = 1;
        }
        for (int i = 1; i <= n; i++) {  // i个骰子
            for (int j = i; j <= 6 * i; j++) {  // i个骰子能够得到的总和为j
                for (int k = 1; k <= 6; k++) {  // 当前骰子的值为k
                    dp[i][j] += j - k > 0 ? dp[i - 1][j - k] : 0;
                }
                if (i == n) {
                    res[j - i] = dp[i][j] / all;
                }
            }
        }
        return res;
     }
}

⭐⭐剑指 Offer 61. 扑克牌中的顺子

若干副扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。

class Solution {
    public boolean isStraight(int[] nums) {
        // 除了0以外,剩下的 最大值 - 最小值 < 5且无重复
        Set<Integer> set = new HashSet<>();
        int maxVal = -1, minVal = 14;
        for (int num : nums) {
            if (num == 0) continue;
            if (set.contains(num)) return false;
            set.add(num);
            maxVal = Math.max(maxVal, num);
            minVal = Math.min(minVal, num);
        }
        return maxVal - minVal < 5;
    }
}

⭐⭐剑指 Offer 62. 圆圈中最后剩下的数字

0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

// 1. 模拟
class Solution {
    public int lastRemaining(int n, int m) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            list.add(i);
        }
        int idx = 0;
        while (n > 1) {
            idx = (idx + m - 1) % n;
            list.remove(idx);
            n--;
        }
        return list.get(0);
    }
}
// 2. 公式:(当前index + m) % 上一轮剩余数字的个数
class Solution {
    public int lastRemaining(int n, int m) {
        // (当前idx + m)% 上一轮剩余个数 = 上一轮idx
        int idx = 0;
        for (int len = 2; len <= n; len++) {
            idx = (idx + m) % len;
        }
        return idx;
    }
}

剑指 Offer 63. 股票的最大利润

难度中等213

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

class Solution {
    public int maxProfit(int[] prices) {
        // dp[i][0] 表示第i天不持股的当前利润
        // dp[i][1] 表示第i天持股的当前利润
        if (prices == null || prices.length == 0) return 0;
        int n = prices.length;
        int[][] dp = new int[n][2];
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        for (int i = 1; i < n; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
            dp[i][1] = Math.max(dp[i - 1][1], -prices[i]);
        }
        return dp[n - 1][0];
    }
}
// 空间优化
class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length == 0) return 0;
        int n = prices.length;
        int no_keep = 0, keep = -prices[0];
        for (int i = 1; i < n; i++) {
            no_keep = Math.max(no_keep, keep + prices[i]);
            keep = Math.max(keep, -prices[i]);
        }
        return no_keep;
    }
}

剑指 Offer 64. 求1+2+…+n

1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

// 1. 数学法 用到了运算符
// 2. 迭代 用到了循环判断
// 3. 递归 用到了if边界判断
class Solution {
    public int sumNums(int n) {
        if (n == 1) return 1;
        n += sumNums(n - 1);
        return n;
    }
}
// 那么就思考如何才能在 n==1的时候结束递归,不执行下面递归语句
// 短路效应
// if(A && B)	// 若 A 为 false ,则 B 的判断不会执行(即短路),直接判定 A && B 为 false
// if(A || B) 	// 若 A 为 true ,则 B 的判断不会执行(即短路),直接判定 A || B 为 true
// 我们需要在n==1的时候不执行n += sumNums(n - 1);
class Solution {
    public int sumNums(int n) {
        boolean flag = (n > 1) && (n += sumNums(n - 1)) > 0;
        return n;
    }
}
class Solution {
    public int sumNums(int n) {
        boolean flag = (n == 1) || (n += sumNums(n - 1)) > 0;
        return n;
    }
}

剑指 Offer 65. 不用加减乘除做加法

写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。

class Solution {
    public int add(int a, int b) {
        // ^ 亦或相当于 无进位的求和
        // & 与  相当于求每位的进位数
        // (a^b) ^ ((a&b)<<1)
        while (b != 0) {
            int c = (a & b) << 1;   // 新的进位
            a ^= b;
            b = c;
        }
        return a;
    }
}

剑指 Offer 66. 构建乘积数组

给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

class Solution {
    public int[] constructArr(int[] a) {
        if (a == null || a.length == 0) return new int[0];
        // 对于 b[i] 值为 a[0] * ... * a[i - 1] 1 a[i + 1] * .. * a[n - 1]
        int n = a.length;
        int[] b = new int[n];
        // prefix[i] 表示 [0, i - 1]的乘积
        // suffix[i] 表示 [i + 1, n - 1]的乘积
        int[] prefix = new int[n], suffix = new int[n];
        prefix[0] = 1;
        for (int i = 1; i < n; i++) {
            prefix[i] = prefix[i - 1] * a[i - 1];
        }
        suffix[n - 1] = 1;
        for (int i = n - 2; i >= 0; i--) {
            suffix[i] = suffix[i + 1] * a[i + 1];
        }
        for (int i = 0; i < n; i++) {
            b[i] = prefix[i] * suffix[i];
        }
        return b;
    }
}

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

难度简单204

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) return null;
        if (root.val > Math.max(p.val, q.val)) {
            return lowestCommonAncestor(root.left, p, q);
        }
        if (root.val < Math.min(p.val, q.val)) {
            return lowestCommonAncestor(root.right, p, q);
        }
        return root;
    }
}

剑指 Offer 68 - II. 二叉树的最近公共祖先

难度简单376

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) return null;
        if (root == p || root == q) return root;
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if (left != null && right != null) return root;
        if (left != null) return left;
        if (right != null) return right;
        return null;
    }
}

你可能感兴趣的:(LeetCode,leetcode,算法)