1. 程式人生 > >劍指offer-每日6題之第六天(java版)

劍指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;
    }
}