若是天天作一道算法題,那是否是天天都在進步?
這個活動是從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
};複製代碼
第二期結束,下期見。若是有一塊兒學習的朋友,能夠加微信羣。
關注公衆號「驚天碼盜」,回覆算法,拉你進羣。