找出数组中重复的数字
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; }