队列和栈
- 用两个栈实现队列的头部出队,尾部入队操作:
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
26class Solution
{
public:
void push(int node) {
while(!stack2.empty())
{
stack1.push(stack2.top());
stack2.pop();
}
stack1.push(node);
}
int pop() {
while(!stack1.empty())
{
stack2.push(stack1.top());
stack1.pop();
}
int ans = stack2.top();
stack2.pop();
return ans;
}
private:
stack<int> stack1;
stack<int> stack2;
};
30、包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为$O(1)$)。
- 解题思路:复杂度是$O(1)$,则不是重装栈,trick是辅助栈进展方式为遇到最小就装它,否则装入当前栈顶元素。
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 class Solution {
public:
void push(int value) {
st1.push(value);
if(st2.empty())
st2.push(value);
else
{
int top = st2.top();
if(top>value)
st2.push(value);
else
st2.push(top);
}
}
void pop() {
st1.pop();
st2.pop();
}
int top() {
return st1.top();
}
int min() {
return st2.top();
}
private:
stack<int> st1;
stack<int> st2;
};
31、栈的压入和弹出
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
- 解:比较进栈数组中对每一个进栈的数与出栈数组的第一个元素,相等则对该元素出栈,继续比较进栈数组出栈后的栈顶与出栈数组第二个元素,相等则弹出,否则,继续进栈。若是最后进栈数组为空,则结果匹配正确。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class Solution {
public:
bool IsPopOrder(vector<int> pushV,vector<int> popV) {
if(pushV.empty() && popV.empty()) return true;
if(pushV.empty() || popV.empty() || pushV.size()!=popV.size()) return false;
stack<int> st;
for(int i=0,j=0; i<pushV.size();++i)
{
st.push(pushV[i]);
//未越界且相等
while(j<popV.size() && popV[j]==st.top())
{
st.pop();
j++;
}
}
if(st.empty())
return true;
else
return false;
}
};
59、队列最大的值
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}
1.直接算最大再排除第一个:这个算法总的时间复杂度是O(nk)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class Solution {
public:
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
vector<int> ans;
deque<int> index;
if(num.empty() || size == 1) return num;
if(size<=0) return vector<int>();
int count = 0; //记录滑动窗口大小是否达到
for(int i=0; i<num.size();++i)
{
index.push_back(num[i]);
count++;
if(count==size)
{
count -= 1;
ans.push_back(*max_element(index.begin(), index.end()));
index.pop_front();
}
}
return ans;
}
};
2.
用双向队列实现,主要是理解思路。
从后删除的情况:只有当 当前数字比队列的后面数字大时。
从前删除的情况: 只有 当 队列前面的数字的序号不在滑动窗口内。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Solution {
public:
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
vector<int> res;
deque<int> s;
for(unsigned int i=0;i<num.size();++i){
while(s.size() && num[s.back()]<=num[i])//从后面依次弹出队列中比当前num值小的元素,同时也能保证队列首元素为当前窗口最大值下标,同时排出多个
s.pop_back();
//这里的while改为if也是可行的,因为只可能超出一个
while(s.size() && i-s.front()+1>size)//当当前窗口移出队首元素所在的位置,即队首元素坐标对应的num不在窗口中,需要弹出
s.pop_front();
s.push_back(i);//把每次滑动的num下标加入队列
if(size&&i+1>=size)//当滑动窗口首地址i大于等于size时才开始写入窗口最大值
res.push_back(num[s.front()]);
}
return res;
}
};
数组和数值
- 下面代码的输出是什么
1 | int GetSize(int data[]) |
因为sizeof(data1)
是求数组的大小,每个整数占4个字节;
第二个是因为指针占8个字节;
第三个是因为数组作为函数参数进行传递,数组退化为指针,也为8个字节。
- 面试题:找出数组中任意一个重复的数字,数组满足:长度为n的数组里所有数字都在0~n-1的范围内。
- 思路:若不重复,每个下标对应一个等于下标值的数。对下标
i
与m=arr[i]
作比较,不相等交换arr[i]
与arr[m]
- 时间复杂度$O(n)$,空间复杂度$O(1)$。
1 |
|
- 不修改数组找出重复的数字,数组满足:长度为n+1的数组里所有的数字都在1~n范围内,因此至少有一个数字是重复的。找出任意一个数字。
- 思路:辅助数组的方法$O(n)$时间复杂度,$O(n)$空间复杂度,空间换时间;折半查找的方法$O(n\lg n)$时间复杂度,$O(1)$空间复杂度,时间换空间。
- 排查数组哪一半的数字有重复,遍历数组所有数,计数每个数是否在下标范围,总计数值超过的这一半的大小的话,有重复元素;
1 |
|
方法2:
1 | class Solution { |
- 有规律的二维数组中找出某一个数:数组满足每行数大小递增,每一列大小递增。
- 从第一行最后一列排除:
1 | //number 是要找的数 |
- 数值的整数次方:给定一个
double
类型的浮点数base
和int
类型的整数exponent
。求base
的exponent
次方。
- 解:
利用了公式:
实现的时候先对目标$target$的一半运算出来,然后判断是否是奇数。
1 | class Solution { |
- 调整数组顺序使得奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
- 解:
相对位置不变—->保持稳定性;奇数位于前面,偶数位于后面 —->存在判断,挪动元素位置; 这些都和内部排序算法相似,考虑到具有稳定性的排序算法不多,例如插入排序,归并排序等;
1 | class Solution { |
29、顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 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.
- 解:需要分析它的边界:
打印四个边,什么时候退出:行、列数目大于2倍起点的时候;起点是每次的左上角位置;
打印第一步:因为打印一圈至少有一步,所以直接打印;
打印第二步:行号大于起点行号才能多处行往下打印;
打印第三步:考虑这时候不能为一行一列的打印,至少是两行两列;
打印第四步:考虑已经少了一行数目;
1 | class Solution { |
39、数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
- 解: 两种方法实现:1,快排分割,元素被放置在中间位置,则找到结果;2.设置每次遇到的该数为result,并计数1,若下次遇到的
是该数,计数增加,若不是,计数减少,当计数为0,设result为新的该数字,最后被保存的result肯定是超过一半的数字
1 | class Solution { |
方法1:
1 | class Solution { |
40、最小的k个数
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
- 解析:第一种一般可想到是堆排序,且不会修改输入数据,适合在处理海量数据中进行查找,STL中的
set
与multiset
的底层是红黑树实现的,可以满足在$O(\lg n)$时间内完成查找、删除、插入。第二种方法是partition.
方法1:
1 | class Solution { |
方法2:
1 | class Solution { |
41、数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
- 解析: 假设整个数据容器分割为两部分,位于容器左边的部分比右边的部分小;容器左数组的最右边指向该部分最大的数;同样, 容器右数组的最左边指向该部分最小的数;
具体实现:左边用最大堆,右边用最小堆;首先保证数据平均分配到两个堆中,因此这两个堆中的数据数目之差不超过1,用偶数的数字都插入最小堆;保证最大堆中的数都小于最小堆,当前插入数字小于最大堆堆顶元素,可以把该堆的堆顶元素排除,插入到最小堆,然后把该数字插入最大堆中.
1 | class Solution { |
42、连续子数组的最大和
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
- 解析:1。动态规划问题,设在位置$n-1$处结尾的数组最大和为$f(n-1)$,那前面长度为$n$的数组的最大连续子序列的和为:
可以理解,如果前面的结尾的子串为负数,可以不加;若为正数,可以考虑加上这个值。最后还需要求 $max_{(i=1,2,3,….,N)}f(i)$ 找出最大的和:
1 | class Solution { |
2.跟据求和的性质,若为负,丢弃当前和,并记录每一的最大和;
1 | public: |
43、1~n整数中1出现的次数
求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
- 解析: 方法1:nlog(n)暴力搜索,对每一个数求一个计数1的函数;
方法2:log(n) 考虑问题本身的性质;
1 | //方法1:nlog(n) |
方法2:参考第三题:https://www.jianshu.com/p/1582fbaf05f7
当 N 为 1 位数时,对于 N>=1,1 的个数 f(N) 为1。
当 N 为 2 位数时,则个位上1的个数不仅与个位数有关,还和十位数字有关。
比如当 N=23 时,个位上 1 的个数有 1、11、21 共3个,十位上1的个数为10,11…19 共10个,所以 1 的个数 f(N) = 3+10 = 13。
看出来有规律一:
如果 N 的个位数 >=1,则个位出现1的次数为十位数的数字加1;如果 N 的个位数为0,则个位出现 1 的次数等于十位数的数字。
十位数上出现1的次数类似,如果N的十位数字等于1,则十位数上出现1的次数为各位数字加1;如果N的十位数字大于1,则十位数上出现1的次数为10。
当 N 为 3 位数时,同样分析可得1的个数。如 N=123,可得 1出现次数 = 13+20+24 = 57。当 N 为 4,5…K 位数时,
我们假设 N=abcde,则要计算百位上出现1的数目,则它受到三个因素影响:百位上的数字,百位以下的数字,百位以上的数字。
如果百位上数字为0,则百位上出现1的次数为更高位数字决定。如 N=12013,则百位出现1的数字有100~199, 1000~1199, 2100~2199…11100~111999 共 1200 个,等于百位的更高位数字(12)当前位数(100)。
如果百位上数字为1,则百位上出现1的次数不仅受更高位影响,还受低位影响。如12113,则百位出现1的情况共有 1200+114=1314 个,也就是高位影响的 12 100 + 低位影响的 113+1 = 114 个。
如果百位上数字为其他数字,则百位上出现1的次数仅由更高位决定。如 12213,则百位出现1的情况为 (12+1)*100=1300。
N=12013 | i 是百位为0,高位是12现在从低位走到高位,即1~12013中,百位出现的1的数如下: |
---|---|
1 | 100~199 |
2 | 1100~1199 |
3 | 2100~1299 |
.. | ……….. |
10 | 9100~9199 |
11 | 10100~10199 |
12 | 11100~11199 |
总共有12100个1出现在百位。结论是位数为0时,只受高位数的影响,为高位数的值 当前位 。其他位的分析类似。
有以上分析思路,写出下面的代码。其中 low 表示低位数字,curr 表示当前考虑位的数字,high 表示高位数字。一个简单的分析,考虑数字 123,则首先考虑个位,则 curr 为 3,低位为 0,高位为 12;然后考虑十位,此时 curr 为 2,低位为 3,高位为 1。其他的数字可以以此类推,实现代码如下:
1 | class Solution { |
45、把数组排成最小的数
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
- 解析:数组内的数变为string之后做拼接,按照字符串排序便可,拼接之后字符串AB>BA,那么有B<A,应该把B排前面。
1 | class Solution { |
做法2:
1 | //写的比较骚气的整型到string |
49、第N个丑数。
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
- 解析:
空间换时间,记录存储下丑数,丑数按递增大小寻找;
当前的丑数已经被找到,那么该丑数之前的数字都是排好序的,
下一个丑数也是2、3、5的倍数,且大于当前丑数;找到每一个2、3、5倍数里最小的作为下一个丑数
排序好的丑数中,前面的丑数是后面选出的丑数的因子之一,用下标跟随前面的因子与2、3、5的乘积>大于当前丑数
丑数定义:1是最小的丑数,只能被2或者3或者5整除的数是丑数;
1 | class Solution { |
51、逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
- 解析:
/*归并排序的改进,把数据分成前后两个数组(递归分到每个数组仅有一个数据项),
合并数组,合并时,出现前面的数组值array[i]大于后面数组值array[j]时;则前面
数组array[i]~array[mid]都是大于array[j]的,count += mid+1 - i
参考剑指Offer,但是感觉剑指Offer归并过程少了一步拷贝过程。
还有就是测试用例输出结果比较大,对每次返回的count mod(1000000007)求余
*/
1 | /*参考《剑指offer》,有两种思路。第一就是暴力求解法,时间复杂度为o(n^2),空间复杂度o(1) |
56、数组中数字出现的次数
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度$O(1)$,空间复杂度$O(n)$.
- 解析:
61、扑克牌顺子
LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…..LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0
- 解析:/先计算0,然后去掉0的部分,遍历这个数组,看相等的时候直接返回0,不连续的时候,累加间隔大小
1 | class Solution { |
62、约斯夫环
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数….这样下去….直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
- : 约瑟夫环两种解法,1循环链表,2是找规律直接算出结果;
1.
1 | class Solution { |
66、乘积数组
给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…A[i-1]A[i+1]…A[n-1]。不能使用除法。
- 解析:分析递归式如图:
剑指的思路:
B[i]的值可以看作下图的矩阵中每行的乘积。
下三角用连乘可以很容求得,上三角,从下向上也是连乘。
因此我们的思路就很清晰了,先算下三角中的连乘,即我们先算出B[i]中的一部分,然后倒过来按上三角中的分布规律,把另一部分也乘进去。
1 | //B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1] |
64、
- 利用短路原理:
1 | class Solution { |
65、不用加减乘除法做加法
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
- 分析:考虑位运算,先相加,不进位,对进位的位置再做相加。
1 | class Solution { |
树
94. 二叉树的非递归前序遍历
直接进出入栈,拿出数据:
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/**
* 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 {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> ans;
stack<TreeNode*> st;
if (!root)
return ans;
st.push(root);
while(!st.empty())
{
TreeNode *top = st.top();
ans.push_back(top->val);
st.pop();
if(top->right)
st.push(top->right);
if(top->left)
st.push(top->left);
}
return ans;
}
};94. 二叉树的非递归中序遍历
首先是根非空入栈,对子树最左边节点一直进栈;
直到左为空,重置根为栈顶节点,取出数据并出栈;
重置根为根的右边节点;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/**
* 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 {
public:
//think:首先是根入栈,左边不为空则进栈,直到左为空出栈,然后右边不为空进栈
vector<int> inorderTraversal(TreeNode* root) {
std::stack<TreeNode *> Stack;
vector<int> ans;
while(root || !Stack.empty())
{
if (root != nullptr)
Stack.push(root);
while(root && root->left != nullptr) //先验证根是否存在;再验证左节点
{
Stack.push(root->left);
root = root->left;
}
root = Stack.top();
ans.push_back(root->val);
Stack.pop();
root = root->right;
}
return ans;
}
};
145. 二叉树的非递归后序遍历
- 跟前序遍历很像:第一次访问根节点,用于确定是否还有属于它的子节点需要进栈,若有就进栈,这是第一次遍历该节点;
不同的是:在第二次遍历时出栈。栈顶pop一个新的节点时,是第二次遍历,第二次遍历时出栈。
- 进两次栈的方法:
- 初始化:根非空进栈两次;
- 循环:栈非空的话:
* 设置当前出栈元素cur,栈顶出栈;(第一次遍历) * 当前出栈元素等于栈顶元素(确保为第一次遍历)的话:若右非空,进栈两次,若左非空,进栈两次;否则的话(第二次遍历):取出当前cur的值
1 | /** |
- 考虑根节点与栈顶节点,
- 循环:若
root
非空或者栈非空:* 若根非空,进栈,设置根为根的左节点; * 一直入栈最左的子节点(作为新根节点),直到根为空; * 设置一个节点记录栈顶节点,若记录节点top有右节点,且最后出栈的节点不等于记录节点或栈顶节点,根节点重置为栈顶节点的右边节点; * 否则栈顶节点无右节点,取值,出栈,重置最后出栈的节点;
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/**
* 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 {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> ans;
if(!root) return ans;
stack<TreeNode*> Stack;
TreeNode *last=nullptr;
while(root || !Stack.empty())
{
if(root)
{
Stack.push(root);
root = root->left;
}else{
//
TreeNode* top = Stack.top();
if (top->right && last!=top->right)
{
root = top->right;
}
else
{
ans.push_back(top->val);
last = top;
Stack.pop();
}
}
}
return ans;
}
};
- 用前序遍历,最后反转数组;
pre-order traversal is root-left-right, and post order is left-right-root. modify the code for pre-order to make it root-right-left, and then reverse the output so that we can get left-right-root1
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/**
* 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 {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> ans;
if(!root) return ans;
stack<TreeNode*> Stack;
TreeNode *tmp;
Stack.push(root);
//前左右;左右前=前右左的反转
while(!Stack.empty())
{
tmp = Stack.top();
ans.push_back(tmp->val);
Stack.pop();
if(tmp->left)
Stack.push(tmp->left);
if(tmp->right)
Stack.push(tmp->right);
}
vector<int> rans(ans.rbegin(), ans.rend());
return rans;
}
};
98. 验证二叉搜索树
主要是根节点大于所有的左子树,小于所有的右子树;怎么样把这个根部的节点信息传递下去。
方法1:递归,除了比较根与左右节点,还要比较左右节点与上一次的根节点值是否满足大小关系;
用min判断传入为(当前根节点的)左节点时,比较右节点是否小于上一次根节点值;
用max判断传入为(当前根节点的)右节点时,比较左节点是否大于上一次根节点值;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/**
* 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 {
public:
//Thinking: 全部的子树上的节点都必须大于或者小于根节点才行
//传入子树递归的时候带上根节点的值,进行比较
bool isValidBST(TreeNode* root)
{
return _isValidBST(root, LONG_MIN, LONG_MAX);
}
bool _isValidBST(TreeNode *root,long min, long max)
{
if(!root) return true;
if(root->val<=min || root->val>=max) return false;
return (_isValidBST(root->left,min,root->val) && _isValidBST(root->right,root->val,max));
}
};
1. 重建二叉树:根据前序和中序遍历的结果重建二叉树。假设输入的遍历结果都不含有重复的数字。
1 | * Definition for binary tree |
2. 二叉树的下一个节点:给定一颗二叉树和其中的一个节点,如何找到中序遍历序列的下一个节点,每个节点左右子节点指针,还有指向父节点的指针。
1 | /* |
26. 树的子结构
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
1 | class Solution { |
27、二叉树的镜像
操作给定的二叉树,将其变换为源二叉树的镜像。
- 思路就是左右都是空,就不用管了;而只有一个子节点为空,另一个不为空,也必须交换才行;
1 | class Solution { |
28、对称的二叉树
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
- 解题思路:特例:树上的节点值相同的时候还需要比较空节点位置是否对称;
对称的例子如:8
6 6
5 7 7 51
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Solution {
public:
bool isSymmetrical(TreeNode* pRoot)
{
return isSymmetrical(pRoot,pRoot);
}
private:
bool isSymmetrical(TreeNode* p1, TreeNode* p2)
{
if(p1==nullptr && p2==nullptr)
return true;
if(p1==nullptr || p2==nullptr)
return false;
if(p1->val != p2->val)
return false;
return isSymmetrical(p1->left,p2->right) && isSymmetrical(p1->right, p2->left);
}
};
32、从下到上打印二叉树
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
- 解:层次遍历用队列存储即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Solution {
public:
vector<int> PrintFromTopToBottom(TreeNode* root) {
vector<int> ans;
if(!root) return ans;
queue<TreeNode*> que;
que.push(root);
while(!que.empty())
{
ans.push_back(que.front()->val);
TreeNode* top=que.front();
que.pop();
if(top->left)
que.push(top->left);
if(top->right)
que.push(top->right);
}
return ans;
}
};
32.2、分行从上到下打印二叉树
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
- 解:需要记录当前层中还没有打印的节点数、下一层节点的数目;
1 | class Solution { |
33、二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
- 解析:最末的位置存储的是根节点,然后左右子树的边界通过循环可以找到,进一步判断右子树是否都小于根节点,如果满足,下一步看左右子树若存在,左右子树是否递归满足。
1 | class Solution { |
34、二叉树中和为某一值的路径
输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
- 解析:
方法1. 递归、判断成功的时候把成功的路径压入结果ans;但是每次递归path需要重新拷贝使用;
方法2.
1 | class Solution { |
36、二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
- 递归,根节点进入,然后向左递归,出来之后,若有右节点,向右走,走到none在指向root,root在指向它;右子树也一样这样。
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:
TreeNode* Convert(TreeNode* pRootOfTree)
{
if(pRootOfTree==nullptr) return nullptr;
pRootOfTree=ConvertNode(pRootOfTree);
while(pRootOfTree->left) pRootOfTree=pRootOfTree->left;
return pRootOfTree;
}
private:
//返回子树的根节点
TreeNode* ConvertNode(TreeNode* pRootOfTree)
{
if(pRootOfTree==nullptr) return nullptr;
if(pRootOfTree->left!=nullptr)
{
TreeNode* left = ConvertNode(pRootOfTree->left);
//左子树的根节点往右走、它的下一个节点是当前的根节点
while(left->right) left=left->right;
left->right=pRootOfTree;
pRootOfTree->left=left;
}
if(pRootOfTree->right!=nullptr)
{
TreeNode* right = ConvertNode(pRootOfTree->right);
//右子树的根节点往左走、它的上一个节点是目前的根节点;
while(right->left) right=right->left;
right->left=pRootOfTree;
pRootOfTree->right=right;
}
return pRootOfTree;
}
};
37、序列化二叉树
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
- 不太熟悉char的这些操作,看别人的答案,非得搞指针传入的默认函数。
1 | class Solution { |
54、请找出其中的第k小的结点
给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
- //思路:二叉搜索树按照中序遍历的顺序打印出来正好就是排序好的顺序。
// 所以,按照中序遍历顺序找到第k个结点就是结果。
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
25class Solution {
public:
TreeNode* KthNode(TreeNode* pRoot, int k)
{
if(pRoot== nullptr || k==0)
return nullptr;
return findKthNode(pRoot, k);
}
private:
TreeNode* findKthNode(TreeNode* pRoot, int &k)
{
TreeNode* target = nullptr;
if(pRoot->left!=nullptr)
target = findKthNode(pRoot->left, k);
if(target == nullptr)
{
if(k==1)
target = pRoot;
k--;
}
if(target==nullptr && pRoot->right!=nullptr)
target = findKthNode(pRoot->right, k);
return target;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Solution {
public:
int index = 0;
TreeNode* KthNode(TreeNode* pRoot, int k)
{
if(pRoot!=nullptr)
{
TreeNode* node = KthNode(pRoot->left, k);
if(node!=nullptr)
return node;
index++; //中序遍历操作处
if(index==k)
return pRoot;
node = KthNode(pRoot->right, k);
if(node!=nullptr)
return node;
}
return nullptr;
}
};
55、输入一棵二叉树,求该树的深度。
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
1 | class Solution { |
55.2、输入一棵二叉树,判断该二叉树是否是平衡二叉树。
如果某二叉树中任意节点的左、右子树的深度相差不超过1,那么他就是一颗平衡二叉树。
- 方法1:求节点的左右子树的深度,然后判断是否相差不大于1;
方法2:由于方法1中重复的遍历了某些节点,因此我们选择后序遍历每一个节点,这样的话在遍历一个节点之前,
已经遍历了它的左、右子树;遍历的同时记录节点深度;
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
25class Solution {
public:
/*
方法1:求节点的左右子树的深度,然后判断是否相差不大于1;
*/
bool IsBalanced_Solution(TreeNode* pRoot) {
if(pRoot==nullptr)
return true;
int left = TreeDepth(pRoot->left);
int right = TreeDepth(pRoot->right);
int diff = left - right;
if(diff<-1 || diff>1)
return false;
return IsBalanced_Solution(pRoot->left) && IsBalanced_Solution(pRoot->right);
}
private:
int TreeDepth(TreeNode* root)
{
if(root==nullptr)
return 0;
int left = TreeDepth(root->left);
int right = TreeDepth(root->right);
return 1+max(left,right);
}
};
2.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
32class Solution {
public:
/*
方法1:求节点的左右子树的深度,然后判断是否相差不大于1;
方法2:由于方法1中重复的遍历了某些节点,因此我们选择后序遍历每一个节点,这样的话在遍历一个节点之前,
已经遍历了它的左、右子树;遍历的同时记录节点深度;
*/
bool IsBalanced_Solution(TreeNode* pRoot) {
int depth = 0;
return IsBalanced(pRoot, depth);
}
private:
bool IsBalanced(TreeNode* pRoot, int &depth)
{
if(pRoot == nullptr)
{
depth=0;
return true;
}
int left, right;
if(IsBalanced(pRoot->left, left) && IsBalanced(pRoot->right, right))
{
int diff = left - right;
if(diff <= 1 && diff >=-1)
{
depth = 1 + (left>right ?left :right);
return true;
}
}
return false;
}
};
按之字形打印树
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
- 1.队列+数组翻转来实现,偶数的时候翻转数组再添加;2.双栈实现
1 | class Solution { |
112. Path Sum
问路径上是否有简单路径和等于给定数值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/**
* 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 {
public:
bool hasPathSum(TreeNode* root, int sum) {
if(root)
{
//叶节点减为0才返回true
if (!(sum-root->val) && !root->left && !root->right)
return true;
return (hasPathSum(root->left, sum-root->val) || hasPathSum(root->right, sum-root->val));
}
else
{
return 0;
}
}
};
113. Path Sum II
上一题的基础上,再加上返回所有满足这个要求的路径
- 直接类似上题解法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Solution {
public:
vector<vector<int>> pathSum(TreeNode* root, int sum) {
vector<vector<int> > paths;
vector<int> path;
findPaths(root, sum, path, paths);
return paths;
}
private:
void findPaths(TreeNode* node, int sum, vector<int>& path, vector<vector<int> >& paths) {
if (!node) return;
path.push_back(node -> val);
if (!(node -> left) && !(node -> right) && sum == node -> val)
paths.push_back(path);
findPaths(node -> left, sum - node -> val, path, paths);
findPaths(node -> right, sum - node -> val, path, paths);
path.pop_back();
}
};
递归
1. 斐波那契数列:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。
1 | class Solution { |
2. 跳台阶:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
1 | class Solution { |
题目描述:我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
1 | class Solution { |
查找、排序
旋转数组的最小数字
哈希表的主要优点是在$O(1)$时间内查找某一元素,是效率最高的查找方式;但是缺点是需要额外的空间来实现哈希表。
旋转数组的最小数字:
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
1 | class Solution { |
统计一个数字在排序数组中出现的次数。
- 因为data中都是整数,所以可以稍微变一下,不是搜索k的两个位置,而是搜索k-0.5和k+0.5 这两个数应该插入的位置,然后相减即可。只是取的距离k最近的值,由于都是整数,原数组中可能存在k-1或者k+1;而k+0.5和k-0.5之间保证只有数字k。
1 |
|
57、和为s的一对数
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
- 解析: //假设:若b>a,且存在,
//a + b = s;
//(a - m ) + (b + m) = s
//则:(a- m)(b + m)=ab - (b-a)m - m*m < ab;说明外层的乘积更小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
28class Solution {
public:
//假设:若b>a,且存在,
//a + b = s;
//(a - m ) + (b + m) = s
//则:(a- m)(b + m)=ab - (b-a)m - m*m < ab;说明外层的乘积更小
vector<int> FindNumbersWithSum(vector<int> array,int sum) {
if(array.empty()) return vector<int>();
vector<int> ans;
vector<int>::iterator first=array.begin();
vector<int>::iterator last=array.end()-1;
while(first<last)
{
int fnum = *first, lnum= *last;
if(fnum+lnum == sum)
{
ans.push_back(fnum);
ans.push_back(lnum);
break;
}
else if(fnum + lnum < sum)
first++;
else
last--;
}
return ans;
}
};
2.和为连续整数的序列
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
- 解析:考虑用small和big分别表示序列的最小值和最大值,初始化,small=1与big=2,如果序列和大于sum,那么去掉最小的small也就是说更新small++;小于序列的话,增大big,同时把small到big的和curSum更新;
1 | class Solution { |
动态规划
剪绳子
- 剪绳子:长度为$n$的绳子,剪成$m$段,各段长度记为$k[0],…,k[m]$,求各段最大乘积多少?$(n>1,m>1)$
- dp:即$f(n)$为把长度$n$的绳子剪成若干段($[1,n-1]$)的最大乘积。
- 用子问题定义原问题:$f(n)= \max_{0<i<n}{f(i) * f(n-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
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
using namespace std;
int maxProductAfterCutting(int length)
{
if(length<2)
return 0;
if(length == 2)
return 1;
if(length == 3)
return 2;
//products[i]代表f[i]
int *products = new int[length+1] ;
products[0] = 0; // dumps elems
products[1] = 1;
products[2] = 2;
products[3] = 3;
int max = 0;
//遍历长度i
for(int i=4; i<=length;++i)
{
max = 0;
//切割大小为j,只计算前一半即可;
for(int j=1; j<=i/2; ++j)
{
//根据上述公式计算:取最大那个
int product = products[j] * products[i-j];
if(max < product) max = product;
}
products[i] = max;
}
max = products[length];
delete[] products;
return max;
}
int main() {
cout << maxProductAfterCutting(9) << endl;
}
27
```c++
* greedy:
```c++
int maxProductAfterCuttingGreedy(int length)
{
if(length<2)
return 0;
if(length == 2)
return 1;
if(length == 3)
return 2;
//尽可能去剪长为3的绳子
int timesOf3 = length / 3;
// 当绳子为4例外,剪成2*2
if (length - timesOf3*3==1)
timesOf3-=1;
int timesOf2 = (length - timesOf3 * 3) /2;
return (int)(pow(3, timesOf3) * (int)(pow(2, timesOf2)));
}
位运算
1、机器数
一个数在计算机中的二进制表示形式, 叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1.
比如,十进制中的数 +3 ,计算机字长为8位,转换成二进制就是00000011。如果是 -3 ,就是 10000011 。
那么,这里的 00000011 和 10000011 就是机器数。
2、真值
因为第一位是符号位,所以机器数的形式值就不等于真正的数值。例如上面的有符号数 10000011,其最高位1代表负,其真正数值是 -3 而不是形式值131(10000011转换成十进制等于131)。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。
例:1
20000 0001的真值 = +000 0001 = +1,
1000 0001的真值 = –000 0001 = –1。
二. 原码, 反码, 补码的基础概念和计算方法.
在探求为何机器要使用补码之前, 让我们先了解原码, 反码和补码的概念.对于一个数, 计算机要使用一定的编码方式进行存储. 原码, 反码, 补码是机器存储一个具体数字的编码方式.
- 原码
原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是:1
2
3[+1]原 = 0000 0001
[-1]原 = 1000 00011
2
3[1111 1111 , 0111 1111]
即
[-127 , 127]
原码是人脑最容易理解和计算的表示方式.
- 反码
反码的表示方法是:
正数的反码是其本身
负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.1
2
3[+1] = [00000001]原 = [00000001]反
[-1] = [10000001]原 = [11111110]反
可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算.
- 补码
补码的表示方法是:
正数的补码就是其本身
负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)
1 | [+1] = [00000001]原 = [00000001]反 = [00000001]补 |
对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值.
转:
作者:张子秋
出处:http://www.cnblogs.com/zhangziqiu/
(c语言中移位运算只能用于整数,整数A左移1位得到的结果为A*2,右移1位为A/2取整)。
- 左移运算符$m\operatorname{<<}n$表示把$m$左移$n$位,最左边的$n$位被丢弃,同时在最右边补上$n$个0:
- 右移运算符$m>>n$表示把$m$右移$n$位,最右边的$n$位被丢弃:
1. 如果数字是一个无符号数值,则用0填补最左边的$n$位; 2. 如果数字是一个有符号数值,则用数值的符号位填补最左边$n$位。也就是说,正数在左边补0,负数在左边补1:
其中第二个的符号位是1。
常用m>>1
表示m/2
, m&1
表示m%2
。
- 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
1 | class Solution { |
56、出现一次的数字
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
- 解析: //方法:考虑异或运算,两个想等的数异或运算之后等于0,整个数组做异或运算,其中两个
//数字不一样的异或运算之后某一位肯定为1,按照这一位为1对数组做划分,使得两个子数组
//部分只各有一个出现一次的数字
//两个相同数字异或=0,一个数和0异或还是它本身。
1 | class Solution { |
问题:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
- 如图:$a_n=2(a_n-1)$;2.
1
2
3
4
5
6
7
8class Solution {
public:
int jumpFloorII(int number) {
if(number ==1)
return 1;
return 2*jumpFloorII(number-1);
}
};
链接:https://www.nowcoder.com/questionTerminal/22243d016f6b47f2a6928b4313c85387?f=discussion
来源:牛客网
其实是隔板问题,假设n个台阶,有n-1个空隙,可以用0~n-1个隔板分割,c(n-1,0)+c(n-1,1)+…+c(n-1,n-1)=2^(n-1),其中c表示组合。
有人用移位1<<—number,这是最快的。直接连续乘以2不会慢多少,编译器会自动优化。不过移位还是最有启发的!
1 | class Solution { |
回溯
前言
回溯法适合多个步骤组成的问题,每个步骤有多个选项,形成一颗树状。
在叶节点的状态不满足条件,回溯到上一个节点尝试其他的选项。如果再不满足,继续回溯。
1. 矩阵中的路径
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串”bcced”的路径,但是矩阵中不包含”abcb”路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
1 | class Solution { |
2. 机器人的运动范围
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
1 | class Solution { |
字符串、链表
前言
c/c++把常量字符串放到单独的一个内存区域。当几个指针赋值给相同的常量字符串时,他们实际上会指向相同的内存地址:
1 | int main() |
第一个不同是因为两个不同的数组地址;第二个相同是因为常量字符串在内存中只有一个拷贝,他们都指向同一地址。
5. 替换空格
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
- 解析:先计算增大串长后的长
newStrLength
,用指针2指向该位置,同时指针1指向未增长时串中字符的结尾'\0'
,从后遍历复制指针1、2上的字符。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:
void replaceSpace(char *str,int length) {
if(str==nullptr || length<=0)
return;
int strLength=0, space=0;
//先求替换后的字符串大小
while(str[strLength]!='\0')
{
if(str[strLength]==' ')
++space;
++strLength;
}
//strLength指向终止符处
//增大后的串长
int newStrLength = strLength+2*space;
if (newStrLength > length)//length为该串总容量
return;
while(strLength>=0 && newStrLength>strLength)
{
if(str[strLength]==' ')
{
str[newStrLength--] = '0';
str[newStrLength--] = '2';
str[newStrLength--] = '%';
}
else
{
str[newStrLength--]=str[strLength];
}
--strLength;
}
}
};
19、正则表达式
请实现一个函数用来匹配包括’.’和’*‘的正则表达式。模式中的字符’.’表示任意一个字符,而’*‘表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串”aaa”与模式”a.a”和”abaca”匹配,但是与”aa.a”和”ab*a”均不匹配
- 解:当遇到
*
的时候可以当做前面的字符(一个字符)出现了0次,也就是忽略*
直接前移2步,也可以当做出现n次,在原处待着不动,让str前移n步去比较;退出条件是看str是否为空且模式也为空就是匹配上,否则没有匹配上,递归的调用看各种情况下返回的是否有一个满足条件。
1 | class Solution { |
38、字符串的排列
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
- 解析:第一个字符固定为i,剩下其他字符的全排列在后面;i的取值是整个字符串的取值,因此,每次交换头字符,还有后面字符串中的下一个字符;递归结束条件是该字符串已经找到;
1 | class Solution { |
链表
前言
- 内存分配不是在创建链表时一次完成的,而是每次添加一个节点是分配内存;因此没有闲置的内存,链表的空间效率比数组高。
指针的指针:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// Example program
using namespace std;
int main()
{
int i=1;
int *pi = &i;
int **ppi = π
cout<<"*pi :" << *pi << endl;
cout<<"&pi :" << &pi << endl;
cout <<"*ppi :" << *ppi<<endl;
return 0;
}
*pi :1
&pi :0x70aa208f64e8
*ppi :0x70aa208f64e4链表的尾部添加节点和删除值为某数的节点如下:
1 | // Example program |
6. 输入一个链表,按链表值从尾到头的顺序返回一个ArrayList
。
1 | class Solution { |
18. 删除链表中重复的元素
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
1 | class Solution { |
17. 输入一个链表,输出该链表中倒数第k个结点。
- 双指针开始走,第一个走到k-1步时,第二个开始从根节点走。判断链长是否有k,没有解返回。
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
29class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
if(!pListHead || k==0) return nullptr;
ListNode* preListNode=nullptr;
ListNode* curListNode=pListHead;
//第一个指针走了k-1步,加上本身是一个位移,总共k位移,第二个指针开始从头结点走1步
for(unsigned i=1;i<k;++i)
{
if(curListNode->next!=nullptr)
{
curListNode=curListNode->next;
}
else //链表长度不足
{
return nullptr;
}
}
//开始走
preListNode = pListHead;
//下一个节点指针非空
while(curListNode->next!=nullptr)
{
curListNode=curListNode->next;
preListNode=preListNode->next;
}
return preListNode;
}
};
23.链表中环的入口节点
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
解:1. 确定链表是否有环;(设置一个走得快的指针是否能追上走得慢的指针;)
2. 环的大小是多少;
3. 快指针先走环的大小步数,慢指针再走,必然在环的入口处相遇。
1 | class Solution { |
35、复杂链表的复制
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
- 解析:重复各个节点,然后关联新的重复节点的random,再然后重链接取出偶数链接的链表。
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
65class Solution {
public:
//复制原始链表的任一节点N并创建新节点N',再把N'链接到N的后边
void CloneNodes(RandomListNode* pHead)
{
RandomListNode* pNode=pHead;
while(pNode!=NULL)
{
RandomListNode* pCloned=new RandomListNode(0);
pCloned->label=pNode->label;
pCloned->next=pNode->next;
pCloned->random=NULL;
pNode->next=pCloned;
pNode=pCloned->next;
}
}
//如果原始链表上的节点N的random指向S,则对应的复制节点N'的random指向S的下一个节点S'
void ConnectRandomNodes(RandomListNode* pHead)
{
RandomListNode* pNode=pHead;
while(pNode!=NULL)
{
RandomListNode* pCloned=pNode->next;
if(pNode->random!=NULL)
pCloned->random=pNode->random->next;
pNode=pCloned->next;
}
}
//把得到的链表拆成两个链表,奇数位置上的结点组成原始链表,偶数位置上的结点组成复制出来的链表
RandomListNode* ReConnectNodes(RandomListNode* pHead)
{
RandomListNode* pNode=pHead;
RandomListNode* pClonedHead=NULL;
RandomListNode* pClonedNode=NULL;
//初始化
if(pNode!=NULL)
{
pClonedHead=pClonedNode=pNode->next;
pNode->next=pClonedNode->next;
pNode=pNode->next;
}
//循环
while(pNode!=NULL)
{
pClonedNode->next=pNode->next;
pClonedNode=pClonedNode->next;
pNode->next=pClonedNode->next;
pNode=pNode->next;
}
return pClonedHead;
}
//三步合一
RandomListNode* Clone(RandomListNode* pHead)
{
CloneNodes(pHead);
ConnectRandomNodes(pHead);
return ReConnectNodes(pHead);
}
};
52、两个链表的第一个公共节点
输入两个链表,找出它们的第一个公共结点。
- 解: 方法1:对链表1中每一个节点,都遍历一遍链表2,复杂度为O(mn);m\n分别为两个链表的长度;
方法2:若中间某一个节点相同,之后剩下的节点都是一样的地址存在,因此结尾处都一样,
设置栈结构,从尾部排除,遇到不相等的元素时,之前的那个元素便是第一个相同的元素点,空间换时间
的O(m+N)空间换取时间为O(m+n);
方法3:先遍历两个链表得到最长链表的长度s1与短链表的长度s2,在最长链表上先移动s1-s2,再同时移动
链表1,2,且不断比较直到相等;
方法3的原因是:链表$A$长度是$LA$,链表$B$长度是$LB$,他们的公共部分长度是$LC$,那么不妨设$LA$长度更长,那么有:$ LA>LB \ge LC$,那么$A$与$B$的公共部分至少在$A$后面的长度还有$B$这么长的时候才有可能。
1 | class Solution { |
50、第一个只出现一次的字符
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).
- :方法1:暴力求解,对当前每一个字符,搜索后面的真个字符串,复杂度是O(n^2);
方法2:哈希表:自建一个简单哈希表;
可以把哈希表设为256,因为char是8bit的类型,总共只有256个字符,
可以设置哈希表为负,第一次插入后为正,再次插入同样的字符,设为负,下次只有找出数组非负的位置便是对应的第一次出现的字符
1 | class Solution { |
58、反转单词顺序字符串
牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?
- 先反转整个串,再反转每一个单词;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class Solution {
public:
string ReverseSentence(string str) {
if(str.empty()) return str;
int begin=0,end=0;
reverse(str.begin(),str.end());
int size = str.size();
while(begin<size)
{
//起点为空,重置游标
while(begin<size && str[begin]==' ') begin++;
end=begin;
//终点不为空,一直往前走到头
while(end<size && str[end]!=' ') end++;
reverse(str.begin()+begin, str.begin()+end);
//反转之后,起点的顺序是重点的顺序
begin = end;
}
return str;
}
};
2。左旋转字符串
字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!
- 先翻转这个串的前后两部分,然后再全部一次性翻转
1
2
3
4
5
6
7
8
9
10
11class Solution {
public:
string LeftRotateString(string str, int n) {
if(str.empty() || n<=0) return str;
reverse(str.begin(),str.begin()+n);
reverse(str.begin()+n,str.end());
reverse(str.begin(),str.end());
return str;
}
};
67、将一个字符串转换成一个整数
将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。
1 | class Solution { |
50、字符流中第一个出现的字符
方法1:用常规方法来解:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class Solution
{
public:
//Insert one char from stringstream
string str;
//全部初始化为0
int hash[256]= {0};
void Insert(char ch)
{
str+=ch;
hash[ch]++;
}
//return the first appearence once char in current stringstream
char FirstAppearingOnce()
{
int size = str.size();
for(int i=0; i<size; ++i)
{
if(hash[str[i]]==1)
return str[i];
}
return '#';
}
};
方法2:用stl来解: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:
//Insert one char from stringstream
string str;
map<char,int> m; //用map来记录字符出现的次数
void Insert(char ch)
{
str += ch;
m[ch]++;
}
//return the first appearence once char in current stringstream
char FirstAppearingOnce()
{
for(auto it : str)
{
if(m[it] == 1)
{
return it;
}
}
return '#';
}
};
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
例如,字符串”+100”,”5e2”,”-123”,”3.1416”和”-1E-16”都表示数值。 但是”12e”,”1a3.14”,”1.2.3”,”+-5”和”12e+4.3”都不是。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
26class Solution {
public:
bool isNumeric(char* str) {
// 标记符号、小数点、e是否出现过
bool sign = false, decimal = false, hasE = false;
for (int i = 0; i < strlen(str); i++) {
if (str[i] == 'e' || str[i] == 'E') {
if (i == strlen(str)-1) return false; // e后面一定要接数字
if (hasE) return false; // 不能同时存在两个e
hasE = true;
} else if (str[i] == '+' || str[i] == '-') {
// 第二次出现+-符号,则必须紧接在e之后
if (sign && str[i-1] != 'e' && str[i-1] != 'E') return false;
// 第一次出现+-符号,且不是在字符串开头,则也必须紧接在e之后
if (!sign && i > 0 && str[i-1] != 'e' && str[i-1] != 'E') return false;
sign = true;
} else if (str[i] == '.') {
// e后面不能接小数点,小数点不能出现两次
if (hasE || decimal) return false;
decimal = true;
} else if (str[i] < '0' || str[i] > '9') // 不合法字符
return false;
}
return true;
}
};