找出數組中重複的數字
n個數字,且數字都在0到n-1範圍內
思路:從頭至尾掃描數組每一個數字,當掃描到下標爲i的數字m時,首先比較m是否是等於i,若是是,繼續掃描;若是不是,再拿m和第m個數字進行比較。若是他們相等,就找到第一個重複數字,若是不相等,交換二者位置。接下來重複上述過程,直到找到第一個重複數字。javascript
function Find(arrNumbers) { for (var i = 0; i < arrNumbers.length; i++) { while(arrNumbers[i]!==i) { if(arrNumbers[i] === arrNumbers[arrNumbers[i]]) { return arrNumbers[i]; } let temp = arrNumbers[i]; arrNumbers[i] = arrNumbers[temp]; arrNumbers[temp] = temp; } } } let arr = [2,3,1,0,2,5,3]; console.log(Find(arr));
代碼中儘管有一個兩重循環,可是每一個數字最多隻要交換兩次就可以找到屬於它本身的位置,所以總的時間複雜度是O(n)。另外,全部的操做步驟都是在輸入數組上進行的,不須要額外分配內存,所以空間複雜度爲O(1)。html
不修改數組找出重複的數字(二分查找)
在一個長度爲n+1的數組裏的全部數字都在1~n的範圍內,因此數組中至少有一個數字是重複的。請找出數組中任意一個重複的數字,可是不能修改輸入的數組。例如,若是輸入長度爲8的數組{2,3,5,4,3,2,6,7},那麼對應的輸出是重複的數字2或者3。java
思路1:
因爲不能修改輸入的數組,咱們能夠建立一個長度爲n+1的輔助數組,而後逐一把原數組的每一個數字複製到輔助數組。若是原數組中被複制的數字是m,則把它複製到輔助數組中下標爲m的位置。若是下標爲m的位置上已經有數字了,則說明該數字重複了。因爲使用了輔助空間,故該方案的空間複雜度是O(n)。jquery
思路2:
因爲思路1的空間複雜度是O(n),所以咱們須要想辦法避免使用輔助空間。咱們能夠想:若是數組中有重複的數,那麼n+1個0~n範圍內的數中,必定有幾個數的個數大於1。那麼,咱們能夠利用這個思路解決該問題。正則表達式
咱們把從1~n的數字從中間的數字m分爲兩部分,前面一半爲1~m,後面一半爲m+1~n。若是1~m的數字的數目等於m,則不能直接判斷這一半區間是否包含重複的數字,反之,若是大於m,那麼這一半的區間必定包含重複的數字;若是小於m,另外一半m+1~n的區間裏必定包含重複的數字。接下來,咱們能夠繼續把包含重複的數字的區間一分爲二,直到找到一個重複的數字。算法
因爲若是1~m的數字的數目等於m,則不能直接判斷這一半區間是否包含重複的數字,咱們能夠逐步減小m,而後判斷1~m之間是否有重複的數,即,咱們能夠令m=m-1,而後再計算1~m的數字的數目是否等於m,若是等於m,再令m=m-1,若是大於m,則說明1~m的區間有重複的數,若是小於m,則說明m+1~n有重複的數,不斷重複此過程。api
function Find(arrNumbers) { let start = 1; let end = arrNumbers.length - 1; while(end >= start) { let middle = parseInt((end - start)/2) + start; let count = countRange(arrNumbers,start,middle); if(end == start) { if(count > 1) { return start; } else { break; } } if(count > (middle - start + 1)) { end = middle; } else { start = middle + 1; } } return -1; } function countRange(arrNumbers,start,end) { let count = 0; for (var i = 0; i < arrNumbers.length; i++) { if(arrNumbers[i] >=start && arrNumbers[i] <= end) { count++; } } return count; } let arr = [2,3,5,4,3,2,6,7]; console.log(Find(arr));
上述代碼按照二分查找的思路,若是輸入長度爲n的數組,那麼函數countRange最多將被調用O(logn)次,每次須要O(n)的時間,所以總的時間複雜度是O(nlogn)。和前面提到的須要o(n)的輔助空間的算法比,這種算法至關於以時間換空間。數組
二維數組中的查找
在一個二維數組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。app
function Find(num,arr) { let found = false; let row = 0; let col = arr[0].length - 1; while(row < arr.length && col >= 0){ if(arr[row][col] == num) { found = true; break; } else if(arr[row][col] > num) { col--; } else { row ++; } } return found; } let arr = [ [1,2,8,9], [2,4,9,12], [4,7,10,13], [6,8,11,15] ]; console.log(Find(7,arr));
替換空格
請實現一個函數,將一個字符串中的每一個空格替換成「%20」。例如,當字符串爲We Are Happy。則通過替換以後的字符串爲We%20Are%20Happy。函數
一、直接用空格將字符串切割成數組,再用20%進行鏈接。
function replaceSpace(str) { return str.split(' ').join('%20'); }
2.用正則表達式找到全部空格依次替換
function replaceSpace(str) { return str.replace(/\s/g,'%20'); }
3.由於javascript提供了很是便利的api方便咱們解決這個問題,可是,若是是用C++來解決呢?
思路:前提是咱們要在原數組上面操做。假設這個數組後面還有足夠的空間。若是從前日後遍歷數組,遇到一個空格就把剩下的元素所有後移2位,這樣的時間複雜度是O(n^2),顯然不可取。再換一個思路,先遍歷一次字符串,統計出字符串中空格的總數,並能夠由此計算出替換以後的字符串的總長度。每替換一個空格,長度增長2,所以替換之後字符串的長度等於原來的長度加上2乘以空格數目。而後我咱們從後向前替換。時間複雜度爲O(n)。
class Solution { public: void replaceSpace(char *str,int length) { int i,j,sum=0,tmp_len; for(i=0;i<length;i++) { if(str[i]==' ') { sum++; } } tmp_len = length+2*sum-1; j=length-1; while(j>=0) { if(str[j]==' ') { j--; str[tmp_len--]='0'; str[tmp_len--]='2'; str[tmp_len--]='%'; } else { str[tmp_len--]=str[j--]; } } } };
表示數值的字符串
請實現一個函數用來判斷字符串是否表示數值(包括整數和小數)。 例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示數值。 可是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。
一、解法一:正則表達式
/^[+-]?[0-9]*(\.[0-9]*)?([eE][+-]?[0-9]+)?$/.test("12e+4.3"); // false
附上正則表達式手冊:
http://tool.oschina.net/uploa...
二、解法二:考慮各類狀況,遍歷字符串每一個字符來判斷。
考慮徹底全部狀況
1.只能出現數字、符號位、小數點、指數位
2.小數點,指數符號只能出現一次、且不能出如今開頭結尾
3.指數位出現後,小數點不容許在出現
4.符號位只能出如今開頭和指數位後面
function isNumeric(s) { if (s == undefined) { return false; } let hasPoint = false; let hasExp = false; for (let i = 0; i < s.length; i++) { const target = s[i]; if (target >= 0 && target <= 9) { continue; } else if (target === 'e' || target === 'E') { if (hasExp || i === 0 || i === s.length - 1) { return false; } else { hasExp = true; continue; } } else if (target === '.') { if (hasPoint || hasExp || i === 0 || i === s.length - 1) { return false; } else { hasPoint = true; continue; } } else if (target === '-' || target === '+') { if (i === 0 || s[i - 1] === 'e' || s[i - 1] === 'E') { continue; } else { return false; } } else { return false; } } return true; }
字符流中第一個不重複的字符
請實現一個函數用來找出字符流中第一個只出現一次的字符。例如,當從字符流中只讀出前兩個字符"go"時,第一個只出現一次的字符是"g"。 當從該字符流中讀出前六個字符「google"時,第一個只出現一次的字符是"l"。
若是當前字符流沒有存在出現一次的字符,返回#字符。
一、解法一:用Map存儲
要求得到第一個只出現一次的。
使用一個有序的存儲結構爲每一個字符計數,再遍歷這個對象,第一個出現次數爲1的即爲結果。在JavaScript中有序存儲空間選擇Map,用迭代器遍歷。
注:不能使用Object,由於Object的key是無序的。
二、解法二:借用C++作法,用數組+ASCII碼做爲下標
源源不斷的有字母放到字符串中,創建一個256個大小的int型數組來表明哈希表,輸入的字母做爲數組下標,若是字母出現就將哈希表中該下標的值加一。要找第一個只出現一次的字符,就遍歷字符串,字符串中的字符做爲數組下標,哈希數組值爲1對應的字符就是要找的字符。
let hashTable = new Array(256).fill(0); //創建哈希數組 let s = ''; function Insert(ch) { const code = ch.charCodeAt(0); if(code > 256) return; s = s + ch; //字符放入字符串中 hashTable[code]++; //根據字符,修改哈希數組元素的值 } function FirstAppearingOnce() { for(let i = 0;i<s.length;i++){ //注意的是,要找第一個出現一次的字符,因此遍歷字符串,不能遍歷哈希數組 const code = s[i].charCodeAt(0); if(hashTable[code] == 1) //若是字符串做爲下標的元素值爲1,就說明該字符出現一次,直接返回該字符 return s[i]; } return '#'; }
字符串的全排列
輸入一個字符串,按字典序打印出該字符串中字符的全部排列。例如輸入字符串abc,則打印出由字符a,b,c所能排列出來的全部字符串abc,acb,bac,bca,cab和cba。
思路:回溯法
function swap(arr,i,j) { let temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } function Permutation(str) { var result = []; if (!str) { return result; } var array = str.split(''); permutate(array, 0, result); result.sort(); return result; } function permutate(array, index, result) { if (array.length - 1 === index) { result.push(array.join('')); } for (let i = index; i < array.length; i++) { swap(array, index, i); permutate(array, index + 1, result); swap(array, i, index); } }
正則表達式匹配
請實現一個函數用來匹配包括'.'和''的正則表達式。模式中的字符'.'表示任意一個字符,而''表示它前面的字符能夠出現任意次(包含0次)。 在本題中,匹配是指字符串的全部字符匹配整個模式。例如,字符串"aaa"與模式"a.a"和"abaca"匹配,可是與"aa.a"和"ab*a"均不匹配。
思路:
當模式中的第二個字符不是「*」時:
一、若是字符串第一個字符和模式中的第一個字符相匹配,那麼字符串和模式都後移一個字符,而後匹配剩餘的。
二、若是 字符串第一個字符和模式中的第一個字符相不匹配,直接返回false。
而當模式中的第二個字符是「*」時:
若是字符串第一個字符跟模式第一個字符不匹配,則模式後移2個字符,繼續匹配。
若是字符串第一個字符跟模式第一個字符匹配,能夠有3種匹配方式:
一、模式後移2字符,至關於x*被忽略;
二、字符串後移1字符,模式後移2字符;
三、字符串後移1字符,模式不變,即繼續匹配字符下一位,由於*能夠匹配多位;
function match(s, pattern) { if (s == undefined || pattern == undefined) { return false; } return matchStr(s, pattern, 0, 0); } function matchStr(s, pattern, sIndex, patternIndex) { if (sIndex === s.length && patternIndex === pattern.length) { return true; } if (sIndex !== s.length && patternIndex === pattern.length) { return false; } if (patternIndex + 1 < pattern.length && pattern[patternIndex + 1] === '*') { if (sIndex < s.length && (s[sIndex] === pattern[patternIndex] || pattern[patternIndex] === '.')) { return matchStr(s, pattern, sIndex, patternIndex + 2) || matchStr(s, pattern, sIndex + 1, patternIndex + 2) || matchStr(s, pattern, sIndex + 1, patternIndex); } else { return matchStr(s, pattern, sIndex, patternIndex + 2) } } if (sIndex < s.length && (s[sIndex] === pattern[patternIndex] || pattern[patternIndex] === '.')) { return matchStr(s, pattern, sIndex + 1, patternIndex + 1) } return false; }
翻轉字符串
輸入一個英文句子,翻轉句子中單詞的順序,但單詞內字符的順序不變。爲簡單起見,標點符號和普通字母同樣處理。例如輸入字符串"I am a student.",則輸出"student. a am I"。
解法一:直接調用數組API進行翻轉
function ReverseSentence(str) { if(!str){return ''} return str.split(' ').reverse().join(' '); }
解法二:兩次翻轉單詞順序
第一步將整個字符串翻轉,"I am a student." -> ".tneduts a ma I"
第二步將字符串內的單個字符串進行翻轉:".tneduts a ma I" -> "student. a am I"
左旋轉字符串
字符串的左旋轉操做是把字符串前面的若干個字符轉移到字符串的尾部。請定義一個函數實現字符串左旋轉操做的功能。好比輸入字符串"abcdefg"和數字2,該函數將返回左旋轉2位獲得的結果"cdefgab"。
解法一:拼接兩個str
function LeftRotateString(str, n) { if(str&&n!=null){ return (str+str).substr(n,str.length) }else{ return '' } }
解法二:和上題思路同樣,翻轉單詞順序
以"abcdefg"爲例,將字符串分爲兩部分ab和cdefg
將兩部分分別進行翻轉,獲得 -> "bagfedc"
再將整個字符串進行翻轉,獲得 -> "cdefgab"
調整數組順序使奇數位於偶數前面
輸入一個整數數組,實現一個函數來調整該數組中數字的順序,使得全部的奇數位於數組的前半部分,全部的偶數位於數組的後半部分
思路:
設定兩個指針
第一個指針left從數組第一個元素出發,向尾部前進
第二個指針right從數組的最後一個元素出發,向頭部前進
left遍歷到偶數,right遍歷到奇數時,交換兩個數的位置
當left>right時,完成交換
function reOrderArray(array) { if (Array.isArray(array)) { let left = 0; let right = array.length - 1; while(left < right) { while(array[left] % 2 == 1){ left ++; } while(array[right] % 2 == 0) { right --; } if(left < right) { [array[left],array[right]] = [array[right],array[left]]; } } } return array; }
若是要保證奇數和奇數,偶數和偶數之間的相對位置不變。
思路:
首先尋找第一個奇數,並將其放在0號位置。而後將第一個奇數以前的元素所有日後移一位。
依次在第一個奇數以後的元素中尋找奇數,並作移動操做。就能夠保證原來的相對順序。
function reOrderArray2(array) { if (Array.isArray(array)) { let left = 0; let right = 0; while(right < array.length) { // 找到第一個奇數 while(array[right] % 2 == 0 && right < array.length) { right ++; } if(right != left && right < array.length) { let temp = array[right]; for (let i = right; i > left; i--) { array[i] = array[i-1]; } array[left] = temp; } left ++; right ++; } } return array; }
和爲S的兩個數字
輸入一個遞增排序的數組和一個數字S,在數組中查找兩個數,使得他們的和正好是S,若是有多對數字的和等於S,輸出兩個數的乘積最小的。
思路:
頭尾相加是最快的方法,第一組遇到的和sum相等的值就必定是乘積最小的(和相同,差越大,乘積就越小),大了就大值往前移動,小了就小值日後移動
function reOrderArray(array) { if (Array.isArray(array)) { let start = 0; let end = array.length - 1; while (start < end) { while (array[start] % 2 === 1) { start++; } while (array[end] % 2 === 0) { end--; } if (start < end) { [array[start], array[end]] = [array[end], array[start]] } } } return array; }
和爲S的連續正整數序列
輸入一個正數S
,打印出全部和爲S的連續正數序列。
例如:輸入15
,有序1+2+3+4+5
=4+5+6
=7+8
=15
因此打印出3個連續序列1-5
,5-6
和7-8
思路:
建立一個容器child
,用於表示當前的子序列,初始元素爲1,2
記錄子序列的開頭元素small
和末尾元素big
big
向右移動子序列末尾增長一個數small
向右移動子序列開頭減小一個數
當子序列的和大於目標值,small
向右移動,子序列的和小於目標值,big
向右移動
function FindContinuousSequence(sum) { const result = []; const child = [1, 2]; let big = 2; let small = 1; let currentSum = 3; while (big < sum) { while (currentSum < sum && big < sum) { child.push(++big); currentSum += big; } while (currentSum > sum && small < big) { child.shift(); currentSum -= small++; } if (currentSum === sum && child.length > 1) { result.push(child.slice()); child.push(++big); currentSum += big; } } return result; }