前言
本文将详细解析两道常见的 JavaScript 面试题:如何去除对象数组中的重复元素以及如何实现快速排序算法。
- 数组去重:不是像leetcode里面的算法题一样存的全部都是数字,还会含有对象。
- 快速排序:通过二分法快速排序
正文
一、对象数组去重
问题描述
假设我们有一个对象数组,其中可能包含相同的对象(这里的“相同”是指对象的属性值完全一致,我们肉眼看上去一模一样的)。在js中是没有内置方法可以做到这样的需求的,所有我们要手写一个方法,它传入一个数组,然后返回一个新数组,其中去除了所有重复的对象。
完整代码
const arr = [
{age: 18, name: '老王', like: {n: 1} },
{name: '剑哥', age: 19 },
{name: '宇哥', age: 18 },
{name: '老王', age: 18, like: {n: 1} }
]
function uniqueArr(arr) {
let res = []
for (let i = 0; i < arr.length; i++) {
let isFind = false
for (let j = 0; j < res.length; j++) {
if (equals(arr[i], res[j])) {
isFind = true
break;
}
}
if (!isFind) {
res.push(arr[i])
}
}
return res
}
function equals(v1, v2) {
if ((typeof v1 === 'object' && v1 !== null) && (typeof v2 === 'object' && v2 !== null)) {
if (Object.keys(v1).length !== Object.keys(v2).length) return false
for (let key in v1) {
if (v2.hasOwnProperty(key)){
// 值是否相同 v1[key] v2[key]
if (!equals(v1[key], v2[key])) return false
} else {
return false
}
}
return true
} else {
return v1 === v2
}
}
console.log(uniqueArr(arr));
代码解释
要去重的数组:
const arr = [
{age: 18, name: '老王', like: {n: 1}},
{name: '剑哥', age: 19},
{name: '宇哥', age: 18},
{name: '老王', age: 18, like: {n: 1}}
];
我们假设这个要去重的数组是这样的,很明显有老王的两个对象是相同的要去掉一个,这是随便写的例子。
uniqueArr函数
在 uniqueArr
函数中,res
数组是用来存储不重复的值。
让我们一步一步来解析这段代码是如何工作的:
function uniqueArr(arr) {
let res = []; // 初始化一个空数组用来存放不重复的对象
for (let i = 0; i < arr.length; i++) { // 遍历输入数组 arr
let isFind = false; // 标记是否找到了重复的对象
for (let j = 0; j < res.length; j++) { // 遍历 res 数组
if (equals(arr[i], res[j])) { // 检查 arr[i] 是否与 res[j] 相等
isFind = true; // 如果相等,则标记为找到重复
break; // 结束内层循环
}
}
if (!isFind) { // 如果没有找到重复
res.push(arr[i]); // 将 arr[i] 添加到 res 数组中
}
}
return res; // 返回不重复的对象数组
}
- 当
arr
中的第一个元素被处理时,第一次进入到外层循环,res
数组是空的 ([]
)对吧,也就是说res.length
为 0,所以此时内层循环不会执行,for (let j = 0; j < 0; j++)
就啥也不干。isFind
会保持为false
,(isFind
是用来判断是否是否又相同的)然后直接跳到下面那个if
判断,所以arr
的第一个就会被添加到res
数组中,第一个肯定不会有重复咯,所有直接放很好理解。 - 接下来每次处理
arr
中的新元素时,都要和res
里面的值比吧,所有这个时候我们多么希望有这么一个方法我传入两个对象就可以比较两个对象是否相等,想了半天js
中没有内置方法可以做到比较两个对象,毕竟没有两个对象的引用地址是相同的,所有我们又得手写一个方法来实现这个功能(也就是代码里的equals()
函数),那么好,如果相同,isFind
为ture
说明相同吧,到下面的if
就不会放去,如果不相同就放进去。
equals函数
在 equals
函数中,我们通过递归的方式来比较两个对象是否相等。
让我们一步一步来解析这段代码是如何工作的:
function equals(v1, v2) {
if ((typeof v1 === 'object' && v1 !== null) && (typeof v2 === 'object' && v2 !== null)) { 检查 v1 和 v2 是否都是对象类型且非 null
if (Object.keys(v1).length !== Object.keys(v2).length) return false; // 如果两个对象的键数量不同,则它们不相等
for (let key in v1) { // 遍历 v1 的所有键
if (v2.hasOwnProperty(key)){ // 检查 v2 是否有相同的键
if (!equals(v1[key], v2[key])) return false; // 使用递归调用 equals 函数来比较键对应的值
} else {
return false; // 如果 v2 没有相同的键,则它们不相等
}
}
return true; // 如果所有键和值都相等,则两个对象相等
} else {
return v1 === v2; // 如果 v1 和 v2 不是对象类型,则直接使用 === 比较
}
}
- 先判断是否是引用类型,
typeof
可以判断原始类型除null
会判定objec
t,引用类型除function
判定为function
外其他都是object
,所有if
里面就可以判断是不是一个对象了,是引用类型的话,我们接着通过比较它们键的个数先快速判断是不是相同,筛选掉一部分对吧,如果这个值是原始类型就直接使用===
比较,返回比较结果。 - 接着遍历
v1
的所有键,每循环一个v1
然后用hasOwnProperty()
判断v2
是否有相同的键名,如果v2
没有相同的键名,则它们不相等,返回false
,如果有,就递归调用equals
函数来比较键对应的值,重来一遍对吧,如果这个值是原始类型就直接使用===
比较,如果还是引用类型就继续递归,一旦有不相等就返回flase
,如果都没有问题那最后返回true
。
运行结果
[
{age: 18, name: '老王', like: {n: 1}},
{name: '剑哥', age: 19},
{name: '宇哥', age: 18}
]
二、快速排序算法
问题描述
实现快速排序算法,该算法是一种高效的排序方法,采用二分法来给数组快速排序,假设就从小到大排序。
示例代码
let arr = [2, 7, 4, 1, 3, 5]
function quickSort(arr) {
if (arr.length < 2) return arr
let middleIndex = Math.floor(arr.length / 2)
let middle = arr.splice(middleIndex, 1)[0]
let left = []
let right = []
for (let i = 0; i < arr.length; i++) {
if (arr[i] < middle){
left.push(arr[i])
} else {
right.push(arr[i])
}
}
// return quickSort(left).concat([middle], quickSort(right))
return [...quickSort(left), middle, ...quickSort(right)]
}
console.log(quickSort(arr));
代码解释
要排序的数组:
let arr = [2, 7, 4, 1, 3, 5]
这是一个简单的数字数组,我们需要将其按升序排序。
quickSort 函数
在 quickSort
函数中,我们首先检查数组的长度。如果数组的长度小于 2,这意味着数组要么为空要么只包含一个元素,这两种情况下无需排序,直接返回原数组即可。
接下来,我们选择一个基准值(pivot),这里选择的是数组中间位置的元素。然后我们将数组分为两部分:一部分包含所有小于基准值的元素,另一部分包含所有大于或等于基准值的元素。
让我们一步一步来解析这段代码是如何工作的:
function quickSort(arr) {
if (arr.length < 2) return arr; // 如果数组长度小于 2,直接返回数组
let middleIndex = Math.floor(arr.length / 2); // 找到数组中间位置的索引
let middle = arr.splice(middleIndex, 1)[0]; // 移除中间位置的元素作为基准
let left = []; // 左侧数组,用于存放小于基准值的元素
let right = []; // 右侧数组,用于存放大于或等于基准值的元素
for (let i = 0; i < arr.length; i++) {
if (arr[i] < middle) { // 如果元素小于基准值
left.push(arr[i]); // 放入左侧数组
} else {
right.push(arr[i]); // 否则放入右侧数组
}
}
// 使用递归的方式分别对左右两侧的数组进行快速排序,然后合并结果
// return quickSort(left).concat([middle], quickSort(right));
return [...quickSort(left), middle, ...quickSort(right)]; // 返回合并后的数组
}
console.log(quickSort(arr)); // 输出排序后的数组
- 我们从数组中间位置选择一个元素作为基准值(pivot),这样做的目的是为了尽可能平均地分割数组,提高排序效率。 使用
Math.floor(arr.length / 2)
来获取中间位置的索引,第一次的middleIndex
为3,我们用splice
方法移除该位置的元素,同时将其赋值给变量middle
,第一次middle
即中间值为1,这时的arr为[2,7,4,3,5]。 - 用
for
循环遍历ar
,比middle
小的放左边数组left
,大的就放右边数组right
,然后左边的数组再进入到quickSort()
函数再次找到中间值比较,右边的数组也再次进入到quickSort()
函数再次找到中间值比较,比它小的放左边,大的放右边,直到每个数组只剩一个元素,那么返回它们,一个一个拼接拼接的返回。
过程:
第一次的结果:
1 [2, 7, 4, 3, 5]
第二次的结果:
1
[2, 3] 4 [5, 7]
第三次的结果:
1
4
[2] 3 |
[5] 7
第四次的结果:
1
4
3 |
7
[2] |
[5]
return返回:
[2] |
[5]
[2,3] |
[5,7]
[2,3,4,5,7]
[1,2,3,4,5,7]
运行结果
[1, 2, 3, 4, 5, 7]
总结
以上就是这两道面试题的详细解析。希望这篇文章能帮助你更好地理解和掌握 JavaScript 中的对象数组去重和快速排序算法。
本文到这里就结束了,感谢你的阅读!希望对你有所帮助!