《劍指offer》數學題及其它 (牛客11.05)

比較多的思惟題,涉及位運算、快速冪、二進制、約瑟夫問題、隊列、貪心、dp等等。html

難度 題目 知識點
十二、數值的整數次方 細節,快速冪
☆☆ 4七、求1+2+3+···+n 思惟發散
☆☆ 4八、不用加減乘除作加法 二進制運算
☆☆☆ 十一、二進制中1的個數 補碼,位運算
☆☆☆☆ 2九、最小的K個數 查找第K大,或各類排序算法
3一、從1到n整數中1出現的次數 思惟
3三、醜數 思惟
4一、和爲S的連續正數序列 滑動窗口,雙指針
4二、和爲S的兩個數字 滑動窗口,雙指針
4五、撲克牌順子 多種狀況判斷
☆☆☆ 4六、圓圈中最後剩下的數 約瑟夫環,數學
☆☆☆ 6三、數據流中的中位數 妙用堆,PriorityQueue
☆☆☆ 6四、滑動窗口的最大值 模擬,滑動窗口,雙端隊列
☆☆☆ 6七、剪繩子 貪心,動態規劃

十二、數值的整數次方+

細節,快速冪

給定一個double類型的浮點數 base 和 int 類型的整數exponent。求base的exponent次方。 保證base和exponent不一樣時爲0java

題意分析算法

指數可能爲負數。數組

Java Code
class Solution {
public:
    double Power(double base, int exponent) {
        double ans=1;
        bool neg= false;
        if(exponent<0){
            neg=true;
            exponent=-exponent;
        }
        for( ; exponent; base*=base,exponent/=2){
            if(exponent&1){
                ans*=base;
            }
        }
        return neg?1/ans:ans;
    }
};

4七、求1+2+3+···+n ++

思惟發散

求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字及條件判斷語句(A?B:C)。ide

題意分析函數

須要思惟發散,善用遞歸和短路運算,見代碼。測試

Java Code
class Solution {
public:
    int Sum_Solution(int n) {
        int ans = n;
        ans && (ans += Sum_Solution(n - 1));
        return ans;
    }
};

4八、不用加減乘除作加法++

二進制運算

寫一個函數,求兩個整數之和,要求在函數體內不得使用+、-、*、/四則運算符號。spa

題意分析.net

第一步:相加各位的值,不算進位,獲得010,二進制每位相加就至關於各位作異或操做,101^111。
第二步:計算進位值,獲得1010,至關於各位作與操做獲得101,再向左移一位獲得1010,(101&111)<<1。 第三步:重複上述兩步, 各位相加 010^1010=1000,進位值爲100=(010&1010)<<1。指針

繼續重複上述兩步:1000^100 = 1100,進位值爲0,跳出循環,1100爲最終結果。

注意循環終止條件。 有負數相加的狀況。

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

十一、二進制中1的個數 ++

補碼,位運算

輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼錶示。

題意分析

注意對於負數,右移一位會補 1 而非補零。

CPP Code
// 1. 去掉符號位 1
class Solution {
public:
     int  NumberOf1(int n) {
         int cnt=0;
         if(n<0) {
             n &=0x7fffffff;
             cnt++;
         }
         while(n){
             cnt+=(n&1);
             n>>=1;
         }
         return cnt;
     }
};
// 2. 轉爲 unsigned int
class Solution {
public:
     int  NumberOf1(int n) {
         unsigned int nn=n;
         int cnt=0;
         while(nn){
             cnt+=(nn&1);
             nn>>=1;
         }
         return cnt;
     }
};
// 3. 每次 n&(n-1) 將從右邊起的第一個 1 變爲 0
public class Solution {
    public int NumberOf1(int n) {
        int count = 0;
        while(n!= 0){
            count++;
            n = n & (n - 1);
         }
        return count;
    }
}

2九、最小的K個數+++

查找第K大,或各類排序算法

輸入n個整數,找出其中最小的K個數。例如輸入4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4,。

題意分析

用Partition O(n)找到第K大。而後遍歷輸出前K個數。

  • 檢查數據合法狀況
  • partition編寫
  • 不用IDE的話出現了全角字符、忘記導入包的問題
Java Code
import java.util.Arrays;
import java.util.ArrayList;
public class Solution {
    public ArrayList
   
   
   

  
   
  GetLeastNumbers_Solution(int [] input, int k) { ArrayList 
 
   
     ans=new ArrayList<>(); if(input==null|| k==0 || k>input.length) return ans; int x=k+1,st=0,ed=input.length-1; do{ x=Partition(input,st,ed); if(x 
    
      k-1){ed=x-1;} }while(x!=k-1); //Arrays.sort(input,0,k); for(int i=0;i < k;i++){ ans.add(input[i]); } return ans; } private int Partition(int[] arr,int st,int ed){//[, ] int pivot=arr[st]; int i=st,j=ed; while(i < j){ while(j > i && arr[j] >= pivot)j--; arr[i]=arr[j]; while(i < j && arr[i] <= pivot)i++; arr[j]=arr[i]; } arr[i]=pivot; return i; } } 
     
    

  

3一、從1到n整數中1出現的次數++++

思惟

題目描述

求出1~13的整數中1出現的次數,並算出100~1300的整數中1出現的次數?爲此他特別數了一下1~13中包含1的數字有一、十、十一、十二、13所以共出現6次,可是對於後面問題他就沒轍了。ACMer但願大家幫幫他,並把問題更加廣泛化,能夠很快的求出任意非負整數區間中1出現的次數(從1 到 n 中1出現的次數)。

題意分析

將n的各個位分爲兩類:個位與其它位。
對個位來講:

  • 若個位大於0,1出現的次數爲round*1+1
  • 若個位等於0,1出現的次數爲round*1

對其它位來講,記每一位的權值爲base,位值爲weight,該位以前的數是former,舉例如圖:
這裏寫圖片描述

則:

  • weight爲0,則1出現次數爲round*base
  • weight爲1,則1出現次數爲round*base+former+1
  • weight大於1,則1出現次數爲rount*base+base

好比:

534 = (個位1出現次數)+(十位1出現次數)+(百位1出現次數)
    =(53*1+1)+(5*10+10)+(0*100+100)= 214
530 = (53*1)+(5*10+10)+(0*100+100) = 213
504 = (50*1+1)+(5*10)+(0*100+100) = 201
514 = (51*1+1)+(5*10+4+1)+(0*100+100) = 207
10 = (1*1)+(0*10+0+1) = 2

————————————————
版權聲明:本文爲CSDN博主「yi_afly」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接及本聲明。
原文連接:https://blog.csdn.net/yi_afly/article/details/52012593

Java Code
public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        int base=1,round=n,weight,former;
        int cnt=0;
        while(base <= n){
            weight=round%10;
            round=round/10;
            cnt+=round*base;
            former=n%base;
            if(weight==1)    cnt+=former+1;
            else if(weight > 1)cnt+=base;
            base*=10;
        }
        return cnt;
    }
}

3三、醜數 +

思惟

把只包含質因子二、3和5的數稱做醜數(Ugly Number)。例如六、8都是醜數,但14不是,由於它包含質因子7。 習慣上咱們把1當作是第一個醜數。求按從小到大的順序的第N個醜數。

題意分析

根據醜數的定義,醜數應該是另外一個醜數乘以二、3或者5的結果(1除外)。所以,咱們能夠建立一個數組,裏面保存的是排好序的醜數,每個醜數均可以由前面的醜數乘以二、3或者5獲得。

問題在於,如何保證生成的醜書序列是有序的。解決方法是,維護3個「指針」,分別指向二、三、5當前要乘的數,而後取三個乘積中的最小加入醜數序列,同時,維護指針所指位置。要注意的是,爲了不重複,若是出現minn==2*num[p2]==5*nums[p5]的狀況,那麼兩個指針都須要後移。

  • 細節 - 重複不計
Java Code
import java.util.ArrayList;
public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if (index <= 0) return 0;
        int cnt = 0;
        ArrayList
   
   
   

  
   
  nums = new ArrayList<>(); nums.add(1); int p2 = 0, p3 = 0, p5 = 0; while (cnt < index) { int x = Math.min(2 * nums.get(p2), Math.min(3 * nums.get(p3), 5 * nums.get(p5))); nums.add(x); cnt++; if (x == 2 * nums.get(p2)) p2++; if (x == 3 * nums.get(p3)) p3++;// 不是else if if (x == 5 * nums.get(p5)) p5++; } return nums.get(index - 1); } } 

  

4一、和爲S的連續正數序列

滑動窗口,雙指針

題目描述

小明很喜歡數學,有一天他在作數學做業時,要求計算出9~16的和,他立刻就寫出了正確答案是100。可是他並不知足於此,他在想究竟有多少種連續的正數序列的和爲100(至少包括兩個數)。沒多久,他就獲得另外一組連續正數和爲100的序列:18,19,20,21,22。如今把問題交給你,你能不能也很快的找出全部和爲S的連續正數序列? Good Luck!

輸出描述:

輸出全部和爲S的連續正數序列。序列內按照從小至大的順序,序列間按照開始數字從小到大的順序

題意分析

n從1開始遞增,長度從sum開始遞減,檢查是否知足序列和爲sum。

  • 審題 - 序列長大於1
Java Code
import java.util.ArrayList;
public class Solution {
    public ArrayList
   
   
   

   
 
   
     > FindContinuousSequence(int sum) { ArrayList 
     
     
       > ans = new ArrayList<>(); ArrayList 
      
        cur; if (sum <= 0) return ans; int n = 1, l = sum; while (n <= sum && l > 1) { while ((n + n + l - 1) * l / 2 > sum) l--; if (l > 1 && (n + n + l - 1) * l / 2 == sum) { cur = new ArrayList<>(); for (int i = n; i < n + l; i++) cur.add(i); ans.add(cur); } n++; } return ans; } } 
       
      
     
    

  

4二、和爲S的兩個數字

滑動窗口,雙指針

題目描述

輸入一個遞增排序的數組和一個數字S,在數組中查找兩個數,使得他們的和正好是S,若是有多對數字的和等於S,輸出兩個數的乘積最小的。

輸出描述:

對應每一個測試案例,輸出兩個數,小的先輸出。

題意分析

和上題相似的滑動窗口,雙指針。

Java Code
import java.util.ArrayList;
public class Solution {
    public ArrayList
   
   
   

  
   
  FindNumbersWithSum(int[] array, int sum) { ArrayList 
 
   
     ans = new ArrayList<>(); if (array == null || array.length < 2 || sum < array[0]) return ans; int p1 = 0, p2 = array.length - 1; while (p1 < p2) { while (array[p2] + array[p1] > sum && p1 < p2) p2--; if (p1 < p2 && array[p2] + array[p1] == sum) { ans.add(array[p1]); ans.add(array[p2]); return ans; } p1++; } return ans; } } 
    

  

4五、撲克牌順子

多種狀況判斷

題目描述

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。

題意分析

有四張王必定爲順子,其他狀況下,一副牌爲順子當且僅當最大數字牌和最小數字牌之差小於5且沒有重複的數字牌。

  • 輸入數據檢查
  • 數組邊界
Java Code
import java.util.Arrays;
public class Solution {
    public boolean isContinuous(int[] numbers) {
        if (numbers == null || numbers.length != 5) return false;
        Arrays.sort(numbers);
        int minn = 0, maxx = numbers[numbers.length - 1];
        int loc = -1;
        for (int i = 0; i < numbers.length; i++) {
            if (numbers[i] > 0) {
                loc = i;
                minn = numbers[i];
                break;
            }
        }
        if (loc == numbers.length - 1) return true;// 4 king
        if (maxx - minn + 1 > 5) return false;
        for (int i = loc; i + 1 < numbers.length; i++) {
            if (numbers[i] == numbers[i + 1])
                return false;
        }
        return true;
    }
}

4六、圓圈中最後剩下的數+++

約瑟夫環,數學

題目描述

每一年六一兒童節,牛客都會準備一些小禮物去看望孤兒院的小朋友,今年亦是如此。HF做爲牛客的資深元老,天然也準備了一些小遊戲。其中,有個遊戲是這樣的:首先,讓小朋友們圍成一個大圈。而後,他隨機指定一個數m,讓編號爲0的小朋友開始報數。每次喊到m-1的那個小朋友要出列唱首歌,而後能夠在禮品箱中任意的挑選禮物,而且再也不回到圈中,從他的下一個小朋友開始,繼續0...m-1報數....這樣下去....直到剩下最後一個小朋友,能夠不用表演,而且拿到牛客名貴的「名偵探柯南」典藏版(名額有限哦!!^_^)。請你試着想下,哪一個小朋友會獲得這份禮品呢?(注:小朋友的編號是從0到n-1)

若是沒有小朋友,請返回-1

題意分析

用模擬來作天然是能夠的。但複雜度是O(n*m)。看了一下別人的數學推導思路。

原問題是從0...n-1中循環去掉第m個數,求剩下的最後一個數是多少,咱們假設原求解問題是f(n,m)

去掉第一個數k=(m-1)%n以後,還剩下k+1...n-1,0...k-1n-1個數,問題變成了從這n-1個數中循環刪去第m個數,求最後剩下的一個數,記這個問題爲f'(n-1,m)。那麼f(n,m)f'(n-1,m)最終獲得的結果是相同的,即f(n,m)=f'(n-1,m)

而若是把k+1...n-1,0...k-10..n-1做置換,那麼f'(n-1,m)=(f(n-1,m)+k+1)%n,①②式聯合,獲得f(n,m)=(f(n-1,m)+k+1)%n。因而遞歸關係就找到了,這樣計算的時間複雜度是O(n)。

最後注意遞歸的出口,見代碼。

Java Code
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if (n == 0) return -1;
        if (n == 1) return 0;
        return (LastRemaining_Solution(n - 1, m) + m) % n;
    }
}

6三、數據流中的中位數+++

妙用堆,PriorityQueue

題目描述

如何獲得一個數據流中的中位數?若是從數據流中讀出奇數個數值,那麼中位數就是全部數值排序以後位於中間的數值。若是從數據流中讀出偶數個數值,那麼中位數就是全部數值排序以後中間兩個數的平均值。咱們使用Insert()方法讀取數據流,使用GetMedian()方法獲取當前讀取數據的中位數。

題意分析

gzshan的分析很清晰。

方法五:最大堆和最小堆。咱們注意到當數據保存到容器中時,能夠分爲兩部分,左邊一部分的數據要比右邊一部分的數據小。以下圖所示,P1是左邊最大的數,P2是右邊最小的數,即便左右兩部分數據不是有序的,咱們也有一個結論就是:左邊最大的數小於右邊最小的數

img

 所以,咱們能夠有以下的思路:向堆中插入一個數據的時間是O(logn),而中位數就是堆頂的數據,只須要O(1)的時間就可獲得。

  而在具體實現上,首先要保證數據平均分配到兩個堆中,兩個堆中的數據數目之差不超過1,爲了實現平均分配,能夠在數據的總數目是偶數時,將數據插入最小堆,不然插入最大堆。

  此外,還要保證全部最大堆中的數據要小於最小堆中的數據。因此,新傳入的數據要和最大堆中最大值或者最小堆中的最小值比較。當總數目是偶數時,咱們會插入最小堆,可是在這以前,咱們須要判斷這個數據和最大堆中的最大值哪一個更大,若是最大值中的最大值比較大,那麼將這個數據插入最大堆,並把最大堆中的最大值彈出插入最小堆。因爲最終插入到最小堆的是原最大堆中最大的,因此保證了最小堆中全部的數據都大於最大堆中的數據。

Java Code
import java.util.Comparator;
import java.util.PriorityQueue;
public class Solution {
    PriorityQueue
   
   
   

  
   
  minQ = new PriorityQueue<>();// default PriorityQueue 
 
   
     maxQ = new PriorityQueue<>(new Comparator 
    
      () { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } }); int cnt = 0; public void Insert(Integer num) { cnt++; if ((cnt & 1) == 1) { int candidate = num + 1; if (!minQ.isEmpty()) candidate = minQ.peek(); if (num <= candidate) maxQ.add(num); else { maxQ.offer(candidate); minQ.poll(); minQ.offer(num); } } else { int candidate = num - 1; if (!maxQ.isEmpty()) candidate = maxQ.peek(); if (num >= candidate) minQ.offer(num); else { minQ.offer(candidate); maxQ.poll(); maxQ.offer(num); } } } public Double GetMedian() { if ((cnt & 1) == 1) return (double) maxQ.peek(); else return (minQ.peek() + maxQ.peek()) / 2.0; } } 
     
    

  

6四、滑動窗口的最大值+++

模擬,滑動窗口,雙端隊列

題目描述

給定一個數組和滑動窗口的大小,找出全部滑動窗口裏數值的最大值。例如,若是輸入數組{2,3,4,2,6,2,5,1}及滑動窗口的大小3,那麼一共存在6個滑動窗口,他們的最大值分別爲{4,4,6,6,6,5}; 針對數組{2,3,4,2,6,2,5,1}的滑動窗口有如下6個: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

題意分析

用雙端隊列,維護隊首元素爲當前窗口的最大值的下標,隊中元素爲後續窗口可能的最大值的下標。具體爲,每次後移一格成爲新的滑動窗口時,【若是隊首過時,那麼將隊首刪去】,【若是刪掉隊列中全部比新元素小的元素】,【再將新元素下標加入】,此時,隊首值就是當前窗口的最大值下標。

第一個操做保證及時刪掉了過時的最大值。第二個操做保證了隊列中都是可能爲最大值的元素下標,及時剔除了不可能爲最大值的元素。第三個操做是因爲隨着窗口後移,新的元素可能成爲最大元素。

舉例

img

Java Code
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
public class Solution {
    public ArrayList
   
   
   

  
   
  maxInWindows(int[] num, int size) { ArrayList 
 
   
     ans = new ArrayList<>(); if (num == null || num.length == 0 || size <= 0 || size > num.length) return ans; Deque 
    
      dq = new LinkedList<>();// 存的是下標 for (int i = 0; i < size - 1; i++) {// 前size-1個 while (!dq.isEmpty() && num[dq.peekLast()] < num[i]) dq.pollLast(); dq.offerLast(i); } for (int i = size - 1; i < num.length; i++) { if (!dq.isEmpty() && i - dq.peekFirst() >= size) dq.pollFirst(); while (!dq.isEmpty() && num[dq.peekLast()] < num[i]) dq.pollLast(); dq.offerLast(i); ans.add(num[dq.peekFirst()]); } return ans; } } 
     
    

  

6七、剪繩子+++

貪心,動態規劃

題目描述

給你一根長度爲n的繩子,請把繩子剪成m段(m、n都是整數,n>1而且m>1),每段繩子的長度記爲k[0],k[1],...,k[m]。請問k[0]xk[1]x...xk[m]可能的最大乘積是多少?例如,當繩子的長度是8時,咱們把它剪成長度分別爲二、三、3的三段,此時獲得的最大乘積是18。

題意分析

方法一 貪心。儘量分紅3*3*3*..*32*3*3*...*32*2*3*...*3。由於2*2*2<3*3

方法二 動態規劃。dp[i]定義爲,長度爲i的繩子,分割或者不分割,獲得的最大乘積。

Java Code
public class Solution {
    public int cutRope(int target) {
        int[] dp = new int[60 + 5];
        dp[0] = dp[1] = 0;
        dp[2] = 1;
        dp[3] = 2;
        dp[4] = 4;
        if (target <= 4) return dp[target];
        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 3;
        for (int i = 5; i <= target; i++) {
            for (int j = 1; j <= i / 2; j++) {
                dp[i] = Math.max(dp[i], dp[j] * dp[i - j]);
            }
        }
        return dp[target];
    }
}
相關文章
相關標籤/搜索