目录
449.序列化与反序列化二叉搜索树(二叉查找树的编码与解码)
二分查找算法(递归,循环)
具有分治思想的多用循环,具有回溯思想的多用递归。
二分或者二叉排序树都是在 分治的解决问题
二分查找:
二分查找:待查数是跟中间的数对比,只有查找的数恰好等于中间的数返回正确;
递归
若比中间的数大,则去搜索右区间 [mid +1 ,end ]
若比中间的数小,则去搜索左区间 [begin ,mid - 1]
函数参数传引用的原因是:
拷贝大的类类型对象或者容器对象比较低效,所以通过引用形参访问该类型的对象。
bool binary_search(vector<int> &vec,int begin ,int end ,int search_num) {
if (begin > end) {
return false;
}
int mid = ( end + begin ) / 2;
if (vec[mid] == search_num) {
return true;
}
else if (search_num > vec[mid]) {
binary_search(vec, mid+1 , end, search_num);
}
else if (search_num < vec[mid]) {
binary_search(vec, begin, mid-1 , search_num);
}
}
循环:
bool binary_search(vector<int> &vec, int search_num) {
int begin = 0;
int end = vec.size() - 1;
/*只有在搜查范围合法的时候才有查找的必要*/
while (begin <= end) {
int mid = (end + begin) / 2;
if (vec[mid] == search_num) {
return true;
}
if (search_num > vec[mid]) {
begin = mid + 1;
}
else if (search_num < vec[mid]) {
end = mid - 1;
}
}
return false;
}
总结:
分清楚循环和递归的两个查找条件,一个是begin<= end 的时候才继续查找,一个是begin > end 的时候才停止递归搜索。
35.搜索插入的位置
边界条件mid==0时,此时还不等于search_value就说明要查找的那个值比最前面的元素都要小,说明只能插入到第一个元素的位置(0);同理,mid== nums.size()-1,只能插入到最后。
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int index = -1;
int begin = 0;
int end = nums.size()-1;
while(index== -1){
int mid = (begin+end)/2;
if(nums[mid] ==target ){
index = mid;
}
else if(target > nums[mid]){
if( mid == nums.size()-1 || target < nums[mid+1]){
index = mid+1;
}
begin = mid +1 ;
}
else if(target < nums[mid]){
if(mid == 0 || target > nums[mid-1]){
index = mid;
}
end = mid -1 ;
}
}
return index;
}
};
34. 在排序数组中查找元素的第一个和最后一个位置
思考:还是基础的二分查找,只是在判断左右边界的时候要添加约束条件。
左边界:若查找到的mid等于target,并且数组的前一个小于target或者mid已经是数组的最前面的元素,则这个元素的位置就是左边界;否则说明此时查找到的mid并不是左边第一个,再搜索区间[begin,mid-1],然后找到第一个出现的。
右边界:若查找到的mid等于target,并且数组的后一个大于target或者mid已经是数组的最后面的元素,则这个元素的位置就是右边界;否则说明此时查找到的mid并不是右边最后一个,再搜索区间[mid+1,end],然后找到最后一个出现的。
int left_bound(std::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) {
if (nums[mid - 1] < target || mid == 0) {
return mid;
}
//若nums[mid - 1] == target 则说明此时查找到的mid并不是左边第一个
//再搜索区间[begin,mid-1],然后找到第一个出现的
end = mid - 1;
}
else if (target >nums[mid]) {
begin = mid + 1;
}
else if (target <nums[mid]) {
end = mid - 1;
}
}
return -1;
}
int right_bound(std::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) {
if (nums[mid + 1] > target || mid == nums.size() - 1) {
return mid;
}
//若nums[mid + 1] == target 则说明此时查找到的mid并不是右边最后一个
//再搜索区间[mid+ 1 ,end],然后找到第一个出现的
begin = mid + 1;
}
else if (target >nums[mid]) {
begin = mid + 1;
}
else if (target <nums[mid]) {
end = mid - 1;
}
}
return -1;
}
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> range;
range.push_back( left_bound( nums, target));
range.push_back( right_bound( nums, target));
return range;
}
};
33、搜索旋转排序数组
思考:
旋转了之后,整个数组不再满足升序的关系,如果此时查找一个数,单纯的通过和nums[mid]进行对比,可能会判断错误查找的区间,从而得到错误的解。比如,要查找图中的“0”元素,nums[mid] = 7,则会到左边查找,可是此时数组的两部分,左边部分[4,5,6],并没有0,所以会返回查找失败。若查找图中“5”元素,则可以通过二分查找查到。就是说在二分查找的时候,我们要添加一些约束条件,才能到正确的查找区间去寻找target。
特征:给定的升序数组,无论怎么旋转,它的nums[begin]一定满足大于nums[end]。因为它是绕着某个点旋转,前面的部分叫做part1,后面叫part2,则part2的第一个结点一定大于part1的最后一个结点,所以nums[begin]一定满足大于nums[end]。
算法思路:
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;
}
//因为nums[begin] > nums[end]
if(target > nums[mid]){//找旋转区间和递增区间
if(nums[begin] < nums[mid]){
//则说明是递增的区间
//但是此时target > nums[mid],就肯定不会在这个区间了,所以去另外一个区间查找
begin = mid + 1;
}
if(nums[begin] > nums[mid]){
//则说明是旋转区间
//但是,并不能一定判断它就在旋转区间,也有可能在递增区间
if(target >= nums[begin]){
//若taget比nums[begin]还大,又因为nums[begin] > nums[end],此时肯定不会在递增区间了,所以在旋转区间搜索。
end = mid -1;
}
else{
//否则就只用在递增区间搜索
begin = mid +1;
}
if(nums[mid]== nums[begin]){
//此时就两个数了,肯定只用查找剩下的那个了
begin = mid +1 ;
}
}
}
if(target < nums[mid]){
//递增区间
if(nums[begin] < nums[mid]){
if(target >= nums[begin]){
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 ;
}
}
}
return -1;
}
};
二叉查找树:
1、二叉查找(排序)树的定义
2、二叉查找树插入结点(递归)
代码:
void BST_insert(TreeNode* root, TreeNode* node) {
if (node->value < root->value) {
if (!root->left) {
root->left = node;
}
else {
BST_insert(root->left, node);
}
}
else {
if (!root->right) {
root->right = node;
}
else {
BST_insert(root->right, node);
}
}
}
3、二叉查找树搜索
bool BST_search(TreeNode* root, TreeNode* node) {
if (node->value == root->value) {
return true;
}
if (node->value < root->value) {
if (root->left) {
BST_search(root->left, node);
}
else {
return false;
}
}
else {
if (root->right) {
BST_search(root->right, node);
}
else {
return false;
}
}
}
代码:
bool BST_search(TreeNode* root, TreeNode* node) {
if (node->value == root->value) {
return true;
}
if (node->value < root->value) {
if (root->left) {
BST_search(root->left, node);
}
else {
return false;
}
}
else {
if (root->right) {
BST_search(root->right, node);
}
else {
return false;
}
}
}
449.序列化与反序列化二叉搜索树(二叉查找树的编码与解码)
为什么要实现序列化和反序列化?
在计算机网络传输的过程中,如果有一个数据结构要传递给另一个主机,不能直接拷贝这个数据结构的代码,所以得通过把它存储成字符串的形式,然后到另一个主机通过解码把它还原成原来的结构。
先序遍历:8 3 1 6 10 15 //按照这种重新插入成一个新的二叉搜索树和原先的是一样的,所以按照先序遍历得到的结果,再按照二叉查找树插入,可以得到原始的二叉查找树。
中序遍历:1 3 6 8 10 15 //首先是二叉树查找树的根节点都变了,并且,这样插入成了一个右斜的树,成了个链表了。
后序遍历:1 6 3 10 15 8 //二叉树查找树的根节点都变了
整型转字符串(字符串转整型)
1、整型转字符串
把某一个数转换成字符串的形式。
比如89,要从int --->> string ,首先要定义个字符串str,再利用对10取余,获得最低位的9,此时9是int型,加一个字符'0',然后就变成了字符型的'9',此时把数字89除以10,获得次低位8,迭代停止的条件就是while(num),最后再对字符串取反,这里用的是<algorithm>中的reverse;也可以利用for(size_t i = str.size();i >= 0;i++),逆序一下。最后添加的“#”,代表一个终止符,标记一下的意思,因为如果你传入一串数字,再解码的时候,不知道从哪里断开是不行的。
代码:
string IntegerTostring(int num, string &temp) {
while (num) {
temp += num % 10 + '0';
num /= 10;
}
reverse(temp.begin(), temp.end());
temp += "#";
return temp;
}
2、字符串转整型
比如字符串“89#23#”,从string--->> int,首先要定义一个整数integer,对字符串每一位检索,当遇到"#"的时候,则输出一个数字,同时把临时变量integer清零,以便接下来还要用它来输出第二个数字;如果是检索到字符的时候,integer = integer * 10 + str[i] - '0';因为是从高到低的遍历,所以一开始读的是高位,要乘以10。
代码:
int stringToInteger(string str, int &integer) {
for (auto _str : str) {
if (_str == '#') {
return integer;
}
else {
//integer += _str * 10 - '0';
integer += integer * 10 + _str - '0';
}
}
return integer;
}
整体的代码:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
void integerToString(int num, string &str) {
while (num) {
str += num % 10 + '0';
num /= 10;
}
reverse(str.begin(), str.end());
str += "#";
}
void stringToInteger(string &str, vector<TreeNode*>& node_num) {
int integer = 0;
for (auto item : str) {
if (item == '#') {
node_num.push_back(new TreeNode(integer));
integer = 0;/*每一个数字存储了之后要把integer清空*/
}
else {
integer = integer * 10 + item - '0';
}
}
}
void preOrder(TreeNode* root,vector<int>& node_num ) {
if (!root) {
return;
}
node_num.push_back(root->val);
preOrder(root->left, node_num);
preOrder(root->right, node_num);
}
void BST_insert(TreeNode* root, TreeNode * node) {
if (root->val < node->val) {
if (root->right) {
BST_insert(root->right, node);
}
else {
root->right = node;
}
}
else {
if (root->left) {
BST_insert(root->left, node);
}
else {
root->left = node;
}
}
}
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
//先序遍历
vector<int> node_val;
string str;
if(!root){
return str;
}
preOrder(root, node_val);
for (auto item : node_val) {
string temp;
integerToString(item, temp);
str += temp;
}
return str;
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
if(data.empty()){
return NULL;
}
vector<TreeNode*> node_ptr;
stringToInteger(data, node_ptr);
for (size_t i = 1; i < node_ptr.size(); i++) {
BST_insert(node_ptr[0], node_ptr[i]); /*直接把第一个节点当做根节点。*/
}
return node_ptr[0];
}
};
// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));
315.计算右侧小于当前元素的个数
思考:
这个题上次用的归并排序和pair<>对来解决的,这次利用二叉搜索树来解决。
利用的数据结构:多出一个count;
struct BST_node {
int val;
int count; /*用来保存左子树数量*/
BST_node* left;
BST_node* right;
BST_node(int x) :val(x), count(0), left(NULL), right(NULL) {}
};
!!!关键!!!
为什么count_small += node->count + 1 ?
答:以数组[1,-2,5,3,1]为例,如果这个时候要插入的是9。首先9大于root->val(1),所以直接肯定大于root左侧的2个,此时它的count_small += root->count + 1 /*加1 在于比这个root也更大了啊!*/ ,然后插入到右子树,这时遇到了root->val(5),它的左子树个数是1个,然后又插入到5的右侧,此时count_small = 3 + 2= 5;正确!如果只是单纯的count_small = root->count + 1,那么多次更新之后9 的count_small 反而只等于2,错误!
遇到的问题:这里用size_t 定义的i,可以看出来!i是无符号数!!!所以i = 0 的时候,再减1 ,反而成了个无穷大的数!哎!
代码:
struct Treenode{
int val;
int count;
Treenode* left;
Treenode* right;
Treenode(int x):val(x),count(0),left(NULL),right(NULL){}
};
void BST_insert(Treenode* root,Treenode* node,int &smaller_count){
if(node->val > root->val){
smaller_count += root->count + 1 ;
if(root->right){
BST_insert(root->right,node,smaller_count);
}
else{
root->right = node;
}
}
else{
root->count++;
if(root->left){
BST_insert(root->left,node,smaller_count);
}
else{
root->left = node;
}
}
}
class Solution {
public:
vector<int> countSmaller(vector<int>& nums) {
vector<Treenode*> node_vec;
vector<int> count;
if(nums.empty()){
return count ;
}
for(int i = nums.size() - 1 ;i >= 0 ;i--){
node_vec.push_back(new Treenode(nums[i]));
}
count.push_back(0);
for(size_t i = 1; i<node_vec.size();i++ ){
int smaller_count = 0;
BST_insert(node_vec[0],node_vec[i],smaller_count);
count.push_back(smaller_count);
}
reverse(count.begin(),count.end());
return count;
}
};
最大的体会是今天不走,明天要跑。