所有题按照leetcode
JZ3 数组中重复数字 数组中重复的数字
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
1 2 3 输入: [2, 3, 1, 0, 2, 5, 3] 输出:2 或 3
1.遍历数组
1 2 3 4 5 6 7 8 9 Set<Integer> set = new HashSet<>(); int repeat = -1 ;for (int num : nums) { if (!set.add(num)){ repeat = num; break ; } } return repeat;
2.先排序在查找
1 2 3 4 5 6 7 8 public int findRepeatNumber (int [] nums) { Arrays.sort(nums); for (int i = 1 ; i < nums.length; i++) { if (nums[i] == nums[i - 1 ]) return nums[i]; } return -1 ; }
3.空间换时间
1 2 3 4 5 6 7 8 public int findRepeatNumber (int [] nums) { int [] arr = new int [nums.length]; for (int i = 0 ; i < nums.length; i++){ arr[nums[i]]++; if (arr[nums[i]] > 1 ) return nums[i]; } return -1 ; }
JZ4 二维数组的查找 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解题思路 该二维数组中的一个数,小于它的数一定在其左边,大于它的数一定在其下边。因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来缩小查找区间,当前元素的查找区间为左下角的所有元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class JZ1 { public boolean Find (int target, int [][] array) { if (array == null || array.length == 0 || array[0 ].length == 0 ) return false ; int row = array.length; int col = array[0 ].length ; int r = 0 , c = col-1 ; while (r <= row-1 && c >= 0 ){ if (target == array[r][c]){ return true ; }else if ( target < array[r][c]){ c--; }else { r++; } } return false ; } }
JZ5 替换空格 请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
1 2 3 4 5 public class JZ2 { public String replaceSpace (StringBuffer str) { return str.toString().replace(" " , "%20" ); } }
JZ6 输入一个链表,按链表从尾到头的顺序返回一个ArrayList 输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
1.递归
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public int [] reversePrint(ListNode head) { ArrayList<Integer> ret = new ArrayList<>(); if (head != null ){ ret = printListFromTailToHead(head.next); } int res[] = new int [ret.size()]; for (int i = 0 ;i<ret.size();i++){ res[i] = ret.get(i); } return res; } public ArrayList<Integer> printListFromTailToHead (ListNode listNode) { ArrayList<Integer> ret = new ArrayList<>(); if (listNode != null ){ ret.addAll(printListFromTailToHead(listNode.next)); ret.add(listNode.val); } return ret; }
2.栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public int [] reversePrint(ListNode head) { Stack<ListNode> stack = new Stack<>(); ListNode temp = head; while (temp != null ) { stack.push(temp); temp = temp.next; } int size = stack.size(); int [] res = new int [size]; for (int i = 0 ; i < size; i++) { res[i] = stack.pop().val; } return res; }
3.不用栈也不用递归
先遍历获取长度,得到数组,在从最后开始赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static int [] reversePrint3(ListNode head) { ListNode node = head; int count = 0 ; while (node != null ) { ++count; node = node.next; } int [] nums = new int [count]; node = head; for (int i = count - 1 ; i >= 0 ; --i) { nums[i] = node.val; node = node.next; } return nums; }
JZ7 重建二叉树 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
递归
前序遍历的第一个值为根节点的值,使用这个值将中序遍历结果分成两部分,左部分为树的左子树中序遍历结果,右部分为树的右子树中序遍历的结果。然后分别对左右子树递归地求解。
前序遍历的首个元素即为根节点 root 的值;
在中序遍历中搜索根节点 root 的索引 ,可将中序遍历划分为 [ 左子树 | 根节点 | 右子树 ] 。
根据中序遍历中的左(右)子树的节点数量,可将前序遍历划分为 [ 根节点 | 左子树 | 右子树 ] 。
左子树: 根节点索引为 pre_root + 1 ,中序遍历的左右边界分别为 in_left 和 i - 1。 右子树: 根节点索引为pre_root+i-in_left+1(即:根节点索引 + 左子树长度 + 1),中序遍历的左右边界分别为 i + 1 和 in_right。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 HashMap<Integer,Integer> dic = new HashMap<>(); int []po;public TreeNode buildTree (int [] pre, int [] in) { if (pre == null || pre.length == 0 ) return null ; po=pre; for (int i = 0 ;i<in.length;i++){ dic.put(in[i],i); } return recur(0 ,0 ,in.length-1 ); } public TreeNode recur (int pre_root,int in_left ,int in_right) { if (in_left>in_right) return null ; TreeNode root = new TreeNode(po[pre_root]); int i = dic.get(po[pre_root]); root.left = recur(pre_root+1 ,in_left,i-1 ); root.right = recur(pre_root+i-in_left+1 ,i+1 ,in_right); return root; } public static void main (String[] args) { int pre[] = {3 ,9 ,20 ,15 ,7 }; int in[] = {9 ,3 ,15 ,20 ,7 }; new JZ7().buildTree(pre, in); }
栈
前序遍历挨着的两个值比如m和n,他们会有下面两种情况之一的关系。
1,n是m左子树节点的值 。
2,n是m右子树节点的值或者是m某个祖先节点的右节点的值。
对于第一个知识点我们很容易理解,如果m的左子树不为空,那么n就是m左子树节点的值。
对于第二个问题,如果一个结点没有左子树只有右子树,那么n就是m右子树节点的值,如果一个结点既没有左子树也没有右子树,那么n就是m某个祖先节点的右节点,我们只要找到这个祖先节点就好办了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public TreeNode buildTree2 (int [] pre, int [] in) { if (pre == null || pre.length == 0 ) return null ; Stack<TreeNode> s = new Stack<>(); TreeNode root = new TreeNode(pre[0 ]); TreeNode cur = root; for (int i = 1 ,j = 0 ;i<pre.length;i++){ if (cur.val != in[j]){ cur.left = new TreeNode(pre[i]); s.push(cur); cur = cur.left; }else { j++; while (!s.isEmpty() &&s.peek().val == in[j]){ cur = s.pop(); j++; } cur = cur.right = new TreeNode(pre[i]); } } return root; }
JZ9 两个栈来实现一个队列 用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class JZ5 { Stack<Integer> stack1 = new Stack<Integer>(); Stack<Integer> stack2 = new Stack<Integer>(); public void push (int node) { stack1.push(node); } public void inToOut () { if (stack2.isEmpty()){ while (!stack1.isEmpty()){ stack2.push(stack1.pop()); } } } public int pop () throws Exception { inToOut(); if (stack2.isEmpty()){ throw new Exception("queue is empty" ); } return stack2.pop(); } }
JZ10-1 斐波那契数列 斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
非递归
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class ZJ10 { public int fib (int n) { int constant = 1000000007 ; List<Integer> list = new ArrayList<>(); list.add(0 ); list.add(1 ); for (int i = 2 ;i<=n;i++){ int res = list.get(i-1 )+list.get(i-2 ); if (res >= constant) res = res - constant; list.add(res); } return list.get(n); } }
JZ10-2 青蛙跳台阶问题 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
青蛙可从低一阶的台阶上,也可以从低2阶的台阶上去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class ZJ10_2 { public int numWays (int n) { if (n <= 1 ) return 1 ; int [] dp = new int [n + 1 ]; dp[1 ] = 1 ; dp[2 ] = 2 ; for (int i = 3 ; i <= n; i++) { if (dp[i]>1000000007 ){ dp[i] -= 1000000007 ; } dp[i] = dp[i - 1 ] + dp[i - 2 ]; } return dp[n]; } }
JZ11 旋转数组的最小值 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
遍历数组
1 2 3 4 5 6 int min = numbers[0 ];for (int i = 1 ; i < numbers.length; i++) { if (min > numbers[i]) min = numbers[i]; } return min;
二分法查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public int minArray2 (int [] numbers) { int left = 0 ,right = numbers.length-1 ; while (left < right){ int mid = left + (right - left) / 2 ; if (numbers[mid] > numbers[right]){ left = mid +1 ; }else if (numbers[mid]<numbers[left]){ right = mid; }else { right--; } } return numbers[right]; }
JZ12 矩阵中的路径 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[[“a”,”b”,”c”,”e”], [“s”,”f”,”c”,”s”], [“a”,”d”,”e”,”e”]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
回溯算法
遍历数组,有4个方向的走法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public 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 ; } public boolean dfs (char [][] board,char [] words,int i,int j,int index) { if (i > board.length-1 || i<0 || j > board[0 ].length-1 || j<0 || board[i][j] != words[index]){ return false ; } if (index == words.length-1 ){ return true ; } char tmp = board[i][j]; board[i][j] = '/' ; boolean res = dfs(board, words, i + 1 , j, index + 1 ) || dfs(board, words, i - 1 , j, index + 1 ) || dfs(board, words, i, j + 1 , index + 1 ) || dfs(board, words, i , j - 1 , index + 1 ); board[i][j]= tmp; return res; }
也可以使用一个另外的数组来记录是否已经被访问。
JZ13 机器人的运动范围 地上有一个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。请问该机器人能够到达多少个格子?
使用深度优先搜索(Depth First Search,DFS)方法进行求解。
结论: 根据可达解的结构,易推出机器人可 仅通过向右和向下移动,访问所有可达解 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public int movingCount (int m, int n, int k) { boolean [][] visited = new boolean [m][n]; return dfs(0 , 0 , m, n, k, visited); } private int dfs (int i, int j, int m, int n, int k, boolean visited[][]) { if (i < 0 || i >= m || j < 0 || j >= n || (i/10 + i%10 + j/10 + j%10 ) > k || visited[i][j]) { return 0 ; } visited[i][j] = true ; return dfs(i + 1 , j, m, n, k, visited)+dfs(i, j + 1 , m, n, k, visited)+1 ; }
JZ14-1 剪绳子 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1] …*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
1 2 3 4 5 6 7 输入: 2 输出: 1 解释: 2 = 1 + 1, 1 × 1 = 1 输入: 10 输出: 36 解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
动态规划
建立一维动态数组 dp:
边界条件:dp[1] = dp[2] = 1,表示长度为 2 的绳子最大乘积为 1; 状态转移方程:dp[i] = max(dp[i], max((i - j) * j, j * dp[i - j])),可以这样理解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public int cuttingRope (int n) { if (n <= 3 ) return n - 1 ; int [] dp = new int [n + 1 ]; dp[2 ] = 2 ; dp[3 ] = 3 ; for (int i = 4 ;i <= n;i++){ for (int j= 2 ;j<=i/2 ;j++){ dp[i] = Math.max(dp[i],dp[i-j]*dp[j]); } } return dp[n]; }
JZ14-2 剪绳子2 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m - 1] 。请问 k[0]k[1] …*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
贪心算法
认为一份为3是最大的
1 2 3 4 5 6 7 8 9 10 11 12 13 public int cuttingRope (int n) { if (n <= 3 ) return n - 1 ; long res = 1 ; int p=(int )1e9 +7 ; while (n>4 ){ res = res*3 %p; n -= 3 ; } return (int ) (res*n%p); }
JZ15 二进制中1的个数 请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
与运算
根据 与运算 定义,设二进制数字 n ,则有: 若 n & 1 = 0,则 n 二进制 最右一位 为 00 ; 若 n & 1 = 1 ,则 n 二进制 最右一位 为 11 。
1 2 3 4 5 6 7 8 9 10 public class JZ15 { public int hammingWeight (int n) { int res = 0 ; while (n != 0 ){ res += n&1 ; n >>>= 1 ; } return res; } }
JZ16 数值的整次方 实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
我的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public double myPow (double x, int n) { if (x == 1 ){ return 1 ; } double res = 1 ; if (n>1 ){ for (int i = 0 ; i < n; i++){ res *= x; } }else if (n < 0 ){ double temp = 1 / x; for (int i = 0 ;i< Math.abs(n) ;i++){ res *= temp; } }else if (n == 0 ){ res = 1 ; }else { res = x; } return res; }
2.00000 -2147483648 结果为0
因为int的取值范围是-2147483648到2147483647…所以java的Math.abs(n)在这个时候返回的还是-2147483648
递归求解
使用long类型不会溢出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public double myPow (double x, int n) { long N = n; if (N < 0 ) { return 1 / myPow(x, -N); } return myPow(x, N); } private double myPow (double x, long n) { if (n == 0 ) { return 1 ; } if (x == 1 ) { return 1 ; } if ((n % 2 ) == 0 ) { double square = myPow(x, n / 2 ); return square * square; } else { double square = myPow(x, (n - 1 ) / 2 ); return square * square * x; } }
非递归
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public double myPow (double x, int n) { long N = n; if (N < 0 ) { x = 1 / x; N *= -1 ; } double res = 1 ; while (N > 0 ) { if ((N % 2 ) == 1 ) { res *= x; } x *= x; N /= 2 ; } return res; } 。
JZ17 从 11 至最大的 n位数的列表 题目要求打印 “从 11 至最大的 n位数的列表” ,因此需考虑以下两个问题
1 2 3 4 5 6 7 8 public 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; }
实际上
由于存在大数问题,结果不能放入int数组中,故这里采用剑指offer原题的直接打印输出的模式。 increment函数,若发生进位则一直进行for循环,直到不产生进位则break。如果i为0(即到了最高位)还发生了进位,则设置isOverflow为true,并返回至主函数的while判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public void printNumbers (int n) { StringBuilder str = new StringBuilder(); for (int i = 0 ; i < n; i++) { str.append('0' ); } while (!increment(str)){ int index = 0 ; while (index < str.length() && str.charAt(index) == '0' ){ index++; } System.out.println(str.toString().substring(index)); } } public boolean increment (StringBuilder str) { boolean isOverflow = false ; for (int i = str.length() - 1 ; i >= 0 ; i--) { char s = (char )(str.charAt(i) + 1 ); if (s > '9' ) { str.replace(i, i + 1 , "0" ); if (i == 0 ) { isOverflow = true ; } } else { str.replace(i, i + 1 , String.valueOf(s)); break ; } } return isOverflow; }
JZ18 删除链表的节点 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
1 2 3 4 5 6 7 8 9 示例 1: 输入: head = [4,5,1,9], val = 5 输出: [4,1,9] 解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9. 示例 2: 输入: head = [4,5,1,9], val = 1 输出: [4,5,9] 解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public ListNode deleteNode (ListNode head, int val) { ListNode p = head; if (p.val == val){ return p.next; } ListNode q = p; p = p.next; while ( p!=null ){ if (p.val == val){ q.next = p.next; break ; } q=p; p = p.next; } return head; }
本题中,输入的类型为:head: ListNode, val: int,即 val 的类型是整形; 在《剑指offer》中,默认输入为 head: ListNode, val: ListNode,即 val 的类型是链表。
假如 head 为 4 -> 5 -> 1 -> 9,val 为 5 -> 1 -> 9,表示我们要删除结点 5,这时我们使用信息交换,把 val 改为 1 -> 9的信息就行了。
为什么呢?因为我们把待删除节点的后一个元素赋值给待删除节点,也就相当于删除了当前元素。
但是聪明的你一定会举出反例:如果 val 的值是最后一个元素 9 呢?我们无法找到 9 的后一个元素(因为没有),只能重头开始找待删除元素 9,这样的话时间复杂度再次变成了 O(N)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static ListNode deleteNode (ListNode head, ListNode val) { if (head == null || val == null ){ return null ; } if (val.next != null ){ ListNode next = val.next; val.val = next.val; val.next = next.next; } else if (head == val){ head = null ; } else { ListNode cur = head; while (cur.next != val){ cur = cur.next; } cur.next = null ; } return head; }
JZ19 正则表达式匹配 请实现一个函数用来匹配包含’. ‘和’ * ‘的正则表达式。模式中的字符’.’表示任意一个字符,而’ * ‘表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串”aaa”与模式”a.a”和”abac a”匹配,但与”aa.a”和”ab*a”均不匹配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 示例 1: 输入: s = "aa" p = "a" 输出: false 解释: "a" 无法匹配 "aa" 整个字符串。 示例 2: 输入: s = "aa" p = "a*" 输出: true 解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。 示例 3: 输入: s = "ab" p = ".*" 输出: true 解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。 示例 4: 输入: s = "aab" p = "c*a*b" 输出: true 解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。 示例 5: 输入: s = "mississippi" p = "mis*is*p*." 输出: false
JZ21 调整数组顺序使奇数位于偶数前面 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
1 2 3 输入:nums = [1,2,3,4] 输出:[1,3,2,4] 注:[3,1,2,4] 也是正确的答案之一。
我的思路 类似于快速排序
从两边开始查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public int [] exchange(int [] nums) { int l = 0 ,r = nums.length-1 ,temp; while (l<r){ while (l<r && (nums[l]&1 ) == 1 ){ l++; } while (l<r && (nums[r]&1 ) == 0 ){ r--; } if (l<r){ temp = nums[l]; nums[l] = nums[r]; nums[r] = temp; } } return nums; }
JZ22 链表中倒数第k个节点 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 public ListNode getKthFromEnd (ListNode head, int k) { ListNode pre = head , tail = head; int i = 0 ; while (tail.next!=null ){ tail = tail.next; i++; } while (i >= k){ pre = pre .next; i--; } return pre; }
快慢指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public ListNode getKthFromEnd2 (ListNode head, int k) { ListNode pre = head, tail = head; while (tail != null && k > 0 ) { tail = tail.next; k--; } while (tail != null ) { pre = pre.next; tail = tail.next; } return pre; }
JZ24 反转链表 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
头插法
1 2 3 4 5 6 7 8 9 10 11 public ListNode reverseList (ListNode head) { ListNode newhead = new ListNode(0 ); while (head != null ){ ListNode node = head.next; head.next = newhead.next; newhead.next = head; head = node ; } return newhead.next; }
递归
1 2 3 4 5 6 7 8 9 10 public ListNode reverseList2 (ListNode head) { if (head == null || head.next == null ){ return head; } ListNode node = head.next; ListNode newhead = reverseList2(node); node.next = head; head.next = null ; return newhead; }
JZ25 合并两个有序节点 输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
1 2 输入:1 ->2 ->4 , 1 ->3 ->4 输出:1 ->1 ->2 ->3 ->4 ->4
迭代
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public ListNode mergeTwoLists (ListNode l1, ListNode l2) { ListNode head = new ListNode(0 ); ListNode node = head; while (l1!=null && l2 != null ){ if (l1.val<l2.val){ node.next = l1; l1 = l1.next; }else { node.next = l2; l2 = l2.next; } node = node.next; } if (l1!=null ){ node.next = l1; } if (l2!=null ){ node.next = l2; } return head.next; }
递归
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public ListNode mergeTwoLists (ListNode l1, ListNode l2) { if (l1 == null ){ return l2; } if (l2 == null ){ return l1; } if (l1.val<l2.val){ l1.next = mergeTwoLists(l1.next,l2); return l1; }else { l2.next = mergeTwoLists(l1,l2.next); return l2; } }
JZ26 树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:
1 2 3 4 5 6 7 8 9 10 给定的树 A: 3 / \ 4 5 / \ 1 2 给定的树 B: 4 / 1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
解题思路:
若树 B 是树 A 的子结构,则子结构的根节点可能为树 AA的任意一个节点。因此,判断树 B 是否是树 A 的子结构,需完成以下两步工作:
先序遍历树 A 中的每个节点 nA ;(对应函数 isSubStructure(A, B))
判断树 A 中 以 nA 为根节点的子树 是否包含树 B 。(对应函数 recur(A, B))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public boolean isSubStructure (TreeNode A, TreeNode B) { if (B == null || A == null ){ return false ; } if (A.val == B.val && recur(A,B)){ return true ; } return isSubStructure(A.left,B) || isSubStructure(A.right,B); } private 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); }
JZ27 二叉树镜像 请完成一个函数,输入一个二叉树,该函数输出它的镜像。
使用递归的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class JZ27 { public TreeNode mirrorTree (TreeNode root) { if (root == null ){ return null ; } TreeNode temp = root.left; root.left = mirrorTree(root.right); root.right = mirrorTree(temp); return root; } }
辅助栈
特例处理 : 当 root 为空时,直接返回 null ;
初始化 : 栈(或队列),本文用栈,并加入根节点 root。
循环交换 : 当栈 stack 为空时跳出;
出栈: 记为 node ;
添加子节点: 将 node 左和右子节点入栈;
交换: 交换 node 的左 / 右子节点。
返回值 : 返回根节点 root 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public TreeNode mirrorTree2 (TreeNode root) { if (root == null ){ return null ; } Stack<TreeNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()){ TreeNode node = stack.pop(); if (node.left != null ) stack.add(node.left); if (node.right != null ) stack.add(node.right); TreeNode tmp = node.left; node.left = node.right; node.right = tmp; } return root; }
JZ28 对称的二叉树 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
解题思路: 对称二叉树定义: 对于树中 任意两个对称节点 L 和 R ,一定有:
L.val = R.val :即此两对称节点值相等。
L.left.val = R.right.val:即 L 的 左子节点 和 R的 右子节点 对称;
L.right.val = R.left.val:即 L 的 右子节点 和 R的 左子节点 对称。
isSymmetric(root) :
recur(L, R) :
终止条件:
当 L 和 R 同时越过叶节点: 此树从顶至底的节点都对称,因此返回 true;
当 L 或 R 中只有一个越过叶节点: 此树不对称,因此返回 false;
当节点 L值 ≠节点 R 值: 此树不对称,因此返回 false ;
递推工作:
判断两节点 L.left 和 R.right是否对称,即 recur(L.left, R.right) ;
判断两节点 L.right和 R.left是否对称,即 recur(L.right, R.left) ;
返回值: 两对节点都对称时,才是对称树,因此用与逻辑符 && 连接。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public boolean isSymmetric (TreeNode root) { if (root == null ) return true ; return recur(root.left,root.right); } private boolean recur (TreeNode left, TreeNode right) { if (left == null && right == null ){ return true ; } if (left == null || right == null || left.val!= right.val){ return false ; } return recur(left.left,right.right) && recur(left.right,right.left); }
JZ29 顺时针打印矩阵 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
1 2 3 4 5 6 7 8 示例 1: 输入:matrix = [[1,2,3],[4,5,6],[7,8,9]] 输出:[1,2,3,6,9,8,7,4,5] 示例 2: 输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] 输出:[1,2,3,4,8,12,11,10,9,5,6,7]
解题思路: 根据题目示例 matrix = [[1,2,3],[4,5,6],[7,8,9]] 的对应输出 [1,2,3,6,9,8,7,4,5] 可以发现,顺时针打印矩阵的顺序是 “从左向右、从上向下、从右向左、从下向上” 循环。
算法流程:
空值处理: 当 matrix 为空时,直接返回空列表 [] 即可。
初始化: 矩阵 左、右、上、下 四个边界 l , r , t , b ,用于打印的结果列表 res 。
循环打印: “从左向右、从上向下、从右向左、从下向上” 四个方向循环,每个方向打印中做以下三件事 (各方向的具体信息见下表) ;
根据边界打印,即将元素按顺序添加至列表 res 尾部;
边界向内收缩 1 (代表已被打印);
判断是否打印完毕(边界是否相遇),若打印完毕则跳出。
返回值: 返回 res 即可。
打印方向
1. 根据边界打印
2. 边界向内收缩
3. 是否打印完毕
从左向右
左边界l ,右边界 r
上边界 t 加 1
是否 t > b
从上向下
上边界 t ,下边界b
右边界 r 减 1
是否 l > r
从右向左
右边界 r ,左边界l
下边界 b 减 1
是否 t > b
从下向上
下边界 b ,上边界t 1
左边界 l 加 1
是否 l > r
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class JZ29 { public 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]; } if (--r < l) break ; for (int i = r; i >= l; i--){ res[x++] = matrix[b][i]; } if (--b < t) break ; for (int i = b; i >=t ; i--){ res[x++] = matrix[i][l]; } if (++l > r) break ; } return res; } }
JZ30 包含min函数的栈 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
1 2 3 4 5 6 7 8 9 10 示例: MinStack minStack = new MinStack(); minStack.push(-2); minStack.push(0); minStack.push(-3); minStack.min(); --> 返回 -3. minStack.pop(); minStack.top(); --> 返回 0. minStack.min(); --> 返回 -2.
使用2个栈,一个存放数据,一个存放最小值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class JZ30 { Stack<Integer> stack , min; public JZ30 () { stack = new Stack<Integer>(); min = new Stack<Integer>(); } public void push (int x) { stack.push(x); if (min.empty() || x <= min()){ min.push(x); } } public void pop () { if (stack.pop() == min()){ min.pop(); } } public int top () { return stack.peek(); } public int min () { return min.peek(); } }
JZ31 栈的弹出压入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class JZ31 { public boolean validateStackSequences (int [] pushed, int [] popped) { Stack<Integer> stack = new Stack<>(); int i = 0 ; for (int num:pushed){ stack.push(num); while (!stack.isEmpty() && stack.peek() == popped[i]){ stack.pop(); i++; } } return stack.isEmpty(); } }
JZ32 从上到下打印二叉树 1. 从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public int [] levelOrder(TreeNode root) { if (root == null ) return new int [0 ]; List<Integer> list = new ArrayList<>(); LinkedList<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()){ TreeNode node = queue.poll(); list.add(node.val); if (node.left != null ) queue.add(node.left); if (node.right != null ) queue.add(node.right); } int [] res = new int [list.size()]; for (int i = 0 ; i < list.size(); i++){ res[i] = list.get(i); } return res; }
2. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public List<List<Integer>> levelOrder2(TreeNode root) { if (root == null ) return new ArrayList<>(); List<List<Integer>> res = new ArrayList<List<Integer>>(); LinkedList<TreeNode> queue = new LinkedList<>(); 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; }
3. 请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public List<List<Integer>> levelOrder3(TreeNode root) { if (root == null ) return new ArrayList<>(); List<List<Integer>> res = new ArrayList<List<Integer>>(); LinkedList<TreeNode> queue = new LinkedList<>(); boolean flag = false ; 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; }
33 二叉搜索树的后序遍历的 后序遍历定义: [ 左子树 | 右子树 | 根节点 ] ,即遍历顺序为 “左、右、根” 。 二叉搜索树定义: 左子树中所有节点的值 <<根节点的值;右子树中所有节点的值 > 根节点的值;其左、右子树也分别为二叉搜索树。
递归分治
根据二叉搜索树的定义,可以通过递归,判断所有子树的 正确性 (即其后序遍历是否满足二叉搜索树的定义) ,若所有子树都正确,则此序列为二叉搜索树的后序遍历。
终止条件 : 当i≥j ,说明此子树节点数量≤1 ,无需判别正确性,因此直接返回 true ;
递推工作 :
划分左右子树: 遍历后序遍历的 [i, j] 区间元素,寻找 第一个大于根节点 的节点 ,索引记为 m 。此时,可划分出左子树区间 [i,m-1]、右子树区间 [m, j - 1]、根节点索引 j 。
判断是否为二叉搜索树: 左子树区间 [i, m - 1]内的所有节点都应 < postorder[j]。而第 1.划分左右子树 步骤已经保证左子树区间的正确性,因此只需要判断右子树区间即可。 右子树区间 [m, j-1]内的所有节点都应 > postorder[j]。实现方式为遍历,当遇到 ≤postorder[j] 的节点则跳出;则可通过 p = j判断是否为二叉搜索树。
返回值 : 所有子树都需正确才可判定正确,因此使用 与逻辑符 &&&& 连接。 p = j: 判断 此树 是否正确。 recur(i, m - 1): 判断 此树的左子树 是否正确。 recur(m, j - 1 ): 判断 此树的右子树 是否正确。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public boolean verifyPostorder (int [] postorder) { return recur(postorder,0 ,postorder.length-1 ); } private boolean recur (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 && recur(postorder,i,m-1 ) && recur(postorder,m,j-1 ); }
JZ35 二叉树中值为某一值的路径 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 LinkedList<List<Integer>> res = new LinkedList<>(); LinkedList<Integer> tmp = new LinkedList<>(); public List<List<Integer>> pathSum(TreeNode root, int sum) { recur(root,sum); return res; } void recur (TreeNode node, int sum) { if (node == null ) return ; tmp.add(node.val); sum = sum - node.val; if (sum == 0 && node.left == null && node.right == null ){ res.add(new LinkedList<>(tmp)); } recur(node.left,sum); recur(node.right,sum); tmp.removeLast(); }
JZ35 复杂链表的复制 利用hashmap来存储数据key是旧的值,value是新的值,通过旧值来构造一个新的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public Node copyRandomList (Node head) { Node node = head; HashMap<Node,Node> map = new HashMap(); while (node != null ){ map.put(node,new Node(node.val)); node = node.next; } node = head; while (node != null ){ map.get(node).next = map.get(node.next); map.get(node).random = map.get(node.random); node = node.next; } return map.get(head); }
JZ36 二叉搜索树与双向链表 输入一颗二叉搜索树将它转化成一个双向链表。
排序链表:利用”中序遍历 “从小到大访问树的节点
双向链表:在构建相邻节点(前驱pre,当前节点cur),pre.right = cur也应cur.left = pre.
循环链表:head.left = tail 和tail.right = head
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class JZ36 { Node pre,head; public Node treeToDoublyList (Node root) { if (root == null ) return null ; dfs(root); head.left = pre; pre.right = head; return head; } public void dfs (Node cur) { if (cur == null ) return ; dfs(cur.left); if (pre != null ) pre.right = cur; else head = cur; cur.left = pre; pre = cur; dfs(cur.right); } }
JZ37 序列化和反序列化 实现序列化和反序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public String serialize (TreeNode root) { if (root == null ) return "[]" ; StringBuilder res = new StringBuilder("[" ); Queue<TreeNode> queue = new LinkedList<TreeNode>() {{ add(root); }}; while (!queue.isEmpty()) { TreeNode node = queue.poll(); if (node != null ) { res.append(node.val + "," ); queue.add(node.left); queue.add(node.right); } else res.append("null," ); } res.deleteCharAt(res.length() - 1 ); res.append("]" ); return res.toString(); } public TreeNode deserialize (String data) { if (data.equals("[]" )) return null ; String[] vals = data.substring(1 , data.length() - 1 ).split("," ); TreeNode root = new TreeNode(Integer.parseInt(vals[0 ])); Queue<TreeNode> queue = new LinkedList<TreeNode>() {{ add(root); }}; int i = 1 ; while (!queue.isEmpty()) { TreeNode node = queue.poll(); if (!vals[i].equals("null" )) { node.left = new TreeNode(Integer.parseInt(vals[i])); queue.add(node.left); } i++; if (!vals[i].equals("null" )) { node.right = new TreeNode(Integer.parseInt(vals[i])); queue.add(node.right); } i++; } return root; }