当前位置: 代码迷 >> 综合 >> 算法 | 一周刷完《剑指Offer》 Day4:第38~49题
  详细解决方案

算法 | 一周刷完《剑指Offer》 Day4:第38~49题

热度:52   发布时间:2023-10-19 21:23:44.0

写在前面

  • 本系列包含《剑指Offer》66道算法题,预计一周刷完,这是第四篇。
    系列汇总:剑指Offer 66题 Java 刷题笔记汇总
  • 所有题目均可在牛客网在线编程平台进行调试。
    网址:https://www.nowcoder.com/ta/coding-interviews
  • 本系列包含题目,解题思路代码(Java)
    代码同步发布在GitHub:https://github.com/JohnnyJYWu/offer-Java

上一篇:算法 | 一周刷完《剑指Offer》 Day3:第27~37题
下一篇:算法 | 一周刷完《剑指Offer》 Day5:第50~60题


Day4:第38~49题

有几道题比较考逻辑和理解。到后面就简单了,好像更注重思维了。

  • T38. 二叉树的深度
  • T39. 平衡二叉树
  • T40. 数组中只出现一次的数字
  • T41. 和为S的连续正数序列
  • T42. 和为S的两个数字
  • T43. 左旋转字符串
  • T44. 翻转单词顺序列
  • T45. 二叉树的深度
  • T46. 孩子们的游戏(圆圈中最后剩下的数)
  • T47. 求1+2+3+…+n
  • T48. 不用加减乘除做加法
  • T49. 把字符串转换成整数

T38. 二叉树的深度

题目描述

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

解题思路

分别对左右子树递归计算深度,取深度更大的一个。

	public int TreeDepth(TreeNode root) {
    if(root == null) return 0;return 1//当前结点深度1+ Math.max(TreeDepth(root.left), TreeDepth(root.right));//分别对左右子树递归计算深度,取深度更大的一个}

T39. 平衡二叉树

题目描述

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

解题思路

平衡二叉树:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

此题即将上一题计算左右子树深度【取更大】的思想转化为了计算左右子树深度【判断差值是否不超过1】。

	public boolean IsBalanced_Solution(TreeNode root) {
    if(root == null) return true;return getDepth(root) != -1;}private int getDepth(TreeNode root) {
    if(root == null) return 0;int left = getDepth(root.left);if(left == -1) return -1;int right = getDepth(root.right);if(right == -1) return -1;return Math.abs(left - right) > 1 ? -1 : 1 + Math.max(left, right);} }

T40. 数组中只出现一次的数字

题目描述

一个整型数组里除了两个数字之外,其他的数字都出现了偶数次。请写程序找出这两个只出现一次的数字。

解题思路

重点:注意题意,两个数只出现一次,其他数都出现偶数次

方法一:HashSet。不包含则加入,包含则移除。最终出现偶数次的数一定都会被移除,仅留只出现了一次的数。

	//num1,num2分别为长度为1的数组。传出参数//将num1[0],num2[0]设置为返回结果public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2) {
    //HashSetSet<Integer> set = new HashSet<>();for(int num: array) {
    if(set.contains(num)) {
    set.remove(num);} else {
    set.add(num);}}Iterator<Integer> it = set.iterator();num1[0] = it.next();num2[0] = it.next();}

方法二:异或运算。此方法比较巧妙,需仔细理解。

首先理解二进制位运算的几个概念:
与运算( & ):运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1;
或运算( | ):0|0=0; 0|1=1; 1|0=1; 1|1=1;
异或运算( ^ ):运算规则:0^0=0; 0^1=1; 1^0=1; 1^1=0;

异或运算有此特性:n^n=0;即任意数与本身异或得0;

所以对array数组所有数进行异或运算,得到的结果为两个仅出现一次的数异或运算的结果different(其他数由于出现偶数次,异或运算后都为0了),这个different是区分这两个数不同的标志

然后,或运算有此特性:n&-n取得n的二进制表示中最右边的1。(-n的表示百度补码)

对different进行与运算:different = different & (-different)。此时的different就成为了那个标志

最后再对array数组所有数进行一次异或运算,不同的是这一次要根据different将两个数区分开,填入结果。

	//num1,num2分别为长度为1的数组。传出参数//将num1[0],num2[0]设置为返回结果public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2) {
    //位运算int different = 0;for(int num: array) {
    //最终得到两个只出现一次的数相异的的结果different ^= num;}//得到最右边的1different &= - different;for(int num: array) {
    //由于different是那两个数像异的结果,那么这个取得的最右边的1,可将两个只出现一次的数区分开//然后再进行一遍异运算即可if((num & different) == 0) {
    num1[0] ^= num;} else {
    num2[0] ^= num;}}}

方法三:暴力解,不多赘述了。

	//num1,num2分别为长度为1的数组。传出参数//将num1[0],num2[0]设置为返回结果public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2) {
    //暴力解法ArrayList<Integer> list = new ArrayList<Integer>();Arrays.sort(array);int length = array.length;for(int i = 0; i < length; i ++){
    if(i == length - 1 && array[i] != array[i - 1]) {
    list.add(array[i]);}else if(i == 0 && array[i] != array[i + 1]) {
    list.add(array[i]);}else{
    if(i != 0 && i != length - 1 && array[i] != array[i - 1] && array[i] != array[i + 1]) {
    list.add(array[i]);}}}num1[0] = list.get(0);num2[0] = list.get(1);}

T41. 和为S的连续正数序列

题目描述

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

输出描述:输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

解题思路

夹逼思想,定义正数序列的左边界small和右边界big,求small到big的和。和比所求小则big后移,比所求大则small后移。

	public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
    //算法,夹逼ArrayList<ArrayList<Integer>> result = new ArrayList<>(); if(sum <= 1) return result;int small = 1;int big = 2;while(small < (sum + 1) / 2) {
    //要求最少是两个数字,所以small最大为(s+1)/2int curSum = 0;//求small到big的和for(int i = small; i <= big; i ++) {
    curSum += i;}//夹逼思想,小则big后移,大则small后移if(curSum < sum) {
    big ++;} else if(curSum > sum) {
    small ++;} else {
    ArrayList<Integer> list = new ArrayList<>();for(int i = small; i <= big; i ++) {
    list.add(i);}result.add(list);small ++;}}return result;}

公式求解。

	public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
    //公式ArrayList<ArrayList<Integer>> result = new ArrayList<>(); if(sum < 3) return result;for(int i = 1; i <= sum / 2; i ++) {
    int value = 1 + 4 * i * i - 4 * i + 8 * sum;int valueSqrt = (int) Math.sqrt(value);if(value >= 25 && valueSqrt * valueSqrt == value) {
    ArrayList<Integer> list = new ArrayList<Integer>();for(int j = i; j <= (valueSqrt - 1) >> 1; j ++) {
    list.add(j);}result.add(list);}}return result;}

T42. 和为S的两个数字

题目描述

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

输出描述:对应每个测试案例,输出两个数,小的先输出。

解题思路

夹逼思想,且和相等时,差越大乘积越小。因此从两端向中间夹逼时第一个和为S的即为所求。

	public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum) {
    //夹逼思想,且和相等时,差越大乘积越小ArrayList<Integer> result = new ArrayList<>();int small = 0;//数组下标,从前往后移int big = array.length - 1;//数组下标,从后往前移while(small <= big) {
    int curSum = array[small] + array[big];if(curSum < sum) {
    small ++;} else if(curSum > sum) {
    big --;} else {
    result.add(array[small]);result.add(array[big]);return result;}}return result;}

T43. 左旋转字符串

题目描述

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

解题思路

分三步走。(交换步骤顺序也可)

step1:先将左边3个字符串进行翻转:[abc]XYZdef --> [cba]XYZdef

step2:再将右边剩余字符串进行翻转:cba[XYZdef] --> cba[fedZYX]

step3:最后将整个字符串进行翻转: cbafedZYX --> XYZdefabc

	public String LeftRotateString(String str, int n) {
    if(str == null || str.length() == 0) return "";char[] chars = str.toCharArray();//step1:先将左边3个字符串进行翻转:[abc]XYZdef --> [cba]XYZdefreverse(chars, 0, n - 1);//step2:再将右边剩余字符串进行翻转:cba[XYZdef] --> cba[fedZYX]reverse(chars, n, chars.length - 1);//step3:最后将整个字符串进行翻转: cbafedZYX --> XYZdefabcreverse(chars, 0, chars.length - 1);return new String(chars);}private void reverse(char[] chars, int start, int end) {
    while(start < end) {
    char tmp = chars[start]; chars[start] = chars[end]; chars[end] = tmp;start ++; end --;}}

T44. 翻转单词顺序列

题目描述

牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

解题思路

原理同上一题。先翻转每个单词的顺序,再翻转整个句子的顺序。(交换步骤顺序也可)

	public String ReverseSentence(String str) {
    //原理同T43if(str == null || str.length() == 0) return str;char[] chars = str.toCharArray();int length = chars.length;int startIndex = 0;//单词开始标记int endIndex = 0;//单词结束标记//翻转每个单词的字母顺序while(endIndex <= length) {
    if(endIndex == length || chars[endIndex] == ' ') {
    //遇到空格或到句末,翻转单词reversed(chars, startIndex, endIndex - 1);startIndex = endIndex + 1;}endIndex ++;		}//翻转整个句子的顺序reversed(chars, 0, length - 1);return new String(chars);}private void reversed(char[] chars, int start, int end) {
    while(start < end) {
    char tmp = chars[start]; chars[start] = chars[end]; chars[end] = tmp;start ++; end --;}}

T45. 二叉树的深度

题目描述

LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。

解题思路

先对数组排序,计算大小王(癞子)数量。然后计算剩下的数两两的差值减1即为需要用癞子替代的张数。题那么长都是废话,别想太多正常找就行。

	public boolean isContinuous(int[] numbers) {
    if(numbers == null || numbers.length < 5) return false;Arrays.sort(numbers);int sum0 = 0;//大小王数量for(int i = 0; i < numbers.length; i ++) {
    if(numbers[i] == 0) {
    sum0 ++;}}for(int i = sum0; i < numbers.length - 1; i ++) {
    if(numbers[i + 1] == numbers[i]) return false;//有相等的牌不可能为顺子int interval = numbers[i + 1] - numbers[i] - 1;//这两个数之间差了几张牌,需要用大小王代替if(interval > sum0) return false;//相差太大,大小王不够sum0 -= interval;}return true;}

T46. 孩子们的游戏(圆圈中最后剩下的数)

题目描述

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

解题思路

又是一堆废话,丢手绢游戏。。。
约瑟夫环,公式:
n = 1: f(n, m) = 0
n > 1: f(n, m) = [f(n - 1, m) + m] % n

	public int LastRemaining_Solution(int n, int m) {
    //约瑟夫环//公式:f(n, m) = 0 (n = 1) //f(n, m) = [f(n - 1, m) + m] % n (n > 1)if(n == 0) return -1;if(n == 1) return 0;return (LastRemaining_Solution(n - 1, m) + m) % n;}

T47. 求1+2+3+…+n

题目描述

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

解题思路

递归相加。此题关键在于如何跳出递归,基本方向是采用逻辑与或的方式来计算,与的时候通过n>0来短路,这样在n=0的时候不需要计算递归的值,或的时候通过n==0来短路,在n=0的时候可以短路逻辑或运算。

	public int Sum_Solution(int n) {
    int sum = n;//boolean b = (n > 0) && (sum += Sum_Solution(n - 1)) > 0;boolean b = (n == 0) || (sum += Sum_Solution(n - 1)) > 0;return sum;}

T48. 不用加减乘除做加法

题目描述

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

解题思路

位运算。a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位。

	public int Add(int num1, int num2) {
    while(num2 != 0) {
    int tmp = num1 ^ num2;num2 = (num1 & num2) << 1;num1 = tmp;}return num1;}

T49. 把字符串转换成整数

题目描述

将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。

输入描述:
输入一个字符串,包括数字字母符号,可以为空
输出描述:
如果是合法的数值表达则返回该数字,否则返回0

解题思路

正常转换即可。

注意:正负号的存在,字符型代表的整形在转换过程中的计算。

	public int StrToInt(String str) {
    if(str.length() == 0) return 0;char[] chars = str.toCharArray();boolean isNegative = chars[0] == '-';//判断是否有负号int result = 0;for(int i = 0; i < chars.length; i ++) {
    if(i == 0 && (chars[i] == '+' || chars[i] == '-')) continue;//跳过正负号if(chars[i] < '0' || chars[i] > '9') return 0;result = result * 10 + (chars[i] - '0');}return isNegative ? -result : result;//三元}

项目地址:https://github.com/JohnnyJYWu/offer-Java

上一篇:算法 | 一周刷完《剑指Offer》 Day3:第27~37题
下一篇:算法 | 一周刷完《剑指Offer》 Day5:第50~60题

希望这篇文章对你有帮助~

  相关解决方案