博客园  :: 首页  :: 新随笔  :: 管理

2.二分查找/排序

Posted on 2021-03-29 21:36  wsg_blog  阅读(103)  评论(0编辑  收藏  举报

Index LeetCode

二分查找也被称为二分法或者折半查找,一般来说都是针对有序区间的查找,每次查找时通过将待查找区间分成两部分并只取一部分继续查找,将查找的复杂度大大减少,对于一个长度为O(n)的数组,二分查找的时间复杂度为O(log n),二分查找要求数组是有序的。
具体到代码上,二分查找时区间的左右端取开区间还是闭区间在绝大多数时候都可以,因此有些初学者会容易搞不清楚如何定义区间的开闭性,这里有几个小诀窍,第一是尝试熟练使用一种写法,比如左闭右开(满足c++、python等语言习惯)或左闭右闭(便于处理边界条件),尽量只保持一种写法;第二是在刷题时思考如果最后区间只剩下一个数或者两个数,自己的写法是否会陷入死循环,如果某些写法无法跳出死循环,则考虑另一种写法。

BM17.二分查找-I(704.二分查找)[easy]

输入:nums=[-1,0,3,5,9,12] target=9
输出:4

int search(vector<int>& nums, int target){
  int l=0,r=nums.size()-1;
  while(l<=r){
    int mid=(l+r)/2;
    if(nums[mid] == target){
      return mid;
    }else if(nums[mid] > target){
      r=mid-1;
    }else{
      l=mid+1;
    }
  }
  return -1;
}
BM18.二维数组中的查找(剑指Offer04.二维数组中的查找)[medium]

输入:7,[[1,2,8,9],[2,4,9,12][4,7,10,13],[6,8,11,15]]
输出:true
说明:存在7返回true,矩阵从左到右,从上到下递增

bool findNumberIn2DArray(vector<vector<int>>& matrix, int target){
  if(matrix.size()==0 || matrix[0].size()==0) return false;
  int m=matrix.size(), n=matrix[0].size();
  for(int i=m-1,j=0; i>=0 && j<n;){    //从右上角开始,i--变小,j++变大;从左下角也可以
    if(matrix[i][j]==target){
      return true;
    }else if(matrix[i][j]>target){
      i--;
    }else{
      j++;
    }
  }
  return false;
}
BM19.寻找峰值(162.寻找峰值)[medium]

输入:nums=[1,2,3,1]
输出:2
解释:3是峰值元素,你的函数应该返回索引2;峰值元素是指其值严格大于左右相邻值的元素

int findPeakElement(vector<int>& nums){
  int left=0, right=nums.size()-1;
  while(left<right){  //二分法
    int mid=(left+right)/2;
    if(nums[mid]>nums[mid+1]){  //右边是往下不一定有波峰
      right=mid;
    }else{    //右边是往上一定有波峰
      left=mid+1;
    }
  }
  return right;
}
BM20.数组中的逆序对(剑指Offer51.数组中的逆序对)[hard]

输入:[7,5,6,4]
输出:5
说明:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。求数组中逆序对的总数。
归并排序,分治思想。合并阶段统计逆序对

int reversePairs(vector<int>& nums){
  vector<int> tmp(nums.size());
  return mergeSort(0, nums.size()-1, nums, tmp);
}
int mergeSort(int left, int right, vector<int>& nums, vector<int>& tmp){
  //终止条件
  if(left>=right) return 0;
  //递归划分
  int mid=left+(right-left)/2;
  int res=mergeSort(left, mid, nums, tmp) + mergeSort(mid+1, right, nums, tmp);
  //合并阶段
  int i=left, j=mid+1;  //i,j为合并两数组的起始位置
  for(int k=left; k<=right; k++)  tmp[k]=nums[k];  //暂存数组到tmp
  for(int k=left; k<=right; k++){
    if(i == mid+1)  //左子数组已合并完成
      nums[k]=tmp[j++];
    else if(j==right+1 || tmp[i]<=tmp[j])  //右子数组已合并完成
      nums[k]=tmp[i++];
    else{
      nums[k]=tmp[j++];
      res += mid-i+1;  //统计逆序对
    }
  }
  return res;
}
BM21.旋转数组的最小数字(剑指Offer11.旋转数组的最小数字)[easy]

输入:numbers=[3,4,5,1,2]
输出:1
说明:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转,给一个升序可能存在重复元素的数组,进行一次旋转。返回旋转数组的最小元素

int minArry(vector<int>& numbers){
  int l=0, r=numbers.size()-1;
  while(l<r){
    int m=l+(r-l)/2;
    if(numbers[m] > numbers[r]){    //这里如果用l的进行比较的话会遇到问题
      l=m+1;
    }else if(numbers[m] < numbers[r]){
      r=m;
    }else{
      r--;
    }
  }
  return numbers[l];
}
BM22.比较版本号(165.比较版本号)[medium]

输入:version1="1.01",version2="1.001"
输出:0
双指针,分段比较

int compareVersion(string version1, string version2){
  int i=0,j=0;
  while(i<version1.size() || j<version2.size()){
    int a=0,b=0;
    while(i<version1.size() && version1[i]!='.') a=a*10+(version1[i++]-'0');
    while(j<version2.size() && version2[j]!='.') b=b*10+(version2[j++]-'0');
    if(a>b) return 1;
    if(b>a) return -1;
    i++,j++;  //跳过点
  }
  return 0;
}

快速排序(快排)

c++ 中 std::sort()使用了快速排序的算法,基本思想:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的数字均比另一部分数字小,则可分别对这两部分进行排序,以达到整个序列有序。
算法描述

  1. 选定Pivot中心轴(随机选取,一般选数组第一个值为Pivot)
  2. 从后向前遍历,将大于Pivot中心轴的数字放在Pivot的右边
  3. 从前向后遍历,将小于Pivot中心轴的数字放在Pivot的左边
  4. 分别对左右子序列重复前三步
void quick_sort(vector<int> &nums, int l, int r){   //l=0,r=nums.size()
  if(l+1 >= r) return;
  int first=l, last=r-1, key=nums[first];
  while (first < last){   //循环完会把nums分成两部分,左边小于k,右边大于k
    while(first < last && nums[last] >= key){   //数组从后向前查找小于key的值,交换至左边
      --last;
    }
    nums[first] = nums[last];
    while(first < last && nums[first] <= key){  //数组从前向后查找大于key的值,交换至右边
      ++first;
    }
    nums[last]=nums[first];
  }
  nums[first] = key;  //此时的first已不是0
  quick_sort(nums, l, first);
  quick_sort(nums, first+1, r);
}

归并排序

归并排序可以实现对链表的排序(链表一分为二使用快慢指针实现,而且不使用临时数组)
归并排序的主旨思想,先一分为二的拆分,拆分到1,然后再两两合并,合并的时候进行比较排序,会用到临时数组,会用到递归

  1. 从中间一分为二,递归至无法再分为止,全部打散
  2. 两两合并,向上递归,直至合并完成
//对nums数组进行归并排序
void mergeSort( vector<int>& nums, int left, int right, vector<int>& tmp){
  //终止条件
  if(left>=right) return 0;
  //递归划分
  int mid=left+(right-left)/2;
  int res=mergeSort(left, mid, nums, tmp) + mergeSort(mid+1, right, nums, tmp);
  //合并阶段
  int i=left, j=mid+1;  //i,j为合并两数组的起始位置
  for(int k=left; k<=right; k++)  tmp[k]=nums[k];  //暂存数组到tmp
  for(int k=left; k<=right; k++){
    if(i == mid+1)  //左子数组已合并完成
      nums[k]=tmp[j++];
    else if(j==right+1 || tmp[i]<=tmp[j])  //右子数组已合并完成 或者 左数组当前为小值
      nums[k]=tmp[i++];
    else{ //右数组当前为小值
      nums[k]=tmp[j++];
    }
  }
}