代码随想录算法训练营第一天| 704. 二分查找、27. 移除元素
对有序数组进行查找使用二分查找法 | 二分查找的关键:循环不变量
704. 二分查找
链接
题目链接
https://leetcode.cn/problems/binary-search/
文章讲解链接
https://programmercarl.com/0704.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html
视频讲解链接
https://www.bilibili.com/video/BV1fA4y1o715
做题思路
第一想法
由于本题已说明数组为升序且数组中无重复值,因此二分查找是时间复杂度最低的解决方式,二分查找难点主要在确认边界上,主要有两种:左闭右闭和左闭右开。
解法
- 左闭右闭
个人倾向于这种解法,更符合本人的思维逻辑,循环逻辑:left <= right 当val < nums[mid]时,right = mid - 1;当val > nums[mid]时left = mid + 1; - 左闭右开
循环逻辑:left < right; 当val < nums[mid]时 right = mid(因为时左闭右开,如果是mid-1会导致一些值取不到)
细节
为防止计算溢出,采用mid = left + (right - left) / 2;
代码
解法一 左闭右闭
class Solution {
public int search(int[] nums, int target) {
if(nums[0] > target || nums[nums.length - 1] < target){
return -1;
}
int left = 0;
int right = nums.length - 1;
int mid;
while(left <= right){
mid = left + (right - left) / 2;
if(nums[mid] < target){
left = mid + 1;
}else if(nums[mid] > target){
right = mid - 1;
}else{
return mid;
}
}
return -1;
}
}
解法二 左闭右开
class Solution {
public int search(int[] nums, int target) {
if(nums[0] > target || nums[nums.length - 1] < target) return -1;
int left = 0;
int right = nums.length;
int mid;
while(left < right){
mid = (left + right) / 2;
if(nums[mid] < target){
left = mid + 1;
}else if(nums[mid] > target){
right = mid;
}else{
return mid;
}
}
return -1;
}
}
35.搜索插入位置
链接
题目链接
https://leetcode.cn/problems/search-insert-position/
文章讲解链接
思路
解法一:brute force
这道题暴力循环效率也很高,因为数组是已排序的,只要找到第一个大于target的数,那就说明这就是要插入target的位置
时间复杂度:O(n)
空间复杂度:O(1)
class Solution{
public int searchInsert(int[] nums, int target){
for(int i = 0; i < nums.length; i++){
if(nums[i] >= target){
return i;
}
}
return nums.length;
}
}
解法二:二分查找
已排序就可用二分查找
时间复杂度:O(logn)
空间复杂度:O(1)
与找数字一样,返回值需考虑多种情况
- target比数组中所有数字都小
- target比数组中所有数字都大
- 数组中存在target
- 在数组中间插入
// 左闭右闭
class Solution {
public int searchInsert(int[] nums, int target) {
int right = nums.length - 1;
int left = 0;
int mid = 0;
while(left <= right){
mid = left + (right - left) / 2;
if(nums[mid] > target){
right = mid - 1;
}else if (nums[mid] < target){
left = mid + 1;
}else{
return mid;
}
}
// return nums[mid] > target? mid:mid+1;
return right + 1;
// 1. 如果最后查找的数字大于target,那么right = mid - 1,而插入位置是mid,所以返回right + 1;
// 2. 如果最后查找的数字小于target,那么right = mid, 而插入位置是mid + 1, 所以返回right + 1;
}
}
// 左闭右开
class Solution {
public int searchInsert(int[] nums, int target) {
int right = nums.length;
int left = 0;
int mid = 0;
while(left < right){
mid = left + (right - left) / 2;
if(nums[mid] > target){
right = mid;
}else if (nums[mid] < target){
left = mid + 1;
}else{
return mid;
}
}
return right;
}
}
34. 在排序数组中查找元素的第一个和最后一个位置
题目链接
https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/
文章讲解链接
该题是在已排序数组中找元素位置(元素有重复值)
- 解法一: 先用二分查找改元素在不在这个数组,如果不在数组就返回{-1, -1};如果数组中存在这个数字,那就在找到这个数字的位置向左向右遍历,找左右位置
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] res = {-1, -1};
int left = 0;
int right = nums.length;
int mid;
while (left < right) {
mid = left + (right - left) / 2;
if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
res[0] = mid;
res[1] = mid;
int leftA = mid - 1;
int rightA = mid + 1;
while (leftA >= left && nums[leftA] == target) {
res[0] = leftA--;
}
while (rightA < right && nums[rightA] == target) {
res[1] = rightA++;
}
return res;
}
}
return res;
}
}
- 解法二:用二分查找找左右位置
先找左右边界,eg找右边界,如果找到nums[mid] == target, 那就判断一下是否已是最后一位或者右边的是否等于target,如果等于,那就接着二分找,如果不等于就返回mid,找完数组都没有target返回负一
class Solution {
public int[] searchRange(int[] nums, int target) {
int left = searchLeft(nums, target);
int right = searchRight(nums, target);
return new int[]{left, right};
}
// 找目标值右边的那位数
public int searchRight(int[] nums, int target){
int left = 0;
int right = nums.length - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(nums[mid] > target){
right = mid - 1;
}else if(nums[mid] < target){
left = mid + 1;
}else{
if(mid == nums.length - 1 || nums[mid + 1] != target){
return mid;
}else{
left = mid + 1;
}
}
}
return -1;
}
// 找目标值左边的那位数
public int searchLeft(int[] nums, int target){
int left = 0;
int right = nums.length - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(nums[mid] > target){
right = mid - 1;
}else if (nums[mid] < target){
left = mid + 1;
}else{
if(mid == 0 || nums[mid - 1] != target){
return mid;
}else{
right = right - 1;
}
}
}
return -1;
}
}
27. 移除元素
链接
题目链接
https://leetcode.cn/problems/remove-element/
文章讲解链接
https://programmercarl.com/0027.%E7%A7%BB%E9%99%A4%E5%85%83%E7%B4%A0.html
视频讲解链接
https://www.bilibili.com/video/BV12A4y1Z7LP
解题思路
第一想法
双指针的妙用,其实慢指针相当于一个mark位
代码块
自己写的
class Solution {
public int removeElement(int[] nums, int val) {
int mark = nums.length - 1;
for(int i = 0; i <= mark; i++){
if(nums[i] == val){
nums[i] = nums[mark];
mark--;
i--;
}
}
return mark + 1;
}
}
看了卡尔写的
class Solution {
public int removeElement(int[] nums, int val) {
int right = nums.length;
int slow = 0;
for(int fast = 0; fast < right; fast++){
if(val != nums[fast]){
nums[slow++] = nums[fast];
}
}
return slow;
}
}
今日困难
一年没刷题了,手有点生疏,代码能力也稍有弱化,继续加油,60天后蜕变。