1.二分查找
1.1预备知识
例1:二分查找(递归)
已知一个排序数组A,如A=[-1,2,5,20,90,100,207,800]
另外一个乱序数组B,如B=[20,90,3,-1,207,80]
求B中得任意某个元素是否在A中出现,结果存储在数组C中,出现用1表示,未出现用0表示,如,C=[0,1,0,1,1,0]
正常暴力查找需要O(AB)时间复杂度,使用这班查找需要O(BlogA)
//二分查找
bool binary_search(vector<int> &sort_array, int begin,int end ,int target)
{
if (begin > end) {
return false;
}
int mid = (begin + end) / 2;
if (target == sort_array[mid]) {
return true;
}
else if(target>sort_array[mid])
{
begin = mid + 1;
return binary_search(sort_array, begin, end, target);
}
else
{
end = mid - 1;
return binary_search(sort_array, begin, end, target);
}
}
例2:二分查找(非递归)
//非递归
bool binary_search(vector<int>& sort_array, int target) {
int begin = 0;
int end = sort_array.size() - 1;
while (begin<=end)
{
int mid = (begin + end) / 2;
if (target == sort_array[mid]) {
return true;
}
else if(target < sort_array[mid])
{
end = mid - 1;
}
else
{
begin = mid + 1;
}
}
return false;
}
1.2相关例题
例3:搜索插入位置(LeetCode 35-简单)
题目:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例:
输入: [1,3,5,6], 5
输出: 2
分析:
对二分查找进行修改即可
代码:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int begin=0;
int end=nums.size();
int index;
while(begin<=end){
int mid=(begin+end)/2;
if(target == nums[mid]){
return mid;
}
else if(target > nums[mid]){
if(mid==(nums.size()-1) || target<nums[mid+1]){
index= mid+1;
break;
}
else{
begin=mid+1;
}
}
else{
if(mid==0 || target > nums[mid-1]){
index= mid;
break;
}
else{
end=mid-1;
}
}
}
return index;
}
};
例4:区间查找(LeetCode 34-中等)
题目:
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。
示例:
输入: nums = [5,7,7,8,8,10]
, target = 8
输出: [3,4]
分析:
对二分查找增加限制条件:target==nums[mid]时,若mid==0 || nums[mid-1]<target时 搜索左端点
对二分查找增加限制条件:target==nums[mid]时,若mid==nums.size()-1 || target>nums[mid]时,搜索右端点
执行两次二分查找即可找到区间
代码:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int begin=0;
int end=nums.size()-1;
vector<int> range={-1,-1};
//查找区间左端点
while(begin<=end){
int mid=(begin+end)/2;
if(target == nums[mid]){
//找到左端点
if(mid==0 || target>nums[mid-1]){
range[0]=mid;
break;
}
else{
end=mid-1;
}
}
else if(target > nums[mid]){
begin=mid+1;
}
else{
end=mid-1;
}
}
if(range[0]==-1){
return range;
}
//查找区间右端点
begin=range[0];
end=nums.size()-1;
while(begin<=end){
int mid=(begin+end)/2;
if(target == nums[mid]){
//找到右端点
if(mid==nums.size()-1 || target<nums[mid+1]){
range[1]=mid;
break;
}
else{
begin=mid+1;
}
}
else if(target > nums[mid]){
begin=mid+1;
}
else{
end=mid-1;
}
}
return range;
}
};
例5:旋转数组查找(LeetCode 33-中等)
题目:
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
代码1:
思路是首先利用二分查找找到“旋转点“位置,然后通过判断target在前后哪个区间上进行正常得二分查找
class Solution {
public:
int search(vector<int>& nums, int target) {
if(nums.size()==0){
return -1;
}
int begin=0;
int end=nums.size()-1;
int rotate;
//没旋转点直接算
if(nums[0]<=nums[nums.size()-1]){
return binary_search(nums,target,begin,end);
}
//先找到旋转点
while(begin<=end){
int mid = (begin+end)/2;
if(nums[0] <= nums[mid]){
if(nums[mid] > nums[mid+1]){
rotate=mid;
break;
}
else{
begin=mid+1;
}
}
else{
if(nums[mid]<nums[mid-1]){
rotate=mid-1;
break;
}
else{
end=mid-1;
}
}
}
if(target>=nums[0]){
return binary_search(nums,target,0,rotate);
}
else{
return binary_search(nums,target,rotate+1,nums.size()-1);
}
}
int binary_search(vector<int>& nums ,int target ,int begin ,int end){
while(begin<=end){
int mid=(begin+end)/2;
if(target==nums[mid]){
return mid;
}
else if(target < nums[mid]){
end=mid-1;
}
else{
begin=mid+1;
}
}
return -1;
}
};
代码2:
只靠一轮二分查找,判断区间时考虑全面即可
class Solution {
public:
int search(vector<int>& nums, int target) {
int begin=0;
int end=nums.size()-1;
while(begin<=end){
int mid=(begin+end)/2;
if(nums[mid]==target){
return mid;
}
else if(target<nums[mid]){
if(nums[begin]<nums[mid]){
if(nums[begin]<=target){
end=mid-1;
}
else{
begin=mid+1;
}
}
else if(nums[begin]>nums[mid]){
end=mid-1;
}
else if(nums[begin]==nums[mid]){
begin=mid+1;
}
}
else if(target > nums[mid]){
if(nums[begin]<nums[mid]){
begin=mid+1;
}
else if(nums[begin] > nums[mid]) {
if(nums[begin]>target){
begin=mid+1;
}
else if(nums[begin]<=target){
end=mid-1;
}
}
else if(nums[begin]==nums[mid]){
begin=mid+1;
}
}
}
return -1;
}
};
2.二叉查找树(二叉排序树)
2.1基础知识
二叉查找树(binary search tree),是一颗具有下列性质的二叉树:
- 若左子树不空,则左子树上所有的节点值均小于等于根节点的值
- 若右子树不空,则右子树上所有节点的值均大于等于根节点的值
- 左右子树也分别是二叉排序树
- 等于的情况只能出现在左子树或右子树中的某一侧
二叉查找数的中序遍历是从小到大的,所以又叫做二叉排序树。
二叉查找树的插入:
递归写法
void BST_insert(TreeNode *node ,TreeNode *insert_node){
if(insert_node->val <= node->val){
if(node->left){
BST_insert(node->left,insert_node);
}
else{
node->left=insert_node;
}
}
else{
if(node->right){
BST_insert(node->right,insert_node);
}
else{
node->right=insert_node;
}
}
}
二叉查找树中的查找:
bool BST_search(TreeNode *node,int value)
{
if(node->val==value){
return true;
}
if(node->val > value){
if(node->left){
return BST_search(node->left,value);
}
else{
return false;
}
}
else{
if(node->right){
return BST_search(TreeNode *node,int value);
}
else{
return false;
}
}
}
2.2相关例题
例6:二叉查找树编码与解码(LeetCode 449-中等)
题目:
序列化是将数据结构或对象转换为一系列位的过程,以便它可以存储在文件或内存缓冲区中,或通过网络连接链路传输,以便稍后在同一个或另一个计算机环境中重建。
设计一个算法来序列化和反序列化二叉搜索树。 对序列化/反序列化算法的工作方式没有限制。 您只需确保二叉搜索树可以序列化为字符串,并且可以将该字符串反序列化为最初的二叉搜索树。
编码的字符串应尽可能紧凑。
注意:不要使用类成员/全局/静态变量来存储状态。 你的序列化和反序列化算法应该是无状态的。
分析:
将二叉排序树的前序遍历作为编码,解码直接用二叉排序树的插入元素算法即可;
注:用层次遍历作为编码也可!
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
string data;
PreOrder(root, data);
return data;
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
if (data.empty()) {
return nullptr;
}
string value;
vector<TreeNode*> vecNode;
for (int i = 0; i < data.length(); ++i) {
if (data[i] == '#') {
int num = stoi(value);
vecNode.push_back(new TreeNode(num));
value.clear();
} else {
value += data[i];
}
}
for (int i = 1; i < vecNode.size(); ++i) {
BstInsert(vecNode[0], vecNode[i]);
}
return vecNode[0];
}
private:
void PreOrder(TreeNode* root, string& data)
{
if (root == nullptr) {
return;
}
data += std::to_string(root->val) + "#";
PreOrder(root->left, data);
PreOrder(root->right, data);
}
void BstInsert(TreeNode* root, TreeNode* insertNode)
{
if (insertNode->val < root->val) {
if (root->left) {
BstInsert(root->left, insertNode);
} else {
root->left = insertNode;
}
} else {
if (root->right) {
BstInsert(root->right, insertNode);
} else {
root->right = insertNode;
}
}
}
};
// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));
例7:计算右侧小于当前元素的个数(LeetCode 315-困难)
题目:
给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
实例:
输入: [5,2,6,1]
输出: [2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1).
2 的右侧仅有 1 个更小的元素 (1).
6 的右侧有 1 个更小的元素 (1).
1 的右侧有 0 个更小的元素.
思路:
正常遍历计算得话时间复杂度是n^2,那么我们得目标就是优化到nlogn,所以要优先思考常用得nlogn的数据结构和算法。
- 右侧小于当前元素个数,将数组倒置,转化为左侧小于当前元素
- 按照倒置后的数组构建二叉排序树
- 设计新的二叉排序树结点,存储左子树结点数量
- 找到小于结点的数量的数学关系
代码:
class Solution {
public:
vector<int> countSmaller(vector<int>& nums) {
if(nums.empty()){
return nums;
}
vector<int> result;
vector<BSTNode*> node_vec;
vector<int> count;//从后往前插入过程中,比当前节点值小的
for(int i=nums.size()-1;i>=0;i--){
node_vec.push_back(new BSTNode(nums[i]));
}
count.push_back(0);
for(int i=1;i<node_vec.size();i++){
int count_small=0;
BSTADD(node_vec[0],node_vec[i],count_small);
count.push_back(count_small);
}
//逆置数组倒置回正常顺序
for(int i=node_vec.size()-1;i>=0;i--){
delete node_vec[i];
result.push_back(count[i]);
}
return result;
}
public:
struct BSTNode{
int val;
int count;
BSTNode* left;
BSTNode* right;
BSTNode(int x):val(x),left(NULL),right(NULL),count(0){}
};
void BSTADD(BSTNode *node,BSTNode *newnode,int &count_small){
if(newnode->val > node->val){
count_small+=node->count+1;
if(!node->right){
node->right=newnode;
}
else{
BSTADD(node->right,newnode,count_small);
}
}
else{
node->count++;
if(!node->left){
node->left=newnode;
}
else{
BSTADD(node->left,newnode,count_small);
}
}
}
};