一:移动零
思路:本质上就是快慢指针的问题。
代码一:
var moveZeroes = function(nums) {
if(nums.length < 1) return nums
let slow = 0, fast = 0;
while(fast < nums.length){
if(nums[fast] !== 0){
nums[slow] = nums[fast]
slow++
}
fast++
}
while(slow<fast){
nums[slow] = 0
slow++
}
return nums
};
二:移除元素
思路:本质上还是快慢指针的问题。
代码一:
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function(nums, val) {
var n = nums.length
var slow = 0
for(let fast = 0; fast < n; fast++){
if(nums[fast] !== val){
nums[slow] = nums[fast]
slow++
}
}
return slow
};
三:删除有序数组中的重复项 II
思路:当我们阅读题目的时候,可以看到是一个有序的数组,所以相同的元素一定是相邻的。当看到需要删除重复项的时候,可以想到常用的方法:双指针(快慢指针)法。用快指针(fast)来记录需要查看比较的元素,slow来记录数组长度。可以看到题目是需要保留两个相同的元素,所以可以通过比较nums[fast]和nums[slow-2]的值来确定是否移动慢指针。
代码一:
/**
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function(nums) {
var n = nums.length
if(n <= 2){
return n
}
let fast = 2, slow = 2
while(fast < n){
if(nums[slow -2] !== nums[fast]){
nums[slow] = nums[fast]
++slow
}
++fast
}
return slow
};
四:颜色分类
思路:其实单看题目,第一反应会感觉就是很简单的排序问题。但是题目有做一定的限制:就是不能使用库的sort函数。所以看这个问题就能知道这是很经典的「荷兰国旗问题」
ps:荷兰国旗🇳🇱是由红白蓝三种颜色组成。
什么是荷兰国旗问题?对只含有三种数值的数进行分类或者排序。
代码一:
var sortColors = function(nums) {
let left = -1;
let right = nums.length;
let i = 0;
// 下标如果遇到 right,说明已经排序完成
while (i < right) {
if (nums[i] == 0) {
swap(nums, i++, ++left);
} else if (nums[i] == 1) {
i++;
} else {
swap(nums, i, --right);
}
}
};
function swap(array, left, right) {
let rightValue = array[right];
array[right] = array[left];
array[left] = rightValue;
}
五:合并两个有序数组
题目:
方法一:直接合并之后再排序
知识点:
Array.splice()
定义: 向/从数组中添加/删除/替换项目,然后返回被删除的项目。 语法 ArrayObject.splice(index,howmany,item1,.....,itemX)
- index——必须,整数,规定添加或者删除的位置,使用负数,从数组尾部规定位置。
- howmany——必须,要删除的数量,如果为0,则不删除项目。
- item1,…itemx——可选,向数组添加的新项目。
一:删除: 指定两个参数:要删除的第一项的位置和要删除的项数。
二:插入: 三个参数:起始位置,0(要删除的项数),要插入的项。
三:替换 三个参数:起始位置,要删除的项数和要插入的任意数量的项。
sort()函数 1,无参数的情况:如果不传入比较函数,默认会以字典排序的顺序为结果 2,传入比较函数的情况:具体的结果和比较函数有关。
代码一:
/**
* @param {number[]} nums1
* @param {number} m
* @param {number[]} nums2
* @param {number} n
* @return {void} Do not return anything, modify nums1 in-place instead.
*/
var merge = function(nums1, m, nums2, n) {
// 合并数组
nums1.splice(m, nums1.length - m, ...nums2)
// 排序
nums1.sort((a,b)=>a-b)
};
代码二: 双指针,分别指向两个数据,遍历两个数组然后进行比较,将数值比较小的值塞入排序数组中
var merge = function(nums1, m, nums2, n) {
let p1 = 0, p2 = 0;
const sorted = new Array(m + n).fill(0);
var cur;
while (p1 < m || p2 < n) {
if (p1 === m) {
cur = nums2[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 (let i = 0; i != m + n; ++i) {
nums1[i] = sorted[i];
}
};
代码三:代码二是需要开辟额外的数组空间来先储存排序后的数组。先我们可以在原数组上直接进行操作,从前往后操作容易出现值被覆盖的情况,那么我们就从后往前进行操作,那这样就不会存在值被覆盖的情况了。
/**
* @param {number[]} nums1
* @param {number} m
* @param {number[]} nums2
* @param {number} n
* @return {void} Do not return anything, modify nums1 in-place instead.
*/
var merge = function(nums1, m, nums2, n) {
// 先定一好指针
let p0 = m -1,p1 = n -1,all = m + n - 1
let cur
// 比较逻辑处理
while(p0 >= 0 || p1 >= 0){
if(p0 === -1){
cur = nums2[p1--]
}else if(p1 === -1){
cur = nums1[p0--]
}else if(nums1[p0] > nums2[p1]){
cur = nums1[p0--]
}else{
cur = nums2[p1--]
}
nums1[all--] = cur
}
};
六:两数之和 II - 输入有序数组
/**
* @param {number[]} numbers
* @param {number} target
* @return {number[]}
*/
var twoSum = function(numbers, target) {
// 定义两个指针,right,left
// 如果两数之和等于target,返回下标。如果两数之和大于target,right--,反之left++
let left = 0,right = numbers.length - 1
while(left < right) {
if(numbers[left] + numbers[right] == target){
return [++left, ++right]
}else if(numbers[left] + numbers[right] < target){
left++
}else{
right--
}
}
};
七:验证回文串
方法一:双撞指针
/**
* @param {string} s
* @return {boolean}
*/
var isPalindrome = function(s) {
// 判断是否是空字符串,如果是空,直接返回true
if(s == ''){
return true
}
// 利用正则去掉非字母和空字符串,然后将大写转换为小写
s=s.replace(/[^a-zA-Z0-9]/g,"").replace(/\s/g,"").toLowerCase()
// 双指针进行对比
let left = 0, right = s.length -1
while(left < right){
if(s[left] !== s[right]){
return false
}else{
left++
right--
}
}
return true
};
方法二:利用js的API,将字符串转化成数组,用reverse()颠倒元素顺序,再用join()将其转换成字符串于原字符串进行对比。
* @param {string} s
* @return {boolean}
*/
var isPalindrome = function(s) {
// 判断是否是空字符串,如果是空,直接返回true
if(s == ''){
return true
}
// 利用正则去掉非字母和空字符串,然后将大写转换为小写
s=s.replace(/[^a-zA-Z0-9]/g,"").replace(/\s/g,"").toLowerCase()
return s === [...s].reverse().join('')
};
八:反转字符串中的元音字母
知识点:Array.from()
Array.from()方法对一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
代码一:
/**
* @param {string} s
* @return {string}
*/
var reverseVowels = function(s) {
const n = s.length;
const arr = Array.from(s);
let i = 0, j = n - 1;
while (i < j) {
while (i < n && !isVowel(arr[i])) {
++i;
}
while (j > 0 && !isVowel(s[j])) {
--j;
}
if (i < j) {
swap(arr, i, j);
++i;
--j;
}
}
return arr.join('');
};
// 判断是否是元音
const isVowel = (ch) => {
return 'aeiouAEIOU'.indexOf(ch) >= 0
}
// 交换函数
const swap = (arr, a, b)=>{
let temp = arr[a]
arr[a] = arr[b]
arr[b] = temp
}
九:盛最多水的容器
代码一:
/**
* @param {number[]} height
* @return {number}
*/
var maxArea = function(height) {
// 定义变量,左右指针,面积变量,最大面积存放变量
var n = height.length
let right = n - 1, left = 0, area = 0, max = 0
// 逻辑处理
while(left < right){
area = Math.min(height[left], height[right])*(right - left)
max = Math.max(max, area)
if(height[left] > height[right]){
right--
}else{
left++
}
}
return max
};
十:长度最小的子数组
方法一:
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
/**
* @param {number} target
* @param {number[]} nums
* @return {number}
*/
var minSubArrayLen = function(target, nums) {
// 定义变量,起始位置,结束位置,数组和变量,存储最小长度变量
let n = nums.length
var start = 0, end = 0, sum = 0, ans = n + 1
// 不断移动end指针,将sum[end]值相加,当sum>=target时移动start指针
while(end < n){
sum += nums[end]
if(sum >= target){
while(sum - nums[start] >= target){
sum -= nums[start++]
}
ans = Math.min(ans, end-start+1)
}
end++
}
return ans === n+1 ? 0 : ans
};
十一:寻找数组的中心下标
方法一:
/**
* @param {number[]} nums
* @return {number}
*/
var pivotIndex = function(nums) {
for(let i = 0; i < nums.length; i++){
if(sum(sliceArr(nums, i)[0]) === sum(sliceArr(nums, i)[1])){
return i
}
}
return -1
};
//数组分割
var sliceArr = (arr, index)=>{
return [arr.slice(0, index), arr.slice(index+1, arr.length)]
}
// 求和
var sum = (arr) => {
if(arr.length === 0){
return 0
}
return arr.reduce((prev,curr)=>prev+curr)
}
方法二(官方答案):前缀和
/**
* @param {number[]} nums
* @return {number}
*/
var pivotIndex = function(nums) {
var total = nums.reduce((prev,curr)=>prev+curr)
var sum = 0
for(let i = 0; i < nums.length; i++){
if(2*sum + nums[i] === total){
return i
}
sum += nums[i]
}
return -1
};
十二:搜索插入位置
方法一:
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var searchInsert = function(nums, target) {
for(let i = 0; i < nums.length; i++){
if(target <= nums[i]){
return i
}
}
return nums.length
};
方法二:(官方答案:二分查找算法)
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var searchInsert = function(nums, target) {
// 使用二分查找
var n = nums.length
// ans即最终答案,开始的时候设置为要查询的数组最大下标
var left = 0, right = n -1, ans = n
while(left <= right){
// 此操作等同于求平均值
let mid = ((right - left) >> 1) + left
if(target <= nums[mid]){
ans = mid
right = mid - 1
}else{
left = mid + 1
}
}
return ans
};
十三:合并区间
代码一:
/**
* @param {number[][]} intervals
* @return {number[][]}
*/
var merge = function(intervals) {
// 如果数组长度只有1个,直接返回
if(intervals.length === 1) return intervals
// 根据数组的第一个元素进行排序(从小到大)
const sortedIntervals = intervals.sort((a,b) => a[0] - b[0])
// 逐个比较
const result = []
let current = sortedIntervals[0]
for(let i = 1; i < sortedIntervals.length; i++) {
// 循环比较后面的区间
const interval = sortedIntervals[i]
// 如果下一个区间的最小值,存在于当前区间之中,就合并
if(interval[0] <= current[1]) {
// 合并取两个区间的最大值
current[1] = Math.max(current[1], interval[1])
}else{
// 如果不在前一个区间内,说明当前区间于下一个区间不连续,则把当前区间添加到结果集中
result.push(current)
current = interval
}
}
result.push(current);
return result;
};