思维之海

——在云端,寻找我的星匙。

剑指Offer-算例手册

《剑指Offer》算法书的C++实现。

训练OJ地址:https://www.nowcoder.com/ta/coding-interviews

按类型、难度综合不稳定排序。浏览器内Crtl+F可以启用页面查找(Firefox)。

牛客网的在线编程太烦了,完全不能输出中间结果,全靠眼力orz

用两个栈实现队列 栈混洗

题目描述

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

Code $O(n^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
class 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;
};

斐波那契数列 DP

题目描述

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。

n<=39

Code $O(n)$

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
long long Fibonacci(unsigned n) {
if(n < 2) return n; //注意边界情况
long long fib1 = 0, fib2 = 1;
for(int i = 2; i <=n; ++i){
long long tmp = fib1 + fib2;
fib1 = fib2;
fib2 = tmp;
}
return fib2;
}
};

跳台阶 DP

题目描述

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

Code $O(n)$

注意常数基稍有不同。最好先写出递推式

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
long long jumpFloor(int n) {
if(n < 2) return n; //注意边界情况
long long fib1 = 1, fib2 = 1;
for(int i = 2; i <=n; ++i){
long long tmp = fib1 + fib2;
fib1 = fib2;
fib2 = tmp;
}
return fib2;
}
};

变态跳台阶 DP

题目描述

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

Code $O(n)$

类似地,只不过多一层求和DP。还是要注意递归基。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
long long jumpFloorII(int n) {
if(n < 2) return n;
long long f0 = 1, f1 = 1;
long long sum = f0 + f1;
for(int i = 2; i <= n; ++i){
long long f2 = sum;
f0 = f1; f1 = f2;
sum += f1;
}
return f1;
}
};

Code $O(logn)$

数列求和。相当于随便跳,每个台阶的状态分为被跳过没被跳过。自然就是指数了。

1
2
3
4
5
6
class Solution {
public:
long long jumpFloorII(int n) {
return pow(2, n - 1);
}
};

矩形覆盖 DP

题目描述

我们可以用2 1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2 1的小矩形无重叠地覆盖一个2 * n的大矩形,总共有多少种方法?

Code $O(n)$

还是Fibonacci。注意一层和两层是有重复的,因此每层实际只加一种覆盖方法。

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int rectCover(int n) {
if(n < 2) return n;
long long f0 = 1, f1 = 1;
for(int i = 2; i <= n; ++i){
long long f2 = f1 + f0;
f0 = f1; f1 = f2;
}
return f1;
}
};

替换空格 向量插位

题目描述

请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

Code $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
class Solution {
public:
void replaceSpace(char *str, int length) {
if(str == nullptr || length < 0) return ; // check
int orgin = 0, extend = 0;
while(str[orgin] != '\0'){
if(str[orgin] == ' ') extend += 2;
orgin++; extend++;
}
if(extend > length) return ; // overflow
while(orgin >= 0){
if(str[orgin] == ' '){
str[extend--] = '0';
str[extend--] = '2';
str[extend--] = '%';
orgin--;
}
else{
str[extend--] = str[orgin--]; // '\0' is contained
}
}
}
};

旋转数组中的最小数字 二分法

题目描述

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

Code $O(logn)$

  • 平凡情况:没有旋转

  • 普遍情况:中间的值要么高于左端点,要么低于右端点。

  • 特殊情况:有大量相等元素(用顺序查找过滤,也可以考虑unique向量唯一化操作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
int n = rotateArray.size();
if(!n) return 0;
int le = 0, ri = n - 1;
if(rotateArray[le] < rotateArray[ri]) return rotateArray[le]; //平凡特判
while(le < ri - 1){ //缩至距离为1(相邻)
int mi = (le + ri) >> 1;
if(rotateArray[le] < rotateArray[mi]) le = mi;
else if(rotateArray[le] == rotateArray[mi]){ //相等特判
while(rotateArray[le] == rotateArray[le + 1]) le++;
while(rotateArray[ri] == rotateArray[ri - 1]) ri--;
}
else ri = mi;
}
return rotateArray[ri]; //返回右指针
}
};

调整数组顺序使奇数位于偶数前面 二元稳定排序

题目描述

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变

Code $O(n^2)$ 起泡/插入

因为要保序,双端指针不可行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
void reOrderArray(vector<int> &array) {
unsigned len = array.size();
for(unsigned i = 0; i < len; ++i){
unsigned odd = i;
for(; !(array[odd] & 0x1) && odd < len; ++odd); //定位到第一个奇数
if(odd == len) return; //定位失败
for(unsigned j = odd; j >= i + 1; --j){ //反向起泡
int tmp = array[j-1];
array[j-1] = array[j];
array[j] = tmp;
}
}
}
};

Code $O(n)+O(n)$ 辅助数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
void reOrderArray(vector<int> &array) {
unsigned len = array.size();
vector<int> odd, even;
for(unsigned i = 0; i < len; ++i){
if(array[i] & 0x1) odd.push_back(array[i]);
else even.push_back(array[i]);
}
array.clear();
array.insert(array.end(), odd.begin(), odd.end()); //奇
array.insert(array.end(), even.begin(), even.end()); //偶
}
};

Code 通用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bool is_odd(const int x){  //自定义函数,将选择的量排在前面
return x & 0x1;
}
class Solution {
public:
void reOrderArray(vector<int> &array, bool (*func)(int) = is_odd) {
unsigned len = array.size();
vector<int> odd, even;
for(unsigned i = 0; i < len; ++i){
if(is_odd(array[i])) odd.push_back(array[i]);
else even.push_back(array[i]);
}
array.clear();
array.insert(array.end(), odd.begin(), odd.end());
array.insert(array.end(), even.begin(), even.end());
}
};

数组中重复的数字 辅助数组

题目描述

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

Code $O(n)+O(n)$ 平凡哈希

注释信息有毒。删掉了。不能信。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
bool duplicate(int numbers[], int length, int* duplication) {
if(length <= 0) return false; //空
bool cache[length];
for(int i = 0 ; i < length; ++i) cache[i] = false;
for(int i = 0 ; i < length; ++i){
if(numbers[i] > length-1 || numbers[i]<=0) return false; //数据无效
if(cache[numbers[i]] == false){
cache[numbers[i]] = true;
}else{
*duplication = numbers[i];
return true;
}
}
return false;
}
};

Code $O(n)+O(1)$ 归位法

对每个当前位置上数字归位numbers[i] == i),如果不匹配直到交换到匹配为止。如果位置被占用,则返回true

  • 每一步总是有至少一个数字被归位,因此是$O(n)$。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void swap(int& a, int& b){
a = a + b;
b = a - b;
a = a - b;
}
class Solution {
public:
bool duplicate(int numbers[], int length, int* duplication) {
if(length <= 0) return false; //空
for(int i = 0 ; i < length; ++i){
if(numbers[i] > length-1 || numbers[i]<=0) return false; //数据无效
while(numbers[i] != i){ //归位?
if(numbers[i] == numbers[numbers[i]]){
*duplication = numbers[i];
return true;
}
else swap(numbers[i], numbers[numbers[i]]); //交换,将数字归位
}
}
return false;
}
};

Code $O(n)+O(1)$ 量子存储法

作者:豆豆酱

利用符号位来标记重复出现的元素。(只适用于重复出现两次的情况)

相当于将负数部分做了一个映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def find(lists):
for i in range(len(lists)):
lists[lists[i]-1] *= -1

print(lists)
for j in range(len(lists)-2):
if lists[j] > 0:
print(j+1)

find([1,2,3,2,3,4])
# ------------------------------------
# [-1, 2, 3, -2, 3, 4]
# 2
# 3
# [Finished in 2.8s]

数字在排序数组中出现的次数 二分法

题目描述

统计一个数字在排序数组中出现的次数。

Code $O(n)$

水题。

1
2
3
4
5
6
7
8
9
10
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
int ans = 0;
for(int i = 0; i < data.size(); ++i){
if(data[i] == k ) ans++;
}
return ans;
}
};

Code 二分 $O(logn)$

我zz辣。orz

找到lower_boundupper_bound

二分法相当于按照条件的区间上元素的分类器。注意边界情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
int le = -1, ri = data.size(); //边界情况处理,令左端点为-1,这个不太直观
while(le < ri - 1){ //[le, ri)区间缩到1
int mid = (le + ri) >> 1;
if(data[mid] >= k) ri = mid; //保证右边都是大于等于k的数
else le = mid;
}

int hi_le = -1, hi_ri = data.size();
while(hi_le < hi_ri - 1){
int mid = (hi_le + hi_ri) >> 1;
if(data[mid] > k) hi_ri = mid; //保证右边都是大于k的数
else hi_le = mid;
}

return hi_ri - ri;
}
};

数组中出现次数超过一半的数字 众数+减治

题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

Code 减治 $O(n)$

众数的通常减治解法。

充分条件:如果有众数,则大区间内的众数,与减治后区间内的众数一致。(不能反推)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> number) {
int major;
for(int count = 0, i = 0; i < number.size(); i++)
if(0 == count){
major = number[i]; count = 1;
}
else{
major == number[i] ? count++ : count--;
}
int count = 0; // check validation
for(int i = 0; i < number.size(); ++i){
if(major == number[i]) count++;
}
if(count <= number.size()>>1) major = 0;
return major;
}
};

Code 排序 $O(nlogn)$

数组排序后,如果符合条件的数存在,则一定是数组中间那个数

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> number) {
sort(number.begin(), number.end());
int major = number[number.size() >> 1];
int count = 0;
for(int i = 0; i < number.size(); ++i){ //check
if(major == number[i]) count++;
}
if(count <= number.size() >> 1) return 0;
return major;
}
};

Code Partition $O(n)\longrightarrow O(n^2)$

作者:rs勿忘初心

基于Partition函数的O(n)算法

我们回到题目本身分析,就会发现前面的思路并没有考虑到数组的特性:数组中有一个数字出现的次数超过了数组长度的一半。如果我把这个数组排序,那么排序之后位于数组中间的数字一定就是那个出现次数超过数组一半的数字。也就是说,这个数字就是统计学上的中位数,即长度为n的数组中第n/2的数字。

我们有成熟的O(n)的算法得到数组中任意第K大的数字

这种算法是受快速排序算法的启发。在随机快速排序算法中,我们现在数组中随机选择一个数字,然后调整数组中数字的顺序,使得比选中的数字小的数字都排在它的左边,比选中的数字大的数字都排在它的右边。如果这个选中的数字的下标刚好是n/2,那么这个数字就是数组的中位数。如果它的下标大于n/2,那么中位数应该位于它的左边,我们可以接着在它的左边部分的数组中查找。如果它的下标小于n/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
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
int length=numbers.size();
if(numbers.empty()||length<0)
return 0;
int middle=length>>1;
int start=0;
int end=length-1;
//int index=partition(numbers,length,start,end);
int index=partition(numbers,start,end);
while(index!=middle)
{
if(index>middle)
{
end=index-1;
index=partition(numbers,start,end);
}
else
{
start=index+1;
index=partition(numbers,start,end);
}
}
int result=numbers[middle]; //这里的只是得到了第middle=n/2大的数字,但总个体是否超过一段还需要判断
if(!CheckMoreThanHAlf(numbers,length,result)) //此时需要检查result的值的个数是否大于整个数组的一半
return 0;
return result;
}


int partition(vector<int> input,int low,int high)
{
int pivotkey=input[low]; //pivot:枢纽
//这里的pivotkey也可以是[low,high]区间一个随机数,也可以是三数取中,九数取中,详情见<<大话数据结构>>
//int pivotkey=input[RandInRange(start,end)];
while(low<high)
{
while(low<high&&pivotkey<input[high])
high--;
swap(&input[low],&input[high]);
while(low<high&&pivotkey>=input[low])
low++;
swap(&input[low],&input[high]);
}
return low;
}

/*这个partition是优化了不必要的交换,将swap用赋值替换
int partition(vector<int> &input, int begin, int end)
{
int low = begin;
int high = end;
int pivot = input[low];
while (low < high)
{
while (low < high&&pivot <= input[high])
high--;
input[low] = input[high]; //优化不必要的替换
while (low < high&&pivot >= input[low])
low++;
input[high] = input[low]; //优化不必要的替换
}
input[low] = pivot;
return low;
}
*/

bool CheckMoreThanHAlf(vector<int> numbers,int length,int result) //检查result的值的个数是否大于整个数组的一半
{
int times=0;
for(int i=0;i<length;++i)
{
if(numbers[i]==result)
times++;
}
bool isMoreThanHalf=true;
if(times*2<=length)
isMoreThanHalf=false;
return isMoreThanHalf;
}

void swap(int *a,int *b) //交换两个数
{
int temp;
temp=*a;
*a=*b;
*b=temp;
}
/*下面的partition是剑指offer上的实现,考虑的很详情,但是没有上面的好理解
int RandInRange(int a,int b) //产生一个[a,b]范围内的随机整数
{
int c;
c=a+rand()%(b-a+1);
return c;
}

int partition(vector<int>& data,int length,int start,int end) //partition函数是为了找出数组中第K大的数字
{
if(data.empty()||length<0||start<0||end>=length)
return 0; //throw new std::exception("Invakid Parameters");
int index=RandInRange(start,end);
swap(&data[index],&data[end]);
int small=start-1;
for(index=start;index<end;++index)
{
if(data[index]<data[end])
{
++small;
if(small!=index)
swap(&data[index],&data[small]);
}
}
++small;
swap(&data[small],&data[end]);

return small;
}
*/
};

*滑动窗口的最大值 队列最值

题目描述

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{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]}。

Code $O(nk)$

扫描法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int max(int a,int b){
return a > b ? a : b;
}
class Solution {
public:
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
vector<int> ans;
if(num.size() ==0 || size < 0 || size > num.size()) return ans;
for(unsigned i = 0; i + size - 1 < num.size(); ++i){ //注意循环条件
int maxi = INT_MIN;
for(unsigned j = i; j < i + size; ++j)
maxi = max(maxi, num[j]);
ans.push_back(maxi);
}
return ans;
}
};

*Code $O(n)$ 坐标队列/双端单调队列

维护一个最大值可能坐标的队列。

有一个非常变态的错误。。。如代码中注释所示。。orrrrrrz调死我辣。。。

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:
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
vector<int> ans;
if(num.size() < size|| size < 1) return ans;
queue<unsigned> maxq;
for(unsigned i = 0; i < num.size(); ++i){
if(i < size){
while(!maxq.empty() && num[maxq.front()] < num[i]) maxq.pop();
maxq.push(i);
}else{
ans.push_back(num[maxq.front()]);
while(maxq.front() <= i - size) maxq.pop(); //此行必须放在下一行之前,否则有时maxq是empty,从而maxq.front()调用失败
while(!maxq.empty() && num[maxq.front()] < num[i]) maxq.pop();
maxq.push(i);
}
}
//特判,最后一个窗口
int tmp1 = maxq.front(); maxq.pop();
while(!maxq.empty()){
if(num[tmp1] < num[maxq.front()])
tmp1 = maxq.front();
maxq.pop();
}
ans.push_back(num[tmp1]);
return ans;
}
};

二维数组中的查找 剪枝

题目描述

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

Code $O(m+n)$

剑指Offer面试题:2.二维数组中的查找

从左下、右上任意一个斜角开始。快速剪枝。(如果从其它位置,剪枝会很难处理)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
int m = array.size();
int n = array[0].size();
int i=0, j= n-1;
while(i < m && j >= 0){ //可以应付空矩阵
if(array[i][j] == target) return 1;
else if (array[i][j] > target) j--;
else i++;
}
return 0;
}
};

二进制1的个数 位运算&

题目描述

输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

Code $O(n)$

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int NumberOf1(int n) {
int ans = 0;
unsigned i = 1; //注意用unsigned统计
while(i){
ans += (bool)(n & i); //位运算
i = i << 1;
}
return ans;
}
};

Code $O(count(1))$

1的数量成正比的算法。

把一个整数减去1,再和原来的整数做位与运算,会把该整数最右边的1变为0

  • 用一条语句判断一个整数是否是2的整数次方
  • 求海明码距:求异或,统计异或中的1
1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int NumberOf1(int n) {
int ans = 0;
while(n){
++ans;
n &= n - 1;
}
return ans;
}
};

数值的整数次方 分治+位运算优化

题目描述

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

Code $O(logn)$

考虑边界情况:0,1,负指数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
double Power(double base, int exponent) {
if(exponent == 0)
return 1;
if(exponent == 1)
return base;
bool flag = 0;
if(exponent < 0){ //负指数
exponent = -exponent;
flag = 1;
}
double result = Power(base, exponent >> 1); //开方调用
result *= result;
if(exponent & 0x1) // 奇数技巧
result *= base;
if(flag)
return 1/result;
return result;
}
};

链表中倒数第k个结点 双指针

题目描述

输入一个链表,输出该链表中倒数第k个结点。

Code $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
/*
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 == nullptr) return nullptr; //空指针直接返回
int i = 1;
ListNode* suf = pListHead;
while(suf->next != nullptr && i < k){
suf = suf->next;
++i;
}
if(i != k) return nullptr; //如果没有k个就退出
ListNode* pre = pListHead;
while(suf->next != nullptr){ //一起移动
suf = suf->next;
pre = pre->next;
}
return pre;
}
};

反转链表 缓存解耦

题目描述

输入一个链表,反转链表后,输出新链表的表头。

Code $O(n)$

反转操作和位移操作要解耦。使用缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
ListNode* pre=nullptr;
while(pHead!=nullptr){
ListNode* tmp = pHead->next; //缓存,(与反转操作解耦)
pHead->next = pre; //反转
pre = pHead; //位移
pHead = tmp; //位移
}
return pre; //注意返回值
}
};

从头到尾打印链表 向量反转

题目描述

输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。

Code $O(n)$

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> ans;
while(head != nullptr){
ans.push_back(head->val); // 注意指针引用
head = head->next;
}
reverse(ans.begin(), ans.end()); // 注意格式
return ans;
}
};

合并两个排序的链表 链表操作

题目描述

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

Code $O(m+n)$

很复杂。人生苦短,我用Py

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
/*
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 == nullptr){ //有一个为空,无须合并直接返回
return pHead2;
}
if(pHead2 == nullptr){
return pHead1;
}
ListNode* NewList_Head = nullptr;
ListNode* NewList =nullptr;
while(pHead1 != nullptr && pHead2 != nullptr){
if(pHead1->val < pHead2->val){
if(NewList == nullptr) //起始,空特判
NewList_Head = NewList = pHead1;
else{
NewList->next = pHead1;
NewList = NewList->next; //注意NewList也要移动指针
}
pHead1 = pHead1->next; //移动指针
}
else{
if(NewList == nullptr)
NewList_Head = NewList = pHead2;
else{
NewList->next = pHead2;
NewList = NewList->next;
}
pHead2 = pHead2->next;
}
}
//对剩下的做处理
if(pHead1 == nullptr)
NewList->next = pHead2;
else
NewList->next = pHead1;
return NewList_Head;
}
};

Code 递归

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
/*
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 == nullptr){ //有一个为空,无须合并直接返回
return pHead2;
}
if(pHead2 == nullptr){
return pHead1;
}
ListNode* merge_head;
if(pHead1->val < pHead2->val){
merge_head = pHead1;
merge_head->next = Merge(pHead1->next, pHead2);
}
else{
merge_head = pHead2;
merge_head->next = Merge(pHead1, pHead2->next);
}
return merge_head;
}
};

重建二叉树 前序+中序+递归

题目描述

输入某二叉树的前序遍历中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

Code $O(nlogn)$

总结就是,调试功能太难用啦。看不到中间结果,只能靠猜。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
if(pre.size() <=0 || vin.size() <= 0) return nullptr; //递归基!很重要!!
unsigned dex=0;
while(dex < vin.size()){ //找中序遍历中的root
if(vin[dex] == pre[0]) break;
dex++;
}
vector<int> pre1(pre.begin()+1, pre.begin()+dex+1); //vector的快捷生成
vector<int> vin1(vin.begin(), vin.begin()+dex);
vector<int> pre2(pre.begin()+dex+1, pre.end());
vector<int> vin2(vin.begin()+dex+1, vin.end());

TreeNode* ans = new TreeNode(pre[0]); // 新建指针结构的写法
ans->left = reConstructBinaryTree(pre1, vin1);
ans->right = reConstructBinaryTree(pre2, vin2);

return ans;
}
};

*树的子结构 递归/模式匹配

题目描述

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

Code $O(AB)$

树的子结构

剑指offer之树的子结构

这道题比想象的复杂。orz

参考了题解。注重细节是个好习惯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
bool isSubtree(TreeNode* A, TreeNode* B){
if(B == nullptr) return true;
if(A == nullptr) return false;
if(B->val == A->val){
return isSubtree(A->left, B->left) &&
isSubtree(A->right, B->right);
}
return false;
}
bool HasSubtree(TreeNode* A, TreeNode* B)
{
if(B == nullptr || A == nullptr) return false;
return isSubtree(A, B) ||
HasSubtree(A->left, B) ||
HasSubtree(A->right, B);
}
};

二叉树镜像 归并

题目描述

操作给定的二叉树,将其变换为源二叉树的镜像。

输入描述:

1
2
3
4
5
6
7
8
9
10
11
二叉树的镜像定义:源二叉树 
8
/ \
6 10
/ \ / \
5 7 9 11
镜像二叉树
8
/ \
10 6
/ \ / \

Code $O(n)$

有点像归并的感觉。

1
2


顺时针打印矩阵

题目描述

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下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.

Code $O(n)$

1
2


包含min函数的栈

题目描述

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。

Code $O(n)$

1
2


栈的压入、弹出序列

题目描述

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

Code $O(n)$

1
2


从上往下打印二叉树

题目描述

从上往下打印出二叉树的每个节点,同层节点从左至右打印。

Code $O(n)$

1
2


二叉搜索树的后序遍历序列

题目描述

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

Code $O(n)$

1
2


二叉树中和为某一值的路径

题目描述

输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)

Code $O(n)$

1
2


复杂链表的复制

题目描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

Code $O(n)$

1
2


二叉搜索树与双向链表

题目描述

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

Code $O(n)$

1
2


字符串的排列

题目描述

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

输入描述

输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

Code $O(n)$

1
2


*最小的K个数 C++容器,快排

题目描述

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。

Code $O(nlogn)$

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int>& input, int k) {
vector<int> ans;
if(input.empty() || k > input.size()) return ans; //注意边界条件
sort(input.begin(), input.end());
for(int i = 0; i < k; ++i)
ans.push_back(input[i]);
return ans;
}
};

Code $O(n)\longrightarrow O(n^2)$ 快排

需要修改输入。(好麻烦啊。。。

1
orz

Code $O(nlogk)$ 集合容器multiset

作者:搁浅的鱼儿

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
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
int len=input.size();
if(len<=0||k>len) return vector<int>();

//仿函数中的greater<T>模板,从大到小排序
multiset<int, greater<int> > leastNums;
vector<int>::iterator vec_it=input.begin();
for(;vec_it!=input.end();vec_it++)
{
//将前k个元素插入集合
if(leastNums.size()<k)
leastNums.insert(*vec_it);
else
{
//第一个元素是最大值
multiset<int, greater<int> >::iterator greatest_it=leastNums.begin();
//如果后续元素<第一个元素,删除第一个,加入当前元素
if(*vec_it<*(leastNums.begin()))
{
leastNums.erase(greatest_it);
leastNums.insert(*vec_it);
}
}
}

return vector<int>(leastNums.begin(),leastNums.end());
}
};

Code $O(nlogk)$ 堆容器priority_queue

边界条件不对直接WA All,我。。。orz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int>& input, int k) {
vector<int> ans;
if(input.size() < k || k <= 0) return ans; //注意边界条件!!!!!
priority_queue<int> pq;
for(int i = 0; (i < k) && (i < input.size()); ++i){
pq.push(input[i]);
}
for(int i = k; i < input.size(); ++i){
if(input[i] < pq.top()){
pq.push(input[i]); pq.pop();
}
}
while(!pq.empty()){
ans.push_back(pq.top()); pq.pop();
}
reverse(ans.begin(), ans.end());
return ans;
}
};

连续子数组的最大和

题目描述

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

Code $O(n)$

1
2


整数中1出现的次数

题目描述 (从1到n整数中1出现的次数)

求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

Code $O(n)$

1
2


把数组排成最小的数

题目描述

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

Code $O(n)$

1
2


丑数

题目描述

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

Code $O(n)$

1
2


第一个只出现一次的字符

题目描述

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).

Code $O(n)$

1
2


数组中的逆序对

题目描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

输入描述:

题目保证输入的数组中没有的相同的

数字数据范围:

​ 对于%50的数据,size<=10^4

​ 对于%75的数据,size<=10^5

​ 对于%100的数据,size<=2*10^5

输入

1
1,2,3,4,5,6,7,0

输出

1
7

Code $O(n)$

1
2


两个链表的第一个公共结点

题目描述

输入两个链表,找出它们的第一个公共结点。

Code $O(n)$

1
2


删除链表中重复的结点

题目描述

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

Code $O(n)$

1
2


二叉树的下一个结点

题目描述

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

Code $O(n)$

1
2


二叉树的深度

题目描述

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

Code $O(n)$

1
2


平衡二叉树

题目描述

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

Code $O(n)$

1
2


数组中只出现一次的数字

题目描述

一个整型数组里除了两个数字之外,其他的数字都出现了偶数次。请写程序找出这两个只出现一次的数字。

Code $O(n)$

1
2


和为S的连续正数序列

题目描述

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

输出描述

输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

Code $O(n)$

1
2


和为S的两个数字

题目描述

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

输出描述

对应每个测试案例,输出两个数,小的先输出。

Code $O(n)$

1
2


左旋转字符串

题目描述

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

Code $O(n)$

1
2


翻转单词顺序列

题目描述

牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

Code $O(n)$

1
2


扑克牌顺子

题目描述

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。

Code $O(n)$

1
2


孩子们的游戏

题目描述 (圆圈中最后剩下的数)

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数….这样下去….直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

Code $O(n)$

1
2


*求1+2+3+…+n 底层实现

题目描述

求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

Code 循环-实例-构造函数

无外乎三种思路:

  • 公式法:不能用乘除
  • 循环:不能用for、while关键字
  • 递归:不能判断终止

既然受到限制,就必须靠其它的机制实现缺失的功能

使用构造调用实现循环累计。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Temp{
private:
static int N;
static int Sum;
public:
Temp() {Sum += ++N;}
static int GetSum(){ return Sum;}
static void Reset(){ N = 0; Sum = 0; }
};
int Temp::N = 0; //初始化必须在class之外完成
int Temp::Sum = 0;
class Solution {
public:
int Sum_Solution(int n) {
Temp::Reset();
Temp* a = new Temp[n]; //创建n个实例
delete []a;
a = nullptr;
return Temp::GetSum();
}
};

Code 递归+短路

https://zhuanlan.zhihu.com/p/41514000

层数不能太深<3000

1
2
3
4
5
6
7
8
class Solution {
public:
int Sum_Solution(int n) {
int sum = n;
(n > 0) && (sum += Sum_Solution(n-1)); //假短路调用
return sum;
}
};
1
2
3
4
5
6
7
8
class Solution {
public:
int Sum_Solution(int n) {
int sum = n;
(n <= 0) || (sum += Sum_Solution(n-1)); //等价写法,真短路调用
return sum;
}
};

Code 公式+sizeof()

1
2
3
4
5
6
7
class Solution {
public:
int Sum_Solution(int n) {
bool a[n][n+1];
return sizeof(a)>>1;
}
};

Code 公式+快速模乘(快速幂)+循环展开

作者:马客(Mark)

我来一个复杂度 32的,可以说O(logM)吧,M是数值大小,对于int也可以说是O(1)吧虽然常数有点大。

原理是把a拆成2的幂的和,a = 2^e0 + 2^e1 + 2^e2….

那么 a b = (2^e0 + 2^e1 + 2^e2+…) b = b 2^e0 + b 2^e1 + b * 2^e2 + …

= (b << e0) + (b << e1) + ….

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
//奇数返回0xffffffff,否则0
#define f(x) ((((x) & 1) << 31) >> 31)
class Solution {
public:
int Sum_Solution(int n) {
int a = n, b = n + 1, s = 0;
//复制32次。。
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;

s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;

s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;

s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
s += b & f(a); a >>= 1; b <<= 1;
return s >> 1;
}
};

Code 判断-虚函数

作者:molloc01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Base;
Base* Array[2];
class Base{
public:
virtual int Sum(int n){return 0;}
};
class Derived : public Base{
public:
virtual int Sum(int n){return Array[!!n]->Sum(n-1) + n;}

};
//使用虚函数来构造递归,在基类种定义虚函数Sum(n)返回0,通过将指针数组的两个元素分别绑定到基类和派生类,其中基类的Sum()
//结束递归,!!n来构造true(1) false(0)来对指针数组进行访问
class Solution {
public:
int Sum_Solution(int n) {
Base a;
Derived b;
Array[0] = &a;
Array[1] = &b;
return b.Sum(n);
}
};

Code 函数指针

纯C环境不能使用虚函数。

1
2
3
4
5
6
7
typedef unsigned int (*fun)(unsigned int);

unsigned int Solution_Terminator(unsigned int n) {return 0;}
unsigned int Sum_Solution(unsigned int n){
static fun f[2] = {Solution_Terminator, Sum_Solution};
return n + f[!!n](n-1);
}

Code 模板元编程

https://blog.csdn.net/shoulinjun/article/details/20564095

c++ 模板元编程的一点体会

C++ 模板元编程的应用有哪些,意义是什么?

Knight Rush——关于编程语言学习的一些思考

不能动态输入。

1
2
3
4
5
6
7
8
9
// Method 1: TMP, reference: Effective C++
template<unsigned n> struct Sum{
enum { value = Sum<n-1>::value + n };
};

// use template specialization to mimic if expression
template<> struct Sum<1>{
enum { value = 1 };
};
1
2
template <int m> inline int SumTo() { return m + SumTo<m-1>(); }
template <> inline int SumTo<1>() { return 1; }

不用加减乘除做加法 位运算

题目描述

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

Code $O(n)$

二进制加法结果与异或相同。Amazing!

  • 相加但不计进位:异或^
  • 记下进位:与& + 左移1位<<1
  • 将结果相加(循环,caary将不断右移到为0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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(num2 != 0);
return num1;
}
};

拓展

交换两个变量的值

1
2
3
4
5
6
7
8
9
10
11
void swap(int& a, int& b){
a = a + b;
b = a - b;
a = a - b;
}
//------
void swap(int& a, int& b){ //太强辣
a = a ^ b;
b = a ^ b;
a = a ^ b;
}

构建乘积数组 位运算+缓存

题目描述

给定一个数组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]。不能使用除法。

Code $O(n)$ 前缀后缀预处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
vector<int> multiply(const vector<int>& A) {
vector<int> B, C, D;
C.push_back(1);
for(unsigned i = 0; i < A.size(); ++i) //注意边界处理,要对齐
C.push_back(A[i] * C[i]);
D.push_back(1);
for(int i = (int)A.size() - 1; i > 0; --i)
D.push_back(A[i] * D[(int)A.size() - 1 - i]);
reverse(D.begin(), D.end()); //反转一下

for(unsigned i = 0; i < A.size(); ++i)
B.push_back(C[i]*D[i]);
return B;
}
};

把字符串转换成整数

题目描述

将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。

输入描述

输入一个字符串,包括数字字母符号,可以为空

输出描述

如果是合法的数值表达则返回该数字,否则返回0

输入

1
2
+2147483647
1a33

输出

1
2
2147483647
0

Code $O(n)$

1
2


正则表达式匹配

题目描述

请实现一个函数用来匹配包括’.’和’‘的正则表达式。模式中的字符’.’表示任意一个字符,而’‘表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串”aaa”与模式”a.a”和”abaca”匹配,但是与”aa.a”和”ab*a”均不匹配

Code $O(n)$

1
2


表示数值的字符串

题目描述

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串”+100”,”5e2”,”-123”,”3.1416”和”-1E-16”都表示数值。 但是”12e”,”1a3.14”,”1.2.3”,”+-5”和”12e+4.3”都不是。

Code $O(n)$

1
2


字符流中第一个不重复的字符

题目描述

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符”go”时,第一个只出现一次的字符是”g”。当从该字符流中读出前六个字符“google”时,第一个只出现一次的字符是”l”。

输出描述

如果当前字符流没有存在出现一次的字符,返回#字符。

Code $O(n)$

1
2


链表中环的入口结点

题目描述

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

Code $O(n)$

1
2


对称的二叉树

题目描述

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

Code $O(n)$

1
2


按之字形顺序打印二叉树 层次遍历

题目描述

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

Code $O(n)$

1
2


把二叉树打印成多行

题目描述

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

Code $O(n)$

1
2


序列化二叉树

题目描述

请实现两个函数,分别用来序列化和反序列化二叉树

Code $O(n)$

1
2


二叉搜索树的第k个结点

题目描述

给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。

Code $O(n)$

1
2


数据流中的中位数

题目描述

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

Code $O(n)$

1
2


矩阵中的路径

题目描述

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串”bcced”的路径,但是矩阵中不包含”abcb”路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

Code $O(n)$

1
2


机器人的运动范围

题目描述

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

Code $O(n)$

1
2


Reference

牛客网编程题 Python版