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億個整型,然後分桶,哈希統計。
或者用最小堆。