一、数组理论基础
js当中数组的定义: var array = [1, 2, 3, 4, 5]; (字面量定义)
或者: var array = new Array(5);
JavaScript当中没有指针,内存相关的操作向程序员隐藏。
二、704 二分查找
思路:暴力解法、二分查找
暴力解法
从前往后找,遇到相等了的就返回,并跳出循环,时间复杂度O(n)。
var search = function(nums, target) {
var result = -1;
for (let i=0; i<nums.length; i++) {
if (nums[i] == target) {
result = i;
break;
}
}
return result;
};
二分查找
我认为二分查找就是一个另类的双指针法,一开始的时候双指针分别是第一个和最后一个元素,然后慢慢地缩小范围。但需要注意的是,这道题的前提是数组有序,因此可以使用二分法。熟练使用左闭右开即可。时间复杂度为O(logn)。
var search = function(nums, target) {
var left = 0;
var right = nums.length; // 因为左闭右开,所以right是nums.length,也避免了只有一个元素的问题
var mid = Math.floor((left + right) / 2);
while (left < right) { // 此处小于号就可以
if (nums[mid] == target) {
return mid;
}
else if (nums[mid] < target) {
left = mid + 1; // 因为这个时候mid肯定不会在区间里面了,所以left加一
}
else {
right = mid;
}
mid = Math.floor((left + right) / 2);
}
return -1;
};
左闭右开需要注意的灵魂有三个:第一个是开始定义的时候,由于是左闭右开,所以right指针定义为数组上限+1;第二个是while的判断条件为小于号,因为左闭右开的区间中左右指针相等是没有意义的;第三个是已经判断了mid指针与target不相等的情况下,right可以直接赋值mid,因为是开区间,下一次循环不会比较nums[mid]。left则要赋值mid+1。
35 搜索插入位置
题目链接:https://leetcode.cn/problems/search-insert-position/
这题用左闭右闭写一下:
var searchInsert = function(nums, target) {
var left = 0;
var right = nums.length - 1;
var mid = Math.floor((right + left)/2);
while (left <= right) {
if (nums[mid] == target) {
return mid;
}
else if (nums[mid] < target) {
left = mid + 1;
}
else {
right = mid -1;
}
mid = Math.floor((right + left)/2);
}
return left;
};
34 在排序数组中查找元素的第一个和最后一个位置
题目链接:https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/
思路一:首先使用二分法找到任意一个等于target的索引,然后在附近查找(还是记住这个吧,下面那个有点难记)
var searchRange = function(nums, target) {
var left = 0, right = nums.length - 1;
var result = [-1, -1];
while (left<=right){
var mid = left + Math.floor((right - left)/2);
if (nums[mid] < target) {
left = mid + 1;
}
else if (nums[mid] > target) {
right = mid - 1;
}
else {
result[0] = result[1] = mid;
break;
}
}
if (result[0] == -1 && result[1] == -1) {
return result;
}
while (nums[result[0]] == target) {
result[0] -= 1;
}
while (nums[result[1]] == target) {
result[1] += 1;
}
return [result[0] + 1, result[1] - 1];
};
思路二:使用两次二分法,要找的左侧索引相当于是第一个大于等于target的值,右侧索引是第一个大于target的值减去一。
const binarySearch = (nums, target, lower) => {
// lower是为了增强代码复用性,如果是lower是true的话则是判断大于等于
let left = 0, right = nums.length - 1, ans = nums.length;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (nums[mid] > target || (lower && nums[mid] >= target)) {
right = mid - 1;
ans = mid;
} else {
left = mid + 1;
}
}
return ans;
}
var searchRange = function(nums, target) {
let ans = [-1, -1];
const leftIdx = binarySearch(nums, target, true);
const rightIdx = binarySearch(nums, target, false) - 1;
if (leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] === target && nums[rightIdx] === target) {
ans = [leftIdx, rightIdx];
}
return ans;
};
69 x的平方根
题目链接:https://leetcode.cn/problems/sqrtx/
var mySqrt = function(x) {
if (x == 1) {
return 1;
}
// 平方根一定小于这个值的一半
var left = 1;
var right = x/2 + 1;
while (left < right) {
var mid = Math.floor((left + right) / 2);
if (mid * mid == x) {
return mid;
}
else if (mid * mid < x) {
left = mid + 1;
}
else {
right = mid;
}
}
return right - 1;
};
367 有效的完全平方数
题目链接:https://leetcode.cn/problems/valid-perfect-square/
var isPerfectSquare = function(num) {
if (num == 1) {
return true;
}
var left = 0, right = Math.floor(num/2);
while (left <= right) {
var mid = left + Math.floor((right - left)/2);
// 加入常量计算mid的平方,减少冗余计算,提高效率
const square = mid * mid;
if (square > num) {
right = mid - 1;
}
else if (square < num) {
left = mid + 1;
}
else {
return true;
}
}
return false;
};
三、27 移除元素
思路:快慢指针法,时间复杂度为O(n)。
代码
var removeElement = function(nums, val) {
var slow = 0;
var fast = 0;
while (fast < nums.length) {
// 与val不相同的数值才会被赋给slow指针
if (nums[fast] !== val) {
nums[slow] = nums[fast];
slow++;
}
fast++;
}
return slow;
};
26 删除排序数组中的重复项
题目链接:https://leetcode.cn/problems/remove-duplicates-from-sorted-array/
代码:
var removeDuplicates = function(nums) {
if (nums.length == 0) {
return 0;
}
var fast = 1;
var slow = 0;
while (fast < nums.length) {
if (nums[fast-1] < nums[fast]) {
slow++;
nums[slow] = nums[fast];
}
fast++;
}
return slow + 1;
};
283 移动零
题目链接:https://leetcode.cn/problems/move-zeroes/
使用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部。
右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移。
左指针左边均为非零数;右指针左边直到左指针处均为零。
代码:
var moveZeroes = function(nums) {
var fast = 0;
var slow = 0; // slow代表当前所有不为0的序列总和,可以看作是一个计数器而已
var temp = 0;
while (fast < nums.length) {
if (nums[fast] != 0) {
// 当fast探测到不为0的数值时,就与当前的交换
temp = nums[slow];
nums[slow] = nums[fast];
nums[fast] = temp;
// slow左边是已经处理过的序列,每次fast找到一个,slow就应该加一
slow++;
}
fast++;
}
return nums;
};
844 比较含退格的字符串
题目链接:https://leetcode.cn/problems/backspace-string-compare/
思路:暴力解法、双指针法
暴力解法:我第一次写的,不知道是个啥东西。。。
var backspaceCompare = function(s, t) {
var i = s.length - 1, j = t.length - 1;
var skipS = 0, skipT = 0;
// 大循环
while (i >= 0 || j >= 0) {
// S循环
while (i >= 0) {
if (s[i] == "#") {
skipS += 1;
i--;
}
else if (s[i] != "#" && skipS != 0) {
skipS--;
i--;
}
else {
break;
}
}
// T循环
while (j >= 0) {
if (t[j] == "#") {
skipT += 1;
j--;
}
else if (t[j] != "#" && skipT != 0) {
skipT--;
j--;
}
else {
break;
}
}
// 如果不相等,输出false
if (s[i] !== t[j]) {
return false;
}
i--;
j--;
}
return true;
};
双指针法:
这里没什么需要注意的,但是我在数组的slice方法那里卡了很久,用了splice没发现自己错了。。。还是需要再熟悉熟悉
var backspaceCompare = function(s, t) {
var rewriteString = function(string) {
var array = string.split('');
var slow = 0;
var fast = 0;
while (fast < array.length) {
if (array[fast] != '#') {
array[slow] = array[fast];
slow++;
}
else {
slow = slow ? slow-1 : 0;
}
fast++;
}
rewrite = array.slice(0, slow).join('');
return rewrite;
}
return rewriteString(s) == rewriteString(t);
};
977 有序数组的平方
题目链接:https://leetcode.cn/problems/backspace-string-compare/
思路:我们新建一个长度和原来相等的数组,然后在原数组上利用左右指针,不断比较,当中较大的一个填入结果数组的末尾,如果left指针对应的值填进去了,那么left++,反之right--。
var sortedSquares = function(nums) {
var result = [];
var left = 0, right = nums.length - 1;
var k = nums.length - 1;
// 这里是小于等于,不然的话如果i和j相等,就跳出循环,则当前的元素就被落下了
while (left <= right) {
numLeft = nums[left] * nums[left];
numRight = nums[right] * nums[right];
if (numLeft >= numRight) {
result[k--] = numLeft;
left++;
}
else {
result[k--] = numRight;
right--;
}
}
return result;
};
四、总结
今日学习时间:2.5h左右
其实这一部分题目我之前是跟卡哥做过一遍的,但现在看来忘得也都差不多了,果然还是得多次刷,多次复习,在我看来二分法就是另一种形式的双指针法,其中的原理是相通的。这一次算是二刷,题目都有思路但不一定能一次写对,下一次刷的时候需要重点注意的题目有:34/283/977。