代码随想录算法训练营第一天| 704.二分查找、35.搜索插入位置、27.移除元素
二分查找
题目链接:
https://leetcode.cn/problems/binary-search/
问题
解题思路
主要问题就是索引的区间的开闭问题,具化一点唯一的难度也就是while (left < right)
是否带等号以及每次对left或right部分索引的修改
方案一:
搜索区间均闭合,即[left,right],这样也就意味着每次拿来比较的mid不应该再出现在新的区间内,因为mid所在位置的元素已经进行了判断,不需要再出现在新的区间内,只需要取[left, mid - 1]或[mid + 1, right]。同时由于左右区间均为闭合区间,所以允许left=right,该条件允许成立。
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right){
int mid = (left + right) / 2;
if (nums[mid] == target)
return mid;
else if (nums[mid] > target)
right = mid - 1;
else
left = mid + 1;
}
return -1;
}
}
方案二:
搜索区间左开右闭或者左闭合开,即[left, right)或(left, right],下面以左闭又开举例,也就意味着每次right并不在比较的范围内,经过mid判断后只需要取[mid + 1,right) 或 [left, mid),因为对于开区间来说mid本身就不在考虑的范围内,而也就不允许left=right。
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length;
while (left < right){
int mid = (left + right) / 2;
if (nums[mid] == target)
return mid;
else if (nums[mid] > target)
right = mid;
else
left = mid + 1;
}
return -1;
}
}
搜索插入位置
题目链接:
https://leetcode.cn/problems/search-insert-position/
问题
解题思路
对于该问题也是使用二分法,区别仅仅在于当找不到元素时需要查找插入位置,而当使用左开右闭的方式进行二分查找时,当找不到时候的终止条件是left >= right,而其真正的结束循环的位置在left = right。如果在达到结束条件前的最后一次结果是[mid + 1,right) ,那么也就意味着target要比mid+1位置上的元素要大,而right位置上的元素并不在区间内,因为在初始条件下的right就是数组长度,已经是越界的索引,所以最终的位置也就应该是right对应的索引,因为right部分并不是闭合的区间。而如果是[left, mid)则表示最终位置要比left还要小,所以可以使用left位置的索引来表示,而终止条件是left=right,所以使用right统一表示即可
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length;
while (left < right){
int mid = (left + right) / 2;
if (nums[mid] == target)
return mid;
else if (nums[mid] > target){
right = mid;
}
else if (nums[mid] < target){
left = mid + 1;
}
}
return right;//使用left也可以
}
}
移除元素
题目链接:
https://leetcode.cn/problems/remove-element/
问题
解法
暴力解法:
直接两层for,难点仅仅在于第二层for来覆盖数据后记得将第一次计数的i索引进行回退,防止覆盖后的还是target
class Solution {
public int removeElement(int[] nums, int val) {
int size = nums.length;
for (int i = 0; i < size; i++) {
if (nums[i] == val){
for (int j = i; j < size - 1; j++) {
nums[j] = nums[j + 1];
}
size--;
i--;
//i--是防止覆盖当前i会在下一次循环+1,但是为了防止当前位置的i覆盖后的值仍为val,所以要-1
}
}
return size;
}
}
使用双指针(两个都从起始位置出发)
slowIndex与fastIndex最开始一起出发,而slowIndex当遇到val相同时会停下来等待,而fastIndex会继续走来寻找下一个不是val的值,找到后覆盖slowIndex位置处的数据,然后再让slowIndex走,这样也就导致最后slowIndex的位置或者是val值的位置或者是最后一个元素的位置,即导致索引越界的位置,整体复杂度相比于暴力解法减小了,该方法为O(n)
class Solution {
public int removeElement(int[] nums, int val) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.length; fastIndex++){
if (nums[fastIndex] != val){
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
}
使用双指针(一前一后)
左面的找的是等于val的,右面找的是不等于val的,然后用右边覆盖左边,因为左右都是闭区间,所以使用>=
对于这种情况有一个问题是出现rightIndex的值也为val,但是即使如此也可以成功运行,因为覆盖后leftIndex并没有发生变化,会继续进行检查,而rightIndex已经向左移动
class Solution {
public int removeElement(int[] nums, int val) {
int leftIndex = 0;
int rightIndex = nums.length - 1;
while (leftIndex <= rightIndex){
if (nums[leftIndex] == val){
nums[leftIndex] = nums[rightIndex];
rightIndex--;
}else
leftIndex++;
}
return leftIndex;
}
}