剑指offer-每日6题之第六天(java版)

原题链接:

 第一题:整数中1出现的次数(从1到n整数中1出现的次数)
 第二题:把数组排成最小的数
 第三题:丑数
 第四题:第一个只出现一次的字符
 第五题:数组中的逆序对
 第六题:两个链表的第一个公共结点

第一题:整数中1出现的次数(从1到n整数中1出现的次数)

题目描述

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

解析

把1到n分成三部分:如a=12345和b=22345

第一部分和第二部分求23456-12345和23456-22345中的1的个数

      第一部分(数的第一位所能包含的1的个数):

                        因为a的第一位是1,所以,在以1开头的存在的1的个数有2345+1个,+1是因为有10000

                        对于b,因为第一位是2,所以,在以1开头的存在的1的个数有10^4个(从10000到19999)

      第二部分(除开第一位,剩下的位数所能包含的1的个数):

                        对于a除开第一位的剩余位的数字2345,如果固定一位取1,其他三位可以取0-9中的任意的数,所以,有1*4*10^3

                        对于b除开第一位的剩余位的数字2345,如果固定一位取1,其他三位可以取0-9中的任意的数,但是第一位是2,在第一                         位为1的时候有1*4*10^3个,在第一位为2的时候也有1*4*10^3个,所以,共有2*4*10^3

      第三部分(遍历1-2345):同样采用刚才的算法处理子问题,即用递归求解

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
      if(n<0)return 0;
      int one=0,two=0,three=0;
      String str=String.valueOf(n);
      char []chs=str.toCharArray();
      int len=str.length();
      if(len==1 && chs[0]=='0')return 0;
      else if(len==1 && chs[0]>='1')return 1;
      else if(chs[0]>'1') one=(int)Math.pow(10,len-1);
      else  if(chs[0]=='1')one=Integer.valueOf(str.substring(1))+1;
      two=(int)((chs[0]-'0')*(len-1)*Math.pow(10,len-2));
      three=NumberOf1Between1AndN_Solution(Integer.valueOf(str.substring(1)));
      return one+two+three;
    }
}

第二题:把数组排成最小的数

题目描述

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

解析

       利用集合的排序,排序把两个元素拼接起来比较大小,按顺序排,比如321 32,比较32132和32321,显然32132比较小,所以321排序后再32的前面,同理可以得到其他的

       最后把得到的集合连接成字符串

import java.util.*;

public class Solution {
    public String PrintMinNumber(int [] numbers) {
   String res="";
    List<Integer>list=new ArrayList<Integer>();
    for(int i=0;i<numbers.length;i++){
        list.add(numbers[i]);
    }
    Collections.sort(list, new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return (o1+""+o2).compareTo(o2+""+o1);
        }
    });
        for(int i=0;i<numbers.length;i++){
            res+=list.get(i);
        }
    return res;
    }
}

第三题:丑数

题目描述

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

解析

循环判断从1到n的数是否为丑数

判断是否为丑数:%2,%3,%5,不为0,n/=2,4,5

(时间复杂度太高,不能通过)

public class Solution {
    public int GetUglyNumber_Solution(int index) {
       if(index<=0)return 0;
       int number=0;
       int uglyFound=0;
       while(uglyFound<index){
            ++number;
            if(isUgly(number)){
                 ++uglyFound;
            } 
       }   
       return number;
    }
    public boolean isUgly(int num){
       while(num%2==0) num/=2;
       while(num%5==0) num/=5;
       while(num%5==0) num/=5;
       return (num==1)?true:false;
    }
}

解析

定义一个数组存已经得到的丑数,那么第一个已经存在的丑数就是1.定义t2,t3,t5;t2的意义为:已有的丑数中最大的数<t2*2

即t2本次丑数数组中的某个数,t2*2是下一次结果中更新的有关质因子2的最小的丑数,同样,我们就可以定义出t3,t5,最后比较更新后

t2*2,t3*3,t5*5的最小值就为下一次的最小的丑数

    所以,问题是如何找到t2,t3,t5?

在已经得到的丑数中,我们是按顺序排列的,所以,要找t2的位置,我们可以知道t2*2得到的数>已有的丑数中最大的数,那么t2之前的数*2<已有的最大的丑数,我们只需把数组的的丑数*2得到的结果 和当前的最大的丑数对比,就能定位到t2的位置。

public class Solution {
    public int GetUglyNumber_Solution(int index) {
       if(index<=0)return 0;
       int[]ugly=new int[index];
       ugly[0]=1;
       int nextIndex=1;
       int t2=0,t3=0,t5=0;
       while(nextIndex<index){
           int min= min(ugly[t2]*2,ugly[t3]*3,ugly[t5]*5);
           ugly[nextIndex]=min;
           while(ugly[t2]*2<=ugly[nextIndex])t2++;
           while(ugly[t3]*3<=ugly[nextIndex])t3++;
           while(ugly[t5]*5<=ugly[nextIndex])t5++;
           ++nextIndex;
       }
       return ugly[nextIndex-1];
    }
    public int min(int a,int b,int c){
        int min=a<b?a:b;
        return min<c?min:c;
    }
}

第四题:第一个只出现一次的字符

题目描述

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

解析

定义一个HashMap,把字符串添加到map中,value为字符串出现的次数

在遍历一次字符串,找到第一个map中值为1的

import java.util.*;
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        Map<Character,Integer>map=new HashMap<Character,Integer>();
        int i=0;
        for(;i<str.length();i++){
            char ch=str.charAt(i);
            if(map.containsKey(ch)){
                int count=map.get(ch);
                map.put(ch,++count);
            }else{
                map.put(ch,1);
            }
        }
        int index=-1;
        for(i=0;i<str.length();i++){
            if(map.get(str.charAt(i))==1) {
                index = i;
                break;
            }
        }

   return index;
    }
}

第五题:数组中的逆序对

题目描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

解析

归并排序扩展:

       1.归并排序:

           对于一个无序数组如:2 3  1 0 4 2 5 6 9 7

           平均的分为左部分(2 3  1 0 4)和右部分(2 5 6 9 7)

           对左部分和右部分进行排序,排序结果为(0 1 2 3 4)和(2 5 6 9 7)

           左部分和右部分排序完成后,调用merge函数把两部分合成一个有序的数组(用两个指针指向两部分开头,往后遍历,并用辅助数组help存储得到的有序数组)

           左部分和右部分的排序同样调用归并排序过程,一直划分为两个子树组,当左部分和右部分为一个数不能划分时(下标相等时),返回

       2.如果使用归并排序解答这道题呢?

            对左部分和右部分求逆序对数,最后总的逆序对数=左部分的逆序对数+右部分的逆序对数+merge过程的逆序对数

           求解merge的逆序对数:

                   如左部分 4 5 6      右部分3 4 5

                  当指针都指向 两部分开始位置时,4>3,存在逆序对,且4的 右边的都是3的逆序对,所以对于3,逆序对的个数为mid-0+1=2-0+1;然后右部分指针往后移一个,指向4位置,4=4,不产生逆序对,count+=0;一直这样遍历到结束

    注意:count在三个部分计算的时候都要P%1000000007,最后一次在三部分相加后也要P%1000000007,防止三个部分相加的值超过P%1000000007

                  

         

public class Solution {
     public int InversePairs(int [] array) {
            if(array==null||array.length<2)return 0;
            int count=sortPress(0,array.length-1,array)%1000000007;
            return count;
        }
        public int merge(int l,int mid,int r,int[] arr){
            int []help=new int [r-l+1];
            int p1=l;
            int p2=mid+1;
            int i=0;
            int count=0;
            while(p1<=mid&&p2<=r){
                count+=arr[p1]<=arr[p2]?0:mid-p1+1;
                count%=1000000007;
                help[i++]=arr[p1]<=arr[p2]?arr[p1++]:arr[p2++]; 
            }
            while(p1<=mid)help[i++]=arr[p1++];
            while(p2<=r)help[i++]=arr[p2++];
            for(int j=0;j<help.length;j++){
                arr[j+l]= help[j];
            }
            return count;
        }


        public int sortPress(int l,int r,int []arr){
            if(l==r)return 0;
            int mid=l+((r-l)>>1);
            return sortPress(l,mid,arr)%1000000007+sortPress(mid+1,r,arr)%1000000007+merge(l,mid,r,arr);
        }
}

六题:两个链表的第一个公共结点

题目描述

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

解析

方法一:

      分别求出两个链表的长度,让较长的链表先走abs(len1-len2)步,然后两个链表一起走,当遇到值相等时,找到了公共节点

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
import java.util.*;
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
          if(pHead1==null||pHead2==null)return null;
          int len1=getLsitLength( pHead1);
          int len2=getLsitLength( pHead2);
          boolean flag=len1>len2?true:false;
           ListNode lon=pHead1;
                ListNode sho=pHead2;
             if(!flag){
                 lon=pHead2;
                 sho=pHead1;
            }
              
              int i=0;
              while(i<Math.abs(len1-len2)){
                  lon=lon.next;
                  i++;
              }
              while(lon!=null && sho!=null && lon.val!=sho.val){
                  lon=lon.next;
                  sho=sho.next;
              }
       return lon;
    }
    public int getLsitLength(ListNode pHead){
        int len=0;
        while(pHead!=null){
            len++;
            pHead=pHead.next;
        }
            return len;
    }
}

解析

方法二:

       定义两个栈分别存储两个链表的内容,然后从栈顶开始遍历,当栈顶的值步相等时,返回上次相等的节点,否则,两个栈顶同时出栈,直到找到不相等的节点

import java.util.*;
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
       if(pHead1==null||pHead2==null)return null;
            Stack <ListNode>stack1=new Stack();
            Stack <ListNode>stack2=new Stack();
            while(pHead1!=null){
                stack1.push(pHead1);
                pHead1=pHead1.next;
            }
            while(pHead2!=null){
            stack2.push(pHead2);
            pHead2=pHead2.next;
            }
            ListNode comNode=null;
            while(!(stack1.isEmpty() || stack2.isEmpty())&&stack1.peek().val==stack2.pop().val){
                comNode=stack1.pop();
            }
            return comNode;
    }
}

解析

方法三:

       利用HashMap的性质,把第一个链表的值保存到map中,遍历第二个链表,当第二个链表的key在第一个链表中,返回这个节点

import java.util.HashMap;
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode current1 = pHead1;
        ListNode current2 = pHead2;
        HashMap<ListNode, Integer> hashMap = new HashMap<ListNode, Integer>();
        while (current1 != null) {
            hashMap.put(current1, null);
            current1 = current1.next;
        }
        while (current2 != null) {
            if (hashMap.containsKey(current2))
                return current2;
            current2 = current2.next;
        }
        return null;
    }
}

解析

方法四:

       把两个链表连起来,如把pHead1后面连pHead2,pHead2后面连pHead1,此时,两个链表一样长

       用两个指针扫描”两个链表“,最终两个指针到达 null 或者到达公共结点

       eg:1 2 3 6 7

               4 5 6 7

        连起来之后变为:

          1 2 3 6 7 4 5 6 7

          4 5 6 7 1 2 3 6 7

        两个指针遍历到6,找到相等的节点

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        while(p1!=p2){
            p1 = (p1==null ? pHead2 : p1.next);
            p2 = (p2==null ? pHead1 : p2.next);
        }
        return p1;
    }
}

猜你喜欢

转载自blog.csdn.net/ccccc1997/article/details/81709480