天天一道算法題(第二期)

若是天天作一道算法題,那是否是天天都在進步?

前言

這個活動是從2019年7月中旬開始的,人數不算多,也就幾個好友和好友的好友,過程當中也會有人由於工做的緣故或其餘緣由放棄,或許將來還會有人離開。javascript

活動的主要形式就是在leetcode刷題,每一個工做日一道題,每週作總結,目前已是第十三期,接下來我會把每期的題作一個總結,因爲我是側重javascript,因此活動中的每道題都是以js語言來實現解題方法。java

活動的規則比較嚴謹,羣裏天天早上10點以前發題,晚上10點審覈,審覈有管理員專門審覈,稱爲打卡,沒有打卡的人,須要發紅包給管理員做爲天天統計費用。git

活動的目的就是培養算法思惟,瞭解常見的算法,好比分治算法、貪心算法、動態優化等等。算法

微信公衆號驚天碼盜同步。數組


爬樓梯

假設你正在爬樓梯。須要 n 階你才能到達樓頂。bash

每次你能夠爬 1 或 2 個臺階。你有多少種不一樣的方法能夠爬到樓頂呢?微信

注意:給定 n 是一個正整數app

示例 1:學習

輸入: 2
輸出: 2
解釋: 有兩種方法能夠爬到樓頂。
1.  1 階 + 1 階
2.  2 階複製代碼

示例 2:優化

輸入: 3
輸出: 3
解釋: 有三種方法能夠爬到樓頂
1.  1 階 + 1 階 + 1 階
2.  1 階 + 2 階
3.  2 階 + 1 階複製代碼

題解:

思路1:斐波那契數列公式法

dp[n] = dp[n-1] + dp[n-2]

執行用時:64ms;內存消耗:34.2MB;

var climbStairs = function(n) {
    const dp = [];
    dp[0] = 1;
    dp[1] = 1;
    for(let i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
};
複製代碼

思路2:數學公式法



執行用時:72ms;內存消耗:33.7MB;

var climbStairs = function(n) {
    const sqrt_5 = Math.sqrt(5);
    const fib_n = Math.pow((1 + sqrt_5) / 2, n + 1) -Math.pow((1 - sqrt_5) / 2,n + 1);
    return Math.round(fib_n / sqrt_5);
};
複製代碼

只出現一次的數字

給定一個非空整數數組,除了某個元素只出現一次之外,其他每一個元素均出現兩次。找出那個只出現了一次的元素。

說明:

你的算法應該具備線性時間複雜度。你能夠不使用額外空間來實現嗎?

示例 1:

輸入: [2,2,1]
輸出: 1
複製代碼

示例 2:

輸入: [4,1,2,1,2]
輸出: 4
複製代碼

題解:

思路1:對象計數法

用一個對象來存儲出現的次數,而後取出次數爲1的值;

執行用時:116ms;內存消耗:37.5MB;

var singleNumber = function(nums) {
    let obj={};
    let result=null;
    nums.forEach(item=>{
        if(!obj[item]){
            obj[item]=1
        }else{
            obj[item]++
        }
    })
    Object.keys(obj).forEach(item=>{
        if(obj[item]===1){
            result=item-0
        }
    })
    return result
};
複製代碼

思路2:有序互斥法

先排序,而後利用相鄰不想等來找出答案;

執行用時:156ms;內存消耗:36.9MB;

var singleNumber = function(nums) {
  nums = nums.sort();
  for (let i = 0; i < nums.length; i++) {
    if (nums[i] !== nums[i - 1] && nums[i] !== nums[i + 1])
    return nums[i];
  }
};
複製代碼

思路3:XOR法(異或法)

第一次看到這種解法,也從側面體現了對js位運算的不熟悉,XOR位運算,若是你瞭解它的含義,就不懷疑爲何能夠解決此題了,XOR位運算會先轉化爲二進制,若是兩位只有一位爲 1 則設置每位爲 1,最後轉化回來;

執行用時:72ms;內存消耗:35.8MB;

var singleNumber = function(nums) {
    let gg = function(total,num){ 
        return total ^ num;
    } 
    return nums.reduce(gg);  
}
複製代碼

思路4:左右索引法

從左邊檢索和右邊檢索,若是相等,就找到結果;

執行用時:536ms;內存消耗:35.2MB;

var singleNumber = function(nums) {
    for (let i = 0; i < nums.length; i++) {
        if (nums.lastIndexOf(nums[i]) === nums.indexOf(nums[i]))
        return nums[i];
    }
}
複製代碼

思路5:循環減法

從拷貝一個數組,定義一個變量,在循環中,動態修改數組,若是拷貝數組中存在當前值,就相減,不然相加;(這是最笨的方法)

執行用時:1228ms;內存消耗:37.6MB;

var singleNumber = function(nums) {
    let num=0;
    let list=[...nums]
    nums.forEach((item,i)=>{
      list.splice(i,1,null);
      if(list.includes(item)){
          num-=item
      }else{
          num+=item
      }  
    })
    return num
}
複製代碼

思路6:字符串分割法(正則思想)

將數組轉換成字符串,而後遍歷使用每一項的值去分割字符串,若分割後數組長度爲2則該項只出現一次。

執行用時:9460ms;內存消耗:41.9MB;

var singleNumber = function(nums) {
    const numsStr = `/${nums.join('//')}/`;
    for (let i = 0; i < nums.length; i++) {
        if (numsStr.split(`/${nums[i]}/`).length === 2) return nums[i];
    }
}
複製代碼

報數

報數序列是一個整數序列,按照其中的整數的順序進行報數,獲得下一個數。其前五項以下:

1.     1
2.     11
3.     21
4.     1211
5.     111221複製代碼

1 被讀做 "one 1" ("一個一") , 即 11

11 被讀做 "two 1s" ("兩個一"), 即 21

21 被讀做 "one 2", "one 1""一個二" , "一個一") , 即 1211

給定一個正整數 n(1 ≤ n ≤ 30),輸出報數序列的第 n 項。

注意:整數順序將表示爲一個字符串。

示例 1:

輸入: 1
輸出: "1"複製代碼

示例 2:

輸入: 4
輸出: "1211"複製代碼

題解:

思路1:對象法

用一個對象來存儲報數字符串,分兩種狀況處理,當key爲1和當key>1;當key>1的時候,獲取到key-1的字符串,轉化爲數組,在利用對象來存儲此數組中數字出現的次數,但此時會遇到一個問題,像111221這樣前面的1和後面的1不一樣,因此咱們存儲時要區分開;這裏用num作了區分,當值不相同的時候num再賦值。

ob中存的是每一個字符串解讀的對象,好比4的報數「1211」,5的報數是解讀1211的字符串,因此ob中存的是

{ 
    0:{count:1,value:1},
    1:{count:1,value:2},
    2:{count:2,value:1}
}//拼接以後就是111221
複製代碼

obj的每個key值就是n,value就是報數字符串;

執行用時:136ms;內存消耗:36.9MB;

var countAndSay = function(n) {

    let obj={}
    for(let i=1;i<=n;i++){
        obj[i]='';
        if(i==1){
           obj[i]+=1+''
        }
        if(i>1){
            let num=null;
            let ob={};
            let list=obj[i-1].split('');
            let flag=false;
            list.forEach((item,index)=>{
                if(index==0){
                    num=0;
                    ob[index]={
                        value:item,
                        count:1
                    }
                }
                if(index>0){
                    if(ob[num]&&ob[num].value===item){
                         ob[num].count&&ob[num].count++
                    }else{
                        num=index
                         ob[index]={
                             value:item,
                             count:1
                         }
                    }
                }
            })
            for(let k in ob){
                obj[i]+=ob[k].count+''+ob[k].value
            }
        }
    }
    return obj[n]
}
複製代碼

思路2:正則法

先觀察規律,顯然每一階數中任意部分的元素一次最多連續不會超過3次,因此任一階數的組成元素最多隻有一、二、3。因此我直接使用正則/(1+)|(2+)|(3+)/g來匹配字符串便可。

執行用時:80ms;內存消耗:34.8MB;

var countAndSay = function(n) {
   let str = '1';
  for (let i = 0; i < n - 1; i++) {
    str = str.match(/(1+)|(2+)|(3+)/g).reduce(
        (pre, cur) => pre + cur.length + cur[0]
    , '');
  }
  return str;
};
複製代碼

思路3:遞歸法

先從1到n這個過程當中,經過數組來存儲當前結果,因此咱們的結構能夠是這樣fn(index,['...'],n),而後當index==n的時候設立遞歸終止條件;

執行用時:124ms;內存消耗:35.1MB;

var countAndSay = function(n) {

    const createStr = function (index, str, n) {

        //終止條件:查詢到了第n個數了,當即返回,不然index+1

        if(index == n) return str.join('') ;

        index++

        let newChar = []

        let k = 1//保存該數存在次數:當查詢到不等的時候,在下方重置k

        for(let j = 0; j < str.length; j++) {

            let char = str[j]

            if(char == str[j+1] && j != str.length - 1) {

                //不等,且遍歷沒到底,那就繼續尋找

                   k++

            }else {

                newChar.push(k)

                newChar.push(str[j])

                k=1

            }

        }

        return createStr(index, newChar, n)

    }   

   return createStr(1, ['1'], n)

};
複製代碼

最後一個單詞的長度

給定一個僅包含大小寫字母和空格 ' ' 的字符串,返回其最後一個單詞的長度。

若是不存在最後一個單詞,請返回 0 。

說明:一個單詞是指由字母組成,但不包含任何空格的字符串。

示例:

輸入: "Hello World"
輸出: 5
複製代碼

題解:

思路1:字符串分割法

分割成數組,而後取最後一個;

執行用時:68ms;內存消耗:33.7MB;

var lengthOfLastWord = function(s) {
    let list=s.trim().split(' ')
    return list[list.length-1].length
}
複製代碼

固然你也能夠用正則;

執行用時:76ms;內存消耗:33.5MB;

var lengthOfLastWord = function(s) {
     s = s.replace(/(\s*$)/g, "")
    let arr = s.split(' ')
    return arr[arr.length -1].length
}
複製代碼

各位相加

給定一個非負整數 num,反覆將各個位上的數字相加,直到結果爲一位數。

示例:

輸入: 38
輸出: 2
解釋: 各位相加的過程爲:3 + 8 = 11, 1 + 1 = 2。因爲 2 是一位數,因此返回 2。
複製代碼

題解:

思路1:遞歸法

分割成字符串數組,而後遞歸,當小於10,返回結果,大於等於則遞歸;

執行用時:104ms;內存消耗:36MB;

var addDigits = function(num) {
    let val=0;
    let getGe=(n)=>{
        const list=(n+'').split('');
        let res=0;
        list.forEach(item=>{
            res+=item-0
        })
        if(res>=10){
            getGe(res)
        }
        if(res<10){
            val= res
        }
    }
    getGe(num)
    return  val
}
複製代碼

思路2:條件法

有循環就有條件,看見for就想到while;

執行用時:100ms;內存消耗:36.2MB;

var addDigits = function(num) {
    let Digit=num;
    while(Digit>=10){
        Digit=Digit+'';
        let list=[...Digit];
        Digit=list.reduce((a,b)=>(a-0)+(b-0))
          }
    return  Digit
}
複製代碼

思路3:模除法

有以下關係:num = a * 10000 + b * 1000 + c * 100 + d * 10 + e

即:num = (a + b + c + d + e) + (a * 9999 + b * 999 + c * 99 + d * 9)

這道題最後的目標,就是不斷將各位相加,相加到最後,當結果小於10時返回。由於最後結果在1-9之間,獲得9以後將不會再對各位進行相加,所以不會出現結果爲0的狀況。由於 (x + y) % z = (x % z + y % z) % z,又由於 x % z % z = x % z,所以結果爲 (num - 1) % 9 + 1,只模除9一次,並將模除後的結果加一返回。北風其涼 --OSCHINA
可是有1個關鍵的問題,若是num是9的倍數,那麼就不適用上述邏輯。本來我是想獲得n被打包成10個1份的份數+打不進10個1份的散落個數的和。經過與9取模,去得到那個不能整除的1,做爲計算份數的方式,可是若是能夠被9整除,我就沒法獲得那個1,也得不到個位上的數。


能夠這麼作的緣由:本來能夠被完美分紅9個爲一份的n樣物品,我故意去掉一個,那麼就又能夠回到上述邏輯中去獲得我要的n被打包成10個一份的份數+打不進10個一份的散落個數的和。而這個減去的1就至關於從,在10個1份打包的時候散落的個數中借走的,原本就不影響原來10個1份打包的份數,先拿走再放回來,都隻影響散落的個數,因此沒有關係。liveforexperience --力扣(LeetCode)

這是一種數學思惟,也是算法中常見的思惟方式。

執行用時:100ms;內存消耗:35.4MB;

var addDigits = function(num) {
    return (num-1)%9+1
};複製代碼

第二期結束,下期見。若是有一塊兒學習的朋友,能夠加微信羣。

關注公衆號「驚天碼盜」,回覆算法,拉你進羣。

相關文章
相關標籤/搜索