看完整本书,花心思整理,觉得有帮助别忘了点个赞
1 package javaTest.javaBase.算法; 2 3 import sun.plugin.javascript.navig.Array; 4 5 import java.math.BigDecimal; 6 import java.util.*; 7 import java.util.concurrent.ConcurrentLinkedDeque; 8 9 /** 10 * <h1>思路总结:<h1/> 11 *##深度优先搜索DFS##(递归搜索):找到一个单元,对这一个单元进行计算,然后递归辐射到周边单元,并找到退出要求 12 *##广度优先BFS##(链表):蔓延,遍历该单元时,将下一次需要遍历的数据放到待遍历的数据堆栈中 13 *##动态规划##: 14 * 思路一 自底向上思考问题,从一个较小规模的问题开始,通过递推的方式,逐个推导,得到最终问题规模的结果 15 * 思路二 单元n的结果可以根据上一个单元n-1推导。每个子单元可通过上个子单元推导 16 * 重点 定义状态和状态转移方程(得到不同规模的问题之间的关系) 17 *##贪心##:每单元最大化收益计算结果 18 *##递归的思想##:1). 明确递归终止条件 2). 给出递归终止时的处理办法 3). 提取重复的逻辑,缩小问题规模*。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。经典算法:1.阶乘 2.斐波纳契数列 3.回文字符串的判断 19 *##单调栈##:是在栈的 先进后出 基础之上额外添加一个特性:从栈顶到栈底的元素是严格递增(or递减)。 20 *##回溯##:回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。 21 *回溯算法是一种特殊递归,通常有一个全局变量保存满足条件的结果 22 *##剪枝##(结果筛选):在进行dfs搜索时,如果某结果不符合要求,需要去除,可以叫做剪枝。 23 *##递归##:从已知问题的结果出发,用迭代表达式逐步推算出问题的开始的条件,即顺推法的逆过程,称为递归。 24 *##递推##:递推算法是一种用若干步可重复运算来描述复杂问题的方法。递推是序列计算中的一种常用算法。通常是通过计算机前面的一些项来得出序列中的指定象的值。 25 * 递归与递推区别:相对于递归算法,递推算法免除了数据进出栈的过程,也就是说,不需要函数不断的向边界值靠拢,而直接从边界出发,直到求出函数值。 26 *##归并排序##是分治思想的典型应用,它包含这样三个步骤: 27 * 分解: 待排序的区间为 [l, r] ,令 m = (l + r)/2 * ,我们把 [l, r]分成 [l, m] 和 [m + 1, r] 28 * 解决: 使用归并排序递归地排序两个子序列 29 * 合并: 把两个已经排好序的子序列 [l, m]和 [m + 1, r]合并起来 30 * 在待排序序列长度为 11 的时候,递归开始「回升」,因为我们默认长度为 1的序列是排好序的。 31 * 32 * 33 * 34 * 35 */ 36 37 38 /** 39 * @Param: 40 * @return: 41 * @Author: HelloXf 42 * @Date: 43 */ 44 public class Leecode剑指offer { 45 46 47 /** 48 * 03. 数组中重复的数字 49 * [原地置换][哈希] 50 */ 51 public static int findRepeatNumber(int[] nums) { 52 /* HashSet<Integer> hashSet = new HashSet<>(); 53 for(int i:nums){ 54 if(!hashSet.add(i)){ 55 return i; 56 } 57 } 58 return -1;*/ 59 60 //原地置换 61 int temp; 62 for(int i=0;i<nums.length;i++){ 63 while (nums[i]!=i){ 64 if(nums[i]==nums[nums[i]]){ 65 return nums[i]; 66 } 67 temp=nums[i]; 68 nums[i]=nums[temp]; 69 nums[temp]=temp; 70 } 71 } 72 return -1; 73 } 74 75 76 /** 77 * 04. 二维数组中的查找 78 */ 79 public static boolean findNumberIn2DArray(int[][] matrix, int target) { 80 int x=0,y=0; 81 if(matrix.length==0){ 82 return false; 83 } 84 if(matrix[y].length==0){ 85 return false; 86 } 87 88 for( ;x<matrix[y].length;x++){ 89 boolean flag = false; 90 if(matrix[y][0] > target ){ 91 return false; 92 } 93 if(matrix[y][x]<target&& x<matrix[y].length-1){ 94 continue; 95 } 96 97 if( x==matrix[y].length-1&& matrix[y][matrix[y].length-1]<target){ 98 flag =true; 99 } 100 if(matrix[y][x]==target){ 101 return true; 102 } 103 for( ;y<matrix.length;y++){ 104 if(flag){ 105 if(matrix[y][x]<target){ 106 continue; 107 }else if(matrix[y][x]==target){ 108 return true; 109 }else { 110 return false; 111 } 112 }else{ 113 if(matrix[y][x-1]<target){ 114 continue; 115 }else if(matrix[y][x-1]==target){ 116 return true; 117 }else { 118 return false; 119 } 120 } 121 122 } 123 } 124 return false; 125 126 } 127 /** 128 * 05 请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 129 */ 130 public static String replaceSpace(String s) { 131 return s.replaceAll(" ","%20"); 132 } 133 134 public static class ListNode { 135 int val; 136 ListNode next; 137 ListNode(int x) { 138 val = x; 139 } 140 } 141 142 /** 143 * 06. 从尾到头打印链表 144 * [数据结构] 145 */ 146 public static int[] reversePrint(ListNode head) { 147 Stack<ListNode> stack = new Stack<ListNode>(); 148 ListNode temp = head; 149 while (temp != null) { 150 stack.push(temp); 151 temp = temp.next; 152 } 153 int size = stack.size(); 154 int[] print = new int[size]; 155 for (int i = 0; i < size; i++) { 156 print[i] = stack.pop().val; 157 } 158 return print; 159 160 } 161 /** 162 * 剑指 Offer 09. 用两个栈实现队列 163 * 用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 ) 164 */ 165 /*static class CQueue { 166 private Queue<Integer> ss; 167 public CQueue() { 168 this.ss =new ConcurrentLinkedDeque<Integer>(); 169 } 170 public void appendTail(int value) { 171 ss.add(value); 172 } 173 public int deleteHead() { 174 if(ss!=null&&ss.size()>0){ 175 int s= ss.peek(); 176 ss.remove(); 177 return s; 178 }else{ 179 return -1; 180 } 181 } 182 }*/ 183 184 /** 185 * 剑指 Offer 09. 用两个栈实现队列 186 * [数据结构] 187 **/ 188 static class CQueue { 189 Stack<Integer> stack1; 190 Stack<Integer> stack2; 191 192 public CQueue() { 193 stack1 = new Stack<Integer>(); 194 stack2 = new Stack<Integer>(); 195 } 196 197 public void appendTail(int value) { 198 stack1.push(value); 199 } 200 201 public int deleteHead() { 202 // 如果第二个栈为空 203 if (stack2.isEmpty()) { 204 while (!stack1.isEmpty()) { 205 stack2.push(stack1.pop()); 206 } 207 } 208 if (stack2.isEmpty()) { 209 return -1; 210 } else { 211 int deleteItem = stack2.pop(); 212 return deleteItem; 213 } 214 } 215 } 216 217 218 /** 219 * 10-1 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N)) 220 * 斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。 221 * 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。 222 */ 223 public static int fib(int n) { 224 int before =1 ,after =1; 225 int result=0; 226 if(n==0){ 227 return 0; 228 } 229 if(n<=2){ 230 return 1; 231 } 232 for(int i=3;i<=n;i++){ 233 result = (before+after)% 1000000007; 234 before = after; 235 after = result; 236 237 } 238 return result; 239 } 240 241 /** 242 * 10-2 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 243 * 斐波那契额数组 244 */ 245 public static int numWays1(int n) { 246 int a = 1, b = 1, sum; 247 for(int i = 0; i < n; i++){ 248 sum = (a + b) % 1000000007; 249 a = b; 250 b = sum; 251 } 252 return a; 253 } 254 255 /** 256 * 10-2 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 257 * [排列组合] 258 */ 259 public static int numWays2(int n) { 260 int max2 = n/2; 261 int result=0; 262 if(n<=1){ 263 return 1; 264 } 265 // i为2的个数 266 for (int count2 =0;count2<=max2;count2++){ 267 int re = 0; 268 // y为1的个数 269 int count1= n-2*count2; 270 //总步数 271 int count = count2+count1; 272 if(count2==0||count1==0){ 273 re++; 274 }else { 275 re=1; 276 //count个 1 2 拍序结果 其中2的个数count2,1的个数count1 计算公式C(count2,count) 277 BigDecimal fenzi =BigDecimal.ONE; 278 BigDecimal fengmu = BigDecimal.ONE; 279 int countRun = count2>count1?count1:count2; 280 for (int x = countRun; x >0; x--) { 281 fengmu=fengmu.multiply(BigDecimal.valueOf(x)); 282 } 283 for (int x = 0; x < countRun; x++) { 284 fenzi = fenzi .multiply (BigDecimal.valueOf(count - x)); 285 } 286 re = fenzi.divide(fengmu).intValue() % 1000000007 ; 287 } 288 System.out.println("当2的数量为"+count2+",1的数量为:"+count1+"。结果有:"+re); 289 result = (result +re)% 1000000007 ; 290 } 291 return result ; 292 } 293 294 /** 295 * 11. 旋转数组的最小数字 296 * 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。 297 * [二分法][SR] 298 */ 299 public static int minArray(int[] numbers) { 300 int low = 0; 301 int high = numbers.length - 1; 302 while (low < high) { 303 int pivot = low + (high - low) / 2; 304 if (numbers[pivot] < numbers[high]) { 305 high = pivot; 306 } else if (numbers[pivot] > numbers[high]) { 307 low = pivot + 1; 308 } else { 309 high -= 1; 310 } 311 } 312 return numbers[low]; 313 314 } 315 316 317 /** 318 * 12. 矩阵中的路径 319 * 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。 320 * 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。 321 * [深度优先搜索(DFS)+ 剪枝][递归][SSR] 322 */ 323 public static boolean exist(char[][] board, String word) { 324 char[] words = word.toCharArray(); 325 for(int i = 0; i < board.length; i++) { 326 for(int j = 0; j < board[0].length; j++) { 327 if(dfs(board, words, i, j, 0)) { 328 return true; 329 } 330 } 331 } 332 return false; 333 } 334 335 static boolean dfs(char[][] board, char[] word, int i, int j, int k) { 336 if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]){ 337 return false; 338 } 339 if(k == word.length - 1){ 340 return true; 341 } 342 board[i][j] = '\0'; 343 boolean res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) || 344 dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1); 345 //回溯 遍历失败赋值回去重新遍历 346 board[i][j] = word[k]; 347 return res; 348 } 349 350 /** 351 * 12. 矩阵中的路径 352 * 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。 353 * 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。 354 * [深度优先搜索(DFS)+ 回溯][递归][SSR] 355 */ 356 public static boolean exist1(char[][] board, String word) { 357 if (board == null || board.length == 0 || board[0].length == 0) { 358 return false; 359 } 360 361 char[] chars = word.toCharArray(); 362 boolean[][] visited = new boolean[board.length][board[0].length]; 363 for (int i = 0; i < board.length; i++) { 364 for (int j = 0; j < board[0].length; j++) { 365 // 从 (0, 0) 点开始进行 dfs 操作,不断地去找, 366 // 如果以 (0, 0) 点没有对应的路径的话,那么就从 (0, 1) 点开始去找 367 if (dfs(board, chars, visited, i, j, 0)) { 368 return true; 369 } 370 } 371 } 372 return false; 373 } 374 375 private static boolean dfs(char[][] board, char[] chars, boolean[][] visited, int i, int j, int start) { 376 if (i < 0 || i >= board.length || j < 0 || j >= board[0].length 377 || chars[start] != board[i][j] || visited[i][j]) { 378 return false; 379 } 380 if (start == chars.length - 1) { 381 return true; 382 } 383 visited[i][j] = true; 384 boolean ans = false; 385 ans = dfs(board, chars, visited, i + 1, j, start + 1) 386 || dfs(board, chars, visited, i - 1, j, start + 1) 387 || dfs(board, chars, visited, i, j + 1, start + 1) 388 || dfs(board, chars, visited, i, j - 1, start + 1); 389 visited[i][j] = false; 390 return ans; 391 } 392 393 394 395 /** 396 * 13. 机器人的运动范围 397 * 地上有一个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。请问该机器人能够到达多少个格子? 398 *[广度优先搜索BFS][SSR] 399 */ 400 public static int movingCount1(int m, int n, int k) { 401 if (k == 0) { 402 return 1; 403 } 404 Queue<int[]> queue = new LinkedList<int[]>(); 405 // 向右和向下的方向数组 406 int[] dx = {0, 1}; 407 int[] dy = {1, 0}; 408 boolean[][] vis = new boolean[m][n]; 409 queue.offer(new int[]{0, 0}); 410 vis[0][0] = true; 411 int ans = 1; 412 while (!queue.isEmpty()) { 413 int[] cell = queue.poll(); 414 int x = cell[0], y = cell[1]; 415 for (int i = 0; i < 2; ++i) { 416 int tx = dx[i] + x; 417 int ty = dy[i] + y; 418 if (tx < 0 || tx >= m || ty < 0 || ty >= n || vis[tx][ty] || get(tx) + get(ty) > k) { 419 continue; 420 } 421 queue.offer(new int[]{tx, ty}); 422 vis[tx][ty] = true; 423 ans++; 424 } 425 } 426 return ans; 427 } 428 429 /** 430 * 13. 机器人的运动范围 431 * 地上有一个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。请问该机器人能够到达多少个格子? 432 *[递推] 433 */ 434 public static int movingCount(int m, int n, int k) { 435 if (k == 0) { 436 return 1; 437 } 438 boolean[][] vis = new boolean[m][n]; 439 int ans = 1; 440 vis[0][0] = true; 441 for (int i = 0; i < m; ++i) { 442 for (int j = 0; j < n; ++j) { 443 if ((i == 0 && j == 0) || get(i) + get(j) > k) { 444 continue; 445 } 446 // 边界判断 447 if (i - 1 >= 0) { 448 vis[i][j] |= vis[i - 1][j]; 449 } 450 if (j - 1 >= 0) { 451 vis[i][j] |= vis[i][j - 1]; 452 } 453 ans += vis[i][j] ? 1 : 0; 454 } 455 } 456 return ans; 457 } 458 459 private static int get(int x) { 460 int res = 0; 461 while (x != 0) { 462 res += x % 10; 463 x /= 10; 464 } 465 return res; 466 } 467 468 /** 469 * 13. 机器人的运动范围 470 * 地上有一个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。请问该机器人能够到达多少个格子? 471 *[深度优先][递归搜索][SSR] 472 */ 473 int m, n, k; 474 boolean[][] visited; 475 public int movingCount3(int m, int n, int k) { 476 this.m = m; this.n = n; this.k = k; 477 this.visited = new boolean[m][n]; 478 return dfs(0, 0, 0, 0); 479 } 480 public int dfs(int i, int j, int si, int sj) { 481 if(i >= m || j >= n || k < si + sj || visited[i][j]){ 482 return 0; 483 } 484 visited[i][j] = true; 485 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); 486 } 487 488 /** 489 * 13. 机器人的运动范围 490 * 地上有一个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。请问该机器人能够到达多少个格子? 491 *[深度优先][递归搜索][SSR] 492 */ 493 494 public int movingCount4(int m, int n, int k) { 495 this.m = m; this.n = n; this.k = k; 496 this.visited = new boolean[m][n]; 497 return dfs2(0, 0, k); 498 } 499 public int dfs2(int i, int j, int k) { 500 if(i >= m || j >= n ||get(i) + get(j) > k || visited[i][j]){ 501 return 0; 502 } 503 visited[i][j] = true; 504 return 1 + dfs2(i + 1, j, k) + dfs2(i, j + 1, k); 505 } 506 507 508 /** 509 * 剑指 Offer 14- I. 剪绳子 510 * 给你一根长度为 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。 511 *推论一: 将绳子 以相等的长度等分为多段 ,得到的乘积最大。 512 * 推论二: 尽可能将绳子以长度 33 等分为多段时,乘积最大。 513 * 推论二: 若切分方案合理,绳子段切分的越多,乘积越大。 514 * 推论三: 为使乘积最大,只有长度为 22 和 33 的绳子不应再切分,且 33 比 22 更优 (详情见下表) 。 515 * [数学推导] 516 */ 517 public static int cuttingRope(int n) { 518 if(n <= 3) {return n - 1;} 519 int a = n / 3, b = n % 3; 520 if(b == 0) {return (int)Math.pow(3, a);} 521 if(b == 1) {return (int)Math.pow(3, a - 1) * 4;} 522 return (int)Math.pow(3, a) * 2; 523 } 524 525 /** 526 * 剑指 Offer 14- I. 剪绳子 527 * 给你一根长度为 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。 528 * [动态规划][SSR] 529 * 思路一:动态规划 530 * 我们想要求长度为n的绳子剪掉后的最大乘积,可以从前面比n小的绳子转移而来 531 * 用一个dp数组记录从0到n长度的绳子剪掉后的最大乘积,也就是dp[i]表示长度为i的绳子剪成m段后的最大乘积,初始化dp[2] = 1 532 * 我们先把绳子剪掉第一段(长度为j),如果只剪掉长度为1,对最后的乘积无任何增益,所以从长度为2开始剪 533 * 剪了第一段后,剩下(i - j)长度可以剪也可以不剪。如果不剪的话长度乘积即为j * (i - j);如果剪的话长度乘积即为j * dp[i - j]。取两者最大值max(j * (i - j), j * dp[i - j]) 534 * 第一段长度j可以取的区间为[2,i),对所有j不同的情况取最大值,因此最终dp[i]的转移方程为 535 * dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j])) 536 * 最后返回dp[n]即可 537 */ 538 public static int cuttingRope2(int n) { 539 int[] dp = new int[n + 1]; 540 dp[2] = 1; 541 for(int i = 3; i < n + 1; i++){//长度为i时,求最大乘机放入dp 542 for(int j = 2; j < i; j++){ 543 dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j])); 544 System.out.println( Arrays.toString(dp)); 545 } 546 } 547 return dp[n]; 548 } 549 550 551 552 /** 553 * 思路二:贪心 554 * 核心思路是:尽可能把绳子分成长度为3的小段,这样乘积最大 555 */ 556 public static int cuttingRope3(int n) { 557 if(n < 4){ 558 return n - 1; 559 } 560 int res = 1; 561 while(n > 4){ 562 res *= 3; 563 n -= 3; 564 } 565 return res * n; 566 } 567 568 /** 569 * 剑指 Offer 15. 二进制中1的个数 570 * 编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为 汉明重量).)。 571 * [位运算][与][移位][二进制] 572 */ 573 public static int hammingWeight(int n) { 574 int res = 0; 575 while(n != 0) { 576 res += n & 1; 577 n >>>= 1; 578 } 579 return res; 580 } 581 public static int hammingWeight2(int n) { 582 int res = 0; 583 while(n != 0) { 584 res++; 585 n &= n - 1; 586 } 587 return res; 588 } 589 590 /** 591 * 剑指 Offer 16. 数值的整数次方 592 * 实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。 593 *[位运算][二进制]快速幂解析(二进制角度) 594 */ 595 public static double myPow(double x, int n) { 596 if(x == 0) {return 0;} 597 long b = n; 598 double res = 1.0; 599 if(b < 0) { 600 x = 1 / x; 601 b = -b; 602 } 603 while(b > 0) { 604 if((b & 1) == 1) { 605 res *= x; 606 } 607 x *= x; 608 b >>= 1; 609 System.out.println("res:"+res+"-----------x:"+x+"------b:"+b); 610 } 611 return res; 612 } 613 614 /** 615 *剑指 Offer 17. 打印从1到最大的n位数 616 * 输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。 617 */ 618 public static int[] printNumbers(int n) { 619 int end = (int)Math.pow(10, n) - 1; 620 int[] res = new int[end]; 621 for(int i = 0; i < end; i++) 622 { res[i] = i + 1;} 623 return res; 624 } 625 /** 626 *剑指 Offer 17. 打印从1到最大的n位数 627 * 递归生成全排列[递归][SSR] 628 */ 629 static StringBuilder res; 630 static int count = 0, n1=0; 631 static char[] num, loop = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; 632 public static String printNumbers1(int n) { 633 n1 = n; 634 res = new StringBuilder(); // 数字字符串集 635 num = new char[n]; // 定义长度为 n 的字符列表 636 dfs(0); // 开启全排列递归 637 res.deleteCharAt(res.length() - 1); // 删除最后多余的逗号 638 return res.toString(); // 转化为字符串并返回 639 } 640 static void dfs(int x) { 641 if(x == n1) { // 终止条件:已固定完所有位 642 res.append(String.valueOf(num) + ","); // 拼接 num 并添加至 res 尾部,使用逗号隔开 643 return; 644 } 645 for(char i : loop) { // 遍历 ‘0‘ - ’9‘ 646 num[x] = i; // 固定第 x 位为 i 647 dfs(x + 1); // 开启固定第 x + 1 位 648 } 649 } 650 /** 651 * 递归生成全排列 652 */ 653 static int nine = 0, start; 654 public static String printNumbers2(int n) { 655 n1 = n; 656 res = new StringBuilder(); 657 num = new char[n]; 658 start = n - 1; 659 dfs1(0); 660 res.deleteCharAt(res.length() - 1); 661 return res.toString(); 662 } 663 static void dfs1(int x) { 664 if(x == n1) { 665 String s = String.valueOf(num).substring(start); 666 if(!s.equals("0")){ res.append(s + ",");} 667 if(n1 - start == nine){ start--;} 668 return; 669 } 670 for(char i : loop) { 671 if(i == '9') {nine++;} 672 num[x] = i; 673 dfs(x + 1); 674 } 675 nine--; 676 } 677 678 679 /** 680 * 剑指 Offer 18. 删除链表的节点 681 *给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。 682 * 返回删除后的链表的头节点。 683 */ 684 public static ListNode deleteNode(ListNode head, int val) { 685 if(head.val == val) {return head.next;} 686 ListNode pre = head, cur = head.next; 687 while(cur != null && cur.val != val) { 688 pre = cur; 689 cur = cur.next; 690 } 691 if(cur != null) {pre.next = cur.next;} 692 return head; 693 694 } 695 696 697 698 /** 699 * 剑指 Offer 20. 表示数值的字符串 700 * 请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。 701 * [自动机][SSR] 702 */ 703 public static boolean isNumber(String s) { 704 705 Map<State, Map<CharType, State>> transfer = new HashMap<State, Map<CharType, State>>(); 706 Map<CharType, State> initialMap = new HashMap<CharType, State>() {{ 707 put(CharType.CHAR_SPACE, State.STATE_INITIAL); 708 put(CharType.CHAR_NUMBER, State.STATE_INTEGER); 709 put(CharType.CHAR_POINT, State.STATE_POINT_WITHOUT_INT); 710 put(CharType.CHAR_SIGN, State.STATE_INT_SIGN); 711 }}; 712 transfer.put(State.STATE_INITIAL, initialMap); 713 Map<CharType, State> intSignMap = new HashMap<CharType, State>() {{ 714 put(CharType.CHAR_NUMBER, State.STATE_INTEGER); 715 put(CharType.CHAR_POINT, State.STATE_POINT_WITHOUT_INT); 716 }}; 717 transfer.put(State.STATE_INT_SIGN, intSignMap); 718 Map<CharType, State> integerMap = new HashMap<CharType, State>() {{ 719 put(CharType.CHAR_NUMBER, State.STATE_INTEGER); 720 put(CharType.CHAR_EXP, State.STATE_EXP); 721 put(CharType.CHAR_POINT, State.STATE_POINT); 722 put(CharType.CHAR_SPACE, State.STATE_END); 723 }}; 724 transfer.put(State.STATE_INTEGER, integerMap); 725 Map<CharType, State> pointMap = new HashMap<CharType, State>() {{ 726 put(CharType.CHAR_NUMBER, State.STATE_FRACTION); 727 put(CharType.CHAR_EXP, State.STATE_EXP); 728 put(CharType.CHAR_SPACE, State.STATE_END); 729 }}; 730 transfer.put(State.STATE_POINT, pointMap); 731 Map<CharType, State> pointWithoutIntMap = new HashMap<CharType, State>() {{ 732 put(CharType.CHAR_NUMBER, State.STATE_FRACTION); 733 }}; 734 transfer.put(State.STATE_POINT_WITHOUT_INT, pointWithoutIntMap); 735 Map<CharType, State> fractionMap = new HashMap<CharType, State>() {{ 736 put(CharType.CHAR_NUMBER, State.STATE_FRACTION); 737 put(CharType.CHAR_EXP, State.STATE_EXP); 738 put(CharType.CHAR_SPACE, State.STATE_END); 739 }}; 740 transfer.put(State.STATE_FRACTION, fractionMap); 741 Map<CharType, State> expMap = new HashMap<CharType, State>() {{ 742 put(CharType.CHAR_NUMBER, State.STATE_EXP_NUMBER); 743 put(CharType.CHAR_SIGN, State.STATE_EXP_SIGN); 744 }}; 745 transfer.put(State.STATE_EXP, expMap); 746 Map<CharType, State> expSignMap = new HashMap<CharType, State>() {{ 747 put(CharType.CHAR_NUMBER, State.STATE_EXP_NUMBER); 748 }}; 749 transfer.put(State.STATE_EXP_SIGN, expSignMap); 750 Map<CharType, State> expNumberMap = new HashMap<CharType, State>() {{ 751 put(CharType.CHAR_NUMBER, State.STATE_EXP_NUMBER); 752 put(CharType.CHAR_SPACE, State.STATE_END); 753 }}; 754 transfer.put(State.STATE_EXP_NUMBER, expNumberMap); 755 Map<CharType, State> endMap = new HashMap<CharType, State>() {{ 756 put(CharType.CHAR_SPACE, State.STATE_END); 757 }}; 758 transfer.put(State.STATE_END, endMap); 759 760 int length = s.length(); 761 State state = State.STATE_INITIAL; 762 763 for (int i = 0; i < length; i++) { 764 CharType type = toCharType(s.charAt(i)); 765 if (!transfer.get(state).containsKey(type)) { 766 return false; 767 } else { 768 state = transfer.get(state).get(type); 769 } 770 } 771 return state == State.STATE_INTEGER || state == State.STATE_POINT || state == State.STATE_FRACTION || state == State.STATE_EXP_NUMBER || state == State.STATE_END; 772 } 773 774 public static CharType toCharType(char ch) { 775 if (ch >= '0' && ch <= '9') { 776 return CharType.CHAR_NUMBER; 777 } else if (ch == 'e' || ch == 'E') { 778 return CharType.CHAR_EXP; 779 } else if (ch == '.') { 780 return CharType.CHAR_POINT; 781 } else if (ch == '+' || ch == '-') { 782 return CharType.CHAR_SIGN; 783 } else if (ch == ' ') { 784 return CharType.CHAR_SPACE; 785 } else { 786 return CharType.CHAR_ILLEGAL; 787 } 788 } 789 790 enum State { 791 STATE_INITIAL, 792 STATE_INT_SIGN, 793 STATE_INTEGER, 794 STATE_POINT, 795 STATE_POINT_WITHOUT_INT, 796 STATE_FRACTION, 797 STATE_EXP, 798 STATE_EXP_SIGN, 799 STATE_EXP_NUMBER, 800 STATE_END 801 } 802 803 enum CharType { 804 CHAR_NUMBER, 805 CHAR_EXP, 806 CHAR_POINT, 807 CHAR_SIGN, 808 CHAR_SPACE, 809 CHAR_ILLEGAL 810 } 811 812 /** 813 * 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 814 * 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。 815 */ 816 public static int[] exchange(int[] nums) { 817 int n = nums.length; 818 int[] res = new int[n]; 819 int index = 0; 820 for(int num : nums){ 821 if(num % 2 == 1){ 822 res[index++] = num; 823 } 824 } 825 for(int num : nums){ 826 if(num % 2 == 0){ 827 res[index++] = num; 828 } 829 } 830 return res; 831 } 832 833 /** 834 * 头尾双指针[SR] 835 */ 836 public int[] exchange1(int[] nums) { 837 int left = 0, right = nums.length - 1; 838 while(left <= right){ 839 while(left <= right && nums[left] % 2 == 1) 840 {left++;} 841 while(left <= right && nums[right] % 2 == 0) 842 {right--;} 843 if(left > right) 844 { break;} 845 int tmp = nums[left]; 846 nums[left] = nums[right]; 847 nums[right] = tmp; 848 } 849 return nums; 850 } 851 852 /** 853 *剑指 Offer 22. 链表中倒数第k个节点 854 * 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。 855 */ 856 public static ListNode getKthFromEnd(ListNode head, int k) { 857 HashMap<Integer,ListNode> headMap = new HashMap<Integer, ListNode>(); 858 ListNode cur = head; 859 Integer key = 1; 860 while (cur !=null){ 861 headMap.put(key,cur); 862 key++; 863 cur = cur.next; 864 } 865 return headMap.get(key-k); 866 } 867 868 /** 869 *剑指 Offer 22. 链表中倒数第k个节点 870 * 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。 871 *[双指针] 872 */ 873 public static ListNode getKthFromEnd1(ListNode head, int k) { 874 // 初始化都指向链表头 875 ListNode cur = head; 876 ListNode target = head; 877 // cur指针先前进 k-1 次 878 for (int i = 0; i < k - 1; i++) { 879 cur = cur.next; 880 } 881 // 当 cur 指针指向链表尾时,target 指针指向倒数第 k 个节点 882 while (cur.next != null) { 883 cur = cur.next; 884 target = target.next; 885 } 886 return target; 887 } 888 889 890 /** 891 * 剑指 Offer 24. 反转链表 892 * 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。 893 *[数据结构][链表][SSR] 894 */ 895 public static ListNode reverseList(ListNode head){ 896 ListNode prev = null; 897 ListNode curr = head; 898 while (curr != null) { 899 //记录下一个等待指针遍历的位置 900 ListNode next = curr.next; 901 //将指针遍历到的节点拼接到 新的结果链表的前面 902 curr.next = prev; 903 //存放新的结果链表 904 prev = curr; 905 //指针后移 906 curr = next; 907 } 908 return prev; 909 } 910 911 /** 912 * [递归][SSR] 913 */ 914 public static ListNode reverseList2(ListNode head) { 915 if (head == null || head.next == null) { 916 return head; 917 } 918 ListNode newHead = reverseList2(head.next); 919 //赋值的过程都是先放入方法栈中,最后head为倒数第一个时开始赋值 920 head.next.next = head; 921 //防止链表循环 922 head.next = null; 923 return newHead; 924 } 925 926 /** 927 * 剑指 Offer 25. 合并两个排序的链表 928 * 输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。 929 * [数据结构][链表][迭代] 930 */ 931 public static ListNode mergeTwoLists(ListNode l1, ListNode l2) { 932 ListNode dum = new ListNode(0), cur = dum; 933 while(l1 != null && l2 != null) { 934 if(l1.val < l2.val) { 935 cur.next = l1; 936 l1 = l1.next; 937 } 938 else { 939 cur.next = l2; 940 l2 = l2.next; 941 } 942 cur = cur.next; 943 } 944 cur.next = l1 != null ? l1 : l2; 945 return dum.next; 946 } 947 948 /** 949 * 剑指 Offer 25. 合并两个排序的链表 950 * 输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。 951 *[递归][SSR] 952 */ 953 public static ListNode mergeTwoLists2(ListNode l1, ListNode l2) { 954 if(l1 == null || l2 == null){ 955 return l1 != null ? l1 : l2; 956 } 957 if(l1.val <= l2.val){ 958 l1.next = mergeTwoLists(l1.next, l2); 959 return l1; 960 }else{ 961 l2.next = mergeTwoLists(l1, l2.next); 962 return l2; 963 } 964 } 965 966 static class TreeNode { 967 int val; 968 TreeNode left; 969 TreeNode right; 970 TreeNode(int x) { val = x; } 971 } 972 973 /** 974 * 剑指 Offer 26. 树的子结构 975 * 输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构) 976 * B是A的子结构, 即 A中有出现和B相同的结构和节点值。 977 *[对称性递归][二叉树] 978 */ 979 public static boolean isSubStructure(TreeNode A, TreeNode B) { 980 return (A != null && B != null) && (recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B)); 981 } 982 static boolean recur(TreeNode A, TreeNode B) { 983 if(B == null) {return true;} 984 if(A == null || A.val != B.val) {return false;} 985 return recur(A.left, B.left) && recur(A.right, B.right); 986 } 987 988 /** 989 * 剑指 Offer 27. 二叉树的镜像 990 * 请完成一个函数,输入一个二叉树,该函数输出它的镜像。 991 * [递归][二叉树] 992 */ 993 public static TreeNode mirrorTree(TreeNode root) { 994 if (root == null) { 995 return null; 996 } 997 TreeNode left = mirrorTree(root.left); 998 TreeNode right = mirrorTree(root.right); 999 root.left = right; 1000 root.right = left; 1001 return root; 1002 } 1003 1004 /** 1005 * 剑指 Offer 28. 对称的二叉树 1006 * 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。 1007 * [递归][二叉树] 1008 */ 1009 public static boolean isSymmetric(TreeNode root) { 1010 return root == null ? true : recur1(root.left, root.right); 1011 } 1012 1013 static boolean recur1(TreeNode L, TreeNode R) { 1014 if (L == null && R == null) { 1015 return true; 1016 } 1017 if (L == null || R == null || L.val != R.val) { 1018 return false; 1019 } 1020 return recur1(L.left, R.right) && recur(L.right, R.left); 1021 } 1022 1023 1024 1025 /** 1026 * 剑指 Offer 29. 顺时针打印矩阵 1027 * 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。 1028 */ 1029 public static int[] spiralOrder(int[][] matrix) { 1030 if(matrix.length == 0) {return new int[0];} 1031 int l = 0, //游标 1032 r = matrix[0].length - 1, //数组列数 1033 t = 0, //游标 1034 b = matrix.length - 1, //数组排数 1035 x = 0;//返回数值下标 1036 int[] res = new int[(r + 1) * (b + 1)];//返回值 1037 while(true) { 1038 for(int i = l; i <= r; i++) {res[x++] = matrix[t][i]; }// 从左到右 1039 if(++t > b) {break;}//防止重复遍历排 1040 for(int i = t; i <= b; i++) {res[x++] = matrix[i][r];} // top to bottom. 1041 if(l > --r) {break;}//防止重复遍历列 1042 for(int i = r; i >= l; i--) {res[x++] = matrix[b][i];} // right to left. 1043 if(t > --b) {break;}//防止重复遍历排 1044 for(int i = b; i >= t; i--) {res[x++] = matrix[i][l];} // bottom to top. 1045 if(++l > r) {break;}//防止重复遍历列 1046 } 1047 return res; 1048 1049 } 1050 1051 /** 1052 * 按层遍历 1053 * [思维] 1054 */ 1055 public int[] spiralOrder2(int[][] matrix) { 1056 if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { 1057 return new int[0]; 1058 } 1059 int rows = matrix.length, columns = matrix[0].length; 1060 int[] order = new int[rows * columns]; 1061 int index = 0; 1062 int left = 0, right = columns - 1, top = 0, bottom = rows - 1; 1063 while (left <= right && top <= bottom) { 1064 for (int column = left; column <= right; column++) { 1065 order[index++] = matrix[top][column]; 1066 } 1067 for (int row = top + 1; row <= bottom; row++) { 1068 order[index++] = matrix[row][right]; 1069 } 1070 if (left < right && top < bottom) { 1071 for (int column = right - 1; column > left; column--) { 1072 order[index++] = matrix[bottom][column]; 1073 } 1074 for (int row = bottom; row > top; row--) { 1075 order[index++] = matrix[row][left]; 1076 } 1077 } 1078 left++; 1079 right--; 1080 top++; 1081 bottom--; 1082 } 1083 return order; 1084 } 1085 1086 /** 1087 * 剑指 Offer 30. 包含min函数的栈 1088 * 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。 1089 */ 1090 class MinStack { 1091 Stack<Integer> stack = new Stack<>(); 1092 Stack<Integer> source = new Stack<>(); 1093 /** initialize your data structure here. */ 1094 public MinStack() { 1095 stack = new Stack<>(); 1096 source = new Stack<>(); 1097 } 1098 public void push(int x) { 1099 this.source.push(x); 1100 if(stack.empty() || stack.peek()>x){ 1101 stack.push(x); 1102 } 1103 } 1104 public void pop() { 1105 if(this.source.pop().equals( stack.peek())){ 1106 stack.pop(); 1107 } 1108 1109 } 1110 public int top() { 1111 return source.peek(); 1112 } 1113 public int min() { 1114 return stack.peek(); 1115 } 1116 } 1117 1118 1119 /** 1120 * 剑指 Offer 31. 栈的压入、弹出序列 1121 * 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。 1122 * 1123 */ 1124 public static boolean validateStackSequences(int[] pushed, int[] popped) { 1125 Stack<Integer> stack = new Stack<>(); 1126 int i = 0; 1127 for(int num : pushed) { 1128 stack.push(num); // num 入栈 1129 while(!stack.isEmpty() && stack.peek() == popped[i]) { // 循环判断与出栈 1130 stack.pop(); 1131 i++; 1132 } 1133 } 1134 return stack.isEmpty(); 1135 1136 } 1137 1138 /** 1139 * 剑指 Offer 32 - I. 从上到下打印二叉树 1140 * 从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。 1141 * [BFS][广度优先搜索] 1142 */ 1143 public int[] levelOrder(TreeNode root) { 1144 if(root == null){ return new int[0];} 1145 Queue<TreeNode> queue = new LinkedList<TreeNode>(){{ add(root); }}; 1146 ArrayList<Integer> ans = new ArrayList<>(); 1147 while(!queue.isEmpty()) { 1148 TreeNode node = queue.poll(); 1149 ans.add(node.val); 1150 if(node.left != null) {queue.add(node.left);} 1151 if(node.right != null) {queue.add(node.right);} 1152 } 1153 int[] res = new int[ans.size()]; 1154 for(int i = 0; i < ans.size(); i++) 1155 { res[i] = ans.get(i);} 1156 return res; 1157 } 1158 1159 /** 1160 * 剑指 Offer 33 - I. 从上到下打印二叉树2 1161 * 从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。 1162 * 1163 */ 1164 public List<List<Integer>> levelOrder2(TreeNode root) { 1165 LinkedList<TreeNode> queue = new LinkedList<>(); 1166 List<List<Integer>> res = new ArrayList<>(); 1167 if(root != null) {queue.add(root);} 1168 while(!queue.isEmpty()) { 1169 List<Integer> tmp = new ArrayList<>(); 1170 for(int i = queue.size(); i > 0; i--) { 1171 TreeNode node = queue.poll(); 1172 tmp.add(node.val); 1173 if(node.left != null) {queue.add(node.left);} 1174 if(node.right != null) {queue.add(node.right);} 1175 } 1176 res.add(tmp); 1177 } 1178 return res; 1179 } 1180 /** 1181 * 剑指 Offer 32 - III. 从上到下打印二叉树 III 1182 *请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。 1183 *[队列][数据结构][SR][二叉树] 1184 */ 1185 public List<List<Integer>> levelOrder3(TreeNode root) { 1186 Queue<TreeNode> queue = new LinkedList<>(); 1187 List<List<Integer>> res = new ArrayList<>(); 1188 if(root != null) {queue.add(root);} 1189 while(!queue.isEmpty()) { 1190 LinkedList<Integer> tmp = new LinkedList<>(); 1191 for(int i = queue.size(); i > 0; i--) { 1192 TreeNode node = queue.poll(); 1193 if(res.size() % 2 == 0) {tmp.addLast(node.val); }// 偶数层 -> 队列头部 1194 else {tmp.addFirst(node.val);} // 奇数层 -> 队列尾部 1195 if(node.left != null) {queue.add(node.left);} 1196 if(node.right != null) {queue.add(node.right);} 1197 } 1198 res.add(tmp); 1199 } 1200 return res; 1201 } 1202 1203 /** 1204 * 剑指 Offer 33. 二叉搜索树的后序遍历序列 1205 * 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。 1206 * 假设输入的数组的任意两个数字都互不相同。 1207 * 1208 * 解析: 1209 * 根据二叉搜索树的定义,可以通过递归,判断所有子树的 正确性 (即其后序遍历是否满足二叉搜索树的定义) ,若所有子树都正确,则此序列为二叉搜索树的后序遍历。 1210 * 1211 * 终止条件: 当 i \geq ji≥j ,说明此子树节点数量 \leq 1≤1 ,无需判别正确性,因此直接返回 truetrue ; 1212 * 递推工作: 1213 * 划分左右子树: 遍历后序遍历的 [i, j][i,j] 区间元素,寻找 第一个大于根节点 的节点,索引记为 mm 。此时,可划分出左子树区间 [i,m-1][i,m−1] 、右子树区间 [m, j - 1][m,j−1] 、根节点索引 jj 。 1214 * 判断是否为二叉搜索树: 1215 * 左子树区间 [i, m - 1][i,m−1] 内的所有节点都应 << postorder[j]postorder[j] 。而第 1.划分左右子树 步骤已经保证左子树区间的正确性,因此只需要判断右子树区间即可。 1216 * 右子树区间 [m, j-1][m,j−1] 内的所有节点都应 >> postorder[j]postorder[j] 。实现方式为遍历,当遇到 \leq postorder[j]≤postorder[j] 的节点则跳出;则可通过 p = jp=j 判断是否为二叉搜索树。 1217 * 返回值: 所有子树都需正确才可判定正确,因此使用 与逻辑符 \&\&&& 连接。 1218 * p = jp=j : 判断 此树 是否正确。 1219 * recur(i, m - 1)recur(i,m−1) : 判断 此树的左子树 是否正确。 1220 * recur(m, j - 1)recur(m,j−1) : 判断 此树的右子树 是否正确。 1221 * 1222 * [递归分治][二叉树][SSR] 1223 */ 1224 public boolean verifyPostorder(int[] postorder) { 1225 return verifyPostorderrecur(postorder, 0, postorder.length - 1); 1226 } 1227 boolean verifyPostorderrecur(int[] postorder, int i, int j) { 1228 if(i >= j) {return true;} 1229 int p = i; 1230 while(postorder[p] < postorder[j]) {p++;} 1231 int m = p; 1232 while(postorder[p] > postorder[j]) {p++;} 1233 return p == j && verifyPostorderrecur(postorder, i, m - 1) && verifyPostorderrecur(postorder, m, j - 1); 1234 } 1235 1236 /** 1237 * 剑指 Offer 33. 二叉搜索树的后序遍历序列 1238 * 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。 1239 * 假设输入的数组的任意两个数字都互不相同。 1240 * [单调栈][SSR] 1241 */ 1242 public boolean verifyPostorder2(int[] postorder) { 1243 Stack<Integer> stack = new Stack<>(); 1244 int root = Integer.MAX_VALUE; 1245 for (int i = postorder.length - 1; i >= 0; i--) { 1246 if (postorder[i] > root){ return false;} 1247 while (!stack.isEmpty() && stack.peek() > postorder[i]) 1248 { 1249 root = stack.pop(); 1250 } 1251 stack.add(postorder[i]); 1252 } 1253 return true; 1254 } 1255 1256 1257 List<List<Integer>> ret = new LinkedList<List<Integer>>(); 1258 Deque<Integer> path = new LinkedList<Integer>(); 1259 1260 /** 1261 * 剑指 Offer 34. 二叉树中和为某一值的路径 1262 * 输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。 1263 * [DFS][深度优先搜索][SSR][二叉树] 1264 */ 1265 public List<List<Integer>> pathSum(TreeNode root, int target) { 1266 pathSumdfs(root, target); 1267 return ret; 1268 } 1269 1270 public void pathSumdfs(TreeNode root, int target) { 1271 if (root == null) { 1272 return; 1273 } 1274 path.offerLast(root.val); 1275 target -= root.val; 1276 if (root.left == null && root.right == null && target == 0) { 1277 ret.add(new LinkedList<Integer>(path)); 1278 } 1279 pathSumdfs(root.left, target); 1280 pathSumdfs(root.right, target); 1281 path.pollLast(); 1282 } 1283 //存放子节点对应父节点的映射 1284 Map<TreeNode, TreeNode> map = new HashMap<TreeNode, TreeNode>(); 1285 1286 /** 1287 * 剑指 Offer 34. 二叉树中和为某一值的路径 1288 * [广度优先搜索][BFS][SSR][二叉树] 1289 */ 1290 public List<List<Integer>> pathSum2(TreeNode root, int target) { 1291 if (root == null) { 1292 return ret; 1293 } 1294 1295 //分别存放即将遍历的每个节点以及走到该节点时的合计值 1296 Queue<TreeNode> queueNode = new LinkedList<TreeNode>(); 1297 Queue<Integer> queueSum = new LinkedList<Integer>(); 1298 queueNode.offer(root); 1299 queueSum.offer(0); 1300 1301 while (!queueNode.isEmpty()) { 1302 TreeNode node = queueNode.poll(); 1303 int rec = queueSum.poll() + node.val; 1304 1305 if (node.left == null && node.right == null) { 1306 if (rec == target) { 1307 getPath(node); 1308 } 1309 } else { 1310 if (node.left != null) { 1311 map.put(node.left, node); 1312 queueNode.offer(node.left); 1313 queueSum.offer(rec); 1314 } 1315 if (node.right != null) { 1316 map.put(node.right, node); 1317 queueNode.offer(node.right); 1318 queueSum.offer(rec); 1319 } 1320 } 1321 } 1322 1323 return ret; 1324 } 1325 1326 public void getPath(TreeNode node) { 1327 List<Integer> temp = new LinkedList<Integer>(); 1328 while (node != null) { 1329 temp.add(node.val); 1330 node = map.get(node); 1331 } 1332 Collections.reverse(temp); 1333 ret.add(new LinkedList<Integer>(temp)); 1334 } 1335 1336 //复杂链表 1337 class Node { 1338 int val; 1339 Node next; 1340 Node random; 1341 1342 public Node(int val) { 1343 this.val = val; 1344 this.next = null; 1345 this.random = null; 1346 } 1347 } 1348 1349 /** 1350 * 剑指 Offer 35. 复杂链表的复制 1351 * 请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。 1352 *思路及算法 1353 * 1354 * 本题要求我们对一个特殊的链表进行深拷贝。如果是普通链表,我们可以直接按照遍历的顺序创建链表节点。而本题中因为随机指针的存在,当我们拷贝节点时,「当前节点的随机指针指向的节点」可能还没创建,因此我们需要变换思路。一个可行方案是,我们利用回溯的方式,让每个节点的拷贝操作相互独立。对于当前节点,我们首先要进行拷贝,然后我们进行「当前节点的后继节点」和「当前节点的随机指针指向的节点」拷贝,拷贝完成后将创建的新节点的指针返回,即可完成当前节点的两指针的赋值。 1355 * 1356 * 具体地,我们用哈希表记录每一个节点对应新节点的创建情况。遍历该链表的过程中,我们检查「当前节点的后继节点」和「当前节点的随机指针指向的节点」的创建情况。如果这两个节点中的任何一个节点的新节点没有被创建,我们都立刻递归地进行创建。当我们拷贝完成,回溯到当前层时,我们即可完成当前节点的指针赋值。注意一个节点可能被多个其他节点指向,因此我们可能递归地多次尝试拷贝某个节点,为了防止重复拷贝,我们需要首先检查当前节点是否被拷贝过,如果已经拷贝过,我们可以直接从哈希表中取出拷贝后的节点的指针并返回即可。 1357 * 1358 * 在实际代码中,我们需要特别判断给定节点为空节点的情况。 1359 *[递归][回溯][SSR][链表] 1360 */ 1361 Map<Node, Node> cachedNode = new HashMap<Node, Node>(); 1362 public Node copyRandomList(Node head) { 1363 if (head == null) { 1364 return null; 1365 } 1366 if (!cachedNode.containsKey(head)) { 1367 Node headNew = new Node(head.val); 1368 cachedNode.put(head, headNew); 1369 headNew.next = copyRandomList(head.next); 1370 headNew.random = copyRandomList(head.random); 1371 } 1372 return cachedNode.get(head); 1373 1374 } 1375 //[迭代] + 节点拆分 1376 public Node copyRandomList2(Node head) { 1377 if (head == null) { 1378 return null; 1379 } 1380 for (Node node = head; node != null; node = node.next.next) { 1381 Node nodeNew = new Node(node.val); 1382 nodeNew.next = node.next; 1383 node.next = nodeNew; 1384 } 1385 for (Node node = head; node != null; node = node.next.next) { 1386 Node nodeNew = node.next; 1387 nodeNew.random = (node.random != null) ? node.random.next : null; 1388 } 1389 Node headNew = head.next; 1390 for (Node node = head; node != null; node = node.next) { 1391 Node nodeNew = node.next; 1392 node.next = node.next.next; 1393 nodeNew.next = (nodeNew.next != null) ? nodeNew.next.next : null; 1394 } 1395 return headNew; 1396 } 1397 class NodeList { 1398 public int val; 1399 public NodeList left; 1400 public NodeList right; 1401 1402 public NodeList() {} 1403 1404 public NodeList(int _val) { 1405 val = _val; 1406 } 1407 1408 public NodeList(int _val,NodeList _left,NodeList _right) { 1409 val = _val; 1410 left = _left; 1411 right = _right; 1412 } 1413 }; 1414 // 打印中序遍历 1415 void dfs中序遍历(NodeList root) { 1416 if(root == null) {return;} 1417 dfs中序遍历(root.left); // 左 1418 System.out.println(root.val); // 根 1419 dfs中序遍历(root.right); // 右 1420 } 1421 1422 NodeList pre, head; 1423 1424 /**剑指 Offer 36. 二叉搜索树与双向链表 1425 *输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。 1426 * [递归][二叉树][链表][中序遍历][深度优先搜索算法][DFS] 1427 */ 1428 public NodeList treeToDoublyList(NodeList root) { 1429 if(root == null) {return null;} 1430 dfstreeToDoublyList(root); 1431 head.left = pre; 1432 pre.right = head; 1433 return head; 1434 } 1435 void dfstreeToDoublyList(NodeList cur) { 1436 if(cur == null) {return;} 1437 dfstreeToDoublyList(cur.left); 1438 if(pre != null) {pre.right = cur;} 1439 else{ head = cur;} 1440 cur.left = pre; 1441 pre = cur; 1442 dfstreeToDoublyList(cur.right); 1443 } 1444 1445 1446 /** 1447 * 剑指 Offer 37. 序列化二叉树 1448 * 请实现两个函数,分别用来序列化和反序列化二叉树。 1449 * 1450 * 你需要设计一个算法来实现二叉树的序列化与反序列化。 1451 * 这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串 1452 * 并且将这个字符串反序列化为原始的树结构。 1453 * [SSR][序列化][二叉树] 1454 */ 1455 // Encodes a tree to a single string. 1456 public static String serialize(TreeNode root) { 1457 return rserialize(root, ""); 1458 } 1459 1460 public static TreeNode deserialize(String data) { 1461 String[] dataArray = data.split(","); 1462 List<String> dataList = new LinkedList<String>(Arrays.asList(dataArray)); 1463 return rdeserialize(dataList); 1464 } 1465 1466 public static String rserialize(TreeNode root, String str) { 1467 if (root == null) { 1468 str += "None,"; 1469 } else { 1470 str += root.val + ","; 1471 str = rserialize(root.left, str); 1472 str = rserialize(root.right, str); 1473 } 1474 return str; 1475 } 1476 1477 public static TreeNode rdeserialize(List<String> dataList) { 1478 if (dataList.get(0).equals("None")) { 1479 dataList.remove(0); 1480 return null; 1481 } 1482 1483 TreeNode root = new TreeNode(Integer.valueOf(dataList.get(0))); 1484 dataList.remove(0); 1485 root.left = rdeserialize(dataList); 1486 root.right = rdeserialize(dataList); 1487 1488 return root; 1489 } 1490 1491 /** 1492 * 案例 1493 * [回溯][SSR] 1494 */ 1495 static class Permutations { 1496 1497 //题目描述:Given a collection of distinct integers, return all possible permutations.(给定一组不同的整数,返回其所有的可能组合) 1498 public List<List<Integer>> permute(int[] nums) { 1499 //一个全局变量,用于保存所有集合 1500 List<List<Integer>> list = new ArrayList<>(); 1501 //传入三个参数,没有附加参数 1502 backtrack(list, new ArrayList<>(), nums); 1503 return list; 1504 } 1505 1506 private void backtrack(List<List<Integer>> list, List<Integer> tempList, int[] nums) { 1507 //一个终结条件,也就是满足条件的时候 1508 if (tempList.size() == nums.length) { 1509 //全局变量添加一个满足条件的集合 1510 list.add(new ArrayList<>(tempList)); 1511 } else { 1512 for (int i = 0; i < nums.length; i++) { 1513 if (tempList.contains(nums[i])) { 1514 continue; 1515 } 1516 //如果tempList没有包含nums[i]才添加 1517 tempList.add(nums[i]); 1518 //递归调用,此时的tempList一直在变化,直到满足终结条件才结束 1519 backtrack(list, tempList, nums); 1520 System.out.println("tempList的内容:" + tempList + "-------" + "i的值:" + i); 1521 //它移除tempList最后一个元素的作用就是返回上一次调用时的数据,也就是希望返回之前的节点再去重新搜索满足条件。这样才能实现回溯 1522 tempList.remove(tempList.size() - 1); 1523 } 1524 } 1525 } 1526 } 1527 /** 1528 * 剑指 Offer 38. 字符串的排列 1529 * 输入一个字符串,打印出该字符串中字符的所有排列。 1530 *[递归][剪枝](类似与剪枝,因为不是在DFS中)[SSR] 1531 */ 1532 static class Solution38_1 { 1533 //为了让递归函数添加结果方便,定义到函数之外,这样无需带到递归函数的参数列表中 1534 List<String> list = new ArrayList<>(); 1535 //同;但是其赋值依赖c,定义声明分开 1536 char[] c; 1537 public String[] permutation(String s) { 1538 c = s.toCharArray(); 1539 //从第一层开始递归 1540 dfs(0); 1541 //将字符串数组ArrayList转化为String类型数组 1542 return list.toArray(new String[list.size()]); 1543 } 1544 1545 private void dfs(int x) { 1546 //当递归函数到达第三层,就返回,因为此时第二第三个位置已经发生了交换 1547 if (x == c.length - 1) { 1548 //将字符数组转换为字符串 1549 list.add(String.valueOf(c)); 1550 return; 1551 } 1552 //为了防止同一层递归出现重复元素 1553 HashSet<Character> set = new HashSet<>(); 1554 //这里就很巧妙了,第一层可以是a,b,c那么就有三种情况,这里i = x,正巧dfs(0),正好i = 0开始 1555 // 当第二层只有两种情况,dfs(1)i = 1开始 1556 for (int i = x; i < c.length; i++){ 1557 //发生剪枝,当包含这个元素的时候,直接跳过 1558 if (set.contains(c[i])){ 1559 continue; 1560 } 1561 set.add(c[i]); 1562 //交换元素,这里很是巧妙,当在第二层dfs(1),x = 1,那么i = 1或者 2, 不是交换1和1,要就是交换1和2 1563 swap(i,x); 1564 //进入下一层递归 1565 dfs(x + 1); 1566 //返回时交换回来,这样保证到达第1层的时候,一直都是abc。这里捋顺一下,开始一直都是abc,那么第一位置总共就3个交换 1567 //分别是a与a交换,这个就相当于 x = 0, i = 0; 1568 // a与b交换 x = 0, i = 1; 1569 // a与c交换 x = 0, i = 2; 1570 //就相当于上图中开始的三条路径 1571 //第一个元素固定后,每个引出两条路径, 1572 // b与b交换 x = 1, i = 1; 1573 // b与c交换 x = 1, i = 2; 1574 //所以,结合上图,在每条路径上标注上i的值,就会非常容易好理解了 1575 swap(i,x); 1576 } 1577 } 1578 1579 private void swap(int i, int x) { 1580 char temp = c[i]; 1581 c[i] = c[x]; 1582 c[x] = temp; 1583 } 1584 } 1585 1586 /** 1587 * 剑指 Offer 38. 字符串的排列 1588 * 输入一个字符串,打印出该字符串中字符的所有排列。 1589 * [递归][剪枝][回溯][SSR][代表性] 1590 */ 1591 static class Solution38_2 { 1592 //结果集 1593 List<String> rec; 1594 //防重复 1595 boolean[] vis; 1596 1597 public String[] permutation(String s) { 1598 //字符串长度 1599 int n = s.length(); 1600 rec = new ArrayList<String>(); 1601 vis = new boolean[n]; 1602 //字符 1603 char[] arr = s.toCharArray(); 1604 //字符排序 1605 Arrays.sort(arr); 1606 StringBuffer perm = new StringBuffer(); 1607 backtrack(arr, 0, n, perm); 1608 int size = rec.size(); 1609 String[] recArr = new String[size]; 1610 for (int i = 0; i < size; i++) { 1611 recArr[i] = rec.get(i); 1612 } 1613 return recArr; 1614 } 1615 //思路及解法 1616 // 1617 //我们将这个问题看作有 nn 个排列成一行的空位,我们需要从左往右依次填入题目给定的 n 个字符,每个字符只能使用一次。首先可以想到穷举的算法,即从左往右每一个空位都依次尝试填入一个字符,看是否能填完这 n 个空位,编程实现时,我们可以用「回溯法」来模拟这个过程。 1618 //定义递归函数backtrack(i,perm) 表示当前排列为perm,下一个待填入的空位是第 i 个空位(下标从 00 开始)。那么该递归函数分为两个情况: 1619 //如果 i=n,说明我们已经填完了 n 个空位,找到了一个可行的解,我们将 perm 放入答案数组中,递归结束。 1620 //如果 i<n,此时需要考虑第 i 个空位填哪个字符。根据题目要求我们肯定不能填已经填过的字符,因此很容易想到的一个处理手段是我们定义一个标记数组 vis 来标记已经填过的字符,那么在填第 i 个字符的时候我们遍历题目给定的 n 个字符,如果这个字符没有被标记过,我们就尝试填入,并将其标记,继续尝试填下一个空位,即调用函数 backtrack(i+1,perm)。回溯时,我们需要要撤销该空位填的字符以及对该字符的标记,并继续向当前空位尝试填入其他没被标记过的字符。 1621 1622 public void backtrack(char[] arr, int i, int n, StringBuffer perm) { 1623 if (i == n) { 1624 rec.add(perm.toString()); 1625 return; 1626 } 1627 for (int j = 0; j < n; j++) { 1628 //保证在填每一个空位的时候重复字符只会被填入一次。具体地,我们首先对原字符串排序,保证相同的字符都相邻,在递归函数中,我们限制每次填入的字符一定是这个字符所在重复字符集合中「从左往右第一个未被填入的字符」 1629 // 保证没有浏览过 1630 if (vis[j] || (j > 0 && !vis[j - 1] && arr[j - 1] == arr[j])) { 1631 continue; 1632 } 1633 vis[j] = true; 1634 perm.append(arr[j]); 1635 backtrack(arr, i + 1, n, perm); 1636 perm.deleteCharAt(perm.length() - 1); 1637 vis[j] = false; 1638 } 1639 } 1640 } 1641 1642 /** 1643 * 剑指 Offer 38. 字符串的排列 1644 * 输入一个字符串,打印出该字符串中字符的所有排列。 1645 *对于一个长度为 n 的字符串(假设字符互不重复),其排列方案数共有: 1646 * n×(n−1)×(n−2)…×2×1 1647 *重复排列方案与剪枝: 1648 * 1649 * 当字符串存在重复字符时,排列方案中也存在重复的排列方案。为排除重复方案,需在固定某位字符时,保证 “每种字符只在此位固定一次” ,即遇到重复字符时不交换,直接跳过。从 DFS 角度看,此操作称为 “剪枝” 。 1650 *[下一个排列] 1651 */ 1652 static class Solution38_3 { 1653 public String[] permutation(String s) { 1654 List<String> ret = new ArrayList<String>(); 1655 char[] arr = s.toCharArray(); 1656 Arrays.sort(arr); 1657 do { 1658 ret.add(new String(arr)); 1659 } while (nextPermutation(arr)); 1660 int size = ret.size(); 1661 String[] retArr = new String[size]; 1662 for (int i = 0; i < size; i++) { 1663 retArr[i] = ret.get(i); 1664 } 1665 return retArr; 1666 } 1667 1668 public boolean nextPermutation(char[] arr) { 1669 int i = arr.length - 2; 1670 while (i >= 0 && arr[i] >= arr[i + 1]) { 1671 i--; 1672 } 1673 if (i < 0) { 1674 return false; 1675 } 1676 int j = arr.length - 1; 1677 while (j >= 0 && arr[i] >= arr[j]) { 1678 j--; 1679 } 1680 swap(arr, i, j); 1681 reverse(arr, i + 1); 1682 return true; 1683 } 1684 1685 public void swap(char[] arr, int i, int j) { 1686 char temp = arr[i]; 1687 arr[i] = arr[j]; 1688 arr[j] = temp; 1689 } 1690 1691 public void reverse(char[] arr, int start) { 1692 int left = start, right = arr.length - 1; 1693 while (left < right) { 1694 swap(arr, left, right); 1695 left++; 1696 right--; 1697 } 1698 } 1699 } 1700 1701 1702 /** 1703 * 剑指 Offer 39. 数组中出现次数超过一半的数字 1704 * 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 1705 * [HASH] 1706 */ 1707 public static int majorityElement(int[] nums) { 1708 HashMap<Integer,Integer> count = new HashMap<>(); 1709 int lengthHalf = Integer.valueOf(nums.length/2) ; 1710 if(lengthHalf==0){ 1711 return nums[0]; 1712 } 1713 1714 for(int s :nums){ 1715 if(count.get(s)==null){ 1716 count.put(s,1); 1717 }else{ 1718 if(count.get(s)+1>lengthHalf){ 1719 return s; 1720 } 1721 count.put(s,count.get(s)+1); 1722 } 1723 } 1724 return 0; 1725 } 1726 1727 /** 1728 * 剑指 Offer 39. 数组中出现次数超过一半的数字 1729 * [排序] 1730 */ 1731 public static int majorityElement2(int[] nums) { 1732 Arrays.sort(nums); 1733 return nums[nums.length / 2]; 1734 } 1735 1736 /** 1737 * 剑指 Offer 39. 数组中出现次数超过一半的数字 1738 * [分治][SSR] 1739 * 我们使用经典的分治算法递归求解,直到所有的子问题都是长度为 1 的数组。 1740 * 长度为 1 的子数组中唯一的数显然是众数,直接返回即可。 1741 * 如果回溯后某区间的长度大于 1,我们必须将左右子区间的值合并。 1742 * 如果它们的众数相同,那么显然这一段区间的众数是它们相同的值。 1743 * 否则,我们需要比较两个众数在整个区间内出现的次数来决定该区间的众数。 1744 * 1745 */ 1746 static class Solution39 { 1747 private int countInRange(int[] nums, int num, int lo, int hi) { 1748 int count = 0; 1749 for (int i = lo; i <= hi; i++) { 1750 if (nums[i] == num) { 1751 count++; 1752 } 1753 } 1754 return count; 1755 } 1756 1757 private int majorityElementRec(int[] nums, int lo, int hi) { 1758 // base case; the only element in an array of size 1 is the majority 1759 // element. 1760 if (lo == hi) { 1761 return nums[lo]; 1762 } 1763 1764 // recurse on left and right halves of this slice. 1765 int mid = (hi - lo) / 2 + lo; 1766 int left = majorityElementRec(nums, lo, mid); 1767 int right = majorityElementRec(nums, mid + 1, hi); 1768 1769 // if the two halves agree on the majority element, return it. 1770 if (left == right) { 1771 return left; 1772 } 1773 1774 // otherwise, count each element and return the "winner". 1775 int leftCount = countInRange(nums, left, lo, hi); 1776 int rightCount = countInRange(nums, right, lo, hi); 1777 1778 return leftCount > rightCount ? left : right; 1779 } 1780 1781 public int majorityElement(int[] nums) { 1782 return majorityElementRec(nums, 0, nums.length - 1); 1783 } 1784 } 1785 1786 /** 1787 * Boyer-Moore 投票算法[投票] 1788 */ 1789 public static int majorityElement5(int[] nums) { 1790 int count = 0; 1791 Integer candidate = null; 1792 1793 for (int num : nums) { 1794 if (count == 0) { 1795 candidate = num; 1796 } 1797 count += (num == candidate) ? 1 : -1; 1798 } 1799 1800 return candidate; 1801 } 1802 1803 1804 /** 1805 * 剑指 Offer 40. 最小的k个数 1806 * 输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。 1807 * [排序] 1808 */ 1809 public static int[] getLeastNumbers(int[] arr, int k) { 1810 int[] vec = new int[k]; 1811 Arrays.sort(arr); 1812 for (int i = 0; i < k; ++i) { 1813 vec[i] = arr[i]; 1814 } 1815 return vec; 1816 } 1817 1818 /** 1819 * 剑指 Offer 40. 最小的k个数 1820 * [数据结构][堆] 1821 */ 1822 class Solution40_2 { 1823 public int[] getLeastNumbers(int[] arr, int k) { 1824 int[] vec = new int[k]; 1825 if (k == 0) { // 排除 0 的情况 1826 return vec; 1827 } 1828 PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() { 1829 @Override 1830 public int compare(Integer num1, Integer num2) { 1831 return num2 - num1; 1832 } 1833 }); 1834 for (int i = 0; i < k; ++i) { 1835 queue.offer(arr[i]); 1836 } 1837 for (int i = k; i < arr.length; ++i) { 1838 if (queue.peek() > arr[i]) { 1839 queue.poll(); 1840 queue.offer(arr[i]); 1841 } 1842 } 1843 for (int i = 0; i < k; ++i) { 1844 vec[i] = queue.poll(); 1845 } 1846 return vec; 1847 } 1848 } 1849 1850 /** 1851 * 剑指 Offer 40. 最小的k个数 1852 * [递归][快排思想] 1853 */ 1854 class Solution40 { 1855 public int[] getLeastNumbers(int[] arr, int k) { 1856 randomizedSelected(arr, 0, arr.length - 1, k); 1857 int[] vec = new int[k]; 1858 for (int i = 0; i < k; ++i) { 1859 vec[i] = arr[i]; 1860 } 1861 return vec; 1862 } 1863 1864 private void randomizedSelected(int[] arr, int l, int r, int k) { 1865 if (l >= r) { 1866 return; 1867 } 1868 int pos = randomizedPartition(arr, l, r); 1869 int num = pos - l + 1; 1870 if (k == num) { 1871 return; 1872 } else if (k < num) { 1873 randomizedSelected(arr, l, pos - 1, k); 1874 } else { 1875 randomizedSelected(arr, pos + 1, r, k - num); 1876 } 1877 } 1878 1879 // 基于随机的划分 1880 private int randomizedPartition(int[] nums, int l, int r) { 1881 int i = new Random().nextInt(r - l + 1) + l; 1882 swap(nums, r, i); 1883 return partition(nums, l, r); 1884 } 1885 1886 private int partition(int[] nums, int l, int r) { 1887 int pivot = nums[r]; 1888 int i = l - 1; 1889 for (int j = l; j <= r - 1; ++j) { 1890 if (nums[j] <= pivot) { 1891 i = i + 1; 1892 swap(nums, i, j); 1893 } 1894 } 1895 swap(nums, i + 1, r); 1896 return i + 1; 1897 } 1898 1899 private void swap(int[] nums, int i, int j) { 1900 int temp = nums[i]; 1901 nums[i] = nums[j]; 1902 nums[j] = temp; 1903 } 1904 } 1905 1906 1907 /** 1908 * 剑指 Offer 41. 数据流中的中位数 1909 * 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。 1910 * 如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。 1911 * [栈][根堆][顶堆] 1912 */ 1913 static class MedianFinder { 1914 /** initialize your data structure here. */ 1915 Queue<Integer> A, B; 1916 public MedianFinder() { 1917 A = new PriorityQueue<>(); // 小顶堆,保存较大的一半 1918 B = new PriorityQueue<>((x, y) -> (y - x)); // 大顶堆,保存较小的一半 1919 } 1920 //从数据流中添加一个整数到数据结构中。 1921 public void addNum(int num) { 1922 if(A.size() != B.size()) { 1923 A.add(num); 1924 B.add(A.poll()); 1925 } else { 1926 B.add(num); 1927 A.add(B.poll()); 1928 } 1929 } 1930 // 返回目前所有元素的中位数。 1931 public double findMedian() { 1932 return A.size() != B.size() ? A.peek() : (A.peek() + B.peek()) / 2.0; 1933 } 1934 1935 } 1936 1937 /** 1938 * 剑指 Offer 42. 连续子数组的最大和 1939 * 输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。 1940 * 要求时间复杂度为O(n)。 1941 * [动态规划][SSR] 1942 */ 1943 public int maxSubArray(int[] nums) { 1944 int res = nums[0]; 1945 for(int i = 1; i < nums.length; i++) { 1946 nums[i] += Math.max(nums[i - 1], 0); 1947 res = Math.max(res, nums[i]); 1948 } 1949 return res; 1950 } 1951 1952 /** 1953 * 剑指 Offer 42. 连续子数组的最大和 1954 * 输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。 1955 * 要求时间复杂度为O(n)。 1956 * [分治][线段树][SSR] 1957 */ 1958 class Solution { 1959 public class Status { 1960 public int lSum, rSum, mSum, iSum; 1961 1962 public Status(int lSum, int rSum, int mSum, int iSum) { 1963 this.lSum = lSum; 1964 this.rSum = rSum; 1965 this.mSum = mSum; 1966 this.iSum = iSum; 1967 } 1968 } 1969 1970 public int maxSubArray(int[] nums) { 1971 return getInfo(nums, 0, nums.length - 1).mSum; 1972 } 1973 1974 public Status getInfo(int[] a, int l, int r) { 1975 if (l == r) { 1976 return new Status(a[l], a[l], a[l], a[l]); 1977 } 1978 int m = (l + r) >> 1; 1979 Status lSub = getInfo(a, l, m); 1980 Status rSub = getInfo(a, m + 1, r); 1981 return pushUp(lSub, rSub); 1982 } 1983 1984 public Status pushUp(Status l, Status r) { 1985 int iSum = l.iSum + r.iSum; 1986 int lSum = Math.max(l.lSum, l.iSum + r.lSum); 1987 int rSum = Math.max(r.rSum, r.iSum + l.rSum); 1988 int mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum + r.lSum); 1989 return new Status(lSum, rSum, mSum, iSum); 1990 } 1991 } 1992 1993 /** 1994 * 剑指 Offer 43. 1~n 整数中 1 出现的次数 1995 * 输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。 1996 * 例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。 1997 * 枚举每一数位上 11 的个数 1998 * 1999 */ 2000 public static int countDigitOne(int n) { 2001 // mulk 表示 10^k 2002 // 在下面的代码中,可以发现 k 并没有被直接使用到(都是使用 10^k) 2003 // 但为了让代码看起来更加直观,这里保留了 k 2004 long mulk = 1; 2005 int ans = 0; 2006 for (int k = 0; n >= mulk; ++k) { 2007 ans += (n / (mulk * 10)) * mulk + Math.min(Math.max(n % (mulk * 10) - mulk + 1, 0), mulk); 2008 mulk *= 10; 2009 } 2010 return ans; 2011 } 2012 2013 2014 2015 2016 /** 2017 *剑指 Offer 44. 数字序列中某一位的数字 2018 * 数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。 2019 * 请写一个函数,求任意第n位对应的数字。 2020 *[迭代] 2021 */ 2022 public static int findNthDigit(int n) { 2023 int digit = 1; 2024 long start = 1; 2025 long count = 9; 2026 while (n > count) { // 1. 2027 n -= count; 2028 digit += 1; 2029 start *= 10; 2030 count = digit * start * 9; 2031 } 2032 long num = start + (n - 1) / digit; // 2. 2033 return Long.toString(num).charAt((n - 1) % digit) - '0'; // 3. 2034 } 2035 2036 2037 /** 2038 * 剑指 Offer 45. 把数组排成最小的数 2039 * 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。 2040 * 2041 */ 2042 public String minNumber(int[] nums) { 2043 String[] strs = new String[nums.length]; 2044 for(int i = 0; i < nums.length; i++) 2045 { strs[i] = String.valueOf(nums[i]);} 2046 Arrays.sort(strs, (x, y) -> (x + y).compareTo(y + x)); 2047 StringBuilder res = new StringBuilder(); 2048 for(String s : strs) 2049 { res.append(s);} 2050 return res.toString(); 2051 } 2052 /** 2053 * 剑指 Offer 45. 把数组排成最小的数 2054 * 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。 2055 *[快速排序][SSR] 2056 */ 2057 class Solution45 { 2058 public String minNumber(int[] nums) { 2059 String[] strs = new String[nums.length]; 2060 for(int i = 0; i < nums.length; i++) 2061 strs[i] = String.valueOf(nums[i]); 2062 quickSort(strs, 0, strs.length - 1); 2063 StringBuilder res = new StringBuilder(); 2064 for(String s : strs) 2065 res.append(s); 2066 return res.toString(); 2067 } 2068 void quickSort(String[] strs, int l, int r) { 2069 if(l >= r) {return;} 2070 int i = l, j = r; 2071 String tmp = strs[i]; 2072 while(i < j) { 2073 while((strs[j] + strs[l]).compareTo(strs[l] + strs[j]) >= 0 && i < j) {j--;} 2074 while((strs[i] + strs[l]).compareTo(strs[l] + strs[i]) <= 0 && i < j) {i++;} 2075 tmp = strs[i]; 2076 strs[i] = strs[j]; 2077 strs[j] = tmp; 2078 } 2079 strs[i] = strs[l]; 2080 strs[l] = tmp; 2081 quickSort(strs, l, i - 1); 2082 quickSort(strs, i + 1, r); 2083 } 2084 } 2085 2086 /** 2087 * 标准快排算法 2088 * 既快又省空间 2089 * [快速排序][SSR] 2090 */ 2091 public class QuickSort { 2092 public void quickSort(int[] arr,int low,int high){ 2093 int i,j,temp,t; 2094 if(low>high){ 2095 return; 2096 } 2097 i=low; 2098 j=high; 2099 //temp就是基准位 2100 temp = arr[low]; 2101 2102 while (i<j) { 2103 //先看右边,依次往左递减 2104 while (temp<=arr[j]&&i<j) { 2105 j--; 2106 } 2107 //再看左边,依次往右递增 2108 while (temp>=arr[i]&&i<j) { 2109 i++; 2110 } 2111 //如果满足条件则交换 2112 if (i<j) { 2113 t = arr[j]; 2114 arr[j] = arr[i]; 2115 arr[i] = t; 2116 } 2117 2118 } 2119 //最后将基准为与i和j相等位置的数字交换 2120 arr[low] = arr[i]; 2121 arr[i] = temp; 2122 //递归调用左半数组 2123 quickSort(arr, low, j-1); 2124 //递归调用右半数组 2125 quickSort(arr, j+1, high); 2126 } 2127 2128 } 2129 2130 /** 2131 * 剑指 Offer 46. 把数字翻译成字符串 2132 * 给定一个数字,我们按照如下规则把它翻译为字符串: 2133 * 0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。 2134 * 一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。 2135 * [动态规划][递推][递归][SSR] 2136 * 2137 *我们可以归纳出翻译的规则,字符串的第 i 位置: 2138 * 可以单独作为一位来翻译 2139 * 如果第 i - 1位和第 i 位组成的数字在 10到 25 之间,可以把这两位连起来翻译 2140 * 到这里,我们发现它和「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。我们可以列出这样的动态规划转移方程: 2141 * f(i)=f(i−1)+f(i−2)[i−1≥0,10≤x≤25] 2142 * 边界条件是 f(-1) = 0f(−1)=0,f(0) = 1f(0)=1。方程中 [c] 的意思是 cc 为真的时候 [c] = 1[c]=1,否则 [c] = 0[c]=0。 2143 * 有了这个方程我们不难给出一个时间复杂度为 O(n) ,空间复杂度为 O(n) 的实现。考虑优化空间复杂度:这里的 f(i)f(i) 只和它的前两项 f(i - 1) 和 f(i - 2) 相关,我们可以运用「滚动数组」思想把 ff 数组压缩成三个变量,这样空间复杂度就变成了 O(1)。 2144 * 2145 * 类似青蛙跳台 2146 * * [动态规划][SSR] 2147 */ 2148 public static int translateNum46_动态规划版1(int num) { 2149 String s = String.valueOf(num); 2150 //a为dp[n-1],b为 dp[n-2] 2151 int a = 1, b = 1; 2152 for(int i = 2; i <= s.length(); i++) { 2153 String tmp = s.substring(i - 2, i); 2154 int c = tmp.compareTo("10") >= 0 && tmp.compareTo("25") <= 0 ? a + b : a; 2155 b = a; 2156 a = c; 2157 } 2158 return a; 2159 } 2160 2161 /** 2162 *存储过程的解法,易理解 2163 */ 2164 public int translateNum46动态规划版2(int num) { 2165 String s = String.valueOf(num); 2166 int[] dp = new int[s.length()+1]; 2167 dp[0] = 1; 2168 dp[1] = 1; 2169 for(int i = 2; i <= s.length(); i ++){ 2170 String temp = s.substring(i-2, i); 2171 if(temp.compareTo("10") >= 0 && temp.compareTo("25") <= 0) 2172 {dp[i] = dp[i-1] + dp[i-2];} 2173 else 2174 { dp[i] = dp[i-1];} 2175 } 2176 return dp[s.length()]; 2177 } 2178 2179 public static int translateNum46(int num) { 2180 if(num < 10) {return 1;} 2181 if(num <= 25) {return 2;} 2182 2183 int digit = 1; 2184 while(num / digit >= 10){ 2185 digit *= 10; 2186 } 2187 2188 int high = num / digit; 2189 int low = num - high * digit; 2190 int high2 = num / (digit/10); 2191 int low2 = num - high2 * (digit/10); 2192 2193 return ( high2 > 25 ) ? translateNum46(low) : translateNum46(low2) + translateNum46(low); 2194 } 2195 2196 2197 2198 /** 2199 * 剑指 Offer 47. 礼物的最大价值 2200 * 在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。 2201 * 你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。 2202 * 给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物? 2203 * [SSR][求最优解][动态规划] 2204 */ 2205 public int maxValue47(int[][] grid) { 2206 int row = grid.length; 2207 int column = grid[0].length; 2208 //dp[i][j]表示从grid[0][0]到grid[i - 1][j - 1]时的最大价值 2209 int[][] dp = new int[row + 1][column + 1]; 2210 for (int i = 1; i <= row; i++) { 2211 for (int j = 1; j <= column; j++) { 2212 dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1]; 2213 } 2214 } 2215 return dp[row][column]; 2216 } 2217 2218 /** 2219 * DIY的方法,leecode解题超时,由于是全部遍历,其实不用,用int[][] gridMax 右下角存储每个"子数组"当前最大值结果。 [递归][DFS] 2220 */ 2221 static class Solution47 { 2222 static int xl,y1,result=0; 2223 public static int maxValue(int[][] grid) { 2224 xl =grid.length; 2225 y1=grid[0].length; 2226 result= 0; 2227 maxV(grid,0,0,grid[0][0]); 2228 return result; 2229 } 2230 2231 public static void maxV(int[][] grid,int x,int y,int cunt ){ 2232 if(x==xl-1||y==y1-1){ 2233 result = cunt+grid[x][y]>result?cunt+grid[x][y]:result; 2234 return; 2235 } 2236 maxV(grid,x+1,y,grid[x][y]+cunt); 2237 maxV(grid,x,y+1,grid[x][y]+cunt); 2238 2239 } 2240 } 2241 2242 /** 2243 * 优化后 2244 */ 2245 private static class Solution47_2 { 2246 static int xl,y1; 2247 static int[][] result; 2248 public static int maxValue(int[][] grid) { 2249 xl =grid.length; 2250 y1=grid[0].length; 2251 result= new int[xl][y1]; 2252 result[0][0]=0; 2253 maxV(grid,0,0); 2254 return result[xl-1][y1-1]; 2255 } 2256 2257 public static void maxV(int[][] grid,int x,int y ){ 2258 if(x==xl||y==y1){ 2259 return ; 2260 } 2261 int yy= y==0?0: result[x][y-1]; 2262 int xx= x==0?0: result[x-1][y]; 2263 result[x][y] =xx+grid[x][y]>yy+grid[x][y]?xx+grid[x][y]:yy+grid[x][y]; 2264 maxV(grid,x+1,y); 2265 maxV(grid,x,y+1); 2266 } 2267 } 2268 2269 private static class Solution47_3 { 2270 //用来存储该节点到右下角的最大价值 2271 static int[][] store; 2272 static int row, column; 2273 2274 public static int maxValue(int[][] grid) { 2275 row = grid.length; 2276 column = grid[0].length; 2277 store = new int[row][column]; 2278 return helper(grid, 0, 0); 2279 } 2280 2281 public static int helper(int[][] grid, int i, int j) { 2282 int right = 0, down = 0; 2283 if (i == row - 1 && j == column - 1) 2284 { return grid[i][j];} 2285 //当前节点越界 2286 if (i < 0 || j < 0 || i >= row || j >= column) 2287 { return 0;} 2288 //右边没有被访问 2289 if (j + 1 < column && store[i][j + 1] == 0) { 2290 right = helper(grid, i, j + 1); 2291 store[i][j + 1] = right; 2292 } 2293 //右边已经被访问了,取存储的值 2294 if (j + 1 < column && store[i][j + 1] > 0) { 2295 right = store[i][j + 1]; 2296 } 2297 //下边没有被访问了 2298 if (i + 1 < row && store[i + 1][j] == 0) { 2299 down = helper(grid, i + 1, j); 2300 store[i + 1][j] = down; 2301 } 2302 //下边已经被访问了,取存储的值 2303 if (i + 1 < row && store[i + 1][j] == 1) { 2304 down = store[i + 1][j]; 2305 } 2306 //该节点到右下角的最大价值 2307 return Math.max(right, down) + grid[i][j]; 2308 } 2309 } 2310 2311 2312 2313 2314 /** 2315 * 剑指 Offer 48. 最长不含重复字符的子字符串 2316 * 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。 2317 * [动态规划] 2318 */ 2319 public static int lengthOfLongestSubstring(String s) { 2320 Map<Character, Integer> dic = new HashMap<>(); 2321 int res = 0, tmp = 0; 2322 for(int j = 0; j < s.length(); j++) { 2323 int i = dic.getOrDefault(s.charAt(j), -1); // 获取索引 i 2324 dic.put(s.charAt(j), j); // 更新哈希表 2325 tmp = tmp < j - i ? tmp + 1 : j - i; // dp[j - 1] -> dp[j] 2326 res = Math.max(res, tmp); // max(dp[j - 1], dp[j]) 2327 } 2328 return res; 2329 } 2330 2331 /** 2332 * 剑指 Offer 48. 最长不含重复字符的子字符串 2333 * 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。 2334 * [双指针] 2335 */ 2336 public static int lengthOfLongestSubstring2(String s) { 2337 Map<Character, Integer> dic = new HashMap<>(); 2338 int i = -1, res = 0; 2339 for(int j = 0; j < s.length(); j++) { 2340 if(dic.containsKey(s.charAt(j))) 2341 {i = Math.max(i, dic.get(s.charAt(j))); }// 更新左指针 i 2342 dic.put(s.charAt(j), j); // 哈希表记录 2343 res = Math.max(res, j - i); // 更新结果 2344 } 2345 return res; 2346 } 2347 2348 2349 2350 /** 2351 * 剑指 Offer 49. 丑数 2352 * 我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。 2353 *[动态规划][SSR] 2354 */ 2355 public int nthUglyNumber(int n) { 2356 int[] dp = new int[n + 1]; 2357 dp[1] = 1; 2358 int p2 = 1, p3 = 1, p5 = 1; 2359 for (int i = 2; i <= n; i++) { 2360 int num2 = dp[p2] * 2, num3 = dp[p3] * 3, num5 = dp[p5] * 5; 2361 dp[i] = Math.min(Math.min(num2, num3), num5); 2362 if (dp[i] == num2) { 2363 p2++; 2364 } 2365 if (dp[i] == num3) { 2366 p3++; 2367 } 2368 if (dp[i] == num5) { 2369 p5++; 2370 } 2371 } 2372 return dp[n]; 2373 } 2374 2375 2376 2377 2378 2379 /** 2380 * 剑指 Offer 50. 第一个只出现一次的字符 2381 * 在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。 2382 */ 2383 public char firstUniqChar(String s) { 2384 Integer l = s.length(); 2385 String re=" "; 2386 for(Integer i=0;i<l;i++){ 2387 String nowS= s.substring(i,i+1); 2388 String leftS=s.substring(0,i)+s.substring(i+1,l); 2389 if(!leftS.contains(nowS)){ 2390 return nowS.toCharArray()[0]; 2391 } 2392 } 2393 return re.toCharArray()[0]; 2394 } 2395 2396 public char firstUniqChar2(String s) { 2397 HashMap<Character, Boolean> dic = new HashMap<>(); 2398 char[] sc = s.toCharArray(); 2399 for(char c : sc) 2400 { dic.put(c, !dic.containsKey(c));} 2401 for(char c : sc) 2402 { if(dic.get(c)) {return c;}} 2403 return ' '; 2404 } 2405 2406 2407 /** 2408 * 剑指 Offer 51. 数组中的逆序对 2409 * 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。 2410 * 输入一个数组,求出这个数组中的逆序对的总数。 2411 *[自创超时] 2412 */ 2413 public static int reversePairs(int[] nums) { 2414 int res = 0; 2415 for(int x=0;x<nums.length;x++){ 2416 for(int y=x+1;y<nums.length;y++){ 2417 if(nums[x]>nums[y]){ 2418 sysout(nums[x]+"-"+nums[y]); 2419 res++; 2420 } 2421 } 2422 } 2423 return res; 2424 } 2425 2426 2427 2428 /** 2429 * [归并排序][SSR][分治][递归] 2430 */ 2431 public class Solution51 { 2432 public int reversePairs(int[] nums) { 2433 int len = nums.length; 2434 2435 if (len < 2) { 2436 return 0; 2437 } 2438 2439 int[] copy = new int[len]; 2440 for (int i = 0; i < len; i++) { 2441 copy[i] = nums[i]; 2442 } 2443 2444 int[] temp = new int[len]; 2445 return reversePairs(copy, 0, len - 1, temp); 2446 } 2447 2448 private int reversePairs(int[] nums, int left, int right, int[] temp) { 2449 if (left == right) { 2450 return 0; 2451 } 2452 2453 int mid = left + (right - left) / 2; 2454 int leftPairs = reversePairs(nums, left, mid, temp); 2455 int rightPairs = reversePairs(nums, mid + 1, right, temp); 2456 2457 if (nums[mid] <= nums[mid + 1]) { 2458 return leftPairs + rightPairs; 2459 } 2460 2461 int crossPairs = mergeAndCount(nums, left, mid, right, temp); 2462 return leftPairs + rightPairs + crossPairs; 2463 } 2464 2465 private int mergeAndCount(int[] nums, int left, int mid, int right, int[] temp) { 2466 for (int i = left; i <= right; i++) { 2467 temp[i] = nums[i]; 2468 } 2469 2470 int i = left; 2471 int j = mid + 1; 2472 2473 int count = 0; 2474 for (int k = left; k <= right; k++) { 2475 2476 if (i == mid + 1) { 2477 nums[k] = temp[j]; 2478 j++; 2479 } else if (j == right + 1) { 2480 nums[k] = temp[i]; 2481 i++; 2482 } else if (temp[i] <= temp[j]) { 2483 nums[k] = temp[i]; 2484 i++; 2485 } else { 2486 nums[k] = temp[j]; 2487 j++; 2488 count += (mid - i + 1); 2489 } 2490 } 2491 return count; 2492 } 2493 } 2494 2495 2496 /** 2497 * 离散化树状数组 2498 * [离散化][树状数组] 2499 * 「树状数组」是一种可以动态维护序列前缀和的数据结构,它的功能是: 2500 * 2501 * 单点更新 update(i, v): 把序列 i 位置的数加上一个值 v,这题 v = 1 2502 * 区间查询 query(i): 查询序列 [1⋯i] 区间的区间和,即 i 位置的前缀和 2503 * 修改和查询的时间代价都是 O(logn),其中 nn 为需要维护前缀和的序列的长度。 2504 */ 2505 class Solution51_2 { 2506 public int reversePairs(int[] nums) { 2507 int n = nums.length; 2508 int[] tmp = new int[n]; 2509 System.arraycopy(nums, 0, tmp, 0, n); 2510 // 离散化 2511 Arrays.sort(tmp); 2512 for (int i = 0; i < n; ++i) { 2513 nums[i] = Arrays.binarySearch(tmp, nums[i]) + 1; 2514 } 2515 // 树状数组统计逆序对 2516 BIT bit = new BIT(n); 2517 int ans = 0; 2518 for (int i = n - 1; i >= 0; --i) { 2519 ans += bit.query(nums[i] - 1); 2520 bit.update(nums[i]); 2521 } 2522 return ans; 2523 } 2524 } 2525 2526 class BIT { 2527 private int[] tree; 2528 private int n; 2529 2530 public BIT(int n) { 2531 this.n = n; 2532 this.tree = new int[n + 1]; 2533 } 2534 2535 public int lowbit(int x) { 2536 return x & (-x); 2537 } 2538 2539 public int query(int x) { 2540 int ret = 0; 2541 while (x != 0) { 2542 ret += tree[x]; 2543 x -= lowbit(x); 2544 } 2545 return ret; 2546 } 2547 2548 public void update(int x) { 2549 while (x <= n) { 2550 ++tree[x]; 2551 x += lowbit(x); 2552 } 2553 } 2554 } 2555 2556 2557 /** 2558 * 剑指 Offer 52. 两个链表的第一个公共节点 2559 */ 2560 public ListNode getIntersectionNode(ListNode headA, ListNode headB) { 2561 ListNode PA = headA; 2562 ListNode PB = headB; 2563 while (PA != PB) { 2564 PA = PA == null ? headB : PA.next; 2565 PB = PB == null ? headA : PB.next; 2566 } 2567 return PA; 2568 } 2569 2570 2571 2572 /** 2573 * 剑指 Offer 53 - I. 在排序数组中查找数字 I 2574 */ 2575 public int search(int[] nums, int target) { 2576 // 搜索右边界 right 2577 int i = 0, j = nums.length - 1; 2578 while(i <= j) { 2579 int m = (i + j) / 2; 2580 if(nums[m] <= target) {i = m + 1;} 2581 else {j = m - 1;} 2582 } 2583 int right = i; 2584 // 若数组中无 target ,则提前返回 2585 if(j >= 0 && nums[j] != target) {return 0;} 2586 // 搜索左边界 right 2587 i = 0; j = nums.length - 1; 2588 while(i <= j) { 2589 int m = (i + j) / 2; 2590 if(nums[m] < target) { 2591 i = m + 1; 2592 }else { 2593 j = m - 1; 2594 } 2595 } 2596 int left = j; 2597 return right - left - 1; 2598 } 2599 2600 /** 2601 * 剑指 Offer 53 - I. 在排序数组中查找数字 I 2602 * 二分法 2603 */ 2604 public int search2(int[] nums, int target) { 2605 return helper(nums, target) - helper(nums, target - 1); 2606 } 2607 int helper(int[] nums, int tar) { 2608 int i = 0, j = nums.length - 1; 2609 while(i <= j) { 2610 int m = (i + j) / 2; 2611 if(nums[m] <= tar) {i = m + 1;} 2612 else {j = m - 1;} 2613 } 2614 return i; 2615 } 2616 2617 /** 2618 * 剑指 Offer 53 - II. 0~n-1中缺失的数字 2619 * 一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。 2620 * 在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。 2621 * [二分][SR] 2622 */ 2623 public int missingNumber(int[] nums) { 2624 int i = 0, j = nums.length - 1; 2625 while(i <= j) { 2626 int m = (i + j) / 2; 2627 if(nums[m] == m) {i = m + 1;} 2628 else {j = m - 1;} 2629 } 2630 return i; 2631 } 2632 2633 2634 /** 2635 * 剑指 Offer 54. 二叉搜索树的第k大节点 2636 * [中序遍历][SSR] 2637 */ 2638 class Solution54 { 2639 int res, k; 2640 public int kthLargest(TreeNode root, int k) { 2641 this.k = k; 2642 dfs(root); 2643 return res; 2644 } 2645 void dfs(TreeNode root) { 2646 if(root == null) {return;} 2647 dfs(root.right); 2648 if(k == 0) {return;} 2649 if(--k == 0) {res = root.val;} 2650 dfs(root.left); 2651 } 2652 } 2653 2654 /** 2655 * 输入一棵二叉树的根节点,求该树的深度。 2656 * 从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。 2657 * 后序遍历(DFS)[SSR] 2658 */ 2659 public int maxDepth1(TreeNode root) { 2660 if(root == null) {return 0;} 2661 return Math.max(maxDepth1(root.left), maxDepth1(root.right)) + 1; 2662 } 2663 2664 2665 /** 2666 * 层序遍历(BFS)[SSR] 2667 */ 2668 public int maxDepth2(TreeNode root) { 2669 if(root == null) {return 0;} 2670 List<TreeNode> queue = new LinkedList<TreeNode>() {{ add(root); }}, tmp; 2671 int res = 0; 2672 while(!queue.isEmpty()) { 2673 tmp = new LinkedList<>(); 2674 for(TreeNode node : queue) { 2675 if(node.left != null) {tmp.add(node.left);} 2676 if(node.right != null) {tmp.add(node.right);} 2677 } 2678 queue = tmp; 2679 res++; 2680 } 2681 return res; 2682 } 2683 2684 2685 /** 2686 * 剑指 Offer 55 - II. 平衡二叉树 2687 * 输入一棵二叉树的根节点,判断该树是不是平衡二叉树。 2688 * 如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。 2689 * 后序遍历 + 剪枝 (从底至顶)[SSR] 2690 */ 2691 static class Solution55 { 2692 public boolean isBalanced(TreeNode root) { 2693 return recur(root) != -1; 2694 } 2695 2696 private int recur(TreeNode root) { 2697 if (root == null) {return 0;} 2698 int left = recur(root.left); 2699 if(left == -1) {return -1;} 2700 int right = recur(root.right); 2701 if(right == -1) {return -1;} 2702 return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1; 2703 } 2704 2705 } 2706 2707 2708 2709 /** 2710 * 先序遍历 + 判断深度 (从顶至底)[SSR] 2711 */ 2712 class Solution55_2 { 2713 public boolean isBalanced(TreeNode root) { 2714 if (root == null) {return true;} 2715 return Math.abs(depth(root.left) - depth(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right); 2716 } 2717 2718 private int depth(TreeNode root) { 2719 if (root == null) {return 0;} 2720 return Math.max(depth(root.left), depth(root.right)) + 1; 2721 } 2722 } 2723 2724 /** 2725 * 剑指 Offer 56 - I. 数组中数字出现的次数 2726 * 一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。 2727 * 请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。 2728 * [分组异或][位运算] 2729 */ 2730 public int[] singleNumbers(int[] nums) { 2731 int ret = 0; 2732 for (int n : nums) { 2733 ret ^= n; 2734 } 2735 int div = 1; 2736 while ((div & ret) == 0) { 2737 div <<= 1; 2738 } 2739 int a = 0, b = 0; 2740 for (int n : nums) { 2741 if ((div & n) != 0) { 2742 a ^= n; 2743 } else { 2744 b ^= n; 2745 } 2746 } 2747 return new int[]{a, b}; 2748 } 2749 2750 2751 2752 /** 2753 * 有限状态自动机[位运算] 2754 */ 2755 public int singleNumber(int[] nums) { 2756 int ones = 0, twos = 0; 2757 for(int num : nums){ 2758 ones = ones ^ num & ~twos; 2759 twos = twos ^ num & ~ones; 2760 } 2761 return ones; 2762 } 2763 2764 2765 /** 2766 * 剑指 Offer 56 - II. 数组中数字出现的次数 II 2767 * 在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。 2768 * 遍历统计[位运算] 2769 */ 2770 public int singleNumber2(int[] nums) { 2771 int[] counts = new int[32]; 2772 for(int num : nums) { 2773 for(int j = 0; j < 32; j++) { 2774 counts[j] += num & 1; 2775 num >>>= 1; 2776 } 2777 } 2778 int res = 0, m = 3; 2779 for(int i = 0; i < 32; i++) { 2780 res <<= 1; 2781 res |= counts[31 - i] % m; 2782 } 2783 return res; 2784 } 2785 2786 2787 2788 2789 2790 /** 2791 * 剑指 Offer 57. 和为s的两个数字 2792 * 输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。 2793 * 如果有多对数字的和等于s,则输出任意一对即可。 2794 * [双指针] 2795 */ 2796 public int[] twoSum(int[] nums, int target) { 2797 int i = 0, j = nums.length - 1; 2798 while(i < j) { 2799 int s = nums[i] + nums[j]; 2800 if(s < target) {i++;} 2801 else if(s > target) {j--;} 2802 else {return new int[] { nums[i], nums[j] };} 2803 } 2804 return new int[0]; 2805 } 2806 2807 2808 2809 /** 2810 * 剑指 Offer 57 - II. 和为s的连续正数序列 2811 * 输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。 2812 * 序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。 2813 * [滑动窗口][SSR] 2814 */ 2815 public int[][] findContinuousSequence(int target) { 2816 int left = 1; // 滑动窗口的左边界 2817 int right = 2; // 滑动窗口的右边界 2818 int sum = left + right; // 滑动窗口中数字的和 2819 List<int[]> res = new ArrayList<>(); 2820 //窗口的左边是窗口内的最小数字,只能小于等于target / 2, 2821 //因为题中要求的是至少含有两个数 2822 while (left <= target / 2) { 2823 if (sum < target) { 2824 //如果窗口内的值比较小,右边界继续向右移动, 2825 //来扩大窗口 2826 sum += ++right; 2827 } else if (sum > target) { 2828 //如果窗口内的值比较大,左边边界往右移动, 2829 //缩小窗口 2830 sum -= left++; 2831 } else { 2832 //如果窗口内的值正好等于target,就把窗口内的值记录 2833 //下来,然后窗口的左边和右边同时往右移一步 2834 int[] arr = new int[right - left + 1]; 2835 for (int k = left; k <= right; k++) { 2836 arr[k - left] = k; 2837 } 2838 res.add(arr); 2839 //左边和右边同时往右移一位 2840 sum -= left++; 2841 sum += ++right; 2842 } 2843 } 2844 //把结果转化为数组 2845 return res.toArray(new int[res.size()][]); 2846 } 2847 2848 2849 /** 2850 * 数学公式:S=n*a+n*(n-1)/2 2851 */ 2852 public int[][] findContinuousSequence2(int target) { 2853 List<int[]> res = new ArrayList<>(); 2854 int n = 2; 2855 //死循环 2856 while (true) { 2857 int total = target - n * (n - 1) / 2; 2858 //当分子小于等于0的时候,退出循环 2859 if (total <= 0) 2860 { break;} 2861 //如果首项是正整数,满足条件 2862 if (total % n == 0) { 2863 int[] arr = new int[n]; 2864 //找出首项的值 2865 int startValue = total / n; 2866 for (int k = 0; k < n; k++) { 2867 arr[k] = startValue + k; 2868 } 2869 res.add(arr); 2870 } 2871 //继续找 2872 n++; 2873 } 2874 //反转,比如当target等于9的时候,结果是 2875 //[[4,5],[2,3,4]],但题中要求的是不同 2876 // 序列按照首个数字从小到大排列,所以这里反转一下 2877 Collections.reverse(res); 2878 //把list转化为数组 2879 return res.toArray(new int[res.size()][]); 2880 } 2881 2882 /** 2883 * 数学公式2: 2884 * 假如target是两个连续数字的和,那么这个序列的首项就是(target-1)/2。 2885 * 假如target是三个连续数字的和,那么这个序列的首项就是(target-1-2)/3。 2886 * 假如target是四个连续数字的和,那么这个序列的首项就是(target-1-2-3)/4。 2887 */ 2888 public int[][] findContinuousSequence3(int target) { 2889 List<int[]> res = new ArrayList<>(); 2890 //因为至少是两个数,所以target先减1 2891 target--; 2892 for (int n = 2; target > 0; n++) { 2893 //找到了一组满足条件的序列 2894 if (target % n == 0) { 2895 int[] arr = new int[n]; 2896 //找出首项的值 2897 int startValue = target / n; 2898 for (int k = 0; k < n; k++) { 2899 arr[k] = startValue + k; 2900 } 2901 res.add(arr); 2902 } 2903 target -= n; 2904 } 2905 Collections.reverse(res); 2906 //把list转化为数组 2907 return res.toArray(new int[res.size()][]); 2908 } 2909 2910 2911 /** 2912 * 字符串反转 2913 */ 2914 public static String reverseWords(String s) { 2915 /* char[] ss=s.toCharArray(); 2916 int leng = ss.length-1; 2917 for(int i=leng;i>=leng/2;i--){ 2918 char st= ss[i]; 2919 ss[i]=ss[leng-i]; 2920 ss[leng-i]=st; 2921 } 2922 StringBuffer str5 = new StringBuffer(); 2923 for (char s1 : ss) { 2924 str5.append(s1); 2925 } 2926 return str5.toString() ;*/ 2927 return new StringBuffer(s).reverse().toString(); 2928 } 2929 2930 /** 2931 * 剑指 Offer 58 - I. 翻转单词顺序 2932 * 输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。 2933 * 为简单起见,标点符号和普通字母一样处理。 2934 * 例如输入字符串"I am a student. ",则输出"student. a am I"。 2935 * [双指针] 2936 */ 2937 public String reverseWords2(String s) { 2938 s = s.trim(); // 删除首尾空格 2939 int j = s.length() - 1, i = j; 2940 StringBuilder res = new StringBuilder(); 2941 while(i >= 0) { 2942 while(i >= 0 && s.charAt(i) != ' ') {i--;} // 搜索首个空格 2943 res.append(s.substring(i + 1, j + 1) + " "); // 添加单词 2944 while(i >= 0 && s.charAt(i) == ' ') {i--;} // 跳过单词间空格 2945 j = i; // j 指向下个单词的尾字符 2946 } 2947 return res.toString().trim(); // 转化为字符串并返回 2948 } 2949 2950 /** 2951 * 分割 + 倒序 2952 */ 2953 public String reverseWords3(String s) { 2954 String[] strs = s.trim().split(" "); // 删除首尾空格,分割字符串 2955 StringBuilder res = new StringBuilder(); 2956 for(int i = strs.length - 1; i >= 0; i--) { // 倒序遍历单词列表 2957 if(strs[i].equals("")) {continue;} // 遇到空单词则跳过 2958 res.append(strs[i] + " "); // 将单词拼接至 StringBuilder 2959 } 2960 return res.toString().trim(); // 转化为字符串,删除尾部空格,并返回 2961 } 2962 2963 /** 2964 * 剑指 Offer 58 - II. 左旋转字符串 2965 * 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。 2966 * 字符串切片 2967 */ 2968 public String reverseLeftWords(String s, int n) { 2969 return s.substring(n, s.length()) + s.substring(0, n); 2970 } 2971 2972 public String reverseLeftWords2(String s, int n) { 2973 StringBuilder res = new StringBuilder(); 2974 for(int i = n; i < s.length(); i++) 2975 {res.append(s.charAt(i));} 2976 for(int i = 0; i < n; i++) 2977 { res.append(s.charAt(i));} 2978 return res.toString(); 2979 } 2980 2981 2982 /** 2983 * 字符串遍历拼接 2984 */ 2985 public String reverseLeftWords3(String s, int n) { 2986 String res = ""; 2987 for(int i = n; i < n + s.length(); i++) 2988 { res += s.charAt(i % s.length());} 2989 return res; 2990 } 2991 2992 /** 2993 * 剑指 Offer 59 - I. 滑动窗口的最大值 2994 * 给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。 2995 * [双端队列][单调栈][滑动窗口] 2996 */ 2997 class Solution59 { 2998 public int[] maxSlidingWindow(int[] nums, int k) { 2999 if(nums.length == 0 || k == 0) {return new int[0];} 3000 Deque<Integer> deque = new LinkedList<>(); 3001 int[] res = new int[nums.length - k + 1]; 3002 for(int j = 0, i = 1 - k; j < nums.length; i++, j++) { 3003 // 删除 deque 中对应的 nums[i-1] 3004 if(i > 0 && deque.peekFirst() == nums[i - 1]) 3005 { deque.removeFirst();} 3006 // 保持 deque 递减 3007 while(!deque.isEmpty() && deque.peekLast() < nums[j]) 3008 { deque.removeLast();} 3009 deque.addLast(nums[j]); 3010 // 记录窗口最大值 3011 if(i >= 0) 3012 { res[i] = deque.peekFirst();} 3013 } 3014 return res; 3015 } 3016 } 3017 3018 /** 3019 * 剑指 Offer 59 - II. 队列的最大值 3020 * 请定义一个队列并实现函数 max_value 得到队列里的最大值, 3021 * 要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。 3022 * 若队列为空,pop_front 和 max_value 需要返回 -1 3023 * [双栈] 3024 */ 3025 class MaxQueue { 3026 Queue<Integer> q; 3027 Deque<Integer> d; 3028 3029 public MaxQueue() { 3030 q = new LinkedList<Integer>(); 3031 d = new LinkedList<Integer>(); 3032 } 3033 3034 public int max_value() { 3035 if (d.isEmpty()) { 3036 return -1; 3037 } 3038 return d.peekFirst(); 3039 } 3040 3041 public void push_back(int value) { 3042 while (!d.isEmpty() && d.peekLast() < value) { 3043 d.pollLast(); 3044 } 3045 d.offerLast(value); 3046 q.offer(value); 3047 } 3048 3049 public int pop_front() { 3050 if (q.isEmpty()) { 3051 return -1; 3052 } 3053 int ans = q.poll(); 3054 if (ans == d.peekFirst()) { 3055 d.pollFirst(); 3056 } 3057 return ans; 3058 } 3059 } 3060 3061 /** 3062 * 剑指 Offer 60. n个骰子的点数 3063 * 把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。 3064 * [动态规划][SSR] 3065 * 由于新增骰子的点数只可能为 11 至 66 ,因此概率 f(n - 1, x)仅与 f(n, x + 1) , 3066 * f(n, x + 2), ... , f(n, x + 6) 相关。 3067 * 因而,遍历 f(n - 1) 中各点数和的概率,并将其相加至 f(n) 3068 * 中所有相关项,即可完成 f(n - 1)至 f(n) 的递推。 3069 */ 3070 class Solution60 { 3071 public double[] dicesProbability(int n) { 3072 double[] dp = new double[6]; 3073 Arrays.fill(dp, 1.0 / 6.0); 3074 for (int i = 2; i <= n; i++) { 3075 double[] tmp = new double[5 * i + 1]; 3076 for (int j = 0; j < dp.length; j++) { 3077 for (int k = 0; k < 6; k++) { 3078 tmp[j + k] += dp[j] / 6.0; 3079 } 3080 } 3081 dp = tmp; 3082 } 3083 return dp; 3084 } 3085 } 3086 3087 /** 3088 * 剑指 Offer 61. 扑克牌中的顺子 3089 * 从若干副扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。 3090 */ 3091 class Solution61 { 3092 public boolean isStraight(int[] nums) { 3093 Set<Integer> repeat = new HashSet<>(); 3094 int max = 0, min = 14; 3095 for(int num : nums) { 3096 if(num == 0) continue; // 跳过大小王 3097 max = Math.max(max, num); // 最大牌 3098 min = Math.min(min, num); // 最小牌 3099 if(repeat.contains(num)) return false; // 若有重复,提前返回 false 3100 repeat.add(num); // 添加此牌至 Set 3101 } 3102 return max - min < 5; // 最大牌 - 最小牌 < 5 则可构成顺子 3103 } 3104 } 3105 3106 /** 3107 * 剑指 Offer 64. 求1+2+…+n 3108 * 求 1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。 3109 */ 3110 class Solution64 { 3111 public int sumNums(int n) { 3112 boolean flag = n > 0 && (n += sumNums(n - 1)) > 0; 3113 return n; 3114 } 3115 } 3116 3117 /** 3118 * 剑指 Offer 65. 不用加减乘除做加法 3119 * 写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。 3120 *本题考察对位运算的灵活使用,即使用位运算实现加法。 3121 * 设两数字的二进制形式 a, b,其求和 s = a + b,a(i) 代表 a 的二进制第 i 位,则分为以下四种情况: 3122 * 无进位和 与 异或运算 规律相同,进位 和 与运算 规律相同(并需左移一位)。 3123 * 因此,无进位和 n 与进位 c 的计算公式如下; 3124 */ 3125 class Solution65 { 3126 public int add(int a, int b) { 3127 while(b != 0) { // 当进位为 0 时跳出 3128 int c = (a & b) << 1; // c = 进位 3129 a ^= b; // a = 非进位和 3130 b = c; // b = 进位 3131 } 3132 return a; 3133 } 3134 } 3135 3136 /** 3137 * 剑指 Offer 66. 构建乘积数组 3138 * 给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1], 3139 * 其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 3140 * 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。 3141 * 3142 */ 3143 class Solution66 { 3144 public int[] constructArr(int[] a) { 3145 int len = a.length; 3146 if(len == 0) return new int[0]; 3147 int[] b = new int[len]; 3148 b[0] = 1; 3149 int tmp = 1; 3150 for(int i = 1; i < len; i++) { 3151 b[i] = b[i - 1] * a[i - 1]; 3152 } 3153 for(int i = len - 2; i >= 0; i--) { 3154 tmp *= a[i + 1]; 3155 b[i] *= tmp; 3156 } 3157 return b; 3158 } 3159 } 3160 3161 /** 3162 * 剑指 Offer 67. 把字符串转换成整数 3163 */ 3164 class Solution67 { 3165 public int strToInt(String str) { 3166 int res = 0, bndry = Integer.MAX_VALUE / 10; 3167 int i = 0, sign = 1, length = str.length(); 3168 if(length == 0) return 0; 3169 while(str.charAt(i) == ' ') 3170 if(++i == length) return 0; 3171 if(str.charAt(i) == '-') sign = -1; 3172 if(str.charAt(i) == '-' || str.charAt(i) == '+') i++; 3173 for(int j = i; j < length; j++) { 3174 if(str.charAt(j) < '0' || str.charAt(j) > '9') break; 3175 if(res > bndry || res == bndry && str.charAt(j) > '7') 3176 return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; 3177 res = res * 10 + (str.charAt(j) - '0'); 3178 } 3179 return sign * res; 3180 } 3181 } 3182 3183 3184 /** 3185 * 剑指 Offer 68 - I. 二叉搜索树的最近公共祖先 3186 */ 3187 class Solution68 { 3188 public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { 3189 while(root != null) { 3190 if(root.val < p.val && root.val < q.val) // p,q 都在 root 的右子树中 3191 root = root.right; // 遍历至右子节点 3192 else if(root.val > p.val && root.val > q.val) // p,q 都在 root 的左子树中 3193 root = root.left; // 遍历至左子节点 3194 else break; 3195 } 3196 return root; 3197 } 3198 } 3199 3200 /** 3201 * d 3202 */ 3203 class Solution682 { 3204 public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { 3205 if(root == null || root == p || root == q) return root; 3206 TreeNode left = lowestCommonAncestor(root.left, p, q); 3207 TreeNode right = lowestCommonAncestor(root.right, p, q); 3208 if(left == null) return right; 3209 if(right == null) return left; 3210 return root; 3211 } 3212 } 3213 3214 3215 public static void main(String[] arg){ 3216 sysout(reverseWords("I am a student. ")); 3217 //sysout(lengthOfLongestSubstring("abcabcbb") ); 3218 //reversePairs(new int[]{7,5,6,4}); 3219 //sysout(lengthOfLongestSubstring("dvdf") ); 3220 /*int[][] matrix = {{1, 4, 7, 11, 15} 3221 , {2, 5, 8, 12, 19} 3222 , {3, 6, 9, 16, 22} 3223 , {10, 13, 14, 17, 24} 3224 , {18, 21, 23, 26, 30}};*/ 3225 /*int[][] matrix = {{3,8,6,0,5,9,9,6,3,4,0,5,7,3,9,3}, 3226 {0,9,2,5,5,4,9,1,4,6,9,5,6,7,3,2}, 3227 {8,2,2,3,3,3,1,6,9,1,1,6,6,2,1,9}, 3228 {1,3,6,9,9,5,0,3,4,9,1,0,9,6,2,7}, 3229 {8,6,2,2,1,3,0,0,7,2,7,5,4,8,4,8}, 3230 {4,1,9,5,8,9,9,2,0,2,5,1,8,7,0,9}, 3231 {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}}; 3232 */ //sysout( Solution47_2.maxValue(matrix)); 3233 // sysout( Solution47.maxValue(matrix)); 3234 /* sysout(translateNum46_动态规划版1(1234120193)); 3235 sysout(translateNum46(1234120193));*/ 3236 // countDigitOne(120); 3237 3238 /* MedianFinder obj = new MedianFinder(); 3239 obj.addNum(1); 3240 obj.addNum(2); 3241 sysout(obj.findMedian()); 3242 obj.addNum(3); 3243 sysout(obj.findMedian());*/ 3244 3245 /* System.out.println( Arrays.toString ((new Solution38_1()).permutation("fba"))); 3246 System.out.println( Arrays.toString ((new Solution38_2()).permutation("fba"))); 3247 System.out.println( Arrays.toString ((new Solution38_3()).permutation("fba")));*/ 3248 // System.out.println( majorityElement( new int[]{1})); 3249 3250 /* PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() { 3251 @Override 3252 public int compare(Integer num1, Integer num2) { 3253 return num2 - num1; 3254 } 3255 }); 3256 queue.add(1); 3257 queue.add(13); 3258 queue.add(2); 3259 sysout(queue.poll()); sysout(queue.poll()); sysout(queue.poll()); 3260 queue.offer(1); 3261 queue.offer(13); 3262 queue.offer(2); 3263 sysout(queue.poll());sysout( queue.poll());sysout( queue.poll());*/ 3264 3265 /*ListNode node = new ListNode(1); 3266 node.next = new ListNode(2); 3267 node.next.next = new ListNode(3);*/ 3268 3269 /* TreeNode oo= deserialize("1,2,None,None,3,4,None,None,5,None,None,"); 3270 System.out.println(serialize(deserialize("1,2,None,None,3,4,None,None,5,None,None,"))); 3271 System.out.println(oo);*/ 3272 3273 /* int[] nums={1,2,3}; 3274 (new Permutations()).permute(nums);*/ 3275 3276 // reverseList2(node); 3277 // System.out.println(printNumbers2(3)); 3278 // myPow(2,3); 3279 //cuttingRope2(20); 3280 //System.out.println(Arrays.toString(exchange(new int[]{1,2,3,4,5,6}))); 3281 //cuttingRope2(20); 3282 3283 //System.out.println(movingCount( 20, 30, 20)); 3284 //System.out.println(movingCount1( 15, 15, 3)); 3285 3286 /*char[][] board = new char[][]{"ABCE".toCharArray(), "DECS".toCharArray(), "AEEE".toCharArray()}; 3287 System.out.println(exist( board, "ABCCED")); 3288 System.out.println(exist1( board, "ABCCED"));*/ 3289 3290 // System.out.println(minArray(new int[]{3,4,5,1,2})); 3291 3292 /*System.out.println(numWays1(51)); 3293 System.out.println(numWays2(51));*/ 3294 //System.out.println(fib(6)); 3295 3296 /* CQueue obj = new CQueue(); 3297 obj.appendTail(1); 3298 int param_2 = obj.deleteHead();*/ 3299 3300 /* int[][] matrix = {{1, 4, 7, 11, 15} 3301 , {2, 5, 8, 12, 19} 3302 , {3, 6, 9, 16, 22} 3303 , {10, 13, 14, 17, 24} 3304 , {18, 21, 23, 26, 30}}; 3305 int target = 16; 3306 System.out.println(findNumberIn2DArray(matrix, target));*/ 3307 3308 /* int nums[] = {2, 3, 1, 0, 2, 5, 3}; 3309 System.out.println(findRepeatNumber(nums)); */ 3310 3311 //add测试 3312 //结论:add测试一样 3313 /* LinkedList<Integer> linkedList = new LinkedList<>(); 3314 Stack<Integer> stack = new Stack<>(); 3315 3316 linkedList.add(1); 3317 linkedList.add(2); 3318 linkedList.add(3); 3319 3320 stack.add(1); 3321 stack.add(2); 3322 stack.add(3); 3323 System.out.println(linkedList); 3324 System.out.println(stack); 3325 3326 //push测试 3327 //结论:测试不一样,linkedlist的push是addFirst;stack是在数组尾部addElement 3328 linkedList.push(4); 3329 linkedList.push(5); 3330 linkedList.push(6); 3331 3332 stack.push(4); 3333 stack.push(5); 3334 stack.push(6); 3335 stack.pop(); 3336 stack.pop(); 3337 linkedList.pop(); 3338 linkedList.pop(); 3339 System.out.println(linkedList); 3340 System.out.println(stack);*/ 3341 } 3342 3343 public static void sysout(Object dd){ 3344 System.out.println(dd); 3345 } 3346 3347 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)