剑指offer(31-40题)题解

举报
bigsai 发表于 2021/04/02 02:39:42 2021/04/02
【摘要】 如果有问题,可以加我交流哈!欢迎点个在看收藏 推荐相关文章: [排序]归并排序和逆序数详解 剑指offer(01-15题)优化题解 剑指offer(16-30题) 精解 31 整数中1出现的次数 题目描述 求出1~ 13的整数中1出现的次数,并算出100~ 1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、...

如果有问题,可以加我交流哈!欢迎点个在看收藏

推荐相关文章: [排序]归并排序和逆序数详解
剑指offer(01-15题)优化题解
剑指offer(16-30题) 精解

31 整数中1出现的次数

题目描述

求出1~ 13的整数中1出现的次数,并算出100~ 1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

思路
最笨的方法(可过)。使用字符串,将从1道n的字符串拼凑成新的字符串,然后遍历查找1就可以了。至于数学方法的话当初想了一会感觉考虑点挺多,后面还会再想想。

实现代码:


   
  1. public class Solution {
  2.         public  int NumberOf1Between1AndN_Solution(int n) {
  3.           StringBuilder str=new StringBuilder();
  4.          for(int i=1;i<=n;i++)
  5.          {
  6.              str.append(i);
  7.          }
  8.          int va=0;
  9.          for(int i=0;i<str.length();i++)
  10.          {
  11.              if(str.charAt(i)=='1')va++;
  12.          }
  13.          return va;
  14.         }
  15. }

参考讨论区:
用数学方法效率更高,不过这个规律还是得经验,,,我还是有点菜当时想了一段时间总是bug。。

在这里插入图片描述

32 把数组排成最小的数

题目描述

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

思路
贪心算法,主要是一种贪心思想。其实也是个变形排序,其实你不用考虑具体的贪心思想是什么,你要保证整个序列排成的数字最小。那么你就要保证任意两个数字排序的相对数字最小哦。,本质是需要我们考虑最小前缀和组合的问题,但是对于任意两个数,在比较接口中你直接拼凑比较就可以了。但是如果有更好方法后面会学习。

实现代码:


   
  1. import java.util.ArrayList;
  2. import java.util.Arrays;
  3. import java.util.Comparator;
  4. public class Solution {
  5.     public String PrintMinNumber(int [] numbers) {
  6.         Integer[] nums=new Integer[numbers.length];//赋值
  7.         for(int i=0;i<numbers.length;i++)
  8.         {
  9.             nums[i]=numbers[i];
  10.         }
  11.          Arrays.sort(nums, cmp);
  12.          StringBuilder str=new StringBuilder();
  13.          for(int i=0;i<nums.length;i++)
  14.          {
  15.              str.append(nums[i]);
  16.          }
  17.          return str.toString();
  18.         }
  19.      static Comparator<Integer>cmp=new Comparator<Integer>() {
  20.         public int compare(Integer o1, Integer o2) {
  21.             //51 3551
  22.             //921 3
  23.             //51 515
  24.             String a1=o1+"";
  25.             String a2=o2+"";
  26.             return Integer.parseInt(a1+a2)-Integer.parseInt(a2+a1);
  27.         }
  28.     };
  29. }

33 丑数★

题目描述

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

思路
这题自己的方法只过了87%的样例。当数据大的时候就凉了。这题想了一段时间最终忍不住看了题解发现真的太鸡儿秒了!

先说说我自己的想法,怎么想的,他首先没说数据范围是啥,所以我一直以为这个会是一个慢慢试探的过程。

  1. 刚开始尝试的肯定是暴力。他说第N个,那我我就一个个试。每个数除以2、3、5直到无法除看看找到第n个。这种最暴力也最好想。我尝试了但是失败了。

  2. 我知道任何数都能实现质因数分解。也就是这个丑数要满足(2^a*3^b*5^c)。我抱着侥幸尝试用三层循环(剪枝优化一下次数),将(2^a*3^b*5^c)的所有可能性全部添加到集合中然后排个序取出。但是结果依然爆炸。

  3. 根据第二种的进行优化。我是这样想的,这个序列我们是不是可以用一个优先队列来维护它?比如将1放进去,然后抛出的元素放入到hashset中去重。每次抛出的数值m将m * 2,m * 3, m * 5放入优先队列,然后再抛最小的出来加入set。直到set中元素满了为止。但是面对巨大的数字依然失败了。因为占据大空间,思想没错,但是巨大重复!!

整个过程数组这个结构都被我忽略了!!!!实在hold不出来,忍不住去参考了下讨论区发现这个方法真的是太好了。其实思想还是跟我上面的很相似,只是结构用了最优结构——数组!

简单来说就是用了三个光标,a2,a3,a5.每次比较三个位置分别*2,*3,*5谁最小,然后对应光标往右移动一位。!到结束即可!

看个图就理解了:



实现代码为:


   
  1. import java.util.*;
  2. public class Solution {
  3. public static int GetUglyNumber_Solution(int index) {
  4.         int a2=0,a3=0,a5=0;
  5.         if(index<7)return index;//小于7都是
  6.         List<Integer>list=new ArrayList<Integer>();
  7.         list.add(1);
  8.         for(int i=1;i<index;i++)
  9.         {
  10.             int min=Math.min(list.get(a2)*2, Math.min(list.get(a3)*3, list.get(a5)*5));
  11.             list.add(min);
  12.             if(min==list.get(a2)*2)a2++;
  13.             if(min==list.get(a3)*3)a3++;
  14.             if(min==list.get(a5)*5)a5++;
  15.         }
  16.         return list.get(index-1);
  17.     }   
  18. }

34 第一个只出现一次的字符

题目描述

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写)

思路
用hashmap储存记录每个字母出现的次数。然后再进行遍历查找第一个出现一次的字母返回位置。

实现代码:


   
  1. import java.util.HashMap;
  2. public class Solution {
  3.   public  int FirstNotRepeatingChar(String str) {
  4.         HashMap<String, Integer>map=new HashMap<String, Integer>();
  5.         String team="";
  6.         for(int i=0;i<str.length();i++)
  7.         {
  8.             team=str.charAt(i)+"";      
  9.             if(map.containsKey(team))
  10.             {
  11.                 map.put(team, 2);
  12.             }
  13.             else {
  14.                 map.put(team, 1);
  15.             }
  16.         }
  17.         int index=-1;
  18.         for(int i=0;i<str.length();i++)
  19.         {
  20.             team=str.charAt(i)+"";
  21.             if(map.get(team)==1)
  22.             {
  23.                 index=i;
  24.                 break;
  25.             }
  26.         }
  27.         return index;
  28.     }
  29. }

35 数组中的逆序数

题目描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。即输出P%1000000007
输入描述:
题目保证输入的数组中没有的相同的数字

数据范围


   
  1. 对于%50的数据,size<=10^4
  2. 对于%75的数据,size<=10^5
  3. 对于%100的数据,size<=2*10^5

示例1

输入
1,2,3,4,5,6,7,0
输出
7

思路
至于逆序数,一般有三种方法求解,首当其中的就是暴力的O(n^2^)的方法,但是这种方法复杂度过高一看这个数据范围肯定是过不了的。18年夏天遇到过逆序数记录下来,但是写的不够好,从写了一下!

今天刚写的归并排序和逆序数各位一定好好看看,进行了详细分析。

然后可以采用树状数组或者归并排序求解逆序数。当然,笔者这里采用的是归并排序。

实现代码为:


   
  1. public class Solution {
  2.     static int value=0;
  3.   public static int InversePairs(int [] array) {        
  4.         mergesort(array,0,array.length-1);
  5.         return value;
  6.     }   
  7.     private static void mergesort(int[] array, int l, int r) {
  8.         int mid=(l+r)/2;
  9.         if(l<r)
  10.         {
  11.             mergesort(array, l, mid);
  12.             mergesort(array, mid+1, r);
  13.             merge(array, l,mid, r);
  14.         }
  15.     }
  16.     private static void merge(int[] array, int l, int mid, int r) {
  17.         int lindex=l;int rindex=mid+1;
  18.         int team[]=new int[r-l+1];
  19.         int teamindex=0;
  20.         while (lindex<=mid&&rindex<=r) {
  21.             if(array[lindex]<=array[rindex])
  22.             {
  23.                 team[teamindex++]=array[lindex++];
  24.             }
  25.             else {              
  26.                 team[teamindex++]=array[rindex++];
  27.                 value+=mid-lindex+1;
  28.                 value%=1000000007;
  29.             }
  30.         }
  31.         while(lindex<=mid)
  32.           {
  33.               team[teamindex++]=array[lindex++];
  34.           }
  35.         while(rindex<=r)
  36.           {
  37.               team[teamindex++]=array[rindex++];
  38.           } 
  39.         for(int i=0;i<teamindex;i++)
  40.         {
  41.             array[l+i]=team[i];
  42.         }   
  43.     }
  44. }

36 两个链表的第一个公共节点

题目描述

输入两个链表,找出它们的第一个公共结点

思路
这题的吧不要用暴力匹配的O(n^2^)的方法。大家直到链表是一条直的对吧?然后一个链表节点相同也就是后面都相同是吧? 所以如果有相同节点的话那么一定从那个节点到最后都是相同的。并且肯定在短的后面才可能匹配。

至于实现方式其实还是蛮多的,比如你可以先遍历一次分别把两个长度求出来然后把长的节点往后移到相同长度,然后一个个比较就好。当然这样可能跑2次但是影响确实不大。你也可以借助外部空间然后将遍历一次的存下来。然后从后往前找到第一个不一样的那个后面就是了。 当然笔者采用上面一个方法并没有采用外部空间。



实现代码为:


   
  1. /*
  2. public class ListNode {
  3.     int val;
  4.     ListNode next = null;
  5.     ListNode(int val) {
  6.         this.val = val;
  7.     }
  8. }*/
  9. public class Solution {
  10.    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
  11.               int lp1=getlen(pHead1);   
  12.               int lp2=getlen(pHead2);
  13.               while (lp1>lp2) {
  14.                 pHead1=pHead1.next;lp1--;
  15.             }
  16.               while (lp1<lp2) {
  17.                     pHead2=pHead2.next;lp2--;
  18.                 }
  19.               while (pHead1!=pHead2) {
  20.                 pHead1=pHead1.next;
  21.                 pHead2=pHead2.next;
  22.             }
  23.               return pHead1;
  24.         }
  25.     private int getlen(ListNode pHead1) {
  26.         int len=0;
  27.         ListNode team=pHead1;
  28.         while (team!=null) {
  29.              team=team.next;
  30.              len++;
  31.         }
  32.         return len;
  33.     }
  34. }

37 数字在排序数组中出现的次数

题目描述

统计一个数字在排序数组中出现的次数

思路
数组问题,一般枚举能过但是不够优雅。有序的数组、序列当然可以使用二分法哈!先二分找到其中任意一个数字所在的序号,然后根据这个坐标左右试探就可以直到多少个啦!

实现代码为:


   
  1. public class Solution {
  2.      public int GetNumberOfK(int [] array , int k) {
  3.            int l=0,r=array.length;
  4.            int mid=(l+r)/2;
  5.            while (l<r) {
  6.             if(array[mid]==k)
  7.             {
  8.                 return search(array,mid);
  9.             }
  10.             else if (array[mid]<k) {
  11.                 l=mid+1;mid=(l+r)/2;
  12.             }
  13.             else {
  14.                 r=mid;mid=(l+r)/2;
  15.             }
  16.         }
  17.            return 0;
  18.         }
  19.     private int search(int[] array, int mid) {
  20.         int l=mid-1,r=mid+1;
  21.         int value=0;
  22.         while (l>=0&&array[l]==array[mid]) {
  23.             value++;l--;
  24.         }
  25.         while (r<array.length&&array[r]==array[mid]) {
  26.             value++;r++;
  27.         }
  28.         return value+1;
  29.     }
  30. }

38 二叉树的深度

题目描述

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

思路
因为二叉树有 左右子节点,左右节点的问题。每个孩子都要比自己更深一层,对于这个问题,你可以自己定义一个包含次数的结构体或者类用队列或者栈进行遍历找到最大的。但是这题可以直接使用递归进行求解。父节点和子节点之间的关系就是deep(parent)=max(parent.left,parent.right)+1。当然,这里的left和right节点也要判空之类的处理。

实现代码为:


   
  1. /**
  2. public class TreeNode {
  3.     int val = 0;
  4.     TreeNode left = null;
  5.     TreeNode right = null;
  6.     public TreeNode(int val) {
  7.         this.val = val;
  8.     }
  9. }
  10. */
  11. public class Solution {
  12.     public int TreeDepth(TreeNode root) {
  13.         if(root==null)return 0;
  14.          return Math.max(TreeDepth(root.left), TreeDepth(root.right))+1;            
  15.         }
  16. }

39 平衡二叉树

题目描述

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

思路
至于平衡二叉树(avl),之所以平衡,是因为它的任意一个左右节点的高度相差都小于等于1. 如果有不了解的可以参考以前写的一篇avl二叉平衡树 。但是这个跟文中写的的其实不太一样,是因为之前我创造二叉树的时候有个height值每次插入的时候动态维护,判断是否平衡。但是显然这题什么都没有,需要自己完成所有

好吧,来就来,不就是判断每个节点都要满足左右高度都要相差在1之内嘛!上面那题,不就是求高度的嘛?!!我用个队列把所有点存下来,然后一个个判断可还行?虽然可能效率不一定高。但是还是能过的。

实现代码为:


   
  1. import java.util.ArrayDeque;
  2. import java.util.Queue;
  3. public class Solution {
  4.      public boolean IsBalanced_Solution(TreeNode root) {
  5.          if(root==null)return true;
  6.             Queue<TreeNode>q1=new ArrayDeque<TreeNode>();
  7.             q1.add(root);
  8.             while (!q1.isEmpty()) {
  9.                 TreeNode team=q1.poll();
  10.                 if(Math.abs(TreeDepth(team.left)-TreeDepth(team.right))<=1)
  11.                 {
  12.                     if(team.left!=null)q1.add(team.left);
  13.                     if(team.right!=null)q1.add(team.right);
  14.                 }
  15.                 else {
  16.                     return false;
  17.                 }       
  18.             }
  19.             return true;
  20.         }
  21.      public int TreeDepth(TreeNode root) {
  22.             if(root==null)return 0;
  23.              return Math.max(TreeDepth(root.left), TreeDepth(root.right))+1;        
  24.             }
  25. }

40 数组中只出现一次的数字

题目描述

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

思路
这题只能用普通方法过,但是还是有技巧的。感觉可以用位运算但是奈何自己没想出来。两个相同的数进行异或^运算等于0.而0和任何数异或等于它自己。如果只求1个数那可能好搞一些,但是后面会研究讨论区大佬的位运算方案

对于普通思路,可能大部分人会用hashmap先添加,最后遍历两个次数等于1个的两个数返回。但是其实可以使用hash进行减。如果存在这个元素,就减去这个元素。如果不存在那就加入hash中,那么最终剩的2个元素其实就是我们出现一次的元素。由于java底层用hashmap维护hashset我就用hashmap了差不了太大。

实现代码为:


   
  1. import java.util.HashMap;
  2. //num1,num2分别为长度为1的数组。传出参数
  3. //将num1[0],num2[0]设置为返回结果
  4. public class Solution {
  5.    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
  6.             HashMap<Integer, Integer>map=new HashMap<Integer, Integer>();
  7.             for(int i=0;i<array.length;i++)
  8.             {
  9.                 if(map.containsKey(array[i])) {map.remove(array[i]);}
  10.                 else {
  11.                     map.put(array[i],1);
  12.                 }
  13.             }
  14.             int count=0;
  15.             for(Integer va:map.keySet())
  16.             {
  17.                 if(count++==0)num1[0]=va;
  18.                 else {
  19.                     num2[0]=va;
  20.                 }
  21.             }
  22.         }
  23. }

参考讨论区:
这题的讨论区很妙,用^来解决问题。至于讨论区的题解稍微解释一下:大概就是将数组一分为2份
就是说先异或到最后得到一个数,肯定是a^b的值。这个数位位1的那个肯定就是两个位不同的。比如10100.第三位就是a和b不同的,然后我们就再次遍历所有数,用两个数进行异或,如果第三位为1xx那么和a1异或,如果为0xx那么和a2异或。就相当于把这个数逻辑一分为2.因为所有数除了这两都出现两次,所以是可以消下去的!



实现代码为:


   
  1. 链接:https://www.nowcoder.com/questionTerminal/e02fdb54d7524710a7d664d082bb7811?f=discussion
  2. 来源:牛客网
  3. public class Solution {
  4.     public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2)    {
  5.         int length = array.length;
  6.         if(length == 2){
  7.             num1[0] = array[0];
  8.             num2[0] = array[1];
  9.             return;
  10.         }
  11.         int bitResult = 0;
  12.         for(int i = 0; i < length; ++i){
  13.             bitResult ^= array[i];
  14.         }
  15.         int index = findFirst1(bitResult);
  16.         for(int i = 0; i < length; ++i){
  17.             if(isBit1(array[i], index)){
  18.                 num1[0] ^= array[i];
  19.             }else{
  20.                 num2[0] ^= array[i];
  21.             }
  22.         }
  23.     }
  24.     private int findFirst1(int bitResult){
  25.         int index = 0;
  26.         while(((bitResult & 1) == 0) && index < 32){
  27.             bitResult >>= 1;
  28.             index++;
  29.         }
  30.         return index;
  31.     }
  32.     private boolean isBit1(int target, int index){
  33.         return ((target >> index) & 1) == 1;
  34.     }
  35. }

文章来源: bigsai.blog.csdn.net,作者:Big sai,版权归原作者所有,如需转载,请联系作者。

原文链接:bigsai.blog.csdn.net/article/details/115381038

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。