leecode剑指offer集合及题解(持续更新。。)

leecode剑指offer集合及题解(持续更新。。)

  • 剑指 Offer 03. 数组中重复的数字
  • 剑指 Offer 04. 二维数组中的查找
  • 剑指 Offer 06. 从尾到头打印链表
  • 剑指 Offer 07. 重建二叉树
  • 剑指 Offer 09. 用两个栈实现队列
  • 剑指 Offer 10- I. 斐波那契数列
  • 剑指 Offer 10- II. 青蛙跳台阶问题
  • 剑指 Offer 12. 矩阵中的路径
  • 剑指 Offer 13. 机器人的运动范围
  • 剑指 Offer 14- I. 剪绳子
  • 剑指 Offer 14- II. 剪绳子 II
  • 剑指 Offer 15. 二进制中1的个数
  • 剑指 Offer 16. 数值的整数次方
  • 剑指 Offer 17. 打印从1到最大的n位数
  • 剑指 Offer 18. 删除链表的节点
  • 剑指 Offer 19. 正则表达式匹配(※)
  • 剑指 Offer 20. 表示数值的字符串
  • 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
  • 剑指 Offer 22. 链表中倒数第k个节点
  • 剑指 Offer 24. 反转链表
  • 剑指 Offer 25. 合并两个排序的链表
  • 剑指 Offer 26. 树的子结构
  • 剑指 Offer 27. 二叉树的镜像
  • 剑指 Offer 28. 对称的二叉树
  • 剑指 Offer 29. 顺时针打印矩阵
  • 剑指 Offer 30. 包含min函数的栈
  • 剑指 Offer 31. 栈的压入、弹出序列
  • 剑指 Offer 32 - I. 从上到下打印二叉树
  • 剑指 Offer 32 - II. 从上到下打印二叉树 II
  • 剑指 Offer 32 - III. 从上到下打印二叉树 III
  • 剑指 Offer 33. 二叉搜索树的后序遍历序列
  • 剑指 Offer 34. 二叉树中和为某一值的路径
  • 剑指 Offer 35. 复杂链表的复制
  • 剑指 Offer 36. 二叉搜索树与双向链表
  • 剑指 Offer 37. 序列化二叉树
  • 剑指 Offer 38. 字符串的排列
  • 剑指 Offer 39. 数组中出现次数超过一半的数字
  • 剑指 Offer 40. 最小的k个数
  • 剑指 Offer 41. 数据流中的中位数
  • 剑指 Offer 42. 连续子数组的最大和
  • 剑指 Offer 43. 1~n 整数中 1 出现的次数
  • 剑指 Offer 44. 数字序列中某一位的数字
  • 剑指 Offer 45. 把数组排成最小的数
  • 剑指 Offer 46. 把数字翻译成字符串
  • 剑指 Offer 47. 礼物的最大价值
  • 剑指 Offer 48. 最长不含重复字符的子字符串
  • 剑指 Offer 49. 丑数
  • 剑指 Offer 50. 第一个只出现一次的字符
  • 剑指 Offer 51. 数组中的逆序对
  • 剑指 Offer 52. 两个链表的第一个公共节点
  • 剑指 Offer 53 - I. 在排序数组中查找数字 I
  • 剑指 Offer 53 - II. 0~n-1中缺失的数字
  • 剑指 Offer 54. 二叉搜索树的第k大节点
  • 剑指 Offer 55 - I. 二叉树的深度
  • 剑指 Offer 55 - II. 平衡二叉树
  • 剑指 Offer 56 - I. 数组中数字出现的次数
  • 剑指 Offer 56 - II. 数组中数字出现的次数 II
  • 剑指 Offer 57. 和为s的两个数字
  • 剑指 Offer 57 - II. 和为s的连续正数序列
  • 剑指 Offer 58 - I. 翻转单词顺序
  • 剑指 Offer 58 - II. 左旋转字符串
  • 剑指 Offer 59 - I. 滑动窗口的最大值
  • 剑指 Offer 59 - II. 队列的最大值
  • 剑指 Offer 60. n个骰子的点数
  • 剑指 Offer 61. 扑克牌中的顺子
  • 剑指 Offer 62. 圆圈中最后剩下的数字
  • 剑指 Offer 63. 股票的最大利润
  • 剑指 Offer 64. 求1+2+…+n
  • 剑指 Offer 65. 不用加减乘除做加法
  • 剑指 Offer 66. 构建乘积数组
  • 剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
  • 剑指 Offer 68 - II. 二叉树的最近公共祖先

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

题目:找出数组中重复的数字。

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

输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3 

题解
巧用HashSet的去重特点,遍历数据集合,向set中添加元素,如果添加返回false,则遇到重复的数据直接返回即可。

class Solution {
     
    public int findRepeatNumber(int[] nums) {
     
       HashSet set =  new HashSet<Integer>();
        for(int num: nums){
     
            if(!set.add(num))
                return num;
        }
        return -1;
    }
}

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

题目

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

题解
优化版暴力遍历法,根据条件“每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序”因此我们逐行遍历之前,判断该行的首位元素是否小于target目标值,末尾元素是否大于target目标值,两个条件同时满足时,遍历该行。

示例:

现有矩阵 matrix 如下:

[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。

给定 target = 20,返回 false。
class Solution {
     
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
     
        int n = matrix.length;
        if(n==0)
        return false;
        int m = matrix[0].length;
        if(m==0)
        return false;
        for(int i=0; i<n;i++){
     
            if(matrix[i][0]<=target && matrix[i][m-1]>=target){
     
                for(int num: matrix[i]){
     
                    if(num==target)
                    return true;
                }
            }
        }
        return false;
    }
}

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

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

示例 1:

输入:head = [1,3,2]
输出:[2,3,1]

题解:
使用栈的先进后出的特点,遍历链表放入栈中,再逐个出栈放入数组。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
     
    public int[] reversePrint(ListNode head) {
     
        Stack<Integer> st = new Stack<Integer>();
        int size = 0;
        while(null!=head){
     
            st.push(head.val);
            size++;
            head = head.next;
        }
        int[] arr = new int[size];
        int j =0 ;
        while(!st.isEmpty()){
     
            arr[j] = st.pop();
            j++;
        }
        return arr;
    }
}

剑指 Offer 07. 重建二叉树

题目:
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例如,给出

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:

    3
   / \
  9  20
    /  \
   15   7

限制:

0 <= 节点个数 <= 5000
题解:

对于任意一颗树而言,
前序遍历的形式总是

[ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]

中序遍历的形式总是

[ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]

比如下面这棵树:


   		 a
  		/ \
	  b 	c
  	 / \   / \
 	d   e  f  g
 			 根    左子树       右子树
 前序遍历结果:a	  [b d e]	  [c f g]
 
			  左子树     根     右子树
 中序遍历结果:[d b e]    a	  [f c g]
 
 思路:
 	1.利用中序数组获取当前节点左右子树的大小。
		前序遍历的结果首元素就是这棵树的根节点a。
		在中序数组中找到根节点a的位置,a左边的元素数量为左子树元素数量。
		同理,a右边为右子树元素。
	2.结合左右子树的大小 在前序数组中 定位左右子树的根节点。
		在前序数组中:
		左子树根节点b的索引 = a的索引+1;
		右子树根节点c的索引 = a的索引+左子树的大小+1; 
	```


/**
 * 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) {
     
        //key:中序数组的值  value:中序数组的索引
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int i = 0; i < preorder.length; i++) {
     
            map.put(inorder[i],i);
        }

        return buildMyTree(preorder,inorder,map,0,preorder.length-1,0,preorder.length-1);
    }

    /**
     * 参数解释
     * @param pre_left 当前树在前序数组的左边界
     * @param pre_right 当前树在前序数组的右边界
     * @param in_left 当前树在中序数组的左边界
     * @param in_right 当前树在中序数组的右边界
     * @return
     */
    public  TreeNode buildMyTree(int[] preorder,int[] inorder,HashMap<Integer,Integer> map,
                                int pre_left,int pre_right,int in_left,int in_right){
     
		//递归结束条件
        if (pre_left>pre_right){
     
            return null;
        }
		//当前树(包括为子树的情况)的根节点为前序数组的 pre_left
        Integer pre_root = pre_left;
        //当前树的根节点在中序数组中的索引
        Integer in_root = map.get(preorder[pre_root]);
        //创建当前树的根节点
        TreeNode root = new TreeNode(preorder[pre_root]);
        //左子树的大小
        int leftSize = in_root-in_left;

		//向左递归左子树
        root.left = buildMyTree(preorder,inorder,map,pre_left+1,pre_left+leftSize,in_left, in_root-1);
        //向右递归右子树
        root.right = buildMyTree(preorder,inorder,map,pre_left+leftSize+1,pre_right,in_root+1,in_right);

        return root;
    }
}

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

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

示例 1:

输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:

输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]

题解:
该题比较简单,主要是利用栈的先进后出特点。两个栈”负负得正“就可达到先进先出的结果。需要注意的地方有:
1、连续增加的元素需要一次性的从stack1压入stack2;因此,添加flag记录连续增加的次数。
2、stack1向stack2压入元素时,需检查stack2是不是为空,只有stack2为空才允许压入新的元素。否则先进来的元素会被后来的元素挤到栈底。

class CQueue {
     
    private final Stack<Integer> stack1;
    private final Stack<Integer> stack2;
    //用于记录stack1的添加次数
    int flag = 0;
    public CQueue() {
     
        stack1 = new Stack<Integer>();
        stack2 = new Stack<Integer>();
    }
	
    public void appendTail(int value) {
     
        stack1.push(value);
        flag++;
    }

    public int deleteHead() {
     
        Integer pop;
        //stack2只有为空时才允许再加入,否则先进来的元素会被压倒栈底
        //当然flag>0表示有新元素已就位时才允许入stack2
        if (stack2.size()==0 && flag>0){
     
            for (int i = 0; i < flag; i++) {
     
                stack2.push(stack1.pop());
            }
            //stack1的元素已全部压入stack2,次数清空
            flag = 0;
        }
        try {
     
            pop = stack2.pop();
            return pop;
        }catch (Exception e){
     
        	//pop的底层当stack为空时,抛出异常。
            pop = -1;
            return pop;
        }
    }
}

剑指 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。


示例 1:

输入:n = 2
输出:1
示例 2:

输入:n = 5
输出:5

题解:

动态规划
以斐波那契数列性质 f(n + 1) = f(n) + f(n - 1)f(n+1)=f(n)+f(n−1) 为状态转移方程。

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

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

题目:
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:2
示例 2:

输入:n = 7
输出:21
示例 3:

输入:n = 0
输出:1

题解:
(图作者:jyd链接:https://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/solution/mian-shi-ti-10-ii-qing-wa-tiao-tai-jie-wen-ti-dong/)
leecode剑指offer集合及题解(持续更新。。)_第1张图片
leecode剑指offer集合及题解(持续更新。。)_第2张图片

//暴力递归法
class Solution13 {
     
    public int numWays(int n) {
     
        if (n==0||n==1)
            return 1;
        return (numWays(n-1)+numWays(n-2))%1000000007;
    }
}

//改进版递归,避免重复计算
class Solution12 {
     
    private   HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
    public int numWays(int n) {
     
        if (n==0||n==1)
            return 1;
        if (map.get(n)!=null)
            return map.get(n);
        map.put(n, (numWays(n-1)+numWays(n-2))%1000000007);
        return map.get(n);
    }
}

//迭代法
class Solution11 {
     
    public int numWays(int n) {
     
        if (n==0||n==1)
            return 1;
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        map.put(0,1);
        map.put(1,1);
        for (int i = 2; i <= n; i++) {
     
            map.put(i, (map.get(i-1)+map.get(i-2))%1000000007);
        }
        return map.get(n);
    }
}

剑指 Offer 12. 矩阵中的路径

题目:
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。

示例 1:

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
示例 2:

输入:board = [["a","b"],["c","d"]], word = "abcd"
输出:false

题解:
(图作者:jyd链接:https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/solution/mian-shi-ti-12-ju-zhen-zhong-de-lu-jing-shen-du-yo/)
leecode剑指offer集合及题解(持续更新。。)_第3张图片
leecode剑指offer集合及题解(持续更新。。)_第4张图片

class Solution {
     
     public boolean exist(char[][] board, String word) {
     
        for (int i = 0; i < board.length; i++) {
     
            for (int j = 0; j < board[0].length; j++) {
     
                if (traceBack(board, word, i, j, 0))
                    return true;
            }
        }
        return false;
    }

    public boolean traceBack(char[][] board, String word, int i, int j, int k) {
     
        //递归结束条件
        //1.碰壁则回溯
        if (board.length <= i || i < 0 || board[0].length <= j || j < 0 || board[i][j] != word.charAt(k))
            return false;
        //2.word已经全部找完
        if (k == word.length() - 1)
            return true;
            
        //1.不能走回头路
        board[i][j] = '\0';
        boolean res = traceBack(board, word, i + 1, j, k + 1) ||
                traceBack(board, word, i - 1, j, k + 1) ||
                traceBack(board, word, i, j + 1, k + 1) ||
                traceBack(board, word, i, j - 1, k + 1);
        //2.恢复原数组,便于后续递归回溯
        board[i][j]=word.charAt(k);
        return res;
    }
}

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

题目:
地上有一个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。请问该机器人能够到达多少个格子?

示例 1:

输入:m = 2, n = 3, k = 1
输出:3
示例 2:

输入:m = 3, n = 1, k = 0
输出:1
提示:

1 <= n,m <= 100
0 <= k <= 20

题解:
使用深度优先遍历、广度优先遍历(相关链接)。
深度优先遍历思路:
1. 深度优先搜索:可以理解为暴力法模拟机器人在矩阵中的所有路径。DFS 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
2. 剪枝: 在搜索中,遇到数位和超出目标值、此元素已访问,则应立即返回,称之为 可行性剪枝 。
3. 终止条件: 当 ① 行列索引越界 或 ② 数位和超出目标值 k 或 ③ 当前元素已访问过 时,返回 0 ,代表不计入可达解。
4. 递推工作:
记当前单元格 :将二维数组中索引 (i, j)位置记为1,代表此单元格已被访问过。
搜索下一单元格: 计算当前元素的 下、右 两个方向元素的数位和,并开启下层递归 。
6. 回溯返回值: 返回 1 + 右方搜索的可达解总数 + 下方搜索的可达解总数,代表从本单元格递归搜索的可达解总数。

class Solution {
     
    public int movingCount(int m, int n, int k) {
     
    	//初始化一个二维数组,未经过的位置为0,已经过的设置为1
        int[][] table = new int[m][n];
        for (int i = 0; i < table.length; i++) {
     
            for (int j = 0; j < table[0].length; j++) {
     
                table[i][j] = 0;
            }
        }
        
        return traceBack(table, 0, 0, k);
    }

    public static int traceBack(int[][] arr, int i, int j, int k) {
     
    //3个终止条件
        if (i >= arr.length || i < 0 || j >= arr[0].length || j < 0 || k < getMn(i, j) || arr[i][j] == 1)
            return 0;
        //经过的位置值为1,未经过的为0
        arr[i][j] = 1;
        //只会向下和向右移动,只有两个移动方向
        return 1 + traceBack(arr, i + 1, j, k) +
                    traceBack(arr, i, j + 1, k);
    }

    //1 <= n,m <= 100
    //0 <= k <= 20
    //与k相关的约束条件计算
    public static int getMn(int m, int n) {
     
        return m % 10 + m / 10 % 10 + m / 100 + n % 10 + n / 10 % 10 + n / 100;
    }
}

剑指 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。

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:

2 <= n <= 58

题解:
一、贪心思路
leecode剑指offer集合及题解(持续更新。。)_第5张图片
二、动态规划思路

  1. 我们想要求长度为n的绳子剪掉后的最大乘积,可以从前面比n小的绳子转移而来
  2. 用一个dp数组记录从0到n长度的绳子剪掉后的最大乘积,也就是dp[i]表示长度为i的绳子剪成m段后的最大乘积,初始化dp[2] = 1
  3. 我们先把绳子剪掉第一段(长度为j),如果只剪掉长度为1,对最后的乘积无任何增益,所以从长度为2开始剪
  4. 剪了第一段后,剩下(i - j)长度可以剪也可以不剪。如果不剪的话长度乘积即为j * (i - j);如果剪的话长度乘积即为j * dp[i - j]。取两者最大值max(j * (i - j), j * dp[i - j])
  5. 第一段长度j可以取的区间为[2,i),对所有j不同的情况取最大值,因此最终dp[i]的转移方程为 dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]))
  6. 最后返回dp[n]即可
//贪心算法
class Solution {
     
    public static int cuttingRope(int n) {
     
        if (n<=3)return n-1;
        int res = 1;
        while (n>4){
     
            res*=3;
            n-=3;
        }
        return res*n;
    }
}

//动态规划
class Solution {
     
    // 2<=m<=n  n为绳子的长度,m为剪后的段数
    public int cuttingRope(int n) {
     
                int[] dp = new int[n+1];
        //初始值
        dp[2]=1;
        //绳子长度为3到n的所有情况
        for (int i = 3; i < n+1; i++) {
     
            //将长度为i的绳子切后,最后一段长度的绳子为2到i的所有情况.
            // 如果为1的话肯定不会取到最大面积,所以初始值为2.
            for (int j = 2; j < i; j++) {
     
                //第一层取最大值是针对最后一段j取不同值时的最大乘积
                //第二层取最大值是指,(i-j)这段可能需要剪:dp[i-j]*j,也可能不需要:(i-j)*j
                dp[i]=Math.max(dp[i], Math.max((i-j)*j,dp[i-j]*j));
            }
        }
        return dp[n];
    }
}

剑指 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:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
 

提示:

2 <= n <= 1000

题解:
leecode剑指offer集合及题解(持续更新。。)_第6张图片

class Solution {
     
    public int cuttingRope(int n) {
     
        if (n<=3)return n-1;
        long res = 1L;
        int p = (int) (1e9+7);
        while (n>4){
     
            res=res*3%p;
            n-=3;
        }
        return (int) (res*n%p);
    }
}

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

题目:
请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。

示例 1:

输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
示例 2:

输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。
示例 3:

输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。
 

提示:

输入必须是长度为 32 的 二进制串 。

题解:
位运算符的使用。
举例:目标值与逐位做 & 运算,非零个数就是目标值中1的个数。

10101    
00001       &       00001  (非0)

10101
00010       &       00000   (0)

10101
00100       &       00100  (非0)

10101
01000       &       00000   (0)

10101
10000       &       10000  (非0)
    public int hammingWeight(int n) {
     
       int count=0;
       for(int i=1;i<=32;i++)
           if((n&(1<<(i-1)))!=0)
               count++;
       return count;
   }

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

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

示例 1:

输入:x = 2.00000, n = 10
输出:1024.00000
示例 2:

输入:x = 2.10000, n = 3
输出:9.26100
示例 3:

输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25

题解:
leecode剑指offer集合及题解(持续更新。。)_第7张图片

class Solution {
     
    public double myPow(double x, int n) {
     
        if (x == 0) return 0;
        long b = n;
        double res = 1.0;
        if (b<0){
     
            x = 1/x;
            b = -b;
        }
        while (b>0){
     
            if ((b&1)==1) res*=x;
            x*=x;
            b>>=1;
        }
        return res;
    }
}

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

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

示例 1:

输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]


说明:

用返回一个整数列表来代替打印
n 为正整数

题解:
当 n 较大时,end 会超出 int32 整型的取值范围,超出取值范围的数字无法正常存储。但由于本题要求返回 int 类型数组,相当于默认所有数字都在 int32 整型取值范围内,因此不考虑大数越界问题。

    public int[] printNumbers(int n) {
     
        int[] arr = new int[(int) Math.pow(10, n)-1];
        for (int i = 1; i < Math.pow(10, n); i++) {
     
            arr[i-1] = i;
        }
        return arr;
    }

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

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

示例 1:

输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:

输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
 

说明:

题目保证链表中节点的值互不相同
若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点

题解:

/**
 * 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) {
     
        if (head.val==val)
            return head.next;
        ListNode pre = head;
        while (pre!=null && pre.next!=null){
     
            if (pre.next.val==val){
     
                pre.next = pre.next.next;
            }
            pre = pre.next;
        }
        return head;
    }
}

剑指 Offer 19. 正则表达式匹配(※)

题目:
请实现一个函数用来匹配包含'. '和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串

示例 1:

输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
示例 2:

输入:
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例 3:

输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
示例 4:

输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
示例 5:

输入:
s = "mississippi"
p = "mis*is*p*."
输出: false
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母以及字符 . 和 *,无连续的 '*'。

题解:
动态规划。输入字符串是否匹配可以转化子字符串是否匹配。
leecode剑指offer集合及题解(持续更新。。)_第8张图片
leecode剑指offer集合及题解(持续更新。。)_第9张图片
传送门

class Solution {
     
          public boolean isMatch(String s, String p) {
     
        int m = s.length() +1;
        int n = p.length() +1;
        boolean[][] dp = new boolean[m ][n ];
        dp[0][0] = true;
        for (int i = 2; i < n; i+=2) {
     
            dp[0][i] = dp[0][i-2] && p.charAt(i-1)=='*';
        }

        //s的前i个字符
        //p的前j个字符
        for (int i = 1; i < m; i++) {
     
            for (int j = 1; j < n; j++) {
     
                if (p.charAt(j-1)=='*'){
     
                    dp[i][j] = dp[i][j-2] ||   //1.
                            dp[i-1][j] && s.charAt(i-1)==p.charAt(j-2) ||  //2.
                            dp[i-1][j] && p.charAt(j-2)=='.';  //3.
                }else {
     
                    dp[i][j] = dp[i-1][j-1] && s.charAt(i-1)==p.charAt(j-1) ||
                            dp[i-1][j-1] && p.charAt(j-1)=='.';

                }

            }
        }

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

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

题目
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、"-1E-16"、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、"±5"及"12e+5.4"都不是。
题解:

class Solution {
     
    public boolean isNumber(String s) {
     
        if (s == null || s.trim().length() == 0) return false;
        s= s.trim();
        try {
     
            Double.valueOf(s);
        } catch (Exception e) {
     
            return false;
        }
        char c = s.charAt(s.length() - 1);
        return (c >= '0' && c <= '9') || c == '.';
    }
}

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

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

示例:

输入:nums = [1,2,3,4]
输出:[1,3,2,4] 
注:[3,1,2,4] 也是正确的答案之一。
 

提示:

0 <= nums.length <= 50000
1 <= nums[i] <= 10000

题解:
双指针法。
指针left从左向右移动,遇到偶数停止。
指针right从右向左移动,遇到奇数停止。
交换两个指针位置的元素。
继续移动两个指针,直到相遇。

class Solution {
     
    public int[] exchange(int[] nums) {
     
        int left = 0;
        int right = nums.length-1;
        int temp = 0;
        while (left<right){
     
            while (nums[left]%2==1 && left<right){
     
                left++;
            }
            while (nums[right]%2==0 && left<right ){
     
                right--;
            }
            temp = nums[left];
            nums[left] = nums[right];
            nums[right] = temp;
            left++;
            right--;
        }
        return nums;
    }
}

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

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

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

示例:

给定一个链表: 1->2->3->4->5, 和 k = 2.

返回链表 4->5.

题解:
双指针法。
指针right先行。
走了k步后指针left开始遍历。两个指针速度一致,相距始终为k。
当right指针遍历完毕时,指针left到达倒数第k个节点

class Solution {
     
    public ListNode getKthFromEnd(ListNode head, int k) {
     
        ListNode left  = head;
        ListNode right = head;
        while (right!=null){
     
            right = right.next;
            k--;
            if (k<0){
     
                left = left.next;
            }
        }
        return left;
    }
}

剑指 Offer 24. 反转链表

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

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
 

限制:

0 <= 节点个数 <= 5000

题解:
分别使用pre,cur,pos保存连续的三个节点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
     
    public ListNode reverseList(ListNode head) {
     
        if (head==null||head.next==null)
            return head;
        ListNode pre = null;
        ListNode cur = head;
        ListNode pos = head.next;
        
        while (cur.next!=null){
     
            cur.next = pre;
            pre = cur;
            cur = pos;
            pos = pos.next;
        }
        cur.next = pre;
        return cur;
    }
}

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

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

示例1:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
限制:

0 <= 链表长度 <= 1000

题解:
使用head记录新链表的入口。
pos1 、pos2分别指向待比较的两个节点。
cur记录比较后接入的节点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
     
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
     
	    if (l1 ==null)
	    return l2;
	    if(l2 ==null)
	    return l1;
	    
        ListNode head = null;
        ListNode cur = new ListNode(0);
        ListNode pos1 = l1;
        ListNode pos2 = l2;
        
        while (pos1!=null && pos2!=null){
     
            if (pos1.val<=pos2.val){
     
                cur.next = pos1;
                cur = pos1;
                pos1 = pos1.next;
            }else {
     
                cur.next = pos2;
                cur = pos2;
                pos2 = pos2.next;
            }
            if (head==null){
     
                head = cur;
            }
        }
        if (pos1==null){
     
            cur.next = pos2;
        }
        if (pos2 ==null){
     
            cur.next = pos1;
        }
        return head;
    }
}

剑指 Offer 26. 树的子结构

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

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

例如:

给定的树 A:

     3
    / \
   4   5
  / \
 1   2
给定的树 B:

   4 
  /
 1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。

示例 1:

输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:

输入:A = [3,4,5,1,2], B = [4,1]
输出:true
限制:

0 <= 节点个数 <= 10000

题解:
思路:
若树B是树A的子结构,则子结构的根节点可能为树A的任意一个节点。因此,判断树B是树A的子结构,需要完成以下两步工作:

  1. 先序遍历树A中的每个节点nA;(对应函数 isSubStructure(A,B)
  2. 判断树A中以nA为根节点的子树是否包含树B。(对应函数:traceBack(A, B)

解题步骤

  1. isSubStructure(A,B)函数
    特例处理:当树A为空 或 树B为空时,直接返回false;
    返回值:若树B是树A的子结构,则必须满足以下三种情况之一,因此用 || 连接。
    ①以节点A为根节点的子树包含树B,对应traceBack(A,B)
    ②树B是树A左子树的子结构,对应isSubStructure(A.left,B)
    ③树B是树A右子树的子结构,对应isSubStructure(A.right,B)

  2. traceBack(A, B)函数
    终止条件
    ①当节点B为空:B已完成匹配,返回true;
    ②当节点A为空:已经越过A的节点,匹配失败,返回false;
    ③当节点A和B的值不同:说明匹配失败,返回false;
    返回值
    ①判断A和B的左子节点是否相等,即traceBack(A.left,B.left);
    ②判断A和B的右子节点是否相等,即traceBack(A.right,B.right);

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
     
    public boolean isSubStructure(TreeNode A, TreeNode B) {
     
        if (A==null || B==null)
            return false;

        if (A.val==B.val &&traceBack(A.left, B.left) && traceBack(A.right, B.right))
            return true;

        //1.递归寻找根节点,有一次递归结果为true则表示包含
        return isSubStructure(A.left, B)||isSubStructure(A.right, B);
    }

    public Boolean traceBack(TreeNode A, TreeNode B) {
     
        if (B==null)
            return true;
        if (A==null || A.val!=B.val)
            return false;
        //A.val == B.val 则继续递归
        return traceBack(A.left, B.left) && traceBack(A.right, B.right);
    }
}

剑指 Offer 27. 二叉树的镜像

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

例如输入:

     4
   /   \
  2     7
 / \   / \
1   3 6   9
镜像输出:

     4
   /   \
  7     2
 / \   / \
9   6 3   1

 

示例 1:

输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
 

限制:

0 <= 节点个数 <= 1000

题解:
从根节点开始,递归交换左右节点即可。
结束条件:节点为空。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
     
    public TreeNode mirrorTree(TreeNode root) {
     
        TreeNode head = root;
        reverse(head);
        return root;
    }
    
    public void reverse(TreeNode node){
     
        if (node==null)
            return;
        TreeNode temp = node.left;
        node.left = node.right;
        node.right = temp;
        reverse(node.left);
        reverse(node.right);
    }
}

剑指 Offer 28. 对称的二叉树

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

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

    1
   / \
  2   2
 / \ / \
3  4 4  3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:

    1
   / \
  2   2
   \   \
   3    3

 

示例 1:

输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:

输入:root = [1,2,2,null,3,null,3]
输出:false

题解:
思路
对称二叉树的定义:
对于头节点head有:

  1. head.left.val==head.right.val:左右节点的值相等;
  2. head.left.left.val==head.right.right.val:外层节点值相等;
  3. head.left.right.val==head.right.left.val:内层节点值相等;
					   head
					   / \
					left right
					 / \ / \
					l  r l  r

从头节点开始,从上到下开始递归,判断每对节点是否对称,从而判断是否为对称二叉树。

流程
isSymmetric:

  • 特例:root为空返回true;
  • 返回值:compare(root.left,head.right):子树的比较结果;
    compare:
  • 终止条件:
    1. 当 left 和 right 都为 null 时,返回 true;
    2. 当 left 和 right 只有其一为 null 时,返回 false;
    3. 当left 和 right 的 val 不相等时,返回 false;
  • 递推工作:
    1. 外层节点是否相等:compare(left.left, right.right)
    2. 内层节点是否相等:compare(left.right, right.left)
  • 返回值:内层节点相等&& 外层节点相等
/**
 * 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 compare(root.left, root.right);
    }
    public boolean compare(TreeNode left,TreeNode right){
     
        if (left==right)
            return true;
        if (left==null || right==null||left.val!=right.val)
            return false;
        return compare(left.left, right.right)&&compare(left.right, right.left);
    }
}

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

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

示例 1:

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:

输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
 

限制:

0 <= matrix.length <= 100
0 <= matrix[i].length <= 100

题解:
leecode剑指offer集合及题解(持续更新。。)_第10张图片

class Solution {
     
    public int[] spiralOrder(int[][] matrix) {
     
      if (matrix.length==0)
            return new int[0];
        int x = 0;
        int l = 0;
        int t = 0;
        int r = matrix[0].length-1;
        int b = matrix.length-1;
        int[] res = new int[matrix.length*matrix[0].length];
  
        while (true){
     
            for (int i = l; i <= r; i++) {
     
                res[x++] = matrix[t][i];
            }
            if (++t>b)break;
            for (int i = t; i <= b; i++) {
     
                res[x++] = matrix[i][r];
            }
            if (--r<l)break;
            for (int i = r; i >= l; i--) {
     
                res[x++] = matrix[b][i];
            }
            if (--b<t)break;
            for (int i = b; i >=t ; i--) {
     
                res[x++] = matrix[i][l];
            }
            if (++l>r)break;
        }
        return res;
    }
}

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

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

示例:

MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.min();   --> 返回 -2.

题解:
与题目:剑指 Offer 59 - II. 队列的最大值 非常相似。难点都在于如何保存当前结构中的最值。
队列使用 双端队列 保存最值。
本题目使用 保存最值

class MinStack {
     
    Stack<Integer> stack;
    Stack<Integer> minStack;

    //构造函数
    public MinStack() {
     
        stack = new Stack<>();
        minStack = new Stack<>();
    }

    public void push(int x) {
     
        stack.push(x);
        if (minStack.isEmpty()) {
     
            minStack.push(x);
        } else if (x <= minStack.peek()) {
     
            minStack.push(x);
        }
    }

    public void pop() {
     
        Integer pop = stack.pop();
        if (pop.equals(minStack.peek()))
            minStack.pop();
    }

    public int top() {
     
        return stack.peek();
    }

    public int min() {
     
        return minStack.peek();
    }
}

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

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

示例 1:

输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:

输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
 

提示:

0 <= pushed.length == popped.length <= 1000
0 <= pushed[i], popped[i] < 1000
pushed 是 popped 的排列。

题解:
考虑借用一个辅助栈 stack,模拟 压入 / 弹出操作的排列。根据是否模拟成功,即可得到结果。
入栈操作: 按照压栈序列的顺序执行。
出栈操作: 每次入栈后,循环判断 “栈顶元素 == 弹出序列的当前元素” 是否成立,将符合弹出序列顺序的栈顶元素全部弹出。

class Solution {
     
    public boolean validateStackSequences(int[] pushed, int[] popped) {
     
        int i = 0, j = 0;
        Stack<Integer> stack = new Stack<>();
        while (i <= pushed.length - 1) {
     
        //1.按入栈顺序压入
                stack.push(pushed[i++]);
                //2.循环判断栈顶元素是否和出栈元素一致,一致则出栈
            while (!stack.isEmpty() && stack.peek() == popped[j]) {
     
                stack.pop();
                j++;
            }
        }
        //3.全部出栈则模拟成功,否则失败。
        return stack.isEmpty();
    }
}

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

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

例如:
给定二叉树: [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
返回:

[3,9,20,15,7]
 

提示:

节点总数 <= 1000

题解:
题目要求的二叉树的 从上至下 打印(即按层打印),又称为二叉树的 广度优先搜索(BFS)。
BFS 通常借助 队列 的先入先出特性来实现(从上到下,从左到右)。
BFS 循环: 当队列 queue 为空时跳出;

/**
 * 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) {
     
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        ArrayList<Integer> list = new ArrayList<>();
        while (!queue.isEmpty()){
     
            TreeNode node = queue.poll();
            if (node!=null){
     
                list.add(node.val);
                queue.offer(node.left);
                queue.offer(node.right);
            }
        }
        int[] res = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
     
            res[i] = list.get(i);
        }
        return res;
    }
}

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

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

例如:
给定二叉树: [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
返回其层次遍历结果:

[
  [3],
  [9,20],
  [15,7]
]
 

题解:
当前层打印循环: 循环次数为当前层节点数(即队列 queue 长度);

/**
 * 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) {
     
        Queue<TreeNode> queue = new LinkedList<>();
        if (root!=null)
        queue.add(root);
        List<List<Integer>> list = new ArrayList<>();

        while (!queue.isEmpty()) {
     
            ArrayList<Integer> list1 = new ArrayList<>();
            for (int i = queue.size(); i > 0; i--) {
     
                TreeNode node = queue.poll();
                if(node.left != null) queue.add(node.left);
                if(node.right != null) queue.add(node.right);
                list1.add(node.val);
            }
            list.add(list1);
        }
        return list;
    }
}

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

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

例如:
给定二叉树: [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
返回其层次遍历结果:

[
  [3],
  [20,9],
  [15,7]
]

题解:
树的层序遍历借助queue即可。
本题的难点在于:
①每层节点数控制;
②之字型插入顺序;

/**
 * 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) {
     
        ArrayList<List<Integer>> res = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        if (root!=null)queue.add(root);
        while (!queue.isEmpty()){
     
            LinkedList<Integer> tmp = new LinkedList<>();
            //①:这个for循环设置为倒遍历,是因为  queue.size() 会随着 queue.poll()变化。只有第一次的值可以反映树这一层的节点个数。 如果放在结束条件,i
            for (int i = queue.size(); i >0 ; i--) {
     
                TreeNode node = queue.poll();
                //②:在层序遍历的基础上改变了添加方式。如果是偶数层(从1开始)则倒着插,实现倒叙
                if (res.size()%2==0)tmp.addLast(node.val);
                //奇数层,则正着插
                else tmp.addFirst(node.val);
                if (node.left!=null)queue.add(node.left);
                if (node.right!=null)queue.add(node.right);
            }
            res.add(tmp);
        }
        return res;
    }
}

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

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

参考以下这颗二叉搜索树:

     5
    / \
   2   6
  / \
 1   3
示例 1:

输入: [1,6,3,2,5]
输出: false
示例 2:

输入: [1,3,2,6,5]
输出: true
 

提示:

数组长度 <= 1000

题解:
后序遍历定义: [ 左子树 | 右子树 | 根节点 ] ,即遍历顺序为 “左、右、根” 。
二叉搜索树定义: 左子树中所有节点的值 << 根节点的值;右子树中所有节点的值 >> 根节点的值;其左、右子树也分别为二叉搜索树。
分治思想:
终止条件: 当 left>=right,说明此子树节点数量 ≤1 ,无需判别正确性,因此直接返回 true;
迭代过程:

  1. 索引 p 从子数组的 left 起始位置向右移动。
  2. 先遍历左子树节点(左子树中所有节点的值 << 根节点的值),再遍历右子树节点(右子树中所有节点的值 >> 根节点的值)。遍历完成后如果到达根节点 right,则返回 true。
  3. 递归左子树右子树
class Solution {
     
    public boolean verifyPostorder(int[] postorder) {
     
        return recur(postorder, 0, postorder.length - 1);
    }
    public boolean recur(int[] postorder, int left ,int right){
     
        if (left>=right)
            return true;
        int p = left;
        while (postorder[p]<postorder[right]){
     
            p++;
        }
        int middle = p;
        while (postorder[p]>postorder[right]){
     
            p++;
        }
        return p== right && recur(postorder, left, middle-1)
                && recur(postorder, middle, right-1);
    }
}

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

题目:
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。

示例:
给定如下二叉树,以及目标和 target = 22,

              5
             / \
            4   8
           /   / \
          11  13  4
         /  \    / \
        7    2  5   1
返回:

[
   [5,4,11,2],
   [5,8,4,5]
]

题解:
本问题是典型的二叉树方案搜索问题,使用回溯法解决,其包括 先序遍历+路径记录
先序遍历:按照“根、左、右”的顺序,遍历所有节点。
路径记录:在先序遍历中,记录从根节点到当前节点的路径。当路径为①根节点到叶节点形成的路径②各节点值的和等于目标值target。则将此路径加入结果列表。

递归工作:

  1. 路径更新:将当前节点的值 root.val 加入路径 path
  2. 目标值更新:target -= root.val
  3. 路径记录:
  4. 先序遍历:
  5. 路径恢复:向上回溯前,需将当前节点从path中删除。 path.removeLast();
class Solution {
     
    LinkedList<List<Integer>> res = new LinkedList<>();
    LinkedList<Integer> path = new LinkedList<>();

    public List<List<Integer>> pathSum(TreeNode root, int target) {
     
        traceBack(root, target);
        return res;
    }

    private void traceBack(TreeNode root, int target) {
     
        //情况1:已经达到最深处,则返回
        if (root==null)return;        //如果目标值减为0,且已达到树的最深处
        //情况2:没有达到最深处,则累计当前节点的值
        target-=root.val;
        path.add(root.val);

        //每次递归都判断是否已经满足题目条件
        if (target == 0 && root.left == null && root.right == null) {
     
            List<Integer> tmp = new LinkedList<>(path);
            res.add(tmp);
        }
        
        //递归过程
        traceBack(root.left, target);
        traceBack(root.right, target);
        
        //-----上面是递归,下面是回溯
        
        //能执行到这,说明开始回溯。回溯要先将当前节点的值去除
        path.removeLast();
    }
}

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

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

示例 1:
leecode剑指offer集合及题解(持续更新。。)_第11张图片
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
leecode剑指offer集合及题解(持续更新。。)_第12张图片
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:
leecode剑指offer集合及题解(持续更新。。)_第13张图片
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
示例 4:

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

提示:

-10000 <= Node.val <= 10000
Node.random 为空(null)或指向链表中的节点。
节点数目不超过 1000 。

题解:
利用哈希表的查询特点,考虑构建 原链表节点 和 新链表对应节点 的键值对映射关系,再遍历构建新链表各节点的 next 和 random 引用指向即可。

/*
// 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;
    }
}
*/
class Solution {
     
    public Node copyRandomList(Node head) {
     
        if (head == null)
            return head;
        Node tmp = head;
        HashMap<Node, Node> map = new HashMap<>();
        while (tmp!=null){
     
            map.put(tmp,new Node(tmp.val));
            tmp = tmp.next;
        }
        
        tmp = head;
        while (tmp!=null){
     
            map.get(tmp).next = map.get(tmp.next);
            map.get(tmp).random = map.get(tmp.random);
            tmp = tmp.next;
        }
        return map.get(head);
    }
}

剑指 Offer 36. 二叉搜索树与双向链表

题目:
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。

为了让您更好地理解问题,以下面的二叉搜索树为例:
leecode剑指offer集合及题解(持续更新。。)_第14张图片
我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。

下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。
leecode剑指offer集合及题解(持续更新。。)_第15张图片
特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。
题解:
使用二叉树的中序遍历访问树的各个节点 cur 。并在访问过程中构建 cur 和前驱节点 pre 的引用指向;中序遍历完成后,最后构建头节点 和 尾节点的引用指向即可。
递归流程
leecode剑指offer集合及题解(持续更新。。)_第16张图片

class Solution {
     
    Node head;
    Node pre;
    public Node treeToDoublyList(Node root) {
     
        if (root==null)return null;
        dfs(root);
        head.left=pre;
        pre.right=head;
        return head;
    }

    private void dfs(Node cur) {
     
        if (cur==null)return;
        dfs(cur.left);
        if (pre!=null){
     
            pre.right=cur;
        }else {
     
            head = cur;   
        }
        cur.left=pre;
        pre= cur;
        dfs(cur.right);
    }
}

剑指 Offer 37. 序列化二叉树

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

示例: 
你可以将以下二叉树:

    1
   / \
  2   3
     / \
    4   5

序列化为 "[1,2,3,null,null,4,5]"

题解:
序列化:

  • 借助队列,对二叉树做层序遍历。循环判断条件是队列queue不为空。

反序列化:

  • 思路和序列化一致,只不过是判断条件变为 字符串数组遍历是否结束index
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {
     

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
     
        if (root == null)
            return null;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        ArrayList<String> res = new ArrayList<>();
        while (!queue.isEmpty()) {
     
            TreeNode node = queue.poll();
            if (node!=null){
     
                res.add(String.valueOf(node.val));
                queue.offer(node.left);
                queue.offer(node.right);
            }else {
     
                res.add("null");
            }
        }
        return res.toString();
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
     
        if(data==null){
     
            return null;
        }
        String[] arr = data.substring(1, data.length() - 1).split(",");
        Queue<TreeNode> queue = new LinkedList<>();
        TreeNode head = new TreeNode(Integer.parseInt(arr[0]));
        queue.offer(head);
        int index = 1;
        while (index<arr.length) {
     
            TreeNode tmp = queue.poll();
            String l = arr[index++];
            if (!l.trim().equals("null")) {
     
                tmp.left = new TreeNode(Integer.parseInt(l.trim()));
                queue.offer(tmp.left);
            }
            String r = arr[index++];
            if (!r.trim().equals("null")) {
     
                tmp.right = new TreeNode(Integer.parseInt(r.trim()));
                queue.offer(tmp.right);
            }
        }
        return head;
    }
}

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

剑指 Offer 38. 字符串的排列

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

示例:
输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]

你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
题解:
对于"abc":(-表示待插入位置)
首先固定“a”:在“-a-”分别插入“b”得到[“ba”,“ab”]
读取[“ba”,“ab”]固定其中元素:在 ["-b-a-","-a-b-"]分别插入“c”,得到[“cba”,“bca”,“bac”, “cab”,“acb”,“abc”]

class Solution {
     
    public String[] permutation(String s) {
     
        if (s == null || s.equals(""))
            return new String[0];
        char[] chars = s.toCharArray();
        //用于存储中间结果:比如["a","ba","ab","cba","bca","bac",  "cab","acb","abc"]
        ArrayList<String> res = new ArrayList<>();

        res.add(String.valueOf(chars[0]));
               //i:待插入的下一个元素索引
        for (int i = 1; i < chars.length; i++) {
     
        		//j:用于遍历res中间结果 
            for (int j = res.size()-1; j >= 0; j--) {
     
            		//k:用于遍历 "-" 待插入位置。
                for (int k = 0; k < i+1; k++) {
     
                    StringBuilder stringBuilder = new StringBuilder();
                    String s1 = res.get(j);
                    if (s1.length()==i){
     
                        stringBuilder.append(s1).insert(k, chars[i]);
                        res.add(stringBuilder.toString());
                    }
                }
            }
        }
		//根据长度过滤最终结果,同时去重
        List<String> list = res.stream().filter(e -> e.length() == s.length()).distinct().collect(Collectors.toList());
        String[] strings = new String[list.size()];
        for (int i = 0; i < list.size(); i++) {
     
            strings[i] = list.get(i);
        }
        return strings;
    }
}

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

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

示例 1:

输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2

你可以假设数组是非空的,并且给定的数组总是存在多数元素
题解:
哈希表统计法: 遍历数组 nums ,用 HashMap 统计各数字的数量,即可找出 众数 。此方法时间和空间复杂度均为 O(N) 。

class Solution {
     
    public int majorityElement(int[] nums) {
     
        HashMap<Integer, Integer> map = new HashMap<>();

        for (int i = 0; i < nums.length; i++) {
     
            if (map.get(nums[i])==null){
     
                map.put(nums[i], 1);
            }else {
     
                map.put(nums[i], map.get(nums[i])+1);
            }
        }
        Integer integer = map.values().stream().max(Integer::compareTo).get();
        for (Integer integer1 : map.keySet()) {
     
            if (integer==map.get(integer1))
                return integer1;
        }
        return 0;
    }
}

剑指 Offer 40. 最小的k个数

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

示例 1:

输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:

输入:arr = [0,1,2,1], k = 1
输出:[0]

题解:

class Solution {
     
    public int[] getLeastNumbers(int[] arr, int k) {
     
        ArrayList<Integer> list = new ArrayList<>();
        for (int j : arr) {
     
            list.add(j);
        }
        Collections.sort(list);
        int[] res = new int[k];
        for (int i = 0; i < k; i++) {
     
            res[i] = list.get(i);
        }
        return res;
    }
}

剑指 Offer 41. 数据流中的中位数

题目:
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

例如,

  • [2,3,4] 的中位数是 3

  • [2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

  • void addNum(int num) - 从数据流中添加一个整数到数据结构中。
  • double findMedian() - 返回目前所有元素的中位数。
示例 1:

输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]
示例 2:

输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]

题解:
优先队列PriorityQueue的使用。
PriorityQueue与先进先出队列Queue的不同:

  • (出队/获取队首元素)的规则:按优先级出队列(默认最大值),也选择可以自定义Comparator
    在这里插入图片描述
    流程:
  • A保存值较大的一半元素。(小顶堆 A:最小值出队列,剩下的都是大值)
  • B保存值较小的一半元素。(大顶堆 B:最大值出队列,剩下的都是小值)
  • 当元素个数N为奇数时:A=B+1=(N+1)/2,新元素入堆流程:
    • 将新元素入堆A;
    • A poll 出最小值到堆 B (过滤作用,保证入B的元素小于中位数)
  • 当元素个数N为偶数时:A=B=N/2,新元素入堆流程:
    • 将新元素入堆B;
    • B poll 出最大值到堆 A(过滤作用,保证入A的元素大于中位数)
  • 返回值:
    • 如果A.size() == B.size(), 返回 (A.peek()+B.peek()) / 2
    • 如果A.size()!=B.size(),返回 A.peek()
class MedianFinder {
     
    Queue<Integer> A, B;
    public MedianFinder() {
     
        A = new PriorityQueue<Integer>();
        B = new PriorityQueue<Integer>((x, y) -> (y - x));
    }

    public void addNum(int num) {
     
        if(A.size() != B.size()) {
     
            A.add(num);
            B.add(A.poll());
        } else {
     
            B.add(num);
            A.add(B.poll());
        }
    }

    public double findMedian() {
     
        return A.size()==B.size()?((A.peek()+ B.peek())/2.0):A.peek();
    }
}

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

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

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

示例1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

题解:
动态规划。

  1. 状态定义: 设动态规划列表 dp,dp[i] 代表以元素 nums[i] 为结尾的连续子数组最大和。
    为何定义最大和 dp[i]中必须包含元素 nums[i] :保证 dp[i] 递推到 dp[i+1] 的正确性;如果不包含 nums[i] ,递推时则不满足题目的 连续子数组 要求.

  2. 转移方程: 若 dp[i-1]<=0,说明dp[i−1]dp[i] 产生负贡献,即 dp[i-1] + nums[i] 还不如 nums[i] 本身大。

dp[i - 1] > 0时:执行 dp[i] = dp[i-1] + nums[i]
dp[i−1]≤0时:执行 dp[i] = nums[i]

  1. 初始状态: dp[0] = nums[0],即以 nums[0]结尾的连续子数组最大和为nums[0]
  2. 返回值: 返回 dp列表中的最大值,代表全局最大值。
解法1public int maxSubArray(int[] nums) {
     
        int res = nums[0];
        for (int i = 1; i < nums.length; i++) {
     
            nums[i] += Math.max(nums[i-1],0);
            res = Math.max(res, nums[i]);
        }
        return res;
    }
解法2public int maxSubArray(int[] nums) {
     
        if (nums==null || nums.length==0)
            return 0;
        if (nums.length==1)
            return nums[0];
        //有正数时,返回res
        int res = 0;
        //临时值:单个或连续元素的值
        int tmp = 0;
        //都是负数时,记录最小的一个
        int special=nums[0];
        for (int num : nums) {
     
            tmp += num;
            //如果为负数,会起反作用,直接抛弃掉
            if (tmp <= 0) {
     
                tmp = 0;
            }
            //记录最大值(单个、连续)
            res = Math.max(tmp, res);
            //记录(单个)
            special = Math.max(special, num);
        }
        //全是负数时,special即为最大值
        return res!=0?res:special;
    }

剑指 Offer 43. 1~n 整数中 1 出现的次数

题目:
输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。

例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。

示例 1:

输入:n = 12
输出:5
示例 2:

输入:n = 13
输出:6

限制:
1 <= n < 2^31

题解:
leecode剑指offer集合及题解(持续更新。。)_第17张图片

class Solution {
     
    public int countDigitOne(int n) {
     
        int res =0,digit=1;
        int high=n/10,cur=n%10,low=0;
        while (high != 0 || cur!=0){
     
            if (cur==0){
     
                res+=high*digit;
            }else if (cur==1){
     
                res+=(high*digit+low+1);
            }else {
     
                res+=(high+1)*digit;
            }
            low += cur*digit;
            cur = high%10;
            high/=10;
            digit*=10;
        }
        return res;
    }
}

剑指 Offer 44. 数字序列中某一位的数字

题目:
数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。

请写一个函数,求任意第n位对应的数字。

示例 1:

输入:n = 3
输出:3
示例 2:

输入:n = 11
输出:0
 

限制:

0 <= n < 2^31

题解:
leecode剑指offer集合及题解(持续更新。。)_第18张图片

class Solution {
     
    public int findNthDigit(int n) {
     
        int digit = 1;
        long start = 1;
        long count = 9;
        while (n>count){
     
            n-=count;
            digit+=1;
            start*=10;
            count = 9*start*digit;
        }
        long num = start+(n-1)/digit;
        return String.valueOf(num).charAt((n-1)%digit)-'0';
    }
}

剑指 Offer 45. 把数组排成最小的数

题目:
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

示例 1:

输入: [10,2]
输出: "102"
示例 2:

输入: [3,30,34,5,9]
输出: "3033459"

题解:
内置函数:list.sort((x, y) -> (x + y).compareTo(y + x));的使用。

class Solution {
     
    public String minNumber(int[] nums) {
     
        ArrayList<String> list = new ArrayList<>();
        for (int num : nums) {
     
            list.add(String.valueOf(num));
        }
        list.sort((x, y) -> (x + y).compareTo(y + x));
        StringBuilder res = new StringBuilder();
        for (String s : list) {
     
            res.append(s);
        }
        return res.toString();
    }
}

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

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

示例 1:

输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"

题解:
动态规划:
leecode剑指offer集合及题解(持续更新。。)_第19张图片

class Solution {
     
    public int translateNum(int num) {
     
        String s = String.valueOf(num);
        //无数字
        int b = 1;
        //第一位数字
        int a = 1;
        for(int i = 2; i <= s.length(); i++) {
     
            String tmp = s.substring(i - 2, i);
            int c = tmp.compareTo("10") >= 0 && tmp.compareTo("25") <= 0 ? a + b : a;
            b = a;
            a = c;
        }
        return a;
    }
}


class Solution {
     
    public int translateNum(int num) {
     
        int[] ints = new int[String.valueOf(num).length()];
        ints[0] = num/(int) Math.pow(10,String.valueOf(num).length()-1);
        for (int i = 1; i <String.valueOf(num).length(); i++) {
     
            ints[i] = num/(int) Math.pow(10,String.valueOf(num).length()-(i+1))%10;
        }
        if (ints.length<=1)
            return ints.length;
        int[] dp = new int[ints.length];
        dp[0] = 1;
        if (ints[0]==1 || (ints[0]==2 && ints [1]<=5)){
     
            dp[1] = 2;
        }else {
     
            dp[1] = 1;
        }
        int index = 2;
        while (index<ints.length){
     
            if (ints[index-1]==1 || (ints[index-1]==2 && ints [index]<=5)){
     
                dp[index] = dp[index-1]+dp[index-2];
            }else {
     
                dp[index] = dp[index-1];
            }
            index++;
        }
        return dp[dp.length-1];
    }
}

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

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

示例 1:

输入: 
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物

题解:
leecode剑指offer集合及题解(持续更新。。)_第20张图片

class Solution {
     
    public int maxValue(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];
        }
        //状态转移方程: dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1])+grid[i][j];
        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:

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

题解:
从左向右遍历字符串s,遇到相同的元素时,去除左边的重复元素它以前的所有元素(多次执行else分支)。

class Solution {
     
    public int lengthOfLongestSubstring(String s) {
     
        if (s==null || s.length()==0)
            return 0;
        //临时保存最长的字符串。
        StringBuilder builder = new StringBuilder();
      	//记录最大长度
        int res = 0;
		//遍历s
        int i = 0;
        while (i<s.length()){
     
        	//如果没有重复元素存在,就不断添加
            if (builder.indexOf(String.valueOf(s.charAt(i)))==-1){
     
                builder.append(s.charAt(i));
                res = Math.max(res, builder.length());
                i++;
            }else {
     
            //注意,这里并没有i++;因此下次循环还是判断 s.charAt(i) 有没有在builder 重复存在。
            //举例:“abcfbe” 元素b重复,在遍历到“abcf”准备添加“b”时,则需要截取“cf”。else会被执行两次,每次删除最左边的元素。第一次删除“a”,第二次删除“b”。
                builder = new StringBuilder(builder.substring(1, builder.length()));
            }
        }
        return res;
    }
}

剑指 Offer 49. 丑数

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

示例:

输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:  

1 是丑数。
n 不超过1690。

题解:
leecode剑指offer集合及题解(持续更新。。)_第21张图片

class Solution {
     
    public int nthUglyNumber(int n) {
     
        int[] dp = new int[n];
        dp[0] = 1;
        int a=0,b=0,c=0,i=1;
        while (i<n){
     
            dp[i] = Math.min(dp[a]*2,Math.min(dp[b]*3,dp[c]*5));
            if (dp[i]==dp[a]*2)
                a++;
            if (dp[i]==dp[b]*3)
                b++;
            if (dp[i]==dp[c]*5)
                c++;
            i++;
        }
        return dp[n-1];
    }
}

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

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

示例:

s = "abaccdeff"
返回 "b"

s = "" 
返回 " "

题解:

class Solution {
     
    public char firstUniqChar(String s) {
     
        if (s==null || s.equals(" "))
            return ' ';
        for (int i = 0; i < s.length(); i++) {
     
            if (s.indexOf(s.charAt(i))==s.lastIndexOf(s.charAt(i)))
                return s.charAt(i);
        }
        return ' ';
    }
}

剑指 Offer 51. 数组中的逆序对

题目:
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

示例 1:

输入: [7,5,6,4]
输出: 5

题解:
分治思想+归并排序传送门。

  1. 以数组 {1,3,5,2,4,6}为例。
	将数组分成两部分:						① 3 5 | ② 4 6          -->   {}
	对两部分进行合并的过程中计算逆序对的数量 count。
	
	①<②则将①放入新数组: 				      ③ 5 | ② 4 6          -->{1}
	1 没有和任何元素构成逆序对。

	③>②则将②放入新数组: 			          ③ 5 | ④ 6          -->{1,2}
	2和{3,5}构成逆序对,count = 2;

	③<④则将③放入新数组:					    ⑤ | ④ 6          -->{1,2,3}
	3 没有和任何元素构成逆序对。

	⑤>④则将④放入新数组:				        ⑤ | ⑥           -->{1,2,3,4}
	4和{5}构成逆序对, count=count+1=3;

	同理⑤、⑥加入新数组,没有增加逆序对。   		  |  		    -->{1,2,3,4,5,6}

总结:如果可以将一个数组分成两个有序数组。在将两个子数组合并的过程中,逆序对儿的计算方式为:

   if(left>right){
     
   			count += 左边数组剩余元素数量
   }

但是,如何将一个任意数组分成有序的子数组呢。需要使用到归并排序中的分解归并
2. 「归并排序」是分治思想的典型应用,它包含这样三个步骤:
- 分解:待排序的区间为 [l, r],令 m = (l+r)/2,我们把 [l, r] 分成 [l, m] 和 [m + 1, r]
- 解决:使用归并排序递归地排序两个子序列
- 合并: 把两个已经排好序的子序列 [l, m] 和 [m + 1, r] 合并起来
3. 由此可以看到,归并排序会把任意数组分解成只包含一个元素的子数组,而单个元素必然是有序数组。归并的过程则是同步骤1所示,计算所有逆序对的数量。

class Solution {
     
    public int reversePairs(int[] nums) {
     
        int length = nums.length;
        if (length<2)
            return 0;
        int[] tmp = new int[length];
        return reversePairs(nums,0,length-1,tmp);
    }
	//分解过程
    public int reversePairs(int[] nums,int left,int right,int[] tmp){
     
    	//分解结束条件:左右边界相遇(即只有一个元素)
        if (left==right)
            return 0;
        //计算中点
        int mid = left + (right-left)/2;
        //向左递归分解
        int leftPairs = reversePairs(nums,left,mid,tmp);
        //向右递归分解
        int rightPairs = reversePairs(nums,mid+1,right,tmp);
        //分解结束后开始合并
        int crossPairs = crossPairs(nums,left,mid,right,tmp);
        //返回所有合并过程中计算的逆序对数量
        return leftPairs+rightPairs+crossPairs;
    }
	//合并
    public int crossPairs(int[] nums,int left,int mid,int right,int[] tmp){
     
    	//复制一个临时数组
        for (int i = left; i < right+1; i++) {
     
            tmp[i] = nums[i];
        }
        int i=left,j=mid+1,count=0;
        for (int k = left; k < right+1; k++) {
     
        	//左边数组遍历完,将右边剩余的元素直接添加
            if (i==mid+1){
     
                nums[k]=tmp[j];
                j++;
             //右边数组遍历完,将左边剩余的元素直接添加
            }else if (j==right+1){
     
                nums[k]=tmp[i];
                i++;
             //左边的元素小,直接添加,不会有逆序对产生
            }else if (tmp[i]<=tmp[j]){
     
                nums[k]=tmp[i];
                i++;
            }else {
     
            //右边元素小,逆序对的数量等于左边数组的剩余元素数量
                nums[k]=tmp[j];
                j++;
                count+=(mid-i+1);
            }
        }
        return count;
    }
}

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

题目:
输入两个链表,找出它们的第一个公共节点。
注意:

如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
题解:

/**
 * 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) {
     
        ArrayList<ListNode> listNodes = new ArrayList<>();
        while (headA!=null){
     
            listNodes.add(headA);
            headA=headA.next;
        }
        while (headB!=null){
     
            if (listNodes.contains(headB)){
     
                return headB;
            }
            headB=headB.next;
        }
        return null;
    }
}

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

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

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:

输入: nums = [5,7,7,8,8,10], target = 6
输出: 0

题解:

class Solution {
     
    public int search(int[] nums, int target) {
     
        int i =0;
        while (i<nums.length && nums[i]!=target){
     
            i++;
        }
        int j =i;
        while (j<nums.length && nums[j]==target){
     
            j++;
        }
        return j-i;
    }
}

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

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

示例 1:
输入: [0,1,3]
输出: 2
示例 2:

输入: [0,1,2,3,4,5,6,7,9]
输出: 8
class Solution {
     
    public int missingNumber(int[] nums) {
     
        for (int i = 0; i < nums.length; i++) {
     
            if (i!=nums[i])
                return i;
        }
        return nums.length;
    }
}

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

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

示例 1:

输入: root = [3,1,4,null,2], k = 1
   3
  / \
 1   4
  \
   2
输出: 4
示例 2:

输入: root = [5,3,6,2,4,null,null,1], k = 3
       5
      / \
     3   6
    / \
   2   4
  /
 1
输出: 4

题解:
搜索二叉树的中序遍历,遍历结果是有序的。

/**
 * 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) {
     
        ArrayList<Integer> list = new ArrayList<>();
        traceBack(list,root);
        return list.get(list.size()-k);
    }
    
    
    public void traceBack(List<Integer> list,TreeNode node){
     
        if (node==null)
            return;
        traceBack(list, node.left);
        list.add(node.val);
        traceBack(list, node.right);
    }
}

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

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

给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

返回它的最大深度 3 。
题解:
方法一:
后续遍历法。
leecode剑指offer集合及题解(持续更新。。)_第22张图片
此树的深度和其左(右)子树的深度之间的关系。显然,此树的深度 等于 左子树的深度右子树的深度中的 最大值 +1

方法二:
层序遍历法。

  • 树的层序遍历 / 广度优先搜索往往利用 队列 实现。
  • 关键点: 每遍历一层,则计数器 +1+1 ,直到遍历完成,则可得到树的深度。
class Solution {
     
//后续遍历法
    public int maxDepth(TreeNode root) {
     
        if (root==null)
            return 0;
        return Math.max(maxDepth(root.left), maxDepth(root.right))+1;
    }
    
//层序遍历法
    public int maxDepth(TreeNode root) {
     
        if (root==null)
            return 0;
        Queue<TreeNode> queue = new LinkedList<>();
        Queue<TreeNode> tmp ;
        queue.add(root);
        int res=0;
        while (!queue.isEmpty()){
     
            tmp = new LinkedList<>();
            for(TreeNode node : queue) {
     
                if(node.left != null) tmp.add(node.left);
                if(node.right != null) tmp.add(node.right);
            }
            queue=tmp;
            res++;
        }
        return res;
    }
}

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

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

示例 1:

给定二叉树 [3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7
返回 true 。

示例 2:

给定二叉树 [1,2,2,3,3,null,null,4,4]

       1
      / \
     2   2
    / \
   3   3
  / \
 4   4
返回 false 。

题解:
树深度的计算方法
此树的深度和其左(右)子树的深度之间的关系。显然,此树的深度 等于 左子树的深度右子树的深度中的 最大值 +1

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
     
    public boolean isBalanced(TreeNode root) {
     
        if (root==null)return true;
        return Math.abs(maxDep(root.left)-maxDep(root.right))<=1&&isBalanced(root.left)&&isBalanced(root.right);
    }
    //获取以当前节点为根节点的树的最大深度。
    public int maxDep(TreeNode node){
     
        if (node==null)
            return 0;
        return Math.max(maxDep(node.left),maxDep(node.right))+1;
    }
}

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

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

示例 1:

输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:

输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]

题解位异或运算
异或:二进制中,相同为0,不同为1;
异或的特点

  1. 满足交换律;
  2. 任何数异或自己都为0;
  3. 0与任何数异或,都为数本身;
  • 可以想到,如果一个整型数组nums中只有一个数字出现1次,其他数字都出现2次。则根据异或的三个特点可知,可以通过全员异或,最终得到只出现1次的那个数。举个例子:
[4,1,4,2,2]
因为异或满足交换律,所以执行异或前可以把数组可以变成
[4,4,2,2,1]
开始执行全员异或:
4 异或 4 = 0
0 异或 2 = 2
2 异或 2 = 0
0 异或 1 = 1
最终异或的结果 = 1 ,即为仅出现1次的数字。

本题中数组里面有两个仅出现1次的数字,如果直接使用上述全员异或的方法,最终只能得到这两个数异或的结果,二者分不开了,不能得到两个数本身。
解决方法

  • 我们有没有办法根据某个条件将这两个不同的数分开,然后再分别执行异或呢?
  • 既然是两个不同的数,那么在它们的二进制格式中,至少有一位是不同的(一个为0,另一个必然为1)。
  • 于是我们想到,先找到二进制中两个数必然有有区别的。拿它当作判断条件,将原数组中的元素进行分组。则这两个不同的数必然也会被分配到不同的子数组中。再对两个子数组分别进行全员异或,就可以直接得到这两个数本身了。
    再举个例子:
[4,1,4,6]
按照上面的步骤,先对整个数组进行一次全员异或:
4 异或 1 异或 4 异或 6 = 1 异或 6

1 的二进制: 001
6 的二进制: 110
1 异或 6 的结果: 111

寻找两个数二进制中必然不同的位,因为 0 异或 1 = 1,所以我们只需要在 (1 异或 6的结果)中从右向左找到一个为 1 的位即可(从右向左第 1 位)。

根据二进制从右向左第一位是否为 1 ,对原数组中的元素进行分组并全员异或:
组1(二进制从右向左第一位为1):1 = 1
组2(二进制从右向左第一位为0):4 异或 4 异或 6 = 6

最终得到结果:1、6
class Solution {
     
    public int[] singleNumbers(int[] nums) {
     
        int x = 0, y = 0, n = 0, m = 1;
        for(int num : nums)               // 1. 遍历异或
            n ^= num;
        while((n & m) == 0)               // 2. 循环左移,计算 m
            m <<= 1;
        for(int num: nums) {
                   // 3. 遍历 nums 分组
            if((num & m) != 0) x ^= num;  // 4. 当 num & m != 0
            else y ^= num;                // 4. 当 num & m == 0
        }
        return new int[] {
     x, y};          // 5. 返回出现一次的数字
    }
}

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

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

示例 1:

输入:nums = [3,4,3,3]
输出:4
示例 2:

输入:nums = [9,1,7,9,7,9,7]
输出:1

题解:
二进制与位运算;
举例:

[1,5,5,5]
数组中元素的二进制为:
1: 0 0 0 1
5: 0 1 0 1
5: 0 1 0 1
5: 0 1 0 1

如果将所有元素的二进制逐位求和:
	0 3 0 4
再逐位取余3:
	0 0 0 1     =     十进制的 1 
就可以得到仅出现 1 次的元素

时间复杂度为O(N),空间复杂度为O(1);

class Solution {
     
    public int singleNumber(int[] nums) {
     
    //Int型整数 最大为 2 的 32 次方,创建一个32长度的数组存储 位 的和。
        int[] ints = new int[32];
        for (int num : nums) {
     
            for (int i = 0; i < 32; i++) {
     
                ints[i]+=num&1;
                //无符号右移,再与1做与运算,可以获取逐 位 的值。
                num>>>=1;
            }
        }
        //如果将3改为其他的数字,可以解决只有一个1出现1次,其他数出现m次的情况,
        int res = 0 , m=3;
        for (int i = 0; i < 32; i++) {
     
        	//0的左移还是0;
            res<<=1;
            //从最高位开始,0与取余结果的 或运算 可以获取每 位 的值。同时配合左移,从高位依次赋值到低位。最终res就是返回结果。
            res |= ints[31-i] % m;
        }
        return res;
    }
}

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

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

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]
示例 2:

输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]

题解:
双指针法。将两个索引 leftright指向有序数组的首尾。

  1. 如果nums[left]+nums[right]==target:则找到;
  2. 如果nums[left]+nums[right]>target:说明和过大,nums[left]已经是小值了,需要减小nums[right]
  3. 如果nums[left]+nums[right]:说明和过小,nums[right]已经是大值了,需要增大nums[left]
class Solution {
     
    public int[] twoSum(int[] nums, int target) {
     
        int left = 0, right = nums.length-1;
        while (left<right){
     
            if (nums[left]+nums[right]==target){
     
                break;
            }else if (nums[left]+nums[right]>target){
     
                right--;
            }else {
     
                left++;
            }
        }
        return new int[]{
     nums[left],nums[right]};
    }
}

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

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

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

示例 1:

输入:target = 9
输出:[[2,3,4],[4,5]]
示例 2:

输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]

题解:
滑动窗口。
leecode剑指offer集合及题解(持续更新。。)_第23张图片
设窗口左边界left,有边界right,窗口内元素和res.

  1. res=target,则记录窗口内元素。
  2. res>target,说明窗口内元素和过大,需要减少元素,向右移动left
  3. res,说明窗口内元素和过小,需要增加元素,向右移动right
class Solution {
     
public int[][] findContinuousSequence(int target) {
     
        int left=1,right=1,sum=0;
        List<int[]> res = new ArrayList<>();
        while (left<=target/2){
     
            if (sum==target){
     
                int[] ints = new int[right - left];
                for (int i = 0; i < ints.length; i++) {
     
                    ints[i] = i+left;
                }
                res.add(ints);
                sum+=right++;
            }
            else if (sum<target){
     
                sum+=right++;
            }else{
     
                sum-=left++;
            }
        }

        return res.toArray(new int[res.size()][]);
    }
}

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

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

示例 1:

输入: "the sky is blue"
输出: "blue is sky the"
示例 2:

输入: "  hello world!  "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:

输入: "a good   example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
 

说明:

无空格字符构成一个单词。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

题解:

class Solution {
     
    public String reverseWords(String s) {
     
        if(s.trim().length()==0)
            return "";
        String[] strings = s.trim().split(" ");
        StringBuilder builder = new StringBuilder();
        for (int i = strings.length-1; i >=0; i--) {
     
                builder.append(strings[i].trim());
                if (strings[i].trim().length()>0){
     
                    builder.append(" ");
                }
        }
        return builder.substring(0, builder.length()-1);
    }
}

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

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

示例 1:

输入: s = "abcdefg", k = 2
输出: "cdefgab"
示例 2:

输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"

题解:

class Solution {
     
    public String reverseLeftWords(String s, int n) {
     
        return s.substring(n) + s.substring(0, n);
    }
}

剑指 Offer 59 - I. 滑动窗口的最大值

题目:
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:

  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

题解:
方法一:暴力解法
方法二:单调队列

//暴力解法
class Solution {
     
    public int[] maxSlidingWindow(int[] nums, int k) {
     
                if (nums.length<1)
            return new int[0];
        int left =0,right=left+k;
        ArrayList<Integer> list = new ArrayList<>();
        while (right<=nums.length){
     
            int[] ints = Arrays.copyOfRange(nums, left, right);
            int max = ints[0];
            for (int anInt : ints) {
     
                max=Math.max(max, anInt);
            }
            list.add(max);
            left++;
            right++;
        }

        int[] res = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
     
            res[i] = list.get(i);
        }
        return res;
    }
}

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

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

若队列为空,pop_front 和 max_value 需要返回 -1

示例 1:

输入: 
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例 2:

输入: 
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]

题解:

  • 普通队列的时间复杂度就是O(1),因此难点主要在于如何保证获取队列中的最大值的时间复杂度也是O(1)。如果在入队时比较每个元素大小,保留最大值,则在出队时如果最大元素出队,则最大值无法更新。

解决办法:用空间换时间,上述方法只用单个值保存了最大值导致更新失败。如果保存一个递减序列,如果最大值出队列,则由后续元素”顶上“,就可以解决最大元素出队列后无法更新的问题。
举个例子:

初始化一个queue存储队列元素,初始化一个deque保存元素中的最大值(递减排列)。
开始:
queue : 4
deuqe: 4
入队第一个元素,必然最大值也是4;

queue: 5、4
deque: 5
入队第二个元素5,此时最大元素应该是5;deque的操作如下:
	新来的5 和 deque中队尾的 4 比较,5大:4出队抛弃,5进队列(从队尾)。
	这步可以这么理解,queue进出满足”先入先出“的规则,5比4后来,所以在5离开之前,4必然已经出队了。那么在5离开之前,5一直都是最大值。那么deque也就不用再保存4了,因为有5在它后面出队,4永远没有机会再当最大值。

queue:3、5、4
deque:  5、3
入队第三个元素3,此时最大元素还是5;deque操作如下:
	新来的3 比 deque队尾的5小,因为deque递减保存。它只能跟在5的后面。
	这步可以这么理解,老大哥5比3来的早,队列先进先出,5出去以后,3还是有机会当最大值的。所以3必须入deque。

queue: 3、5
deque:5、3
出队队首元素4、此时最大值仍然是5;
queue:3
deque:3
这两步的deque操作如下:
	出队的元素跟deque队首(当前最大值)比较,看看是不是最大值自己要离开了。如果不是”大哥“要走了,那么deque不变,大哥仍然是大哥。如果是”大哥“要走了,那么deque队首出队,新的deque队首就变成了原deque的第二个元素(3)。

总结:
使用queue保存队列元素。
使用deque保存最大值序列,队首元素为当前”大哥“,后续元素为候选大哥。不过,新来元素如果很大,就会把比它小的候选元素都挤出候选队列。”年轻人还厉害,老人就没机会了!

class MaxQueue {
     
    private Queue<Integer> queue;
    private Deque<Integer> deque;

    public MaxQueue() {
     
        queue = new LinkedList<>();
        deque = new LinkedList<>();
    }

    public int max_value() {
     
        return deque.isEmpty()?-1:deque.peekFirst();
    }

    public void push_back(int value) {
     
        queue.offer(value);
        while (!deque.isEmpty()&&deque.peekLast()<value)
            deque.pollLast();
        deque.offer(value);
    }

    public int pop_front() {
     
        if (queue.isEmpty())
            return -1;
        if (queue.peek().equals(deque.peekFirst()))
            deque.pollFirst();
        return queue.poll();
    }
}
 

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

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

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

示例 1:

输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]
示例 2:

输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]
 

题解:
动态规划。
三步骤:

  1. 定状态:F(n,j)表示当投掷完 n 枚骰子后,点数 j 出现的次数。j的取值范围:[n,6n]
  2. 写方程:第 n 枚骰子必然有1-6六种结果。因此投掷完 n 枚骰子后点数 j 出现的次数,可以由投掷完 n-1 枚骰子后,对应点数 j-1, j-2, j-3, … , j-6 出现的次数之和转化过来。在这里插入图片描述
for (第n枚骰子的点数 i = 1; i <= 6; i ++) {
    dp[n][j] += dp[n-1][j - i]
}
  1. 定边界
for (int i = 1; i <= 6; i ++) {
    dp[1][i] = 1;
}
class Solution {
     
    public double[] dicesProbability(int n) {
     
        int[][] dp = new int[n + 1][6 * n + 1];
        for (int i = 1; i <= 6; i++) {
     
            dp[1][i] = 1;
        }
        for (int i = 2; i <= n; i++) {
     
            for (int j = i; j <=6*i ; j++) {
     
                for (int k = 1; k <=6 && k<j ; k++) {
     
                    dp[i][j] += dp[i-1][j-k];
                }
            }
        }
        double[] ans = new double[6 * n - n + 1];
        for(int i = n; i <= 6 * n; i++)
            ans[i - n] = ((double)dp[n][i]) / (Math.pow(6,n));
        return ans;
    }
}

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

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

示例 1:

输入: [1,2,3,4,5]
输出: True
 

示例 2:

输入: [0,0,1,2,5]
输出: True

题解:
0可以作为任何数填充。 所以只需要判断拥有的0是否比需要的0更多。
特殊情况:有相同的数则立即返回false。

class Solution {
     
    public boolean isStraight(int[] nums) {
     
        //1.排序
        Arrays.sort(nums);
        //2.统计 0 的个数
        int len = 0;
        for (int num : nums) {
     
            if (num==0)len++;
        }
        //3.判断连续(从第一个非0开始判断)
        int dif = 0;
        for (int i = len+1; i < nums.length; i++) {
     
            int s = nums[i] - nums[i - 1];
            if (s==0)return false;
            dif +=(s-1);
        }
        return len>=dif;
    }
}

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

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

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

示例 1:

输入: n = 5, m = 3
输出: 3
示例 2:

输入: n = 10, m = 17
输出: 2

题解:
约瑟夫环公式:
leecode剑指offer集合及题解(持续更新。。)_第24张图片

class Solution {
     
    public int lastRemaining(int n, int m) {
     
        int[] dp = new int[n+1];
        dp[1] = 0;
        for (int i = 2; i < n+1; i++) {
     
            dp[i] = (dp[i-1]+m)%i;
        }
        return dp[n];
    }
}

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

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

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

题解:
动态规划:

  1. 定状态:dp[i]i天卖出时的最大利润.
  2. 状态转移方程:前 i 日最大利润 dp[i] 等于前i−1日最大利润 dp[i-1] 和第i 日卖出的最大利润中的最大值。
    public int maxProfit(int[] prices) {
     
        int min = Integer.MAX_VALUE,res=0;
        for (int price:prices) {
     
            min = Math.min(min, price);
            res = Math.max(res, price-min);
        }
        return res;
    }

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

题目:
求 1+2+…+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
题解:
用递归代替循环。用逻辑判断的短路效应代替条件判断。

class Solution {
     
    public int sumNums(int n) {
     
        boolean x = n > 1 && (n += sumNums(n - 1)) > 0;
        return n;
    }
}

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

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

示例:

输入: a = 1, b = 1
输出: 2

题解:
参考连接
^:无进位相加结果;
二进制都为1才进位。
&:找出都为1的位置(进位位置)
<<:左移,得到进位值。
最后循环执行:进位值+无进位值

class Solution {
     
    public int add(int a, int b) {
     
        while (b!=0){
     
            int tmp = a^b;
            b = (a&b)<<1;
            a = tmp;
        }
        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]。不能使用除法。

示例:

输入: [1,2,3,4,5]
输出: [120,60,40,30,24]

题解:
用两个数组分别存储左右三角形的每行乘积。再组合起来。
leecode剑指offer集合及题解(持续更新。。)_第25张图片

class Solution {
     
    public int[] constructArr(int[] a) {
     
        if (a.length<1)return new int[0];
        //第一个数组维护左下三角形
        int[] arr1 = new int[a.length];arr1[0]=1;
        //第二个数组维护右下三角型
        int[] arr2 = new int[a.length];arr2[a.length-1]=1;
		//遍历存储左三角
        for (int i = 1; i < a.length; i++) {
     
            arr1[i] = a[i-1]*arr1[i-1];
        }
        //遍历存储右三角
        for (int i = a.length-2; i >=0 ; i--) {
     
            arr2[i] = arr2[i+1]*a[i+1];
        }
		//组合出结果
        int[] res = new int[a.length];
        for (int i = 0; i < res.length; i++) {
     
            res[i] = arr1[i]*arr2[i];
        }
        return res;
    }
}

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

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

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

例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]

leecode剑指offer集合及题解(持续更新。。)_第26张图片

示例 1:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6 
解释: 节点 2 和节点 8 的最近公共祖先是 6。
示例 2:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

题解:

若 root.val < p.val,则 p 在 root 右子树 中;
若 root.val > p.val,则 p 在 root 左子树 中;
若 root.val = p.val,则 p 和 root 指向 同一节点 。
/**
 * 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) {
     
        while (root!=null){
     
            if (p.val>root.val&&q.val>root.val){
     
                root=root.right;
            }else if (p.val<root.val&&q.val<root.val){
     
                root = root.left;
            }else {
     
                break;
            }
        }
        return root;
    }
}

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

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

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

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
leecode剑指offer集合及题解(持续更新。。)_第27张图片
示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
题解:
见注释。

class Solution {
     
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
     
        if (root==null)return null;//如果根节点为null,则直接返回null
        if(root==p||root==q)return root;//如果根节点中有p 或 q。则直接返回(自己也可以当自己的父亲节点)
        //开始遍历左子树,p、q、null先碰到谁就返回谁
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        //开始遍历右子树,p、q、null先碰到谁就返回谁
        TreeNode right = lowestCommonAncestor(root.right, p, q);

        //如果在左子树中 p和 q都找不到,则 p和 q一定都在右子树中,右子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)
        if (left==null)return right;
        // 否则,如果 left不为空,在左子树中有找到节点(p或q),这时候要再判断一下右子树中的情况,如果在右子树中,p和q都找不到,则 p和q一定都在左子树中,左子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)
        else if (right==null)return left;
        //否则,当 left和 right均不为空时,说明 p、q节点分别在 root异侧, 最近公共祖先即为 root
        else return root;
    }
}

你可能感兴趣的:(数据结构与算法,算法,二叉树,链表,数据结构,leetcode)