文章目录
-
- 好未来数据结构与算法题
- 1、快排的时间复杂度是多少?怎么计算的?空间复杂度是多少?怎么计算的?手写冒泡和快排?
- 2、常见的数据结构和应用场景
- 3、合并两个有序数组并进行排序
- 4、二维数组相加相乘怎么计算?
- 6、求前十个最大的值
- 8、奖英文字符串按照大小写反转,并且以空格进行逆序
- 9、100个人循环报数,报到5的时候就淘汰,最后剩下一个人
- 10、reduce求和
- 11、实现数组的乱序输出
- 12、将出现次数最多的前k 个单词输出
- 13、匹配括号
- <一>、基本算法题
- 1、双指针—和为s的两个数字
- 2、和为s的连续正数序列
- 3、二进制中1的个数
- 4、不用加减乘除做加法
- 5、实现1+2+...+n不用乘除法
- 6、斐波那契数列
- 7、青蛙跳台阶问题
- 8、扑克牌中的顺子
- 9、数值的整数次方 Math.pow(x,n)的实现
- 10、数字序列中某一位的数字
- 11、n个筛子的点数
- 12、机器人的运动范围之广度优先搜索
- <二>、数组
- 常见方法
- 1、查找重复元素
- 2、调换数组元素
- 3、查找出现次数超过一半的元素
- 4、数组中只出现一次的两个元素
- 5、数组中只出现一次的一个数字
- 6、打印从1到n的最大n位数
- 7、求一个连续整数值组成数组中的缺失值
- 8、数组排序
- 9、求数组中最小的前k个数
- 10、统计一个数字在数组中出现的次数
- 11、圆圈中最后剩下的那个数字
- 12、二维数组的查找
- 13、顺时针打印矩阵
- 14、数组乘积
- 15、把数组排成最小的数
- 16、矩阵中的路径之单词的查找
- <三>、字符串
- 常见方法
- 1、找出只出现一次的字符
- 2、左旋转字符串
- 3、字符串的替换
- 4、最长不含重复字符的字符串
- 5、字符串的排序之所有出现的可能
- <四>、动态规划
- 题目特点:
- 解题思路
- 1.1、最值问题之硬币
- 1.2、最值问题之礼物的最大价值
- 1.3、最值问题之股票的最大利润
- 1.4、最值问题之剪绳子一
- 2.1、计数问题之机器人走格子
- 2.2、计数问题之把数字翻译成字符串
- 2.3计数问题之求丑数
- 3.1、存在性问题之青蛙跳台阶
- <五>、贪心算法
- 1、求子数组的最大和
- 六、二叉树
- 前序中序后序遍历的理解
- 1、二叉树镜像
- 2、求二叉树的深度
- 3、二叉树的最近的公共祖先
- 4、二叉搜索树的最近的公共祖先
- 5、平衡二叉树
- 6、对称二叉树
- 7、从上到下打印出二叉树的每一个节点,放在一个数组中
- 8、从上往下打印二叉树,每层从左往右放一行
- 9、打印二叉树,第一行从左往右放,第二行从右往左放
- 10、二叉搜索树的第k大节点
- 11、二叉搜索树的后序遍历
- 12、树的子结构
- 13、重建二叉树
- 14、二叉树中和为某一值的路径
- <七>、栈和队列
- 1、栈的压入和弹出序列
- 2、用两个栈实现队列
- 3、自己包装最小的栈
- 4、自己包装队列的最大值
- <八>、链表
- 1、返回链表的倒数第K个节点
- 2、从尾到头打印链表
- 3、反转链表
- 4、合并两个排序的链表
- 5、两个链表的第一个公共节点
- 6、删除一个链表的节点
- 7、复杂链表的复制
- 8、二叉搜索树转换成双向链表
- <九>、正则表达式匹配
- 1、把字符串转换成数组
- 2、表示数值的字符串
- <十>、牛客网题目-华为机试题(用JS V8实现)
- 1、实现最小公倍数
- 2、十进制整数转换成二进制后一的个数
- 3、实现一个浮点型数据输入的四舍五入方法
- 4、字符串反转
- 5、一串数字反转
- 6、汽水瓶
- 7、实现数组相乘
好未来数据结构与算法题
1、快排的时间复杂度是多少?怎么计算的?空间复杂度是多少?怎么计算的?手写冒泡和快排?
时间复杂度:指的是算法完成需要的时间
空间复杂度:指的是算法完成需要的存储空间
冒泡排序的时间复杂度是O(n^2); 空间复杂度是O(1)
快速排序的时间复杂度是O(nlg(n));空间复杂度是O(n),这里的n相当于压栈的次数
快速排序的时间复杂度计算
快速排序采用的是分区排序的方式,假设数据个数为n,每次分区的比例为1:9,那么每次遍历的数据个数均为n。用递归树表示,现在要求的就是递归树的高度
因为对数的复杂度,不管底数是多少,均为底数为10,所以递归树深度为 lg(n),再乘以每层需要遍历的数据个数,所以为O(nlg(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)