剑指offer刷题笔记(一)
剑指offer刷题笔记(一)
一、数组
3.数组中重复的数字
题目一
题目描述
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2
简单解法:
- 用一个长度为n的check数组,当check[numbers[i]]>0时,表示numers[i]出现过,返回false;否则继续检查,直到遍历完numbers。
空间复杂度:O(n),时间复杂度:O(n)
1 | class Solution { |
- hash表来存储,
空间复杂度:O(n),时间复杂度:O(n)
- 先排序,再查找。
时间复杂度O(nlogn)
- 最优解法:
时间复杂度O(n)
遍历numbers,如果numbers[i]等于i,说明位于自己所在位置,否则
- 循环判断,直到numbers[i]等于i
- 与i位置上的数字进行比较,如果不等于,则交换
- 如果相等则得到重复的数字。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28bool duplicate(int numbers[], int length, int* duplication) {
if(numbers==NULL||length<=0)
return false;
for(int i = 0; i < length; i++)
{
if(numbers[i] < 0 || numbers[i] > length-1 )
return false;
}
for(int i = 0;i < length; i++)
{
while(numbers[i] != i)
{
int temp = numbers[i];
if(numbers[i]!=numbers[temp])
numbers[i] = numbers[temp];
else
{
*duplication = numbers[temp];
return true;
}
numbers[temp] = temp;
}
}
return false;
}
};
- 如果相等则得到重复的数字。
注意:判断所有的数字是否在合法范围
题目二
在一个长度为n+1的数组里的所有数字都在1到n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为8的数组{2, 3, 5, 4, 3, 2, 6, 7},那么对应的输出是重复的数字2或者3。
简单解法:
- check数组,同题目一
空间复杂度:O(n),时间复杂度:O(n)
- 二分法:
- 以数m为分界分为两类数组,1~m,m+1~n
- 分别统计以上两类数字出现的次数,如果任意一类出现的次数超过m(或n-m),则说明重复的数字在那一类中出现
- 再从出现重复数字的一类中继续以中间数分为两类,重复1、2两步,直到找到重复数字
由于countNum的时间复杂度是O(n),而且会被调用O(logn)次,所以最终的
时间复杂度是O(nlogn)。空间复杂度为O(1)
。
1 | class Solution { |
4. 二维数组中的查找
题目
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
- 穷举。
时间复杂度O(m*n)
- 结合折半查找
- 最优:从右上角比较,如果
- 如果= target,返回
- 如果 < target,这一行剔除;
- 如果 > target,这一列剔除。
时间复杂度O(m+n)
1 | bool Find(int target, vector<vector<int> > array) { |
21. 调整数组顺序使奇数位于偶数前面
题目
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
解法
- 两个数组存,一个存奇数,一个存偶数 ,拼接(保证相对顺序)。
时间复杂度:O(n);空间复杂度:O(n)
- 每当遇到一个偶数,把后面的数往前移动一位,然后这个数放到最后面。
时间复杂度O(n^2)
- 前后双指针:一个指针指向第一个,另一个指向最后一个。如果前一个指向偶数,后一个指向奇数,则交换两个数。
时间复杂度:O(n)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56牛客解法(要求保证相对顺序)
class Solution {
public:
void reOrderArray(vector<int> &array) {
if(array.size()==0)
return ;
vector<int> orderArray;
for(auto number:array)
{
if(number % 2 == 1)
{
orderArray.push_back(number);
}
}
for(auto number:array)
{
if(number % 2 == 0)
orderArray.push_back(number);
}
array = orderArray;
}
};
剑指offer解法:函数指针
class Solution {
public:
bool isEven(int num)
{
return (num & 1) ==0;
}
void reOrder(int* array,size_t length,bool(* func)(int))
{
int * head,* end;
head = array;
end = array +(length - 1);
while(head < end)
{
if(func(*head)!=0)
head++;
if(func(*end)==0)
end--;
if(func(*head)==0&&func(*end)!=0)
{
swap(*head,*end);//伪代码,具体实现省略
}
}
}
29. 顺时针打印矩阵
题目
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
- 我的解法-只适用于方阵
- 剑指offer;
- 选取左上角start(startX,startX);
- 发现只要满足rows > startX 2,colums>startX2,循环满足条件
标准解法
1 | class Solution { |
39. 数组中超过一半的数字
题目
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
解法:
- 先排序,再查找。排序
时间复杂度:O(n*log(n))
- 遍历数组,使用一个变量存num,另一个存count. 与下一个数字比较。
时间复杂度O(n),空间复杂度O(1)。
- 如果相等,count++
- 如果不等,count–
- 如果==0,num存放下一个数字
最后,得到一个count>=1的数字和num,遍历数组判断num次数,得到数字。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43方法二
class Solution {
public:
int checkNum(vector<int>& numbers,int num)
{
int times = 0;
for(auto number:numbers)
{
if(number==num)
times++;
}
if(times>numbers.size()/2)
return num;
else
return 0;
}
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.size()<=0)
return 0;
int num = numbers[0],count=1;
for(int i=0;i<numbers.size()-1;i++)
{
if(num!=numbers[i+1])
{
count--;
if(count == 0)
{
i++;
num=numbers[i+1];
count=1;
}
}
else{
count++;
}
}
return checkNum(numbers,num);
}
};
- 如果==0,num存放下一个数字
快速排序实现
- 基于Partition函数的时间复杂度为O(n)的算法
关于Partition的实现
1 | 实现快速排序的关键,先选中一个,接下来把数组数字分为两部分,比选择小的数字移到数组的左边,比选择数字大的数字移到数组的右边。如下实现 |
1 | bool g_bInputInvalid = false; |
42. 连续子数组的最大和(动态规划)
题目描述
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。你会不会被他忽悠住?(子向量的长度至少是1)
解法
- 找规律法,
时间复杂度:O(n)
- 使用两个变量,一个存放每一步的sum,另一个存放maxSum。每一步考虑
sum < 0 ? sum = array[i] : sum += array[i]
maxSum > sum ? 1 : maxSum = sum
- 遍历整个数组可得结果
1 | class Solution { |
- 动态规划法(与上一方法相同)
51. 数组中的逆序对
解法: - 穷举比较
时间复杂度O(n^2)
- 空间换时间(归并排序)
时间复杂度:O(n*log(n)),空间复杂度:O(n)
先对数组二分,然后合并的时候排序并统计逆序对的数量。等同于归并排序。归并排序
1 | int InversePairs(int* data, int length) |
57. 和为s的数字
和为S的两个数字-题目描述
输入一个递增排序的数组和一个数字S,在数组中查找两个数,他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
输出描述:对应每个测试案例,输出两个数,小的先输出。
解法:
双指针,一个head从前往后,另一个tail从后往前,遍历数组。
- 如果array[head] + array[tail] < sum;head++
- 如果array[head] + array[tail] > sum;tail–
- 如果相等,有输出条件,则head++,tail–
时间复杂度O(n)
1 | class Solution { |
66. 构建乘积数组
解法:
- 遍历,对每个B[i]都从头到尾遍历A数组相乘,
时间复杂度:O(n^2)
- 递归解法
- 从B[0]到B[1],推广到B[0]到B[n-1]
- 每一趟迭代B[i]并计算sum值
- 因为
B[end]=sum;B[i]‘=B[i]*A[end]
,时间复杂度:O(n*log(n))
1 | class Solution { |
- 构建矩阵求解
- 把B[i]看成A[0] A[1] … A[i-1]和A[i+1] … *A[n-1]
1 | void BuildProductionArray(const vector<double>& input, vector<double>& output) |
二、字符串
5. 替换空格
不要使用辅助数组,因为传入的是*str,也就是指针,只能修改指针所指的内容,如果想修改指针指向哪里,就必须传入的是**str,即指针的指针。
解法:
双指针:时间复杂度O(n)
先计算出空格数量,然后p2指向预计的最后位置,p1指向初始结尾。如果p1遇到空格,复制%20到p2的位置,否则正常复制,直到p1==p2
1 | class Solution { |
20. 表示数值的字符串
三、链表
6. 从尾到头打印链表
解法
- 使用栈,顺序push,然后pop打印。
时间复杂度O(n),空间复杂度O(n)。
(空间占用过多) - 递归实现。
时间复杂度O(n)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class Solution {
public:
void print(ListNode* cursor,vector<int>& results)
{
if(cursor!=nullptr)
{
if(cursor->next!=nullptr)
print(cursor->next,results);
results.push_back(cursor->val);
}
}
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> results;
ListNode* cursor = head;
if(head ==nullptr)
return results;
print(cursor,results);
return results;
}
};
18. 删除链表的节点
题目一
解法
- 把要删除节点的后一个节点的值赋值到当前节点,然后删除后一个节点。
- 考虑节点是头结点和尾节点的情况
时间复杂度O(1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29ListNode* deleteDuplication(ListNode* pHead,ListNode* cursor)
{
if(cursor->next!=nullptr)
{
ListNode* p = cursor->next;
cursor->val = cursor->next->val
cursor->next = p->next;
delete p;
p = nullptr;
return pHead;
}
else if(phead == cursor)
{
delete phead;
phead =nullptr;
}
else{
ListNode* p = phead;
while(p->next!=cursor)
{
p = p->next;
}
p->next = nullptr;
delete cursor;
cursor = nullptr;
}
}
题目二-
- 三个指针:pPreNode->前一个节点,pNode->当前节点,pNext->下一个节点
- 如果
pNode->val == pNext->val
,设置Flag == true
- 如果
Flag == true
,那么设置delNode = pNode
,delvalue = pNode->val
- 直到删除所有重复节点,注意考虑
pPreNode == nullptr,pNode = pHead
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41ListNode* deleteDuplication(ListNode* pHead)
{
if(pHead == nullptr||pHead->next== nullptr)
return pHead;
ListNode* pPreNode = nullptr,* pNode = pHead;
while(pNode!=nullptr)
{
bool delFlag = false;
ListNode* pNext = pNode->next;
if(pNext!=nullptr&&pNode->val==pNext->val)
delFlag = true;
if(delFlag == false)
{
pPreNode = pNode;
pNode = pNext;
}
else{
int delvalue = pNode->val;
ListNode* delNode = pNode;
while(delNode!=nullptr&&delNode->val==delvalue)
{
pNext = delNode->next;
delete delNode;
delNode = nullptr;
delNode = pNext;
}
}
if(pPreNode == nullptr)
pHead = pNext;
else
pPreNode->next = pNext;
pNode = pNext;
}
return pHead;
}
22. 链表中倒数第k个节点
- 遍历链表获得长度,然后让pointer走n-k+1步(需要遍历两次链表)
时间复杂度O(n^2)
- 双指针:前后距离为k-1,当后一个pointer走到尾部,返回前一个指针
时间复杂度O(n)
- 注意:
k <1
,head ==nullptr
和k>链表size
三情况返回nullptr
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23解法2
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
if( pListHead == nullptr || k == 0 )
return nullptr;
ListNode* preNode = nullptr,*Node = pListHead;
for(unsigned int i = 0;i < k - 1;++i)
{
if(Node->next!=nullptr)
Node = Node->next;
else
return nullptr;
}
preNode = pListHead;
while(Node->next!=nullptr)
{
Node = Node->next;
preNode = preNode->next;
}
return preNode;
}
- 注意:
23. 链表中环的入口节点
分两部分:
- 判断是否有环
- 快慢指针:两个指针指向head,一个走1步,一个走2步,如果相遇则证明有环。
- 差值为环上节点个数k
- 判断入口节点
- 双指针:前后距离为k,同步推进,直到相遇,相遇的节点即为环的入口。
1 | class Solution { |
24. 反转链表
解法
三指针法:
- 判断是否head为空或者只有head
- prev指向nullptr,current指向head,forward指向head->next
遍历list
current->next = prev;prev =current;current=forward;
最后
current->next=prev;forward->next = current;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if(pHead==nullptr||pHead->next==nullptr)
return pHead;
ListNode* prev =nullptr ,*current =pHead,*forward = pHead->next;
for(;forward->next!=nullptr;forward=forward->next)
{
current->next = prev;
prev = current;
current = forward;
}
current->next = prev;
forward->next = current;
return forward;
}
};
25. 合并两个排序的链表
- 递归解法
1 | class Solution { |
35. 复杂链表的复制
题目描述:
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
解法:
- 暴力法解法
- 首先遍历一遍复制链表
- 然后复制random指针,每一次复制都必须从头遍历。
时间复杂度:O(n^2)
- 利用hash表存储映射信息
- 遍历复制链表的时候使用hashtable存储<N,N’>的对应信息
- 根据信息复制random指针
时间复杂度:O(n),空间复杂度O(n)
- 巧妙解法
- 遍历链表在Node n后面复制n‘
- 遍历链表复制random指针
- 拆分链表
时间复杂度:O(n)
1 | class Solution { |
53. 两个链表的第一个公共节点
题目描述
输入两个链表,找出它们的第一个公共结点。
解法
考虑问题
- 从头到尾找?还是从尾到头找?
- 单链表从尾到头怎么找?考虑时间复杂度和空间复杂度?
解决问题 - 从尾到头找。
- 对于list m,list n。利用stack模拟从尾到头遍历,
时间复杂度和空间复杂度O(m + n)
。 - 然后遍历栈,寻找第一个不同的元素,前一个被pop的就是公共节点。
- 遍历的
时间复杂度O(k),k为公共节点数+1
四、树
7. 重建二叉树++
递归解法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35class Solution {
public:
TreeNode* Construct(vector<int>& pre,vector<int>& vin,int preStart,int preEnd,int vinStart,int vinEnd)
{
int rootValue = pre[preStart];
TreeNode* root = new TreeNode(rootValue);
if(preStart==preEnd)
{
if(vinStart==vinEnd&&pre[preStart]==vin[vinStart])
return root;
}
int rootInOrder = vinStart;
while(rootInOrder<=vinEnd&&vin[rootInOrder]!=rootValue)
{
rootInOrder++;
}
int leftLength = rootInOrder - vinStart;
int leftPreEnd = preStart + leftLength;
if(leftLength > 0)
root->left =Construct(pre,vin,preStart+1,leftPreEnd,vinStart,rootInOrder-1);
if(leftLength < preEnd - preStart)
root->right = Construct(pre,vin,leftPreEnd+1,preEnd,rootInOrder+1,vinEnd);
return root;
}
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin)
{
if(pre.size()==0||vin.size()==0)
return nullptr;
return Construct(pre,vin,0,pre.size()-1,0,vin.size()-1);
}
};
8. 二叉树的下一个节点+
解法:
- 首先判断Node是否有right节点
- 如果有,一直向left查找是否有left节点,返回最left的节点
- 如果没有返回right节点
- 判断是否是root节点
- 如果是返回
nullptr
3. 双指针:
- current指向当前node,parent指向parent node
- 如果
parent!=nullptr
且current==parent->right
,则一直向上遍历 - 否则跳出循环返回parent节点
1 | class Solution { |
26. 树的子结构+
解法:双重递归
首先查找val相等的节点。
- 从这个node对比t1和t2是否为同一子树
- 如果
t2==nullptr
,(说明t2已经遍历完成,证明t2是t1的子结构 - 如果
t2!=nullptr&&t1==nullptr
,说明(t2的节点t1没有,则返回false) - 如果两个节点val不相等,也返回false
- 然后递归比较left和right
- 如果子树判断不相等,则继续比较t1->left和t2已经t1->right和t2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31bool doesT1HaveT2(TreeNode*pRoot1,TreeNode* pRoot2)
{
if(pRoot2==nullptr)
return true;
if(pRoot1==nullptr)
return false;
if(pRoot1->val!=pRoot2->val)
return false;
return doesT1HaveT2(pRoot1->left,pRoot2->left)&&doesT1HaveT2(pRoot1->right,pRoot2->right);
}
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
bool result = false;
if(pRoot1!=nullptr&&pRoot2!=nullptr)
{
if(pRoot1->val==pRoot2->val)
{
result = doesT1HaveT2(pRoot1,pRoot2);
}
if(!result)
result = HasSubtree(pRoot1->left,pRoot2);
if(!result)
result = HasSubtree(pRoot1->right,pRoot2);
}
return result;
}
27. 二叉树的镜像
题目:完成一个函数,输入一棵二叉树,该函数输出它的镜像。
解法:
递归
- 判断是否nullptr或单节点,是则返回
- 交换left和right
- 遍历Tree
1 | class Solution { |
28. 对称的二叉树
解法:
- 使用对称的先序遍历,判断是否为对称二叉树
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33class Solution {
public:
bool isSymTree(TreeNode* t1,TreeNode* t2)
{
if((t1==nullptr)&&(t2==nullptr))
return true;
if(t1==nullptr||t2==nullptr)
return false;
if(t1->val!=t2->val)
return false;
return isSymTree(t1->left,t2->right)&&isSymTree(t1->right,t2->left);
}
bool isSymmetrical(TreeNode* pRoot)
{
bool result = false;
if(pRoot==nullptr||(pRoot->left==nullptr&&pRoot->right==nullptr))
{
return true;
}
if(pRoot->left!=nullptr&&pRoot->right!=nullptr)
{
result = isSymTree(pRoot->left,pRoot->right);
}
else{
return false;
}
return result;
}
};
32. 从上到下打印二叉树(层序遍历)
1.使用queue
- 先把root放入queue
- 然后每次把front节点的left和right放入queue
- pop front节点,放入vector,直到queue为空
- 最后返回vector
1 | class Solution { |
33. 二叉搜索树的后序遍历序列
1.我的解法
- 首先空树返回 false
- 然后计算从与root相比大小的跳变次数进行计数
- 对于大的子树在小的子树前面的情况要首先排除
- 如果跳变次数大于1则为false
时间复杂度O(n)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class Solution {
public:
bool VerifySquenceOfBST(vector<int> sequence) {
if(sequence.size()==0)
return false;
int root = sequence[sequence.size()-1];
int flagCount = 0;
for(int i=0;i<sequence.size()-1;i+=1)
{
if((sequence[i]-root)>0&&(sequence[i+1]-root)<0)
return false;
if((sequence[i]-root)*(sequence[i+1]-root)<0)
{
flagCount++;
}
}
if(flagCount>1)
return false;
else
return true;
}
};
- offer解法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35bool VerifySquenceOfBST(int sequence[], int length)
{
if(sequence == nullptr || length <= 0)
return false;
int root = sequence[length - 1];
// 在二叉搜索树中左子树的结点小于根结点
int i = 0;
for(; i < length - 1; ++ i)
{
if(sequence[i] > root)
break;
}
// i = 左侧最大的小于root节点
// 在二叉搜索树中右子树的结点大于根结点
int j = i;
for(; j < length - 1; ++ j)
{
if(sequence[j] < root)
return false;
}
// j = 末尾root前一个节点
// 判断左子树是不是二叉搜索树
bool left = true;
if(i > 0)
left = VerifySquenceOfBST(sequence, i);
// 判断右子树是不是二叉搜索树
bool right = true;
if(i < length - 1)
right = VerifySquenceOfBST(sequence + i, length - i - 1);
return (left && right);
}
34. 二叉树中和为某一值的路径
题目描述
输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
解法
1.考虑问题
- 以叶子节点为终点,判断是否是一条符合要求路径。递归可以实现回溯
- 保存一个路径长度,考虑回溯要剪短路径长度
- 前序遍历可以满足遍历,使用递归解法。
1 | class Solution { |
40. 最小的k个数(Top k问题)**
题目描述
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
提示:在STL中,set和multiset都是基于红黑树实现的。
解法分析:
- 最直接的方法
先排序(O(nlogn)),然后取前k个数- 基于Partition的方法(不借助辅助空间)
- 相当于对于第k个数,左边都是小于k的数,右边都是大于k的数。
时间复杂度O(n)
- 限制:需要修改数组,如果不能修改怎么办?
- 时间复杂度为O(logk)的算法(适合海量数据)
- 首先创建一个大小为k的容器存储最小的k个数字
- 考虑如果容器没满
- 从数组中读一个数T放在容器
- 考虑如果容器满了
- 在容器里找最大的数S
- 如果T > S则不操作,如果T < S则删除S并插入T
- 考虑使用什么数据结构作为容器(从CRUD的角度考虑)
- 最大堆(查找最大值O(1),插入和删除O(logk))
- 红黑树(查询,插入和删除O(logk))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 解法一
class Solution {
public:
int Random(int start,int end)
{
default_random_engine e;
uniform_int_distribution<int> u(start,end);
return u(e);
}
void Swap(int& a,int& b)
{
int temp = b;
b = a;
a = temp;
}
int Partition(vector<int>& input,int start,int end)
{
int index = Random(start,end);
Swap(input[index],input[end]);
int small = start - 1;
for(index = start;index < end;index++)
{
if(input[index]<input[end])
{
small++;
if(index!=small)
Swap(input[index],input[small]);
}
}
Swap(input[++small],input[end]);
return small;
}
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
vector<int> result;
if(input.size()==0||k <= 0||k>input.size())
{
return result;
}
int start = 0;
int end = input.size() - 1;
int index = Partition(input,start,end);
while(index!=k-1)
{
if(index < k-1)
{
start = index + 1;
index = Partition(input,start,end);
}
else
{
end = index - 1;
index = Partition(input,start,end);
}
}
for(int i = 0;i < k;i++)
{
result.push_back(input[i]);
}
return result;
}
};
1 | 解法二:利用红黑树的查询,插入,删除特性。从海量数据中处理topk问题 |
五. 栈和队列
通常栈不需要考虑排序问题,所以一般要查找最大或者最小元素时需要O(n)
的时间复杂度,但是经过改造可以在O(1)
时间查找最大和最小。
9. 用两个栈实现队列
使用两个栈模拟队列
- push()
- 元素都放入stack1中
- pop()
- 考虑两种情况
- 如果stack2为空,那么把stack1全部元素放到stack2中,输出栈顶,stack2.pop()
- 如果stack2不为空,那么直接输出栈顶,stack2.pop()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30class Solution
{
public:
void push(int node) {
stack1.push(node);
}
int pop() {\
if(stack2.empty())
{
while(!stack1.empty())
{
int element = stack1.top();
stack2.push(element);
stack1.pop();
}
}
int result = stack2.top();
stack2.pop();
return result;
}
private:
stack<int> stack1;
stack<int> stack2;
};
30. 包含min函数的栈(最小栈)
借助一个辅助栈(空间换时间)
每次push的时候比较辅助stack栈顶和value的大小,如果value<stack2.top;push value到stack2的栈顶,否则把top再push一遍
。每次pop时候,两个stack同时pop。
1 | class Solution { |
31. 栈的压入、弹出序列
方法:
- 借助一个辅助栈,两个指针
- 如果push数组指针对应的不等于pop数组对应的,则压入stack。
- 如果相等,同时推进两个指针,直到push数组遍历完成。
- 然后对应pop数组遍历,和stack的top比较,
- 如果不等返回false
- 如果相等继续遍历
- 最后判断stack是否为空。如果不为空,返回false。为空返回true。
1 | class Solution { |
六、其他
16. 数值的整数次方
我的解法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31class Solution {
public:
double Power(double base, int exponent) {
double result = 0;
if(exponent==0)
{
result = 1.0;
return result;
}
else
{
result = base;
for(int i=0;i<abs(exponent)-1;i++)
{
result*=base;
}
}
if(exponent<0)
{
result = 1 / result ;
}
else{
result = result * 1;
}
return result;
}
};
递归解法
1 | class Solution { |
七、递归和循环
10. 斐波那契数列(青蛙跳台阶)
题目一
递归
1
2
3
4
5
6
7
8
9int fib(int n)
{
if(n == 0)
return 0;
if(n == 1)
return 1;
return fib(n-1)+fib(n-2);
}循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19long long Fibonacci_Solution2(unsigned n)
{
int result[2] = {0, 1};
if(n < 2)
return result[n];
long long fibNMinusOne = 1;
long long fibNMinusTwo = 0;
long long fibN = 0;
for(unsigned int i = 2; i <= n; ++ i)
{
fibN = fibNMinusOne + fibNMinusTwo;
fibNMinusTwo = fibNMinusOne;
fibNMinusOne = fibN;
}
return fibN;
}
题目二
想法是青蛙一次可以跳一步,也可以跳两步。
那么就是跳一步和f(n-1);跳两步和f(n-2)。
和斐波那契同理。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Solution {
public:
int jumpFloor(int number) {
int count = 0;
if(number == 0)
return 0;
if(number == 1)
return 1;
else if(number == 2)
return 2;
else
{
count = jumpFloor(number-1) + jumpFloor(number-2);
}
return count;
}
};
题目三
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
返回2^(n-1)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Solution {
public:
int jumpFloorII(int number) {
int count = 1;
if(number ==0)
return 0;
else
{
for(int i =1;i<number;i++)
{
count = count <<1;
}
}
return count;
}
};
八、位运算
15. 二进制中1的个数
九、回溯法
12. 矩阵中的路径
题目描述
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串”bcced”的路径,但是矩阵中不包含”abcb”路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
解法
考虑两个问题
- 走错路怎么办(即matrix[row*cols+col]==str[pathLength]但是它的上下左右没有找到符合str[pathLength+1]的点)
- 避免走重复路(用一个visited[]数组,标记走过的路,如果为true表明已经走过且符合要求,不能重复走)
回溯法 - 如果matrix[row*cols+col]==str[pathLength]说明当前位于str对应的位置上,考虑上下左右四个方向走pathLength+1的方向
- 当
str[pathLength]=='\0'
时,说明已经找到最终位置,开始返回true。
1 | class Solution { |