斐波那契数列
斐波那契数列的标准公式为:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)
爬楼梯问题
假设你正在爬楼梯,你需要爬n级才能到达楼顶,每次你可以爬1或两个台阶,你有多少种方法可以爬到楼顶?
爬到第一级台阶有1种:1
爬到第二级台阶有2种:(1,1)、2
爬到第三级台阶有3种:(1,1,1)、(1,2)、(2,1)
爬到第四级台阶有5种:(1*5),(1*2,2),(2,1*2),(1,2,1),(2,2) ...
综上:从第三级楼梯开始,f(n)=f(n-1)+f(n-2)
使用递归实现
function climbStairs(n){
if(n===1||n===2){
return n;
}else{
return climbStairs(n-1)+climbStairs(n-2)
}
}
缺点:此算法速度太慢,递归造成大量的重复子问题,比如走10阶台阶,递归立马要去计算怎么走到第9阶和第8阶台阶,然后一层层下去,中间造成大量浪费。
改进:将每一阶的走法存下来
function climbStairs(n){
let arr=[0,1,2]; //0只是来占位
for(let i=3;i<=n;i++){
arr[i]=arr[i-1]+arr[i-2]
}
return arr[n]; //返回数组的最后一个元素,即是总方法
}
查找值
二分查找
二分查找,是在一个有序序列中查找某一个值,每次从中间开始猜,分界设在开头和结尾的值。如果目标值比中间值大了,则移动开头去指向中间后面的值,否则移动结尾去指向中间前面的值,然后继续这个范围的中间值猜。
递归写法
function binaryFind(arr,target,low=0,high=arr.length-1){
let mid=Math.floor((low+high)/2);
if(target>arr[mid]){
binaryFind(arr,target,mid+1,high)
}
else if(target<arr[mid]){
binaryFind(arr,target,low,mid-1);
}else{
return `找到目标值,在第${mid+1}个`
}
return -1;
}
使用循环
function binaryFind(arr,target){
let low=0,high=arr.length-1;
let mid;
while(low<high){
mid=Math.floor((low+high)/2);
if(arr[mid]===target){
return `找到目标值,在第${mid+1}个`
}
else if(arr[mid]>target){
high=mid-1;
}
else{
low=mid+1
}
}
return -1;
}
字符串与数组的结合
判断字符串 a 是否被包含在字符串 b 中,并返回第一次出现的位置(找不到返回 -1)。
function f2(a,b) {
let char_=a.charAt(0);
let index=b.indexOf(char_);
if(index!==-1){
while (index<b.length){
let substr=b.substr(index,a.length);
if(substr===a){
return index;
}
index=index+a.length
}
}
return -1
}
console.log(f2('abc', 'aasabc'));//3
实现千位分隔符
比如1234567 -- 1,234,567
可以使用数组的join函数,传入‘,’。
1)首先将数字转为字符串
2)每三位每三位的分隔,作为数组的一个元素
3)但是要先将不足三位的放在数组的第一个元素(如果有的话)
function f3(number) {
let str=number.toString();
let subindex=str.length%3;
let substr=str.substr(0,subindex);
console.log(substr);//如果subindex是0,则说明number的个数是3的倍数,此时返回的字符串是空字符串
let arr=[];
if(substr){ //有东西才加入数组
arr.push(substr);
}
while (subindex<str.length){
let s=str.substr(subindex,3);
arr.push(s);
subindex+=3;
}
str=arr.join(",")
return str;
}
console.log(f3(123456));
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回-1(需要区分大小写)
思路一
用一个map来存储每个字符出现的次数
function fn(str){
if(!str){
return -1
}
let arr=str.split('');
let map={};
for(var i=0;i<arr.length;i++){
if(map[arr[i]]){
map[arr[i]]+=1
}else{
map[arr[i]]=1
}
}
for(var item in map){
if(map[item]==1){
return arr.indexOf(item);
}
}
return -1;
}
思路二
使用数组的indexOf方法和lastIndexOf方法。判断该字符在这两个方法下是否返回相同的值。
(lastIndexof是查找某个字符最后一次出现的位置。)
function FirstNotRepeatingChar(str) {
// write code here
for (var i = 0; i < str.length; i++) {
if (str.indexOf(str[i]) == str.lastIndexOf(str[i])) {
return i;
}
}
return -1;
}
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
- 遍历数组,每找到一个奇数,就向前找其他的奇数
- 将遇到的偶数都向后推,推到遇到其他奇数
- 此时奇数后面的位置就是此时遍历到的奇数将要插入的位置
function f2(arr){
for(let i=1;i<arr.length;i++){
if(arr[i]%2===1){
let t=arr[i];
let j=i-1;
while (j>=0&&arr[j]%2===0){ //查找到第一个数或为奇数的数则退出循环
arr[j+1]=arr[j] //将偶数向后移
j--;
}
arr[j+1]=t; //此时的j指向的是-1或arr[i]前面的第一个奇数
}
}
return arr;
}
在一个数组中找出两个数的和为目标值,要得到两个数在数组里的下标
// 遍历数组,如果当前元素在目标对象中,则将这两个数字取出并添加到最终结果中
// 如果不存在,则将当前元素添加到目标对象中
function twoSum(arr, target) {
var obj = {},
res = {},
index = 0;
for(var i = 0, len = arr.length; i < len; i++) {
var dif = target - arr[i];
if(dif in obj) {
res[index] = [];
res[index].push(obj[dif]);
res[index].push(i);
// 这里要记得从对象中删除
delete obj[dif];
index++;
} else {
obj[arr[i]] = i;
}
}
console.log(res);
};
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。
注意:
- num1 和num2 的长度都小于 5100.
- num1 和num2 都只包含数字 0-9.
- num1 和num2 都不包含任何前导零。 -你不能使用任何內建 BigInteger 库,也不能直接将输入的字符串转换为整数形式。
从末位开始计算,如果超过10记住要进1
function fn(num1,num2){
let arr1=[...num1];
let arr2=[...num2];
let jin=0;
let res=[];
while(arr1.length>0||arr.length>0){
let add1=arr1.pop()||0;
let add2=+arr2.pop()||0
res.unshift((add1+add2+jin)%10);
jin=(add1+add2+jin>=10? 1:0)
}
if(jin===1){
res.unshift(1)
}
return res.join("")
}
二维数组
1、在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
- 从左下角的数开始判断,向上递减,向右递增
- 如果target<这个数,则向上查找,若大于,则向右查找
- 为什么要找左下角呢?因为这个数的两个方向是一个分水岭,而第一个数两个方向的数都比它大,不好判断
function find(target,array){
let row=array.length-1;
let col=0;
while (row>=0&&row<array.length&&col>=0&&col<array.length){
if(target===array[row][col]){
return true;
}else if(target<array[row][col]){
row--;
}
else{
col++;
}
}
}
还有一种思路就是一个个找,使用数组的some函数,但是效率没有第一个算法高
function Find(target, array)
{
return array.some(arr=>arr.some(i=>target===i))
}
螺旋式内向输出5 * 5 的二维数组
function f1(num) {
let res={}, //存放结果
changeNum=5, //走几步换方向
changeFlag=1, //换方向的标志
xy={ //此时的坐标
x:0,y:0
},
directionIndex=-1, //0:向下走,1:左,2:上,3:右
direction={
'0':function (res,xy,num) {
xy.x++;
res[xy.x][xy.y]=num
},
'1':function (res,xy,num) {
xy.y--;
res[xy.x][xy.y]=num
},
'2':function (res,xy,num) {
xy.x--;
res[xy.x][xy.y]=num
},
'3':function (res,xy,num) {
xy.y++;
res[xy.x][xy.y]=num
},
};
//初始化数组
for (let j=0;j<5;j++){
res[j]=new Array(5);
}
let i=0,currentCount=0;
for (i=0;i<5;i++){
res[xy.x][xy.y++]=num+i;
currentCount++;
}
//此时坐标向右走多了一步,退后
xy.y--;
for (let k=i+num;k<=25;k++){
if(currentCount===changeNum){ //在一个方向上走的步数达到换方向的数目
if(changeFlag){
changeFlag=0;
changeNum--; //转变两个方向之后,转方向的条件就减1
}else{
changeFlag=1
}
//换方向
directionIndex=(directionIndex+1)%4;
currentCount=0; //重新计数在这个方向走的步数
}
direction[directionIndex](res,xy,k); //计算坐标
currentCount++;
}
for (let item in res){
console.log(res[item].join(' '))
}
}
f1(1)
实现n阶矩阵(上面题目的升级,5阶就是上面的输出结果)
function f(n) {
let res=[], //结果
changeCount=n,
flag=1,
direction=-1,
xy={
x:0,y:0
},
location={
//向下移动
0:function (xy,res,step) {
xy.x++;
res[xy.x][xy.y]=step;
},
//向左移动
1:function (xy,res,step) {
xy.y--;
res[xy.x][xy.y]=step;
},
//向上移动
2:function (xy,res,step) {
xy.x--;
res[xy.x][xy.y]=step;
},
//向右移动
3:function (xy,res,step) {
xy.y++;
res[xy.x][xy.y]=step;
}
};
//初始化数组
for(let i=0;i<n;i++){
res[i]=[];
}
let count=0;
//往第一行填充数据
for(let i=0;i<n;i++){
res[xy.x][xy.y++]=i+1;
count++;
}
//退出循环后,此时坐标超出一列,减回去
xy.y--;
for(let k=count+1;k<=n*n;k++){
if(count===changeCount){
if(flag){ //变换两个方向后,要减少在一个方向上移动的次数
changeCount--;
flag=0;
}else{
flag=1;
}
count=0;
direction=(direction+1)%4;
}
location[direction](xy,res,k);
count++;
}
for(let j=0;j<n;j++){
console.log(res[j].join(' '));
}
}
f(3)
链表
输入一个链表,输出该链表中倒数第k个结点。
- 再设置一个指针,last,初始指向head
- last指针先移动,只要有后一个节点则移动
- 移动k-1位,加上head节点则算k个节点,相当于一把长度为k的尺子
- 如果在移动的过程中还未移动k-1位就无节点的话,说明k太大,返回空
- 获取到尺子之后,将尺子向后移,则head和last一起向后移
- 当last移动到最后一个节点时,此时head节点就是指向倒数第k个节点
function FindKthToTail(head, k)
{
if(!head||k<=0)
return null;
//let last=head.next;
let last=head; //设置两个指针
while(k>1){ //last指针向后走了k-1步,然后加上head节点则相当于一把长度为k的尺
if(last.next){
last=last.next;
k--;
}else{
return null; //如果还没走到k-1步就没有节点,说明k太大,超过链表的长度
}
}
//生成一把k的尺子,然后两个指针一起向后移,等last指针移动到最后一个节点时,head节点则为倒数第k个节点
while(last.next){
head=head.next;
last=last.next;
}
return head;
}
树
2、输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
解题思路:
- 前序是根-左-右,中序是左-根-右,后序是左-右-根。
- 所有前序遍历的第一个是根,中序遍历中间索引是根,索引前后为左右子树
- 使用递归算法,每次将左右两棵子树当作新的子树进行处理,按前两步的思路再分成左右子树。方法每次都返回左右子树的根节点
function reConstructBinaryTree(pre, vin)
{
// write code here
let node=reConstructBinaryTree(pre,0,pre.length-1,vin,0,vin.length-1);
return node;
//这个函数不能写在上面那个函数的外面,不然会出错
//startIndex_p:此新子树前序遍历的第一个索引,endIndex_p:最后一个索引
//startIndex_v:此新子树中序遍历的第一个索引,endIndex_v:最后一个索引
function reConstructBinaryTree(pre,startIndex_p,endIndex_p,vin,startIndex_v,endIndex_v){
if(startIndex_p>endIndex_p||startIndex_v>endIndex_v){
return null;
}
let root=pre[startIndex_p]; //每棵子树的根的值
let node=new TreeNode(root);
let index;
for(let i=startIndex_v;i<=endIndex_v;i++){ //在中序遍历里,在传入的左右界限里找到此子树的根的索引
if(vin[i]===root){
index=i;
break;
}
}
//左子树
node.left=reConstructBinaryTree(pre,startIndex_p+1,startIndex_p+index-startIndex_v, vin,startIndex_v,index-1);
//右子树
node.right=reConstructBinaryTree(pre,startIndex_p+index-startIndex_v+1,endIndex_p, vin,index+1,endIndex_v);
return node;
}
}