目录

  • 剑指offer
    • 一、数组
    • 1. 数组中重复的数字
    • 2.二维数组中的查找
      • 多个if和if,elseif语句的区别
    • 3.旋转数组的最小数字
    • 4.构建乘积数组
    • 5.把数组排成最小的数
    • 6.矩阵中的路径
    • 7.调整数组顺序使奇数位于偶数前面
    • 8.顺时针打印矩阵
    • 9.栈的压入、弹出序列
    • 10.数组中出现次数超过一半的数字
    • 11.最小的k个数
    • 13. 在排序数组中查找数字 I
    • 14. 0~n-1中缺失的数字
    • 15.数组中数字出现的次数
    • 16. 和为s的两个数字
    • 17.扑克牌中的顺子
    • 18.数组中的逆序对
    • 动态规划
    • 1.连续子数组的最大和
  • 链表
    • 反转链表
  • 二叉树
    • 二叉树的镜像
    • 二叉搜索树的第k大节点
    • 二叉树的最近公共祖先

剑指offer

一、数组

1. 数组中重复的数字


我的简单思路:先排序,再输出答案,但是效率不高。

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

下面是最好的思路:原地交换。数组元素的 索引 和 值 是 一对多 的关系。遍历数组并通过交换操作,使元素的 索引 与 值 一一对应(即 nums[i] = inums[i]=i )。因而,就能通过索引映射对应的值,起到与字典等价的作用。

时间复杂度 O(N) : 遍历数组使用 O(N)O(N) ,每轮遍历的判断和交换操作使用 O(1)O(1) 。
空间复杂度 O(1) : 使用常数复杂度的额外空间。

代码:遍历数组
1.所遍历的数字=所在索引值,就遍历下一个;
2.所遍历的数字≠所在索引值,就和它该在的索引的数字进行交换;
3.所遍历的数字=它该在的索引的数,则得出结果。

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

2.二维数组中的查找


思路:从右上角开始。对于每个元素,其左分支元素更小、右分支元素更大。

复杂度分析:

时间复杂度 O(M+N)O(M+N) :其中,NN 和 MM 分别为矩阵行数和列数,此算法最多循环 M+NM+N 次。

空间复杂度 O(1)O(1) : i, j 指针使用常数大小额外空间。

多个if和if,elseif语句的区别

①if无论是否满足条件都会向下执行,知道程序结束,else if 满足一个条件就会停止执行。
②由于if都会执行一遍,则可能会同一个需要判断的事件,会进入2个if语句中,出现错误,而else if就不会发生这样的事情。

class Solution {public boolean findNumberIn2DArray(int[][] matrix, int target) {int i = matrix.length - 1, j = 0;while(i >= 0 && j < matrix[0].length){if(matrix[i][j] == target){return true;}if(matrix[i][j] > target) i--;else if(matrix[i][j] < target) j++;//换成if就报错}return false;}
}

3.旋转数组的最小数字


思路:普通的太简单了,使用二分查找。注意性质(下图一目了然)。

循环二分: 设 m = (i + j) / 2m=(i+j)/2 为每次二分的中点( “/” 代表向下取整除法,因此恒有 i≤m<j ),可分为以下三种情况:
①当 nums[m] > nums[j]时: m一定在 左半,最小值一定在 [m+1,j] 闭区间内,因此执行 i = m + 1;
②当 nums[m] < nums[j] 时: m 一定在 右半,最小值 一定在[i, m]闭区间内,因此执行 j = m;
③当 nums[m] = nums[j] 时: 无法判断 m 在哪个排序数组中,即无法判断旋转点 x 在 [i, m还是 [m + 1, j]区间中。解决方案: 执行 j = j - 1缩小判断范围

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

补充思考: 为什么本题二分法不用 nums[m]和 nums[i]作比较?

二分目的是判断 m 在哪个排序数组中,从而缩小区间。而在 nums[m] > nums[i]情况下,无法判断 m 在哪个排序数组中。本质上是由于 j 初始值肯定在右排序数组中; i 初始值无法确定在哪个排序数组中。举例如下:

对于以下两示例,当 i = 0, j = 4, m = 2时,有 nums[m] > nums[i] ,而结果不同。
[1, 2, 3, 4 ,5]旋转点 x = 0 : m 在右排序数组(此示例只有右排序数组);
[3, 4, 5, 1 ,2] 旋转点 x = 3 : m 在左排序数组。

4.构建乘积数组

思路:把图画出来就知道了。

class Solution {public int[] constructArr(int[] a) {int length=a.length;if(length==0){return a;}int[] b=new int[length];int temp=1;b[0]=1;int i=0;for(i=1;i<length;i++){b[i]=b[i-1]*a[i-1];看图易知B2=B1*A1}for(i=length-2;i>=0;i--){temp=temp*a[i+1];b[i]=b[i]*temp;}return b;}
}

5.把数组排成最小的数


Arrays.sort(Object[] oj1,new SortComparator()):这种方式能够对引用类型数组,按照Comparator中声明的compare方法对对象数组进行排序.
lamda表达式

compareTo()方法
增强for循环

相对于for(;;)而言 增强for循环有两个好处:
1.写起来简单
2.遍历集合、容器简单

//没弄明白
class Solution {public String minNumber(int[] nums) {String[] strs = new String[nums.length];for(int i = 0; i < nums.length; i++)strs[i] = String.valueOf(nums[i]);//String.valueOf()把基本类型转换成string类型Arrays.sort(strs, (x, y) -> (x + y).compareTo(y + x));//自定义排序需要深入研究一下,还有lamda表达式StringBuilder res = new StringBuilder();//StringBuilder非线程安全但是效率高for(String s : strs)//增强for循环res.append(s);return res.toString();//转换成string类型}
}

6.矩阵中的路径

class Solution {public boolean exist(char[][] board, String word) {char[] words = word.toCharArray();//String转换成char数组// 遍历图for(int i = 0; i < board.length; i++) {for(int j = 0; j < board[0].length; j++) {// 如果找到了,就返回true。否则继续找if(dfs(board, words, i, j, 0)) {return true;}}}// 遍历结束没找到falsereturn false;}boolean dfs(char[][] board, char[] word, int i, int j, int k) {// 判断传入参数的可行性: i 与图行数row比较,j与图列数col比较,i,j初始都是0,都在图左上角// k是传入字符串当前索引,一开始是0,如果当前字符串索引和图当前索引对应的值不相等,表示第一个数就不相等// 所以继续找第一个相等的数。题目说第一个数位置不固定,即路径起点不固定(不一定是左上角为第一个数)// 如果board[i][j] == word[k],则表明当前找到了对应的数,就继续执行(标记找过,继续dfs 上下右左)
//board[i][j] != word[k]要放里面否则会有越界报错if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]){return false;} // 表示每个字符都找到了// 一开始k=0,而word.length肯定不是0,所以没找到,就执行dfs继续找。if(k == word.length - 1) return true;   // 访问过的暂时标记空字符串:不标记会导致搜回去,//“ ”是空格 '\0'是空字符串,不一样的!board[i][j] = '\0';// 顺序是 上下  右 左(这四个顺序顺便);上面找到了对应索引的值所以k+1boolean res = dfs(board, word, i-1, j, k + 1) || dfs(board, word, i +1, j, k + 1) || dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);// 还原找过的元素,因为之后可能还要访问它的其他路径board[i][j] = word[k];// 返回结果,如果false,则if(dfs(board, words, i, j, 0)) return true;不会执行,就会继续找return res;}
}

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


考虑定义双指针 i , j 分列数组左右两端,循环执行:

指针 i 从左向右寻找偶数;
指针 j 从右向左寻找奇数;
将 偶数nums[i] 和 奇数 nums[j] 交换。
可始终保证: 指针 i 左边都是奇数,指针 j 右边都是偶数 。

class Solution {public int[] exchange(int[] nums) {int i = 0, j = nums.length - 1, tmp;while(i < j) {while(i < j && (nums[i] %2) == 1){i++;} while(i < j && (nums[j] %2 ) == 0) {j--;}tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;}return nums;}
}

8.顺时针打印矩阵


思路:四个边界,注意最后一个元素可能会重复读取

class Solution {public int[] spiralOrder(int[][] matrix) {if(matrix.length == 0) return new int[0];int a = 0, d = matrix[0].length - 1, w = 0, s = matrix.length - 1, x = 0;int i,j,temp=0;int[] sum = new int[(d + 1) * (s + 1)];while(true){for(j=a;j<=d;j++){// a to d.sum[temp++]=matrix[w][j];}w++;if(temp >= sum.length) break;//每个for循环因为有等号,所以到了最后会重复加进去,所以要加这个for(i=w;i<=s;i++){// w to s.sum[temp++]=matrix[i][d];}d--;if(temp >= sum.length) break;for(j=d;j>=a;j--){// d to a.sum[temp++]=matrix[s][j];}s--;if(temp >= sum.length) break;for(i=s;i>=w;i--){// s to w.if(++a > d) break;sum[temp++]=matrix[i][a];}a++;if(temp >= sum.length) break;    }return sum;}
}

9.栈的压入、弹出序列

class Solution {public boolean validateStackSequences(int[] pushed, int[] popped) {Stack<Integer> stack = new Stack<>();int j=0;for(int i=0;i<pushed.length;i++){stack.push(pushed[i]);// 进栈while(!stack.isEmpty()&&stack.peek()==popped[j]){// 栈顶元素和出栈的元素相等就出栈,不相等继续进栈stack.pop();j++;//出栈就往后遍历poped}}return stack.isEmpty();}
}

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

核心就是对拼消耗

假设有一个擂台,有一组人,每个人有编号,相同编号为一组,依次上场,没人时上去的便是擂主(x),若有人,编号相同则继续站着(人数+1),若不同,假设每个人战斗力相同,都同归于尽,则人数-1;那么到最后站着的肯定是人数占绝对优势的那一组啦~

class Solution {public int majorityElement(int[] nums) {int persons=0;int leizhu=0;for(int i=0;i<nums.length;i++){if(persons==0){leizhu=nums[i];}persons=persons+(leizhu==nums[i]?1:-1);}return leizhu;}
}

11.最小的k个数

思路:题目只要求返回最小的 k 个数,对这 k 个数的顺序并没有要求。因此,只需要将数组划分为 最小的 k 个数 和 其他数字 两部分即可,而快速排序的哨兵划分可完成此目标。

根据快速排序原理,如果某次哨兵划分后 基准数正好是第 k+1 小的数字 ,那么此时基准数左边的所有数字便是题目所求的 最小的 k 个数 。

根据此思路,考虑在每次哨兵划分后,判断基准数在数组中的索引是否等于 k ,若 true 则直接返回此时数组的前 k 个数字即可。

《啊哈!算法》 关于快速排序法为什么一定要哨兵j 先出动的原因?
假如i先动,如果后面没有大于基准的数就导致排序错误!!

class Solution {public int[] getLeastNumbers(int[] arr, int k) {if (k == arr.length) return arr;return quickSort(arr, k, 0, arr.length - 1);}public int[] quickSort(int[] arr, int k, int l, int r) {int i = l, j = r;//l:left,r:rigthtwhile (i < j) {while (i < j && arr[j] >= arr[l]) j--;//i<j控制越界,j先动,改成i就报错了while (i < j && arr[i] <= arr[l]) i++;swap(arr, i, j);//快排思路,后面小于基准的和前面大于基准的要交换}swap(arr, i, l);//交换完了一轮基准,把基准放到该放的位置if (i > k) {//i > k时基准在k后面,所以在前一半找return quickSort(arr, k, l, i - 1);}if (i < k){//i < k时基准在k前面,所以在后一半找return quickSort(arr, k, i + 1, r);}return Arrays.copyOf(arr, k);//i==k时就可以返回前k个数}public void swap(int[] arr, int i, int j) {int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}
}

13. 在排序数组中查找数字 I


思路:二分查找找右边界,简单来说就是找到第一个比target大的数的位置。

假如tar-1不存在呢?比如{1,2,3,5,7,7,7,8},tar=7,而tar-1=6并不在数组nums里边。回看第二个问题if(nums[m] <= tar)合并了两个条件, 上边说了nums[m] = tar,这里说说nums[m] < tar。其实tar-1存在与否并不重要,按照开头的数组,设tar=6。
第一次i = 0,j = 7,m=3,nums[m]=5<tar;
第二次 i = 4,j = 7,m = 5,nums[m] = 7 > tar;
第三次i = 4,j = 4,m = 4,nums[m] = 7 >tar;
第四次 i = 4,j = 3,i>j 退出while,renturn 4,tar的右边界为下标4。

为什么tar不存在不影响?因为helper方法是为了求右边界(简单来说就是找到第一个比target大的数的位置),tar不存在时,不考虑nums[m] == tar的判断;nums[m] > tar,j = m-1;nums[m] < tar,i =m+1,最终返回的是tar的右边界(return i 保证是返回右边界,不可用return j)。所以tar的右边界一定存在,即使tar不存在。

class Solution {public int search(int[] nums, int target) {//helper(nums, target):找到target的右边界//helper(nums, target - 1):找到target-1的右边界return helper(nums, target) - helper(nums, target - 1);//target-1不存在也可以找到重复数字的左边界}int helper(int[] nums, int tar) {int i = 0, j = nums.length - 1;while(i <= j) {int m = (i + j) / 2;if(nums[m] <= tar){//找重复数字的右边界i = m + 1;}if(nums[m] > tar){j = m - 1;//中间值大于目标值,就在左半找} }return i;}
}

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


思路:

返回值: 跳出时,变量 i 和 j 分别指向 “右子数组的首位元素” 和 “左子数组的末位元素” 。因此返回 i 即可。

class Solution {public int missingNumber(int[] nums) {int i = 0, j = nums.length - 1;while(i <= j) {int mid = (i + j) / 2;if(nums[mid] == mid){//是正常数就找右半i = mid + 1;}else{j = mid - 1;//不是正常数就找左半} }return i;}
}

15.数组中数字出现的次数


思路:回归异或的本质,1^0 = 1, 1^1 = 0, 0^0 = 1。a^b的结果里,为1的位表明a与b在这一位上不相同。这个不相同很关键,不相同就意味着我们在结果里任选一位位1的位置i,所有数可以按照i位的取值(0,1)分成两组,那么a与b必然不在同一组里。再对两组分别累计异或。那么两个异或结果就是a、b。

一个例子:5,3,2,3,4,5
1.遍历异或:结果为2=0100异或4=0010,n=0110=6
2.使用&找出a与b在这一位上不相同,不相同就可以把a和b划分开。先0110&0001=0,然后0110&0010=1,m=2
3,划分成了[0010=2,0011=3],[0100=4,0101=5],两个子数组异或结果为2,5

class Solution {public int[] singleNumbers(int[] nums) {int x = 0, y = 0, n = 0, m = 1;for(int num : nums)               // 1. 遍历异或,初始化为0,因为0异或a=a;n ^= num;while((n & m) == 0)               // 2. 循环左移,计算出第一个二进制异或结果为1的m值,也就是这两个值一个&m=0,另一个&m≠0,所以可以起到划分作用m <<= 1;for(int num: nums) {              // 3. 遍历 nums 分组if((num & m) != 0) x ^= num;  // 4. 当 num & m != 0else y ^= num;                // 4. 当 num & m == 0}return new int[] {x, y};          // 5. 返回出现一次的数字}
}class Solution {public int[] singleNumbers(int[] nums) {int n=0,m=1,x=0,y=0;for(int i=0;i<nums.length;i++){// 1. 遍历异或,初始化为0,因为0异或a=a;n^=nums[i];}while((n&m)==0){//中间要加括号,因为判断符先计算,循环左移,计算出第一个二进制异或结果为1的m值,也就是这两个值一个&m=0,另一个&m≠0,所以可以起到划分作用m=m<<1;}for(int i=0;i<nums.length;i++){if((nums[i]&m)!=0){x=x^nums[i];}else{y=y^nums[i];}}return new int[]{x,y};}
}

16. 和为s的两个数字

思路:就是简单的双指针

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

17.扑克牌中的顺子


思路:为什么不直接写成==4,因为可能是0,0,3, 4 ,5

class Solution {public boolean isStraight(int[] nums) {int joker = 0;Arrays.sort(nums); // 数组排序for(int i = 0; i < 4; i++) {if(nums[i] == 0) joker++; // 统计大小王数量else if(nums[i] == nums[i + 1]) return false; // 若有重复,提前返回 false}return nums[4] - nums[joker] < 5; // 最大牌 - 最小牌 < 5 则可构成顺子}
}

18.数组中的逆序对


思路:归并排序中分治计算。
双指针,因为子数组已经排好了序,每当遇到 左子数组当前元素 > 右子数组当前元素 时,意味着
「左子数组当前元素 至 左子数组末尾元素」 与 「右子数组当前元素」 构成了几个 「逆序对」 。

class Solution {int count;//全局变量计数器public int reversePairs(int[] nums) {count=0;//默认返回值GB(nums,0,nums.length-1);return count;}public void GB(int[] nums,int left,int right){int mid=(left+right)/2;if(left<right){//没有=,因为一个数字不需要排序GB(nums,left,mid);//处理左半GB(nums,mid+1,right);//处理右半GBpaixu(nums,left,mid,right);//归并排序}}public void GBpaixu(int[] nums,int left,int mid,int right){int[] temparr=new int[right-left+1];//创建临时数组存储排序好的数int index=0;//临时数组指针//定义两个指针int temp1=left;int temp2=mid+1;while(temp1<=mid&&temp2<=right){//两个指针要满足的条件if(nums[temp1]<=nums[temp2]){//不是逆序temparr[index++]=nums[temp1];temp1++;//指针往后走}else{//是逆序count=count+(mid+1-temp1);//因为左边已经排好序,所以左边的后面全满足temparr[index++]=nums[temp2];temp2++;//指针往后走}}//两个子数组可能剩下数字while(temp1<=mid){temparr[index++]=nums[temp1++];}while(temp2<=right){temparr[index++]=nums[temp2++];}//把排好的临时数组赋值到原始数组numsfor(int i=0;i<temparr.length;i++){nums[i+left]=temparr[i];}}}

动态规划

1.连续子数组的最大和


思路:以某个数作为结尾,意思就是这个数一定会加上去,那么要看的就是这个数前面的部分要不要加上去。大于零就加,小于零就舍弃。

1.状态定义: 设动态规划列表 dp ,dp[i] 代表以元素 nums[i]为结尾的连续子数组最大和。

为何定义最大和 dp[i] 中必须包含元素 nums[i]:保证 dp[i]递推到 dp[i+1] 的正确性;如果不包含 nums[i],递推时则不满足题目的 连续子数组 要求。
转移方程: 若 dp[i−1]≤0 ,说明 dp[i−1] 对dp[i] 产生负贡献,即 dp[i-1] + nums[i] 还不如 nums[i] 本身大。

①当 dp[i - 1] > 0 时:执行 dp[i] = dp[i-1] + nums[i] ;
②当 dp[i−1]≤0 时:执行 dp[i]=nums[i] ;
初始状态: dp[0]=nums[0],即以 nums[0] 结尾的连续子数组最大和为 nnums[0] 。

返回值: 返回 dp 列表中的最大值,代表全局最大值。

class Solution {public int maxSubArray(int[] nums) {int max= nums[0];//最大值初始化为第一个数for(int i = 1; i < nums.length; i++) {if(nums[i-1]>0){//如果dp[i-1]>0,则dp[i]=dp[i-1]+nums[i] (代表以元素 nums[i]为结尾的连续子数组最大和。)nums[i]=nums[i]+nums[i-1];}if(nums[i]>max){//更新最大值max=nums[i];}}return max;}
}

链表

反转链表

题目:

答案:

public ListNode reverseList(ListNode head) {if(head==null){//如为空返回空return null;}if(head.next==null){//如只有一个头结点就返回头结点return head;}ListNode last=reverseList(head.next);head.next.next=head;head.next=null;return last;}

解析:
①ListNode last = reverse(head.next);

这个 reverse(head.next) 执行完成后,整个链表就成了这样:

②head.next.next = head;


head.next = null;
return last;

二叉树

二叉树的镜像

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

答案:

递归解析:

  • 终止条件: 当节点 root 为空时(即越过叶节点),则返回 null;
  • 递推工作:
    初始化节点 tmp ,用于暂存 root 的左子节点;
    开启递归 右子节点 mirrorTree(root.right) ,并将返回值作为 root 的 左子节点 。
    开启递归 左子节点 mirrorTree(tmp),并将返回值作为 root 的 右子节点 。
  • 返回值: 返回当前节点 root ;

当结点是叶子结点是就会return它自己。

复杂度分析:
时间复杂度 O(N): 其中 N 为二叉树的节点数量,建立二叉树镜像需要遍历树的所有节点,占用O(N) 时间。
空间复杂度 O(N) : 最差情况下(当二叉树退化为链表),递归时系统需使用 O(N)大小的栈空间。
代码:

class Solution {public TreeNode mirrorTree(TreeNode root) {if(root == null) return null;TreeNode tmp = root.left;//先暂存左右结点都一样root.left = mirrorTree(root.right);root.right = mirrorTree(tmp);return root;}
}

二叉搜索树的第k大节点

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

答案:求 “二叉搜索树第 k 大的节点” 可转化为求 “此树的中序遍历倒序的第 k 个节点”。

关键还是用类变量来维护k和res。类变量(也叫静态变量)是类中独立于方法之外的变量,用static 修饰。

 public static int count=0, res=0;//形参k不能随着dfs的迭代而不断变化,为了记录迭代进程和结果,引入类变量count和res。public int kthLargest(TreeNode root, int k) {count=k;//利用形参值k对类变量count进行初始化dfs(root);//这里不要引入形参k,dfs中直接使用的是初始值为k的类变量countreturn res;            }public void dfs(TreeNode root){if(root==null||count==0) return;//当root为空或者已经找到了res时,直接返回dfs(root.right);if(--count==0){//先--,再判断,比如count=3:2,1,0即代表第三个了res = root.val;return;//这里的return可以避免之后的无效迭代dfs(root.left);}dfs(root.left);  }

错误做法:

 public static int n;public int kthLargest(TreeNode root, int k) {if(root==null){return 0;}kthLargest(root.right,k);n=--k;//这样做n的值一直不变,所以k要作为类变量if(n==0){res=root.val;}kthLargest(root.left,k);return res;}

二叉树的最近公共祖先


答案:
根据以上定义,若 root 是p,q 的 最近公共祖先 ,则只可能为以下情况之一:

  • p 和 q 在 root 的子树中,且分列 root 的 异侧(即分别在左、右子树中);
  • p=root ,且q 在 root 的左或右子树中;
  • q=root ,且 p 在 root 的左或右子树中;
class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {if(root == null) return null; // 如果树为空,直接返回nullif(root == p || root == q) return root; // 如果 p和q中有等于 root的,那么它们的最近公共祖先即为root(一个节点也可以是它自己的祖先)TreeNode left = lowestCommonAncestor(root.left, p, q); // 递归遍历左子树,只要在左子树中找到了p或q,则先找到谁就返回谁TreeNode right = lowestCommonAncestor(root.right, p, q); // 递归遍历右子树,只要在右子树中找到了p或q,则先找到谁就返回谁if(left == null) return right; // 如果在左子树中 p和 q都找不到,则 p和 q一定都在右子树中,右子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)else if(right == null) return left; // 否则,如果 left不为空,在左子树中有找到节点(p或q),这时候要再判断一下右子树中的情况,如果在右子树中,p和q都找不到,则 p和q一定都在左子树中,左子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)else return root; //否则,当 left和 right均不为空时,说明 p、q节点分别在 root异侧, 最近公共祖先即为 root}
}

剑指offer和LeetCode题目笔记相关推荐

  1. LeetCode刷题——剑指offer深度优先搜索题目汇总

    剑指offer深度优先搜索题目汇总 剑指 Offer 12. 矩阵中的路径 剑指 Offer 34. 二叉树中和为某一值的路径 剑指 Offer 36. 二叉搜索树与双向链表 剑指 Offer 54. ...

  2. 最新算法校招编程-剑指offer、Leetcode常考题目及解法分享

    本资源整理了BAT.TMD等互联网算法岗校招面试过程中常考的LeetCode和剑指offer编程题:此外,还整理了部分百度.腾讯.阿里.今日头条相关的面试经验,对于正准备校招面试的同学非常值得参考复习 ...

  3. 《剑指offer》刷题笔记(发散思维能力):求1+2+3+...+n

    <剑指offer>刷题笔记(发散思维能力):求1+2+3+-+n 转载请注明作者和出处:http://blog.csdn.net/u011475210 代码地址:https://githu ...

  4. python连续质数计算_GitHub - xxqfft/AlgorithmsByPython: 算法/数据结构/Python/剑指offer/机器学习/leetcode...

    尝试用Python实现一些简单的算法和数据结构 之前的算法和数据结构基本都是用Swift写的,现在尝试用Python实现一些简单的算法和数据结构. update 20160704 准备加入<剑指 ...

  5. 剑指offer所有的题目总结(转)

    目录 基本都是参考别的博客和书本的代码,仅作为自己笔记用!! 零.小结: 一.位运算 1.二进制中1的个数 2.判断二进制中0的个数 3.二进制高位连续0的个数 二.二叉树 1.二叉搜索树第k个结点 ...

  6. 【剑指Offer】个人学习笔记_41_数据流中的中位数

    目录 题目: [剑指 Offer 41. 数据流中的中位数](https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lc ...

  7. 【剑指Offer】个人学习笔记_15_二进制中1的个数

    目录 题目: [剑指 Offer 15. 二进制中1的个数](https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/) 题 ...

  8. 【剑指Offer】个人学习笔记_61_扑克牌中的顺子

    目录 题目: [剑指 Offer 61. 扑克牌中的顺子](https://leetcode-cn.com/problems/bu-ke-pai-zhong-de-shun-zi-lcof/) 题目分 ...

  9. 剑指offer (From Leetcode) 汇总

    剑指 Offer 03. 数组中重复的数字 找出数组中重复的数字. 在一个长度为 n 的数组 nums 里的所有数字都在 0-n-1 的范围内.数组中某些数字是重复的,但不知道有几个数字重复了,也不知 ...

最新文章

  1. 二代三代转录组测序分析实战班
  2. android view自定义
  3. 如何在柱状图中点连线_练瑜伽,如何放松僵硬紧张的髂腰肌?
  4. window.opener 的用法
  5. Universal-Image-Loader(UIL)图片载入框架使用简介
  6. 2.1 js 基础--select深入
  7. note同步不及时 one_朱海舟回应锤子便签同步不及时:工程师已经解决
  8. 有关输出图形的代码,我觉得好难啊,好蒙啊。
  9. [高光谱] 在开源项目Hyperspectral-Classification Pytorch中加入自己的网络
  10. 【语音处理】基于matlab GUI语音信号处理与滤波【含Matlab源码 1663期】
  11. Android调用手机摄像头
  12. C++——fcntl
  13. react 中加载静态word文档(或加载静态的html文件)
  14. Day08——字符串匹配、小结
  15. 深度学习方法实现红外图片中人物动作识别
  16. JavaScript Dom编程艺术(第二版)读书笔记 第三章DOM
  17. 错误模块名称: KERNELBASE.dll 问题记录
  18. Activity标签设置
  19. 几道特别难搞的数据库面试题
  20. 光纤收发器常见故障问题的时候我们该如何解决呢

热门文章

  1. 多种数据形式下智能问答的应用解读
  2. GPT v.s. 中国象棋:写过文章解过题,要不再来下盘棋?
  3. Seq2Seq之双向解码机制 | 附开源实现
  4. 阿里巴巴创新研究计划AIR2018正式发布 邀全球学者共创未来
  5. 【归并排序】-求逆序数算法
  6. Numpy-浅拷贝和深拷贝
  7. 【Linux部署】Greenplum数据库6.13.0单机版 [CRITICAL]:-Error occurred: non-zero rc: 1(报错详情+问题处理:内存释放)
  8. HttpClient:绕开https证书(三)
  9. Windows11——Modern Standby(现代待机) S0改Suspend to RAM(待机到内存)S3睡眠解决方案
  10. 牛客题霸 NC11 将升序数组转化为平衡二叉搜索树