1的個數問題、數組最大最小值java---編程之美


劍指offer原題:

給定一個十進制整數N,求出從1到N的所有整數中出現"1"的個數。

例如:N=2,1,2出現了1個"1"。

N=12,1,2,3,4,5,6,7,8,9,10,11,12。出現了5個"1"。

最直接的方法就是從1開始遍歷到N,將其中每一個數中含有"1"的個數加起來,就得到了問題的解。

public static long CountOne3(long n)
{
	long i = 0,j = 1;
	long count = 0;
	for (i = 0; i <= n; i++)
	{
		j = i;
		while (j != 0)
		{
			if (j % 10 == 1)
				count++;
			j = j / 10;
		}
	}
	return count;
}

此方法簡單,容易理解,但它的問題是效率,時間複雜度爲O(N * lgN),N比較大的時候,需要耗費很長的時間。

我們重新分析下這個問題,對於任意一個個位數n,只要n>=1,它就包含一個"1";n<1,即n=0時,則包含的"1"的個數爲0。於是我們考慮用分治的思想將任意一個n位數不斷縮小規模分解成許多個個位數,這樣求解就很方便。

但是,我們該如何降低規模?仔細分析,我們會發現,任意一個n位數中"1"的個位可以分解爲兩個n-1位數中"1"的個數的和加上一個與最高位數相關的常數C。例如,f(12) = f(10 - 1) + f(12 - 10) + 3,其中3是表示最高位爲1的數字個數,這裏就是10,11,12;f(132)=f(100 -1) + f(132 - 100) + 33,33代表最高位爲1的數字的個數,這裏就是100~132;f(232) = 2*f(100 - 1) + f(32) + 100,因爲232大於199,所以它包括了所有最高位爲1的數字即100~199,共100個。

綜上,我們分析得出,最後加的常數C只跟最高位n1是否爲1有關,當最高位爲1時,常數C爲原數字N去掉最高位後剩下的數字+1,當最高位爲1時,常數C爲10bit,其中bit爲N的位數-1,如N=12時,bit=1,N=232時,bit=2。

於是,我們可以列出遞歸方程如下:

if(n1 == 1) 
f(n) = f(10bit-1) + f(n - 10bit) + n - 10bit+ 1; 
else 
f(n) = n1*f(10bit-1) + f(n – n1*10bit) + 10bit; 

遞歸的出口條件爲:

if(1<n<10)  return 1;
else if (n == 0) return 0;

基於此,編寫如下代碼:

public static long CountOne(long n)
{  
	long count = 0;
	if (n == 0)
		count = 0;
	else if (n > 1 && n < 10)
		count =  1;
	else
	{
		long highest = n;//表示最高位的數字
		int bit = 0;
		while (highest >= 10)
		{
			highest = highest / 10;
			bit++;
		}
		int weight = (int)Math.pow(10, bit);//代表最高位的權重,即最高位一個1代表的大小
		if (highest == 1)
		{
			count = CountOne(weight - 1)
			+ CountOne(n - weight)
			+ n - weight + 1;
		}
		else
		{
      		count = highest * CountOne(weight - 1)
    		+ CountOne(n - highest * weight)
        	+ weight;
		}
	}
	return count;
}

解法二告訴我們1~ N中"1"的個數跟最高位有關,那我們換個角度思考,給定一個N,我們分析1~N中的數在每一位上出現1的次數的和,看看每一位上"1"出現的個數的和由什麼決定。

1位數的情況:在解法二中已經分析過,大於等於1的時候,有1個,小於1就沒有。

2位數的情況:N=13,個位數出現的1的次數爲2,分別爲1和11,十位數出現1的次數爲4,分別爲10,11,12,13,所以f(N) = 2+4。N=23,個位數出現的1的次數爲3,分別爲1,11,21,十位數出現1的次數爲10,分別爲10~19,f(N)=3+10。

由此我們發現,個位數出現1的次數不僅和個位數有關,和十位數也有關,如果個位數大於等於1,則個位數出現1的次數爲十位數的數字加1;如果個位數爲0,個位數出現1的次數等於十位數數字。而十位數上出現1的次數也不僅和十位數相關,也和個位數相關:如果十位數字等於1,則十位數上出現1的次數爲個位數的數字加1,假如十位數大於1,則十位數上出現1的次數爲10。

3位數的情況:

N=123,個位出現1的個數爲13:1,11,21,…,91,101,111,121。十位出現1的個數爲20:10~19,110~119。百位出現1的個數爲24:100~123。

我們可以繼續分析4位數,5位數,推導出下面一般情況: 假設N,我們要計算百位上出現1的次數,將由三部分決定:百位上的數字,百位以上的數字,百位以下的數字。

如果百位上的數字爲0,則百位上出現1的次數僅由更高位決定,比如12013,百位出現1的情況爲100~199,1100~1199,2100~2199,…,11100~11199,共1200個。等於更高位數字乘以當前位數,即12 * 100。

如果百位上的數字大於1,則百位上出現1的次數僅由更高位決定,比如12213,百位出現1的情況爲100~199,1100~1199,2100~2199,…,11100~11199,12100~12199共1300個。等於更高位數字加1乘以當前位數,即(12 + 1)*100。

如果百位上的數字爲1,則百位上出現1的次數不僅受更高位影響,還受低位影響。例如12113,受高位影響出現1的情況:100~199,1100~1199,2100~2199,…,11100~11199,共1200個,但它還受低位影響,出現1的情況是12100~12113,共114個,等於低位數字113+1。

綜合以上分析,寫出如下代碼:

public long CountOne2(long n)
{
	long count = 0;
	long i = 1;
	long current = 0,after = 0,before = 0;
	while((n / i) != 0)
	{           
		current = (n / i) % 10;
		before = n / (i * 10);
		after = n - (n / i) * i;
		if (current > 1)//百位上的數字大於1
			count = count + (before + 1) * i;
		else if (current == 0)//百位上的數字爲0
			count = count + before * i;
		else if(current == 1)//百位上的數字爲1
			count = count + before * i + after + 1;
		i = i * 10;
	}
	return count;
}

此算法的時間複雜度僅爲O(lgN),且沒有遞歸保存現場的消耗和堆棧溢出的問題。
注:字符串解法會時間很長
public class NumOf1 {
	public static void main(String[] args) {
		int m = 1000000;
		String all = "";
		int al = 0;
		for (int i = 0; i <= m; i++) {
			all = all + "" + i;
		}
		for (int j = 0; j < all.length(); j++) {
			if (all.charAt(j) == '1') {
				al++;
			}
		}
		System.out.println(al);
		
		
	}


附錄:數組最大最小值,基本解法和分治法




package Test;

/**
 *
 * 尋找數組中的最大值和最小值
 */public class SearchArrayMaxMin {
    public static void main(String[] args) {
        int[] arry = new int[]{5,6,8,3,7,9};
        //解法一 分別尋找最大值和最小值
        int[] num1 = searchArrayMaxMin1(arry);
        System.out.println("min:"+num1[0]+"max:"+num1[1]);
        
        //解法四  分治策略
        int[] num4 = searchArrayMaxMin(arry,0,arry.length-1);
        System.out.println("min:"+num4[0]+"max:"+num4[1]);
    }

    private static int[] searchArrayMaxMin1(int[] arry) {
       int[] result = new int[2];
       int min = arry[0];
       int max = arry[0];
       for(int i =1;i<arry.length;i++){
           if(arry[i]<min)
               min =arry[i];
           else
               max =arry[i];
       }
       result[0]= min;
       result[1]=max;
       return result;
    }

    private static int[] searchArrayMaxMin(int[] arry, int begin, int end) {
        int[] result = new int[2];
        if(end-begin <=1){
            if(arry[begin]<arry[end]){
                result[0]=arry[begin];
                result[1]= arry[end];
                return result;
            }else{
                result[0] = arry[end];
                result[1] = arry[begin];
                return result;
            }
        }
        int mid = (begin+end)/2;
        int[] result_left = searchArrayMaxMin(arry,begin,mid);
        int[] result_right =searchArrayMaxMin(arry,mid+1,end);
        if(result_left[0]<result_right[0])
            result[0]= result_left[0];
        else
            result[0]=result_right[0];
        if(result_left[1]>result_right[1])
            result[1]= result_left[1];
        else
            result[1]=result_right[1];
        return result;
    }
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章