二分搜索和二叉查找树
二叉查找树又叫二叉排序树
预备知识 二分查找
折半查找。假设元素升序排列
递归形式
#include <vector>
using namespace std;
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]) {
return binary_search(sort_array, begin, mid - 1, target);
} else {
return binary_search(sort_array, mid + 1, end, target);
}
}
二分搜索的范围都用闭区间。
循环形式
#include <vector>
using namespace std;
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 插入位置
Given a sorted array of distinct integers and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.
Example 1:
Input: nums = [1,3,5,6], target = 5
Output: 2
Example 2:
Input: nums = [1,3,5,6], target = 2
Output: 1
Example 3:
Input: nums = [1,3,5,6], target = 7
Output: 4
Example 4:
Input: nums = [1,3,5,6], target = 0
Output: 0
Example 5:
Input: nums = [1], target = 0
Output: 0
Constraints:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums
contains distinct values sorted in ascending order.-104 <= target <= 104
链接:https://leetcode-cn.com/problems/search-insert-position/
思考:
-
当target在nums中出现时,二分查找的流程没有变化。
-
当target在nums中没有出现时:
(1) 如果target<nums[mid],并且target>nums[mid-1] or mid==0,那应该插入到mid
(2) 如果target>nums[mid],并且target<nums[mid+1] or mid==nums.size()-1,那应该插入到mid+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 (target == nums[mid]) {
index = mid;
} else if (target < nums[mid]) { // 每次指针位置移动之前都判断一下mid是不是要找的位置
if (mid == 0 || target > nums[mid-1]) {
index = mid;
}
end = mid - 1;
} else if (target > nums[mid]) {
if (mid == nums.size()-1||target<nums[mid+1]) {
index = mid + 1;
}
begin = mid + 1;
}
}
return index;
}
};
更简单的方法是直接返回begin
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int begin = 0;
int end = nums.size() - 1;
int mid = 0;
while (begin <= end) {
mid = (begin + end) / 2;
if (target == nums[mid]) {
return mid;
} else if (target < nums[mid]) {
end = mid - 1;
} else {
begin = mid + 1;
}
}
return begin;
}
};
begin mid end 会呈现什么特征?
- 最终是不是一定会聚集到一点,然后 begin+1 或 end-1 从而退出循环?好像是的。(中途退出就不是了)
- begin最终一定会在比target大的位置?是
- end一定会在比target小的位置?是
- 二分法为什么不会越界,即
mid<0
或mid>=len
?因为end=-1
时,end=-1
时,begin=0
,begin<=end
不成立,循环退出
问题2 区间查找
Given an array of integers nums
sorted in ascending order, find the starting and ending position of a given target
value.
If target
is not found in the array, return [-1, -1]
.
Follow up: Could you write an algorithm with O(log n)
runtime complexity?
Example 1:
Input: nums = [5,7,7,8,8,10], target = 8
Output: [3,4]
Example 2:
Input: nums = [5,7,7,8,8,10], target = 6
Output: [-1,-1]
Example 3:
Input: nums = [], target = 0
Output: [-1,-1]
Constraints:
0 <= nums.length <= 10^5
-10^9 <= nums[i] <= 10^9
nums
is a non-decreasing array.-10^9 <= target <= 10^9
链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/
把问题拆分成求左右端点。
为什么不能先找到一个数,再往左右搜索?因为最差情况下会退化到 O ( N ) O(N) O(N)
查找区间左端点时,增加限制条件
- 当 target == nums[mid] 时,若此时 nums[mid-1]<target,则说明mid是区间左端点,返回;否则设置区间右端点为mid-1
在查找右端点时,当 nums[mid+1]>target时则mid是右端点。
上面的算法都是在查找过程中进行操作,所以这应该是二分查找的通用思路。
测试技巧:测试一段数据,而不是一个一个测试
int main() {
int test[] = {5, 7, 7, 8, 8, 8, 8, 10};
vector<int> nums;
Solution solve;
nums.reserve(8);
for (int & i : test) {
nums.push_back(i);
}
for (int i = 0; i < 12; i++) {
vector<int> result = solve.searchRange(nums, i);
printf("%d : [%d, %d]\n", i, result[0], result[1]);
}
return 0;
}
问题3 旋转数组查找
You are given an integer array nums
sorted in ascending order, and an integer target
.
Suppose that nums
is rotated at some pivot unknown to you beforehand (i.e., [0,1,2,4,5,6,7]
might become [4,5,6,7,0,1,2]
).
If target
is found in the array return its index, otherwise, return -1
.
Example 1:
Input: nums = [4,5,6,7,0,1,2], target = 0
Output: 4
Example 2:
Input: nums = [4,5,6,7,0,1,2], target = 3
Output: -1
Example 3:
Input: nums = [1], target = 0
Output: -1
Constraints:
1 <= nums.length <= 5000
-10^4 <= nums[i] <= 10^4
- All values of
nums
are unique. nums
is guranteed to be rotated at some pivot.-10^4 <= target <= 10^4
链接:https://leetcode-cn.com/problems/search-in-rotated-sorted-array/
个人方法:
先找旋转数组的起点。(经测试速度慢)
class Solution {
public:
int search(vector<int> &nums, int target) {
int offset = findBegin(nums);
cout << "offset=" << offset << endl;
int begin = 0;
int end = int(nums.size()) - 1;
for (int i = 0; i < nums.size(); i++) {
cout << nums[i] << ' ';
}
cout << endl;
while (begin <= end) {
int mid = (begin + end) / 2;
int mid_offset = (mid + offset) % int(nums.size());
if (nums[mid_offset] == target) {
cout << "mid=" << mid << endl;
cout << "nums[mid_offset]"
<< "=nums[" << mid_offset << "]=" << nums[mid_offset]
<< endl;
return mid_offset;
} else if (target < nums[mid_offset]) {
end = mid - 1;
} else {
begin = mid + 1;
}
}
return -1;
}
private:
// 查找旋转数组中最小的位置在哪
static int findBegin(vector<int> &nums) {
if (nums.size() < 2)
return 0;
else if (nums.size() == 2)
return nums[0] < nums[1] ? 0 : 1;
int begin = 0;
int end = int(nums.size()) - 1;
while (begin < end - 1) { // break when begin==end-1
int mid = (begin + end) / 2;
if (nums[begin] > nums[mid]) {
end = mid;
} else if (nums[mid] > nums[end]) {
begin = mid;
} else { // 如果从 begin 到 end 是有序的,则返回 begin
return begin;
}
}
return end;
}
};
视频思路:
数据的排布一共可以分为三种情况
(1) | (2) | (3) |
---|---|---|
-
当 target < \lt <*mid时,
- 当*begin
≤
\le
≤*mid时,说明是情况1或者3,
- 当target ≥ \ge ≥*begin,说明应该在[begin,mid-1]中找
- 当target < < <*begin,说明应该在[mid+1,end]中找(*begin==*mid的情况合并到这里面)
- 当*begin
>
\gt
>*mid时,说明是情况2,
- 只能在[begin,mid-1]中找
- 当*begin
≤
\le
≤*mid时,说明是情况1或者3,
-
当 target>*mid 时,
- 当*begin
<
\lt
<*mid时,说明是情况1或者3,【b】
- 只能在区间[mid+1,end]中查找
- 当*begin
>
\gt
>*mid时,说明是情况2
- 如果*target ≥ \ge ≥*begin,说明应该在[begin,mid-1]中查找
- 如果*target < \lt <*begin,说明应该在[mid+1,end]中查找
- 当*begin==*mid时,说明是情况1或3,所以应该合并到情况b中
- 当*begin
<
\lt
<*mid时,说明是情况1或者3,【b】
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 (target == nums[mid]) {
return mid;
} else if (target < nums[mid]) {
if (nums[begin] <= nums[mid]) {
if (target >= nums[begin]) {
end = mid - 1;
} else {
begin = mid + 1;
}
} else {
end = mid - 1;
}
} else {
if (nums[begin] <= nums[mid]) {
begin = mid + 1;
} else {
if (target >= nums[begin]) {
end = mid - 1;
} else {
begin = mid + 1;
}
}
}
}
return -1;
}
};
执行时间是4ms的范例,别人的代码:
int search(vector<int> &nums, int target) {
if (nums.size() == 0) return -1;
int left = 0, right = nums.size() - 1, mid;
int begin = nums[0];
int end = nums[right];
while (left <= right) {
mid = (left + right) / 2;
if (nums[mid] == target) return mid;
if (begin <= nums[mid]) { // 如果左半部分是有序的: 注意 等号是为了解决mid==0的情况
if (begin <= target && target < nums[mid]) { // 如果target在左半部分,第二个小于号没有等于,因为在上上行处理了等于的情况
right = mid - 1;
} else {
left = mid + 1;
}
} else { // 如果右半部分是有序的
if (nums[mid] < target && target <= end) { // 如果target在右半部分
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
发现其实跟视频里的思路类似,只不过划分的情况更简单了。
预备知识 二叉查找树
二叉查找树(二叉排序树)
左子树的所有节点都小于等于根节点的值,右子树的所有节点都大于等于根节点的值。
堆排序中的数是根节点大于所有节点的值。
(上述两种树都是递归定义的)
二叉查找树插入节点
将待插入节点(insert_node),插入至以node为根的二叉查找树中:
如果 insert_node > node:
如果 node 有左子树,则递归的将该节点插入至左子树为根的二叉排序树中
否则,将node->left赋值为该节点地址
否则:
如果有右子树,则递归的将该节点插入至右子树为根的二叉排序树中
否则,将node->right赋值为该节点地址
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;
}
}
}
查找数值
如果value等于当前查看的node的节点值:返回真
如果value小于当前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(node->right, value);
} else {
return false;
}
}
}
问题4 二叉树编码与解码
Serialization is converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.
Design an algorithm to serialize and deserialize a binary search tree. There is no restriction on how your serialization/deserialization algorithm should work. You need to ensure that a binary search tree can be serialized to a string, and this string can be deserialized to the original tree structure.
The encoded string should be as compact as possible.
Example 1:
Input: root = [2,1,3]
Output: [2,1,3]
Example 2:
Input: root = []
Output: []
Constraints:
- The number of nodes in the tree is in the range
[0, 10^4]
. 0 <= Node.val <= 10^4
- The input tree is guaranteed to be a binary search tree.
链接:https://leetcode-cn.com/problems/serialize-and-deserialize-bst/
思路:先序遍历,用逗号分割各数字,然后再用逗号解码。先序是先根。
问题的关键是,知道一颗二叉查找树,推断出按什么顺序插入节点才能构造出这样的树。
对于8--3--1
这条线,只要保证是按8,3,1
的顺序插入的,广度优先搜索和深度优先搜索都无所谓。
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode *root) {
string data;
BST_preorder(root, data);
return data;
}
// Decodes your encoded data to tree.
TreeNode *deserialize(string data) {
if (data.length() == 0) return nullptr; // 这一行如果去掉就会执行出错
vector<TreeNode*>node_vec;
int val = 0;
for (char i : data) {
if (i == ',') {
node_vec.push_back(new TreeNode(val));
val = 0;
} else {
val = val * 10 + i - '0';
}
}
for (int i = 1; i < node_vec.size(); i++) {
BST_insert(node_vec[0], node_vec[i]);
}
return node_vec[0];
}
private:
static void int2string(int val, string &str_val) {
string tmp;
while (val) {
tmp += val % 10 + '0';
val = val / 10;
}
for (int i = int(tmp.length()) - 1; i >= 0; i--) {
str_val += tmp[i];
}
str_val += ',';
}
static void BST_preorder(TreeNode*node, string&data) {
if (!node) return;
string str_val;
int2string(node->val, str_val);
data += str_val;
BST_preorder(node->left, data);
BST_preorder(node->right, data);
}
static void BST_insert(TreeNode*root, TreeNode*node) {
// 有个问题,有等号怎么办?
// 提交时大于号还是大于等于号都行
if (root->val > node->val) {
if (root->left) {
BST_insert(root->left, node);
} else {
root->left = node;
}
} else {
if (root->right) {
BST_insert(root->right, node);
} else {
root->right = node;
}
}
}
};
问题5 逆序数
You are given an integer array nums and you have to return a new counts array. The counts array has the property where counts[i]
is the number of smaller elements to the right of nums[i]
.
Example 1:
Input: nums = [5,2,6,1]
Output: [2,1,1,0]
Explanation:
To the right of 5 there are 2 smaller elements (2 and 1).
To the right of 2 there is only 1 smaller element (1).
To the right of 6 there is 1 smaller element (1).
To the right of 1 there is 0 smaller element.
Constraints:
0 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4
链接:https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/
这是一道重复的题。新思路。
#include <iostream>
#include <vector>
using namespace std;
struct BSTNode {
int val;
int count; // 有多少数字小于等于当前node
BSTNode *left;
BSTNode *right;
BSTNode(int x) : val(x), left(nullptr), right(nullptr), count(0) {}
};
void BST_insert(BSTNode *node, BSTNode *insert_node, int &count_small) {
if (node->val >= insert_node->val) { // 必须是<=
node->count++;
if (node->left) {
BST_insert(node->left, insert_node, count_small);
} else {
node->left = insert_node;
}
} else {
count_small += node->count + 1;
if (node->right) {
BST_insert(node->right, insert_node, count_small);
} else {
node->right = insert_node;
}
}
}
class Solution {
public:
vector<int> countSmaller(vector<int> &nums) {
vector<int> result;
vector<BSTNode *> node_vec;
vector<int> count;
for (int i = int(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;
BST_insert(node_vec[0], node_vec[i], count_small);
count.push_back(count_small);
}
for (int i = int(node_vec.size()) - 1; i >= 0; i--) {
delete node_vec[i];
result.push_back(count[i]);
}
return result;
}
};
int main() {
int data[] = {6, 5, 4, 3, 2, 1};
vector<int> v;
v.reserve(6);
for (int & i : data) {
v.push_back(i);
}
v = Solution().countSmaller(v);
for (auto &i : v) {
cout << i << endl;
}
return 0;
}