刷题笔记:二分查找与二叉排序树(第一遍刷搞懂每道题的解法;第二遍刷搞懂这一类题通用的思想和策略;第三遍刷思考什么样的题属于这个类别)

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),是一颗具有下列性质的二叉树:

  1. 若左子树不空,则左子树上所有的节点值均小于等于根节点的值
  2. 若右子树不空,则右子树上所有节点的值均大于等于根节点的值
  3. 左右子树也分别是二叉排序树
  4. 等于的情况只能出现在左子树或右子树中的某一侧

二叉查找数的中序遍历是从小到大的,所以又叫做二叉排序树。

二叉查找树的插入

递归写法

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);
            }

        }
    }
};

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值