package javaTest.javaBase.算法; import sun.plugin.javascript.navig.Array; import java.math.BigDecimal; import java.util.*; import java.util.concurrent.ConcurrentLinkedDeque; /** * <h1>思路总结:<h1/> *##深度优先搜索DFS##(递归搜索):找到一个单元,对这一个单元进行计算,然后递归辐射到周边单元,并找到退出要求 *##广度优先BFS##(链表):蔓延,遍历该单元时,将下一次需要遍历的数据放到待遍历的数据堆栈中 *##动态规划##: * 思路一 自底向上思考问题,从一个较小规模的问题开始,通过递推的方式,逐个推导,得到最终问题规模的结果 * 思路二 单元n的结果可以根据上一个单元n-1推导。每个子单元可通过上个子单元推导 * 重点 定义状态和状态转移方程(得到不同规模的问题之间的关系) *##贪心##:每单元最大化收益计算结果 *##递归的思想##:1). 明确递归终止条件 2). 给出递归终止时的处理办法 3). 提取重复的逻辑,缩小问题规模*。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。经典算法:1.阶乘 2.斐波纳契数列 3.回文字符串的判断 *##单调栈##:是在栈的 先进后出 基础之上额外添加一个特性:从栈顶到栈底的元素是严格递增(or递减)。 *##回溯##:回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。 *回溯算法是一种特殊递归,通常有一个全局变量保存满足条件的结果 *##剪枝##(结果筛选):在进行dfs搜索时,如果某结果不符合要求,需要去除,可以叫做剪枝。 *##递归##:从已知问题的结果出发,用迭代表达式逐步推算出问题的开始的条件,即顺推法的逆过程,称为递归。 *##递推##:递推算法是一种用若干步可重复运算来描述复杂问题的方法。递推是序列计算中的一种常用算法。通常是通过计算机前面的一些项来得出序列中的指定象的值。 * 递归与递推区别:相对于递归算法,递推算法免除了数据进出栈的过程,也就是说,不需要函数不断的向边界值靠拢,而直接从边界出发,直到求出函数值。 *##归并排序##是分治思想的典型应用,它包含这样三个步骤: * 分解: 待排序的区间为 [l, r] ,令 m = (l + r)/2 * ,我们把 [l, r]分成 [l, m] 和 [m + 1, r] * 解决: 使用归并排序递归地排序两个子序列 * 合并: 把两个已经排好序的子序列 [l, m]和 [m + 1, r]合并起来 * 在待排序序列长度为 11 的时候,递归开始「回升」,因为我们默认长度为 1的序列是排好序的。 * * * * */ /** * @Param: * @return: * @Author: HelloXf * @Date: */ public class Leecode剑指offer { /** * 03. 数组中重复的数字 * [原地置换][哈希] */ public static int findRepeatNumber(int[] nums) { /* HashSet<Integer> hashSet = new HashSet<>(); for(int i:nums){ if(!hashSet.add(i)){ return i; } } return -1;*/ //原地置换 int temp; for(int i=0;i<nums.length;i++){ while (nums[i]!=i){ if(nums[i]==nums[nums[i]]){ return nums[i]; } temp=nums[i]; nums[i]=nums[temp]; nums[temp]=temp; } } return -1; } /** * 04. 二维数组中的查找 */ public static boolean findNumberIn2DArray(int[][] matrix, int target) { int x=0,y=0; if(matrix.length==0){ return false; } if(matrix[y].length==0){ return false; } for( ;x<matrix[y].length;x++){ boolean flag = false; if(matrix[y][0] > target ){ return false; } if(matrix[y][x]<target&& x<matrix[y].length-1){ continue; } if( x==matrix[y].length-1&& matrix[y][matrix[y].length-1]<target){ flag =true; } if(matrix[y][x]==target){ return true; } for( ;y<matrix.length;y++){ if(flag){ if(matrix[y][x]<target){ continue; }else if(matrix[y][x]==target){ return true; }else { return false; } }else{ if(matrix[y][x-1]<target){ continue; }else if(matrix[y][x-1]==target){ return true; }else { return false; } } } } return false; } /** * 05 请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 */ public static String replaceSpace(String s) { return s.replaceAll(" ","%20"); } public static class ListNode { int val; ListNode next; ListNode(int x) { val = x; } } /** * 06. 从尾到头打印链表 * [数据结构] */ public static int[] reversePrint(ListNode head) { Stack<ListNode> stack = new Stack<ListNode>(); ListNode temp = head; while (temp != null) { stack.push(temp); temp = temp.next; } int size = stack.size(); int[] print = new int[size]; for (int i = 0; i < size; i++) { print[i] = stack.pop().val; } return print; } /** * 剑指 Offer 09. 用两个栈实现队列 * 用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 ) */ /*static class CQueue { private Queue<Integer> ss; public CQueue() { this.ss =new ConcurrentLinkedDeque<Integer>(); } public void appendTail(int value) { ss.add(value); } public int deleteHead() { if(ss!=null&&ss.size()>0){ int s= ss.peek(); ss.remove(); return s; }else{ return -1; } } }*/ /** * 剑指 Offer 09. 用两个栈实现队列 * [数据结构] **/ static class CQueue { Stack<Integer> stack1; Stack<Integer> stack2; public CQueue() { stack1 = new Stack<Integer>(); stack2 = new Stack<Integer>(); } public void appendTail(int value) { stack1.push(value); } public int deleteHead() { // 如果第二个栈为空 if (stack2.isEmpty()) { while (!stack1.isEmpty()) { stack2.push(stack1.pop()); } } if (stack2.isEmpty()) { return -1; } else { int deleteItem = stack2.pop(); return deleteItem; } } } /** * 10-1 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N)) * 斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。 * 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。 */ public static int fib(int n) { int before =1 ,after =1; int result=0; if(n==0){ return 0; } if(n<=2){ return 1; } for(int i=3;i<=n;i++){ result = (before+after)% 1000000007; before = after; after = result; } return result; } /** * 10-2 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 * 斐波那契额数组 */ public static int numWays1(int n) { int a = 1, b = 1, sum; for(int i = 0; i < n; i++){ sum = (a + b) % 1000000007; a = b; b = sum; } return a; } /** * 10-2 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 * [排列组合] */ public static int numWays2(int n) { int max2 = n/2; int result=0; if(n<=1){ return 1; } // i为2的个数 for (int count2 =0;count2<=max2;count2++){ int re = 0; // y为1的个数 int count1= n-2*count2; //总步数 int count = count2+count1; if(count2==0||count1==0){ re++; }else { re=1; //count个 1 2 拍序结果 其中2的个数count2,1的个数count1 计算公式C(count2,count) BigDecimal fenzi =BigDecimal.ONE; BigDecimal fengmu = BigDecimal.ONE; int countRun = count2>count1?count1:count2; for (int x = countRun; x >0; x--) { fengmu=fengmu.multiply(BigDecimal.valueOf(x)); } for (int x = 0; x < countRun; x++) { fenzi = fenzi .multiply (BigDecimal.valueOf(count - x)); } re = fenzi.divide(fengmu).intValue() % 1000000007 ; } System.out.println("当2的数量为"+count2+",1的数量为:"+count1+"。结果有:"+re); result = (result +re)% 1000000007 ; } return result ; } /** * 11. 旋转数组的最小数字 * 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。 * [二分法][SR] */ public static int minArray(int[] numbers) { int low = 0; int high = numbers.length - 1; while (low < high) { int pivot = low + (high - low) / 2; if (numbers[pivot] < numbers[high]) { high = pivot; } else if (numbers[pivot] > numbers[high]) { low = pivot + 1; } else { high -= 1; } } return numbers[low]; } /** * 12. 矩阵中的路径 * 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。 * 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。 * [深度优先搜索(DFS)+ 剪枝][递归][SSR] */ public static boolean exist(char[][] board, String word) { char[] words = word.toCharArray(); for(int i = 0; i < board.length; i++) { for(int j = 0; j < board[0].length; j++) { if(dfs(board, words, i, j, 0)) { return true; } } } return false; } static boolean dfs(char[][] board, char[] word, int i, int j, int k) { if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]){ return false; } if(k == word.length - 1){ return true; } board[i][j] = '\0'; boolean res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) || dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1); //回溯 遍历失败赋值回去重新遍历 board[i][j] = word[k]; return res; } /** * 12. 矩阵中的路径 * 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。 * 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。 * [深度优先搜索(DFS)+ 回溯][递归][SSR] */ public static boolean exist1(char[][] board, String word) { if (board == null || board.length == 0 || board[0].length == 0) { return false; } char[] chars = word.toCharArray(); boolean[][] visited = new boolean[board.length][board[0].length]; for (int i = 0; i < board.length; i++) { for (int j = 0; j < board[0].length; j++) { // 从 (0, 0) 点开始进行 dfs 操作,不断地去找, // 如果以 (0, 0) 点没有对应的路径的话,那么就从 (0, 1) 点开始去找 if (dfs(board, chars, visited, i, j, 0)) { return true; } } } return false; } private static boolean dfs(char[][] board, char[] chars, boolean[][] visited, int i, int j, int start) { if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || chars[start] != board[i][j] || visited[i][j]) { return false; } if (start == chars.length - 1) { return true; } visited[i][j] = true; boolean ans = false; ans = dfs(board, chars, visited, i + 1, j, start + 1) || dfs(board, chars, visited, i - 1, j, start + 1) || dfs(board, chars, visited, i, j + 1, start + 1) || dfs(board, chars, visited, i, j - 1, start + 1); visited[i][j] = false; return ans; } /** * 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。请问该机器人能够到达多少个格子? *[广度优先搜索BFS][SSR] */ public static int movingCount1(int m, int n, int k) { if (k == 0) { return 1; } Queue<int[]> queue = new LinkedList<int[]>(); // 向右和向下的方向数组 int[] dx = {0, 1}; int[] dy = {1, 0}; boolean[][] vis = new boolean[m][n]; queue.offer(new int[]{0, 0}); vis[0][0] = true; int ans = 1; while (!queue.isEmpty()) { int[] cell = queue.poll(); int x = cell[0], y = cell[1]; for (int i = 0; i < 2; ++i) { int tx = dx[i] + x; int ty = dy[i] + y; if (tx < 0 || tx >= m || ty < 0 || ty >= n || vis[tx][ty] || get(tx) + get(ty) > k) { continue; } queue.offer(new int[]{tx, ty}); vis[tx][ty] = true; ans++; } } return ans; } /** * 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。请问该机器人能够到达多少个格子? *[递推] */ public static int movingCount(int m, int n, int k) { if (k == 0) { return 1; } boolean[][] vis = new boolean[m][n]; int ans = 1; vis[0][0] = true; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if ((i == 0 && j == 0) || get(i) + get(j) > k) { continue; } // 边界判断 if (i - 1 >= 0) { vis[i][j] |= vis[i - 1][j]; } if (j - 1 >= 0) { vis[i][j] |= vis[i][j - 1]; } ans += vis[i][j] ? 1 : 0; } } return ans; } private static int get(int x) { int res = 0; while (x != 0) { res += x % 10; x /= 10; } return res; } /** * 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。请问该机器人能够到达多少个格子? *[深度优先][递归搜索][SSR] */ int m, n, k; boolean[][] visited; public int movingCount3(int m, int n, int k) { this.m = m; this.n = n; this.k = k; this.visited = new boolean[m][n]; return dfs(0, 0, 0, 0); } public int dfs(int i, int j, int si, int sj) { if(i >= m || j >= n || k < si + sj || visited[i][j]){ return 0; } visited[i][j] = true; return 1 + dfs(i + 1, j, (i + 1) % 10 != 0 ? si + 1 : si - 8, sj) + dfs(i, j + 1, si, (j + 1) % 10 != 0 ? sj + 1 : sj - 8); } /** * 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。请问该机器人能够到达多少个格子? *[深度优先][递归搜索][SSR] */ public int movingCount4(int m, int n, int k) { this.m = m; this.n = n; this.k = k; this.visited = new boolean[m][n]; return dfs2(0, 0, k); } public int dfs2(int i, int j, int k) { if(i >= m || j >= n ||get(i) + get(j) > k || visited[i][j]){ return 0; } visited[i][j] = true; return 1 + dfs2(i + 1, j, k) + dfs2(i, j + 1, k); } /** * 剑指 Offer 14- I. 剪绳子 * 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0]*k[1]*...*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。 *推论一: 将绳子 以相等的长度等分为多段 ,得到的乘积最大。 * 推论二: 尽可能将绳子以长度 33 等分为多段时,乘积最大。 * 推论二: 若切分方案合理,绳子段切分的越多,乘积越大。 * 推论三: 为使乘积最大,只有长度为 22 和 33 的绳子不应再切分,且 33 比 22 更优 (详情见下表) 。 * [数学推导] */ public static int cuttingRope(int n) { if(n <= 3) {return n - 1;} int a = n / 3, b = n % 3; if(b == 0) {return (int)Math.pow(3, a);} if(b == 1) {return (int)Math.pow(3, a - 1) * 4;} return (int)Math.pow(3, a) * 2; } /** * 剑指 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。 * [动态规划][SSR] * 思路一:动态规划 * 我们想要求长度为n的绳子剪掉后的最大乘积,可以从前面比n小的绳子转移而来 * 用一个dp数组记录从0到n长度的绳子剪掉后的最大乘积,也就是dp[i]表示长度为i的绳子剪成m段后的最大乘积,初始化dp[2] = 1 * 我们先把绳子剪掉第一段(长度为j),如果只剪掉长度为1,对最后的乘积无任何增益,所以从长度为2开始剪 * 剪了第一段后,剩下(i - j)长度可以剪也可以不剪。如果不剪的话长度乘积即为j * (i - j);如果剪的话长度乘积即为j * dp[i - j]。取两者最大值max(j * (i - j), j * dp[i - j]) * 第一段长度j可以取的区间为[2,i),对所有j不同的情况取最大值,因此最终dp[i]的转移方程为 * dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j])) * 最后返回dp[n]即可 */ public static int cuttingRope2(int n) { int[] dp = new int[n + 1]; dp[2] = 1; for(int i = 3; i < n + 1; i++){//长度为i时,求最大乘机放入dp for(int j = 2; j < i; j++){ dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j])); System.out.println( Arrays.toString(dp)); } } return dp[n]; } /** * 思路二:贪心 * 核心思路是:尽可能把绳子分成长度为3的小段,这样乘积最大 */ public static int cuttingRope3(int n) { if(n < 4){ return n - 1; } int res = 1; while(n > 4){ res *= 3; n -= 3; } return res * n; } /** * 剑指 Offer 15. 二进制中1的个数 * 编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为 汉明重量).)。 * [位运算][与][移位][二进制] */ public static int hammingWeight(int n) { int res = 0; while(n != 0) { res += n & 1; n >>>= 1; } return res; } public static int hammingWeight2(int n) { int res = 0; while(n != 0) { res++; n &= n - 1; } return res; } /** * 剑指 Offer 16. 数值的整数次方 * 实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。 *[位运算][二进制]快速幂解析(二进制角度) */ public static 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; System.out.println("res:"+res+"-----------x:"+x+"------b:"+b); } return res; } /** *剑指 Offer 17. 打印从1到最大的n位数 * 输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。 */ public static int[] printNumbers(int n) { int end = (int)Math.pow(10, n) - 1; int[] res = new int[end]; for(int i = 0; i < end; i++) { res[i] = i + 1;} return res; } /** *剑指 Offer 17. 打印从1到最大的n位数 * 递归生成全排列[递归][SSR] */ static StringBuilder res; static int count = 0, n1=0; static char[] num, loop = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; public static String printNumbers1(int n) { n1 = n; res = new StringBuilder(); // 数字字符串集 num = new char[n]; // 定义长度为 n 的字符列表 dfs(0); // 开启全排列递归 res.deleteCharAt(res.length() - 1); // 删除最后多余的逗号 return res.toString(); // 转化为字符串并返回 } static void dfs(int x) { if(x == n1) { // 终止条件:已固定完所有位 res.append(String.valueOf(num) + ","); // 拼接 num 并添加至 res 尾部,使用逗号隔开 return; } for(char i : loop) { // 遍历 ‘0‘ - ’9‘ num[x] = i; // 固定第 x 位为 i dfs(x + 1); // 开启固定第 x + 1 位 } } /** * 递归生成全排列 */ static int nine = 0, start; public static String printNumbers2(int n) { n1 = n; res = new StringBuilder(); num = new char[n]; start = n - 1; dfs1(0); res.deleteCharAt(res.length() - 1); return res.toString(); } static void dfs1(int x) { if(x == n1) { String s = String.valueOf(num).substring(start); if(!s.equals("0")){ res.append(s + ",");} if(n1 - start == nine){ start--;} return; } for(char i : loop) { if(i == '9') {nine++;} num[x] = i; dfs(x + 1); } nine--; } /** * 剑指 Offer 18. 删除链表的节点 *给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。 * 返回删除后的链表的头节点。 */ public static ListNode deleteNode(ListNode head, int val) { if(head.val == val) {return head.next;} ListNode pre = head, cur = head.next; while(cur != null && cur.val != val) { pre = cur; cur = cur.next; } if(cur != null) {pre.next = cur.next;} return head; } /** * 剑指 Offer 20. 表示数值的字符串 * 请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。 * [自动机][SSR] */ public static boolean isNumber(String s) { Map<State, Map<CharType, State>> transfer = new HashMap<State, Map<CharType, State>>(); Map<CharType, State> initialMap = new HashMap<CharType, State>() {{ put(CharType.CHAR_SPACE, State.STATE_INITIAL); put(CharType.CHAR_NUMBER, State.STATE_INTEGER); put(CharType.CHAR_POINT, State.STATE_POINT_WITHOUT_INT); put(CharType.CHAR_SIGN, State.STATE_INT_SIGN); }}; transfer.put(State.STATE_INITIAL, initialMap); Map<CharType, State> intSignMap = new HashMap<CharType, State>() {{ put(CharType.CHAR_NUMBER, State.STATE_INTEGER); put(CharType.CHAR_POINT, State.STATE_POINT_WITHOUT_INT); }}; transfer.put(State.STATE_INT_SIGN, intSignMap); Map<CharType, State> integerMap = new HashMap<CharType, State>() {{ put(CharType.CHAR_NUMBER, State.STATE_INTEGER); put(CharType.CHAR_EXP, State.STATE_EXP); put(CharType.CHAR_POINT, State.STATE_POINT); put(CharType.CHAR_SPACE, State.STATE_END); }}; transfer.put(State.STATE_INTEGER, integerMap); Map<CharType, State> pointMap = new HashMap<CharType, State>() {{ put(CharType.CHAR_NUMBER, State.STATE_FRACTION); put(CharType.CHAR_EXP, State.STATE_EXP); put(CharType.CHAR_SPACE, State.STATE_END); }}; transfer.put(State.STATE_POINT, pointMap); Map<CharType, State> pointWithoutIntMap = new HashMap<CharType, State>() {{ put(CharType.CHAR_NUMBER, State.STATE_FRACTION); }}; transfer.put(State.STATE_POINT_WITHOUT_INT, pointWithoutIntMap); Map<CharType, State> fractionMap = new HashMap<CharType, State>() {{ put(CharType.CHAR_NUMBER, State.STATE_FRACTION); put(CharType.CHAR_EXP, State.STATE_EXP); put(CharType.CHAR_SPACE, State.STATE_END); }}; transfer.put(State.STATE_FRACTION, fractionMap); Map<CharType, State> expMap = new HashMap<CharType, State>() {{ put(CharType.CHAR_NUMBER, State.STATE_EXP_NUMBER); put(CharType.CHAR_SIGN, State.STATE_EXP_SIGN); }}; transfer.put(State.STATE_EXP, expMap); Map<CharType, State> expSignMap = new HashMap<CharType, State>() {{ put(CharType.CHAR_NUMBER, State.STATE_EXP_NUMBER); }}; transfer.put(State.STATE_EXP_SIGN, expSignMap); Map<CharType, State> expNumberMap = new HashMap<CharType, State>() {{ put(CharType.CHAR_NUMBER, State.STATE_EXP_NUMBER); put(CharType.CHAR_SPACE, State.STATE_END); }}; transfer.put(State.STATE_EXP_NUMBER, expNumberMap); Map<CharType, State> endMap = new HashMap<CharType, State>() {{ put(CharType.CHAR_SPACE, State.STATE_END); }}; transfer.put(State.STATE_END, endMap); int length = s.length(); State state = State.STATE_INITIAL; for (int i = 0; i < length; i++) { CharType type = toCharType(s.charAt(i)); if (!transfer.get(state).containsKey(type)) { return false; } else { state = transfer.get(state).get(type); } } return state == State.STATE_INTEGER || state == State.STATE_POINT || state == State.STATE_FRACTION || state == State.STATE_EXP_NUMBER || state == State.STATE_END; } public static CharType toCharType(char ch) { if (ch >= '0' && ch <= '9') { return CharType.CHAR_NUMBER; } else if (ch == 'e' || ch == 'E') { return CharType.CHAR_EXP; } else if (ch == '.') { return CharType.CHAR_POINT; } else if (ch == '+' || ch == '-') { return CharType.CHAR_SIGN; } else if (ch == ' ') { return CharType.CHAR_SPACE; } else { return CharType.CHAR_ILLEGAL; } } enum State { STATE_INITIAL, STATE_INT_SIGN, STATE_INTEGER, STATE_POINT, STATE_POINT_WITHOUT_INT, STATE_FRACTION, STATE_EXP, STATE_EXP_SIGN, STATE_EXP_NUMBER, STATE_END } enum CharType { CHAR_NUMBER, CHAR_EXP, CHAR_POINT, CHAR_SIGN, CHAR_SPACE, CHAR_ILLEGAL } /** * 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 * 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。 */ public static int[] exchange(int[] nums) { int n = nums.length; int[] res = new int[n]; int index = 0; for(int num : nums){ if(num % 2 == 1){ res[index++] = num; } } for(int num : nums){ if(num % 2 == 0){ res[index++] = num; } } return res; } /** * 头尾双指针[SR] */ public int[] exchange1(int[] nums) { int left = 0, right = nums.length - 1; while(left <= right){ while(left <= right && nums[left] % 2 == 1) {left++;} while(left <= right && nums[right] % 2 == 0) {right--;} if(left > right) { break;} int tmp = nums[left]; nums[left] = nums[right]; nums[right] = tmp; } return nums; } /** *剑指 Offer 22. 链表中倒数第k个节点 * 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。 */ public static ListNode getKthFromEnd(ListNode head, int k) { HashMap<Integer,ListNode> headMap = new HashMap<Integer, ListNode>(); ListNode cur = head; Integer key = 1; while (cur !=null){ headMap.put(key,cur); key++; cur = cur.next; } return headMap.get(key-k); } /** *剑指 Offer 22. 链表中倒数第k个节点 * 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。 *[双指针] */ public static ListNode getKthFromEnd1(ListNode head, int k) { // 初始化都指向链表头 ListNode cur = head; ListNode target = head; // cur指针先前进 k-1 次 for (int i = 0; i < k - 1; i++) { cur = cur.next; } // 当 cur 指针指向链表尾时,target 指针指向倒数第 k 个节点 while (cur.next != null) { cur = cur.next; target = target.next; } return target; } /** * 剑指 Offer 24. 反转链表 * 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。 *[数据结构][链表][SSR] */ public static ListNode reverseList(ListNode head){ ListNode prev = null; ListNode curr = head; while (curr != null) { //记录下一个等待指针遍历的位置 ListNode next = curr.next; //将指针遍历到的节点拼接到 新的结果链表的前面 curr.next = prev; //存放新的结果链表 prev = curr; //指针后移 curr = next; } return prev; } /** * [递归][SSR] */ public static ListNode reverseList2(ListNode head) { if (head == null || head.next == null) { return head; } ListNode newHead = reverseList2(head.next); //赋值的过程都是先放入方法栈中,最后head为倒数第一个时开始赋值 head.next.next = head; //防止链表循环 head.next = null; return newHead; } /** * 剑指 Offer 25. 合并两个排序的链表 * 输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。 * [数据结构][链表][迭代] */ public static ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode dum = new ListNode(0), cur = dum; 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 ? l1 : l2; return dum.next; } /** * 剑指 Offer 25. 合并两个排序的链表 * 输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。 *[递归][SSR] */ public static ListNode mergeTwoLists2(ListNode l1, ListNode l2) { if(l1 == null || l2 == null){ return l1 != null ? l1 : l2; } if(l1.val <= l2.val){ l1.next = mergeTwoLists(l1.next, l2); return l1; }else{ l2.next = mergeTwoLists(l1, l2.next); return l2; } } static class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(int x) { val = x; } } /** * 剑指 Offer 26. 树的子结构 * 输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构) * B是A的子结构, 即 A中有出现和B相同的结构和节点值。 *[对称性递归][二叉树] */ public static boolean isSubStructure(TreeNode A, TreeNode B) { return (A != null && B != null) && (recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B)); } static boolean recur(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); } /** * 剑指 Offer 27. 二叉树的镜像 * 请完成一个函数,输入一个二叉树,该函数输出它的镜像。 * [递归][二叉树] */ public static TreeNode mirrorTree(TreeNode root) { if (root == null) { return null; } TreeNode left = mirrorTree(root.left); TreeNode right = mirrorTree(root.right); root.left = right; root.right = left; return root; } /** * 剑指 Offer 28. 对称的二叉树 * 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。 * [递归][二叉树] */ public static boolean isSymmetric(TreeNode root) { return root == null ? true : recur1(root.left, root.right); } static boolean recur1(TreeNode L, TreeNode R) { if (L == null && R == null) { return true; } if (L == null || R == null || L.val != R.val) { return false; } return recur1(L.left, R.right) && recur(L.right, R.left); } /** * 剑指 Offer 29. 顺时针打印矩阵 * 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。 */ public static int[] spiralOrder(int[][] matrix) { if(matrix.length == 0) {return new int[0];} int l = 0, //游标 r = matrix[0].length - 1, //数组列数 t = 0, //游标 b = matrix.length - 1, //数组排数 x = 0;//返回数值下标 int[] res = new int[(r + 1) * (b + 1)];//返回值 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];} // top to bottom. if(l > --r) {break;}//防止重复遍历列 for(int i = r; i >= l; i--) {res[x++] = matrix[b][i];} // right to left. if(t > --b) {break;}//防止重复遍历排 for(int i = b; i >= t; i--) {res[x++] = matrix[i][l];} // bottom to top. if(++l > r) {break;}//防止重复遍历列 } return res; } /** * 按层遍历 * [思维] */ public int[] spiralOrder2(int[][] matrix) { if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { return new int[0]; } int rows = matrix.length, columns = matrix[0].length; int[] order = new int[rows * columns]; int index = 0; int left = 0, right = columns - 1, top = 0, bottom = rows - 1; while (left <= right && top <= bottom) { for (int column = left; column <= right; column++) { order[index++] = matrix[top][column]; } for (int row = top + 1; row <= bottom; row++) { order[index++] = matrix[row][right]; } if (left < right && top < bottom) { for (int column = right - 1; column > left; column--) { order[index++] = matrix[bottom][column]; } for (int row = bottom; row > top; row--) { order[index++] = matrix[row][left]; } } left++; right--; top++; bottom--; } return order; } /** * 剑指 Offer 30. 包含min函数的栈 * 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。 */ class MinStack { Stack<Integer> stack = new Stack<>(); Stack<Integer> source = new Stack<>(); /** initialize your data structure here. */ public MinStack() { stack = new Stack<>(); source = new Stack<>(); } public void push(int x) { this.source.push(x); if(stack.empty() || stack.peek()>x){ stack.push(x); } } public void pop() { if(this.source.pop().equals( stack.peek())){ stack.pop(); } } public int top() { return source.peek(); } public int min() { return stack.peek(); } } /** * 剑指 Offer 31. 栈的压入、弹出序列 * 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。 * */ public static boolean validateStackSequences(int[] pushed, int[] popped) { Stack<Integer> stack = new Stack<>(); int i = 0; for(int num : pushed) { stack.push(num); // num 入栈 while(!stack.isEmpty() && stack.peek() == popped[i]) { // 循环判断与出栈 stack.pop(); i++; } } return stack.isEmpty(); } /** * 剑指 Offer 32 - I. 从上到下打印二叉树 * 从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。 * [BFS][广度优先搜索] */ public int[] levelOrder(TreeNode root) { if(root == null){ return new int[0];} Queue<TreeNode> queue = new LinkedList<TreeNode>(){{ add(root); }}; ArrayList<Integer> ans = new ArrayList<>(); 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; } /** * 剑指 Offer 33 - I. 从上到下打印二叉树2 * 从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。 * */ public List<List<Integer>> levelOrder2(TreeNode root) { LinkedList<TreeNode> queue = new LinkedList<>(); List<List<Integer>> res = new ArrayList<>(); if(root != null) {queue.add(root);} while(!queue.isEmpty()) { List<Integer> tmp = new ArrayList<>(); for(int i = queue.size(); i > 0; i--) { TreeNode node = queue.poll(); tmp.add(node.val); if(node.left != null) {queue.add(node.left);} if(node.right != null) {queue.add(node.right);} } res.add(tmp); } return res; } /** * 剑指 Offer 32 - III. 从上到下打印二叉树 III *请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。 *[队列][数据结构][SR][二叉树] */ public List<List<Integer>> levelOrder3(TreeNode root) { Queue<TreeNode> queue = new LinkedList<>(); List<List<Integer>> res = new ArrayList<>(); if(root != null) {queue.add(root);} while(!queue.isEmpty()) { LinkedList<Integer> tmp = new LinkedList<>(); for(int i = queue.size(); i > 0; i--) { TreeNode node = queue.poll(); 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。 * 假设输入的数组的任意两个数字都互不相同。 * * 解析: * 根据二叉搜索树的定义,可以通过递归,判断所有子树的 正确性 (即其后序遍历是否满足二叉搜索树的定义) ,若所有子树都正确,则此序列为二叉搜索树的后序遍历。 * * 终止条件: 当 i \geq ji≥j ,说明此子树节点数量 \leq 1≤1 ,无需判别正确性,因此直接返回 truetrue ; * 递推工作: * 划分左右子树: 遍历后序遍历的 [i, j][i,j] 区间元素,寻找 第一个大于根节点 的节点,索引记为 mm 。此时,可划分出左子树区间 [i,m-1][i,m−1] 、右子树区间 [m, j - 1][m,j−1] 、根节点索引 jj 。 * 判断是否为二叉搜索树: * 左子树区间 [i, m - 1][i,m−1] 内的所有节点都应 << postorder[j]postorder[j] 。而第 1.划分左右子树 步骤已经保证左子树区间的正确性,因此只需要判断右子树区间即可。 * 右子树区间 [m, j-1][m,j−1] 内的所有节点都应 >> postorder[j]postorder[j] 。实现方式为遍历,当遇到 \leq postorder[j]≤postorder[j] 的节点则跳出;则可通过 p = jp=j 判断是否为二叉搜索树。 * 返回值: 所有子树都需正确才可判定正确,因此使用 与逻辑符 \&\&&& 连接。 * p = jp=j : 判断 此树 是否正确。 * recur(i, m - 1)recur(i,m−1) : 判断 此树的左子树 是否正确。 * recur(m, j - 1)recur(m,j−1) : 判断 此树的右子树 是否正确。 * * [递归分治][二叉树][SSR] */ public boolean verifyPostorder(int[] postorder) { return verifyPostorderrecur(postorder, 0, postorder.length - 1); } boolean verifyPostorderrecur(int[] postorder, int i, int j) { if(i >= j) {return true;} int p = i; while(postorder[p] < postorder[j]) {p++;} int m = p; while(postorder[p] > postorder[j]) {p++;} return p == j && verifyPostorderrecur(postorder, i, m - 1) && verifyPostorderrecur(postorder, m, j - 1); } /** * 剑指 Offer 33. 二叉搜索树的后序遍历序列 * 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。 * 假设输入的数组的任意两个数字都互不相同。 * [单调栈][SSR] */ public boolean verifyPostorder2(int[] postorder) { Stack<Integer> stack = new Stack<>(); int root = Integer.MAX_VALUE; for (int i = postorder.length - 1; i >= 0; i--) { if (postorder[i] > root){ return false;} while (!stack.isEmpty() && stack.peek() > postorder[i]) { root = stack.pop(); } stack.add(postorder[i]); } return true; } List<List<Integer>> ret = new LinkedList<List<Integer>>(); Deque<Integer> path = new LinkedList<Integer>(); /** * 剑指 Offer 34. 二叉树中和为某一值的路径 * 输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。 * [DFS][深度优先搜索][SSR][二叉树] */ public List<List<Integer>> pathSum(TreeNode root, int target) { pathSumdfs(root, target); return ret; } public void pathSumdfs(TreeNode root, int target) { if (root == null) { return; } path.offerLast(root.val); target -= root.val; if (root.left == null && root.right == null && target == 0) { ret.add(new LinkedList<Integer>(path)); } pathSumdfs(root.left, target); pathSumdfs(root.right, target); path.pollLast(); } //存放子节点对应父节点的映射 Map<TreeNode, TreeNode> map = new HashMap<TreeNode, TreeNode>(); /** * 剑指 Offer 34. 二叉树中和为某一值的路径 * [广度优先搜索][BFS][SSR][二叉树] */ public List<List<Integer>> pathSum2(TreeNode root, int target) { if (root == null) { return ret; } //分别存放即将遍历的每个节点以及走到该节点时的合计值 Queue<TreeNode> queueNode = new LinkedList<TreeNode>(); Queue<Integer> queueSum = new LinkedList<Integer>(); queueNode.offer(root); queueSum.offer(0); while (!queueNode.isEmpty()) { TreeNode node = queueNode.poll(); int rec = queueSum.poll() + node.val; if (node.left == null && node.right == null) { if (rec == target) { getPath(node); } } else { if (node.left != null) { map.put(node.left, node); queueNode.offer(node.left); queueSum.offer(rec); } if (node.right != null) { map.put(node.right, node); queueNode.offer(node.right); queueSum.offer(rec); } } } return ret; } public void getPath(TreeNode node) { List<Integer> temp = new LinkedList<Integer>(); while (node != null) { temp.add(node.val); node = map.get(node); } Collections.reverse(temp); ret.add(new LinkedList<Integer>(temp)); } //复杂链表 class Node { int val; Node next; Node random; public Node(int val) { this.val = val; this.next = null; this.random = null; } } /** * 剑指 Offer 35. 复杂链表的复制 * 请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。 *思路及算法 * * 本题要求我们对一个特殊的链表进行深拷贝。如果是普通链表,我们可以直接按照遍历的顺序创建链表节点。而本题中因为随机指针的存在,当我们拷贝节点时,「当前节点的随机指针指向的节点」可能还没创建,因此我们需要变换思路。一个可行方案是,我们利用回溯的方式,让每个节点的拷贝操作相互独立。对于当前节点,我们首先要进行拷贝,然后我们进行「当前节点的后继节点」和「当前节点的随机指针指向的节点」拷贝,拷贝完成后将创建的新节点的指针返回,即可完成当前节点的两指针的赋值。 * * 具体地,我们用哈希表记录每一个节点对应新节点的创建情况。遍历该链表的过程中,我们检查「当前节点的后继节点」和「当前节点的随机指针指向的节点」的创建情况。如果这两个节点中的任何一个节点的新节点没有被创建,我们都立刻递归地进行创建。当我们拷贝完成,回溯到当前层时,我们即可完成当前节点的指针赋值。注意一个节点可能被多个其他节点指向,因此我们可能递归地多次尝试拷贝某个节点,为了防止重复拷贝,我们需要首先检查当前节点是否被拷贝过,如果已经拷贝过,我们可以直接从哈希表中取出拷贝后的节点的指针并返回即可。 * * 在实际代码中,我们需要特别判断给定节点为空节点的情况。 *[递归][回溯][SSR][链表] */ Map<Node, Node> cachedNode = new HashMap<Node, Node>(); public Node copyRandomList(Node head) { if (head == null) { return null; } if (!cachedNode.containsKey(head)) { Node headNew = new Node(head.val); cachedNode.put(head, headNew); headNew.next = copyRandomList(head.next); headNew.random = copyRandomList(head.random); } return cachedNode.get(head); } //[迭代] + 节点拆分 public Node copyRandomList2(Node head) { if (head == null) { return null; } for (Node node = head; node != null; node = node.next.next) { Node nodeNew = new Node(node.val); nodeNew.next = node.next; node.next = nodeNew; } for (Node node = head; node != null; node = node.next.next) { Node nodeNew = node.next; nodeNew.random = (node.random != null) ? node.random.next : null; } Node headNew = head.next; for (Node node = head; node != null; node = node.next) { Node nodeNew = node.next; node.next = node.next.next; nodeNew.next = (nodeNew.next != null) ? nodeNew.next.next : null; } return headNew; } class NodeList { public int val; public NodeList left; public NodeList right; public NodeList() {} public NodeList(int _val) { val = _val; } public NodeList(int _val,NodeList _left,NodeList _right) { val = _val; left = _left; right = _right; } }; // 打印中序遍历 void dfs中序遍历(NodeList root) { if(root == null) {return;} dfs中序遍历(root.left); // 左 System.out.println(root.val); // 根 dfs中序遍历(root.right); // 右 } NodeList pre, head; /**剑指 Offer 36. 二叉搜索树与双向链表 *输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。 * [递归][二叉树][链表][中序遍历][深度优先搜索算法][DFS] */ public NodeList treeToDoublyList(NodeList root) { if(root == null) {return null;} dfstreeToDoublyList(root); head.left = pre; pre.right = head; return head; } void dfstreeToDoublyList(NodeList cur) { if(cur == null) {return;} dfstreeToDoublyList(cur.left); if(pre != null) {pre.right = cur;} else{ head = cur;} cur.left = pre; pre = cur; dfstreeToDoublyList(cur.right); } /** * 剑指 Offer 37. 序列化二叉树 * 请实现两个函数,分别用来序列化和反序列化二叉树。 * * 你需要设计一个算法来实现二叉树的序列化与反序列化。 * 这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串 * 并且将这个字符串反序列化为原始的树结构。 * [SSR][序列化][二叉树] */ // Encodes a tree to a single string. public static String serialize(TreeNode root) { return rserialize(root, ""); } public static TreeNode deserialize(String data) { String[] dataArray = data.split(","); List<String> dataList = new LinkedList<String>(Arrays.asList(dataArray)); return rdeserialize(dataList); } public static String rserialize(TreeNode root, String str) { if (root == null) { str += "None,"; } else { str += root.val + ","; str = rserialize(root.left, str); str = rserialize(root.right, str); } return str; } public static TreeNode rdeserialize(List<String> dataList) { if (dataList.get(0).equals("None")) { dataList.remove(0); return null; } TreeNode root = new TreeNode(Integer.valueOf(dataList.get(0))); dataList.remove(0); root.left = rdeserialize(dataList); root.right = rdeserialize(dataList); return root; } /** * 案例 * [回溯][SSR] */ static class Permutations { //题目描述:Given a collection of distinct integers, return all possible permutations.(给定一组不同的整数,返回其所有的可能组合) public List<List<Integer>> permute(int[] nums) { //一个全局变量,用于保存所有集合 List<List<Integer>> list = new ArrayList<>(); //传入三个参数,没有附加参数 backtrack(list, new ArrayList<>(), nums); return list; } private void backtrack(List<List<Integer>> list, List<Integer> tempList, int[] nums) { //一个终结条件,也就是满足条件的时候 if (tempList.size() == nums.length) { //全局变量添加一个满足条件的集合 list.add(new ArrayList<>(tempList)); } else { for (int i = 0; i < nums.length; i++) { if (tempList.contains(nums[i])) { continue; } //如果tempList没有包含nums[i]才添加 tempList.add(nums[i]); //递归调用,此时的tempList一直在变化,直到满足终结条件才结束 backtrack(list, tempList, nums); System.out.println("tempList的内容:" + tempList + "-------" + "i的值:" + i); //它移除tempList最后一个元素的作用就是返回上一次调用时的数据,也就是希望返回之前的节点再去重新搜索满足条件。这样才能实现回溯 tempList.remove(tempList.size() - 1); } } } } /** * 剑指 Offer 38. 字符串的排列 * 输入一个字符串,打印出该字符串中字符的所有排列。 *[递归][剪枝](类似与剪枝,因为不是在DFS中)[SSR] */ static class Solution38_1 { //为了让递归函数添加结果方便,定义到函数之外,这样无需带到递归函数的参数列表中 List<String> list = new ArrayList<>(); //同;但是其赋值依赖c,定义声明分开 char[] c; public String[] permutation(String s) { c = s.toCharArray(); //从第一层开始递归 dfs(0); //将字符串数组ArrayList转化为String类型数组 return list.toArray(new String[list.size()]); } private void dfs(int x) { //当递归函数到达第三层,就返回,因为此时第二第三个位置已经发生了交换 if (x == c.length - 1) { //将字符数组转换为字符串 list.add(String.valueOf(c)); return; } //为了防止同一层递归出现重复元素 HashSet<Character> set = new HashSet<>(); //这里就很巧妙了,第一层可以是a,b,c那么就有三种情况,这里i = x,正巧dfs(0),正好i = 0开始 // 当第二层只有两种情况,dfs(1)i = 1开始 for (int i = x; i < c.length; i++){ //发生剪枝,当包含这个元素的时候,直接跳过 if (set.contains(c[i])){ continue; } set.add(c[i]); //交换元素,这里很是巧妙,当在第二层dfs(1),x = 1,那么i = 1或者 2, 不是交换1和1,要就是交换1和2 swap(i,x); //进入下一层递归 dfs(x + 1); //返回时交换回来,这样保证到达第1层的时候,一直都是abc。这里捋顺一下,开始一直都是abc,那么第一位置总共就3个交换 //分别是a与a交换,这个就相当于 x = 0, i = 0; // a与b交换 x = 0, i = 1; // a与c交换 x = 0, i = 2; //就相当于上图中开始的三条路径 //第一个元素固定后,每个引出两条路径, // b与b交换 x = 1, i = 1; // b与c交换 x = 1, i = 2; //所以,结合上图,在每条路径上标注上i的值,就会非常容易好理解了 swap(i,x); } } private void swap(int i, int x) { char temp = c[i]; c[i] = c[x]; c[x] = temp; } } /** * 剑指 Offer 38. 字符串的排列 * 输入一个字符串,打印出该字符串中字符的所有排列。 * [递归][剪枝][回溯][SSR][代表性] */ static class Solution38_2 { //结果集 List<String> rec; //防重复 boolean[] vis; public String[] permutation(String s) { //字符串长度 int n = s.length(); rec = new ArrayList<String>(); vis = new boolean[n]; //字符 char[] arr = s.toCharArray(); //字符排序 Arrays.sort(arr); StringBuffer perm = new StringBuffer(); backtrack(arr, 0, n, perm); int size = rec.size(); String[] recArr = new String[size]; for (int i = 0; i < size; i++) { recArr[i] = rec.get(i); } return recArr; } //思路及解法 // //我们将这个问题看作有 nn 个排列成一行的空位,我们需要从左往右依次填入题目给定的 n 个字符,每个字符只能使用一次。首先可以想到穷举的算法,即从左往右每一个空位都依次尝试填入一个字符,看是否能填完这 n 个空位,编程实现时,我们可以用「回溯法」来模拟这个过程。 //定义递归函数backtrack(i,perm) 表示当前排列为perm,下一个待填入的空位是第 i 个空位(下标从 00 开始)。那么该递归函数分为两个情况: //如果 i=n,说明我们已经填完了 n 个空位,找到了一个可行的解,我们将 perm 放入答案数组中,递归结束。 //如果 i<n,此时需要考虑第 i 个空位填哪个字符。根据题目要求我们肯定不能填已经填过的字符,因此很容易想到的一个处理手段是我们定义一个标记数组 vis 来标记已经填过的字符,那么在填第 i 个字符的时候我们遍历题目给定的 n 个字符,如果这个字符没有被标记过,我们就尝试填入,并将其标记,继续尝试填下一个空位,即调用函数 backtrack(i+1,perm)。回溯时,我们需要要撤销该空位填的字符以及对该字符的标记,并继续向当前空位尝试填入其他没被标记过的字符。 public void backtrack(char[] arr, int i, int n, StringBuffer perm) { if (i == n) { rec.add(perm.toString()); return; } for (int j = 0; j < n; j++) { //保证在填每一个空位的时候重复字符只会被填入一次。具体地,我们首先对原字符串排序,保证相同的字符都相邻,在递归函数中,我们限制每次填入的字符一定是这个字符所在重复字符集合中「从左往右第一个未被填入的字符」 // 保证没有浏览过 if (vis[j] || (j > 0 && !vis[j - 1] && arr[j - 1] == arr[j])) { continue; } vis[j] = true; perm.append(arr[j]); backtrack(arr, i + 1, n, perm); perm.deleteCharAt(perm.length() - 1); vis[j] = false; } } } /** * 剑指 Offer 38. 字符串的排列 * 输入一个字符串,打印出该字符串中字符的所有排列。 *对于一个长度为 n 的字符串(假设字符互不重复),其排列方案数共有: * n×(n−1)×(n−2)…×2×1 *重复排列方案与剪枝: * * 当字符串存在重复字符时,排列方案中也存在重复的排列方案。为排除重复方案,需在固定某位字符时,保证 “每种字符只在此位固定一次” ,即遇到重复字符时不交换,直接跳过。从 DFS 角度看,此操作称为 “剪枝” 。 *[下一个排列] */ static class Solution38_3 { public String[] permutation(String s) { List<String> ret = new ArrayList<String>(); char[] arr = s.toCharArray(); Arrays.sort(arr); do { ret.add(new String(arr)); } while (nextPermutation(arr)); int size = ret.size(); String[] retArr = new String[size]; for (int i = 0; i < size; i++) { retArr[i] = ret.get(i); } return retArr; } public boolean nextPermutation(char[] arr) { int i = arr.length - 2; while (i >= 0 && arr[i] >= arr[i + 1]) { i--; } if (i < 0) { return false; } int j = arr.length - 1; while (j >= 0 && arr[i] >= arr[j]) { j--; } swap(arr, i, j); reverse(arr, i + 1); return true; } public void swap(char[] arr, int i, int j) { char temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } public void reverse(char[] arr, int start) { int left = start, right = arr.length - 1; while (left < right) { swap(arr, left, right); left++; right--; } } } /** * 剑指 Offer 39. 数组中出现次数超过一半的数字 * 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 * [HASH] */ public static int majorityElement(int[] nums) { HashMap<Integer,Integer> count = new HashMap<>(); int lengthHalf = Integer.valueOf(nums.length/2) ; if(lengthHalf==0){ return nums[0]; } for(int s :nums){ if(count.get(s)==null){ count.put(s,1); }else{ if(count.get(s)+1>lengthHalf){ return s; } count.put(s,count.get(s)+1); } } return 0; } /** * 剑指 Offer 39. 数组中出现次数超过一半的数字 * [排序] */ public static int majorityElement2(int[] nums) { Arrays.sort(nums); return nums[nums.length / 2]; } /** * 剑指 Offer 39. 数组中出现次数超过一半的数字 * [分治][SSR] * 我们使用经典的分治算法递归求解,直到所有的子问题都是长度为 1 的数组。 * 长度为 1 的子数组中唯一的数显然是众数,直接返回即可。 * 如果回溯后某区间的长度大于 1,我们必须将左右子区间的值合并。 * 如果它们的众数相同,那么显然这一段区间的众数是它们相同的值。 * 否则,我们需要比较两个众数在整个区间内出现的次数来决定该区间的众数。 * */ static class Solution39 { private int countInRange(int[] nums, int num, int lo, int hi) { int count = 0; for (int i = lo; i <= hi; i++) { if (nums[i] == num) { count++; } } return count; } private int majorityElementRec(int[] nums, int lo, int hi) { // base case; the only element in an array of size 1 is the majority // element. if (lo == hi) { return nums[lo]; } // recurse on left and right halves of this slice. int mid = (hi - lo) / 2 + lo; int left = majorityElementRec(nums, lo, mid); int right = majorityElementRec(nums, mid + 1, hi); // if the two halves agree on the majority element, return it. if (left == right) { return left; } // otherwise, count each element and return the "winner". int leftCount = countInRange(nums, left, lo, hi); int rightCount = countInRange(nums, right, lo, hi); return leftCount > rightCount ? left : right; } public int majorityElement(int[] nums) { return majorityElementRec(nums, 0, nums.length - 1); } } /** * Boyer-Moore 投票算法[投票] */ public static int majorityElement5(int[] nums) { int count = 0; Integer candidate = null; for (int num : nums) { if (count == 0) { candidate = num; } count += (num == candidate) ? 1 : -1; } return candidate; } /** * 剑指 Offer 40. 最小的k个数 * 输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。 * [排序] */ public static int[] getLeastNumbers(int[] arr, int k) { int[] vec = new int[k]; Arrays.sort(arr); for (int i = 0; i < k; ++i) { vec[i] = arr[i]; } return vec; } /** * 剑指 Offer 40. 最小的k个数 * [数据结构][堆] */ class Solution40_2 { public int[] getLeastNumbers(int[] arr, int k) { int[] vec = new int[k]; if (k == 0) { // 排除 0 的情况 return vec; } PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() { @Override public int compare(Integer num1, Integer num2) { return 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) { vec[i] = queue.poll(); } return vec; } } /** * 剑指 Offer 40. 最小的k个数 * [递归][快排思想] */ class Solution40 { public int[] getLeastNumbers(int[] arr, int k) { randomizedSelected(arr, 0, arr.length - 1, k); int[] vec = new int[k]; for (int i = 0; i < k; ++i) { vec[i] = arr[i]; } return vec; } private void randomizedSelected(int[] arr, int l, int r, int k) { if (l >= r) { return; } int pos = randomizedPartition(arr, l, r); int num = pos - l + 1; if (k == num) { return; } else if (k < num) { randomizedSelected(arr, l, pos - 1, k); } else { randomizedSelected(arr, pos + 1, r, k - num); } } // 基于随机的划分 private int randomizedPartition(int[] nums, int l, int r) { int i = new Random().nextInt(r - l + 1) + l; swap(nums, r, i); return partition(nums, l, r); } private int partition(int[] nums, int l, int r) { int pivot = nums[r]; int i = l - 1; for (int j = l; j <= r - 1; ++j) { if (nums[j] <= pivot) { i = i + 1; swap(nums, i, j); } } swap(nums, i + 1, r); return i + 1; } private void swap(int[] nums, int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } } /** * 剑指 Offer 41. 数据流中的中位数 * 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。 * 如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。 * [栈][根堆][顶堆] */ static class MedianFinder { /** initialize your data structure here. */ Queue<Integer> A, B; public MedianFinder() { A = new PriorityQueue<>(); // 小顶堆,保存较大的一半 B = new PriorityQueue<>((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() : (A.peek() + B.peek()) / 2.0; } } /** * 剑指 Offer 42. 连续子数组的最大和 * 输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。 * 要求时间复杂度为O(n)。 * [动态规划][SSR] */ public 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; } /** * 剑指 Offer 42. 连续子数组的最大和 * 输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。 * 要求时间复杂度为O(n)。 * [分治][线段树][SSR] */ class Solution { public class Status { public int lSum, rSum, mSum, iSum; public Status(int lSum, int rSum, int mSum, int iSum) { this.lSum = lSum; this.rSum = rSum; this.mSum = mSum; this.iSum = iSum; } } public int maxSubArray(int[] nums) { return getInfo(nums, 0, nums.length - 1).mSum; } public Status getInfo(int[] a, int l, int r) { if (l == r) { return new Status(a[l], a[l], a[l], a[l]); } int m = (l + r) >> 1; Status lSub = getInfo(a, l, m); Status rSub = getInfo(a, m + 1, r); return pushUp(lSub, rSub); } public Status pushUp(Status l, Status r) { int iSum = l.iSum + r.iSum; int lSum = Math.max(l.lSum, l.iSum + r.lSum); int rSum = Math.max(r.rSum, r.iSum + l.rSum); int mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum + r.lSum); return new Status(lSum, rSum, mSum, iSum); } } /** * 剑指 Offer 43. 1~n 整数中 1 出现的次数 * 输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。 * 例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。 * 枚举每一数位上 11 的个数 * */ public static int countDigitOne(int n) { // mulk 表示 10^k // 在下面的代码中,可以发现 k 并没有被直接使用到(都是使用 10^k) // 但为了让代码看起来更加直观,这里保留了 k long mulk = 1; int ans = 0; for (int k = 0; n >= mulk; ++k) { ans += (n / (mulk * 10)) * mulk + Math.min(Math.max(n % (mulk * 10) - mulk + 1, 0), mulk); mulk *= 10; } return ans; } /** *剑指 Offer 44. 数字序列中某一位的数字 * 数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。 * 请写一个函数,求任意第n位对应的数字。 *[迭代] */ public static int findNthDigit(int n) { int digit = 1; long start = 1; long count = 9; while (n > count) { // 1. n -= count; digit += 1; start *= 10; count = digit * start * 9; } long num = start + (n - 1) / digit; // 2. return Long.toString(num).charAt((n - 1) % digit) - '0'; // 3. } /** * 剑指 Offer 45. 把数组排成最小的数 * 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。 * */ public String minNumber(int[] nums) { String[] strs = new String[nums.length]; for(int i = 0; i < nums.length; i++) { strs[i] = String.valueOf(nums[i]);} Arrays.sort(strs, (x, y) -> (x + y).compareTo(y + x)); StringBuilder res = new StringBuilder(); for(String s : strs) { res.append(s);} return res.toString(); } /** * 剑指 Offer 45. 把数组排成最小的数 * 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。 *[快速排序][SSR] */ class Solution45 { public String minNumber(int[] nums) { String[] strs = new String[nums.length]; for(int i = 0; i < nums.length; i++) strs[i] = String.valueOf(nums[i]); quickSort(strs, 0, strs.length - 1); StringBuilder res = new StringBuilder(); for(String s : strs) res.append(s); return res.toString(); } void quickSort(String[] strs, int l, int r) { if(l >= r) {return;} int i = l, j = r; String tmp = strs[i]; while(i < j) { while((strs[j] + strs[l]).compareTo(strs[l] + strs[j]) >= 0 && i < j) {j--;} while((strs[i] + strs[l]).compareTo(strs[l] + strs[i]) <= 0 && i < j) {i++;} tmp = strs[i]; strs[i] = strs[j]; strs[j] = tmp; } strs[i] = strs[l]; strs[l] = tmp; quickSort(strs, l, i - 1); quickSort(strs, i + 1, r); } } /** * 标准快排算法 * 既快又省空间 * [快速排序][SSR] */ public class QuickSort { public void quickSort(int[] arr,int low,int high){ int i,j,temp,t; if(low>high){ return; } i=low; j=high; //temp就是基准位 temp = arr[low]; while (i<j) { //先看右边,依次往左递减 while (temp<=arr[j]&&i<j) { j--; } //再看左边,依次往右递增 while (temp>=arr[i]&&i<j) { i++; } //如果满足条件则交换 if (i<j) { t = arr[j]; arr[j] = arr[i]; arr[i] = t; } } //最后将基准为与i和j相等位置的数字交换 arr[low] = arr[i]; arr[i] = temp; //递归调用左半数组 quickSort(arr, low, j-1); //递归调用右半数组 quickSort(arr, j+1, high); } } /** * 剑指 Offer 46. 把数字翻译成字符串 * 给定一个数字,我们按照如下规则把它翻译为字符串: * 0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。 * 一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。 * [动态规划][递推][递归][SSR] * *我们可以归纳出翻译的规则,字符串的第 i 位置: * 可以单独作为一位来翻译 * 如果第 i - 1位和第 i 位组成的数字在 10到 25 之间,可以把这两位连起来翻译 * 到这里,我们发现它和「198. 打家劫舍」非常相似。我们可以用 f(i) 表示以第 i 位结尾的前缀串翻译的方案数,考虑第 ii 位单独翻译和与前一位连接起来再翻译对 f(i) 的贡献。单独翻译对 f(i) 的贡献为 f(i - 1);如果第 i - 1 位存在,并且第 i - 1位和第 i位形成的数字 xx 满足 10≤x≤25,那么就可以把第 i - 1位和第 i 位连起来一起翻译,对 f(i)的贡献为 f(i - 2),否则为 0。我们可以列出这样的动态规划转移方程: * f(i)=f(i−1)+f(i−2)[i−1≥0,10≤x≤25] * 边界条件是 f(-1) = 0f(−1)=0,f(0) = 1f(0)=1。方程中 [c] 的意思是 cc 为真的时候 [c] = 1[c]=1,否则 [c] = 0[c]=0。 * 有了这个方程我们不难给出一个时间复杂度为 O(n) ,空间复杂度为 O(n) 的实现。考虑优化空间复杂度:这里的 f(i)f(i) 只和它的前两项 f(i - 1) 和 f(i - 2) 相关,我们可以运用「滚动数组」思想把 ff 数组压缩成三个变量,这样空间复杂度就变成了 O(1)。 * * 类似青蛙跳台 * * [动态规划][SSR] */ public static int translateNum46_动态规划版1(int num) { String s = String.valueOf(num); //a为dp[n-1],b为 dp[n-2] int a = 1, b = 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; } /** *存储过程的解法,易理解 */ public int translateNum46动态规划版2(int num) { String s = String.valueOf(num); int[] dp = new int[s.length()+1]; dp[0] = 1; dp[1] = 1; for(int i = 2; i <= s.length(); i ++){ String temp = s.substring(i-2, i); if(temp.compareTo("10") >= 0 && temp.compareTo("25") <= 0) {dp[i] = dp[i-1] + dp[i-2];} else { dp[i] = dp[i-1];} } return dp[s.length()]; } public static int translateNum46(int num) { if(num < 10) {return 1;} if(num <= 25) {return 2;} int digit = 1; while(num / digit >= 10){ digit *= 10; } int high = num / digit; int low = num - high * digit; int high2 = num / (digit/10); int low2 = num - high2 * (digit/10); return ( high2 > 25 ) ? translateNum46(low) : translateNum46(low2) + translateNum46(low); } /** * 剑指 Offer 47. 礼物的最大价值 * 在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。 * 你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。 * 给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物? * [SSR][求最优解][动态规划] */ public int maxValue47(int[][] grid) { int row = grid.length; int column = grid[0].length; //dp[i][j]表示从grid[0][0]到grid[i - 1][j - 1]时的最大价值 int[][] dp = new int[row + 1][column + 1]; for (int i = 1; i <= row; i++) { for (int j = 1; j <= column; j++) { dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1]; } } return dp[row][column]; } /** * DIY的方法,leecode解题超时,由于是全部遍历,其实不用,用int[][] gridMax 右下角存储每个"子数组"当前最大值结果。 [递归][DFS] */ static class Solution47 { static int xl,y1,result=0; public static int maxValue(int[][] grid) { xl =grid.length; y1=grid[0].length; result= 0; maxV(grid,0,0,grid[0][0]); return result; } public static void maxV(int[][] grid,int x,int y,int cunt ){ if(x==xl-1||y==y1-1){ result = cunt+grid[x][y]>result?cunt+grid[x][y]:result; return; } maxV(grid,x+1,y,grid[x][y]+cunt); maxV(grid,x,y+1,grid[x][y]+cunt); } } /** * 优化后 */ private static class Solution47_2 { static int xl,y1; static int[][] result; public static int maxValue(int[][] grid) { xl =grid.length; y1=grid[0].length; result= new int[xl][y1]; result[0][0]=0; maxV(grid,0,0); return result[xl-1][y1-1]; } public static void maxV(int[][] grid,int x,int y ){ if(x==xl||y==y1){ return ; } int yy= y==0?0: result[x][y-1]; int xx= x==0?0: result[x-1][y]; result[x][y] =xx+grid[x][y]>yy+grid[x][y]?xx+grid[x][y]:yy+grid[x][y]; maxV(grid,x+1,y); maxV(grid,x,y+1); } } private static class Solution47_3 { //用来存储该节点到右下角的最大价值 static int[][] store; static int row, column; public static int maxValue(int[][] grid) { row = grid.length; column = grid[0].length; store = new int[row][column]; return helper(grid, 0, 0); } public static int helper(int[][] grid, int i, int j) { int right = 0, down = 0; if (i == row - 1 && j == column - 1) { return grid[i][j];} //当前节点越界 if (i < 0 || j < 0 || i >= row || j >= column) { return 0;} //右边没有被访问 if (j + 1 < column && store[i][j + 1] == 0) { right = helper(grid, i, j + 1); store[i][j + 1] = right; } //右边已经被访问了,取存储的值 if (j + 1 < column && store[i][j + 1] > 0) { right = store[i][j + 1]; } //下边没有被访问了 if (i + 1 < row && store[i + 1][j] == 0) { down = helper(grid, i + 1, j); store[i + 1][j] = down; } //下边已经被访问了,取存储的值 if (i + 1 < row && store[i + 1][j] == 1) { down = store[i + 1][j]; } //该节点到右下角的最大价值 return Math.max(right, down) + grid[i][j]; } } /** * 剑指 Offer 48. 最长不含重复字符的子字符串 * 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。 * [动态规划] */ public static int lengthOfLongestSubstring(String s) { Map<Character, Integer> dic = new HashMap<>(); int res = 0, tmp = 0; for(int j = 0; j < s.length(); j++) { int i = dic.getOrDefault(s.charAt(j), -1); // 获取索引 i dic.put(s.charAt(j), j); // 更新哈希表 tmp = tmp < j - i ? tmp + 1 : j - i; // dp[j - 1] -> dp[j] res = Math.max(res, tmp); // max(dp[j - 1], dp[j]) } return res; } /** * 剑指 Offer 48. 最长不含重复字符的子字符串 * 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。 * [双指针] */ public static int lengthOfLongestSubstring2(String s) { Map<Character, Integer> dic = new HashMap<>(); int i = -1, res = 0; for(int j = 0; j < s.length(); j++) { if(dic.containsKey(s.charAt(j))) {i = Math.max(i, dic.get(s.charAt(j))); }// 更新左指针 i dic.put(s.charAt(j), j); // 哈希表记录 res = Math.max(res, j - i); // 更新结果 } return res; } /** * 剑指 Offer 49. 丑数 * 我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。 *[动态规划][SSR] */ public int nthUglyNumber(int n) { int[] dp = new int[n + 1]; dp[1] = 1; int p2 = 1, p3 = 1, p5 = 1; for (int i = 2; i <= n; i++) { int num2 = dp[p2] * 2, num3 = dp[p3] * 3, num5 = dp[p5] * 5; dp[i] = Math.min(Math.min(num2, num3), num5); if (dp[i] == num2) { p2++; } if (dp[i] == num3) { p3++; } if (dp[i] == num5) { p5++; } } return dp[n]; } /** * 剑指 Offer 50. 第一个只出现一次的字符 * 在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。 */ public char firstUniqChar(String s) { Integer l = s.length(); String re=" "; for(Integer i=0;i<l;i++){ String nowS= s.substring(i,i+1); String leftS=s.substring(0,i)+s.substring(i+1,l); if(!leftS.contains(nowS)){ return nowS.toCharArray()[0]; } } return re.toCharArray()[0]; } public char firstUniqChar2(String s) { HashMap<Character, Boolean> dic = new HashMap<>(); char[] sc = s.toCharArray(); for(char c : sc) { dic.put(c, !dic.containsKey(c));} for(char c : sc) { if(dic.get(c)) {return c;}} return ' '; } /** * 剑指 Offer 51. 数组中的逆序对 * 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。 * 输入一个数组,求出这个数组中的逆序对的总数。 *[自创超时] */ public static int reversePairs(int[] nums) { int res = 0; for(int x=0;x<nums.length;x++){ for(int y=x+1;y<nums.length;y++){ if(nums[x]>nums[y]){ sysout(nums[x]+"-"+nums[y]); res++; } } } return res; } /** * [归并排序][SSR][分治][递归] */ public class Solution51 { public int reversePairs(int[] nums) { int len = nums.length; if (len < 2) { return 0; } int[] copy = new int[len]; for (int i = 0; i < len; i++) { copy[i] = nums[i]; } int[] temp = new int[len]; return reversePairs(copy, 0, len - 1, temp); } private int reversePairs(int[] nums, int left, int right, int[] temp) { if (left == right) { return 0; } int mid = left + (right - left) / 2; int leftPairs = reversePairs(nums, left, mid, temp); int rightPairs = reversePairs(nums, mid + 1, right, temp); if (nums[mid] <= nums[mid + 1]) { return leftPairs + rightPairs; } int crossPairs = mergeAndCount(nums, left, mid, right, temp); return leftPairs + rightPairs + crossPairs; } private int mergeAndCount(int[] nums, int left, int mid, int right, int[] temp) { for (int i = left; i <= right; i++) { temp[i] = nums[i]; } int i = left; int j = mid + 1; int count = 0; for (int k = left; k <= right; k++) { if (i == mid + 1) { nums[k] = temp[j]; j++; } else if (j == right + 1) { nums[k] = temp[i]; i++; } else if (temp[i] <= temp[j]) { nums[k] = temp[i]; i++; } else { nums[k] = temp[j]; j++; count += (mid - i + 1); } } return count; } } /** * 离散化树状数组 * [离散化][树状数组] * 「树状数组」是一种可以动态维护序列前缀和的数据结构,它的功能是: * * 单点更新 update(i, v): 把序列 i 位置的数加上一个值 v,这题 v = 1 * 区间查询 query(i): 查询序列 [1⋯i] 区间的区间和,即 i 位置的前缀和 * 修改和查询的时间代价都是 O(logn),其中 nn 为需要维护前缀和的序列的长度。 */ class Solution51_2 { public int reversePairs(int[] nums) { int n = nums.length; int[] tmp = new int[n]; System.arraycopy(nums, 0, tmp, 0, n); // 离散化 Arrays.sort(tmp); for (int i = 0; i < n; ++i) { nums[i] = Arrays.binarySearch(tmp, nums[i]) + 1; } // 树状数组统计逆序对 BIT bit = new BIT(n); int ans = 0; for (int i = n - 1; i >= 0; --i) { ans += bit.query(nums[i] - 1); bit.update(nums[i]); } return ans; } } class BIT { private int[] tree; private int n; public BIT(int n) { this.n = n; this.tree = new int[n + 1]; } public int lowbit(int x) { return x & (-x); } public int query(int x) { int ret = 0; while (x != 0) { ret += tree[x]; x -= lowbit(x); } return ret; } public void update(int x) { while (x <= n) { ++tree[x]; x += lowbit(x); } } } /** * 剑指 Offer 52. 两个链表的第一个公共节点 */ public ListNode getIntersectionNode(ListNode headA, ListNode headB) { ListNode PA = headA; ListNode PB = headB; while (PA != PB) { PA = PA == null ? headB : PA.next; PB = PB == null ? headA : PB.next; } return PA; } /** * 剑指 Offer 53 - I. 在排序数组中查找数字 I */ public int search(int[] nums, int target) { // 搜索右边界 right int i = 0, j = nums.length - 1; while(i <= j) { int m = (i + j) / 2; if(nums[m] <= target) {i = m + 1;} else {j = m - 1;} } int right = i; // 若数组中无 target ,则提前返回 if(j >= 0 && nums[j] != target) {return 0;} // 搜索左边界 right i = 0; j = nums.length - 1; while(i <= j) { int m = (i + j) / 2; if(nums[m] < target) { i = m + 1; }else { j = m - 1; } } int left = j; return right - left - 1; } /** * 剑指 Offer 53 - I. 在排序数组中查找数字 I * 二分法 */ public int search2(int[] nums, int target) { return helper(nums, target) - helper(nums, target - 1); } int helper(int[] nums, int tar) { int i = 0, j = nums.length - 1; while(i <= j) { int m = (i + j) / 2; if(nums[m] <= tar) {i = m + 1;} else {j = m - 1;} } return i; } /** * 剑指 Offer 53 - II. 0~n-1中缺失的数字 * 一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。 * 在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。 * [二分][SR] */ public int missingNumber(int[] nums) { int i = 0, j = nums.length - 1; while(i <= j) { int m = (i + j) / 2; if(nums[m] == m) {i = m + 1;} else {j = m - 1;} } return i; } /** * 剑指 Offer 54. 二叉搜索树的第k大节点 * [中序遍历][SSR] */ class Solution54 { int res, k; public int kthLargest(TreeNode root, int k) { this.k = k; dfs(root); return res; } void dfs(TreeNode root) { if(root == null) {return;} dfs(root.right); if(k == 0) {return;} if(--k == 0) {res = root.val;} dfs(root.left); } } /** * 输入一棵二叉树的根节点,求该树的深度。 * 从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。 * 后序遍历(DFS)[SSR] */ public int maxDepth1(TreeNode root) { if(root == null) {return 0;} return Math.max(maxDepth1(root.left), maxDepth1(root.right)) + 1; } /** * 层序遍历(BFS)[SSR] */ public int maxDepth2(TreeNode root) { if(root == null) {return 0;} List<TreeNode> queue = new LinkedList<TreeNode>() {{ add(root); }}, tmp; 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,那么它就是一棵平衡二叉树。 * 后序遍历 + 剪枝 (从底至顶)[SSR] */ static class Solution55 { public boolean isBalanced(TreeNode root) { return recur(root) != -1; } private int recur(TreeNode root) { if (root == null) {return 0;} int left = recur(root.left); if(left == -1) {return -1;} int right = recur(root.right); if(right == -1) {return -1;} return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1; } } /** * 先序遍历 + 判断深度 (从顶至底)[SSR] */ class Solution55_2 { public boolean isBalanced(TreeNode root) { if (root == null) {return true;} return Math.abs(depth(root.left) - depth(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right); } private int depth(TreeNode root) { if (root == null) {return 0;} return Math.max(depth(root.left), depth(root.right)) + 1; } } /** * 剑指 Offer 56 - I. 数组中数字出现的次数 * 一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。 * 请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。 * [分组异或][位运算] */ public int[] singleNumbers(int[] nums) { int ret = 0; for (int n : nums) { ret ^= n; } int div = 1; while ((div & ret) == 0) { 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}; } /** * 有限状态自动机[位运算] */ public int singleNumber(int[] nums) { int ones = 0, twos = 0; for(int num : nums){ ones = ones ^ num & ~twos; twos = twos ^ num & ~ones; } return ones; } /** * 剑指 Offer 56 - II. 数组中数字出现的次数 II * 在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。 * 遍历统计[位运算] */ public int singleNumber2(int[] nums) { int[] counts = new int[32]; for(int num : nums) { for(int j = 0; j < 32; j++) { counts[j] += num & 1; num >>>= 1; } } int res = 0, m = 3; for(int i = 0; i < 32; i++) { res <<= 1; res |= counts[31 - i] % m; } return res; } /** * 剑指 Offer 57. 和为s的两个数字 * 输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。 * 如果有多对数字的和等于s,则输出任意一对即可。 * [双指针] */ public int[] twoSum(int[] nums, int target) { int i = 0, j = nums.length - 1; while(i < j) { int s = nums[i] + nums[j]; if(s < target) {i++;} else if(s > target) {j--;} else {return new int[] { nums[i], nums[j] };} } return new int[0]; } /** * 剑指 Offer 57 - II. 和为s的连续正数序列 * 输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。 * 序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。 * [滑动窗口][SSR] */ public int[][] findContinuousSequence(int target) { int left = 1; // 滑动窗口的左边界 int right = 2; // 滑动窗口的右边界 int sum = left + right; // 滑动窗口中数字的和 List<int[]> res = new ArrayList<>(); //窗口的左边是窗口内的最小数字,只能小于等于target / 2, //因为题中要求的是至少含有两个数 while (left <= target / 2) { if (sum < target) { //如果窗口内的值比较小,右边界继续向右移动, //来扩大窗口 sum += ++right; } else if (sum > target) { //如果窗口内的值比较大,左边边界往右移动, //缩小窗口 sum -= left++; } else { //如果窗口内的值正好等于target,就把窗口内的值记录 //下来,然后窗口的左边和右边同时往右移一步 int[] arr = new int[right - left + 1]; for (int k = left; k <= right; k++) { arr[k - left] = k; } res.add(arr); //左边和右边同时往右移一位 sum -= left++; sum += ++right; } } //把结果转化为数组 return res.toArray(new int[res.size()][]); } /** * 数学公式:S=n*a+n*(n-1)/2 */ public int[][] findContinuousSequence2(int target) { List<int[]> res = new ArrayList<>(); int n = 2; //死循环 while (true) { int total = target - n * (n - 1) / 2; //当分子小于等于0的时候,退出循环 if (total <= 0) { break;} //如果首项是正整数,满足条件 if (total % n == 0) { int[] arr = new int[n]; //找出首项的值 int startValue = total / n; for (int k = 0; k < n; k++) { arr[k] = startValue + k; } res.add(arr); } //继续找 n++; } //反转,比如当target等于9的时候,结果是 //[[4,5],[2,3,4]],但题中要求的是不同 // 序列按照首个数字从小到大排列,所以这里反转一下 Collections.reverse(res); //把list转化为数组 return res.toArray(new int[res.size()][]); } /** * 数学公式2: * 假如target是两个连续数字的和,那么这个序列的首项就是(target-1)/2。 * 假如target是三个连续数字的和,那么这个序列的首项就是(target-1-2)/3。 * 假如target是四个连续数字的和,那么这个序列的首项就是(target-1-2-3)/4。 */ public int[][] findContinuousSequence3(int target) { List<int[]> res = new ArrayList<>(); //因为至少是两个数,所以target先减1 target--; for (int n = 2; target > 0; n++) { //找到了一组满足条件的序列 if (target % n == 0) { int[] arr = new int[n]; //找出首项的值 int startValue = target / n; for (int k = 0; k < n; k++) { arr[k] = startValue + k; } res.add(arr); } target -= n; } Collections.reverse(res); //把list转化为数组 return res.toArray(new int[res.size()][]); } /** * 字符串反转 */ public static String reverseWords(String s) { /* char[] ss=s.toCharArray(); int leng = ss.length-1; for(int i=leng;i>=leng/2;i--){ char st= ss[i]; ss[i]=ss[leng-i]; ss[leng-i]=st; } StringBuffer str5 = new StringBuffer(); for (char s1 : ss) { str5.append(s1); } return str5.toString() ;*/ return new StringBuffer(s).reverse().toString(); } /** * 剑指 Offer 58 - I. 翻转单词顺序 * 输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。 * 为简单起见,标点符号和普通字母一样处理。 * 例如输入字符串"I am a student. ",则输出"student. a am I"。 * [双指针] */ public String reverseWords2(String s) { s = s.trim(); // 删除首尾空格 int j = s.length() - 1, i = j; StringBuilder res = new StringBuilder(); while(i >= 0) { while(i >= 0 && s.charAt(i) != ' ') {i--;} // 搜索首个空格 res.append(s.substring(i + 1, j + 1) + " "); // 添加单词 while(i >= 0 && s.charAt(i) == ' ') {i--;} // 跳过单词间空格 j = i; // j 指向下个单词的尾字符 } return res.toString().trim(); // 转化为字符串并返回 } /** * 分割 + 倒序 */ public String reverseWords3(String s) { String[] strs = s.trim().split(" "); // 删除首尾空格,分割字符串 StringBuilder res = new StringBuilder(); for(int i = strs.length - 1; i >= 0; i--) { // 倒序遍历单词列表 if(strs[i].equals("")) {continue;} // 遇到空单词则跳过 res.append(strs[i] + " "); // 将单词拼接至 StringBuilder } return res.toString().trim(); // 转化为字符串,删除尾部空格,并返回 } /** * 剑指 Offer 58 - II. 左旋转字符串 * 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。 * 字符串切片 */ public String reverseLeftWords(String s, int n) { return s.substring(n, s.length()) + s.substring(0, n); } public String reverseLeftWords2(String s, int n) { StringBuilder res = new StringBuilder(); for(int i = n; i < s.length(); i++) {res.append(s.charAt(i));} for(int i = 0; i < n; i++) { res.append(s.charAt(i));} return res.toString(); } /** * 字符串遍历拼接 */ public String reverseLeftWords3(String s, int n) { String res = ""; for(int i = n; i < n + s.length(); i++) { res += s.charAt(i % s.length());} return res; } /** * 剑指 Offer 59 - I. 滑动窗口的最大值 * 给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。 * [双端队列][单调栈][滑动窗口] */ class Solution59 { public int[] maxSlidingWindow(int[] nums, int k) { if(nums.length == 0 || k == 0) {return new int[0];} Deque<Integer> deque = new LinkedList<>(); int[] res = new int[nums.length - k + 1]; for(int j = 0, i = 1 - k; j < nums.length; i++, j++) { // 删除 deque 中对应的 nums[i-1] if(i > 0 && deque.peekFirst() == nums[i - 1]) { deque.removeFirst();} // 保持 deque 递减 while(!deque.isEmpty() && deque.peekLast() < nums[j]) { deque.removeLast();} deque.addLast(nums[j]); // 记录窗口最大值 if(i >= 0) { res[i] = deque.peekFirst();} } return res; } } /** * 剑指 Offer 59 - II. 队列的最大值 * 请定义一个队列并实现函数 max_value 得到队列里的最大值, * 要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。 * 若队列为空,pop_front 和 max_value 需要返回 -1 * [双栈] */ 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; } } /** * 剑指 Offer 60. n个骰子的点数 * 把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。 * [动态规划][SSR] * 由于新增骰子的点数只可能为 11 至 66 ,因此概率 f(n - 1, x)仅与 f(n, x + 1) , * f(n, x + 2), ... , f(n, x + 6) 相关。 * 因而,遍历 f(n - 1) 中各点数和的概率,并将其相加至 f(n) * 中所有相关项,即可完成 f(n - 1)至 f(n) 的递推。 */ class Solution60 { public double[] dicesProbability(int n) { double[] dp = new double[6]; Arrays.fill(dp, 1.0 / 6.0); for (int i = 2; i <= n; i++) { double[] tmp = new double[5 * i + 1]; for (int j = 0; j < dp.length; j++) { for (int k = 0; k < 6; k++) { tmp[j + k] += dp[j] / 6.0; } } dp = tmp; } return dp; } } /** * 剑指 Offer 61. 扑克牌中的顺子 * 从若干副扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。 */ class Solution61 { public boolean isStraight(int[] nums) { Set<Integer> repeat = new HashSet<>(); int max = 0, min = 14; for(int num : nums) { if(num == 0) continue; // 跳过大小王 max = Math.max(max, num); // 最大牌 min = Math.min(min, num); // 最小牌 if(repeat.contains(num)) return false; // 若有重复,提前返回 false repeat.add(num); // 添加此牌至 Set } return max - min < 5; // 最大牌 - 最小牌 < 5 则可构成顺子 } } /** * 剑指 Offer 64. 求1+2+…+n * 求 1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。 */ class Solution64 { public int sumNums(int n) { boolean flag = n > 0 && (n += sumNums(n - 1)) > 0; return n; } } /** * 剑指 Offer 65. 不用加减乘除做加法 * 写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。 *本题考察对位运算的灵活使用,即使用位运算实现加法。 * 设两数字的二进制形式 a, b,其求和 s = a + b,a(i) 代表 a 的二进制第 i 位,则分为以下四种情况: * 无进位和 与 异或运算 规律相同,进位 和 与运算 规律相同(并需左移一位)。 * 因此,无进位和 n 与进位 c 的计算公式如下; */ class Solution65 { public int add(int a, int b) { while(b != 0) { // 当进位为 0 时跳出 int c = (a & b) << 1; // c = 进位 a ^= b; // a = 非进位和 b = c; // b = 进位 } return a; } } /** * 剑指 Offer 66. 构建乘积数组 * 给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1], * 其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, * 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。 * */ class Solution66 { public int[] constructArr(int[] a) { int len = a.length; if(len == 0) return new int[0]; int[] b = new int[len]; b[0] = 1; int tmp = 1; for(int i = 1; i < len; i++) { b[i] = b[i - 1] * a[i - 1]; } for(int i = len - 2; i >= 0; i--) { tmp *= a[i + 1]; b[i] *= tmp; } return b; } } /** * 剑指 Offer 67. 把字符串转换成整数 */ class Solution67 { public int strToInt(String str) { int res = 0, bndry = Integer.MAX_VALUE / 10; int i = 0, sign = 1, length = str.length(); if(length == 0) return 0; while(str.charAt(i) == ' ') if(++i == length) return 0; if(str.charAt(i) == '-') sign = -1; if(str.charAt(i) == '-' || str.charAt(i) == '+') i++; for(int j = i; j < length; j++) { if(str.charAt(j) < '0' || str.charAt(j) > '9') break; if(res > bndry || res == bndry && str.charAt(j) > '7') return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; res = res * 10 + (str.charAt(j) - '0'); } return sign * res; } } /** * 剑指 Offer 68 - I. 二叉搜索树的最近公共祖先 */ class Solution68 { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { while(root != null) { if(root.val < p.val && root.val < q.val) // p,q 都在 root 的右子树中 root = root.right; // 遍历至右子节点 else if(root.val > p.val && root.val > q.val) // p,q 都在 root 的左子树中 root = root.left; // 遍历至左子节点 else break; } return root; } } /** * d */ class Solution682 { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { if(root == null || root == p || root == q) return root; TreeNode left = lowestCommonAncestor(root.left, p, q); TreeNode right = lowestCommonAncestor(root.right, p, q); if(left == null) return right; if(right == null) return left; return root; } } public static void main(String[] arg){ sysout(reverseWords("I am a student. ")); //sysout(lengthOfLongestSubstring("abcabcbb") ); //reversePairs(new int[]{7,5,6,4}); //sysout(lengthOfLongestSubstring("dvdf") ); /*int[][] 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}};*/ /*int[][] matrix = {{3,8,6,0,5,9,9,6,3,4,0,5,7,3,9,3}, {0,9,2,5,5,4,9,1,4,6,9,5,6,7,3,2}, {8,2,2,3,3,3,1,6,9,1,1,6,6,2,1,9}, {1,3,6,9,9,5,0,3,4,9,1,0,9,6,2,7}, {8,6,2,2,1,3,0,0,7,2,7,5,4,8,4,8}, {4,1,9,5,8,9,9,2,0,2,5,1,8,7,0,9}, {6,2,1,7,8,1,8,5,5,7,0,2,5,7,2,1},{8,1,7,6,2,8,1,2,2,6,4,0,5,4,1,3},{9,2,1,7,6,1,4,3,8,6,5,5,3,9,7,3},{0,6,0,2,4,3,7,6,1,3,8,6,9,0,0,8},{4,3,7,2,4,3,6,4,0,3,9,5,3,6,9,3},{2,1,8,8,4,5,6,5,8,7,3,7,7,5,8,3},{0,7,6,6,1,2,0,3,5,0,8,0,8,7,4,3},{0,4,3,4,9,0,1,9,7,7,8,6,4,6,9,5},{6,5,1,9,9,2,2,7,4,2,7,2,2,3,7,2},{7,1,9,6,1,2,7,0,9,6,6,4,4,5,1,0},{3,4,9,2,8,3,1,2,6,9,7,0,2,4,2,0},{5,1,8,8,4,6,8,5,2,4,1,6,2,2,9,7}}; */ //sysout( Solution47_2.maxValue(matrix)); // sysout( Solution47.maxValue(matrix)); /* sysout(translateNum46_动态规划版1(1234120193)); sysout(translateNum46(1234120193));*/ // countDigitOne(120); /* MedianFinder obj = new MedianFinder(); obj.addNum(1); obj.addNum(2); sysout(obj.findMedian()); obj.addNum(3); sysout(obj.findMedian());*/ /* System.out.println( Arrays.toString ((new Solution38_1()).permutation("fba"))); System.out.println( Arrays.toString ((new Solution38_2()).permutation("fba"))); System.out.println( Arrays.toString ((new Solution38_3()).permutation("fba")));*/ // System.out.println( majorityElement( new int[]{1})); /* PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() { @Override public int compare(Integer num1, Integer num2) { return num2 - num1; } }); queue.add(1); queue.add(13); queue.add(2); sysout(queue.poll()); sysout(queue.poll()); sysout(queue.poll()); queue.offer(1); queue.offer(13); queue.offer(2); sysout(queue.poll());sysout( queue.poll());sysout( queue.poll());*/ /*ListNode node = new ListNode(1); node.next = new ListNode(2); node.next.next = new ListNode(3);*/ /* TreeNode oo= deserialize("1,2,None,None,3,4,None,None,5,None,None,"); System.out.println(serialize(deserialize("1,2,None,None,3,4,None,None,5,None,None,"))); System.out.println(oo);*/ /* int[] nums={1,2,3}; (new Permutations()).permute(nums);*/ // reverseList2(node); // System.out.println(printNumbers2(3)); // myPow(2,3); //cuttingRope2(20); //System.out.println(Arrays.toString(exchange(new int[]{1,2,3,4,5,6}))); //cuttingRope2(20); //System.out.println(movingCount( 20, 30, 20)); //System.out.println(movingCount1( 15, 15, 3)); /*char[][] board = new char[][]{"ABCE".toCharArray(), "DECS".toCharArray(), "AEEE".toCharArray()}; System.out.println(exist( board, "ABCCED")); System.out.println(exist1( board, "ABCCED"));*/ // System.out.println(minArray(new int[]{3,4,5,1,2})); /*System.out.println(numWays1(51)); System.out.println(numWays2(51));*/ //System.out.println(fib(6)); /* CQueue obj = new CQueue(); obj.appendTail(1); int param_2 = obj.deleteHead();*/ /* int[][] 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}}; int target = 16; System.out.println(findNumberIn2DArray(matrix, target));*/ /* int nums[] = {2, 3, 1, 0, 2, 5, 3}; System.out.println(findRepeatNumber(nums)); */ //add测试 //结论:add测试一样 /* LinkedList<Integer> linkedList = new LinkedList<>(); Stack<Integer> stack = new Stack<>(); linkedList.add(1); linkedList.add(2); linkedList.add(3); stack.add(1); stack.add(2); stack.add(3); System.out.println(linkedList); System.out.println(stack); //push测试 //结论:测试不一样,linkedlist的push是addFirst;stack是在数组尾部addElement linkedList.push(4); linkedList.push(5); linkedList.push(6); stack.push(4); stack.push(5); stack.push(6); stack.pop(); stack.pop(); linkedList.pop(); linkedList.pop(); System.out.println(linkedList); System.out.println(stack);*/ } public static void sysout(Object dd){ System.out.println(dd); } }