前言
本博客按照以下顺序逐步的完成,也是一个学习的过程,所以有啥问题可以随时评论。希望和大家一起进步。
栈,队列,堆
栈,队列,堆的数据结构就不多介绍了,只需要知道栈是先进后出,队列是先进先出。在这里就主要介绍一下堆,其实就是一颗二叉树,还是直接上算法及实现方法吧。
1.寻找一个数组中第K大的数
input:[2,3,4,5,6] k=2
output:5
#include<iostream>
#include<queue>
#include<vector>
#include<functional>
using namespace std;
//算法思路
//1.创建一个优先队列大小为K
//2.维护这个优先队列,如果有数字比优先队列的顶端更大则pop顶端,push新的元素,直至结束。
//3.优先队列中保存了前K大的元素,第一个则是第K大的元素
int Kmax(vector<int>&input,int k){
if(k>input.size())return 0;
std::priority_queue<int,vector<int>,greater<int>> max_queue;
int i;
for(i=0;i<k;++i){
max_queue.push(input[i]);
}
for(i=k;i<input.size();++i){
if(input[i]>max_queue.top()){
max_queue.pop();
max_queue.push(input[i]);
}
}
return max_queue.top();
}
2.寻找一个数组中的中位数
input:[1,2,3] output:2
input:[1,2,3,4] output:2.5
#include<iostream>
#include<queue>
#include<vector>
#include<functional>
using namespace std;
namespace KMAX {
int Kmax(vector<int>&input,int k){
if(k>input.size())return 0;
std::priority_queue<int,vector<int>,greater<int>> max_queue;
int i;
for(i=0;i<k;++i){
max_queue.push(input[i]);
}
for(i=k;i<input.size();++i){
if(input[i]>max_queue.top()){
max_queue.pop();
max_queue.push(input[i]);
}
}
return max_queue.top();
}
//获取中位数
double getMidNum(vector<int>&input){
if(input.empty())return 0;
if(input.size()%2!=0){
return Kmax(input,input.size()/2+1);
}
else {
return (double)(Kmax(input,input.size()/2)+Kmax(input,input.size()/2+1))/2;
}
}
结合第一题的思路,其实中位数就是第K/2大的元素,如果k是奇数则直接为k/2+1,例如K=3则就是第二个,也就是3/2+1;同理可得偶数的情况。
面试题 17.14. 最小K个数:设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。
示例:
输入: arr = [1,3,5,7,2,4,6,8], k = 4
输出: [1,2,3,4]
#include<iostream>
#include<queue>
#include<vector>
#include<functional>
using namespace std;
vector<int> smallestK(vector<int>& input, int k) {
vector<int> output;
if(k>input.size()){return output;}
std::priority_queue<int,vector<int>,less<int>> min_queue;
int i;
for(i=0;i<k;++i){
min_queue.push(input[i]);
}
if(min_queue.empty())return output;
for(i=k;i<input.size();++i){
if(input[i]<min_queue.top()){
min_queue.pop();
min_queue.push(input[i]);
}
}
while(!min_queue.empty()){
output.push_back(min_queue.top());
min_queue.pop();
}
return output;
}
给定两个排序后的数组 A 和 B,其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法,将 B 合并入 A 并排序。
初始化 A 和 B 的元素数量分别为 m 和 n。
示例:
输入:
A = [1,2,3,0,0,0], m = 3
B = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
//逆向双指针
void merge(vector<int>& A, int m, vector<int>& B, int n) {
int pa = m - 1, pb = n - 1;
int tail = m + n - 1;
int cur;
while (pa >= 0 || pb >= 0) {
if (pa == -1)
cur = B[pb--];
else if (pb == -1)
cur = A[pa--];
else if (A[pa] > B[pb])
cur = A[pa--];
else
cur = B[pb--];
A[tail--] = cur;
}
}
链表
链表的概念就先放着,直接上链表的头插法,尾插法。
链表记住:让新来的节点有所指向,前驱节点指向新来的节点
头插法:
//头插法
void head_push(ListNode*head,int val){
ListNode* node = new ListNode(val);
node->next = head->next;
head->next = node;
}
尾插法
//(尾插法)
void push_back(ListNode*head,int val){
ListNode* node = new ListNode(val);
ListNode* pre = head;
while (pre->next) {
pre=pre->next;
}
pre->next = node;
}
链表的反转
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
//链表反转
ListNode* revolveList(ListNode*head){
ListNode* pre =NULL;
while(head){
ListNode *temp = head->next;
head->next = pre; //新来的指针有所指向
pre = head;
head= temp;
}
return pre;
}
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。
示例:
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
ListNode* reverseBetween(ListNode* head, int m, int n) {
int change_len = n-m+1;
ListNode* pre_head = NULL;
ListNode* result =head;
while(head&&--m){
pre_head = head;
head = head->next;
}
//记录起始地点,反转后即为末尾地点
ListNode*modify_lise_tail = head;
ListNode *new_head = NULL;
while(head&&change_len){
//头插法
ListNode* next = head->next;
head->next = new_head;
new_head = head;
head = next;
--change_len;
}
modify_lise_tail->next = head;
if(pre_head){
pre_head->next = new_head;
}else{
result = new_head;
}
return result;
}
//检查链表是否有环
//快慢指针法
int num =0;
if(!head)return 0;
ListNode * fast=head;
ListNode * slow=head;
while(fast->next&&slow->next){
fast = fast->next;
slow = slow->next;
if(fast->next==NULL){
return 0;
}
fast = fast->next;
if(fast == slow){
return 1;
}
num++;
if(num>10000000){
break;
}
}
return 0;
//给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
//算法思路
1.确定一个快慢指针,fast = 2slow
2.当第一次相遇时,假设从头节点到环起始点的距离为a,从环的起始点到相遇点的距离为b,从相遇点到环起点的距离为c,则第一次相遇时,慢指针走过的路程为a+b,快指针走过的路程为a+b+c+b。
3,为此满足2(a+b) = a+2b+c,由此得到a = c。
4,所以只需要让快指针在相遇的位置,一次向前移动一步,让慢指针指向头节点,一次移动一步相遇时,即为环的起点。
ListNode *detectCycle(ListNode *head) {
int num =0;
if(!head)return NULL;
ListNode * fast=head;
ListNode * slow=head;
while(fast->next&&slow->next){
fast = fast->next;
slow = slow->next;
if(fast->next==NULL){
return NULL;
}
fast = fast->next;
if(fast == slow){
slow = head;
while(!(slow == fast)){
slow = slow->next;
fast = fast->next;
}
return slow;
}
num++;
if(num>10000000){
break;
}
}
return NULL;
}
贪心
//分糖果
//假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
//对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,
//都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。
//你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
//leetcode 455
int findContentChildren(std::vector<int>&g,vector<int>&s){
int child =0;
sort(g.begin(),g.end());
sort(s.begin(),s.end());
for(int i=0;i<s.size();++i){
if(child<g.size()&&s[i]>=g[child]){
child++;
}
}
return child;
}
//摇摆序列
//leetcode 376 wiggle subsequence
//如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列
int wiggleMaxLength(vector<int>& nums) {
if(nums.empty())return 0;
int num =1;
int begin = nums[0];
const int UP=1;
const int DOWN = 2;
const int BEGIN =0;
int STATE = 0;
for(int i=1;i<nums.size();++i){
if(begin>nums[i]){
switch (STATE)
{
case UP:
/* code */
begin =nums[i];
num++;
STATE = DOWN;
break;
case DOWN:
begin = nums[i];
break;
case BEGIN:
begin = nums[i];
STATE = DOWN;
num++;
break;
default:
break;
}
}else if(begin<nums[i]){
switch (STATE)
{
case UP:
/* code */
begin = nums[i];
break;
case DOWN:
begin = nums[i];
num++;
STATE = UP;
break;
case BEGIN:
begin = nums[i];
STATE = UP;
num++;
break;
default:
break;
}
}else{
continue;
}
}
return num;
}
移除k个数字使得数字最小
leetcode 402
string removeKdigits(string num, int k) {
vector<char> s;
string result = "";
for(int i=0;i<num.length();++i){
while (s.size()!=0&&s.back()>num[i]&&k>0)
{
/* code */
s.pop_back();
k--;
}
if(num[i]!='0'||s.size()!=0){
s.push_back(num[i]);
}
}
while (s.size()!=0&&k>0)
{
/* code */
s.pop_back();
k--;
}
for(int i=0;i<s.size();++i){
result.append(1,s[i]);
}
result = result==""?"0":result;
return result;
}
跳跃游戏
leetcode 55 跳跃游戏
bool canJump(vector<int>& nums) {
vector<int> jumpPosition;
for(int i=0;i<nums.size();++i){
jumpPosition.push_back(i+nums[i]);
}
int jump =0;
int max_index = jumpPosition[0];
while(jump<jumpPosition.size()&&jump<max_index){
if(max_index<jumpPosition[jump]){
max_index = jumpPosition[jump];
}
jump++;
}
return jump==jumpPosition.size();
}
跳跃游戏二
leetcode 45 跳跃游戏二
int jump(vector<int> & nums){
if(nums.size()<2){
return 0;
}
int jumpMin =1;
int currentMaxIndex = nums[0];
int preMaxIndex = nums[0];
for(int i=0;i<nums.size();++i){
if(i>currentMaxIndex){
jumpMin++;
currentMaxIndex = preMaxIndex;
}
preMaxIndex = preMaxIndex>nums[i]+i?preMaxIndex:nums[i]+i;
}
return jumpMin;
}
射击气球
leetcode 452 用最少数量的箭引爆气球
bool cmp(vector<int>& a,vector<int>& b){
return a[0]<b[0];
}
int findMinArrowShots(vector<vector<int>>& points) {
if(points.empty()){
return 0;
}
sort(points.begin(),points.end(),cmp);
int shootNum =1;
int shootBegin = points[0][0];
int shootEnd = points[0][1];
for(int i=1;i<points.size();++i){
if(points[i][0]<= shootEnd){
shootBegin = points[i][0];
if(shootEnd>points[i][1]){
shootEnd = points[i][1];
}
}else{
shootNum++;
//新的区间
shootBegin = points[i][0];
shootEnd = points[i][1];
}
}
return shootNum;
}
//用户分组
//leetcode 1282 用户分组
vector<vector<int>> groupThePeople(vector<int>& groupSizes) {
//第一步 先把相同的用户组都存到一起pair<用户组的大小,用户组的成员(vector)>
//对这个pair中的成员进行分类
if(groupSizes.empty()){
return vector<vector<int>>();
}
vector<vector<int>> result;
unordered_map<int,vector<int>> ump;
for(int i = 0;i<groupSizes.size();++i){
if(ump.find(groupSizes[i])!=ump.end()){
ump[groupSizes[i]].push_back(i);
}else
{
vector<int> newV {i};
ump.insert(pair<int,vector<int>>(groupSizes[i],newV));
}
}
for(auto k:ump){
while(!k.second.empty()){
vector<int> temp;
for(int j=0;j<k.first;++j){
temp.push_back(k.second.back());
k.second.pop_back();
}
result.push_back(temp);
}
}
return result;
}
递归、回溯、分治
寻找所有子集
给定一个集合,输出该集合的所有子集
#include<iostream>
#include<vector>
using namespace std;
void subsetR(int i,vector<int>& num,vector<int>&temp,vector<vector<int>>&outPut){
if(i>=num.size()){
return;
}
//一个元素有两种选择
temp.push_back(num[i]);
outPut.push_back(temp);
subsetR(i+1,num,temp,outPut);
temp.pop_back(); //回溯
subsetR(i+1,num,temp,outPut);
}
vector<vector<int>> subsets(vector<int>& nums) {
vector<int> temp;
vector<vector<int>> results;
subsetR(0,nums,temp,results);
results.push_back(vector<int>());
return results;
}
非递归版
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> result;
int all_set = 1<<nums.size();//一共有多少种取法
for(int i=0;i<all_set;++i){
vector<int> temp;
for(int j=0;j<nums.size();++j){
if(i&(1<<j)){
//j表示从第0个到nums.size()-1的位置,例如第
temp.push_back(nums[j]);
}
}
result.push_back(temp);
}
return result;
}
至于为什么是这样的,leetcode上有非常详细的过程。
二叉树与图
二分查找与二叉排序树
哈希表与字符串
搜索
动态规划
插叙(就是随便写写)
//序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
//输入:target = 9
//输出:[[2,3,4],[4,5]]
思路:创建一个链表,如果链表内的元素比目标小,就从后面push,如果大,则从前pop直至链表内的元素比目标小或者相等,如果相等则直接把链表元素复制出来。整个链表的循环一共循环target/2+1次。最后直到链表为空,返回结果。
vector<vector<int>> findContinuousSequence(int target) {
vector<vector<int>> output;
if(target<2){
return output;
}
std::list<int> li;
li.push_back(1);
int sum =1;
int i=2;
//终止条件
while(!li.empty()){
//往链表添加元素
for(;i<target/2+2;++i){
if(sum == target){
vector<int> temp;
for(auto n:li){
temp.push_back(n);
}
output.push_back(temp);
}
li.push_back(i);
sum+=i;
while(sum>target){
sum-=li.front();
li.pop_front();
}
}
if(sum == target){
vector<int> temp;
for(auto n:li){
temp.push_back(n);
}
output.push_back(temp);
}
sum-=li.front();
li.pop_front();
}
return output;
}