day1
1、输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
循环
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
// 空处理
if(pHead1 == NULL )
return pHead2;
if (pHead2 == NULL)
return pHead1;
ListNode* pNewHead = NULL;
// 比较两个链表头,找出头结点
if( pHead1->val < pHead2->val){
pNewHead = pHead1;
pHead1 = pHead1->next;
}
else{
pNewHead = pHeada2;
pHead2 = pHead2->next;
}
// 循环在两个链表中找出最小值,连接到pNewHead
ListNode* pTail = pNewHead;
while(pHead1 && pHead2){
if(pHead1->val < pHead2->val){
pTail->next = pHead1;
pHead1 = pHead1->next;
}
else{
pTail->next = pHead2;
pHead2 = pHead2->next;
}
pTail = pTail->next;
}
// 处理一个链表元素和并完毕,另一个链表还有元素的情况。
if(pHead1 == NULL){
pTail->next = pHead2;
}
else if(pHead2 == NULL)
pTail->next = pHead1;
return pNewHead;
}
};
递归
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
// 递归出口,其中一个链表为空或两个都为空
if(pHead1 == NULL)
return pHead2;
if (pHead2 == NULL)
return pHead1;
ListNode* pNewHead = NULL;
// 比较两个链表的第一个元素,将pNewHead 指向最小的,然后继续向后推进
if( pHead1->val < pHead2->val){
pNewHead = pHead1;
pNewHead->next = Merge(pHead1->next, pHead2);
}
else{
pNewHead = pHead2;
pNewHead->next = Merge(pHead1, pHead2->next);
}
return pNewHead;
}
};
2、求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
递归
class Solution {
public:
int Sum_Solution(int n) {
int ret = n;
ret && (ret = ret + Sum_Solution(n-1));
return ret;
}
};
构造函数
#include <iostream>
using namespace std;
class Sum
{
public:
Sum()
{
i++;
sum += i;
}
static int i;
static int sum;
};
int Sum::sum = 0;
int Sum::i = 0;
int main()
{
Sum s[100];
cout << Sum::sum << endl;
return 0;
}
奇淫技巧,VS 编译通不过,GCC 可以
class Solution {
public:
int Sum_Solution(int n) {
bool a[n][n+1];
return sizeof(a)>>1;
}
};
3、输入一个链表,反转链表后,输出链表的所有元素。
答:一个指针保存当前结点,初始值为 pHead,一个指针保存上一个结点初始值为 NULL,一个指针保存新链表的头初始值为NULL。在循环内用一个临时指针保存链表下一个结点。如果当前结点不为空则继续循环,在循环内首先保存链表的下一个结点,然后将当前结点的next 指向pRev,更新pRev,然后判断是否走的最后一个结点(next为空),如果是,则循环结束,更新newHead 的指向,否则向后推进当前指针。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
ListNode* pRev = NULL;
ListNode* pCur = pHead;
ListNode* pNewHead = NULL;
while(pCur){
ListNode* pNext = pCur->next;
pCur->next = pRev;
pRev = pCur;
if (pNext == NULL){
pNewHead = pRev;
break;
}
pCur = pNext;
}
return pNewHead;
}
};
4、输入一个链表,输出该链表中倒数第k个结点。
答:考虑 k 和 链表为空和只有一个结点的情况,1.先让快指针走 K 步,2. 快慢指针一起走,直到快指针为空,返回慢指针
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
if (pListHead == NULL || pListHead->next == NULL)
return pListHead;
ListNode* fast = pListHead;
while(k--){
if(fast == NULL)
return NULL;
fast = fast->next;
}
while(fast){
fast = fast->next;
pListHead = pListHead->next;
}
return pListHead;
}
};
5、写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
答:1. 无进位加(异或)2.取得进位(与运算,左移) 3.累加(直到进位为0)
class Solution {
public:
int Add(int num1, int num2)
{
int sum,carry;
do{
sum = num1^num2;
carry = (num1&num2)<<1;
num1 = sum;
num2 = carry;
}while(carry!=0);
return sum;
}
};
6、判断链表否带环,不带环返回空,带环返回环上节点。
答:一个指针走两步一个指针走一步,如果快指针与慢指针相遇,则返回,否则向后走。循环继续条件是fast 和 fast->next 不为空。
ListNode* HasCircle (ListNode* pHead)
{
ListNode* pFast = pHead;
ListNode* pSlow = pHead;
while(pFast && pFast->next)
{
pFast = pFast->next->next;
pSlow = pSlow->next;
if(pSlow == pFast)
return pSlow;
}
return NULL;
}
7、 求带环链表环的长度
int CircleLength(ListNode* meetNode)
{
ListNode* pCur = meetNode->next;
int length = 1;
while (meetNode != pCur)
{
length++;
pCur = pCur->next;
}
return length;
}
8、带环链表的入口点
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* GetMeetNode(ListNode* pHead){
ListNode* pFast = pHead;
ListNode* pSlow = pHead;
while( pFast && pFast->next){
pFast = pFast->next->next;
pSlow = pSlow->next;
if(pSlow == pFast)
return pSlow;
}
return NULL;
}
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
ListNode* pMeetNode = GetMeetNode(pHead);
if(pMeetNode == NULL)
return NULL;
ListNode* pBegin = pHead;
while(pMeetNode != pBegin){
pMeetNode = pMeetNode->next;
pBegin = pBegin->next;
}
return pMeetNode;
}
};
9、不能被继承的类
// 虚拟继承,虚基类会由派生类构造
// 友元不能被继承
// 模板参数设置为友元
template<class T>
class A
{
friend T;
private:
A(){}
~A(){}
};
class B : virtual A<B>
{
public:
B(){}
~B(){}
};
10、只能在堆上创建
// OnlyHeap
// 将构造函数设置为私有,显示提供一个接口 delete this
class OnlyHeap
{
public:
OnlyHeap()
{}
void Destory()
{
delete this;
}
private:
~OnlyHeap()
{}
};
int main()
{
OnlyHeap* p = new OnlyHeap();
return 0;
}
11、 只能在栈创建
重载 new 和 delete 并设置为私有
class OnlyStack
{
public:
OnlyStack()
{}
~OnlyStack()
{}
private:
void* operator new(size_t);
void operator delete(void*);
}
12、判断两个单链表链表是否相交,若相交,求交点。【基础版:不考虑带环】
答:1. 先判断连个链表是否相交:都走到最后一个结点,然后比较是否相等,如果相交,则最后一个结点肯定相同。PS:不要想到 X 形交叉, 因为这是单链表!只有一个next指针。
2.如果在 1. 中判断相交的情况,然后求出两个链表的长度,让较长的链表向前走 两个链表长度差 的步数,然后同时出发,相遇时就是相交点。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
// 判断链表是否相交
bool isCross(ListNode* pHead1, ListNode* pHead2){
if(pHead1 == NULL || pHead2 == NULL)
return false;
while(pHead1->next)
pHead1 = pHead1->next;
while(pHead2->next)
pHead2 = pHead2->next;
if(pHead1 == pHead2 )
return true;
return false;
}
// 获取链表长度
int getSize(ListNode* pHead){
int i = 0;
while(pHead){
i++;
pHead = pHead->next;
}
return i;
}
// 找出公共结点
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
if(pHead1 == NULL || pHead2 == NULL)
return NULL;
// 特殊处理一下,两个指针指向同一个链表,且链表只有一个结点的情况
if(pHead1 == pHead2 )
return pHead1;
if(!isCross(pHead1, pHead2)){
return NULL;
}
int step = 0;
int size1 = getSize(pHead1);
int size2 = getSize(pHead2);
step = size1-size2;
if(step > 0){
while(step--)
pHead1 = pHead1->next;
}
else if (step < 0){
while(step++)
pHead2 = pHead2->next;
}
while(pHead1 != pHead2){
pHead1 = pHead1->next;
pHead2 = pHead2->next;
}
return pHead1;
}
};
13、判断两个单链表链表是否相交,若相交,求交点。【升级版:考虑带环】
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
// 找出链表的第一个公共结点
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
// 第一个公共结点
ListNode* commonNode = NULL;
if(pHead1 == NULL || pHead2 == NULL )
return NULL;
// (1) 判断是否都有环
ListNode* circleNode1; // 链表1 环上的结点(快慢指针相遇点),如果无环则为空
ListNode* circleNode2; // 链表2 环上的结点(快慢指针相遇点),如果无环则为空
circleNode1 = GetMeetNode(pHead1);
circleNode2 = GetMeetNode(pHead2);
// (2) 如果都有环 or 如果都无环 or 只有一个有环
// 1. 都有环
if (circleNode1 && circleNode2){
// 链表1 环的入口点
ListNode* circleEnterNode1 = GetCircleEnterNode(pHead1, circleNode1);
// 链表2 环的入口点
ListNode* circleEnterNode2 = GetCircleEnterNode(pHead2, circleNode2);
// 如果入口点相同,临时修改链表为 Y 形状,处理完毕后恢复
if(circleEnterNode1 == circleEnterNode2){
// 保存
ListNode* enterNodeNext = circleEnterNode1->next;
// 设置为 Y 形,即无环
circleEnterNode1->next = NULL;
// 调用处理无环情况的函数
commonNode = GetFirstCommonNodeNoCircle(pHead1, pHead2);
// 恢复
circleEnterNode1->next = enterNodeNext;
}// 如果入口点不同,将一个环遍历一周看是否能遇到另外一个环的入口点
else{
// 获取其中一个环的长度
int circleLength = GetCircleLength(circleNode2);
// 遍历一周看是否能遇到另外一个环的入口点,如果遇到则找到,否则未找到返回环
ListNode* next = circleEnterNode2->next;
// 遍历一圈
while(circleLength--){
// 找到公共结点
if(next == circleEnterNode1){
commonNode = circleEnterNode1;
break;
}
next = next->next;
}
// 未找到公共结点
if(circleLength <= 0)
return NULL;
}
} // 2. 都无环
else if (circleNode1 == NULL && circleNode2 == NULL){
commonNode = GetFirstCommonNodeNoCircle(pHead1, pHead2);
}
// (3).其中一个无环, 肯定无公共结点
return commonNode;
}
private:
// 获取带环链表环上的一个结点(快慢指针相遇点),如果不带环则返回空
ListNode* GetMeetNode(ListNode* pHead){
if(pHead == NULL)
return NULL;
ListNode* pFast = pHead;
ListNode* pSlow = pHead;
// 快慢指针法
while(pFast && pFast->next){
pFast = pFast->next->next;
pSlow = pSlow->next;
if(pFast == pSlow){
return pFast;
}
}
return NULL;
}
// 根据快慢指针相遇点,过去环的入口点
ListNode* GetCircleEnterNode(ListNode* pHead, ListNode* meetNode){
if(pHead == NULL || meetNode == NULL)
return NULL;
while(pHead != meetNode){
pHead = pHead->next;
meetNode = meetNode->next;
}
return pHead;
}
// 判断两个链表是否相交
bool isCross(ListNode* pHead1, ListNode* pHead2){
if(pHead1 == NULL || pHead2 == NULL){
return false;
}
while(pHead1->next)
pHead1 = pHead1->next;
while(pHead2->next)
pHead2 = pHead2->next;
if(pHead1 == pHead2)
return true;
return false;
}
// 获取链表长度
int getListLength(ListNode* pHead){
int length = 0;
while(pHead){
length++;
pHead = pHead->next;
}
return length;
}
// 处理 Y 形状
ListNode* GetFirstCommonNodeNoCircle(ListNode* pHead1, ListNode* pHead2){
if(pHead1 == NULL || pHead2 == NULL)
return NULL;
// 如果不相交,直接返回NULL
if (!isCross(pHead1, pHead2))
return NULL;
int step = 0;
int lengthList1 = getListLength(pHead1);
int lengthList2 = getListLength(pHead2);
step = lengthList1 - lengthList2;
if(step > 0){
while(step--)
pHead1 = pHead1->next;
}
else if(step<0){
while(step++)
pHead2 = pHead2->next;
}
while(pHead1 != pHead2){
pHead1 = pHead1->next;
pHead2 = pHead2->next;
}
return pHead1;
}
// 获取带环链表环的长度
int GetCircleLength(ListNode* meetNode){
int length = 0;
if(meetNode == NULL)
return length;
ListNode* next = meetNode->next;
while(next != meetNode){
length++;
next = next->next;
}
return length;
}
};
14、从尾到头打印单链表
void print_list_reverse(ListNode* pNode)
{
if (pNode == NULL)
return;
print_list_reverse(pNode->next);
cout << pNode->data << " ";
}
15、 删除一个无头单链表的非尾结点
void delete_not_tail(ListNode* pNode)
{
assert(pNode);
assert(pNode->next);
pNode->data = pNode->next->data;
ListNode* temp = pNode->next;
pNode->next = temp->next;
delete temp;
}
16、输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
/*
struct RandomListNode {
int label;
struct RandomListNode *next, *random;
RandomListNode(int x) :
label(x), next(NULL), random(NULL) {
}
};
*/
class Solution {
public:
RandomListNode* Clone(RandomListNode* pHead)
{
if( NULL == pHead){
return NULL;
}
// 保存新链表的头
RandomListNode* new_head = new RandomListNode(pHead->label);
RandomListNode* cur_node_new_list = new_head;// 指向新链表的当前结点,用来遍历
RandomListNode* cur_node_old_list = pHead; // 指向旧链表的当前结点,用来遍历
// 构建一个 map ,保存两个链表相同位置上的结点指针
map<RandomListNode*, RandomListNode*> map_random;
map_random.insert(make_pair( cur_node_old_list, cur_node_new_list));// 插入第一个结点
while(cur_node_old_list->next != NULL){
// 创建一个新的结点
RandomListNode* temp = new RandomListNode(cur_node_old_list->next->label);
// 连接到新链表,并向后移动 cur 指针
cur_node_new_list->next = temp;
cur_node_new_list = temp;
// 当前结点数据复制后,向后推进原链表的 cur
cur_node_old_list = cur_node_old_list->next;
// 将新节点与对应原结点指针插入 map
map_random.insert(make_pair(cur_node_old_list, cur_node_new_list));
}
// 遍历原链表,复制随机指针指向
cur_node_old_list = pHead;
cur_node_new_list = new_head;
while(cur_node_old_list){
// 在map中取原结点的随机指针指向
cur_node_new_list->random = map_random[cur_node_old_list->random];
cur_node_new_list = cur_node_new_list->next;
cur_node_old_list = cur_node_old_list->next;
}
return new_head;
}
};
17、两个栈实现一个队列
class Solution
{
public:
void push(int node) {
stack1.push(node);
}
int pop() {
if(stack2.empty()){
while(!stack1.empty()){
stack2.push(stack1.top());
stack1.pop();
}
}
int ret = stack2.top();
stack2.pop();
return ret;
}
private:
stack<int> stack1;
stack<int> stack2;
};
18、两个队列实现一个栈
#include<queue>
class Stack
{
public:
void push(int data)
{
if (queue1.empty())
queue2.push(data);
else if (queue2.empty())
queue1.push(data);
}
int pop()
{
int ret;
if (queue1.empty())
{
while (queue2.size() > 1)
{
queue1.push(queue2.front());
queue2.pop();
}
ret = queue2.front();
queue2.pop();
}
else if (queue2.empty())
{
while (queue1.size() > 1)
{
queue2.push(queue1.front());
queue1.pop();
}
ret = queue1.front();
queue1.pop();
}
return ret;
}
private:
queue<int> queue1;
queue<int> queue2;
};
void test_queue()
{
Stack q;
q.push(1);
q.push(2);
q.pop();
q.push(3);
q.pop();
q.push(4);
q.push(5);
q.pop();
q.pop();
q.pop();
}
19、替换字符串中的空格为$$$。要求时间复杂度为O(N)
例如:将”talk is cheap show me the code”替换
为”talk$$$is$$$cheap$$$show$$$me$$$the$$$code”。
void space_to_symbol()
{
char str[100] = "talk is cheap show me the code";
cout << str << endl;
char* start = str;
int space_count = 0;
int old_length = 0;
while (*start)
{
if (*start == ' ')
space_count++;
old_length++;
start++;
}
int new_length = old_length + space_count * 2;
while (old_length >= 0)
{
if (str[old_length] == ' ')
{
str[new_length--] = '$';
str[new_length--] = '$';
str[new_length--] = '$';
}
else
{
str[new_length--] = str[old_length];
}
old_length--;
}
cout << str << endl;
}
20、实现一个栈,要求push pop,min的时间复杂度为O(1)
答:入栈时data 栈每次都需要压入,min 栈,如果为空则压入,如果不为空,用min栈顶元素和压入的元素比较,如果压入的元素小于min栈顶的值,则压入min栈,并且压入data栈。
出栈,如果两个栈数据相同,则同时pop,否则只有data出栈
class Solution {
public:
void push(int value) {
if(s_min.empty()){
s_min.push(value);
}
else{
if(s_min.top() > value)
s_min.push(value);
}
data.push(value);
}
void pop() {
if(data.top() == s_min.top()){
data.pop();
s_min.pop();
}
else{
data.pop();
}
}
int top() {
return data.top();
}
int min() {
return s_min.top();
}
private:
stack<int> s_min;
stack<int> data;
};
21、查找一个字符串中第一个只出现两次的字符。
比如 “abcdefabcdefabc” 中第一个只出现两次为‘d’,
要求空间复杂度O(1),时间复杂度O(1)
char find_char_two(const char* str)
{
assert(str);
const char* start = str;
char count[256]; // 因为字符只有256 不管你字符串多长,都是O(1)的空间复杂度
memset(count, 0, sizeof(count));
// 统计次数
while (*start)
{
count[*start]++;
start++;
}
start = str;
while (*start)
{
if (count[*start] == 2)
{
return *start;
}
start++;
}
return -1;
}
int main()
{
//space_to_symbol();
char c = find_char_two("abcdefabcdefabc");
cout << c << endl;
return 0;
}
22、输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
class Solution {
public:
bool IsPopOrder(vector<int> v_push,vector<int> v_pop) {
if(v_push.size() == 0 || v_pop.size() == 0 || v_push.size() != v_pop.size())
return false;
int idx_push = 0;
int idx_pop = 0;
int size = v_push.size();
stack<int> s;
while(idx_push < size)
{
s.push(v_push[idx_push++]);
while(!s.empty() && s.top() == v_pop[idx_pop] && idx_pop < size)
{
s.pop();
idx_pop++;
}
}
return s.empty();
}
};
23、输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
class Solution {
public:
int NumberOf1(int n) {
int count = 0;
while(n){
count++;
n = n&(n-1);
}
return count;
}
};
24、 二叉树的层序遍历
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
vector<int> PrintFromTopToBottom(TreeNode* root) {
vector<int> v;
if ( NULL == root)
return v;
queue<TreeNode*> q;
q.push(root);
while(!q.empty()){
TreeNode* temp = q.front();
v.push_back(temp->val);
q.pop();
if(temp->left)
q.push(temp->left);
if(temp->right)
q.push(temp->right);
}
return v;
}
};
25、给定一个整数N, 那么N的阶乘N!,末尾有多少个0呢?例如:N=10,N! = 3628800,末尾有两个0。
int fac_zero_num(int num)
{
int count = 0;
int i = 1;
for (; i <= num; i++)
{
int j = i;
while (j % 5 == 0){
count++;
j /= 5;
}
}
return count;
}
int fac_zero_num_2(int num)
{
int ret = 0;
while (num)
{
ret += num / 5;
num /= 5;
}
return ret;
}
26、求二叉树叶子结点的个数
int leaf_size(node* pnode)
{
if (NULL == pnode)
return 0;
if (NULL == pnode->left && NULL == pnode->right)
return 1;
return leaf_size(pnode->left) + leaf_size(pnode->right);
}
27、求二叉树第K层结点个数,设定根节点为第一层
int num_K_node(node*pnode, int k)
{
if (pnode == NULL || k <= 0)
return 0;
if (pnode && k == 1)
return 1;
return num_K_node(pnode->left, k-1) + num_K_node(pnode->right, k-1);
}
28、一个数组中有一个数字的次数超过了数组的一般,求出这个数字。如:int a[] = {2,3,2,2,2,2,2,4,1,2,3};
class Solution {
public:
bool check(const vector<int>& v, int num){
int count = 0;
for(int i = 0; i < v.size(); ++i){
if(num == v[i])
count++;
}
if( count*2<=v.size())
return false;
return true;
}
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.size() == 0){
cout << 0;
return 0;
}
int count = 1;
int length = numbers.size();
int ret = numbers[0];
int i = 1;
for(; i < length; ++i){
if(count == 0){
ret = numbers[i];
count = 1;
}
else if (ret == numbers[i])
count++;
else
count--;
}
if(check(numbers, ret)){
cout << ret;
return ret;
}
cout << 0;
return 0;
}
};
day11
29、求二叉树的高度
int TreeHeight(Node* pnode)
{
if( NULL == pnode)
return 0;
int left= 1 + height(pnode->left);
int right = 1 + height(pnode->right);
return left > right ? left : right;
}
30、销毁一颗二叉树
void Destroy(Node*& root)
{
if(root)
{
Destroy(root->left);
Destroy(root->right);
delete root;
root = NULL;
}
}
31、链表翻转。给出一个链表和一个数K,比如链表1->2->3->4->5->6->NULL,K=2,翻转后 2->1->4->3->6->5->NULL,若K=3,翻转后3->2->1->6->5->4->NULL,若K=4,翻转后4->3->2->1->5->6->NULL,用程序实现
ListNode* RotateList(ListNode* list, size_t k)
。
递归实现
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *reverseKGroup(ListNode *head, int k) {
// 为空或一个结点或 K 不符合 规范直接返回原head
if( NULL == head || NULL == head->next || k < 2)
return head;
// 以k为步数,找到逆置前下一组的头指针
ListNode* next_group_head = head;
for(int i = 0; i < k; ++i){
if(next_group_head)
next_group_head = next_group_head->next;
else // 剩余结点数小于K 不够分一组时,下一组的头指针就是head
return head;
}
// 递归去处理剩余组,直到不够 K 个结点,即求出下一组逆置后的头指针
ListNode* new_next_group_head = reverseKGroup(next_group_head, k);
ListNode* prev = NULL ,* cur = head;
// 开始逆置处理当前组,从 head --- next_group_head下一组逆置前的第一个结点
while(cur != next_group_head){
ListNode* next = cur->next; // 保存下一个结点
if(prev == NULL) // 为空则将当前组的一个结点指向下一组逆置后的第一个结点
cur->next = new_next_group_head;
else // 不为空则表示逆置当前组的结点,指向prev即可
cur->next = prev;
prev = cur; // 向后推进,直到走完当前组
cur = next;
}
// 返回逆置后的头指针
return prev;
}
};
迭代解法
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *reverseKGroup(ListNode *head, int k)
{
// 特殊情况处理
if (NULL == head || NULL == head->next || k < 2)
return head;
ListNode* prev = NULL, *pcur = head, *newhead = NULL;
// 遍历链表,处理每一组
while (pcur){
// 找到下一组的头
ListNode* next_group_head = pcur;
int i = 0;
for (; next_group_head && i < k; i++){
next_group_head = next_group_head->next;
}
// 处理不足K 个元素的组
if (i < k ){
// 防止K 大于链表结点
if (newhead == NULL)
newhead = head;
break;
}
// 将当前分组[pcur, next_group_head) 逆置,并返回逆置后的头指针
ListNode* tmp = reverse(pcur, next_group_head);
if (prev == NULL) // 第一个分组需要更新新的头指针
newhead = tmp;
else // 后续只需要将上一个分组的最后一个结点next 指向当前分组逆序后的的头指针
prev->next = tmp;
// 当前分组的最后一个结点next 指向下一个分组开始
pcur->next = next_group_head;
// prev 保存当前分组的最后一个结点
prev = pcur;
// pcur 向后推进到下一个分组的开始
pcur = next_group_head;
}
return newhead;
}
// [start, end)
ListNode* reverse(ListNode* start, ListNode* end)
{
ListNode* prev = NULL;
ListNode* pcur = start;
while (pcur != end){
ListNode* next = pcur->next;
pcur->next = prev;
prev = pcur;
pcur = next;
}
return prev;
}
};
day12
32、输入一棵二叉树,判断该二叉树是否是平衡二叉树。
(1)、方法1
前序遍历的思想,每到一个结点求出其左右子树的深度,判断差是否大于-1或小于1。
然后递归判断左右子树。效率较低,因为求高度时有很多结点被重复遍历。
class Solution {
public:
bool IsBalanced_Solution(TreeNode* pRoot) {
if(pRoot == NULL)
return true;
int left_depth = Depth(pRoot->left);
int right_depth = Depth(pRoot->right);
int diff = left_depth - right_depth;
if( diff > 1 || diff < -1)
return false;
return IsBalanced_Solution(pRoot->left)&&IsBalanced_Solution(pRoot->right);
}
int Depth(TreeNode* node){
if( NULL == node)
return 0;
int left = Depth(node->left);
int right = Depth(node->right);
return (left > right ? left + 1 : right + 1);
}
};
(2)、方法2
采用后序遍历的方式,通过一个引用参数从叶子结点往上求树的高度,较少遍历结点次数。如果差符合条件时,更新参数中传来的height,并返回true。
class Solution {
public:
bool IsBalanced_Solution(TreeNode* pRoot) {
int index = 0;
return IsBalanced(pRoot, index);
}
bool IsBalanced(TreeNode* node, int& height){
if( node == NULL){
height = 0;
return true;
}
int left = 0;
int right = 0;
if( IsBalanced(node->left, left) && IsBalanced(node->right, right)){
int diff = left - right;
if(diff >= -1 && diff <= 1)
{
height = 1 + (left > right ? left : right);
return true;
}
}
return false;
}
};
33、求一颗二叉树的镜像
class Solution {
public:
void Mirror(TreeNode *pRoot) {
if(pRoot == NULL)
return;
std::swap(pRoot->left, pRoot->right);
Mirror(pRoot->left);
Mirror(pRoot->right);
}
};
34、在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
从右上角开始
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
if( array.size() == 0)
return false;
int rows = array.size();
int cols = array[0].size();
int idx_row = 0;
int idx_col = cols-1;
while( idx_row <rows && idx_col >= 0){
if(array[idx_row][idx_col] == target){
return true;
}
else if(array[idx_row][idx_col] < target )
idx_row++;
else
idx_col--;
}
return false;
}
};
从左下角开始
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
if(array.size() == 0)
return false;
int row_size = array.size();
int col_size = array[0].size();
int idx_row = row_size - 1;
int idx_col = 0;
while(idx_col < col_size && idx_row >= 0)
{
if(target == array[idx_row][idx_col])
return true;
else if( target > array[idx_row][idx_col])
++idx_col;
else
--idx_row;
}
return false;
}
};
day13
35、二叉树的前序、中序和后序非递归遍历
前序
借助栈来实现。
void PreOrder( node* pnode)
{
if (pnode == NULL)
return;
stack<node*> s;
s.push(pnode);
while (!s.empty())
{
const node* tmp = s.top();
cout << tmp->data << " ";
s.pop();
if (tmp->right)
s.push(tmp->right);
if (tmp->left)
s.push(tmp->left);
}
}
中序
void inOrder(node* root)
{
if (root == NULL)
return;
stack<node*> s;
node* pcur = root;
while (!s.empty() || pcur)
{
while (pcur)
{
s.push(pcur);
pcur = pcur->left;
}
node* tmp = s.top();
cout << tmp->data << " ";
s.pop();
if (tmp->right)
{
pcur = tmp->right;
}
}
}
后序
void postOrder(node* root)
{
if (root == NULL)
return;
stack<node*> s;
node* pcur = root;
node* prev = NULL;
while (pcur || !s.empty())
{
while (pcur)
{
s.push(pcur);
pcur = pcur->left;
}
node* tmp = s.top();
if (NULL == tmp->right || prev == tmp->r
{
cout << tmp->data << " ";
s.pop();
prev = tmp;
}
else
{
pcur = tmp->right;
}
}
}
36、已知集合A和B的元素分别用不含头结点的单链表存储,函数difference(
)用于求解集合A与B的差集,并将结果保存在集合A的单链表中。例如,若集合A={5,10,20,15,25,30},集合B={5,15,35,25},完成计算后A={10,20,30}。
void difference(node* & l1, node* l2)
{
node* pa = l1;
node* pb = l2;
node* prev = NULL;
while(pa)
{
node* pfind = pb;
while(pfind && pa->data != pfind->data)
pfind = pfind->next;
if(pfind)
{
if(prev == NULL)
{
l1 = pa->next;
}
else
{
prev->next = pa->next;
}
node* tmp = pa;
pa = pa->next;
delete tmp;
}
else
{
prev = pa;
pa = pa->next;
}
}
}
day14
37、判断一个节点是否在一棵二叉树中/在二叉树中查找某个值
// 查找某个值
node* Find(node* root, const T& key)
{
if (root == NULL)
return NULL;
if (root->data == key)
return root;
TreeNode<T>* ret = Find(root->left, key);
if (ret)
return ret;
return Find(root->right, key);
}
// 判断某个结点是否存在
bool InTree(TreeNode<T>* root, TreeNode<T>* pnode)
{
if (root == NULL || pnode == NULL)
return false;
if (root == pnode)
return true;
bool ret = InTree(root->left,pnode);
if (ret)
return ret;
return InTree(root->right, pnode);
}
38、输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
bool is_subtree(TreeNode* pRoot1, TreeNode* pRoot2){
if( NULL == pRoot2 )
return true;
if (NULL == pRoot1 || pRoot1->val != pRoot2->val)
return false;
return is_subtree(pRoot1->left, pRoot2->left) && is_subtree(pRoot1->right, pRoot2->right);
}
public:
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
if( NULL == pRoot1 || NULL == pRoot2)
return false;
bool ret = false;
if(pRoot1->val == pRoot2->val )
ret = is_subtree(pRoot1, pRoot2);
if(!ret)
ret = HasSubtree(pRoot1->left, pRoot2);
if(!ret)
ret = HasSubtree(pRoot1->right, pRoot2);
return ret;
}
};
day15
39、判断一棵树是否是完全二叉树
bool judge_full_binary_tree(TreeNode<char>* proot)
{
if (NULL == proot)
return true;
bool must_has_child = false;
queue<TreeNode<char>*> q;
q.push(proot);
while (!q.empty())
{
TreeNode<char>* tmp = q.front();
q.pop();
if (must_has_child)
{
if (tmp->left || tmp->right)
return false;
}
else
{
if (tmp->left && tmp->right)
{
q.push(tmp->left);
q.push(tmp->right);
}
else if (tmp->left && tmp->right == NULL)
{
must_has_child = true;
q.push(tmp->left);
}
else if (tmp->left == NULL && tmp->right)
return false;
}
}
return true;
}
40、求二叉树中两个节点的最近公共祖先。
要求考虑以下三种种情况,给出解决方案,并解决:
1:二叉树每个节点有parent(三叉链)
2:二叉树是搜索二叉树。
3:就是普通二叉树。(尽可能实现时间复杂度为O(N))
(1)、二叉树每个节点有parent(三叉链)
// 获取链表长度
int GetLength(TreeNode* pnode)
{
int length = 0;
while (pnode)
{
length++;
pnode = pnode->parent;
}
return length;
}
// 两个节点的最近公共祖先。
TreeNode* common_ancestor(TreeNode* node1, TreeNode* node2)
{
if (NULL == node1 || NULL == node2)
return NULL;
int list1_length = GetLength(node1);
int list2_length = GetLength(node2);
int diff = list1_length - list2_length;
if (diff > 0)
{
while (diff--)
node1 = node1->parent;
}
else if (diff < 0)
{
while (diff++)
node2 = node2->parent;
}
while (node1 != node2)
{
node1 = node1->parent;
node2 = node2->parent;
}
return node1;
}
(2)、二叉树是搜索二叉树
循环
BSTreeNode* common_ancestor(BSTreeNode* root,BSTreeNode* node1, BSTreeNode* node2)
{
if (node1 == NULL || node2 == NULL || root == NULL)
return NULL;
BSTreeNode* pcur = root;
while (pcur)
{
if (pcur->data < node1->data &&
pcur->data < node2->data)
pcur = pcur->right;
else if (pcur->data > node1->data &&
pcur->data > node2->data)
pcur = pcur->left;
else
return pcur;
}
return NULL;
}
3:就是普通二叉树。(尽可能实现时间复杂度为O(N))
普通方法时间复杂度高一些
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
bool GetPath(vector<TreeNode*>& v, TreeNode* root, TreeNode* p)
{
if(root == p)
{
v.push_back(root);
return true;
}
if(NULL == root)
return false;
v.push_back(root);
if(GetPath(v,root->left,p))
return true;
if(GetPath(v,root->right,p))
return true;
v.pop_back();
return false;
}
TreeNode* GetCommNode(vector<TreeNode*>& v1, vector<TreeNode*>& v2)
{
TreeNode* ret = NULL;
int i = 0;
int j = 0;
while(i < v1.size() && j < v2.size())
{
if(v1[i] == v2[j])
ret = v1[i];
i++;
j++;
}
return ret;
}
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if( NULL == root || p == NULL || q == NULL)
return NULL;
vector<TreeNode*> path1;
GetPath(path1,root, p);
vector<TreeNode*> path2;
GetPath(path2,root, q);
return GetCommNode(path1,path2);
}
};
递归方法O(N)
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if( NULL == root)
return NULL;
if( root == p || root == q )
return root;
TreeNode* left = lowestCommonAncestor(root->left, p,q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if(left && right)
return root;
else
return left ? left : right;
}
};
day16
41、由前序遍历和中序遍历重建二叉树(前序:1 2 3 4 5 6 中序 3 2 4 1 6 5)
class Solution
{
private:
TreeNode* ConTree(const vector<int>& pre, int startPre, int endPre,
const vector<int>& vin, int startIn, int endIn )
{
if(startPre > endPre || startIn > endIn)
return NULL;
TreeNode* root = new TreeNode(pre[startPre]);
for(int i = startIn; i <= endIn; ++i)
{
if (vin[i] == root->val){
root->left = ConTree(pre, startPre + 1, startPre+i-startIn,
vin, startIn, i - 1);
root->right = ConTree(pre, startPre+i-startIn+1, endPre,
vin, i+1, endIn);
}
}
return root;
}
public:
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin)
{
if( pre.size() != vin.size() || pre.empty() || vin.empty())
return NULL;
TreeNode* root = ConTree(pre, 0, pre.size() - 1, vin, 0, vin.size()-1);
return root;
}
};
42、C语言模拟实现C++继承和多态
提示:C 实现一个 struct A 和 stuct B 包含一个 int 成员 a 和 b,要求达到B 继承 A 的效果,也就是 B里面包含一个 A,并且能达到多态的效果,也就是一个 A* p 指向一个指向A 调的是 A 的函数,指向 B 调的是 B 的函数。
// 父类虚表
typedef struct vtblA
{
void(*pfun1)();
}vtblA;
// 子类虚表
typedef struct vtblB
{
vtblA vtbl;
// 指向子类特有的成员函数
void(*fun2_B)(int);
}vtblB;
// 父类虚函数
void fun1()
{
printf("父类fun1\n");
}
// 子类重写父类虚函数fun1
void fun1_B()
{
printf("子类重写fun1\n");
}
// 子类特有函数
void fun2_B(int)
{
printf("子类特有fun2\n");
}
// 父类
typedef struct A
{
vtblA* pvtl;// 父类虚表指针
int a; // 父类数据成员
}A;
// 子类
typedef struct B
{
A base_a; // 从父类继承而来的基类A
int b; // 子类数据成员
}B;
// 父类虚表结构
vtblA g_vtbl_A = {&fun1};
// 子类虚表结构
vtblB g_btbl_B = { {&fun1_B}, &fun2_B };
// 父类构造函数
void init_A(A* pa)
{
pa->a = 10;
pa->pvtl = &g_vtbl_A;
}
// 子类构造函数
void init_B(B* pb)
{
init_A((A*)pb);
pb->base_a.pvtl = (vtblA*)&g_btbl_B;
pb->b = 20;
}
// 测试多态函数
void dosomething(A* p)
{
// 如果p指向子类对象那么输出结果就是重写后的函数pfun1
vtblA* pvtbl = (vtblA*)p->pvtl;
(*pvtbl).pfun1();
}
int main()
{
// 定义子类对象并构造
B b;
init_B(&b);
// 调用父类自己的函数
vtblB* pvtvl = (vtblB*)b.base_a.pvtl;
(*pvtvl).fun2_B(5);
// 定义父类指针
A* pa = (A*)&b;
// 测试多态
dosomething(pa);
return 0;
}
day17
43、输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
class Solution {
public:
TreeNode* Convert(TreeNode* pRootOfTree)
{
TreeNode* list_head = NULL;
Con(pRootOfTree,list_head);
return list_head;
}
void Con(TreeNode* root, TreeNode*& prev){
if(root == NULL)
return ;
Con(root->right, prev);
root->right = prev;
if(prev)
prev->left = root;
prev = root;
Con(root->left,prev);
}
};
实现单例模式,线程安全且高效
懒汉 模式
特点:第一个获取时创建实例,类似写时拷贝,节省资源,缺点是需要加锁,性能上可能会有影响。
// 单例模式之懒汉
std::mutex mtx;
class Singleton
{
public:
static Singleton* GetInstance()
{
if (NULL == instance)
{
// 守卫锁 (RAII) 防止异常造成死锁
// 以及保证线程安全
lock_guard<mutex> clk(mtx);
if (NULL == instance)
{
Singleton* tmp = new Singleton();
MemoryBarrier();// 内存栅栏,防止指令优化导致顺序改变
instance = tmp;
}
}
return instance;
}
static void DelInstance()
{
if (instance)
{
delete instance;
instance = NULL;
}
}
void Print()
{
cout << a << endl;
}
// 删除对象可以用GC类然后定义全局对象 或者 etexit
class GC
{
public:
~GC()
{
cout << "DelInstance" << endl;
DelInstance();
}
};
private:
// 防止拷贝
Singleton()
:a(1)
{}
Singleton(Singleton&);
Singleton& operator=(const Singleton&);
private:
static Singleton* instance;
int a;
};
Singleton* Singleton::instance = NULL;
饿汉模式
特点:简洁高效,无需加锁,但是在静态加载时会有缺陷。
// 饿汉模式
// 局部静态或者全局静态
class Singleton
{
public:
static Singleton* GetInstance()
{
static Singleton instance;
return &instance;
}
void Print()
{
cout << a << endl;
}
// 删除对象可以用GC类 或者 etexit
private:
// 防止拷贝
Singleton()
:a(1)
{}
Singleton(Singleton&);
Singleton& operator=(const Singleton&);
private:
int a;
};
day18
44、实现插入排序和希尔排序
插入排序
void InsertSort(int* a, int size)
{
if (NULL == a || size <= 0)
return;
for (int i = 1; i < size; ++i)
{
int temp = a[i];
int end = i - 1;
while (end >= 0 && a[end] > temp)
{
a[end + 1] = a[end];
end--;
}
a[end + 1] = temp;
}
}
希尔排序
void ShellSort(int* a, int size)
{
for (int gap = size / 2; gap > 0; gap /= 2)
{
for (int i = gap; i < size; i += gap)
{
int tmp = a[i];
int end = i - gap;
while (end >= 0 && a[end] > tmp)
{
a[end + gap] = a[end];
end -= gap;
}
a[end + gap] = tmp;
}
}
}
45、 使用shell 脚本实现 希尔排序
#!/bin/bash
# 希尔排序
function shell_sort()
{
# 获取数组长度
size=${#a[@]}
for ((gap=size/2; gap > 0; gap/=2))
do
for ((idx = gap; idx < size; idx+=gap))
do
let tmp=a[idx]
let end=idx-gap
let save=a[end]
while [ $end -ge 0 -a $save -ge $tmp ]
do
let a[end+gap]=a[end]
let end=end-gap
if [ $end -ge 0 ];then
let save=a[end]
fi
done
let a[end+gap]=tmp
done
done
}
# 获取数组个数
echo 'please input num'
read num
i=0
while [ $num -gt 0 ]
do
read a[i]
let i++
let num-=1
done
shell_sort a
echo "排序 完成 开始打印"
echo ${a[@]}
day19
46、实现选择排序和堆排序
选择排序
static void selectSort(int* a, int size)
{
if (a == NULL || size <= 0)
return;
// i < size - 1 最后一个元素已经是有序
for (int i = 0; i < size - 1; ++i)
{
int min_flag = i;
for (int j = i+1; j < size; ++j)
{
if (a[min_flag] > a[j])
min_flag = j;
}
if (min_flag != i)
std::swap(a[i], a[min_flag]);
}
}
堆排序
void heapAdjustDown(int *a, int parent, int size)
{
for (int child = parent * 2 + 1; child < size; child = parent * 2 + 1)
{
if (child + 1 < size && a[child + 1] > a[child])
child++;
if (a[parent] < a[child])
{
std::swap(a[parent], a[child]);
parent = child;
}
else
break;
}
}
void heapSort(int* a, int size)
{
if (a == NULL || size <= 0)
return;
// 1. 建堆
for (int lastNotLeaf = (size - 2) / 2; lastNotLeaf >= 0; --lastNotLeaf)
heapAdjustDown(a, lastNotLeaf, size);
// 2.排序
int count = size - 1;
while (count > 0)
{
std::swap(a[0], a[count]);
heapAdjustDown(a, 0, count);
count--;
}
}
47、本公司现在要给公司员工发波福利,在员工工作时间会提供大量的水果供员工补充营养。由于水果种类比较多,但是却又不知道哪种水果比较受欢迎,然后公司就让每个员工报告了自己最爱吃的k种水果,并且告知已经将所有员工喜欢吃的水果存储于一个数组中。然后让我们统计出所有水果出现的次数,并且求出大家最喜欢吃的前k种水果。
void GetFavoriteFruit(const vector& fruits,size_t k);
ps:要求打印出最喜欢的水果,并且效率尽可能的高。
提示:尽量STL的容器和算法,这样能更快速高效的实现。
void GetFavoriteFruit(const vector<string>& fruits, size_t k)
{
if (fruits.size() == 0 || k == 0)
return;
// 统计次数
map<string, int> fruits_count;
for (int i = 0; i < fruits.size(); ++i)
{
fruits_count[fruits[i]]++;
}
// 比较函数
struct Compare
{
bool operator()(const map<string, int>::iterator& left, const map<string, int>::iterator& right)
{
return left->second > right->second;
}
};
set< map<string, int>::iterator, Compare> myset;
map<string, int>::iterator it=fruits_count.begin();
while (it != fruits_count.end())
{
myset.insert(it);
it++;
}
set< map<string, int>::iterator>::iterator itset = myset.begin();
while (k-- > 0)
{
if (itset != myset.end())
{
cout << (*itset)->first << " 次数 : " << (*itset)->second << endl;
itset++;
}
else
{
cout << "K 有误" << endl;
return;
}
}
}
day20
48、冒泡排序和排序排序的三种实现方式
冒泡排序普通
void bubleSort(int*a, int size)
{
if (a == NULL || size <= 0)
return;
for (int i = 0; i < size - 1; ++i)
for (int j = 0; j < size - 1 - i; ++j)
if (a[j] > a[j + 1])
std::swap(a[j], a[j + 1]);
}
冒泡排序优化
void bubleSort_2(int*a, int size)
{
if (a == NULL || size <= 0)
return;
bool flag = true;
for (int i = 0; i < size - 1; ++i)
{
for (int j = 0; j < size - 1 - i; ++j)
{
if (a[j] > a[j + 1])
{
std::swap(a[j], a[j + 1]);
flag = false;
}
}
if (flag)
break;
}
}
快速排序交换版本
/*
* 交换法 左边从右边开始,左边从右边开始
*/
int partition(int* a, int low, int hight)
{
int begin = low;
int end = hight - 1;
int baseval = a[end];
while (begin < end)
{
while (begin < end && a[begin] <= baseval)
begin++;
while (begin < end && a[end] >= baseval)
end--;
if (begin < end)
std::swap(a[begin], a[end]);
}
std::swap(a[begin], a[hight - 1]);
return begin;
}
快速排序填坑法
/*
* 填坑法
*/
int partition2(int*a, int left, int right)
{
int begin = left;
int end = right - 1;
int baseval = a[begin];
while (begin < end)
{
while (begin < end && a[end] >= baseval)
end--;
a[begin] = a[end];
while (begin < end && a[begin] <= baseval)
begin++;
a[end--] = a[begin];
}
a[begin] = baseval;
return begin;
}
快速排序双指针同向法
/*
* 如果基准值在左边,prev 右边的都比基准值大,所需直接和low 交换
*如果是在右边, prev 右边的基准值都比基准值大,所以需要++然后在交换
*/
int partition3(int*a, int left, int right)
{
int prev = left - 1;
int pcur = left;
int baseval = a[left];
while (pcur < right)
{
if (a[pcur] <= baseval && ++prev != pcur)
std::swap(a[prev], a[pcur]);
++pcur;
}
std::swap(a[prev], a[left]);
return prev;
}
void _quickSort(int *a, int left, int right)
{
if (left < right)
{
int key = partition3(a, left, right);
_quickSort(a, left, key);
_quickSort(a, key + 1, right);
}
}
49、–实现快速排序的非递归。
void quickSort(int*a, int size)
{
if (a == NULL || size <= 0)
return;
stack<int> s;
int left = 0;
int right = size;
s.push(left);
s.push(right);
while (!s.empty())
{
right = s.top();
s.pop();
left = s.top();
s.pop();
if (left < right)
{
int key = partition2(a, left, right);
s.push(left);
s.push(key);
s.push(key + 1);
s.push(right);
}
}
}
并分析以下问题:
1:什么时候快速排序最坏?怎么避免最坏情况的出现?
基准值选的最差的时候最坏:
(1)、已经有有序
(2)、基准值每次都在开头选导致分区不平衡
优化?
- 随机枢纽
- 三数区中
- 元素较少插入排序
- 用栈
2:快速排序的时间复杂度是多少?
快速排序最佳运行时间O(nlogn),最坏运行时间O(N^2),随机化以后期望运行时间O(nlogn),
day21
50、实现归并排序
递归版本
void Merge(int* a, int * tmp, int left, int mid, int right)
{
int begin1 = left;
int end1 = mid;
int begin2 = mid + 1;
int end2 = right;
int index = left;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
tmp[index++] = a[begin1++];
else
tmp[index++] = a[begin2++];
}
while (begin1 <= end1)
tmp[index++] = a[begin1++];
while (begin2 <= end2)
tmp[index++] = a[begin2++];
}
void _MergeSort(int *a, int * tmp, int left, int right)
{
if (left < right)
{
int mid = (left + right) / 2;
_MergeSort(a, tmp, left, mid);
_MergeSort(a, tmp, mid + 1, right);
Merge(a, tmp, left, mid, right);
memcpy(a + left, tmp + left, sizeof(int)*(right - left + 1));
}
}
void MergeSort(int* a, int size)
{
int * temp = new int[size];
_MergeSort(a, temp, 0, size - 1);
delete temp;
}
非递归版本
void MergeSort_Nor(int* a, int size)
{
// 1. 分区
// 2. 归并
// 3. 拷贝回原数组
// 4. 重复 1.2.3.直到区间大于size
int * temp = new int[size];
int step = 1;
while (step < size)
{
for (int i = 0; i < size; i += 2*step)
{
int left = i;
int mid = left + step - 1;
int right = mid + step;
if (mid >= size)
mid = size - 1;
if (right >= size)
right = size - 1;
Merge(a, temp, left, mid, right);
}
memcpy(a, temp, sizeof(int)*size);
step *= 2;
}
delete[] temp;
}
51、有一个大文件内容如下:
data.txt
小明,1班,88分
小李,1班,90分
小王,1班,99分
小杨,1班,59分
…
要求:这个文件内容很大,不能全部放到内存,请按第三列的分数对这个文件的内容进行排序。
ps:可以考虑两方面入手:归并排序或linux指令。另外如果用代码实现,可以尽可能使考虑使用STL辅助,也不用造太大的文件,太麻烦,就给一个小文件,给10行数据,假设内存中最多能放3行数据,这样模拟实现就可以。
1、分
2、排序
3、合
split -- cat -- sort
day22
52、int a[] = { 12, 13, 12, 13, 19, 18, 15, 12, 15, 16, 17 },要求对数组a进行排序,要求时间复杂度为O(N)
void GetMinMax(int *a, int size, int& min_data, int & max_data)
{
min_data = max_data = a[0];
for (int i = 1; i < size; ++i)
{
if (a[i] > max_data)
max_data = a[i];
if (a[i] < min_data)
min_data = a[i];
}
}
void CountSort(int *a, int size)
{
int min_data;
int max_data;
GetMinMax(a, size,min_data, max_data);
int range = max_data - min_data + 1;
int *pcount = new int[range];
memset(pcount, 0, sizeof(int)* range);
for (int i = 0; i < size; ++i)
{
pcount[a[i]-min_data]++;
}
int index = 0;
for (int i = 0; i < range; i++)
{
for (int j = 0; j < pcount[i]; ++j)
a[index++] = i + min_data;
}
}
53、求一个无序数组的中位数。
如:{2,5,4,9,3,6,8,7,1}的中位数为5,{2,5,4,9,3,6,8,7,1,0}的中位数为4和5。
要求:不能使用排序,时间复杂度尽可能低。
提示:考虑堆或者快排思想解决。
static int partition(int*a, int left, int right)
{
int begin = left;
int end = right - 1;
int baseval = a[begin];
while (begin < end)
{
while (begin < end && a[end] >= baseval)
end--;
a[begin] = a[end];
while (begin < end && a[begin] <= baseval)
begin++;
a[end] = a[begin];
}
a[begin] = baseval;
return begin;
}
static int MidElem(int*a, int size)
{
if (a == NULL || size <= 0)
return 0x7FFFFFFFF;
int left = 0;
int right = size;
int mid = left + (right - left) / 2;
int key = partition(a, left, right);
if (size % 2 != 0)
{
while (key != mid)
{
if (key > mid)
key = partition(a, left, key);
else if (key < mid)
key = partition(a, key + 1, right);
}
cout << a[mid] << endl;;
}
else
{
while (key != mid)
{
if (key > mid)
key = partition(a, left, key);
else if (key < mid)
key = partition(a, key + 1, right);
}
cout << a[mid] << endl;
mid -= 1;
while (key != mid)
{
if (key > mid)
key = partition(a, left, key);
else if (key < mid)
key = partition(a, key + 1, right);
}
cout << a[mid ] << endl;
}
}
day23
54、将N个字符的数组,循环右移K位。时间复杂度O(N)。
普通解法
class Solution {
public:
void reverse(string& str, int begin, int end){
while (begin < end){
std::swap(str[begin++], str[end--]);
}
}
string LeftRotateString(string str, int n) {
if (str.empty() || n < 0)
return "";
int begin1 = 0;
int end1 = n - 1;
int begin2 = n;
int end2 = str.size() - 1;
reverse(str, begin1, end1);
reverse(str, begin2, end2);
reverse(str, begin1, end2);
return str;
}
};
神奇解法
static string rever(string str, int n)
{
if (n == 0 || str.empty())
return "";
int size = str.size();
str += str;
return str.substr(n , size);
}
55、删除小写字母字符串中重复字符。如果可以,优先删除重复字符中排在比他小字符前面的字符。 比如,输入:bbcacdww;输出:bacdw
void DeleteReptChar(char* str)
{
char count[26] = { 0 };
char* pstart = str;
while (*pstart)
{
int index = (*pstart++) - 'a';
count[index]++;
}
while (*str)
{
char index = *str - 'a';
if (count[index] == 1)
cout << *str;
else if (count[index] > 1)
count[index]--;
str++;
}
}
day24
56 、实现一个位图
class BitMap
{
public:
BitMap(size_t size = 32)
:_size(size)
{
_bitmap.resize(size / 32 + 1);
}
void Set(unsigned data)
{
// 获取下标
size_t index = data / 32;
// 获取比特位未知
size_t seq = data % 32;
_bitmap[index] = _bitmap[index] | (1 << seq);
}
void Reset(unsigned data)
{
// 获取下标
size_t index = data / 32;
// 获取比特位未知
size_t seq = data % 32;
int flag = 1 << seq;
_bitmap[index] = _bitmap[index] & (~flag);
}
bool Check(unsigned data)
{
// 获取下标
size_t index = data / 32;
// 获取比特位未知
size_t seq = data % 32;
// 防止越界,不允许访问超过size 的值
assert(_size >= data);
if (_bitmap[index] & (1 << seq))
return true;
return false;
}
private:
vector<int> _bitmap;
size_t _size;
};
void TestBitmap()
{
BitMap bm(10);
bm.Set(0);
bm.Set(11);
cout << boolalpha << bm.Check(0) << endl;
cout << boolalpha << bm.Check(2) << endl;
cout << boolalpha << bm.Check(3) << endl;
cout << boolalpha << bm.Check(11) << endl;//不允许
}
57、
以下三个问题都是位图相关类似题目,请给出解决方案:
1)给定100亿个整数,设计算法找到只出现一次的整数
思路:
100亿整数的文件在32位平台下大概40GB,一次无法装下,如果有需要可以将文件分割(Linux 命令 split)。
现在不考虑文件需要分割的情况。
在32为平台 int 整数一共有 42 亿个多,现在题目给了100亿说明肯定有重复。
如果一个整数存储需要一个字节,那么 4GB 刚好可以存储42 亿多。
再如果一个整数用1个bit 位表示,需要4GB/8 = 512M 大小的内存空间。因为 1B = 8 bit。
但是题目要求找出只出现一次的整数,说明一个bit 根本达不到目的,那么此时就可以再占用1bit位,用2个bit位表示一个整数。 00 表示不存在, 01 表示出现一次,10 表示出现多次,11 不关心。这样我们需要表示42 亿整数的内存就会是原来的大小翻倍,即1GB。
如果这个时候,考官还想你500M左右处理这道题呢?那么,我们可以考虑正整数和负整数分开处理。
2)给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集
思路一
使用hash函数将第一个文件所有整数映射到1000个文件中,分别编号a1,a2…a1000每个文件中大约有1000万个整数,大约40 M内存。
用同样的方法将第二个文件映射到,b1,b2。。。b1000。由于使用相同的hash函数,所有两个文件中一样的数字会被分配到文件编号一直的文件中,分别对a1,b2 …. a1000 b1000 求交集,最后汇总
思路二
42亿个整数每个整数用一个bit位需要512M 内存,遍历一个文件,将出现的数字置为1,遍历完后,再遍历第二个文件,如果对应的bit位不为0,就是重复出现的数字,直到遍历完。
3)1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
使用位图,用两个比特位表示一个整数,1G内存刚好,00表示没出现,01表示一次,10表示两个,11表示多次,最后只需要检查01 和 10即可。
day25
58 、实现一个简单的布隆过滤器
size_t BKDRHash(const char * str)
{
unsigned int seed = 131; // 31 131 1313 13131 131313
unsigned int hash = 0;
while (*str)
{
hash = hash * seed + (*str++);
}
return (hash & 0x7FFFFFFF);
}
size_t SDBMHash(const char* str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = 65599 * hash + ch;
//hash = (size_t)ch+(hash<<6)+ (hash<<16)-hash;
}
return hash;
}
size_t RSHash(const char *str)
{
register size_t hash = 0;
size_t magic = 63689;
while (size_t ch = (size_t)*str++)
{
hash = hash * magic + ch;
magic *= 378551;
}
return hash;
}
size_t APHash(const char* str)
{
register size_t hash = 0;
size_t ch;
for (long i = 0; ch = (size_t)*str++; i++)
{
if (0 == (i & 1))
{
hash ^= ((hash << 7) ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
}
}
return hash;
}
size_t JSHash(const char* str)
{
if (!*str)
return 0;
register size_t hash = 1315423911;
while (size_t ch = (size_t)*str++)
{
hash ^= ((hash << 5) + ch + (hash >> 2));
}
return hash;
}
template<class K>
struct __HashFunc1
{
size_t operator()(const K& key)
{
return BKDRHash(key.c_str());
}
};
template<class K>
struct __HashFunc2
{
size_t operator()(const K& key)
{
return SDBMHash(key.c_str());
}
};
template<class K>
struct __HashFunc3
{
size_t operator()(const K& key)
{
return RSHash(key.c_str());
}
};
template<class K>
struct __HashFunc4
{
size_t operator()(const K& key)
{
return APHash(key.c_str());
}
};
template<class K>
struct __HashFunc5
{
size_t operator()(const K& key)
{
return JSHash(key.c_str());
}
};
class BitMap
{
public:
BitMap(size_t size = 32)
:_size(size)
{
_bitmap.resize(size / 32 + 1);
}
void Set(unsigned data)
{
// 获取下标
size_t index = data / 32;
// 获取比特位未知
size_t seq = data % 32;
_bitmap[index] = _bitmap[index] | (1 << seq);
}
void Reset(unsigned data)
{
// 获取下标
size_t index = data / 32;
// 获取比特位未知
size_t seq = data % 32;
int flag = 1 << seq;
_bitmap[index] = _bitmap[index] & (~flag);
}
bool Check(unsigned data)
{
// 获取下标
size_t index = data / 32;
// 获取比特位未知
size_t seq = data % 32;
// 防止越界,不允许访问超过size 的值
assert(_size >= data);
if (_bitmap[index] & (1 << seq))
return true;
return false;
}
private:
vector<int> _bitmap;
size_t _size;
};
template <class K,
class HashFun1 = __HashFunc1<K>,
class HashFun2 = __HashFunc2<K>,
class HashFun3 = __HashFunc3<K>,
class HashFun4 = __HashFunc4<K>,
class HashFun5 = __HashFunc5<K> >
class BloomFilter
{
public:
BloomFilter( size_t size = 20)
:_bitmap(size)
,_size(size)
{}
void Insert(const K& data)
{
size_t index1 = HashFun1()(data) % _size;
size_t index2 = HashFun2()(data) % _size;
size_t index3 = HashFun3()(data) % _size;
size_t index4 = HashFun4()(data) % _size;
size_t index5 = HashFun5()(data) % _size;
_bitmap.Set(index1);
_bitmap.Set(index2);
_bitmap.Set(index3);
_bitmap.Set(index4);
_bitmap.Set(index5);
}
bool Find(const K& data)
{
size_t index1 = HashFun1()(data) % _size;
size_t index2 = HashFun2()(data) % _size;
size_t index3 = HashFun3()(data) % _size;
size_t index4 = HashFun4()(data) % _size;
size_t index5 = HashFun5()(data) % _size;
if (_bitmap.Check(index1) && _bitmap.Check(index2) &&
_bitmap.Check(index3) && _bitmap.Check(index4) &&
_bitmap.Check(index5))
return true;
return false;
}
private:
BitMap _bitmap;
size_t _size;
};
void TestBloomFilter()
{
BloomFilter<string> s;
s.Insert("呵呵");
s.Insert("霍");
s.Insert("Tom");
cout << boolalpha << s.Find("呵呵") << endl;
cout << boolalpha << s.Find("霍") << endl;
cout << boolalpha << s.Find("Tom") << endl;
cout << boolalpha << s.Find("呵") << endl;
cout << boolalpha << s.Find("不存在的") << endl;
cout << boolalpha << s.Find("哈哈") << endl;
}
59、1:布隆过滤器存在是准确的还是不存在是准确的?
答:布隆过滤器是存在误差的,数据分析方法有误差率。
2:布隆过滤器优缺点?
答:可以快速判断某个字符串是否在所属文件中,用在爬虫中,检测URL是否已经访问过。
3:如何扩展BloomFilter使得它支持删除元素的操作?请设计实现一个支持删除的布隆过滤器.
答:讲bloomFilter 底层的 位图换位计数 数组即可。
day26
60、模拟实现C库的memcpy和memmove
memcpy基础版
void* my_memcpy(void* destination, const void* source, size_t num)
{
void* ret = destination;
assert(destination);
assert(source);
while (num--)
{
*(char*)destination = *(char*)source;
destination = (char*)destination + 1;
source = (char*)source + 1;
}
return ret;
}
memcpy考虑重叠版
void* my_memcpy(void* destination, const void* source, size_t num)
{
assert(destination);
assert(source);
char* pdest = static_cast<char*>(destination);
const char* psrc = static_cast<const char*>(source);
if (pdest > psrc && pdest < psrc + num)
{
while (num)
{
*(pdest + num - 1) = *(psrc + num - 1);
num--;
}
}
else
{
while (num--)
*pdest++ = *psrc++;
}
return destination;
}
memcpy优化版本
void* my_memcpy_3(void* destination, const void* source, size_t num)
{
assert(destination);
assert(source);
size_t byteNum = num / sizeof(int);
size_t bitNum = num % sizeof(int);
int * pdest = (int*)destination;
const int* psrc = (const int*)source;
while (byteNum--)
*pdest++ = *psrc++;
char* pd = (char*)pdest;
const char* ps = (const char*)psrc;
while (bitNum--)
*pd++ = *ps++;
return destination;
}
memmove
void* my_memmove(void* destination, const void* source, size_t num)
{
assert(destination);
assert(source);
char* pdest = static_cast<char*>(destination);
const char* psrc = static_cast<const char*>(source);
if (pdest > psrc && pdest < psrc + num)
{
while (num)
{
*(pdest + num - 1) = *(psrc + num - 1);
num--;
}
}
else
{
while (num--)
*pdest++ = *psrc++;
}
return destination;
}
61、给两个文件,分别有100亿个URL,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法
精确算法:Hash分桶,文件切割,处理对应文件,汇总结果。
近似算法:bloom filter 讲一个文件映射,然后遍历另一个文件,结果写到文件。
day27
62、模拟实现C库的atoi和itoa
atoi
int my_atoi(const char* str)
{
if (NULL == str || *str == '\0')
return 0X7FFFFFFF;
bool flag = false; // 标记正负
int ret = 0;
if (*str == '-')
{
str++;
flag = true;
}
else if (*str == '+')
{
str++;
}
while (*str != '\0')
{
if (*str <= '9' && *str >= '0')
{
ret = ret * 10 + *str - '0';
str++;
}
else
{
ret = 0x7FFFFFFF;
break;
}
}
if (*str == '\0')
{
if (flag)
return 0 - ret;
else
return ret;
}
return ret;
}
itoa
char* my_itoa(int num, char* str, int radix)
{
assert(str);
const char index[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWZX";
unsigned temp;
/* 只有十进制才有正负*/
int i = 0;
if (radix == 10 && num < 0)
{
temp = (unsigned)-num;
str[i++] = '-';
}
else
{
temp = (unsigned)num;
}
do
{
str[i++] = index[temp % (unsigned)radix];
temp /= radix;
} while (temp);
str[i] = '\0';
/* 翻转*/
int begin;
int end = i - 1;
if (str[0] == '-')
begin = 1;
else
begin = 0;
while (begin < end)
{
std::swap(str[begin], str[end]);
begin++;
end--;
}
return str;
}
63、给一个超过100G的log file, log中存着IP地址, 设计算法找到出现次数最多的100个IP地址?
转为100亿个整型,然后分桶,哈希统计。
或者用最小堆。