可参考内容:
c++动态规划类算法编程汇总(二)全排列| O(n)排序 | manacher法
c++策略类O(n)编程问题汇总(扑克的顺子|约瑟夫环|整数1出现的次数|股票最大利润)
目录
一、扑克牌的顺子
1.1 题干
一个数组,5个数字作为排号,可能出现从1-13,0值可以当作任意牌。如果出现顺子,比如12345,或者01345(0可以当作2补全)则返回true(能构成顺子)
1.2 解法
较为简单,统计出来0的值,再统计出来gap,如果排中有重复的数字,则返回false
如果gap>0的个数,返回false,其他返回true,较简单
class Solution {
public:
bool IsContinuous(vector<int> numbers) {
int size = numbers.size();
if (size != 5)return false;
sort(numbers.begin(),numbers.end());
int number_of_0 = 0;
int seq_num = 0;
int botton, top;
for (int idx = 0; idx < 5; idx++){
if (numbers[idx] == 0)number_of_0++;
else break;
}
int gap = 0;
for (int idx = number_of_0; idx < 4; idx++){
if (numbers[idx + 1] == numbers[idx])return false;
gap += numbers[idx + 1] - numbers[idx] - 1;
}
if (gap>number_of_0)return false;
else return true;
}
};
二、约瑟夫环
环状链表的最后一个数字,经典的约瑟夫环的问题。
2.1 题干
数字0到n构成一个环,每次从第0个数到第m-1个,把m-1个删掉,再从第m个开始数。最终剩下一个数字,这个数字是多少?
2.2 解法
常规解法是O(mn)的复杂度。
用双向链表list,可以方便的进行插入和删除。需要注意的点:
- list.end()的时候,相当于NULL,即比list中最后一个元素还往后一位
- list对iterator进行erase的时候,erase之后不能进行++或者--了,因为这个元素已经不存在了,所以需要用next来存iterator++
- 当前数到m个,相当于往后移动m-1位
- list中与vector不同,不能用iterator+1,只能++或者--表示前移动或者后移动
#include<list>
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
if (n < 1 || m<1)return -1;
list<int> students;
for (int idx = 0; idx < n; idx++){
students.push_back(idx);
}
auto current = students.begin();
while (students.size() != 1){
int next_idx = (m-1)%students.size();
for (int idx = 0; idx < next_idx;idx++){
current++;
if (current == students.end())current = students.begin();
}
auto next = ++current ;
if (next == students.end())next = students.begin();
current--;
students.erase(current);
current = next;
}
return *(students.begin());
}
};
int main(){
Solution s1;
cout << s1.LastRemaining_Solution(5, 3) << endl;
int end; cin >> end;
return 0;
}
2.3 找出映射规律
剑指offer P322
我们采用与之类似的思路,
三、股票的最大利润
股票价格按先后顺序,存于数组之中,如果卖出价格减去前面买入价格即为利润,问利润最大多少?
如果遍历,则复杂度O(n*n),复杂度较高。
可以将之前最低值存入min_value, 则目前减去前面最低值,则为当前卖出的最大利润。
#include<vector>
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
class Solution {
public:
int most_benefit(vector<int> value){
int v_size = value.size();
if (v_size < 2)return 0;
int current_min = value[0];
int max_benefit = 0x80000000;
for (auto item : value){
int benefit = item - current_min;
if (benefit>max_benefit)max_benefit = benefit;
if (item < current_min)current_min = item;
}
return max_benefit;
}
};
int main(){
Solution s1;
vector<int> value1 = { 7, 1, 5, 3, 6, 4 };//output 5
vector<int> value2 = { 7, 6, 4, 3, 1 };//output 0
cout << s1.most_benefit(value1) << endl;
cout << s1.most_benefit(value2) << endl;
int end; cin >> end;
return 0;
}
四、整数中1出现的次数
4.1 题干
1,2,3 ...到n,几个数字,这些数字中1出现了几次?
输入n,输出1,2,3...n中1出现的次数。
4.2 常规解法
统计出每个数字中1出现的次数,算法复杂度 n*logn显然不是最优:
//代码简单但是算法复杂度较高,需要n*logn的算法复杂度
int NumberOf1Between1AndN_Solution(int n)
{
int times_one = 0;
for (int current_num = 1; current_num <= n; current_num++){
int calcu_num = current_num;
while (calcu_num>0){
if (calcu_num % 10 == 1)times_one++;
calcu_num = calcu_num / 10;
}
}
return times_one;
}
4.3 按规律优化为O(n)的复杂度
分而治之,依次统计出个位,十位,百位...的1出现的次数。相比剑指offer给出的递归的方法,更加节省内存。
我们拿 21345来举例,每位上出现1的次数:
个位出现了 (2134+1)*1 次
十位出现了 (213 +1)*10 次
百位出现了 (21+1)*100 次
千位出现了 (2+0)*1000+345+1 次
万位出现了 (0+1)*10000 次
归纳一下规律,就是:
#include<iostream>
#include<vector>
#include<string>
#include<cmath>
using namespace std;
class Solution {
public:
//代码简单但是算法复杂度较高,需要n*logn的算法复杂度
int NumberOf1Between1AndN_Solution(int n)
{
int times_one = 0;
for (int current_num = 1; current_num <= n; current_num++){
int calcu_num = current_num;
while (calcu_num>0){
if (calcu_num % 10 == 1)times_one++;
calcu_num = calcu_num / 10;
}
}
return times_one;
}
// 算法复杂度较低,只有logn的算法复杂度
int easy_logn_NumberOf1Between1AndN(int n){
int pow_of_10 = 0;
int times_one = 0;
int current_decimal_pow = 1; // 1,10,100
while (n / current_decimal_pow){
int upper_pow = current_decimal_pow * 10; //10
int current_decimal = (n / current_decimal_pow) % 10;
if (current_decimal == 0){
times_one += n / upper_pow * current_decimal_pow;
}
else if (current_decimal == 1){
times_one += n / upper_pow*current_decimal_pow;
times_one += n % current_decimal_pow + 1;
}
else{
times_one += (n / upper_pow + 1)*current_decimal_pow;
}
current_decimal_pow = upper_pow;
}
return times_one;
}
};
int main(){
Solution s1;
bool error = false;
for (int idx = 198; idx < 200; idx++){
if (s1.NumberOf1Between1AndN_Solution(idx) != s1.easy_logn_NumberOf1Between1AndN(idx))
error = true;
}
if (error)cout << "Error!" << endl;
int end; cin >> end;
return 0;
}