4、数据结构与算法

文章目录

好未来数据结构与算法题

1、快排的时间复杂度是多少?怎么计算的?空间复杂度是多少?怎么计算的?手写冒泡和快排?

时间复杂度:指的是算法完成需要的时间
空间复杂度:指的是算法完成需要的存储空间
冒泡排序的时间复杂度是O(n^2); 空间复杂度是O(1)
快速排序的时间复杂度是O(nlg(n));空间复杂度是O(n),这里的n相当于压栈的次数
快速排序的时间复杂度计算
快速排序采用的是分区排序的方式,假设数据个数为n,每次分区的比例为1:9,那么每次遍历的数据个数均为n。用递归树表示,现在要求的就是递归树的高度
在这里插入图片描述
因为对数的复杂度,不管底数是多少,均为底数为10,所以递归树深度为 lg(n),再乘以每层需要遍历的数据个数,所以为O(n
lg(n))。
1、冒泡排序
原理:
从第一个元素开始,相邻两个元素亮亮进行比较,较大者往后放,这样第一轮下来,就把最大的数放在了最后边;然后表示第二个元素的两两比较,还是较大者往后放,这样下来,倒数第二大的元素就被放在了倒数第二个位置上。
时间复杂度
比较次数:n-1 + n-2 + …+1 = n(n-1)/2,所以时间复杂度是O(n^2)
代码实现

 function bubbleSort(arr) {
            var length = arr.length
            for(var i = 0; i < length - 1; i++) {
                for(var j = 0; j < length - 1 - i; j++) {
                    if(arr[j] > arr[j+1]) {
                        var temp = arr[j]
                        arr[j] = arr[j+1]
                        arr[j+1] = temp
                    }
                }
            }
            return arr
        }
console.log(bubbleSort([7,6,5,4,3,2,1,8])); //[1,2,3,4,5,6,7,8]

2、快速排序

<script>
       //快速排序 
       //1、找枢纽 用中位数 首先用索引值为 left right (left + right) / 2 进行排序,结束之后,最大的数,就放在最后边,然后把arr[center] 和 arr[right - 1] 交换位置,第二大的数也就是枢纽,被放在了arr[right - 1]的位置
       //2、之后给两个指针,左指针从left开始,找比pivot大的数;右指针从right开始,找比枢纽小的数,两个都找到的话,就交换arr[i]和arr[j];当left = right的时候,就把arr[i]和arr[right](也就是枢纽所在的位置交换)最后返回该枢纽pivot
       //3、对给定的数组,left和right进行快排,先把枢纽找到并放入正确的位置,然后在对枢纽左边的和右边的进行递归快排
       var arr = [85,8,88,77,77,84,74,7,74,4,2,5,85,97,48]
       console.log(quickSort(arr, 0, arr.length - 1))


       function Pivot(arr,left,right) {
           var center = Math.floor((left + right) / 2)
           if(arr[left] > arr[center]) {
               [arr[left], arr[center]] = [arr[center], arr[left]]
           }
           if(arr[center] > arr[right]) {
               [arr[center], arr[right]] = [arr[right], arr[center]]
           }
           if(arr[left] > arr[right]) {
               [arr[left], arr[right]] = [arr[right], arr[left]]
           }
           [arr[center],arr[right - 1]] = [arr[right - 1], arr[center]]
           return arr[right - 1]
       }
       function pivotChange(arr, left, right) {
           var i = left
           var j = right - 2
           var pivot = Pivot(arr,left,right) //找到枢纽值
           
            while(i < j) {
                while(i < j && arr[j] >= pivot) {
                   j--
                }
                while(i < j && arr[i] <= pivot) {
                   i++
                }
                if(i !== j) {
                    [arr[i],arr[j]] = [arr[j], arr[i]]
                }
            }
            //这里注意 应该是 交换 i + 1和原先的枢纽值
           [arr[i + 1], arr[right - 1]] = [arr[right - 1], arr[i + 1]]
           return i + 1
       }
       function quickSort(arr, left, right) {
           if(left < right) {
              var i =  pivotChange(arr, left, right)
              quickSort(arr, left, i - 1)
              quickSort(arr, i + 1, right)
           }
           return arr
       }
</script>

2、常见的数据结构和应用场景

1、栈:先进后出,比如课本的发放。
2、队列:先进先出,比如排队看电影
3、链表:用于存储数据,每个数据存储在一个节点中,该节点包括数据值和next(用于指向下一个节点)。
链表和数组的区别:当该组数据需要改/查的时候用数组,当该组数据需要增/删的时候用链表。数组定义前必须指明数组长度并且是开辟连续的内存空间。链表是从前往后找。
双向链表:有prev item next组成,pre指向前一个节点,next指向下一个节点,item用于存放本身的 数据。
4、哈希表:解决了链表和数组存在的问题,对元素的增删改查极快,但是哈希表的数据是无序的。用于公司员工信息的存储。
5、树:生活实例,比如家庭成员图。因为哈希表是无序排放的,不能按照固定的顺序去访问一组数据,所以用树来解决,树的缺点是效率没有哈希表高。经常使用的是二叉搜索树,节点的左子树小于节点,节点的右子树大于节点,并且左右子树也是二叉搜索树。
项目中用到的算法:
快速排序:从服务器请求商品的价格信息,然后把价格按照由低到高排序,从时间复杂度考虑,我们选用快速排序,从空间复杂度考虑,选用冒泡排序。

3、合并两个有序数组并进行排序

题目:给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
思路:给两个指针p1 p2分别指向 nums1和nums2,然后比较nums[p1]和nums[p2]的大小,把小的追加到新数组中,然后让这个p在加 1 传给下一次的比较,结束的条件是p1<m或者是 p2 < n;然后遍历新数组,把新数组的元素赋值给nums1。

var merge = function(nums1, m, nums2, n) {
    var p1 = 0
    var p2 = 0
    var sorted = []
    var cur
    while(p1 < m || p2 < n ) {
        if(p1 == m) {
            cur = nums2[p2++] //后置递增,先返回原值在加一,在这里就是cur = nums[p2] p2+=1在把这个p2传给下一次的比较。
        } else if(p2 == n) {
            cur = nums1[p1++]
        } else if(nums1[p1] < nums2[p2]) {
            cur = nums1[p1++]
        } else {
            cur = nums2[p2++]
        }
        sorted[p1 + p2 - 1] = cur //这里要注意的的是数组的索引
    }
    for(var i = 0; i !== m + n; i++) {
        nums1[i] = sorted[i]
    }
    return nums1
};

4、二维数组相加相乘怎么计算?

二维数组的相加

var array = [["1.2","1.3","1.5","1.7"],["2.1","2.3","2.4"]]
var sum = []
for(var i = 0; i < array.length; i++) {
           sum[i] = 0
}
for(var count1 = 0; count1 < array.length; count1++) {
     for(var count2 = 0; count2 < array[count1].length; count2++) {
          sum[count1] += parseFloat(array[count1][count2])
	}
}
console.log(sum);

6、求前十个最大的值

//求数组中前十个最大的值
       //思路:首先对数组进行排序后反转数组,之后把数组的前十个元素输出
       function Sort(arr, n) {
            arr = arr.sort((a,b) => a-b).reverse()
            var newArr = []
            for(var i = 0; i < n; i++) {
                newArr.push(arr[i]) 
            }
            return newArr
}
console.log(Sort([1,2,3,43,4,5,6,7,8,98,6,45,3,3,5,6,7,2,12,6,8,3], 10));

8、奖英文字符串按照大小写反转,并且以空格进行逆序

题目:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. “,则输出"student. a am I”

var reverseWords = function(s) {
    //1.首先把字符串两端的空格去掉 然后把字符串以空格为间隔分隔成数组 再把数组中的空格过滤掉
    s = s.trim().split(" ").filter(value => value != '')
    //2.对数组元素实现reverse
     s = s.reverse()
    //3.拼接字符串 以空格拼接成一个数组
    return s.join(" ")
};

9、100个人循环报数,报到5的时候就淘汰,最后剩下一个人

function baoshu(n,m) {
           var arr = []
           for(var i = 1; i <= n; i++) {
               arr.push(i)
           }

           while(arr.length > 1) {
               for(var j = 0; j <= m; j++) {
                   arr.push(arr.shift())
               }
               arr.shift()
           }
           return arr
}
console.log(baoshu(100,5));

10、reduce求和

//reduce方法 加数组的扁平化的实现
       var arr = [1,2,3,[4,5],6]
       var sum = arr.toString().split(",").reduce((total,i) => total += Number(i), 0)
       console.log(sum);

11、实现数组的乱序输出

function mixed(arr) {
           for(var i = 0; i < arr.length; i++) {
               var random = Math.round(Math.random() * (arr.length - 1 - i)) + i;
               [arr[i], arr[random]] = [arr[random], arr[i]]
           }
           return arr
}
console.log(mixed([1,2,3,4,5]));

12、将出现次数最多的前k 个单词输出

给一非空的单词列表,返回前 k 个出现次数最多的单词。返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率,按字母顺序排序。
题解:

var topKFrequent = function(words, k) {
   //思路涉及到存储单词和出现的频率问题 考虑用字典实现
   //1、新建字典 用于保存单词和出现次数
   var ctn = new Map()
   //2、把单词和出现次数放入 字典
   for(var word of words) {
       ctn.set(word, (ctn.get(word) || 0) +1)
   }
   //3、把单词存储在 一个数组中
   var res = []
   for(var entry of ctn.keys()) {
       res.push(entry)
   }
   //4、对res中的单词按照出现次序进行排序
   res.sort((word1,word2) => {
       return ctn.get(word1) === ctn.get(word2) ? word1.localeCompare(word2) : ctn.get(word2) - ctn.get(word1)
   })
   //输出前 k  个单词构成的数组
return res.slice(0, k)
};

13、匹配括号

题目:
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
思路:碰到左括号的时候就入栈,碰到有括号的时候就判断是不是和栈顶元素匹配,如果匹配的话就删除该元素,不匹配的话 返回 false
最后返回栈的元素个数即栈的长度为 0
题解:

var isValid = function(s) {
    //字符串s的长度为奇数时,直接返回false
    if(s.length%2 ==1) return false ;
    //通过数组建立栈
    let stack = [];
    for(let i = 0;i<s.length;i++){

        let c = s[i];
        console.log(c)
        //如果为左括号就进栈
        if(c==='{' || c==='[' || c==='(') {
          stack.push(c);
        } else {

            //若为右括号,若栈空时,返回一个false;
            if(stack.length===0) return false ;
            //取到栈顶元素
            let s = stack[stack.length-1];
            //判断是否匹配,ruo匹配就出栈顶元素,可以不用接收返回结果
            if(s==='{'&&c==='}' || s==='['&&c===']' || s=='('&&c==')') {
                stack.pop();
            } else {
                return false ;
            }
        }
    }
    //遍历完所有字符串后,判断栈是否为空,若为空,即完全配对
    return stack.length ===0;
};

<一>、基本算法题

1、双指针—和为s的两个数字

题目:输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。

思路1:用循环,第一个加数依次为数组顺序元素,第二个加数从第一个加数往后延,直至找到和为target的两个加数。

题解:

var twoSum = function(nums, target) {
    for(var i = 0; i < nums.length; i++) {
        for(var j = i + 1; j < nums.length; j++) {
            if(nums[i] + nums[j] == target) return [nums[i], nums[j]]
        }
    }
};

思路2:因为数组元素是递增的,所以使用双指针来遍历。左右均给定一个指针,当左右指针的元素之和等于target时,输出这两个指针的值;当小于target时,左指针右移;当大于target时,右指针左移。

题解:

var twoSum = function(nums, target) {
    var l = 0
    var r = nums.length - 1
    while(l < r) {
        if(nums[l] + nums[r] == target) return [nums[l], nums[r]]
        else if(nums[l] + nums[r] < target) l += 1
        else  r -= 1
    }
};

2、和为s的连续正数序列

题目:
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
思路:给定两个指针, 一个sum,和一个用于存放结果的数组。当和小于目标值时,和加上这个数,右指针往右;当和大于目标值的时候和放弃这个数,然后左指针往右;当左右指针之间的和等于目标值的时候把元素追加到一个数组中;遍历完毕之后,让和减去左指针的值,左指针右移,进行下一次遍历直至左指针到中间。然后把每次遍历的数组放到一个数组中。

var findContinuousSequence = function(target) {
    var list = []
    var left = 1
    var right = 1
    var sum = 0
    while(left < target/2) {
        if(sum < target) {
            sum += right
            right += 1
        } else if(sum > target) {
            sum -= left
            left += 1
        } else {
            var arr = []
            for(var i = left; i < right; i++) {
                arr.push(i)
            }
            list.push(arr)
            sum -= left
            left += 1
        }
    }
    return list
};

3、二进制中1的个数

题目:请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。

思路:先把十进制转换成二进制,用数组实现。把余数放入一个数组中,十进制变成整数商;除完之后,把数组中的余数从后往前放入一个新数组,构成该十进制的二进制数;最后遍历这个二进制数组,用index计数。

题解:

var hammingWeight = function(n) {
    var arr = []
    while(n > 0) {
        arr.push(n % 2)
        n = Math.floor(n / 2)
    }

    var a = []
    while(arr.length) {
        a.push(arr.pop())
    }

    var index = 0
    for(var i = 0; i < a.length; i++) {
        if(a[i] == 1) {
            index += 1
        }  
    }
    return index
}

4、不用加减乘除做加法

题目:写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。

思路:清晰加法的本质,不进位加法是 a^b;进位是(a&b)<<1;如果进位不为0,就一直把上次得到的不进位和进位进行循环,直至进位为0。

题解:

var add = function(a, b) {
    while(b) {
        var sum = a^b
        b = (a&b)<<1
        a = sum
    }
    return a
};

5、实现1+2+…+n不用乘除法

题目:
求 1+2+…+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
示例 1:
输入: n = 3
输出: 6
思路1:采用递归

var sumNums = function(n) {
    return n && sumNums(n - 1) + n
};

思路2:运用对数。可以将乘法转换成加法,因为这道题就是求等差数列的和 公式为 n * (1 + n) / 2;所以想办法把乘法转换成加法

var sumNums = function(n) {
    return Math.round(Math.exp(Math.log(n) + Math.log(n + 1) - Math.log(2)))
};

Math.exp(x):表示求以e为底的x的真数,Math.log(n):表示求以e为底的n的对数。

6、斐波那契数列

题目:
实现斐波那契数列,f(n) = f(n-1) + f(n-2)
思路:用递归,超出时间限制;
所以要改进时间复杂度,用循环实现

var fib = function(n) {
    if(n == 0) return 0
    if(n == 1) return 1
    var res1 = 0
    var res2 = 1
    for(var i = 0; i < n; i++) {
        var temp = res1
        res1 = res2
        res2 = (temp + res1) % 1000000007
    }
    return res2
};

7、青蛙跳台阶问题

题目:
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
思路:这只青蛙最后只能跳一个台阶或者是两个台阶,总的方式数等于跳(n-1)的方式数加上(n-2)的方式数,所以就变成了斐波那契数列的问题。

var numWays = function(n) {
	//边界和初始条件
    if(n == 1) return 1
    if(n == 0) return 1
    //这个就是斐波数列的思路
   var res1 = 1
   var res2 = 1
   for(var i = 2; i <= n; i++) {
       var temp = res1
       res1 = res2
       res2 = (temp + res1) % 1000000007
   }
   return res2
};

8、扑克牌中的顺子

题目:
从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
示例 1:
输入: [1,2,3,4,5]
输出: True

示例 2:
输入: [0,0,1,2,5]
输出: True
思路:要想是一个顺子的话,最大值减去最小值小于等于4,并且里边不能有重复的。所以先排序在去重0,在把false的情况排除剩下的就是true的了。

var isStraight = function(nums) {
    // 先将 nums 从小到大进行排序,再把数组中的 0 去掉
    nums = nums.sort((a, b) => a - b).filter(item => item !== 0)
    // 找出数组中的最大数与最小数,分别在数组的头和尾,判断它们的差是否超过 4,超过则说明不是连续的
    if (nums[nums.length - 1] - nums[0] > 4) return false 

    // 遍历数组找出是否有重复的数字,因为涉及到 i + 1,所以遍历长度是 数组长度-1
    for (let i = 0; i < nums.length - 1; i++) {
        if (nums[i] === nums[i + 1]) return false
    }
    return true
};

9、数值的整数次方 Math.pow(x,n)的实现

题目:
实现 pow(x, n) ,即计算 x 的 n 次幂函数。不得使用库函数,同时不需要考虑大数问题。
思路:分情况讨论,分为 n = 0、1、负数、奇数、偶数

var myPow = function(x, n) {
    if(n === 0) return 1;
    if(n === 1) return x;
    if(n === -1) return 1/x;
    if(n % 2 == 0) { //为奇数的话 x^n = x^(n/2) * x^(n/2) 
        var a = myPow(x, n/2)
        return a*a
    } else {
        var b = myPow(x, (n-1)/2) //为偶数的话 x^n = x^(n-1/2) * x^(n-1/2) * x
        return b*b*x
    }
};

10、数字序列中某一位的数字

题目:
数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数,求任意第n位对应的数字。
示例 1:
输入:n = 3
输出:3

示例 2:
输入:n = 11
输出:0
思路:
1、要明白第n位数字的起始数字是多少,然后这个n位的数字一共有多少个字符
2、判断这个数字处于哪一个n的区间,
3、判断处于该区间的哪一位上
4、返回该位
题解:

var findNthDigit = function(n) {
    for(var bit = 1; bit < 32; bit++) {
        //bit 位数的初始数字 就比如2位数的初始数字是 10^(2-1)=10
        var startNum = Math.pow(10, bit - 1)
        //bit 位数的字符总数
        var bitSum = 9*startNum*bit
        //
        if(n > bitSum) {
            n -= bitSum
        } else {
            //计算要找的数字
            var num = startNum + Math.ceil(n/bit)-1
            //计算处于这个数字的哪一个位置
            var pos = n - bit*(num-startNum)-1
            //返回该位置的字符
            return num.toString()[pos]
        }

    }
};

11、n个筛子的点数

题目:
把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
示例 1:
输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]
示例 2:
输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]
思路:
1、我们的思路是计算所有骰子点数出现的次数,然后除以总的可能数即可,总的可能性容易求出,为 6 ^ n,问题的关键在于求解每一个点数出现的次数。
2、首先很明显只有一个骰子的时候,1,2,3,4,5,6出现的次数都为1。那么如果两个骰子,出现次数为7的话,有多少种呢?应该是6种。那么如果两个骰子,出现次数为8的话,有多少种呢?应该是5种。
3、我们发现,我们只需要用一个数组cnts,cnts[i] 表示掷出i的次数,那么cnts[i] 就等于前面六个相加,或者前面五个相加,或者。。。。
为了简单起见,我们从后往前遍历,这样我们的逻辑可以统一为 cnts[i] 就等于前面六个cnts[j]相加,其中j等于i - 1, i - 2, i - 3, i - 4, i - 5, i - 6。
如果使用迭代,我们只需要迭代n - 1次,每次迭代相当于一次投掷,而内层循环的逻辑就是上面提到的,我们每次投掷都去更新cnts[i]。
题解:

var dicesProbability = function(n) {
     if (n < 1) {
        return [];
      }
      const res = [0, 1, 1, 1, 1, 1, 1];
      for (let i = 1; i < n; i++) {
        for (let j = 6 * n; j > 0; j--) {
          res[j] = res
            .slice(Math.max(0, j - 6), j)
            .reduce((acc, cur) => acc + cur, 0);
        }
      }
      return res.slice(1).map(num => num / Math.pow(6, n)).filter(Boolean);
};

12、机器人的运动范围之广度优先搜索

题目:
地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
示例 1:
输入:m = 2, n = 3, k = 1
输出:3
示例 2:
输入:m = 3, n = 1, k = 0
输出:1

思路:
1、看到这种带有方位名词的术语,自然而然的就会想到BFS
2、本题的关键在于位数和的计算;四周方位的遍历;限制条件;格子数的统计(走过的格子用set来表示因为set这个数据结构里边的数据具有唯一性,走过的格子就不在统计了,set里边元素的个数就是走过的格子数)
题解:

var movingCount = function(m, n, k) {
   // 位数和
    function getSum(num) {
        let answer = 0;

        while(num) {
            answer += num % 10;
            num = Math.floor(num / 10);
        }

        return answer;
    } 
    // 方向数组
    const directionAry = [
        [-1, 0], // 上
        [0, 1], // 右
        [1, 0], // 下
        [0, -1] // 左
    ];

    // 已经走过的坐标
    let set = new Set(['0,0']);

    // 将遍历的坐标队列,题意要求从[0,0]开始走
    let queue = [[0, 0]];

    // 遍历队列中的坐标
    while(queue.length) {
        // 移除队首坐标
        let [x, y] = queue.shift();

        // 遍历方向
        for(let i = 0; i < 4; i++) {
            let offsetX = x + directionAry[i][0]; //向下走
            let offsetY = y + directionAry[i][1]; //向右走


            // 临界值判断
            if(offsetX < 0 || offsetX >= m || offsetY < 0 || offsetY >= n || getSum(offsetX) + getSum(offsetY) > k || set.has(`${offsetX},${offsetY}`)) {
                continue;
            }

            // 走过的格子就不再纳入统计
            set.add(`${offsetX},${offsetY}`);

            // 将该坐标加入队列(因为这个坐标的四周没有走过,需要纳入下次的遍历)
            queue.push([offsetX, offsetY]);
        }
    }

    // 走过坐标的个数就是可以到达的格子数
    return set.size;
};

<二>、数组

常见方法

1>、开头和结尾的增删改查
末尾删除元素:pop
末尾增加元素:push
开头增加元素:unshift
开头删除元素:shift
任意位置的增删改查:splice(开始的元素索引,想要删除的元素个数,想要增加的元素)

2>、遍历
for(var x of arr) {}
arr.some(function)查看arr中是否有复合条件的元素,有的话返回true反之false
arr.every(function)查看arr中是否所有元素均符合条件,是的话返回true反之false
arr.forEach(function)遍历arr的每一个元素,返回运行结果
arr.map(function)返回一个数组,数组元素由function遍历arr的结果组成
arr.filter(function)返回一个数组,数组元素由function遍历arr为true的元素构成
arr.reduce(x,y,index,array)x为前一个值,y为当前值,index为索引,array为一个数组,index和array可以省略

3>、排序
arr.sort()按照Unicode编码排序

4>、搜索
arr.indexOf(x)从前开始查找数组中为x的索引
arr.lastIndexOf(x)从后开始查找数组中为x的索引
arr.find(function)找到符合条件的第一个元素
arr.findIndex(function)找到符合条件的第一个元素的索引
arr.includes(x)查找数组中是否有该元素值

5>、抓换成字符串
arr.toString()转换后的字符串 以 原样式展示 只不过是由数组转换成了字符串
arr.join(" ")转换后的字符串 以 空格隔开
arr.join()转换后的字符串 以 原样式展示 只不过是由数组转换成了字符串

var s = [1, 2, 3, 4, 5]
console.log(s.join());//1,2,3,4,5 原样输出
console.log(s.join(""));//12345 数组元素之间凑成一块,没有隔开
console.log(s.join(" "));//1 2 3 4 5 数组元素之间用空格隔开

1、查找重复元素

题目:在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

思路1:使用循环,时间复杂度高,从头开始遍历数组,从该元素的下一个开始查找,直至找到相同的元素值。

题解:

var findRepeatNumber = function(nums) {
    for(var i = 0; i < nums.length; i++) {
        for(var j = i + 1; j < nums.length; j++) {
            if(nums[i] == nums[j]) return nums[i]
        }
    }
    return ' '
};

思路2:使用遍历 for of,继续在剩下的数组中查找元素

题解:

var findRepeatNumber = function(nums) {
    for(var x of nums) {
        var newArr = nums.slice(nums.indexOf(x) + 1)
        if(newArr.indexOf(x) >= 0) return x
    }
    return ' '
};

2、调换数组元素

题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

思路:创建一个奇数数组和偶数数组,把奇数元素放在奇数数组偶数元素放在偶数数组,在拼接两个数组

题解

//奇数数组
var exchange = function(nums) {
    var arrOdd = []
    for(var i = 0; i < nums.length; i++) {
        if(nums[i] % 2 == 1) {
            arrOdd.push(nums[i])
        }
    }
//偶数数组
var arrEven = []
    for(var i = 0; i < nums.length; i++) {
        if(nums[i] % 2 == 0) {
            arrEven.push(nums[i])
        }
    }
//拼接两个数组
return arrOdd.concat(arrEven)
};

3、查找出现次数超过一半的元素

题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

思路:首先给数组进行排序,出现次数超过一半的元素,就是在数组中间的元素

题解:

var majorityElement = function(nums) {
    nums = nums.sort((a,b) => {return a - b}) //这里是否按照Unicode编码排序不重要
    return nums[Math.floor(nums.length / 2)] //为什么不是向上取整呢,如果长度为1,则1/2=0.5,向上取整为1,但是nums[1]不存在,所以是向下取整
};

4、数组中只出现一次的两个元素

题目:一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

思路:用新数组来保存这两个数字。遍历数组元素,截取该元素余下的一部分,继续查找,如果没有的话,就是该元素,把该元素追加到新数组中。

var singleNumbers = function(nums) {
    var arr = []
    for(var x of nums) {
        var newArr = nums.slice(nums.indexOf(x) + 1)
        if(newArr.indexOf(x) == -1) arr.push(x)
    }
    return arr
};

5、数组中只出现一次的一个数字

题目:在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

思路:该元素的起始索引值和终止索引值相同

var singleNumber = function(nums) {
    for(var x of nums) {
        if(nums.indexOf(x) == nums.lastIndexOf(x)) return x
    }
};

6、打印从1到n的最大n位数

题目:输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

思路:n位十进制数的最大值为 10^n - 1,所以用数组保存这些元素即可

题解:

var printNumbers = function(n) {
    var arr = []
    for(var i = 1; i <= Math.pow(10, n) - 1; i++) {
        arr.push(i)
    }
    return arr
};

7、求一个连续整数值组成数组中的缺失值

题目:一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字

思路:遍历数组,如果当前元素值不等于索引值,则输出该数字

题解:

var missingNumber = function(nums) {
    for(var i = 0; i <= nums.length; i++) { //考虑特殊情况,当数组长度为1时,无法循环第二次,导致undefined 而不是1
        if(nums[i] != i) {
            return i
        }
    }
};

8、数组排序

题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

思路:首先给数组排序,之后最小值就是第一个元素

题解:

var minArray = function(numbers) {
    numbers = numbers.sort((a,b) => {return a -b})
    return numbers[0]
};

9、求数组中最小的前k个数

题目:输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

思路:首先对数组进行排序,之后把前k个数放在一个新数组中。

题解:

var getLeastNumbers = function(arr, k) {
    arr = arr.sort((a,b) => {return a -b})
    var newArr = []
    for(var i = 0; i < k; i++) {
        newArr.push(arr[i])
    }
    return newArr
};

10、统计一个数字在数组中出现的次数

题目:统计一个数字在排序数组中出现的次数。

思路:遍历数组元素,当当前值等于目标值时,次数加一

题解:

var search = function(nums, target) {
    var index = 0
    for(var x of nums) {
        if(x == target) index += 1
    }
    return index
};

11、圆圈中最后剩下的那个数字

题目:0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
思路:用数组来保存0 - n-1这 n 个数字,当 当前元素值小于 m 的时候,将该元素从数组前边删除,追加到数组后边;当当前元素值等于 m 的时候,把这个元素删除;最后返回该数组。
题解:

var lastRemaining = function(n, m) {
    var arr = []
    for(var i = 0; i < n; i++) {
        arr.push(i)
    }

    while(arr.length > 1) {
        for(var j = 0; j < m - 1; j++) {
             arr.push(arr.shift())
        }
        arr.shift()
    }

    return arr
};

12、二维数组的查找

题目:在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路:从数组的左上角(x, y)开始查找,当当前值> 目标值时,x - 1;当当前值 < 目标值时,y + 1;当当前值等于目标值时,输出true
题解:

var findNumberIn2DArray = function(matrix, target) {
    if(matrix.length == 0) return false
    var [l, r] = [matrix[0].length - 1, 0]
    while(l >= 0 && r < matrix.length) { //边界条件,行数得小于等于长度,列数得大于0
        if(matrix[r][l] > target) { //从右上角开始查找
            l --
        } else if(matrix[r][l] < target) {
            r ++
        } else {
            return true
        }
    }
    return false
};

13、顺时针打印矩阵

题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
思路:创建一个新数组,顺序按照最上边一行直接拼接到数组中 右边一行顺序追加 下边一行拼接逆序 左边一行顺序追加
题解:

var spiralOrder = function(matrix) {
    var res = []

    while(matrix.length) {
        res = res.concat(matrix.shift())

        for(var i = 0; i < matrix.length; i++) {
            matrix[i].length && res.push(matrix[i].pop())
        }

        matrix.length && (res = res.concat(matrix.pop().reverse()))

        for(var i = matrix.length - 1; i > 0; i--) {
            matrix[i].length && res.push(matrix[i].shift())
        }
    }
    return res
};

14、数组乘积

题目:给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。
思路:给定两个指针,left从左往右遍历直至当前值的前一个,right从右往左遍历直到当前值的下一个,最后用左边的乘积乘以右边的乘积。
解析:

var constructArr = function(a) {
    var left = []
    var right = []
    var n = a.length

    for(var i = 0; i < n; i++) {
        var j = n - 1 - i
        if(i == 0) {
            left[i] = a[i]
            right[j] = a[j]
        } else {
            left[i] = left[i - 1] * a[i]
            right[j] = right[j + 1] * a[j]
        }
    }
    var b = []
    for(var i = 0; i < n; i++) {
        if(i == 0) {
            b[i] = right[i + 1]
        }else if(i == n - 1) {
            b[i] = left[i - 1]
        }else {
            b[i] = left[i - 1] * right[i + 1]
        } 
    }
    return b
    
};

15、把数组排成最小的数

题目:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
思路:用循环实现,计数实现,

var reversePairs = function(nums) {
    var index = 0
    for(var i = 0; i < nums.length; i++) {
        var first = nums[i]
        num = nums.slice(i + 1)
        for(var x of num) {
            if(first > x) {
                index += 1
            }
        }
    }
    return index
};

16、矩阵中的路径之单词的查找

题目:
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例 1:
输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
输出:true
思路:
1、首先用一个二维数组来保存当前位置有没有被遍历过
2、然后定义一个可不可以 被找到的函数,最核心的是 当前节点的周围可不可以找到剩余字符
3、最后遍历给出的单词,先找到第一个字母所在的位置,然后调用函数看一下周围是不是找得到 剩余字符
题解:

var exist = function(board, word) {
    //首先定义一个二维数组 used 记录当前坐标点是不是已被遍历过
    var m = board.length
    var n = board[0].length
    var used = new Array(m) 
    for(var i = 0; i < m; i++) {
        used[i] = new Array(n)
    }

    //定义一个 找word的函数 之后调用这个函数即可
    //row col 表示当前坐标 i表示 word 的索引号
    var canFind = function(row, col, i) {
        if(i === word.length) return true

        //边界排除
        if(row < 0 || row >= m || col < 0 || col >= n) return false

        //当前节点已被访问过 或者是 非目标节点
        if(used[row][col] || board[row][col] != word[i]) return false
        
        //排除掉所有false的情况,继续递归考察
        used[row][col] = true
        //看一下当前节点的周围是不是可以找到剩余字符
        var canFindRest = canFind(row+1, col, i+1) || canFind(row-1, col, i+1) || canFind(row, col-1, i+1) || canFind(row, col+1, i+1)
        if(canFindRest) return true
        used[row][col] = false
        return false
    }

    for(var i = 0; i < m; i++) {
        for(var j = 0; j < n; j++) {
            if(board[i][j] == word[0] && canFind(i,j,0)) {
                return true
            }
        }
    }
    return false
};

<三>、字符串

常见方法

1>、slice截取字符串
str.slice(m, n) 截取索引值从m到n的一段字符串包括m不包括n

2>、trim删除左右两端的空格

3>、将字符串转化成数组
str.split()原样输出
str.split(" ")分割成单个字母

var s = "We are happy"
console.log(s.split("")); //['W','e','','a','r','e','','h','a','p','p','y'] 一个一个字符式的分隔
console.log(s.split(" ")); //['We','are','happy'] 以空格为单位进行分隔
console.log(s.split(",")); //['We are happy'] 以逗号为单位进行分隔
console.log(s.split()); //['We are happy'] 原样输出 只是字符串变成了数组形式

4>、替换字符串replace
str.replace(‘原字符串内容’, ‘更改的内容’)只替换第一个
str.replaceAll(‘原字符串内容’, ‘更改的内容’)全局替换

5>、搜索
str.indexOf(‘x’)从头开始查找该元素得到索引值
str.lastIndexOf(‘x’)从后开始查找字符串中为x的索引值

6>、遍历
for(var x of str)

1、找出只出现一次的字符

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。

思路:遍历字符串中的每一个元素,查看该元素的起始索引值和终止索引值是不是一个,是的话返回该元素,反之返回空

题解:

var firstUniqChar = function(s) {
   for(var x of s) {
       if(s.indexOf(x) === s.lastIndexOf(x)) return x
   }
    return ' '
};

2、左旋转字符串

题目:字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
思路:使用字符串的截取slice
题解:

var reverseLeftWords = function(s, n) { 
    return s.slice(n) + s.slice(0,n)
};

3、字符串的替换

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
思路1:用替换方法replaceAll
题解:

var replaceSpace = function(s) {
   return s.replaceAll(' ', '%20')
};

思路2:替换加正则
题解:

var replaceSpace = function(s) {
   return s.replace(/ /g, '%20')
};

思路3:首先转换成数组,遍历数组把空格转换成 %20,最后把数组转换成字符串

var replaceSpace = function(s) {
   s = s.split(""); //分隔时单个字母一起
   for(var i = 0; i < s.length; i++) {
       if(s[i] == " ") {
           s[i] = "%20"
       }
   }
   return s.join("") //数组转换成字符串没有空格的输出
};

4、最长不含重复字符的字符串

题目:
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
思路:
1、首先建立一个字符串用来存储不重复的字符。
2、当该字符串中有重复字符的时候就截取掉,并且把这个新的字符重新加入到字符串中。
3、当该字符串中没有重复的字符的时候就加入该字符,并且返回数组的长度。
题解:

var lengthOfLongestSubstring = function(s) {
    //1.用一个字符串string来保存没有重复出现的子字符串 并且记录该字符串的长度res
    var string = ""
    var  res = 0
    //2.开始遍历s 如果string中没有就添加到string中,并且这时候 res要和当前的string.length比较,如果有了这个数字,就把该数字删除
    for(var i = 0; i < s.length; i++) {
        if(string.indexOf(s[i]) == -1) {
            string += s[i]
            res = Math.max(res, string.length)
        } else {
            string = string.slice(string.indexOf(s[i]) + 1)
            string += s[i]
        }
    }
    //3.最后返回 res
    return res
};

5、字符串的排序之所有出现的可能

题目:
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
示例:
输入:s = “abc”
输出:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]
题解:

var permutation = function(s) {
   if(!s) return []
   if(s.length === 1) return [s]
   var res = []
   var list = s.split('')
   sort(list, 0)
   return res



   function sort(list, begin) {
       if(begin === list.length) {
           res.push(list.join(''));
       } else {
           let set = new Set()
           for(var i = begin; i < list.length; i++) {
               if(set.has(list[i])) {
                   continue
               } 
               set.add(list[i])

               let temp = list[begin]
               list[begin] = list[i]
               list[i] = temp
            
               sort(list, begin + 1)

               temp = list[begin]
               list[begin] = list[i]
               list[i] = temp
           }
       }
   }
};

<四>、动态规划

题目特点:

1>、最值问题
2>、存在性
3>、计数问题

解题思路

1、确定状态 (一般分为最后一步和子问题)
2、转移方程
3、初始条件和边界条件 (初始条件是转移方程定义不出来的,边界条件是转移方程不能越界的)
4、计算顺序(一般是由小到大的,因为大的需要小的递归去实现)

1.1、最值问题之硬币

题目:
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。你可以认为每种硬币的数量是无限的。
思路:最少的硬币个数,用动态规划实现
最后一步是:coins中的一个
子问题就是:f(amount - coins[i])的最小值问题,即去掉最后一枚硬币面额剩下的钱数的最小硬币数问题
转移方程:f(amount) = Min(f(amount - coins[i])) + 1
代码:

var coinChange = function(coins, amount) {
    //最优化问题 采用动态规划
    //1.设状态 最后一步是拿出一个面值为a的硬币,所以子问题就变成了求amount-a的最少的硬币数量
    //2.转移方程 f(x) = min{f(x-1)+1, f(x-2)+1, f(x-5)+1}
    //3.边界条件和初始条件
    //4.计算顺序,从小往大计算 

    //给定一个数组   用于计算当amount=0 1 2 3 4 。。。amount
    var arr = []
    //初始话
    arr[0] = 0
    //开始计算arr[1]到arr[amount]
    for(var i = 1; i <= amount; i++) {
        //因为每次都是取最小值 所以干脆在初始位置就给一个最大值
        arr[i] = Infinity
        //最后一个取到的硬币情况
        for(var j = 0; j < coins.length; j++) {
        	//边界条件
            if(i >= coins[j] && arr[i - coins[j]] != Infinity) {
                //这个关系在数学上是成立的,但是要想代码实现需要加条件
                //因为这是遍历面值为不同硬币的情况 比如 最后一枚硬币是5元就需要和上次硬币是2元的所需要的硬币数量进行比较,取这次和上次的最小值。
                arr[i] = Math.min(arr[i - coins[j]]+1, arr[i])
            }
        }
    }
    //没有任何硬币组合
    if(arr[amount] == Infinity) {
        arr[amount] = -1
    }
    return arr[amount]
};

1.2、最值问题之礼物的最大价值

题目:
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
思路:最值问题,用动态规划。
最后一步:从[i-1][j]或者是[i][j-1]走到[i][j],子问题就变成了走到[i-1][j]或者是[i][j-1]的问题。
转移方程:f[i][j] = Max(f([i][j-1]), f(i][j-1])) + f[i][j]。
初始条件:f[0][0] = grid[0][0]。
计算顺序:由小到大
题解:

var maxValue = function(grid) {
    //1.确定状态 最后一步 就是从左边或者是上边下来的; 子问题就变成了从左上角到右下角的前一行或者是前一列的问题
    //2.转移方程 dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j]
    //3.初始条件,第一行第一列 边界条件第一行或者是第一列
    //4.计算顺序 由小到大
    var m = grid.length
    var n = grid[0].length
    var dp = []
    for(var i = 0; i < m; i++) {
        //定义了一个二维数组
        dp[i] = []
        for(var j = 0; j < n; j++) {
            if(i == 0 && j == 0) {
                dp[i][j] = grid[i][j]
            } else if(i == 0) {
                dp[i][j] = dp[i][j-1] + grid[i][j]
            } else if(j == 0) {
                dp[i][j] = dp[i-1][j] + grid[i][j]
            } else {
                dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]) + grid[i][j]
            }
        }
    }
    return dp[m-1][n-1]
};

1.3、最值问题之股票的最大利润

题目:
假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
思路:最值问题,用动态规划。
最后一步:看一下前一天的最大利润 和 今天的价格减去最小值 的最大值
转移方程:dp[i] = max(dp[i-1], price-min)
初始条件:dp[0] = price[0]
计算顺序:由小到大

var maxProfit = function(prices) {
    if(!prices || prices.length == 0) return []
    var len = prices.length
    var dp = []
    var min = prices[0]
    dp[0] = 0
    var price
    for(var i = 1; i < len; i++) {
        price = prices[i]
        min = Math.min(min, price)
        dp[i] = Math.max(dp[i-1], price-min)
    }
    return dp[len-1]
};

1.4、最值问题之剪绳子一

题目:
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
思路:最值问题采用动态规划

var cuttingRope = function(n) {
    var dp = new Array(n + 1).fill(1)
    for(var i = 3; i <= n; i++) {
        for(var j = 1; j < i; j++) {
            dp[i] = Math.max(dp[i], j * (i - j), j * dp[i - j])
        }
    }
    return dp[n]
};
思路2:这里涉及到大数字问题,因为Math函数没有办法适用于大数,所以要用reduce自定义一个函数
var cuttingRope = function (n) {
    let dp = new Array(n).fill(BigInt(1));
    for (let i = 0; i < n; i++) {
        for (let j = i - 1; j >= 0; j--) {
            dp[i] = max(dp[i], dp[j] * BigInt((i - j)), BigInt((j + 1) * (i - j)));
        }
    }
    return dp[n - 1] % (1000000007n);
};

const max = (...args) => args.reduce((prev, curr) => prev > curr ? prev : curr)

2.1、计数问题之机器人走格子

题目:
个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
思路:这是计数问题采用动态规划,最后一步只能从上边或者是左边过来,所以子问题就变成了机器人从左上角走到倒数第二个终点的位置。
题解:

var uniquePaths = function(m, n) {
    //确定状态 最后一步,从左边过去或者是从上边过来,子问题就是从左上角到倒数第二个位置的方式
    //转移方程 grid[i][j] = grid[i-1][j] + grid[i][j-1]
    //初始条件 m = 0 || n = 0 的时候 为1
    //计算顺序 由小到大
    const grid  = []
    for(var i = 0; i < m; i++) {
        grid[i] = []
        for(var j = 0; j < n; j++) {
            if(i ==0 || j == 0) {
                grid[i][j] = 1
            } else {
                grid[i][j] = grid[i][j-1] + grid[i-1][j]
            }
        }
        
    }
    return grid[m-1][n-1]
};

2.2、计数问题之把数字翻译成字符串

题目:
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
思路:方法数问题,采用动态规划。
确定状态:dp[i]代表第i位的方法数,最后一步有两种可能,如果前两位数字大于等于10小于等于25的话,总的方法数就是前两个的叠加,如果不是的话,最后一个数的计数总数就是前一个数的计数总数。
转移方程:
边界:dp[1] = 1; dp[0] = 1
计算顺序:由小到大

var translateNum = function(num) {
    const str = num.toString();
    const n = str.length;

    const dp = new Array(n + 1);
    dp[0] = 1;
    dp[1] = 1;

    for (let i = 2; i < n + 1; i++) {
        const temp = Number(str[i - 2] + str[i - 1]);
        if (temp >= 10 && temp <= 25) {
        	dp[i] = dp[i - 1] + dp[i - 2];
        } else {
        	dp[i] = dp[i - 1];
        }
    }
    
    return dp[n];
};

2.3计数问题之求丑数

题目:
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
思路:计数问题采用动态规划,
1、用三个指针分别用来保存 2 3 5在丑数序列中出线的次数
2、下一个丑数就是 这些次数加一出现的最小值
3、当丑数轮到该数字的时候,该数字对应的指针数字就加一
题解:

var nthUglyNumber = function(n) {
    var dp = []
    dp[0] = 0
    dp[1] = 1
    var p1 = 1, p2 = 1, p3 = 1
    for(var i = 2; i <= n; i++) {
        //当前值 为三个指针对应的 数字
        var num1 = dp[p1] * 2
        var num2 = dp[p2] * 3
        var num3 = dp[p3] * 5
        dp[i] = Math.min(Math.min(num1, num2), num3)
        //如果当前值为 这三个指对应的某一个数字的时候 该指针就加一
        if(dp[i] === num1) {
            p1 += 1
        } 
        if(dp[i] === num2) {
            p2 += 1
        } 
        if(dp[i] === num3) {
            p3 += 1
        }
    }
    return dp[n]
};

3.1、存在性问题之青蛙跳台阶

题目:有 n 块石头分别在x轴的 0 1 2 … n-1的位置,一直青蛙在石头0的位置,想跳到石头 n-1 的位置,如果青蛙在第i快石头上,那么他最多只能跳a^i下,问青蛙是否可以跳到石头 n-1的位置。
例如:输入 a = [2,3,1,1,4]输出true 就是在第一块石头上最多可以跳2下,第二块石头上最多可以跳3下这样的。
思路:存在性问题,用动态规划。最后一步就是看 n - 1 - i <= a^i
题解:

var jump = function(a) {
	var n = a.length
	var f = []
	f[0] = true
	for(var i = 1; i < n; i++) {
		f[i] = false
		for(var j = 1; j < i; j++) {
			if(f[j] && i + a^i >= j) {
				f[i] = true
				break
			}
		}
	}
	return f[n-1]
}

<五>、贪心算法

1、求子数组的最大和

题目:输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

思路:使用贪心算法

var maxSubArray = function(nums) {
    var sum = nums[0]

    var maxSum = sum

    for(var i = 1; i < nums.length; i++) {
        sum = Math.max(nums[i], sum+nums[i])

        maxSum = Math.max(sum, maxSum)
    }
    return maxSum
};

六、二叉树

前序中序后序遍历的理解

在这里插入图片描述

1、二叉树镜像

题目:请完成一个函数,输入一个二叉树,该函数输出它的镜像。
例如输入:

 4

/
2 7
/ \ /
1 3 6 9
镜像输出:

 4

/
7 2
/ \ /
9 6 3 1
思路:交换左右节点的值,对左右子树使用递归。

var mirrorTree = function(root) {
  if(!root) return null
  var temp = root.left
  root.left = root.right
  root.right = temp
  mirrorTree(root.left)
  mirrorTree(root.right)
  return root
};

2、求二叉树的深度

题目:
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。

例如:

给定二叉树 [3,9,20,null,null,15,7],

3

/
9 20
/
15 7
返回它的最大深度 3 。
思路:碰到树,就用递归

var maxDepth = function(root) {
    if(!root) return 0
    return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1
};

3、二叉树的最近的公共祖先

题目:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。

思路:如果p或是是q等与root的时候,就返回root;如果不是root的话,就更要返回root了
然后遍历左子树和右子树,如果左子树没有找到的话就返回右子树;右子树没有找到的话就返回左子树,最后返回root。

var lowestCommonAncestor = function(root, p, q) {
    //1.如果p或者q为root的一个左节点或者是右节点的时候 则返回root
    if(root === p || root === q) return root
    //2.如果都不是的话 则就更要返回 root
    if(!root) return root
    //3.采用递归 对左右子树进行相同的操作
    var left = lowestCommonAncestor(root.left, p, q)
    var right = lowestCommonAncestor(root.right, p, q)
    //4.如果左子树找不到 则返回右子树
    if(!left) return right
    //5.如果 右子树找不到 则返回左子树
    if(!right) return left

    return root
};

4、二叉搜索树的最近的公共祖先

题目:
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
示例 1:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
思路:因为是二叉搜索树,所以就利用二叉搜索树的性质,左子树的值小于节点值,右子树的值大于节点值。

var lowestCommonAncestor = function(root, p, q) {
    //1.当根节点的val等于p.val或者是q.val则返回 root
    if((root.val - p.val) * (root.val - q.val) <= 0) return root
    //2.如果p.val小于root.val 则遍历左子树
    if(p.val < root.val) return lowestCommonAncestor(root.left, p ,q)
    //3.否则 则遍历右子树
     return lowestCommonAncestor(root.right, p ,q)
};

5、平衡二叉树

题目:
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
示例 1:
给定二叉树 [3,9,20,null,null,15,7]
3
/
9 20
/
15 7
返回 true 。
思路:首先定义深度函数,然后满足平衡树的条件是左右子树都是平衡树并且深度不超过1

var isBalanced = function(root) {  
    //1.定义深度函数 判断树的深度 
    var depth = function(root) {
        if(!root) return 0
        return Math.max(depth(root.left), depth(root.right)) + 1
    }
    //2.平衡的条件是 左右树均是平衡树 并且深度不超过1
    if(!root) return true
    return Math.abs(depth(root.left) - depth(root.right)) <= 1 && isBalanced(root.left) && isBalanced                   (root.right)
};

6、对称二叉树

题目:
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

1

/
2 2
/ \ /
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:

1

/
2 2
\
3 3
思路:首先定义一个镜像函数,两个节点的值相等,并且左子树等于右子树,右子树等于左子树;然后把这个节点本身做镜像比较。

var isSymmetric = function(root) {
    //1.判断两颗树 是不是互为 镜像
    var isMirror = function(r1, r2) {
        if(!r1 && !r2) return true
        if(!r1 || !r2) return false
        return r1.val === r2.val && isMirror(r1.left, r2.right) && isMirror(r2.left, r1.right)
    } 

    return isMirror(root, root)
};

7、从上到下打印出二叉树的每一个节点,放在一个数组中

题目:
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
思路:把节点放在一个数组中,当数组有长度的时候,往数组中放节点值,

var levelOrder = function(root) {
    if(!root) return []
    var res = []
    var queue = [root] 
    while(queue.length) {
        var node  = queue.shift()
        res.push(node.val)
        node.left && queue.push(node.left)
        node.right && queue.push(node.right)
    }
    return res
};

8、从上往下打印二叉树,每层从左往右放一行

题目:
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。

例如:
给定二叉树: [3,9,20,null,null,15,7],

3

/
9 20
/
15 7
返回其层次遍历结果:

[
[3],
[9,20],
[15,7]
]
思路:把节点放在一个数组中保存,有一个res来保存所有行的节点值,然后每一层创建一个数组arr,来保存每一行的节点值。

var levelOrder = function(root) {
   if(!root) return []
   var res = []
   var queue = [root]
   while(queue.length) {
       var arr = []
       var len = queue.length
      while(len --) {
           var node = queue.shift()
       
            arr.push(node.val)

            node.left && queue.push(node.left)
            node.right && queue.push(node.right)
      }
      res.push(arr)
   }
   return res
};

9、打印二叉树,第一行从左往右放,第二行从右往左放

题目:
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
思路:和上边的一样,就是用index来记一下行数

var levelOrder = function(root) {
    if(!root) return []
    var res = []
    var queue = [root]
    var index = 0
    while(queue.length) {
        res[index] = []
        var len = queue.length
        while(len--) {
            var node = queue.shift()
            res[index].push(node.val)
            node.left && queue.push(node.left)
            node.right && queue.push(node.right)
        }
        if(index % 2 == 1) {
            res[index].reverse()
        }
        index++
    }
    return res
};

10、二叉搜索树的第k大节点

题目:
给定一棵二叉搜索树,请找出其中第k大的节点。
思路:先把二叉树的每一个节点保存在一个数组中,第k大节点就是arr[k-1],利用二叉搜索树的中序遍历特点,先遍历右子树,在遍历中间,在遍历左子树。

var kthLargest = function(root, k) {
    var arr = []
    const dfs = function(node) {
        if(!node) return
        dfs(node.right)
        arr.push(node.val)
        dfs(node.left)
    }
    dfs(root)
    return arr[k - 1]
};

11、二叉搜索树的后序遍历

题目:
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
参考以下这颗二叉搜索树:
5
/
2 6
/
1 3
示例 1:
输入: [1,6,3,2,5]
输出: false
示例 2:
输入: [1,3,2,6,5]
输出: true
思路:如果是后序遍历的话,数组的最后一个元素一定是根元素,然后找到比root大的那个数字,来划分左右子树,该元素所在边是右子树,元素一定比root大;该元素的左边一定是左子树,比root小。核心思想:递归实现,遇到树就用递归。

var verifyPostorder = function(postorder) {
    var len = postorder.length;
    if(len < 2) return true
    //找到根节点 如果是后序遍历的话,就是最后一个元素
    var root = postorder[len - 1]
    //先找到比root大的那个数字
    for(var i = 0; i < len -  1; i++) {
        if(postorder[i] > root) break
    }
    //该元素所在的数组元素一定比 root da
    var res = postorder.slice(i, len-1).every(x => x > root);
    //如果满足上边这个条件的话,就递归左右两边
    if(res) {
        return verifyPostorder(postorder.slice(0, i)) && verifyPostorder(postorder.slice(i, len - 1))
    } else {
        return false 
    }
};

12、树的子结构

题目:
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:
给定的树 A:
3
/
4 5
/
1 2
给定的树 B:
4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
思路:先给定一个函数isSame(),判断两个树 是不是一样,然后递归判断A B或者是 A的左子树和B 或者是A的右子树和B。只要有一个返回true结果就是true

var isSubStructure = function(A, B) {
    if(!A || !B) return false //如果A为空或者是B为空,则均返回false
   return isSame(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B) //看一下A的左子树或者是右子树是不是和B一样
	//判断两个树是不是一样
   function isSame(a, b) {
       if(!b) return true //如果b是空的话,那就是true
       if(!a) return false //如果a是空的话那就是false
       if(a.val !== b.val) return false
       return isSame(a.left, b.left) && isSame(a.right, b.right) //如果两个树一样的话,左右子树都得是一样的
   }   
};

13、重建二叉树

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/
9 20
/
15 7
思路:前序遍历的第一个元素就是整棵树的根节点,然后在中序遍历中找到该节点的索引值,中序遍历该索引值前边的是整棵树的左子树,右边是整棵树的右子树。因为树的节点个数一样,所以在索引值上体现的淋漓尽致
题解:

var buildTree = function(preorder, inorder) {
    if(preorder.length == 0 || inorder.length == 0) {
        return null
    }
    //1.根节点是前序遍历的第一个数
    var root = preorder[0]
    var tree = new TreeNode(root)
    //2.找到中序遍历根节点的位置
    var index = inorder.indexOf(root)
    //3.对于中序遍历 根节点左边的节点即左子树 根节点右边的节点即右子树 因为同一个节点的左子树的节点个数相同
    tree.left = buildTree(preorder.slice(1, index+1), inorder.slice(0, index))
    tree.right = buildTree(preorder.slice(index+1), inorder.slice(index+1))

    return tree
};

14、二叉树中和为某一值的路径

题目:
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
示例:
给定如下二叉树,以及目标和 target = 22,

          5
         / \
        4   8
       /   / \
      11  13  4
     /  \    / \
    7    2  5   1

返回:

[
[5,4,11,2],
[5,8,4,5]
]
思路:
1、用一个数组来保存所有的路径
2、当遍历的这个叶子结点的路径总和等于sum的时候就把该条路径保存到数组中
3、当不是的话,继续遍历该子树的其余节点
题解:

var pathSum = function(root, target) {
    if (root === null) return [];
    const res = [];
    var DFS = (root, sum, tmp) => {
        //如果碰到叶子结点的话,就判断叶子结点数是不是等于target 如果是的话,就把叶子结点经历的路径             放入最终结果的数组中
        if (root.val === sum && !root.left && !root.right) {
            res.push(tmp);
        }
        //如果不是叶子结点的话,就把值放入当前数组中
        tmp.push(root.val);
        //继续遍历该节点的左右树 但是这个时候 target已经变成了减去上边存入的节点值 这时候的数组是放           入节点后的数组
        if (root.left) DFS(root.left, sum - root.val, tmp.slice());
        if (root.right) DFS(root.right, sum - root.val, tmp.slice());
    }
    //调用该函数 实现正确路径的输出
    DFS(root, target, []);
    return res;
};

<七>、栈和队列

1、栈的压入和弹出序列

题目:
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
思路:用一个新栈来挨个保存数据,依此把栈中值入栈,并且判断当前入栈值和弹出值是否相同,相同的话,就在新栈中弹出该元素,最后如果序列是正确的话,新栈的长度为0.

var validateStackSequences = function(pushed, popped) {
    //核心思路:用一个新栈来存放当前入栈的元素,判断当前入站的元素和出栈的元素是否相同,相同的话,就弹出该元素。如果顺序正确的话,最后的新栈长度为0
    var stack = []
    var index = 0
    for(var i = 0; i < pushed.length; i++) {
        stack.push(pushed[i]) 
        while(stack.length && stack[stack.length - 1] == popped[index]) {
            stack.pop()
            index += 1
        }
    }
    return stack.length == 0
};

2、用两个栈实现队列

题目:
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
思路:用两个栈实现,一个用于入栈A一个用于出栈B,入栈的话在末尾追加元素即可,出栈的话,先判断B中有没有元素,没有的话,从A中导入元素。

var CQueue = function() {
        this.stackA = []
        this.stackB = []
};
CQueue.prototype.appendTail = function(value) {
    return this.stackA.push(value)
};
CQueue.prototype.deleteHead = function() {
    if(this.stackB.length) {
        return this.stackB.pop()
    } else {
        while(this.stackA.length) {
            this.stackB.push(this.stackA.pop())
        }
        if(!this.stackB.length) return -1
        else {
            return this.stackB.pop()
        }

    }
};

3、自己包装最小的栈

题目:
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.min(); --> 返回 -2.
思路:用两个数组封装两个栈,一个用于存放各个元素值,一个用于存放最小值,在这里,存放最小值的初始值为Infinity(无穷大)。

var MinStack = function() {
    this.stack = []
    this.minStack = [Infinity]
};

MinStack.prototype.push = function(x) {
    this.stack.push(x)
    this.minStack.push(Math.min(this.minStack[this.minStack.length - 1], x))
};

MinStack.prototype.pop = function() {
    this.stack.pop()
    this.minStack.pop()
};

MinStack.prototype.top = function() {
    return this.stack[this.stack.length - 1]
};

MinStack.prototype.min = function() {
    return this.minStack[this.minStack.length - 1]
};

4、自己包装队列的最大值

题目:
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。若队列为空,pop_front 和 max_value 需要返回 -1
示例 1:
输入:
[“MaxQueue”,“push_back”,“push_back”,“max_value”,“pop_front”,“max_value”]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
思路:因为要求时间复杂度为1,所以在往最大值队列中存放的时候,就要和前一个存放的值尽心比较。

var MaxQueue = function() {
    this.list = []
    this.listMax = []
};

MaxQueue.prototype.max_value = function() {
    if(this.list.length == 0) {
        return -1
    }
    return this.listMax[0]
};

MaxQueue.prototype.push_back = function(value) {
    this.list.push(value)
    //如果现在这个数 比上一个数字 大 的话 先把上一个数字 删除 再追加这个元素
    while(value > this.listMax[this.listMax.length-1]) {
        this.listMax.pop()
    }
    this.listMax.push(value)
};

MaxQueue.prototype.pop_front = function() {
    if(this.list.length == 0) {
        return -1
    }
    var temp = this.list.shift() //如果队列前边删除的元素刚好等于最大值队列中的最大值的时候,最大值队列中的那个元素也得删除。
    //归根结底封装两个数组的目的就是这个,一个用于顺序存放和删除元素,一个用于存放最大值
    if(this.listMax[0] == temp) {
        this.listMax.shift()
    }
    return temp
};

<八>、链表

1、返回链表的倒数第K个节点

题目:
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
思路:快慢指针,让快指针先走k步,慢指针才开始,这样的话,当快指针走了n步的时候,慢指针走了n-k步,即从k步开始走。

var getKthFromEnd = function(head, k) {
   var fast = head
   var slow = head
   for(var i = 0; i < k; i++) {
       fast = fast.next
   }
   while(fast) {
       fast = fast.next
       slow = slow.next
   }
   return slow
};

2、从尾到头打印链表

题目:
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
思路:用一个数组来保存每一个节点的值,并且 head指针一直指向下一个head,最后把数组反转

var reversePrint = function(head) {
    var arr = []
    while(head) {
        arr.push(head.val)
        head = head.next
    }
    return arr.reverse()
};

3、反转链表

题目:
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
思路:改变指针指向,用cur保存当前节点,later保存的是正序的下一个节点,prev保存的是上一个节点。现在让cur指向头部,prev为空,当有cur的时候,就一直改变指针指向即可,最终返回prev

var reverseList = function(head) {
   var cur = head
   var prev = null
   var later
   while(cur) {
       later = cur.next
       cur.next = prev
       prev = cur
       cur = later
   }
   return prev
};

4、合并两个排序的链表

题目:
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
思路:创建一个新链表。并且让h指向该链表的头部;当两个链表有值的时候,分为三种情况,只有链表2,那么current.next = L2,返回 h.next; 只有链表1,那么current.next = L1,返回 h.next;再就是两个链表都有值,就比较大小即可。

var mergeTwoLists = function(l1, l2) {
   var current = new ListNode()
   var h = current
   while(l1 || l2) {
       if(!l2) {
           current.next = l1
           return h.next
       }
       if(!l1) {
           current.next = l2
           return h.next
       }
       if(l1.val < l2.val) {
           current.next =  l1
           l1 = l1.next
       } else {
           current.next =  l2
           l2 = l2.next
       }
       current = current.next
   }
   return h.next
};

5、两个链表的第一个公共节点

题目:
输入两个链表,找出它们的第一个公共节点。
思路:给定两个指针,a先走完链表A, 再走链表B的非公共部分,b先走完链表B,然后再走链表A的非公共部分,当这两步都走完之后,自然走到了两个链表的公共部分,这时候,输出a即可。

var getIntersectionNode = function(headA, headB) {
   var a = headA
   var b = headB
   while(a !== b) {
       a = a === null ? headB : a.next
       b = b === null ? headA : b.next
   }
   return a
};

6、删除一个链表的节点

题目:
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
注意:此题对比原题有改动
示例 1:
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
思路:分为删除的是第一个节点和不是第一个节点的情况,如果是第一个节点,那么head = head.next;如果不是第一个结点的话,就用prev来保存当前节点,current保存prev.next;
当prev.val = value的时候,prev.next = current.next;最后返回head即可。

var deleteNode = function(head, val) {
    if(head.val == val) head = head.next
    var prev = head
    var current = prev.next
    while(current) {
        if(current.val == val) {
            prev.next = current.next

        }
        prev = prev.next
        current = current.next
    }
    return head
};

7、复杂链表的复制

题目:
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
思路:
1、先在该节点的后边复制该节点
2、把新拷贝节点的random安排上
3、分离新旧节点
题解:

var copyRandomList = function(head) {
  if(head == null) return head
  //复制节点
  var node = head
  var copy = null
  while(node != null) {
      copy = new Node(node.val)
      copy.next = node.next
      node.next = copy
      node = node.next.next
  }

  //2.复制random
  var node = head
  while(node != null) {
      if(node.random != null) {
          node.next.random = node.random.next
      }
      node = node.next.next
  }

  //3.分离
  var newHead = head.next
  var node = head
  while(node != null && node.next != null) {
      temp = node.next 
      node.next = temp.next
      node = temp
  }
  return newHead
};

8、二叉搜索树转换成双向链表

题目:
我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。
下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。
思路:
1、用中序遍历去遍历
2、最后返回第一个节点

var treeToDoublyList = function(root) {
    //思路:封装一个中序遍历的函数 递归实现
    //先让head指向null pre指向当前节点
    //函数中处理到某一个节点时 pre指向node的上一个节点 node.left = pre pre.right = node pre = node 实现所有节点的遍历
    if(!root) return null
    //刚开始
    var head = null
    var pre = head

    function midInorder(node) {
        if(!node) return null
        //首先遍历左子树
        midInorder(node.left)

        //处理当前节点
        if(!pre) { //如果没有上一个节点 即遍历到左节点了 这时候让head指向该节点
            head = node
        } else {
            pre.right = node
        }
        node.left = pre
        pre = node
        //最后遍历右子树
        midInorder(node.right)
    }

    midInorder(root) //遍历完之后得到的是 最后一个节点node 但是在中序遍历的函数中定义的是 pre = node所以这里                        用pre指代当前node
    head.left = pre //让head的左节点指向最后一个节点
    pre.right = head //让最后一个节点的右节点指向头部head
    return head //返回第一个节点 即head指向的节点
};

<九>、正则表达式匹配

1、把字符串转换成数组

题目:
写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
思路:
1、采用正则表达式把符合条件的 筛选出来
2、判断 该数字是不是已经超过-2^32 到
2^32-1的范围
题解:

var strToInt = function(str) {
    // /^[+-]?\d+/ 表示以+或者是-开头,并且只能出现一次,\d等价于[0-9]表示[0-9]出现一次或者是多次 []表示具有其中的一个或者是多个
    const num = str.trim().match(/^[+-]?\d+/)
    if (!num) return 0

    const min = - (2 ** 31), max = (2 ** 31) - 1
    //逻辑运算符遵循 右结合 法则 右结合法则的还有 赋值运算符 
    //三元表达式采取右结合的方式 等号也采取右结合的方式
    return num <= min ? min : num >= max ? max : num
};

2、表示数值的字符串

题目:
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
数值(按顺序)可以分成以下几个部分:
若干空格
一个 小数 或者 整数
(可选)一个 ‘e’ 或 ‘E’ ,后面跟着一个 整数
若干空格
小数(按顺序)可以分成以下几个部分:
(可选)一个符号字符(’+’ 或 ‘-’)
下述格式之一:
至少一位数字,后面跟着一个点 ‘.’
至少一位数字,后面跟着一个点 ‘.’ ,后面再跟着至少一位数字
一个点 ‘.’ ,后面跟着至少一位数字
整数(按顺序)可以分成以下几个部分:
(可选)一个符号字符(’+’ 或 ‘-’)
至少一位数字
部分数值列举如下:
["+100", “5e2”, “-123”, “3.1416”, “-1E-16”, “0123”]
部分非数值列举如下:
[“12e”, “1a3.14”, “1.2.3”, “±5”, “12e+5.4”]
示例 1:
输入:s = “0”
输出:true
示例 2:
输入:s = “e”
输出:false
示例 3:
输入:s = “.”
输出:false
思路:
1、用正则表达式进行匹配
题解:

var isNumber = function(s) {
    return /^[+-]?(\d+(\.\d*)?|(\.\d+))(e[+-]?\d+)?$/i.test(s.trim())
};
// ?表示出现0次或者是1次;+表示出现一次或者是多次;*表示出现0次或者是1次;/i表示不区分大小写进行搜索;[]表示里边的数字可以出现一个或者是多个;/d等价于[0-9];

<十>、牛客网题目-华为机试题(用JS V8实现)

1、实现最小公倍数

输入 两个数字
输出 一个数字,为他两的最下公倍数
代码
为什么用while呢,因为有多个测试案例。每个测试案例都有一行输入和一行输出

while(str = readline()) { //读入每一行的输入,并且是字符串形式读入的
    var arr= str.split(' '); //把字符串转换成数组
 
    function getNum(a,b){
        var tem;
        while(b != 0){
            tem = a%b;
            a = b;
            b = tem;
        }
        return a;
    }
    function getRNum(a,b){
        return (a*b)/getNum(a,b);
    }
    print(getRNum(arr[0],arr[1])); //输出用 print实现
}

2、十进制整数转换成二进制后一的个数

输入 一行一个字符
输出 一个数字
思路:用一个count计数,如果该字符除以二有余数的话,count就加一,该字符等于该字符除以二
代码:

while(num = readline()) {
    var count = 0
    while(num > 0) {
        if(num % 2) {
            count++
        }
        num = Math.floor(num / 2)
    }
    print(count)
}

3、实现一个浮点型数据输入的四舍五入方法

输入:一行一个字符
输出:一个数字
代码:

while(line = readline()) {
    print(Math.round(line))
}

4、字符串反转

输入:一行字符串
输出:一行字符串
代码:

while(line = readline()){ //接收输入 默认是一个字符串形式的
    print(line.split("").reverse().join(""))
}

5、一串数字反转

输入:一行数字
输出:一行数字
代码:

while(line = readline()) {
    var res = ''
    for(var i = line.length - 1; i>= 0; i--) {
        res += line[i]
    }
    print(res)
}

6、汽水瓶

输入:多行数字,最后一个数字为0,表示该行结束
输出:多行数字,比输入少一行,少0的那一行
代码:

while(s = readline()) {
    if(s !== "0") {
        function h(i) {
            if(i < 2) return 0
            else if(i == 2) return 1
            else {
                let n = parseInt(i/3)
                let empty = n + i%3
                return n + h(empty)
            }
        }
        let num = parseInt(s)
        let all = h(num) 
        print(all)
        
    } else {
        break
    }
}

7、实现数组相乘

输入:多行
输出:多行
代码:

while(line = readline()) {
    let row1 = +line.trim()
    let col1 = +readline().trim()
    let col2 = +readline().trim()
    let array1 = []
    let array2 = []
    for (let i=0; i<row1; i++) {
        array1.push(readline().trim().split(' ').map(Number))
    }
    for (let i=0; i<col1; i++) {
        array2.push(readline().trim().split(' ').map(Number))
    }
    let res = getResult(row1, col1, col2, array1, array2) // x,y,z,xy array,yz array
    console.log(res)
}

function getResult(row1, col1, col2, array1, array2) {
    let array3 = []
    let res = []
    for (let i=0; i<row1; i++) { // xz array
        array3.push(new Array(col2))
    }
    for (let i = 0; i < row1; i++) {//x
        for (let j = 0; j < col2; j++) {//z
            let sum = 0;
            for (let k = 0; k < col1; k++) {//y
                sum += array1[i][k] * array2[k][j];
            }
            array3[i][j] = sum;
        }
    }
       
    for (let i = 0; i < array3.length; i++) {
        res.push(array3[i].join(' '))
    }
    return res.join('\n')
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值