剑指offer刷题笔记

剑指offer刷题笔记

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

3.数组中的重复的数字

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

方法一:暴力解法

class Solution {
    public int findRepeatNumber(int[] nums) {
        for(int i=0;i<nums.length-1;++i){
            for(int j=i+1;j<nums.length;++j){
                if(nums[i]==nums[j]){
                    return nums[i];
                }
            }
        }
        return -1;
    }
}

方法二:先排序再遍历

class Solution {
    public int findRepeatNumber(int[] nums) {
        Arrays.sort(nums);
        for(int i=0;i<nums.length-1;++i){
            if(nums[i] == nums[i+1]){
                return nums[i];
            }
        }
        return -1;
    }
}

方法三:利用数组存储每个数的个数,如果个数大于1说明出现重复数字直接返回

class Solution {
    public int findRepeatNumber(int[] nums) {
        int[] count = new int[nums.length];
        for(int num:nums){
            count[num]++;
            if(count[num]>1){
                return num;
            }
        }
        return -1;
    }
}

方法四:利用HashSet

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

4.二位数组中的查找

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

方法一:逐个遍历

class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
    	if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return false;
        }
        for(int i=0;i<matrix.length;++i){
            for(int j=0;j<matrix[0].length;++j){
                if(matrix[i][j] == target){
                    return true;
                }
                continue;
            }
        }
        return false;
    }
}

方法二:线性查找

/*
	根据数组中数组行和列都是递增的性质
	从左下角开始查找,如果目标值和当前数组的值相等则返回true
	如果目标值大于数组的值,则说明目标值可能在当前行
	如果目标值小于数组的值,则说明目标值可能在上一行
	循环完都没有返回true,说明没有匹配值,返回false
*/
class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return false;
        }
        int row = matrix.length-1;
        int col = 0;
        while(row>=0 && col<matrix[0].length){
            if(matrix[row][col] == target){
                return true;
            }else if(matrix[row][col] > target){
                row--;
            }else{
                col++;
            }
        }
        return false;
    }
}

5.替换空格

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

方法一:调用库函数

class Solution {
    public String replaceSpace(String s) {
        return s.replaceAll(" ","%20");
    }
}

方法二:利用字符数组

class Solution {
    public String replaceSpace(String s) {
        int n = s.length();
        char[] array = new char[n*3];
        int size = 0;
        for(int i=0;i<n;++i){
            if(s.charAt(i) == ' '){
                array[size++] = '%';
                array[size++] = '2';
                array[size++] = '0';
            }else{
                array[size++] = s.charAt(i);
            }
        } 
        return new String(array,0,size);
    }
}

6.从尾到头打印链表

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

方法一:利用栈

/**
 * 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<ListNode> stack = new Stack<>();
        ListNode temp = head;
        while(temp != null){
            stack.push(temp);
            temp = temp.next;
        }
        int n = stack.size();
        int[] ans = new int[n];
        for(int i=0;i<n;++i){
            ans[i] = stack.pop().val;
        }
        return ans;
    }
}

方法二:不使用栈,先遍历链表求出要创建的数组的长度,然后倒序遍历数组,存储链表的值

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        ListNode temp = head;
        int n =0;
        while(temp != null){
            ++n;
            temp = temp.next;
        }
        int[] ans = new int[n];
        temp = head;
        for(int i=n-1;i>=0;--i){
            ans[i] = temp.val;
            temp = temp.next;
        }
        return ans;
    }
}

7.重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

方法一:分治(不利用HashMap)

/**
 * 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 divide(preorder,inorder,0,preorder.length-1,0,inorder.length-1);
    }
    private TreeNode divide(int[] preorder,int[] inorder,int l1,int r1,int l2,int r2){
        if(l1>r1 || l2>r2){
            return null;
        }
        int mid = l2;
        /*
            前序遍历数组中第一个值是根节点的值
            与中序遍历数组中的值进行比较可以找到中序遍历数组中根节点的位置,
            则中序遍历中根节点的左边就是左子节点的值,右边就是右子节点的值
         */
        while(preorder[l1] != inorder[mid]){
            mid++;
        }
        TreeNode root = new TreeNode(preorder[l1]);
        root.left = divide(preorder,inorder,l1+1,l1+mid-l2,l2,mid-1);//mid-l2结果为左子节点的个数
        root.right = divide(preorder,inorder,l1+mid-l2+1,r1,mid+1,r2);
        return root;
    }
}

方法二:分治(利用HashMap)

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    HashMap<Integer,Integer> indexMap;
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        indexMap = new HashMap<>();
        for(int i=0;i<inorder.length;++i){
            indexMap.put(inorder[i],i);
        }
        return divide(preorder,inorder,0,preorder.length-1,0,inorder.length-1);
    }
    private TreeNode divide(int[] preorder,int[] inorder,int l1,int r1,int l2,int r2){
        if(l1>r1){
            return null;
        }
        int mid = indexMap.get(preorder[l1]);//获取中序遍历中根节点的位置
        TreeNode root = new TreeNode(preorder[l1]);
        root.left = divide(preorder,inorder,l1+1,l1+mid-l2,l2,mid-1);//mid-l2结果为左子节点的个数
        root.right = divide(preorder,inorder,l1+mid-l2+1,r1,mid+1,r2);
        return root;
    }
}

10.斐波那契数列

写一个函数,输入 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.确定dp数组
		dp[i]表示n=i时的值
		int[] dp = new int[n+1];
	2.确定递推关系
		int mod = 1000000007;
		由题上给出可知dp[i] = (dp[i-1]+dp[i-2])%mod;
	3.初始化
		由题上给可知
		dp[0] = 0;
        dp[1] = 1;
    4.确定遍历顺序
    	由递推关系可以知道dp[i]由dp[i-1]和dp[i-2]推出
    	因此时顺序遍历
    5.最终结果
    	dp[n];
*/
class Solution {
    public int fib(int n) {
        if(n<2){
            return n;
        }
        int mod = 1000000007;
        int[] dp = new int[n+1];
        dp[0] = 0;
        dp[1] = 1;
        for(int i=2;i<n+1;++i){
            dp[i] = (dp[i-1]+dp[i-2])%mod;
        }
        return dp[n];
    }
}

方法二:对方法一进行优化

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

10-Ⅱ.青蛙跳台阶问题

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

方法一:动态规划

class Solution {
    public int numWays(int n) {
         if(n<2){
            return 1;
        }
        int mod = 1000000007;
        int[] dp = new int[n+1];
        dp[0] = 1;
        dp[1] = 1;
        for(int i=2;i<n+1;++i){
            dp[i] = (dp[i-1]+dp[i-2])%mod;
        }
        return dp[n];
    }
}

方法二:对方法一进行优化

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

11.旋转数组的最小数字

方法一:排序
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

class Solution {
    public int minArray(int[] numbers) {
        Arrays.sort(numbers);
        return numbers[0];
    }
}

方法二:遍历

class Solution {
    public int minArray(int[] numbers) {
        if(numbers.length==1){
            return numbers[0];
        }
        for(int i=0;i<numbers.length-1;++i){
            if(numbers[i]>numbers[i+1]){
                return numbers[i+1];
            }
        }
        return numbers[0];//如果遍历完都没有返回,说明数组是递增的,则最小值就是numbers[0]	
    }
}

方法三:二分查找

class Solution {
    public int minArray(int[] numbers) {
        int l = 0;
        int r = numbers.length-1;
        while(l<r){
            int mid = l + (r-l)/2;
            if(numbers[mid]<numbers[r]){
                r=mid;
            }else if(numbers[mid]>numbers[r]){
                l=mid+1;
            }else{
                r--;
            }
        }
        return numbers[l];
    }
}

12.矩阵中的路径

方法一:dfs+回溯法

class Solution {
    public boolean exist(char[][] board, String word) {
        char[] word1 = word.toCharArray();
        for(int i=0;i<board.length;++i){
            for(int j=0;j<board[0].length;++j){
                if(dfs(board,word1,i,j,0)){
                    return true;
                }
            }
        }
        return false;
    }
    private boolean dfs(char[][] board,char[] word1,int i,int j,int k){
        if(i<0 || i>=board.length || j<0 || j>=board[0].length || k>=word1.length || word1[k] != board[i][j]){
            return false;
        }
        if(k==word1.length-1){
            return true;
        }
        board[i][j] = '+';//表示已经访问过
        if(
            dfs(board,word1,i+1,j,k+1) ||
            dfs(board,word1,i-1,j,k+1) ||
            dfs(board,word1,i,j+1,k+1) ||
            dfs(board,word1,i,j-1,k+1) 
           ){
                return true;
         }
        board[i][j]=word1[k];//还原
        return false;
    }
}

16.数值的整数次方

实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,x^n)。
-100.0 < x < 100.0
-231 <= n <= 231-1
-104 <= x^n <= 104

class Solution {
    public double myPow(double x, int n) {
        return n>=0 ? pow(x,n) : 1/pow(x,-n);//n分为大于等于0和小于0两种情况
    }
    private double pow(double x,int n){
        if(n==0){
            return 1.0;
        }
        double y = pow(x,n/2);
        return n%2==0 ? y*y : y*y*x;//n为奇数还是偶数
    }
}

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

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 99
用返回一个整数列表来代替打印
n 为正整数

class Solution {
    public int[] printNumbers(int n) {
        int m = (int)Math.pow(10,n);
        int[] num = new int[m-1];
        for(int i=0;i<num.length;++i){
            num[i]=i+1;
        }
        return num;
    }
}

18.删除链表的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
题目保证链表中节点的值互不相同
若使用 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) {
    	/*
    		创建一个哑节点指向当前的头节点
    		创建一个指针指向哑节点用来遍历链表
    		要删除值相等的节点,需要找到值相等的节点的前一个节点
    		使值相等节点的前一个节点指向值相等的节点的下一个节点
    	*/
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode cur = dummy;
        while(cur != null && cur.next != null){
            if(cur.next.val == val){
                cur.next = cur.next.next; 
                break;
            }
            cur = cur.next;
        }
        return dummy.next;
    }
}

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

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

class Solution {
    public int[] exchange(int[] nums) {
	    /*
	    	双指针
	    */
        int left =0;
        int right =nums.length-1;
        while(left<right){
            if(nums[left]%2==0 && nums[right]%2 != 0){
                int temp = nums[right];
                nums[right] = nums[left];
                nums[left] = temp;
                left++;
                right--;
            }else if(nums[left]%2 != 0){
                left++;
            }else if(nums[right]%2 == 0){
                right--;
            }   
        }
        return nums;
    }
}

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

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。

方法一:顺序遍历

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
	    /*
	    	先计算出节点的个数count
	    	然后从头遍历链表count-k次就找到了倒数第k个节点
	    */
        ListNode cur = head;
        int count = 0;
        while(cur != null){
            count++;
            cur=cur.next;
        }
        cur = head;
        for(int i=0;i<count-k;++i){
            cur=cur.next;
        }
        return cur;
    }
}

方法二:快慢指针

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode slow = head;
        ListNode fast = head;
		//快指针走了k步,走到第k+1个节点
        while(fast != null && k>0){
            fast = fast.next;
            k--;
        }
        //快指针走到链表末尾,则慢指针走到倒数第k个节点
        while(fast!=null){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

24.反转链表

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

方法一:迭代

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

方法二:递归

/**
 * 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 newHead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}

25.合并两个排序链表

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

方法一:迭代

/**
 * 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 && l2==null){
            return null;
        }
        if(l1==null){
            return l2;
        }
        if(l2==null){
            return l1;
        }
        ListNode newHead = new ListNode(-1);
        ListNode cur = newHead;
        while(l1 != null && l2!=null){
            if(l1.val<=l2.val){
                cur.next = l1;
                l1 = l1.next;
            }else{
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }
        cur.next = l1==null ? l2 : l1;
        return newHead.next;
    }
}

方法二:递归

/**
 * 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 && l2==null){
            return null;
        }
        if(l1==null){
            return l2;
        }
        if(l2==null){
            return l1;
        }
        
        if(l1.val <= l2.val){
            l1.next = mergeTwoLists(l1.next,l2);
            return l1;
        }else{
            l2.next = mergeTwoLists(l1,l2.next);
            return l2;
        }
    }
}

26.树的子结构

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。

/**
 * 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;
        }
        /*
            若B是A的子结构,则有三种可能
            一种是从根节点开始
            一种是在根节点的左子树
            一种是在根节点的右子树
            三种情况成立一种则就存在
         */
        boolean root = check(A,B);
        boolean left = isSubStructure(A.left,B);
        boolean right = isSubStructure(A.right,B);
        return  root || left || right ;
    }

    //递归检查A树和B树的节点值是否一样
    private boolean check(TreeNode A,TreeNode B){
        if(B==null){
            return true;
        }
        if(A==null || A.val != B.val){
            return false;
        }
        return recur(A.left,B.left) && recur(A.right,B.right);
    }
}

27.二叉树的镜像

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

/**
 * 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) {
        if(root == null){
            return root;
        }
        TreeNode temp = root.left;//创建一个临时节点来保存左子节点
        root.left = mirrorTree(root.right);
        root.right = mirrorTree(temp);
        return root;
    }
   
}

28.对称的二叉树

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

/**
 * 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) {
        return equalTreeNode(root,root);
    }
    private boolean equalTreeNode(TreeNode root1,TreeNode root2){
        if(root1==null && root2==null){
            return true;
        }
        if(root1==null || root2==null){
            return false;
        }
        return root1.val==root2.val && equalTreeNode(root1.left,root2.right) && equalTreeNode(root1.right,root2.left);
    }
}

29.顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]

class Solution {
    public int[] spiralOrder(int[][] matrix) {
         if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return new int[0];
        }
        int m = matrix.length;
        int n = matrix[0].length;
        int[] ans = new int[m*n];
        int index = 0;
        int left=0;
        int right = n-1;
        int top = 0;
        int bottom = m-1;
        //一圈一圈的进行存储
        while(left<=right && top<=bottom){
            for(int i=left;i<=right;++i){//第一次循环,存入1 2 3
                ans[index++] = matrix[top][i];
            }
            for(int i=top+1;i<=bottom;++i){//第一次循环,存入6 9
                ans[index++] = matrix[i][right];
            }
            if(left<right && top<bottom){//防止最终只剩下一行或一列的时候重复遍历添加。
                for(int i=right-1;i>=left;--i){//第一次循环,存入8 7
                    ans[index++] = matrix[bottom][i];
                }
                for(int i=bottom-1;i>top;--i){//第一次循环,存入4
                    ans[index++] = matrix[i][left];
                }
            }
            //进入下一圈
            left++;
            right--;
            top++;
            bottom--;
        }
        return ans;
    }
}

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) {
    	/*
    		用一个栈进行模拟
		*/
        Stack<Integer> stack = new Stack<>();
        int index =0;
        for(int num : pushed){
            stack.push(num);
            while(!stack.isEmpty() && stack.peek()==popped[index]){
                stack.pop();
                index++;
            }
        }
        return stack.isEmpty(); 
    }
}

32-Ⅰ.从上到下打印二叉树

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

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

32-Ⅱ.从上到下打印二叉树

从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
节点总数 <= 1000
方法一:迭代

/**
 * 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<>();
        List<List<Integer>> ans = new ArrayList<>();
        if(root != null) queue.add(root);
        while(!queue.isEmpty()) {
            List<Integer> list = new ArrayList<>();
            for(int i=queue.size()-1;i>=0;--i){//从高到底遍历,避免了queue.size()改变带来的影响
                TreeNode node = queue.poll();
                list.add(node.val);
                if(node.left != null) queue.add(node.left);
                if(node.right != null) queue.add(node.right);
            }
            ans.add(list);
        }
        return ans;
    }
}

方法二:递归

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    public List<List<Integer>> levelOrder(TreeNode root) {
        bfs(root,0);
        return ans;
    }
    //前序遍历
    private void bfs(TreeNode root,int level){
        if(root == null){ 
            return;
        }
        //level表示层数,层数和集合的尺寸相等表示在当前层
        if(level == ans.size()){
            ans.add(new ArrayList<Integer>());      
        }
        ans.get(level).add(root.val);//当前层的值添加到集合中
        bfs(root.left,level+1);
        bfs(root.right,level+1);
    }
}

32-Ⅲ.从上到下打印二叉树

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
节点总数 <= 1000
方法一:迭代

/**
 * 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<>();
        List<List<Integer>> ans = new ArrayList<>();
        if(root != null) queue.add(root);
        while(!queue.isEmpty()) {
            List<Integer> list = new ArrayList<>();
            for(int i=queue.size();i>0;--i){//从高到底遍历,避免了queue.size()改变带来的影响
                TreeNode node = queue.poll();
                if(ans.size()%2==0) {//ans.size()的大小和层数是相同的,所以可以用它来判断
                    list.add(node.val);
                }else{
                    list.add(0,node.val);
                }
                if(node.left != null) queue.add(node.left);
                if(node.right != null) queue.add(node.right);
            }
            ans.add(list);
        }
        return ans;
    }
}

方法二:递归

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    public List<List<Integer>> levelOrder(TreeNode root) {
        bfs(root,0);
        return ans;
    }
    //前序遍历
    private void bfs(TreeNode root,int level){
        if(root == null){ 
            return;
        }
        //level表示层数,层数和集合的尺寸相等表示在当前层
        if(level == ans.size()){
            ans.add(new ArrayList<Integer>());      
        }
        if(level%2==0){
            ans.get(level).add(root.val);//当前层的值顺序添加到集合中
        }else{
            ans.get(level).add(0,root.val);//当前层的值逆序添加到集合中
        }
        
        bfs(root.left,level+1);
        bfs(root.right,level+1);
    }
}

33.二叉搜索树的后序遍历

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

方法一:递归

class Solution {
    public boolean verifyPostorder(int[] postorder) {
        return check(postorder,0,postorder.length-1);
    }
    private boolean check(int[] postorder,int i,int j){
        if(i>=j){
            return true;
        }
        int rootValue = postorder[j];
        int k=i;
        while(k<j && postorder[k]<rootValue){//从当前区域找到第一个大于根节点的,说明后续区域数值都在右子树中
            k++;
        }
        for(int a=k;a<j;++a){
            if(postorder[a]<rootValue){// 进行判断后续的区域是否所有的值都是大于当前的根节点,如果出现小于的值就直接返回false
                return false;
            }
        }
        //检查左右子树
        return check(postorder,i,k-1) && check(postorder,k,j-1);
    }
}

34.二叉树中和为一值的路径

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点
树中节点总数在范围 [0, 5000] 内
-1000 <= Node.val <= 1000
-1000 <= targetSum <= 1000

/**
 * 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>> ans = new ArrayList<>();
    List<Integer> list = new LinkedList<>();
    public List<List<Integer>> pathSum(TreeNode root, int target) {
	    /*
	    	深度优先搜索
	    */
        dfs(root,target);
        return ans;
    }
    private void dfs(TreeNode root,int target){
        if(root==null){
            return;
        }
        list.add(root.val);
        if(root.val == target && root.left == null && root.right == null){
            ans.add(new LinkedList<Integer>(list));
        }
        dfs(root.left,target-root.val);
        dfs(root.right,target-root.val);
        list.remove(list.size()-1);
    }
}

35.复杂链表的复制

方法一:回溯+哈希表
递归三部曲:
1.整个递归的终止条件。
2.一级递归需要做什么?
3.应该返回给上一级的返回值是什么?

/*
// 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 {
    HashMap<Node,Node> map = new HashMap<>();//用来存储复制的节点(旧节点,新节点)
    public Node copyRandomList(Node head) {
        if(head == null){//递归终止条件
            return null;
        }

        /*
            为了防止重复拷贝,我们需要首先检查当前节点是否被拷贝过,
            如果没有拷贝,则进行递归创建
            如果已经拷贝过,我们可以直接从哈希表中取出拷贝后的节点的指针并返回即可。 
        */
        //一级递归需要做
        if(!map.containsKey(head)){
            Node newHead = new Node(head.val);
            map.put(head,newHead);
            newHead.next = copyRandomList(head.next);
            newHead.random = copyRandomList(head.random);
        }
        return map.get(head);//要找的就是拷贝后的头节点,所以本层节点就应该是返回给上一级的返回值
        
    }
}

方法二:迭代+节点拆分

/*
// 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 null;
        }
        //将拷贝节点放到原节点后面,例如A->B->C这样的链表就变成了这样hA->A'->B->B'->C->C'
        for(Node node = head;node != null;node = node.next.next){
            Node newNode = new Node(node.val);
            newNode.next = node.next;
            node.next = newNode;
        }
        //拷贝节点的random指针
        for(Node node = head;node != null;node = node.next.next){
            Node newNode = node.next;
            newNode.random = (node.random != null) ? node.random.next : null;
        }
        //将A->A'->B->B'->C->C'拆分为A->B->C 和 A'->B'->C'
        Node newHead = head.next;
        for(Node node = head;node != null;node = node.next){
            Node temp = node.next;
            node.next = node.next.next;
            temp.next = (temp.next != null) ? temp.next.next : null;
        }
        return newHead;
    }
}

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

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

/*
// Definition for a Node.
class Node {
    public int val;
    public Node left;
    public Node right;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val,Node _left,Node _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
    Node pre,head;
    public Node treeToDoublyList(Node root) {
        if(root==null){
            return null;
        }
        dfs(root);
        
        //头尾相连
        head.left = pre;
        pre.right = head;
        return head;
    }
    //中序遍历
    private void dfs(Node root){
        if(root == null){//递归终止条件
            return;
        }
        dfs(root.left);

        //当前层需要做的
        if(pre==null){//说明是第一个节点,用head保存
            head = root;
        }else{
            pre.right = root;//上一个节点的右指针指向当前节点
        }
        root.left=pre;//当前节点的做指针指向上一个节点,构成双向链表
        pre=root;//指针后移,当前节点变为上一个节点

        dfs(root.right);
    }
}

37.序列化二叉树

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

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

/**
 * 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) {
        return preOrderSerialize(root,"");
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        String[] str = data.split(",");
        List<String> dataList = new LinkedList<>(Arrays.asList(str));
        return preOrderDeserialize(dataList);

    }
    //前序遍历
    private String preOrderSerialize(TreeNode root,String str){
        if(root == null){
            str += "None,";
        }else{
            str += str.valueOf(root.val)+",";
            str = preOrderSerialize(root.left,str);
            str = preOrderSerialize(root.right,str);
        }
        return str;
    }
    //前序遍历
    private TreeNode preOrderDeserialize(List<String> dataList){
        if(dataList.get(0).equals("None")){
            dataList.remove(0);
            return null;
        }
        TreeNode root = new TreeNode(Integer.parseInt(dataList.get(0)));
        dataList.remove(0);
        root.left = preOrderDeserialize(dataList);
        root.right = preOrderDeserialize(dataList);
        return root;
    }
}

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

38.字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
1 <= s 的长度 <= 8

class Solution {
    List<String> res = new ArrayList<>();;//用来存储最后结果
    boolean[] vis;//用来判断当前位置字符是否被使用
    public String[] permutation(String s) {
        //1.将字符串转换为字符数组
        char[] c = s.toCharArray();
        //2.对数组中的字符进行排序
        Arrays.sort(c);
        //3.获取排列后的集合
        int n = s.length();
        vis = new boolean[n];
        StringBuffer bf = new StringBuffer();//用来存储每次排列的字符串
        backtrack(c,0,n,bf);
        //4.将集合转换为字符串数组
        String[] ans = new String[res.size()];
        for(int i=0;i<ans.length;++i){
            ans[i] = res.get(i);
        }
        //5.返回结果
        return ans;
    }
    //i表示待填的空位
    private void backtrack(char[] c,int i,int n,StringBuffer bf){
        if(i==n){//说明排列结束,将这个字符串添加到结果集合中
            res.add(bf.toString());
            return;
        }
        for(int j=0;j<n;++j){
            //防止重复排列,剪枝,vis[i-1]是因为c[j-1]在回退的过程中刚刚被撤销
            //如果c[j-1]==c[j],并且vis[j-1]=false;
            //则说明这个位置再使用c[j]就和上次这个位置使用c[j-1]的情况相同
            //因此进行剪枝,跳过
            if(vis[j] || (j>0 && !vis[j-1] && c[j-1]==c[j])){
                continue;
            }
            vis[j]=true;
            bf.append(c[j]);
            backtrack(c,i+1,n,bf);//填下一个空位
            bf.deleteCharAt(bf.length()-1);
            vis[j]=false;
        }
    }
}

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

方法一:排序

class Solution {
    public int majorityElement(int[] nums) {
        Arrays.sort(nums);
        return nums[(nums.length-1)/2];
    }
}

方法二:HashMap

class Solution {
    public int majorityElement(int[] nums) {
        HashMap<Integer,Integer> map = new HashMap<>();
        int ans = 0;
        for(int num : nums){
            map.put(num,map.getOrDefault(num,0)+1);
            if(map.get(num)>(nums.length-1)/2){
                ans = num;
                break;
            }
        }
        return ans;
    }
}

方法三:摩尔投票法

class Solution {
    public int majorityElement(int[] nums) {
        int count = 0;
        Integer candidate = null;
        for(int num : nums){
            if(count == 0){
                candidate = num;
            }
            count += (candidate==num) ? 1 : -1;
        }
        return candidate;
    }
}

40.最小的k个数

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000
方法一:排序

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        Arrays.sort(arr);
        return Arrays.copyOfRange(arr,0,k);
    }
}

方法二:大根堆

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        /*
            1.创建一个数组用来保存结果值
            2.创建一个大小为k的大根堆(因为PriorityQueue默认是小根堆,因此要重写比较器)
            3.将数组的前k个数放入大根堆
            4.数组从第k+1个数开始逐个和大根堆堆顶的数进行比较,如果小于堆顶的值,则将堆顶的值弹出,放入当前数组的值
            5.将最后比较完后的到的大根堆中的数值存入到结果数组中返回
         */
        int[] ans = new int[k];
        if(k==0){
            return ans;
        }
        /*PriorityQueue queue = new PriorityQueue(new Comparator() {
            public int compare(Integer num1, Integer num2) {
                return num2 - num1;
            }
        });*/
        //lambda表达式写法
        PriorityQueue<Integer> queue = new PriorityQueue<>((num1,num2) -> num2-num1);
        for(int i=0;i<k;++i){
            queue.offer(arr[i]);
        }
        for(int i=k;i<arr.length;++i){
            if(queue.peek()>arr[i]){
                queue.poll();
                queue.offer(arr[i]);
            }
        }
        for(int i=0;i<k;++i){
            ans[i] = queue.poll();
        }
        return ans;
    }
}

方法三:快速排序

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        quick_sort(arr,0,arr.length-1);
        return Arrays.copyOfRange(arr,0,k);
    }
    void quick_sort(int s[], int l, int r){
        if (l < r)
        {
            
            int i = l, j = r, x = s[l];
            while (i < j)
            {
                while(i < j && s[j] >= x) // 从右向左找第一个小于x的数
                    j--;  
                if(i < j) 
                    s[i++] = s[j];
                
                while(i < j && s[i] < x) // 从左向右找第一个大于等于x的数
                    i++;  
                if(i < j) 
                    s[j--] = s[i];
            }
            s[i] = x;
            quick_sort(s, l, i - 1); // 递归调用 
            quick_sort(s, i + 1, r);
        }
    }
}

41.数据流中的中位数

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

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

设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。

最多会对 addNum、findMedian 进行 50000 次调用。

class MedianFinder {
	//大顶堆和小顶堆
	//大顶堆中存较小数,小顶堆中存较大数
    Queue<Integer> bigQueue,littleQueue;
    /** initialize your data structure here. */
    public MedianFinder() {
        this.bigQueue = new PriorityQueue<>((num1,num2) -> (num2-num1));//重写比较器,因为PriorityQueue默认是小顶堆
        this.littleQueue = new PriorityQueue<>();
    }
    
    public void addNum(int num) {
        if(bigQueue.size()!=littleQueue.size()){
            littleQueue.add(num);
            bigQueue.add(littleQueue.poll());
        }else{
            bigQueue.add(num);
            littleQueue.add(bigQueue.poll());
        }
    }
    
    public double findMedian() {
        return littleQueue.size() != bigQueue.size() ? (double)littleQueue.peek() : (double)(littleQueue.peek()+bigQueue.peek())/2;
    }
}

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

42.连续子数组的最大和

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
1 <= arr.length <= 10^5
-100 <= arr[i] <= 100
方法一:动态规划

class Solution {
    public int maxSubArray(int[] nums) {
        /*
            1.确定dp数组
                dp[i]表示以第i个数结尾的连续子数组最大和
                int[] dp = new int[nums.length];
            2.确定递推关系式
                dp[i] = Math.max(nums[i],dp[i-1]+nums[i]);
            3.数组初始化
                dp[0] = nums[0];
            4.遍历顺序
                根据递推关系式可知是顺序遍历
            5.最终结果
                dp数组中的最大值
         */
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        int max = nums[0];
        for(int i=1;i<nums.length;++i){
            dp[i] = Math.max(nums[i],dp[i-1]+nums[i]);
            max = Math.max(dp[i],max);
        }
        return max;
    }
}

方法二:动态规划(空间优化)

class Solution {
    public int maxSubArray(int[] nums) {
        /*
            滚动数组思想
         */
        int pre = nums[0];
        int max = nums[0];
        for(int i=1;i<nums.length;++i){
            pre = Math.max(nums[i],pre+nums[i]);
            max = Math.max(pre,max);
        }
        return max;
    }
}

45.把数组排成最小的数

输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
输出结果可能非常大,所以你需要返回一个字符串而不是整数
拼接起来的数字可能会有前导 0,最后结果不需要去掉前导 0

class Solution {
    public String minNumber(int[] nums) {
        List<String> list = new ArrayList<>();
        for (int num : nums) {
            list.add(String.valueOf(num));
        }
        list.sort((o1, o2) -> (o1 + o2).compareTo(o2 + o1));//重写比较器
        return String.join("", list);
    }
}

46.把数字翻译成字符串

方法一:动态规划

class Solution {
    public int translateNum(int num) {
        /*  
            //类比青蛙跳台阶
            1.确定dp数组
                dp[i]表示以第i个数字结尾的数有多少种翻译方法
                int[] dp = new int[n+1];
            2.确定递推关系式
                因为只能选择一个数子或则两个数字,所以
                dp[i] = dp[i-1] + dp[i-2];
                但是只有数字是来两位数的时候才可以跳两步,所以要加上限制条件 num1 >= 10 && num1 <= 25
            3.数组初始化
                dp[0] = 1;原地跳
                dp[1] = 1;
            4.确定遍历顺序
                根据递推关系式可知是顺序遍历
            5.最终结果
                dp[n];

         */
        String s = String.valueOf(num);
        int n = s.length();
        if(n<2){
            return n;
        }
        int[] dp = new int[n+1];
        dp[0] = 1;
        dp[1] = 1;
        for(int i=2;i<=n;++i){
            dp[i] = dp[i-1];
            int num1 = (s.charAt(i-1)-'0')+(s.charAt(i-2)-'0')*10;
            if(num1 >= 10 && num1 <= 25){
                dp[i] += dp[i-2];
            }
        }
        return dp[n];
    }
}

方法二:优化

class Solution {
    public int translateNum(int num) {
        String s = String.valueOf(num);
        int n = s.length();
        if(n<2){
            return n;
        }
        int a = 1;
        int b = 1;
        int ans = 0;
        for(int i=2;i<=n;++i){
            ans = b;
            int num1 = (s.charAt(i-1)-'0')+(s.charAt(i-2)-'0')*10;
            if(num1 >= 10 && num1 <= 25){
                ans += a;
            }
            a=b;
            b=ans;
        }
        return ans;
    }
}

47.礼物的最大价值

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

方法一:动态规划
类比leetcode62 机器人不同路径

class Solution {
    int max = 0;
    public int maxValue(int[][] grid) {
        /*
            1.确定dp数组
                dp[i][j]表示到达(i,j)位置时的最大价值
                int[][] dp = new int[m][n];
            2.确定递推关系式
                可知只能从(i-1,j)(i,j-1)位置到达(i,j)的位置
                所以(i,j)的位置的最大价值等于(i-1,j)和(i,j-1)中的最大价值加上当前位置的价值
                即 dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1])+grid[i][j];
            3.初始化
                dp[0][0] = grid[0][0];
                考虑边缘位置
                因为上边缘的位置只能从(i,j-1)的位置到来,所以dp[0][i] = dp[0][i-1]+grid[0][i]; [0];
                同理左边缘dp[i][0] = dp[i-1][0]+grid[i];
            4.确定遍历顺序
                由递推关系式可知是顺序遍历
            5.最终结果
                dp[m-1][n-1];

         */
        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 i=1;i<n;++i){
            dp[0][i] = dp[0][i-1]+grid[0][i]; 
        }
        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];
    } 
   
}

方法二:对方法一进行优化

class Solution {
    int max = 0;
    public int maxValue(int[][] grid) {
        /*
            对方法一进行优化:
                由于这里dp[i][j]只和dp[i-1][j]与dp[i][j-1]有关,我们可以运用「滚动数组思想」把空间复杂度优化
            1.确定dp数组的下标及其含义
                dp[j]用来缓存第j行
                int[] dp = new int[n+1];
            2.确定递推公式
                由于只能向下或则向右移动,因此要想到达第i行,则一定是从上一行(j-1)或则当前行(j)走过来的
                    则 dp[j] = Math.max(dp[j-1]+dp[j])+grid[i][j];
            3.初始化
                由递推公式可知,其基础都是由dp[0]推导来的
                因此dp[0] = 0;
            4.确定遍历顺序
                由递推公式可以看到dp[j]是由dp[j-1]和dp[j]推导出来的,因此是顺序遍历
            5.最终结果
                可知到达最后位置就是数组的最后一个值即dp[n];
        */
        int m = grid.length;
        int n = grid[0].length;
        int[] dp = new int[n+1];
        for(int i=1;i<=m;++i){
            for(int j=1;j<=n;++j){
                dp[j] = Math.max(dp[j],dp[j-1])+grid[i-1][j-1];
            }
        }
        return dp[n];
    } 
   
}

48.最长不含重复字符的子字符串

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

class Solution {
    public int lengthOfLongestSubstring(String s) {
    	/*
			滑动窗口的本质还是双指针
		*/
        if(s.length()<1 || s==null){
            return 0;
        }
        int[] map = new int[128];//字符对应的ascll码在前128中,用数组来模拟哈希
        int left = 0;
        int right = 0;//滑动窗口为[left,right),其间为不重复元素
        int max = 0;
        while(right<s.length()){
            if( map[s.charAt(right)]==0){//不重复添加
                map[s.charAt(right++)]++;
                max = Math.max(max,right-left);
            }else{//出现重复一直减小到窗口中不重复为止,因为最差的情况也即是减到窗口中剩余1个字符,所以不用考虑left>right的情况
                map[s.charAt(left++)]--;
            }

        }
        return max;
    }
}

49.丑数

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

方法一:小顶堆

class Solution {
    public int nthUglyNumber(int n) {
        /*
            小顶堆+HashSet集合
         */
        PriorityQueue<Long> queue = new PriorityQueue<Long>();
        HashSet<Long> set = new HashSet<>();

         // 1作为第一个丑数存入堆和集合中
        queue.offer(1L);
        set.add(1L);
        int[] factor = {2,3,5};//质因子
        int ugly = 0;//用来保存丑数

        //每次取出堆顶的丑数,第n次取出的就是按从小到大的顺序的第n个丑数
        //利用HashSet集合防止存入堆中的数值出现重复
        for(int i=0;i<n;++i){
           long cur = queue.poll();
           ugly = (int)cur;
           for(int num : factor){
               long next = num*cur;//cur是丑数,则(cur*质数因子)也是丑数
               if(set.add(next)){//能添加进集合,说明不重复
                    queue.offer(next);
               }
           }
        }
        return ugly;
    }
}

方法二:动态规划

class Solution {
    public int nthUglyNumber(int n) {
        /*
            1.确定dp数组
                dp[i]表示从小到大的顺序的第n个丑数
                int[] dp = new int[n+1];
            2.确定递归关系式
                由于当前丑数都是由前面某一个丑数乘以质数因子(3个质数因子中的某一个)得来的,
                因此我们需要定义三个指针来更新上一个丑数,
                int p1=1,p2=1,p3=1;
                则对应的丑数就是dp[p1],dp[p2],dp[p3];
                由于是按照从小到大的顺序,所以当前丑数是由前面的某一个丑数乘以质数因子后其中的最小值
                即dp[i] = Math.min(Math.min(dp[p1]*2,dp[p2]*3),dp[p3]*5);
                确定当前丑数是由dp[p1],dp[p2],dp[p3]中的哪一个得到的,对其对应的指针进行更新
            3.初始化
                第一个丑数肯定是1,则dp[1] = 1;
            4.确定遍历顺序
                由于当前的丑数是由之前的丑数推出的,所以是顺序遍历
            5.最终结果
                dp[n];

         */
        int[] dp = new int[n+1];
        dp[1] = 1;
        int p1=1,p2=1,p3=1;
        for(int i=2;i<=n;++i){
            int num1 = dp[p1]*2;
            int num2 = dp[p2]*3;
            int num3 = dp[p3]*5;
            dp[i] = Math.min(Math.min(num1,num2),num3);
            if(dp[i]==num1){
                p1++;
            }
            if(dp[i]==num2){
                p2++;
            }
            if(dp[i]==num3){
                p3++;
            }
        }
        return dp[n];
    }
}

50.第一只出现一次的字符

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

class Solution {
    public char firstUniqChar(String s) {
        int[] count = new int[128];//用数组模拟哈希
        for(int i=0;i<s.length();++i){
            char c = s.charAt(i);
            count[c]++;
        }
        for(int i=0;i<s.length();++i){
            char c = s.charAt(i);
            if(count[c]==1){
                return c;
            }
        }
        return ' ';
    }
}

//空间优化
class Solution {
    public char firstUniqChar(String s) {
        int[] count = new int[26];//用数组模拟哈希
        for(int i=0;i<s.length();++i){
            int c = s.charAt(i)-'a';
            count[c]++;
        }
        for(int i=0;i<s.length();++i){
            int c = s.charAt(i)-'a';
            if(count[c]==1){
                return s.charAt(i);
            }
        }
        return ' ';
    }
}

51.数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
0 <= 数组长度 <= 50000

class Solution {
	/*
		归并排序的思想
	*/
    int ans;//存储最后结果
    int[] temp;//临时数组,用来存储归并后的结果
    public int reversePairs(int[] nums) {
        ans = 0;
        if(nums.length<2){
            return ans;
        }
        temp = new int[nums.length];
        mergeSort(nums,0,nums.length-1);
        return ans;

    }
    private void mergeSort(int[] nums,int l,int r){
        if(l>=r){
            return;
        }
        int mid = l+(r-l)/2;
        mergeSort(nums,l,mid);//拆分
        mergeSort(nums,mid+1,r);//拆分
        merge(nums,l,mid,r);//合并
    }
    private void merge(int[] nums,int l,int mid,int r){
        int i=l;
        int j =mid+1;
        int index=l;
        while(i<=mid && j<=r){
            if(nums[i]<=nums[j]){
                temp[index++] = nums[i++];
                //当前i指向的数字比j小,但是比  [mid+1 ... j-1] 的其他数字大,
                //[mid+1 ... j-1] 的其他数字本应当排在 i对应数字的左边,但是它排在了右边,
                //所以这里就贡献了j-1-(mid+1)+1 =  j-(mid+1) 个逆序对。
                ans += j-(mid+1);//
            }else{
                temp[index++] = nums[j++];
            } 
        }
        while(i<=mid){
            temp[index++] = nums[i++];
            ans += j-(mid+1);//同上
        }
        while(j<=r){
            temp[index++] = nums[j++];
        }
        for(int k=l;k<=r;++k){
            nums[k] = temp[k];
        }
    }
}

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

输入两个链表,找出它们的第一个公共节点。
如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

方法一:HashSet

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

方法二:双指针

/**
 * 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;
        }
        ListNode node1 = headA;
        ListNode node2 = headB;
        while(node1 != node2){
            node1 = node1 == null ? headB : node1.next;
            node2 = node2 == null ? headA : node2.next;
        }
        return node1;
    }
}

53-Ⅰ.在排序数组中查找数字

统计一个数字在排序数组中出现的次数。
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109

方法一:暴力迭代

class Solution {
    public int search(int[] nums, int target) {
        int count = 0;
        for(int i=0;i<nums.length;++i){
            if(nums[i] == target){
                count++;
            }
        }
        return count;
    }
}

方法二:二分查找

class Solution {
    public int search(int[] nums, int target) {

        //二分查找到数组中第一个与目标值相等的数值的下标
        int l = 0;
        int r = nums.length-1;
        while(l<r){
            int mid = l+(r-l)/2;
            if(target>nums[mid]){
                l = mid+1;
            }else{
                r = mid;
            }
        }
        //从第一个相等的值的下标开始遍历,直到与目标值不相等
        int count=0;
        while(l<nums.length && nums[l++] == target){
            count++;
        }
        return count;
    }
}

53-Ⅱ.0~n-1中缺失的数字

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

方法一:顺序遍历

class Solution {
    public int missingNumber(int[] nums) {
        for(int i=0;i<nums.length;++i){
            if(nums[i] != i){
                return i;
            }
        }
        return nums.length;
    }
}

方法二:二分查找

class Solution {
    public int missingNumber(int[] nums) {
        int l = 0;
        int r = nums.length-1;
        while(l<=r){
            int mid = l+(r-l)/2;
            if(nums[mid]==mid){
                l = mid+1;
            }else{
                r = mid-1;
            }
        } 
        return l;
    }
}

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

给定一棵二叉搜索树,请找出其中第k大的节点。
1 ≤ k ≤ 二叉搜索树元素个数

方法一:中序遍历+集合

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    List<Integer> list = new ArrayList<>();
    public int kthLargest(TreeNode root, int k) {
        inorder(root);
        return list.get(list.size()-k);
    }
    private void inorder(TreeNode root){
        if(root==null){
            return;
        }
        inorder(root.left);
        list.add(root.val);
        inorder(root.right);
    }
}

方法二:中序遍历思想

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    private int ans = 0;
    private int count = 0;
    public int kthLargest(TreeNode root, int k) {
        inorder(root,k);
        return ans;
    }
     /*
        因为是搜索二叉树,所以当前节点大于当前节点的左节点小于当前节点的右节点
        我们利用中序遍历的思想,先遍历右子树再遍历左子树
        当遍历到第k大数的时候就可以停止遍历了
        保存当前节点的值
     */
    private void inorder(TreeNode root,int k){
        if(root==null){
            return;
        }
        inorder(root.right,k);
        if(++count == k){
            ans = root.val;
            return; 
        }
        inorder(root.left,k);
    }
}

55-Ⅰ.二叉树的深度

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

方法一:后序遍历

/**
 * 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;
        }
        int left = maxDepth(root.left);
        int right = maxDepth(root.right);
        return Math.max(left,right)+1;
    }
}

方法二:层序遍历

/**
 * 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;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        int ans = 0;
        while(!queue.isEmpty()){
            int n = queue.size();
            for(int i=0;i<n;++i){
                TreeNode node = queue.poll();
                if(node.left != null){
                    queue.add(node.left);
                }
                if(node.right != null){
                    queue.add(node.right );
                }
            }
            ans++;
        }
        return ans;
    }
}

55-Ⅱ.平衡二叉树

输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过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 isBalanced(root.left) && isBalanced(root.right) && Math.abs(depth(root.left)-depth(root.right))<=1;
    }
    //求某节点的深度
    private int depth(TreeNode root){
        if(root == null){
            return 0;
        }
        int left = depth(root.left);
        int right = depth(root.right);
        return Math.max(left,right)+1;
    }
}

方法二:自底向上

class Solution {
    public boolean isBalanced(TreeNode root) {
        return height(root) >= 0;
    }

    public int height(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftHeight = height(root.left);
        int rightHeight = height(root.right);
        if (leftHeight == -1 || rightHeight == -1 || Math.abs(leftHeight - rightHeight) > 1) {
            return -1;
        } else {
            return Math.max(leftHeight, rightHeight) + 1;
        }
    }
}

56-Ⅰ.数组中数字出现的次数

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

class Solution {
    public int[] singleNumbers(int[] nums) {
        /*
            基于异或运算的以下几个性质 
                1. 交换律 
                2. 结合律 
                3. 对于任何数x,都有x^x=0,x^0=x 
            思路:
                记只出现一次的两个数字为a,b
                1.数组中所有数字进行异或可以得到ret = a^b
                2.根据ret某一位为1可以用来区分a,b,可以将数组划分为两个数组,分别包含a,b,并且数组中其他数组都出现了两次
                3.分别对两个数组进行异或,就得到最终结果
         */
        int ret = 0;//用于记录只出现一次的两个数字的异或结果
        for (int n : nums) {
            ret ^= n;
        }
        int div = 1;
        while ((div & ret) == 0) {//这里用最低位的1进行划分
            div <<= 1;
        }
        int a = 0, b = 0;
        for (int n : nums) {
            if ((div & n) != 0) {
                a ^= n;
            } else {
                b ^= n;
            }
        }
        return new int[]{a, b};
    }
}


55-Ⅱ.数组中数字出现的次数

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

方法一:HashMap

class Solution {
    public int singleNumber(int[] nums) {
        Map<Integer,Integer> map = new HashMap<Integer,Integer>();
        for(int num : nums){
            map.put(num,map.getOrDefault(num,0)+1);
        }
        for(Integer num : map.keySet()){
            if(map.get(num)==1) return num;
        }
        return -1;
    }
}

方法二:位运算

class Solution {
    public int singleNumber(int[] nums) {
        int a = 0;
        int b = 0;
        
        for(int num : nums) {
        	a = (a ^ num) & ~b;
        	b = (b ^ num) & ~a;
        }
        
        return a;
    }
}

57.和为s的两个数字

输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^6

方法一:双指针

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int l = 0;
        int r = nums.length-1;
        while(l<r){
            int sum = nums[l]+nums[r];
            if(target==sum){
                return new int[]{nums[l],nums[r]};
            }else if(target>sum){
                l++;
            }else{
                r--;
            }
        }
        return new int[]{};
    }
}

方法二:HashSet

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Set<Integer> set = new HashSet<>();
        for(int num : nums){
            if(!set.contains(target-num)){
                set.add(num);
            }else{
                return new int[]{num,target-num};
            }
        }
        return new int[]{};
    }
}

57-Ⅱ.和为s的连续正数序列

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

1 <= target <= 10^5

class Solution {
    public int[][] findContinuousSequence(int target) {
        List<int[]> list = new ArrayList<>();
        for(int l=1,r=2;l<r;){
            int sum = (l+r)*(r-l+1)/2;
            if(sum == target){
                int[] ans = new int[r-l+1];
                for(int i=0;i<ans.length;++i){
                    ans[i] = l+i;
                }
                list.add(ans);
                r++;
            }else if(sum>target){
                l++;
            }else{
                r++;
            }
        }
        return list.toArray(new int[list.size()][]);
    }
}

58-Ⅰ.翻转单词顺序

输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. “,则输出"student. a am I”。
无空格字符构成一个单词。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

class Solution {
    public String reverseWords(String s) {
        String[] str = s.trim().split(" ");
        StringBuilder sb = new StringBuilder();
        for(int i=str.length-1;i>=0;--i){
            if(str[i].equals("")){
                continue;
            }
            sb.append(str[i]).append(" ");
            
        }
        return sb.toString().trim();
    }
}

58-II.左旋转字符串

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

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

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

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。

方法一:暴力迭代

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums.length == 0){
            return new int[]{};
        }
        int[] ans = new int[nums.length-k+1];
        for(int l=0,r=k-1;r<nums.length;){
            int max = Integer.MIN_VALUE;
            for(int i=l;i<=r;++i){
                max = Math.max(nums[i],max);
            }
            ans[l] = max;
            l++;
            r++;
        }
        return ans;
    }
}

方法二:双向队列

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums.length == 0) return nums;
        Deque<Integer> deque=new ArrayDeque<>();
        int[] ans = new int[nums.length-k+1];
        for(int i=0;i<nums.length;i++){
            if(!deque.isEmpty() && deque.peekFirst()<i-(k-1))//更新窗口
                deque.removeFirst();
            while(!deque.isEmpty() && nums[deque.peekLast()]<nums[i])//保持窗口中的最大值在队列首部
                deque.removeLast();
            deque.offerLast(i);//将数组的下标添加到队列中
            if(i>=k-1){
                ans[i-(k-1)]=nums[deque.peekFirst()];//保存第一个队列中的最大值
            }  
        }
        return ans;
    }
}

59 - II. 队列的最大值

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

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

class MaxQueue {
    Queue<Integer> q;//普通队列
    Deque<Integer> d;//双向队列,首位用来保存队列的最大值

    public MaxQueue() {
        q = new LinkedList<Integer>();
        d = new LinkedList<Integer>();
    }
    
    public int max_value() {
        if (d.isEmpty()) {
            return -1;
        }
        return d.peekFirst();
    }
    
    public void push_back(int value) {
        while (!d.isEmpty() && d.peekLast() < value) {
            d.pollLast();
        }
        d.offerLast(value);
        q.offer(value);
    }
    
    public int pop_front() {
        if (q.isEmpty()) {
            return -1;
        }
        int ans = q.poll();
        if (ans == d.peekFirst()) {//如果弹出的这个值是队列中的最大值,则双向队列中维护的最大值也应该弹出
            d.pollFirst();
        }
        return ans;
    }
}


/**
 * 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();
 */

60.n个骰子的点数

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。

1 <= n <= 11

class Solution {
    public double[] dicesProbability(int n) {
        /*
            1.确定dp数组
                dp[i][j]表示i个骰子点数的和为j的种类数
                double[][] dp = new double[n+1][6*n+1];
            2.确定递推关系式
                i个骰子点数和为j的情况只与i-1个骰子的和有关。
                因为一个骰子有六个点数,所以第i个骰子点数为1的话,dp[i][j] = dp[i-1][j-1]
                第i个骰子点数为2的话,dp[i][j] = dp[i-1][j-2],....依次类推。
                在i-1个骰子的基础上,再增加一个骰子,和为j的结果只有这六种情况。(前提是总和j要大于骰子的点数)
                即dp[i][j] = dp[i-1][j-1]+dp[i-1][j-2]+dp[i-1][j-3]+dp[i-1][j-4]+dp[i-1][j-5]+dp[i-1][j-6];
            3.初始化
                可知只有一个骰子的时候,和j的情况只有六种(1 2 3 4 5 6),每一种情况数量为1
                for(int j=1;j<=6;++j){
                    dp[1][j] = 1;
                }
            4.确定遍历顺序
                由与第n个骰子的情况是由第n-1个骰子推导出来的,所以是顺序遍历
            5.最终结果
                可知当i=n的时候,n个骰子的最小点数为n,所以结果为dp[n][n]~dp[n][6*n]除以所有可能结果
         */
         double[][] dp = new double[n+1][6*n+1];
         double[] ans = new double[5*n+1];//存储最后结果
         double count = Math.pow(6,n);//骰子所有的可能情况
         for(int j=1;j<=6;++j){
            dp[1][j] = 1;
         }
         for(int i=2;i<=n;++i){
             for(int j=i;j<=6*i;++j){
                 //dp[i][j] = dp[i-1][j-1]+dp[i-1][j-2]+dp[i-1][j-3]+dp[i-1][j-4]+dp[i-1][j-5]+dp[i-1][j-6];
                 for(int k=1;k<=6 && k<j;++k){//第i个骰子扔出的点数是k
                     dp[i][j] += dp[i-1][j-k] ;
                 }
             }
         }
         for(int j=n;j<=6*n;++j){
             ans[j-n] = dp[n][j]/count;
         }
         return ans;
    }
}

61. 扑克牌中的顺子

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

方法一:HashSet

class Solution {
    public boolean isStraight(int[] nums) {
    	/*
			考虑0的情况 
			考虑是否重复
			最大值和最小值差<5
		*/
        Set<Integer> set = new HashSet<>();
        int max = 1;
        int min = 14;
        for(int num : nums){
            if(num == 0) continue;
            max = Math.max(max,num);
            min = Math.min(min,num);
            if(!set.add(num)) return false;
        }
        return max-min<5;
    }
}

方法二:排序

class Solution {
    public boolean isStraight(int[] nums) {
        Arrays.sort(nums);
        int count = 0;
        for(int i=0;i<nums.length-1;++i){
            if(nums[i] == 0) {
                count++;
                continue;
            }
            if(nums[i]==nums[i+1]) return false;
        }
        return nums[nums.length-1] - nums[count]<5;
    }
}

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

0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
1 <= n <= 10^5
1 <= m <= 10^6

约瑟夫环讲解

方法一:约瑟夫环+递归

class Solution {
    public int lastRemaining(int n, int m) {
        return f(n,m);
    }
    private int f(int n,int m){
        if(n == 1){
            return 0;
        }
        int x = f(n-1,m);//n-1个数字,每次删掉第m个数,最终剩余的数
        return (x+m)%n;//约瑟夫环
    }
}

方法二:约瑟夫环+迭代

class Solution {
    public int lastRemaining(int n, int m) {
        int f = 0;
        for (int i = 2; i < n + 1; ++i) {
            f = (m + f) % i;
        }
        return f;
    }
}

63.股票的最大利润

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

方法一:动态规划

class Solution {
    public int maxProfit(int[] prices) {
        /*
           思路:
                1.确定dp数组及其下标含义
                    dp[i][0]表示第i天不持有股票,所获得最大利润
                    dp[i][1]表示第i天持有股票,所获得最大利润
                    int[][] dp = new int[prices.length][2];
                2.确定递推数组
                    (1)如果第i天不持有股票即dp[i][0],那么可以由两个状态推出来:
                        ①第i-1天就不持有股票,就保持现状,则利润为dp[i-1][0];
                        ②第i-1天持有股票,所得利润就是今天卖出后所得的利润即:
                        dp[i-1][1]+prices[i];
                        则最大利润dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
                    (3)如果第i天持有股票即dp[i][1],那么可以由两个状态推出来:
                        ①第i-1天就持有股票,则保持现状,则利润为dp[i-1][1];
                        ②第i-1天不持有股票,则今天买入,所获得利润为-prices[i];
                        则最大利润dp[i][1] = Math.max(dp[i-1][1],-prices[i]);
                3.dp数组初始化
                    由递推公式 dp[i][0] =Math.max(dp[i-1][0], prices[i] + dp[i-1][1]);
                    和 dp[i][1] = Math.max(dp[i-1][1], -prices[i]);可以看出
                    其基础都是要从dp[0][0]和dp[0][1]推导出来。
                        dp[0][0]表示第0天不持有股票,不持有股票那么现金就是0,所以dp[0][0] = 0;
                        dp[0][1]表示第0天持有股票,此时的持有股票就一定是买入股票了,因为不可能由前
                        一天推出来,所以dp[0][1] = -prices[0];
                4.确定遍历顺序
                    从递推公式可以看出dp[i]都是有dp[i-1]推导出来的,那么一定是从前向后遍历。
                5.最终结果
                    最后要获得最大收益,最后的状态一定是不持有股票,所以最终的结果为
                    dp[prices.length-1][0]
         */
            if(prices.length <2){
                return 0;
            }
            int[][] dp = new int[prices.length][2];
            dp[0][0] = 0;
            dp[0][1] = -prices[0];
            for(int i=1;i<prices.length;++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[prices.length-1][0];
    }   
}

方法二:对方法一优化

class Solution {
    public int maxProfit(int[] prices) {
        /*
	        对方法一进行优化
			从递推公式可以看出,dp[i]只是依赖于dp[i - 1]的状态。
				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]);
			那么我们只需要记录 当前天的dp状态和前一天的dp状态就可以了,可以使用滚动数组来节省空间
                
         */
            if(prices.length <2){
                return 0;
            }
            int nohold = 0;
            int hold = -prices[0];
            for(int i=1;i<prices.length;++i){
                nohold = Math.max(nohold,hold+prices[i]);
                hold = Math.max(hold,-prices[i]);
            }
            return nohold;
    }   
}

方法三:一次遍历

class Solution {
    public int maxProfit(int[] prices) {
        if(prices == null || prices.length <= 1) {
            return 0;
        }
        /*
			维护一个最小值
		*/
        int res = 0, min = prices[0];
        for(int i = 1; i < prices.length; i++) {
            if(prices[i] <= min) {
                min = prices[i];
            }else {
                res = Math.max(res, prices[i] - min);
            }
        }
        return res;
    }
}

64.求1+2+…+n

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

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


65.不用加减乘除做加法

写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
a, b 均可能是负数或 0
结果不会溢出 32 位整数

class Solution {
    public int add(int a, int b) {
        /*
            将a+b转换为进位值和不进位值之和
            a^b表示不进位的值
            (a&b)<<1表示进位的值
         */
        if(b == 0){//进位数为0时结束递归
            return a;
        }
        return add(a ^ b,(a&b)<<1);        
    }
}

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]。不能使用除法。
所有元素乘积之和不会溢出 32 位整数
a.length <= 100000

方法一:动态规划

class Solution {
    public int[] constructArr(int[] a) {
        /*
            1.维护两个dp数组,分别用来保存i左边的乘积和右边的乘积
                int[] left = new int[len];
                int[] right = new int[len];
            2.因为left[i-1]的值是a[0]*a[1]*...*a[i-2]
                left[i]的值是a[0]*a[1]*...*a[i-2]*a[i-1]
                所以left[i] = left[i-1]*a[i-1];
                同理right[i] = right[i + 1] * a[i + 1];
            3.因为当i=0时,左边是没有值的,但是left[1]=left[0]*a[0],所以初始化left[0]=1
                同理right[len-1]=1
            4.由递推关系式可知,左边是顺序遍历,右边是逆序遍历
            5.最终结果为左右两边的乘积ans[i] = left[i] * right[i];
        */
        if (a == null || a.length == 0) return a;
        int len = a.length;
        int[] left = new int[len];
        int[] right = new int[len];
        left[0] = right[len - 1] = 1;

        for (int i = 1; i < len; i++) {
            left[i] = left[i - 1] * a[i - 1];
        }
        for (int i = len - 2; i >= 0; i--) {
            right[i] = right[i + 1] * a[i + 1];
        }

        int[] ans = new int[len];
        for (int i = 0; i < len; i++) {
            ans[i] = left[i] * right[i];
        }
        return ans;
    }
}

方法二:对方法一进行优化

class Solution {
    public int[] constructArr(int[] a) {
        /*
            左边乘积使用一个动态数组进行维护,
            右边乘积使用常量维护,
            最后结果存储在动态数组中
        */
        if (a == null || a.length == 0) return a;
        int len = a.length;
        int[] b = new int[len];
        b[0] = 1;
        int temp = 1;
        for (int i = 1; i < len; i++) {
            b[i] = b[i - 1] * a[i - 1];
        }
        for (int i = len - 2; i >= 0; i--) {
            temp *= a[i+1];
            b[i] *= temp;
        }
        return b;
    }
}

67. 把字符串转换成整数

写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。

说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。

方法一:

class Solution {
    public int strToInt(String str) {
        //去前后空格
        char[] chars = str.trim().toCharArray();
        if (chars.length == 0) return 0;
        //记录第一个符合是否为负数
        int sign = 1;
        //开始遍历的位置
        int i = 1;
        //如果首个非空格字符为负号,那么从位置1开始遍历字符串,并且结果需要变成负数
        if (chars[0] == '-') {
            sign = -1;
        } else if (chars[0] != '+') { //如果首个非空格字符不是负号也不是加号,那么从第一个元素开始遍历
            i = 0;
        }
        int number = Integer.MAX_VALUE / 10;
        //结果
        int res = 0;
        for (int j = i; j < chars.length; j++) {
            //遇到非数字直接退出
            if (chars[j] > '9' || chars[j] < '0') break;
            /*
                这里这个条件的意思为,因为题目要求不能超过int范围,所以需要判断结果是否越界
                因为res每次都会 * 10 ,所以外面定义了一个int最大值除以10的数字
                此时只需要保证本次循环的res * 10 + chars[j] 不超过 int 即可保证不越界
                res > number 意思是,此时res已经大于number了,他 * 10 一定越界
                res == number && chars[j] > '7' 的意思是,当res == number时,即:214748364
                此时res * 10 变成 2147483640 此时没越界,但是还需要 + chars[j],
                而int最大值为 2147483647,所以当chars[j] > '7' 时会越界
                */
            if (res > number || (res == number && chars[j] > '7')) {//判断是否越界
                //根据字符串首负号判断返回最大值还是最小值
                return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
            }
            //字符获取数字需要 - '0' 的位移
            res = res * 10 + (chars[j] - '0');
        }
        //返回结果,需要判断正负
        return res * sign;
    }
}

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

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉搜索树中

方法一:迭代

/**
 * 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) {
        TreeNode ancestor = root;
        while (true) {
            if (p.val < ancestor.val && q.val < ancestor.val) {
                ancestor = ancestor.left;
            } else if (p.val > ancestor.val && q.val > ancestor.val) {
                ancestor = ancestor.right;
            } else {
                break;
            }
        }
        return ancestor;
    }
}

方法二:递归

/**
 * 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 > p.val && root.val > q.val){
            return lowestCommonAncestor(root.left,p,q);
        }
        if(root.val < p.val && root.val < q.val){
            return lowestCommonAncestor(root.right,p,q);
        }
        
        return root;
    }
}

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

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

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

/**
 * 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 == p.val || root.val == q.val){//当前节点和 p q 中一个相等,说明当前节点就是最近公共节点
            return root;
        }
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        if(left != null && right != null){
            //p q 一个在左一个在右
            return root;
        }
        if(left != null){
            //两个都在左边
            return left;
        }
        if(right != null){
            //两个都在右边
            return right;
        }
        return null;
    }

}

你可能感兴趣的:(笔记,java,算法,数据结构)