这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。
21. 下一个排列 (next-permutation)
标签
- 语文理解
- 中等
题目
这里不贴题了,leetcode打开就行,题目大意:
实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。必须原地修改,只允许使用额外常数空间。
简单来说所谓下一个排列结果其实是这些数排列组合后变成的整数。比如[1, 2, 3] 有排列
[1, 2, 3] < [1, 3, 2] < [2, 1, 3] < [2, 3, 1] < [3, 1, 2] < [3, 2, 1]
123 < 132 < 213 < 231 < 312 < 321
下一个排列就是这个顺序的下一个是那个,如果已经最大就回到队首变成最小的。
基本思路
-
我们希望下一个数比当前数大,这样才满足“下一个排列”的定义。因此只需要将后面的
大数与前面的小数交换,就能得到一个更大的数。比如213,将1和3交换就能得到一个更大的数231。 -
我们还希望下一个数增加的
幅度尽可能的小,为了满足这个要求,我们需要:在尽可能靠右的低位进行交换,需要从后向前查找将一个尽可能小的大数与前面的小数交换。比如1243,下一个排列应该把3和2交换而不是把4和2交换。将大数换到前面后,需要将大数后面的所有数重置为升序,升序排列就是最小的排列。 -
已经是最大的了,是个降序(最大),直接逆置变成升序(最小)。
步骤
- 从后向前查找第一个相邻升序的元素对 (i, j),满足
A[i] < A[j]。此时[j,end)必然是降序,如果找不到符合的相邻元素对,说明当前[begin, end)为一个降序顺序,则直接跳到步骤 4 - 在 [j,end) 从后向前查找
第一个满足A[i] < A[k]的k。A[i]、A[k]分别就是上文所说的小数、大数 - 将
A[i]与A[k]交换 - 可以断定这时
[j, end)必然是降序,逆置[j, end),使其升序
写法实现
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var nextPermutation = function(nums) {
let len = nums.length
if (len <= 1) {
return nums
}
let [i, j, k] = [len - 2, len - 1, len - 1]
// 找到第一组相邻升序
while (i >= 0 && nums[i] >= nums[j]) {
i--;
j--;
}
// 不是最后一个排列,寻找k,使得A[i]<A[k]
if (i >= 0) {
while (nums[i] >= nums[k]) {
k--;
}
// 解构赋值交换 A[i], A[k]
[nums[i], nums[k]] = [nums[k], nums[i]]
}
// [j, end)转升序,使数变小
for (i = j, j = len - 1; i < j; i++, j--) {
[nums[i], nums[j]] = [nums[j], nums[i]]
}
// console.log(nums)
};
console.log(nextPermutation([3,2,1]))
22. 二分查找 (binary-search)
标签
- 基本查找
- 简单
题目
这里不贴题了,leetcode打开就行,题目大意:
二分查找是一种基于比较目标值和数组中间元素的教科书式算法。
基本思想
- 如果目标值等于中间元素,则找到目标值。
- 如果目标值较小,继续在左侧搜索。
- 如果目标值较大,则继续在右侧搜索。
所以注意使用前提是数组是有序的。每次排除一半错误答案。
步骤
- 初始化指针
left = 0, right = n - 1。 - 当
left <= right时- 比较中间元素
nums[pivot]和目标值target。 - 如果
target === nums[pivot],返回 pivot。 - 如果
target < nums[pivot],则在左侧继续搜索right = pivot - 1。 - 如果
target > nums[pivot],则在右侧继续搜索left = pivot + 1。
- 比较中间元素
写法实现
var binarySearch = function(nums, target) {
// 设置左右指针和中间位基准
let [left, right, pivot] = [0, nums.length - 1, 0]
while (left <= right) {
pivot = left + Math.floor((right - left) / 2)
if (nums[pivot] === target) {
return pivot
} else if (target < nums[pivot]) {
right = pivot - 1
} else {
left = pivot + 1
}
}
return -1
};
console.log(binarySearch([-1,0,3,5,9,12], 9))
23. 搜索旋转排序数组 (search-in-rotated-sorted-array)
标签
- 二分查找
- 中等
题目
这里不贴题了,leetcode打开就行,题目大意:
假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。你可以假设数组中不存在重复的元素。(数组中的每个值都 独一无二)
旋转的含义其实就是原本有序的数列中有一个断开点。把后面随机一段有序的序列放到数组前面,这样形成了前后2段有序的序列。
相关知识
上面的二分查找是这题的铺垫,这题是二分查找的稍加变化。
基本思路
由于数组基本有序,虽然中间有一个“断开点”,还是可以使用二分搜索的算法来实现。
right
[ 大数值区间 ] |
[4, 5, 6, 7, 0, 1, 2]
| | [ 小数值区间 ]
left pviot
其实这数列分2段,一段是小数值区间,一个是大数值区间。
-
我们看如果中间的
pviot落在了 大数值区间,则有nums[pviot] > nums[left]、nums[pviot] > nums[right]像上图一样。 -
如果中间的
pviot落在了 小数值区间,则有nums[pviot] < nums[left]、nums[pviot] < nums[right], 像下图一样。
right
[ 大数值区间 ] |
[6, 7, 0, 1, 2, 4, 5]
| [| 小数值区间 ]
left pviot
判断出落在哪个区间内后,再判断基准和target的关系,再调整左右边界用二分法处理问题。
写法实现
var search = function(nums, target) {
let [len, left, right, pivot] = [nums.length, 0, nums.length - 1, 0]
if (len === 0) {
return -1
}
while (left <= right) {
pivot = left + Math.floor((right - left) / 2)
if (nums[pivot] === target) {
return pivot
} else if (nums[pivot] >= nums[left]) {
// 说明基准在数值大的一部分区间中,再调整左右边界
if (nums[left] <= target && target < nums[pivot]) {
right = pivot - 1
} else {
left = pivot + 1
}
} else if (nums[pivot] <= nums[right]) {
// 说明基准在数值小的一部分区间中,再调整左右边界
if (nums[pivot] < target && target <= nums[right]) {
left = pivot + 1
} else {
right = pivot - 1
}
}
}
return -1
};
let nums = [4, 5, 6, 7, 0, 1, 2], target = 1
console.log(search(nums, target))
另外向大家着重推荐下这位大哥的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列
今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦
搜索我的微信号infinity_9368,可以聊天说地
加我暗号 "天王盖地虎" 下一句的英文,验证消息请发给我
presious tower shock the reiver monster,我看到就通过,暗号对不上不加哈,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧
参考