文章目录
- 1 两个数之和(2020.09.12)
- 2 删除排序数组中的重复项(27,2020.09.15)
- 3 移除元素(27,2020.09.18)
- 4 搜索插入位置(35, 2020.09.21)
- 5 最大子序和(53, 2020.09.23)
- 6 加一(66, 2020.09.29)
- 7 合并两个有序数组(88, 2020.10.01)
- 8 杨辉三角(Pascal's triangle)(118, 2020.10.05)
- 9 杨辉三角 II(Pascal's triangle)(119, 2020.10.07)
- 10 买卖股票的最佳时机(121,2020.10.10)
- 做好初始定义
- 运用基础算法思想
- 双索引技巧 - 对撞指针
- 20 三数之和(15,2021.02.01)
1 两个数之和(2020.09.12)
1 自己解法:
思路:主要是双循环来一个一个试.
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
let res = []
for(let i = 0; i<nums.length; i++){
for(let j = i+1 ; j<nums.length; j++){
if(nums[i] + nums[j] === target){
res.push(i, j)
return res
}
}
}
};
2 优秀解法
注:此题使用ES6的Map,使得时间复杂度降为O(n)
关于Map和Set的使用要重点掌握!!!
var twoSum = function(nums, target) {
var map=new Map();
for(let i=0;i<nums.length;i++){
if(!map.has(target-nums[i])){
map.set(nums[i],i);
}else{
return [map.get(target-nums[i]),i]
}
}
};
2 删除排序数组中的重复项(27,2020.09.15)
1 自己的解法:
双循环来判断去重
/**
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function(nums) {
for(let i = 0; i<nums.length; i++){
let temp_len = nums.length
let j = i + 1
while(j<nums.length){
if(nums[i] === nums[j]){
nums.splice(j, 1)
j = i + 1
}else{
j++
}
}
}
};
2 优秀解法1
利用两个变量来比较
var removeDuplicates = function(nums) {
let cur = nums[0]
let i = 1
while(i < nums.length){
if(nums[i] === cur){
nums.splice(i, 1)
}else{
cur = nums[i++]
}
}
return nums.length
};
3 优秀解法2
双指针, 思路地址
var removeDuplicates = function(nums) {
let slow = 0
let fast = 1
while(fast<nums.length){
if(nums[slow] === nums[fast]){
fast++
}else{
nums[++slow] = nums[fast]
}
}
nums.splice(slow+1)
return nums.length
}
3 移除元素(27,2020.09.18)
1 自己的解法:
逐个比对删除
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function(nums, val) {
let i = 0
while(i<nums.length){
if(nums[i] === val){
nums.splice(i, 1)
}else{
i++
}
}
return nums.length
};
2 优秀解法1
双指针:
var removeElement = function(nums, val){
let i = 0
for(let j=0; j<nums.length; j++){
if(nums[j] !== val){
nums[i] = nums[j]
i++
}
}
return i
}
3 优秀解法2
双指针优化-当删除元素很少时
var removeElement = function(nums, val){
let i = 0
let len = nums.length
while(i<len){
if(nums[i] === val){
nums[i] = nums[len-1]
len--
}else{
i++
}
}
return len
}
4 搜索插入位置(35, 2020.09.21)
1 自己的解法:
思路:
- 先考虑相等情况然后返回index
- 然后考虑不相等情况(除了tar比数字任何数都大的情况), 当target小于当前的数组的值时, 返回对应的index即可.
- 最后tar比数字任何数都大的情况, 直接返回数组长度
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var searchInsert = function(nums, target) {
for(let i=0; i<=nums.length; i++){
if(nums[i] === target && i<nums.length){
return i
}else if(nums[i]>target && i<nums.length){
return i
}else if(i === nums.length){
return nums.length
}
}
};
自己的解法-优化
- 首先tar比数字任何数都大的情况, 直接返回数组长度
- 然后当target小于等于当前的数组的值时, 返回对应的index即可.
var searchInsert = function(nums, target) {
let len = nums.length
if(target > nums[len-1] )
return len
for(let i=0; i<len; i++){
if(target <= nums[i]){
return i
}
}
};
2 优秀的解法1:
二分查找:
mid=(left+right)>>1
的含义
右移运算符>>
,运算结果正好能对应一个整数的二分之一值,这就正好能代替数学上的除2运算,但是比除2运算要快。
mid=((left+right)>>1)
(建议用括号包裹)相当于mid=(left+right)/2
var searchInsert = function(nums, target) {
let left = 0
let right = nums.length -1
while(left <= right){
let mid = ((right - left) >> 1) + left
if(target > nums[mid]){
left = mid + 1
}else{
right = mid - 1
}
}
return left
};
5 最大子序和(53, 2020.09.23)
1 自己的解法:
没得思路只有暴力求解
var maxSubArray = function(nums) {
let len = nums.length
let max = -2147483647
for(let i=0; i<len; i++){
let sum = 0
for(let j=i; j<len; j++){
sum = sum + nums[j]
if(sum > max){
max = sum
}
}
}
return max
};
2 优秀的解法
思路: 满足上一个大于0就累加到当前数组位置
var maxSubArray = function(nums) {
for(let i=1; i<nums.length; i++){
if(nums[i -1] > 0){
nums[i] += nums[i-1]
}
}
return Math.max(...nums)
};
6 加一(66, 2020.09.29)
1 优秀的解法一:
思路: 利用可能超过max_save_integer, 因此用BigInt
var plusOne = function(digits) {
let num = BigInt(digits.join(''))
let str = String(num + 1n)
digits = [...str]
digits.map(Number)
return digits
};
2 优秀的解法二:
思路:
末位无进位,则末位加一即可,因为末位无进位,前面也不可能产生进位,比如 45 => 46
末位有进位,在中间位置进位停止,则需要找到进位的典型标志,即为当前位 %10后为 0,则前一位加 1,直到不为 0 为止,比如 499 => 500
末位有进位,并且一直进位到最前方导致结果多出一位,对于这种情况,需要在第 2 种情况遍历结束的基础上,进行单独处理,比如 999 => 1000
var plusOne = function(digits) {
const len = digits.length
for(let i = len-1; i>=0; i--) {
digits[i]++
digits[i] %= 10;
if(digits[i] !== 0)
return digits
}
digits = [...Array(len + 1)].map(_ => 0)
digits[0] = 1
return digits
};
7 合并两个有序数组(88, 2020.10.01)
1 自己解法:
思路:直接将nums2填充到0的部分然后sort
var merge = function(nums1, m, nums2, n) {
nums1.splice(m)
nums1.push(...nums2)
nums1.sort((a,b) => a-b)
};
2 优秀解法一:
思路:
var merge = function(nums1, m, nums2, n) {
nums1_copy = nums1.slice(0, m)
nums1.splice(0, m+n)
p1 = 0
p2 = 0
while(p1 < m && p2 < n){
if(nums1_copy[p1] < nums2[p2]){
nums1.push(nums1_copy[p1++])
}else{
nums1.push(nums2[p2++])
}
}
while(p1 < m){
nums1.push(nums1_copy[p1++])
}
while(p2 < n){
nums1.push(nums2[p2++])
}
};
3 优秀解法二
思路:
var merge = function(nums1, m, nums2, n) {
p1 = m -1
p2 = n -1
p = m + n -1
while(p1 >= 0 && p2 >= 0){
if(nums1[p1] < nums2[p2]){
nums1[p] = nums2[p2--]
}else{
nums1[p] = nums1[p1--]
}
p--
}
nums1.splice(0, p2+1, ...nums2.splice(0, p2+1))
};
8 杨辉三角(Pascal’s triangle)(118, 2020.10.05)
1 自己的解法:
思路:
- 首先考虑边界条件:
numRows等于1和2的情况,都为1
- 然后开始对numRows大于2的情况:
考虑第一个和最后一个边界情况都为1
普通情况:比如,第2行第1个(2,1)的值为第1行的第0个(1,0)和第一个的和(1,1)
var generate = function(numRows) {
let triangle = []
for(let i=0; i<numRows; i++){
let row = []
if(i === 0){
triangle.push([1])
}else if(i === 1){
triangle.push([1, 1])
}else{
for(let j=0; j<=i; j++){
if(j === 0){
row.push(1)
}else if(j === i){
row.push(1)
}else{
row.push(triangle[i-1][j-1] + triangle[i-1][j])
}
}
triangle.push(row)
}
}
return triangle
};
2 优秀的解法
思路:思路和我差不多,处理row的时候是先声明了i+1
长度的数组之后进行操作
var generate = function(numRows) {
let triangle = []
for(let i=0; i<numRows; i++){
let row = [...Array(i + 1)].map(_ => undefined)
row[0] = 1
row[i] = 1
for(let j=1; j<i; j++){
row[j] = triangle[i-1][j-1] + triangle[i-1][j]
}
triangle.push(row)
}
return triangle
};
9 杨辉三角 II(Pascal’s triangle)(119, 2020.10.07)
1 自己的解法结合参考的解法:
思路:
跟 杨辉三角 别人的解法类似,只是记录上一次的记录,而不是全部的。
/**
* @param {number} rowIndex
* @return {number[]}
*/
var getRow = function(rowIndex) {
let row, last
for(let i=0; i<=rowIndex; i++){
last = row
row = []
row[0] = 1
row[i] = 1
for(let j=1; j<i; j++){
row[j] = last[j-1] + last[j]
}
}
return row
};
10 买卖股票的最佳时机(121,2020.10.10)
1 自己的解法
思路:
- 假设第一个价格为买入价格,先处理掉示例2的条件。
- 然后记录每次可以卖出的最大值即可。
/**
* @param {number[]} prices
* @return {number}
*/
var maxProfit = function(prices) {
let max = 0
let buy = prices[0]
let sell = 0
let i = 1
while(i<prices.length){
if(buy >= prices[i]){
buy = prices[i++]
continue
}else{
sell = prices[i]
if(max < sell - buy){
max = sell - buy
}
i++
}
}
return max
};
2 优秀的解法
思路:和我的差不多
/**
* @param {number[]} prices
* @return {number}
*/
var maxProfit = function(prices) {
let maxProfit = 0
let buy = prices[0]
let sell = 0
for(let i=0; i<prices.length; i++){
buy = Math.min(prices[i], buy)
sell = prices[i]
maxProfit = Math.max(maxProfit, sell - buy)
}
return maxProfit
};
做好初始定义
11 移动零(283,2020.10.12)
1 优秀的解法一
思路:
第一次遍历的时候,j指针记录非0的个数,只要是非0的统统都赋给nums[j]
第二次遍历把末尾的元素都赋为0即可
var moveZeroes = function(nums) {
let j = 0
for(let i=0; i<nums.length; i++){
if(nums[i] !== 0){
nums[j++] = nums[i]
}
}
for(let i=j; i<nums.length; i++){
nums[i] = 0
}
};
2 优秀的解法二
思路:
这里参考了快速排序的思想,快速排序首先要确定一个待分割的元素做中间点x
,然后把所有小于等于x
的元素放到x
的左边,大于x
的元素放到其右边。
这里我们可以用0
当做这个中间点,把不等于0
(注意题目没说不能有负数)的放到中间点的左边,等于0
的放到其右边。
var moveZeroes = function(nums) {
let j = 0
for(let i=0; i<nums.length; i++){
if(nums[i] !== 0){
let temp = nums[i]
nums[i] = nums[j]
nums[j++] = temp
}
}
};
3 优秀的解法三
思路:
其实优化的地方就是#1
处(相比较解法二)。它避免了数组开头是非零元素的交换也就是阻止(i==j
)时交换。
当i > j
时,只需要把i
的值赋值给j
并把原位置的值置0
。同时这里也把交换操作换成了赋值操作,减少了一条操作语句,理论上能更节省时间。
var moveZeroes = function(nums) {
let j = 0
for(let i=0; i<nums.length; i++){
if(nums[i] !== 0){
if(i > j){ // #1
nums[j] = nums[i]
nums[i] = 0
}
j++
}
}
};
3 移除元素(27,2020.10.14)
2 删除排序数组中的重复项(26,2020.10.14)
12 删除排序数组中的重复项 II(80,2020.10.14)
1 自己的解法:
思路:通过count
计数,然后通过splice函数特性来写。
/**
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function(nums) {
let count = 1
let i = 1
while(i<nums.length){
if(nums[i] === nums[i-1]){
count++
if(count > 2){
nums.splice(i, 1)
i--
}
}else{
count = 1
}
i++
}
return nums.length
};
2 优秀的解法
思路:不使用splice
来切割,用后面的来覆盖。
var removeDuplicates = function(nums) {
let count, j = 1
for(let i=1; i<nums.length; i++){
if(nums[i] === nums[i-1]){
count++
if(count <= 2){
nums[j++] = nums[i]
}else{
count = 1
}
};
运用基础算法思想
13 颜色分类(75,2020.11.01)
1 自己的解法
思路:库函数排序和手写计数排序都可以完成这道问题。这里直接用JS提供的sort
函数
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var sortColors = function(nums) {
nums.sort()
};
2 优秀的解法
思路:
循环不变量:声明的变量在遍历的过程中需要保持定义不变。设计循环不变量的原则是 不重不漏。
设计的循环不变量定义如下:
所有在子区间 [0, zero) 的元素都等于 0;
所有在子区间 [zero, i) 的元素都等于 1;
所有在子区间 [two, len - 1] 的元素都等于 2。
我的理解应该zero和two是循环不变量
var sortColors = function(nums) {
nums.sort()
len = nums.length
lt = -1
rt = len - 1
i = 0
while(i < rt){
if(nums[i] === 0){
lt++
swap(nums, i, lt)
i++
}else if(nums[i] === 1){
i++
}else{
swap(nums, i, rt)
rt--
}
}
};
function swap(nums, index1, index2){
let temp = nums[index1]
nums[index1] = nums[index2]
nums[index2] = temp
}
变量的值发生变化是一个很重要的逻辑,应该单独成为一行,否则不利于调试和以后定位问题
14 数组中的第K个最大元素(215,2020.11.06)
1 自己的解法
思路:通过 sort 函数排序后,直接返回 k-1 的值
var findKthLargest = function(nums, k) {
nums.sort((a, b) => b - a)
return nums[k-1]
};
2 优秀的解法
思路:利用快排的思路,对拆分(partition)部分进行操作
var findKthLargest = function(nums, k) {
len = nums.length
left = 0
right = len - 1
target = len - k
while(true){
let index = partition(nums, left, right)
if(index === target){
return nums[index]
}else if(index < target){
left = index + 1
}else{
right = index - 1
}
}
};
// 循环不变量:[left + 1, j] < pivot
// (j, i) >= pivot
var partition = function(nums, left, right){
pivot = nums[left]
j = left
for(let i=left+1; i<right+1; i++){
if(nums[i] < pivot){
j++
[nums[i], nums[j]] = [nums[j], nums[i]]
}
}
[nums[left], nums[j]] = [nums[j], nums[left]]
return j
}
7 合并两个有序数组(88, 2020.10.01)
双索引技巧 - 对撞指针
15 两数之和 II - 输入有序数组(167,2020.11.11)
1 优秀的解法:
思路:用双指针来完成
var twoSum = function(numbers, target) {
let i = 0
let j = numbers.length - 1
while(i < j){
const sum = numbers[i] + numbers[j]
if(target > sum){
i += 1
}else if(target < sum){
j -= 1
}else{
return [i+1, j+1]
}
}
};
16 验证回文串(125,2020.11.14)
1 自己的解法:
思路:双指针
var isPalindrome = function(s) {
let i = 0;
let j = s.length - 1;
const regExp = /([a-zA-Z0-9])/;
while(i < j){
if(regExp.test(s[i]) && regExp.test(s[j])){
if(s[i].toLowerCase() === s[j].toLowerCase() ){
i += 1;
j -= 1;
}else{
return false;
}
}else if(!regExp.test(s[i])){
i += 1;
}else if(!regExp.test(s[j])) {
j -= 1;
}
}
return true
};
2 别人的解法:
思路:先过滤字符串然后通过反转或者双指正来验证是否为回文串
var isPalindrome = function(s) {
const regExp = /([\W|_])/g;
s = s.replace(regExp, '').toLowerCase();
return s === s.split('').reverse().join('');
};
17 反转字符串中的元音字母(345,2020.11.16)
1 自己的解法
思路:利用双指针
var reverseVowels = function(s) {
s = s.split('')
let i = 0
let j = s.length - 1
let vowel = 'aeiouAEIOU'
while(i < j){
if(vowel.indexOf(s[i]) !== -1 && vowel.indexOf(s[j]) !== -1) {
const temp = s[i]
s[i] = s[j]
s[j] = temp
i += 1
j -= 1
}
if(vowel.indexOf(s[i]) === -1){
i += 1
}
if(vowel.indexOf(s[j]) === -1){
j -= 1
}
}
return s.join('')
};
18 盛最多水的容器 (11,2020.11.18)
1 自己的解法:
思路:利用双指针
var maxArea = function(height) {
let i = 0;
let j = height.length - 1;
areaMax = 0
while(i<j){
h = height[i] < height[j] ? height[i] : height[j];
w = j - i;
area = h * w;
if(area > areaMax){
areaMax = area;
}
if(height[i] < height[j]){
i += 1;
}else{
j -= 1;
}
}
return areaMax;
};
2 针对1代码优化:
var maxArea = function(height) {
let i = 0;
let j = height.length - 1;
areaMax = 0
while(i<j){
if(height[i] < height[j]){
areaMax = Math.max(areaMax, height[i] * (j - i))
i += 1
}else{
areaMax = Math.max(areaMax, height[j] * (j - i))
j -= 1
}
}
return areaMax;
};
19 长度最小的子数组(209,2020.11.21)
1 优秀的解法
思路:先左扩张找到满足 sum >= s
的条件的可行解, 然后右收缩优化可行解(最小长度)
var minSubArrayLen = function(s, nums) {
let minLenth = Infinity
let i = 0
let j = 0
let sum = 0
while(j< nums.length){
sum += nums[j]
while(sum >= s){
minLenth = Math.min(minLenth, j - i + 1)
sum -= nums[i]
i += 1
}
j += 1
}
return minLenth === Infinity ? 0 : minLenth
};
20 三数之和(15,2021.02.01)
1 优秀的解法:
思路:先对数组进行排序,然后通过双指针方法来寻找: 固定 3 个指针中最左(最小)数字的指针 k,双指针 i,j 分设在数组索引(k,len(nums)两端,通过双指针交替向中间移动,记录对于每个固定指针 k 的所有满足,nums[k] + nums[i] + nums[j] == 0
的i,j组合。
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function(nums) {
nums.sort((a,b)=> a-b)
let res = []
for(let k=0; k<nums.length - 2; k++){
if(nums[k] > 0) break
if(k > 0 && nums[k-1] === nums[k]) continue
let i = k + 1
let j = nums.length - 1
while(i < j){
let sum = nums[k] + nums[i] + nums[j]
if(sum < 0){
i += 1
while(i < j && nums[i] === nums[i - 1]){
i += 1
}
}else if(sum > 0){
j -= 1
while(i < j && nums[j] === nums[j + 1]){
j -= 1
}
}else{
res.push([nums[k], nums[i], nums[j]])
i += 1
j -= 1
while(i < j && nums[i] === nums[i - 1]){
i += 1
}
while(i < j && nums[j] === nums[j + 1]){
j -= 1
}
}
}
}
return res
};