二分查找
二分查找又称折半查找,假设表中元素是按升序排列,将表中间位置的关键字与查找关键字比较,
(1)如果两者相等,则查找成功;
(2)否则利用中间位置将表分成前、后两个子表;如果中间位置的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。
二分查找
已知一个排序数组A,如 A = [ -1, 2, 5, 20, 90, 100, 207, 800],另一个乱序数组B,如B = [ 50, 90, 3, -1, 207, 80],求B中任意某个元素,是否在A中出现,结果存储在数组C中,出现用1表示,未出现用0表示,如 C= [ 0, 1, 0, 1, 1, 0]。
class search_array
{
public:
std::vector<int> search_array(std::vector<int>& sort_array, std::vector<int>& random_array)
{
std::vector<int> result(random_array.size(), 0);
for(int i=0; i< random_array.size(); i++)
{
int res = binary_search(sort_array,random_array[i]);//循环方法
//int res = binary_search(sort_array,0,sort_array.size()-1,random_array[i]);//递归方法
result[i] = res;
}
return result;
}
private:
/* 二分查找 递归 */
bool binary_search(std::vector<int>& sort_array, int begin, int end, int target)
{
if(begin > end) return; // 终止条件
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);
}
/* 二分查找 循环 */
bool binary_search(std::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;
}
};
插入位置(二分查找)
给定一个排序数组nums(无重复元素)与目标target,如果target在nums里出现,则返回target所在下标,如果target在nums里未出现,则返回target应该插入位置的数组下标,使得将target插入数组nums后,数组仍有序。
如nums = [ 1, 3, 5, 6],插入元素为 0 时,返回 0;为 1 时,返回为 0;为 2时,返回为 1;为 3 时,返回为1。
int search_insert(std::vector<int>& nums, int target)
{
if(nums.empty()) return 0; // 异常判断返回
int begin = 0; // 查找开始位置
int end = nums.size() - 1; // 查找结束位置
int index = -1; // 定义查找位置
while(index == -1) // 退出循环条件为 找到查找位置
{
int mid = (begin + end)/2; // 取 中间位置
if(target == nums[mid]) // 中间关键字满足,则 index = mid
index = mid;
else if(target < nums[mid]) // 中间关键字 大于 目标,则查找前子表
{
if(mid == 0 || target > nums[mid - 1])// 前子表的尾元素 小于 目标, 则 index = mid
index = mid;
end = mid - 1;
}
else // 中间关键字 小于 目标,则 查找后子表
{
if(mid==nums.size()-1 || target<nums[mid+1])// 后子表的头元素 大于 目标, 则 index = mid + 1
index = mid + 1;
begin = mid + 1;
}
}
return index;
}
区间查找(二分查找)
给定一个排序数组nums(nums中有重复元素)与目标值target,如果target在nums里出现,则返回target所在区间的左右端点下标,[左端点,右端点],如果target在nums里未出现,则返回[-1,-1]。
如nums[]=[ 5,7,7,8,8, 8,8,10],target为8时,返回[ 3, 6];target为 6时,返回[-1, -1];target为 10时,返回[ 7,7]。
std::vector<int> search_range(std::vector<int>& nums, int target)
{
int left_bound = search_left_bound(nums,target);
int right_bound = search_right_bound(nums,0,nums.size()-1,target,-1);
std::vector<int> result;
result.push_back(left_bound);
result.push_back(right_bound);
return result;
}
/* 查找目标左边界 (使用循环方法)*/
int search_left_bound(std::vector<int>& nums, int target)
{
int begin = 0; //查找的起始位置
int end = nums.size() - 1; //查找的终止位置
int index = -1; //索引的返回值
while(begin <= end) //终止条件
{
int mid = (begin + end)/2; //二分位置
//(循环)二分查找的经典判断(三分支)
if( target == nums[mid]) //目标值等于索引值
{
index = mid; //更新index
end = mid - 1; //(重点操作)左移终止位置,以便下个循环更新index
}
else if( target < nums[mid]) //目标值小于索引值
end = mid - 1; //左移终止位置
else //目标值大于索引值
begin = mid + 1;//右移起始位置
}
return index;
}
/* 查找目标右边界 (使用递归方法)*/
int search_right_bound(std::vector<int>& nums, int begin, int end, int target, int index)
{
//终止条件
if(begin > end)
return index;
int mid = (begin + end)/2;
//(递归)二分查找的经典判断三分支
if( target == nums[mid]) //(重点)目标值等于索引值,则更新起始位置和index值
return search_right_bound(nums, mid + 1, end, target, mid);
else if( target < nums[mid]) //目标值小于索引值,则更新终止位置
return search_right_bound(nums, begin, mid - 1, target, index);
else //目标值大于索引值,则更新起始位置
return search_right_bound(nums, mid + 1, end, target, index);
}
旋转数组查找(二分查找)
给定一个排序数组nums(nums中无重复元素),且nums可能以某个未知下标旋转,给定目标值target,求target是否在nums中出现,若出现返回所在下标,未出现则返回-1。
如 nums原来为[1,3,6,7,9,12,15,20],旋转后可能为[6,7,9,12,15,20,1,3],[3,6,7,9,12,15,20,1]。
int search(std::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; //返回 mid 索引位置
else 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;
}
}
else if(target > nums[mid])
{
/* 根据起始位置值与中间位置值进行讨论 */
if(nums[begin] < nums[mid])
begin = mid + 1;
else if(nums[begin] > nums[mid])
{
if(target >= nums[begin])
end = mid - 1;
else
begin = mid + 1;
}
else if(nums[begin] == nums[mid])
{
begin = mid + 1;
}
}
}
return -1;
}
分治算法
分治算法:
将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解后进行合并,就可到原问题的解。
一般步骤:
1.分解,将要解决的问题划分成若干规模较小的同类问题;
2.求解,当子问题划分得足够小时,用较简单的方法解决;
2.合并,按原问题的要求,将子问题的解逐层合并构成原问题的解。
合并数组
void merge_two_vec( std::vector<int>& sub_vec1, //数组1
std::vector<int>& sub_vec2, //数组2
std::vector<int>& vec) //合并后的数组
{
int i = 0;
int j = 0;
while(i < sub_vec1.size() && j < sub_vec2.size())
{
if( sub_vec1[i] < sub_vec2[j])
vec.push_back(sub_vec1[i++]);
else
vec.push_back(sub_vec2[j++]);
}
for(;i < sub_vec1.size();i++)
vec.push_back(sub_vec1[i]);
for(;j < sub_vec2.size();j++)
vec.push_back(sub_vec2[j]);
}
void merge_sort(std::vector<int>& vec)
{
if(vec.size() < 2) return; // 求解:子问题足够小时,直接求解
//对原问题进行拆解,即对原数组拆分为两个规模相同的数组,再对它们分别求解(排序)
int mid = vec.size() / 2;
std::vector<int> sub_vec1,sub_vec2;
for(int i=0; i<mid;i++)
sub_vec1.push_back(vec[i]);
for(int i= mid; i<size();i++)
sub_vec2.push_back(vec[i]);
//对拆解后的两个子问题进行求解
merge_sort(sub_vec1);
merge_sort(sub_vec2);
//合并,将子问题的解进行合并
merge_two_vec(sub_vec1,sub_vec2,vecll);
}
归并排序与分治算法
逆序数(归并排序)
已知数组nums,求新数组count,count[i]代表了在nums[i]右侧且比nums[i]小的元素个数。
例如:
nums=[5,2,6,1],count=[2,1,1,0];
nums=[6,6,6,1,1,1],count=[3,3,3,0,0,0];
nums=[5,-7,9,1,3,5,-2,1],count=[5,0,5,1,2,2,0,0];
在这里插入代码片
std::vector<int> count_smaller(std::vector<int>& nums)
{
//将元素nums[i]与元素位置i绑定为Pair,如<nums[i],i>,排序时,按照nums[i]的大小对pair进行排序,这样无论nums[i]如何排序,都知道nums[i]在原数组中的哪个位置。
//利用pair对<nums[i],i>中的i对count[i]进行n更新,任何一次子数组的归并,都可以认为是前半段与后半段有序数组逆序数的计算,只需根据绑定的位置i将逆序数累加至count数组中。
std::vector<std::pair<int,int>> vec;
std::vector<int> count;
for(int i=0; i<nums.size(); i++)
{
vec.push_back(std::make_pair(nums[i],i));
count.push_back(0);
}
merge_sort(vec, count);
return count;
}
void merge_sort_two_vec(std::vector<std::pair<int,int>>& sub_vec1,
std::vector<std::pair<int,int>>& sub_vec2,
std::vector<std::pair<int,int>>& vec,
std::vector<int>& count)
{
int i=0;
int j=0;
while(i<sub_vec1.size() && j<sub_vec2.size())
{
if( sub_vec1[i].first <= sub_vec2[j].first)
{
count[sub_vec1[i].second] += j;
vec.push_back(sub_vec1[i]);
i++;
}
else
{
vec.push_back(sub_vec2[j]);
j++;
}
}
for(;i<sub_vec1.size();i++)
{
count[sub_vec1[i].second] += j;
vec.push_back(sub_vec1[i]);
}
for(;j<sub_vec2.size();j++)
vec.push_back(sub_vec2[j]);
}
void merge_sort(std::vector<std::pair<int,int>>& vec,
std::vector<int>& count)
{
if( vec.size() < 2) return;
int mid = vec.size() / 2;
std::vector<std::pair<int,int>> sub_vec1;
std::vector<std::pair<int,int>> sub_vec2;
for(int i=0; i< mid; i++)
sub_vec1.push_back(nums[i]);
for(int i=mid; i< nums.size(); i++)
sub_vec2.push_back(nums[i]);
merge_sort(sub_vec1, count);
merge_sort(sub_vec2, count);
vec.clear();
merge_sort_two_vec(sub_vec1,sub_vec2,vec,count);
}
K个排序链表的合并(分割算法)
已知k个已排序链表头节点指针,将这k个链表合并,合并后仍为有序的,返回合并后的头节点。
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *_next) : val(x), next(_next) {}
};
ListNode* mergeLists(ListNode* l1, ListNode* l2)
{
// 定义哑结点
ListNode dummy;
ListNode* tail = &dummy; // 定义 尾指针
while(l1 && l2) // 结点指针都不为空时
{
if(l1->val < l2->val)
{
tail->next = l1;
l1 = l1->next;
}
else
{
tail->next = l2;
l2 = l2->next;
}
tail = tail->next;
tail->next = nullptr;
}
if(l1) tail->next = l1;
if(l2) tail->next = l2;
return dummy.next;
}
// 递归方式 合并K个列表
ListNode* mergeKLists(std::vector<ListNode*>& lists)
{
if(lists.size() == 0) return nullptr;
if(lists.size() == 1) return lists[0];
if(lists.size() == 2) return mergeTwoLists(lists[0],lists[1]);
int mid = lists.size()/2;
std::vector<ListNode*> sub_lists1, sub_lists2;
for(int i = 0; i < mid; i++)
sub_lists1.push_back(lists[i]);
for(int i = mid; i < lists.size();i++)
sub_lists2.push_back(lists[i]);
ListNode* l1 = mergeKLists(sub_lists1);
ListNode* l2 = mergeKLists(sub_lists2);
return mergeLists(l1,l2);
}
// 循环方式 合并K个列表
ListNode* mergeKLists(std::vector<ListNode*> lists)
{
if(lists.empty()) return nullptr;
while(lists.size() > 1)
{
std::vector<ListNode*> temp;
for(int i = 0; i < lists.size()-1; i = i + 2)
temp.push_back(mergeLists(lists[i],lists[i+1]));
if(lists.size() % 2)
temp.push_back(lists[lists.size()-1]));
lists = temp;
}
return lists[0];
}