返回类型设为引用,return *this,方便连续赋值、避免返回时的副本拷贝
参数类型设置为常量引用,避免实参到形参调用复制构造函数、不改变传入实例状态
判断传入参数和当前实例是否为同一个实例
释放实例自身的内存,避免内存泄漏
CMyString & operator = (const CMyString & rhs)
{
if(this == &rhs)
return *this;
delete[] m_pData;
m_pData = new char[strlen(rhs->m_pData)+1];
strcpy(m_pData, rhs->m_pData);
return *this;
}
创建临时实例,交换实例,防止new char抛出异常保证异常安全性、临时实例被自动释放避免内存泄漏
CMyString & operator = (CMyString & rhs)
{
if (this == &rhs)
return *this;
CMyString temp(rhs);
char *p = this->m_pData;
this->m_pData = temp.m_pData;
temp.m_pData = p;
return *this;
}
单例类只有一个实例对象,构造函数是私有的,提供一个静态的公有函数创建或获取改静态私有实例。
懒汉式单例,时间换空间,有线程安全问题,内存泄漏需要使用智能指针解决
#include
#include
#include
using namespace std;
class Singleton
{
public:
typedef shared_ptr Ptr;//智能指针,防止内存泄漏
static Ptr getInstance()//共有函数、静态
{
if (m_ptr_instance == nullptr)//双检锁,减少锁开销、某些架构下可能失效
{
if (m_ptr_instance == nullptr)
{
lock_guard lck(m_mtx);//lock_guard锁
m_ptr_instance = (Ptr)(new Singleton);
}
}
return m_ptr_instance;
}
~Singleton()
{
cout << "destory." << endl;
}
//删除拷贝赋值函数、拷贝构造函数
Singleton1& operator = (const Singleto1&) = delete;
Singleton1(const Singleton1&) = delete;
private:
Singleton()
{
cout << "construct." << endl;
}
static Ptr m_ptr_instance;
static mutex m_mtx;
};
Singleton::Ptr Singleton::m_ptr_instance = nullptr;
mutex Singleton::m_mtx;
int main()
{
Singleton::Ptr ptr_s1 = Singleton::getInstance();
Singleton::Ptr ptr_s2 = Singleton::getInstance();
return 0;
}
懒汉式,局部静态变量实现,并发线程会阻塞等待局部静态变量的初始化
class Singleton1
{
public:
~Singleton1()
{
cout << "Singleton1 destory" << endl;
}
//删除拷贝赋值函数、拷贝构造函数
Singleton1& operator = (const Singleton1&) = delete;
Singleton1(const Singleton1&) = delete;
static Singleton1& getInstance()
{
static Singleton1 instance;//该静态变量只会在第一次调用的时候初始化
return instance;
}
private:
Singleton1()
{
cout << "Singleton1 construct" << endl;
}
};
饿汉式单例,空间换时间,没有线程安全问题
class Singleton2
{
public:
~Singleton2()
{
cout << "destory" << endl;
}
//删除拷贝赋值函数、拷贝构造函数
Singleton2& operator = (const Singleton2&) = delete;
Singleton2(const Singleton2&) = delete;
static Singleton2* getInstance()//共有、static
{
return m_ptr_instance;
}
private:
Singleton2() //私有
{
cout << "construct" << endl;
};
static Singleton2* m_ptr_instance;//私有、static
};
Singleton2* Singleton2::m_ptr_instance = (Singleton2*)(new Singleton2);//直接初始化,类定义后立刻生成单例
//二分法核心,升序数组中找到某个数k
bool func(int numbers[], int length, int k)
{
int start = 0, end = length - 1;//设定start、end初值
while(end >= start)//退出条件1
{
if (end == start)//退出条件2
{
if (numbers[end] == k)
return true;
else
return false;
}
int middle = start + (end - start) / 2;//查找一半的范围
if (k >= numbers[start] && k <= numbers[middle])
end = middle;//边界收缩
else
start = middle + 1;//边界收缩
}
return false;
}
核心,从数组的右上角开始找,如果小于则加行,如果大于则减列
// offer04查找二维数组.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
#include
using namespace std;
bool find_stl(vector> matrix, int rows, int columns, int number)
{
bool res = false;
if (rows < 0 || columns < 0)
return res;
int row = 0, column = columns - 1;
while (row < rows && column > 0)
{
if (matrix[row][column] == number)
{
res = true;
break;
}
else
{
if (matrix[row][column] > number)
column--;
else
row++;
}
}
return res;
}
bool find(int* matrix, int rows, int columns, int number)
{
bool result = false;
if (matrix == nullptr || rows < 0 || columns < 0)
return result;
int row = 0, column = columns - 1;
while (row < rows && column > 0)
{
if (matrix[row * column + column] == number)
{
result = true;
break;
}
else
{
if (matrix[row * column + column] > number)
{
++row;
}
else
{
--column;
}
}
}
return result;
}
int main()
{
/*
int matrix[16], num, cnt = 0;
while (cin >> num)
{
matrix[cnt++] = num;
}
cout << find(matrix, 4, 4, 7);
std::cout << "Hello World!\n";*/
vector> matrix;
int num = 0, cnt = 0;
while (cin >> num)
{
}
return 0;
}
先遍历出空格数,从尾遍历,p1指向原尾,p2指向新尾
// offer05字符串替换空格.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
void replace(char* string, int length)
{
if (string == nullptr || length < 0)
return;
int blank_cnt = 0, str_len = 0;
while (string[str_len] != '\0')
{
if (string[str_len] == ' ')
++blank_cnt;
++str_len;
}
int new_len = str_len + 2 * blank_cnt;
if (new_len + 1 > length)
return;
int p1 = str_len, p2 = new_len;
while (p1 >= 0 && p1 < p2)
{
if (string[p1] == ' ')
{
string[p2] = '0';
string[p2 - 1] = '2';
string[p2 - 2] = '%';
p2 -= 3;
--p1;
}
else
{
string[p2] = string[p1];
--p1;
--p2;
}
}
}
int main()
{
char string[100] = { 0 };
const char* str = " we are happy.";
strcpy_s(string, str);
replace(string, 100);
printf("%s\n", string);
return 0;
//std::cout << "Hello World!\n";
}
方法1:将链表指针反向
方法2:使用栈来存储
方法3:使用递归(能用栈的也可以用递归)
#include
#include
using namespace std;
struct ListNode
{
int m_nValue;
ListNode* m_pNext;
};
void print_linkList(ListNode *pHead)
{
stack node_stack;
ListNode* p = pHead;
while (p != nullptr);
{
node_stack.push(p);
p = p->m_pNext;
}
while (!node_stack.empty())
{
p = node_stack.top();
node_stack.pop();
cout << p->m_nValue << endl;
}
}
void print_linkList_recursively(ListNode* pHead)
{
if (pHead == nullptr)
return;
if (pHead->m_pNext != nullptr)
{
print_linkList_recursively(pHead->m_pNext);
}
cout << pHead->m_nValue << endl;
}
ListNode* linkList_reverse(ListNode *pHead)
{
if (pHead == nullptr)
{
return pHead;
}
ListNode *p1 = pHead, *p2 = pHead->m_pNext, *tmp = nullptr;
pHead = nullptr;
while (p2 != nullptr)
{
tmp = p2->m_pNext;
p2->m_pNext = p1;
p1 = p2;
p2 = tmp;
}
pHead = p1;
return pHead;
}
int main()
{
std::cout << "Hello World!\n";
}
每次递归前要检查左右子树序列是否存在
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* reConstructBinaryTree(vector pre,vector vin) {
return core(pre, vin, 0, pre.size() - 1, 0, vin.size() - 1);
}
TreeNode* core(vector pre, vector vin, int preStart, int preEnd, int inStart, int inEnd)
{
TreeNode* root = new TreeNode(pre[preStart]);
if (preStart == preEnd)
return root;
int i = inStart;
while(vin[i] != pre[preStart]) i++;
int leftLen = i - inStart;
int rightLen = inEnd - i;
if (leftLen > 0)//非常重要
root->left = core(pre, vin, preStart + 1, preStart + leftLen, inStart, i - 1);
if (rightLen > 0)//非常重要
root->right = core(pre, vin, preStart + leftLen + 1, preEnd, i + 1, inEnd);
return root;
}
};
分三种情况,第三种是第二种的复杂模式
异常:当前节点是最后的节点,则下一个节点为nullptr,实际上就是第三种情况未找到的结果的情况
#include
typedef struct BinaryTreeNode
{
int m_nVlaue;
BinaryTreeNode* m_pLeft;
BinaryTreeNode* m_pRight;
BinaryTreeNode* m_pParent;
}BTN;
BTN* find_mid_next(BTN* node)
{
if (node == nullptr)
{
return nullptr;
}
BTN* next = nullptr;
if (node->m_pRight != nullptr)
{
BTN* child = node->m_pRight;
while (child->m_pLeft != nullptr)
{
child = child->m_pLeft;
}
next = child;
}
else
{
BTN* parent = node->m_pParent;
BTN* cur = node;
while (parent != nullptr && parent->m_pLeft != cur)
{
cur = parent;
parent = parent->m_pParent;
}
next = parent;
}
return next;
}
int main()
{
std::cout << "Hello World!\n";
}
栈和队列是相互联系的
#include
#include
using namespace std;
template class MyStack
{
public:
MyStack() {}
~MyStack() {}
T pop();
void push(const T &element);
private:
queue q1;
queue q2;
};
template void MyStack::push(const T &element)
{
if (q1.empty())
q2.push(element);
else
q1.push(element);
}
template T MyStack::pop()
{
if (q1.empty() && q2.empty())
throw exception("queue is empty");
T element;
if (q1.empty())
{
while (q2.size() > 1)
{
q1.push(q2.front());
q2.pop();
}
element = q2.front();
q2.pop();
}
else
{
while (q1.size() > 1)
{
q2.push(q1.front());
q1.pop();
}
element = q1.front();
q1.pop();
}
return element;
}
int main()
{
MyStack s1;
s1.push(1);
s1.push(2);
s1.push(3);
cout << s1.pop() << " ";
s1.push(4);
try
{
while(true)
cout << s1.pop() << " ";
}
catch (const std::exception& e)
{
cout << e.what() << endl;
}
return 0;
}
//3421
#include
#include
using namespace std;
template class MyQueue
{
public:
MyQueue(void);
~MyQueue(void);
void appendTail(const T& node);
T deleteHead(void);
private:
stack s1;
stack s2;
};
template void MyQueue::appendTail(const T& node)
{
s1.push(node);
}
template T MyQueue::deleteHead(void)
{
T head;
if (!s2.empty())
{
head = s2.top();
s2.pop();
}
else
{
while (!s1.empty())
{
s2.push(s1.top());
s1.pop();
}
if (s2.empty())
throw exception("MyQueue is empty");
}
head = s2.top();
s2.pop();
return head;
}
int main()
{
std::cout << "Hello World!\n";
}
小青蛙跳台阶略有不同,0, 1, 2开头
铺瓷砖同理
#include
using namespace std;
long long Fibo_recursively(int n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
return Fibo_recursively(n - 1) + Fibo_recursively(n - 2);
}
long long Fibo(int n)
{
int res[2] = { 0, 1 };
if (n < 2)
return res[n];
long long fiboN, fiboN_minus_1 = 1, fiboN_minus_2 = 0;
for (int i = 2; i <= n; ++i)
{
fiboN = fiboN_minus_1 + fiboN_minus_2;
fiboN_minus_2 = fiboN_minus_1;
fiboN_minus_1 = fiboN;
}
return fiboN;
}
long long fi(int n) { int result[2] = { 0, 1 }; int i = 2; long long num = 0; if (n < 2) { return result[n]; } long long fib_minusone = 1; long long fib_minustwo = 0; for (; i <= n; i++) { num = fib_minusone + fib_minustwo; fib_minustwo = fib_minusone; fib_minusone = num; } return num; }
int main()
{
//cout << Fibo_recursively(10)<< " " << Fibo(10) << endl;
//cout << Fibo_recursively(100) << endl;
//cout << Fibo(100) << endl;
cout << Fibo(5000) << endl;
cout << fi(5000) << endl;
return 0;
}
在数组中选择一个数字,将数组的数字分为两个部分,比选择数小的数字移到数组左边,比选择数大的移动到右边
给出数组arr,开始下标start,结束下标end
#include
#include
using namespace std;
int partition(int* arr, int start, int end)
{
if (arr == nullptr || start < 0 || end < start)
{
return 0;
}
int index = start;
swap(arr[index], arr[end]);
int small = start - 1;
for (index = start; index < end; ++index)
{
if (arr[index] < arr[end])
{
++small;
if (index != small)
swap(arr[small], arr[index]);
}
}
++small;
swap(arr[small], arr[end]);
return small;
}
void quickSort(int *arr, int start, int end)
{
if (start == end)
return;
int index = partition(arr, start, end);
if (start < index)
quickSort(arr, start, index - 1);
if (index < end)
quickSort(arr, index + 1, end);
}
int main()
{
int a[] = { 7, 4, 3, 9,
20, 1, 99, 8,
7, 1, 6, 19 };
swap(a[0], a[1]);
cout << a[0] << a[1];
}
使用partition函数对数组进行递归解
递归出口是start==end
递归入口时start < index < end
给公司所有人的年龄排序
// offer11借助常量空间达到o_n方法数组排序.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
using namespace std;
void sortAge(int* ages, int length)
{
if (ages == nullptr || length < 0)
return;
const int maxAge = 99;
int ageTable[maxAge + 1] = { 0 };
for (int i = 0; i < length; ++i)
{
if (i < 0 || i > maxAge)
throw exception("invalid age.");
++ageTable[ages[i]];
}
int index = 0;
for (int i = 0; i <= maxAge; ++i)
{
for (int j = 0; j < ageTable[i]; ++j)
{
ages[index++] = i;
}
}
}
int main()
{
int ages[] = {
1, 56, 87, 23, 12,
43, 45, 23, 23, 56,
12, 22, 99, 0, 11
};
sortAge(ages, 15);
for (auto age : ages)
{
cout << age << " ";
}
return 0;
}
O(logN)的方法
递增数组,旋转之后的特性:前一段数组要大于等于后一段数组
特例1:增值数组其实没有旋转,最小数在第一个,意味着不满足旋转数组特性
特例2:index1和index2和mid的值全部相同,无法判断是在哪一段数组上,此时要遍历[index1, index2]
#include
using namespace std;
int find(int* arr, int start, int end)
{
int min = arr[start];
for (int i = start + 1; i <= end; ++i)
{
if (min > arr[i])
min = arr[i];
}
return min;
}
int findmin(int* arr, int length)
{
if (arr == nullptr || length < 0)
throw exception("invalid input");
int idx1 = 0;
int idx2 = length - 1;
int mid = idx1;//未旋转特例
while (arr[idx1] >= arr[idx2])
{
if (idx2 - idx1 == 1)
{
mid = idx2;
break;
}
mid = (idx1 + idx2) / 2;
if (arr[idx1] == arr[mid] && arr[mid] == arr[idx2])
{
return find(arr, idx1, idx2);
}
if (arr[mid] >= arr[idx1])
idx1 = mid;
if (arr[mid] <= arr[idx2])
idx2 = mid;
}
return arr[mid];
}
int NumberOf1(int n) {
int cnt = 0;
if (n < 0)
++cnt;
int mask = 1;
for (int i = 0; i < sizeof(int) * 8 - 1; ++i)
{
mask = mask << i;
if (mask & n != 0)
++cnt;
}
return cnt;
}
#include
void reOrderArray(vector& array) {
int length = array.size();
if (length <= 1)
return;
int head = 0, tail = length - 1;
while (head < tail)
{
while ((array[head] & 1) != 0)//找偶数
head++;
while ((array[tail] & 1) == 0)//找奇数
tail--;
if (head < tail)
{
swap(array[head], array[tail]);
}
}
}
int main()
{
vector a = { 0,3,2,5,4,7,8,9 };
reOrderArray(a);
for (auto i : a)
cout << i << " ";
return 0;
}
回溯法:
对每个矩阵元素进行遍历,如果当前符合,则探索子节点可能性。子节点具有可能性则继续往具有可能性的子节点探索,如果不具可能性则返回的遍历父节点,到父节点的相邻节点进行探索。
注意对具有可能性的节点进行标记,后面发现没有可能性则取消标记(即如果要对当前节点的子节点探索则先标记当前节点)
建立标记矩阵,标记矩阵记录被选中的和字符串的字符匹配的坐标
对矩阵所有节点进行调用递归函数探索每个节点可能性
探索递归函数工作
一直贯穿始终的是下表追踪和标记追踪,一直是单线查找的
#include
using namespace std;
bool hasPathCore(const char *matrix, int rows, int columns,
int row, int column, const char* str, int& str_idx, bool *mark)
{
if (str[str_idx] == '\0')
return true;
bool result = false;
if (row >= 0 && row < rows && column >= 0 && column < columns &&
mark[row * columns + column] == false &&
matrix[row * columns + column] == str[str_idx])
{
++str_idx;
mark[row * columns + column] = true;
result = hasPathCore(matrix, rows, columns, row + 1, column,
str, str_idx, mark) ||
hasPathCore(matrix, rows, columns, row - 1, column,
str, str_idx, mark) ||
hasPathCore(matrix, rows, columns, row, column + 1,
str, str_idx, mark) ||
hasPathCore(matrix, rows, columns, row, column - 1,
str, str_idx, mark);
if (!result)
{
--str_idx;
mark[row * columns + column] = false;
}
}
return result;
}
bool hasPath(const char* matrix, int rows, int columns, const char* str)
{
if (matrix == nullptr || rows < 0 || columns < 0 || str == nullptr)
return false;
bool* mark = new bool[rows * columns];
memset(mark, 0, rows * columns);
int str_idx = 0;
for (int row = 0; row < rows; ++row)
{
for (int column = 0; column < columns; ++column)
{
if (hasPathCore(matrix, rows, columns, row, column,
str, str_idx, mark))
{
return true;
}
}
}
delete[] mark;
return false;
}
int main()
{
char matrix[] = {
'a', 'b', 't', 'g',
'c', 'f', 'c', 's',
'j', 'd', 'e', 'h'
};
const char* str = "bfce";
const char* str1 = "bfch";
const char* str2 = "afce";
cout << hasPath(matrix, 3, 4, str) << hasPath(matrix, 3, 4, str1) << hasPath(matrix, 3, 4, str2);
return 0;
}
在二维矩阵运动的问题都可以用回溯法解决
#include
int digitSum(int row, int col)
{
int sum = 0;
while (row || col)
{
sum = row % 10 + col % 10;
row /= 10;
col /= 10;
}
return sum;
}
bool check(int rows, int cols, int row, int col, int k, bool* mark)
{
if (row >= 0 && row < rows &&
col >= 0 && col < cols &&
digitSum(row, col) <= k &&
!mark[row * cols + col])
return true;
return false;
}
int moveCountCore(int rows, int cols, int row, int col, bool* mark)
{
int cnt = 0;
if (check(rows, cols, row, col, 18, mark))
{
mark[row * cols + col] = true;
cnt = 1 + moveCountCore(rows, cols, row + 1, col, mark) +
moveCountCore(rows, cols, row - 1, col, mark) +
moveCountCore(rows, cols, row, col + 1, mark) +
moveCountCore(rows, cols, row, col - 1, mark);
}
return cnt;
}
int moveCount(int rows, int cols, int k)
{
if (rows < 0 || cols < 0 || k < 0)
return 0;
bool* mark = new bool[rows * cols];
memset(mark, 0, rows * cols);
return moveCountCore(rows, cols, 0, 0, mark);
}
int main()
{
std::cout << sizeof(H);
}
特点:
特点:
动态规划判断:
动态递归方法:
写出简单的最小子问题最优解,写出procedure数组前面几个数
最小子问题不用满足必须剪1刀,因为最小子问题组成的大问题一定被剪了一刀以上。如果单独求最小子问题则需要满足必须剪一刀的条件。
3. 列出几个项的特殊值,取决于特殊子问题和procedure的不同有几个
设立数组,记录f(n)的最优解
f(n)的最优解可以通过f(0)~f(n-1)求解,求解公式f(n) = max(f(n-i)*f(i)), 其中 0
第一个循环,遍历i;第二个循环用于对i用递归求解公式逻辑求出最大值
#include
using namespace std;
int cut(int n)
{
if (n < 2)
return -1;
if (n == 2)
return 1;
if (n == 3)
return 2;
int* f = new int[n + 1];
f[0] = 0;
f[1] = 1;
f[2] = 2;
f[3] = 3;
int max = 0;
for (int i = 4; i <= n; ++i)
{
max = 0;
for (int j = 1; j < i; ++j)
{
if (max < f[j] * f[i - j])
max = f[j] * f[i - j];
}
f[i] = max;
}
max = f[n];
delete[] f;
return max;
}
int main()
{
std::cout << "Hello World!\n";
}
逻辑右移(高位补0)算术右移(高位补符号位)
逻辑左移(低位补0)算术左移(低位补0)
按照移位补0的原则,为何左移都是逻辑移位呢?
**答疑:**先看看“-8”和“8”在计算机内存中的值分别是:
0xfffffff8
0x8
由于计算机均按补码保存数值,所以不管符号正负,左移对于符号位并不产生影响
解法1
不断用1左移然后和整数做&处理,处理结果不等于0则代表该位为1
解法2重要!!!!!
把整数减去1,和原整数做&运算,会把最右边的1变成0,如果有n个1,可以做n次这样的运算
n = n & (n - 1);
解法2是二进制问题的银弹
#include
int numberOf1(int n)
{
int flag = 1;
int cnt = 0;
while (flag)
{
if (n & flag)
++cnt;
flag = flag << 1;
}
return cnt;
}
int numberOf1_(int n)
{
int cnt = 0;
while (n)
{
n = (n - 1) & n;
++cnt;
}
return cnt;
}
int changeBit(int m, int n)
{
int cnt = 0;
int mn = m ^ n;
while (mn)
{
mn = mn & (mn - 1);
++cnt;
}
return cnt;
}
int main()
{
std::cout << numberOf1(9) << numberOf1_(9) << changeBit(10, 13);
return 0;
}
如果是,那么二进制只有一位是1,其余为零。使用解法2可以做到
if ( n > 1 && !(n & (n - 1)))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FMJxF1d2-1586255098442)(C:\Users\cqlia\AppData\Roaming\Typora\typora-user-images\1584710298237.png)]
位运算的& | ^ ~的运算,不排除符号位,即符号位也要参加运算
问题一般是自上而下的递归分析,代码求解则是基于自下而上的循环实现
解法1
注意!!!!
double判断相等不能使用 == ,而是使用equal函数。因为,代码中逻辑上相等的两个数,在实际存储中会有微小误差
//判断整数的equal函数
#include
#define MINVALUE (1e-8)
bool myEqual(double m, double n)
{
return abs(m - n) < MINVALUE;
}
double参与运算时
要 1.0 / double
解法2[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fQBwgF58-1586255098444)(C:\Users\cqlia\AppData\Roaming\Typora\typora-user-images\1584714025515.png)]
通过递归实现这个公式,达到logN求解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DJOIveW1-1586255098445)(C:\Users\cqlia\AppData\Roaming\Typora\typora-user-images\1584714607641.png)]
陷阱在于大数问题,大数问题需要使用字符串或者数组解决
方法1:
细节:
方法2:
总结:
如果面试关于N位数的整数,且没有限定n的范围,则可能需要大数问题
#include
using namespace std;
void printNumber(const char* number)
{
int notZeroIndex = 0;
while (number[notZeroIndex] == '0')
++notZeroIndex;
printf("%s\n", number + notZeroIndex);
return;
}
bool numberIncriment(char *number, int n)
{
bool isOverflow = false;
int flag = 0;
int index = n - 1;
while (index >= 0)
{
int num = number[index] - '0' + flag;//flag执行进位
if (index == n - 1)
++num;//执行函数加1操作
if (num < 10)
{
flag = 0;
number[index] = '0' + num;
break;
}
flag = 1;
num -= 10;
number[index] = '0' + num;
--index;
}
if (index < 0 && flag == 1)
isOverflow = true;
return isOverflow;
}
void print1ToMaxOfNDigits(int n)
{
if (n < 1)
return;
char* number = new char[n + 1];
memset(number, '0', n + 1);
number[n] = '\0';
while (!numberIncriment(number, n))
{
printNumber(number);
}
delete[] number;
}
void print1ToMaxOfNDigitsRecursivelyCore(char* number, int n, int index)
{
if (index == n)
{
printNumber(number);
return;
}
for (int i = 0; i < 10; ++i)
{
number[index] = '0' + i;
print1ToMaxOfNDigitsRecursivelyCore(number, n, index + 1);
}
return;
}
void print1ToMaxOfNDigitsRecursively(int n)
{
if (n < 1)
return;
char* number = new char[n + 1];
memset(number, '0', n + 1);
number[n] = '\0';
print1ToMaxOfNDigitsRecursivelyCore(number, n, 0);
delete[] number;
return;
}
int main()
{
//print1ToMaxOfNDigits(9);
print1ToMaxOfNDigitsRecursively(8);
return 0;
}
delete指针后,指针要考虑清零
题目1:O(1)时间删除节点
题目2:排序链表中,删除链表重复节点。例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
难点:如何处理好pre指针一开始为空,追随在cur之后的问题
ListNode *pPre = nullptr;
ListNode *pCur = *pHead;
ListNode *pNext = nullptr;
while (pCUr != nullptr)
{
pNext = pCur->next;//衔接next
if(pCur don't need process)
{
pPre = pCur;//衔接pre
pCur = pNext;//衔接cur
}
else
{
//这里pCur 遍历的节点都被删除
//pNext进行了下移
while(pCur != nullptr || pCur need delete)
{
pNext = pCur->next;
delete pCur;
pCur = pNext;
}
if (pPre == nullptr)//头节点被删除了
{
*pHead = pNext;//特殊的头节点
//pre依旧停留在nullptr
pCur = pNext;//衔接cur
}
else
{
pPre->next = pNext;//衔接pre
pCur = pNext;//衔接cur
}
}
}
.可以匹配任意一个字符
*可以匹配0或任意一个前面的字符
核心:递归思路,逐步匹配每一个字符串
注意:这里str为’\0’时,也是可以和x*匹配的,这是(str,pattern+2)的来源
#include
bool matchCore(const char* str, const char* pattern)
{
if (*str == '\0' && *pattern == '\0')
return true;
if (*str != '\0' && *pattern == '\0')
return false;
//*str == '\0' && *pattern != '\0' 有可能会被x*匹配
if (*(pattern + 1) == '*')
{
//如果当前str匹配pattern
if (*str == *pattern || (*str != '\0' && *pattern == '.'))
{
return matchCore(str + 1, pattern);
}
//如果当前str不匹配pattern
else
{
//'\0'被连续的X*匹配
if (*str == '\0')
return matchCore(str, pattern + 2);
else
return matchCore(str + 1, pattern + 2);
}
}
else
{
if (*pattern == *str || (*pattern == '.' && *str != '\0'))
{
return matchCore(str + 1, pattern + 1);
}
else
{
return false;
}
}
}
bool match(const char* str, const char* pattern)
{
if (!str || !pattern)
return false;
return matchCore(str, pattern);
}
int main()
{
std::cout << "Hello World!\n";
}
A[.[B]][e|EC]
.B[e|EC]
A和C前面可以有+ - ,B前面不可以有+ -,ABC都是0-9
小数点后面可以没有小数,前面可以没有整数,.123 和123.都是成立的
// offer20 表示数值的字符串.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
//扫描正整数,并且把指针移动到正整数后面,如果没有扫描到则返回false
bool scanUnsignedInt(const char** str)
{
const char* before = *str;
while (**str != '\0' && **str > '0' && **str < '9')
++(*str);
return *str > before;
}
//扫描整数,并且把指针移动到正整数后面,如果没有扫描到则返回false
bool scanInt(const char** str)
{
if (**str == '+' || **str == '-')
++(*str);
return scanUnsignedInt(str);
}
bool isNumeric(const char* str)
{
if (!str)
return false;
//把整数部分全部扫描
bool isNum = scanInt(&str);
//如果前面的没有扫描到整数,那么不可能匹配到‘.’
if (*str == '.')
{
++str;
//小数点前或后可以没有数字
isNum = scanInt(&str) || isNum;
}
//前面的整数和‘.’都被扫面干净,现在看有没有e
if (*str == 'e' || *str == 'E')
{
++str
//e的前后必须要有数字
isNum = isNum && scanInt(&str);
}
//e之后的整数扫面之后,字符串必须结尾
return isNum && *str == '\0';
}
int main()
{
std::cout <
核心思想:头指针和尾指针,头指针向后移动直到移动到偶数,尾指针向前移动直到奇数。如果尾指针还在头指针后面,则交换两个指针的值
解耦:判断奇偶应该用函数指针判断,可以处理一大类问题
失误:注释部分即是失误部分
失误1
if(length < 0)//这里应该要length <= 0,因为end = arr+length-1;
return;
失误2
while (end > begin)
{
//end > begin是必须的,否则会导致指针溢出
while (end > begin && func(*begin))
++begin;
}
#include
using namespace std;
bool isOdd(int n)
{
return (n & 0x1) == 1;
}
void changeArray(int* arr, int length, bool (*func)(int))
{
if (arr == nullptr || length <= 0)
return;
int* begin = arr;
int* end = arr + length - 1;
while (end > begin)
{
//end > begin && 是必须的,否则会导致指针溢出
while (end > begin && func(*begin))//找到偶数数
++begin;
while (end > begin && !func(*end))//找到奇数
--end;
if (end > begin)
{
int tmp = *begin;
*begin = *end;
*end = tmp;
}
}
}
int main()
{
int arr1[] = { 1,5, 1, 1, 1, 1, 1, 1, 1, 3 };
changeArray(arr1, 10, isOdd);
for (auto i : arr1)
cout << i << " ";
return 0;
}
核心遍历思想:当遍历单向链表需要不止一次的时候,可以设定两个指针一前一后一起遍历,做到一次遍历解决问题。
解题思路:
设定指针ahead和behind,ahead先移动k-1位,然后ahead和behind一起移动,直到最后一个节点,这样behind指向的就是倒数第K个节点
陷阱:
// offer22获取单向链表倒数第k个节点.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
using namespace std;
struct ListNode
{
int m_nVal;
ListNode* m_pNext;
};
ListNode* findReverseK(ListNode* head, unsigned int k)
{
if (head == nullptr || k == 0)
return nullptr;
ListNode* ahead = head;
ListNode* behind = head;
for (unsigned int i = 0; i < k - 1; ++k)
{
if (ahead->m_pNext)
ahead = ahead->m_pNext;
else
return nullptr;
}
while (ahead->m_pNext)
{
ahead = ahead->m_pNext;
behind = behind->m_pNext;
}
return behind;
}
int main()
{
std::cout << "Hello World!\n";
}
设定两个指针p1 和p2
//head 不为空
//只有1个或2节点直接返回头节点
//p1每次走1步,p2每次走两步
//p2完成不了两步的时候,直接返回p1
//p2两步之后到达最后节点,直接返回p1
// offer22获取链表中间节点.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
struct ListNode
{
int m_nVal;
ListNode* m_pNext;
};
ListNode* findMidNode(ListNode* head)
{
//head 不为空
//只有1个或2节点直接返回头节点
//p1每次走1步,p2每次走两步
//p2完成不了两步的时候,直接返回p1
//p2两步之后到达最后节点,直接返回p1
if (!head)
return nullptr;
if (head->m_pNext == nullptr || head->m_pNext->m_pNext == nullptr)
return head;
ListNode* p1 = head;
ListNode* p2 = head;
while (p2->m_pNext)
{
if (p2->m_pNext->m_pNext == nullptr)
return p1;
p2 = p2->m_pNext->m_pNext;
p1 = p1->m_pNext;
}
return p1;
}
int main()
{
std::cout << "Hello World!\n";
}
核心思想
// offer24反转链表.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
struct ListNode
{
int m_nVal;
ListNode* m_pNext;
};
ListNode* listReverse(ListNode* head)
{
if (!head)
return nullptr;
if (head->m_pNext == nullptr)
return head;
ListNode* pre = nullptr;
ListNode* node = head;
ListNode* next = nullptr;
while (node != nullptr)
{
next = node->m_pNext;
node->m_pNext = pre;
pre = node;
node = next;
}
return pre;
}
int main()
{
std::cout << "Hello World!\n";
}
// offer25合并两个排序的链表.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
struct ListNode
{
int m_nVal;
ListNode* m_pNext;
};
ListNode* merge(ListNode* head1, ListNode* head2)
{
if (!head1)
return head2;
if (!head2)
return head1;
ListNode* mergeHead= nullptr;
if (head1->m_nVal > head2->m_nVal)
{
mergeHead = head2;
mergeHead->m_pNext = merge(head1, head2->m_pNext);
}
else
{
mergeHead = head1;
mergeHead->m_pNext = merge(head1->m_pNext, head2);
}
return mergeHead;
}
int main()
{
std::cout << "Hello World!\n";
}
分两步走
1和2都可以采用树的先序遍历,递归思路
如果有两层递归,则定义两个递归函数
bool hasChildTreeCore(BinaryTreeNode* pRoot1, BinaryTreeNode* pRoot2)
{
//检查空指针问题
//必须先检查root2
//如果roo2为空,说明root2的节点全部匹配成功,返回true;
//如果root1为空,说明root1已经没有节点来匹配了,返回false
//判断两个根节点是否相同
//先序同时遍历两颗树的左右子树的节点是否相等
if (!pRoot2)
return true;
if (!pRoot2)
return false;
bool result = false;
if (!doubleEqual(pRoot1->m_dVal, pRoot2->m_dVal))
return false;
return hasChildTreeCore(pRoot1->m_pLeft, pRoot2->m_pLeft) &&
hasChildTreeCore(pRoot1->m_pRight, pRoot2->m_pRight);
}
bool hasChildTree(BinaryTreeNode* pRoot1, BinaryTreeNode*pRoot2)
{
//先检查空指针问题
//对比两个根节点值是否相等
//相等,进入core函数判断子树是否相等
//不等,递归查看root1的左右子树和root2是否相等
if (pRoot1 == nullptr || pRoot2 == nullptr)//递归出口
return false;
if (doubleEqual(pRoot1->m_dVal, pRoot2->m_dVal))
{
return hasChildTreeCore(pRoot1, pRoot2);//递归出口
}
else
{
return hasChildTree(pRoot1->m_pLeft, pRoot2) ||//递归出口
hasChildTree(pRoot1->m_pRight, pRoot2);
}
}
// offer26树的子结构.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
struct BinaryTreeNode
{
double m_dVal;
BinaryTreeNode* m_pLeft;
BinaryTreeNode* m_pRight;
};
bool doubleEqual(double b1, double b2)
{
return (b1 - b2) < 1e-8 || (b2 - b1) < 1e-8;
}
bool hasChildTreeCore(BinaryTreeNode* pRoot1, BinaryTreeNode* pRoot2)
{
//检查空指针问题
//必须先检查root2
//如果roo2为空,说明root2的节点全部匹配成功,返回true;
//如果root1为空,说明root1已经没有节点来匹配了,返回false
//判断两个根节点是否相同
//先序同时遍历两颗树的左右子树的节点是否相等
if (!pRoot2)
return true;
if (!pRoot2)
return false;
bool result = false;
if (!doubleEqual(pRoot1->m_dVal, pRoot2->m_dVal))
return false;
return hasChildTreeCore(pRoot1->m_pLeft, pRoot2->m_pLeft) &&
hasChildTreeCore(pRoot1->m_pRight, pRoot2->m_pRight);
}
bool hasChildTree(BinaryTreeNode* pRoot1, BinaryTreeNode*pRoot2)
{
//先检查空指针问题
//对比两个根节点值是否相等
//相等,进入core函数判断子树是否相等
//不等,递归查看root1的左右子树和root2是否相等
if (pRoot1 == nullptr || pRoot2 == nullptr)//递归出口
return false;
if (doubleEqual(pRoot1->m_dVal, pRoot2->m_dVal))
{
return hasChildTreeCore(pRoot1, pRoot2);//递归出口
}
else
{
return hasChildTree(pRoot1->m_pLeft, pRoot2) ||//递归出口
hasChildTree(pRoot1->m_pRight, pRoot2);
}
}
int main()
{
std::cout << "Hello World!\n";
}
先序遍历每一个节点,如果节点的左右子树至少存在一个,则交换该节点的指针;如果到达叶节点,直接返回。注意空节点的处理
// offer27 树的镜像.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
struct BinaryNodeTree
{
int m_nVal;
BinaryNodeTree* m_pLeft;
BinaryNodeTree* m_pRight;
};
void mirrorTree(BinaryNodeTree* pRoot)
{
if (!pRoot)
return;
if (!pRoot->m_pLeft && !pRoot->m_pRight)
return;
BinaryNodeTree* temp = nullptr;
temp = pRoot->m_pLeft;
pRoot->m_pLeft = pRoot->m_pRight;
pRoot->m_pRight = temp;
mirrorTree(pRoot->m_pLeft);
mirrorTree(pRoot->m_pRight);
}
int main()
{
std::cout << "Hello World!\n";
}
对比二叉树的前序遍历序列和对称前序遍历序列来判断二叉树是否对称,如果两个序列是一样的,那么是对称。
要将nullptr指针考虑在内
#include
struct BinaryNodeTree
{
int m_nVal;
BinaryNodeTree* m_pLeft;
BinaryNodeTree* m_pRight;
};
bool isSymmetricalCore(BinaryNodeTree* pRoot1, BinaryNodeTree* pRoot2)
{
if (!pRoot1 && !pRoot2)
return true;
if (!pRoot1 || !pRoot2)
return false;
if (pRoot1->m_nVal != pRoot2->m_nVal)
return false;
return isSymmetricalCore(pRoot1->m_pLeft, pRoot2->m_pRight) &&
isSymmetricalCore(pRoot1->m_pRight, pRoot2->m_pLeft);
}
bool isSymmetrical(BinaryNodeTree* pRoot)
{
return isSymmetricalCore(pRoot, pRoot);
}
int main()
{
std::cout << "Hello World!\n";
}
bool isSymmetricalCore(BinaryNodeTree* pRoot1, BinaryNodeTree* pRoot2)
{
if (!pRoot1 && !pRoot2)//叶节点的nullptr比较
return true;
if (!pRoot1 || !pRoot2)//nullptr比较
return false;
if (pRoot1->m_nVal != pRoot2->m_nVal)//先序遍历比较,两个树的根节点
return false;
return isSymmetricalCore(pRoot1->m_pLeft, pRoot2->m_pRight) &&
isSymmetricalCore(pRoot1->m_pRight, pRoot2->m_pLeft);//先序遍历比较左右节点
}
bool isSymmetrical(BinaryNodeTree* pRoot)
{
return isSymmetricalCore(pRoot, pRoot);
}
分解
第一个循环确认一圈一圈是否可以打印
第二个循环打印每一圈
// offer29顺时针打印矩阵.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
#include
using namespace std;
void printCircleCore(int* matrix, int rows, int cols, int start)
{
int endRow = rows - 1 - start;
int endCol = cols - 1 - start;
//固定第start行打印
for (int col = start; col <= endCol; ++col)
cout << matrix[start * cols + col] << " ";
//固定第endCol列打印
for (int row = start + 1; row <= endRow; ++row)
cout << matrix[row * cols + endCol] << " ";
//固定第endRow行打印
for (int col = endCol - 1; col >= start; --col)
cout << matrix[endRow * cols + col] << " ";
//固定第start列打印
for (int row = endRow - 1; row > start; --row)
cout << matrix[row * cols + start] << " ";
}
void printCircle(int* matrix, int rows, int cols)
{
if (matrix == nullptr || rows <= 0 || cols <= 0)
return;
int start = 0;
while (rows > 2 * start && cols > 2 * start)
{
cout << start << ":\n";
printCircleCore(matrix, rows, cols, start);
cout << endl;
++start;
}
}
vector printMatrix(vector > matrix) {
int rows = matrix.size();
int cols = matrix[0].size();
vector printSerials;
for (int start = 0; (2 * start < rows) && (2 * start < cols); ++start)
{
int endRow = rows - 1 - start;
int endCol = cols - 1 - start;
//固定行,移动列
for (int col1 = start; col1 <= endCol; ++col1)
printSerials.push_back(matrix[start][col1]);
//固定列,移动行
for (int row1 = start + 1; row1 <= endRow; ++row1)
printSerials.push_back(matrix[row1][endCol]);
//固定行,移动列
for (int col2 = endCol - 1; col2 >= start; --col2)
printSerials.push_back(matrix[endRow][col2]);
//固定列,移动行
for (int row2 = endRow - 1; row2 >= start + 1; --row2)
printSerials.push_back(matrix[row2][start]);
}
return printSerials;
}
int main()
{
vector> mat;
vector m1 = { 1, 2, 3 };
vector m2 = { 4, 5, 6 };
vector m3 = { 7, 8, 9 };
mat.push_back(m1);
mat.push_back(m2);
mat.push_back(m3);
vector r = printMatrix(mat);
for (auto i : r)
cout << i << " ";
return 0;
}
一个可以实现min、push、pop的栈
思路:
设置一个辅助栈,每次栈需要push的时候,对辅助栈push一个min值(辅助栈栈顶元素或者被push的元素)。
每次需要pop的时候,辅助栈也跟着pop,保证辅助栈顶的最小值是栈的最小值
需要执行min的时候就对辅助栈执行top
// offer30含min函数的栈.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
#include
using namespace std;
template class StackWithMin
{
public:
void myPop();
void myPush(T element);
T myTop();
T min();
private:
stack s;
stack s_assistant;
};
template void StackWithMin::myPop()
{
//assert(s.size() > 0 && s_assistant.size() > 0);
s.pop();
s_assistant.pop();
}
template void StackWithMin::myPush(T element)
{
s.push(element);
T min = element;
if (s_assistant.size() > 0 && s_assistant.top() < element)
min = s_assistant.top();
s_assistant.push(min);
}
template T StackWithMin::myTop()
{
return s.top();
}
template T StackWithMin::min()
{
return s_assistant.top();
}
int main()
{
StackWithMin s;
s.myPush(1);
s.myPush(4);
s.myPush(0);
s.myPush(-1);
cout << "min = " << s.min();
s.myPop();
cout << "min = " << s.min();
return 0;
}
给定一个压栈序列A,判断某一序列B是否是出栈序列,假定序列中数字全部不一样
思路
设定一个辅助栈S,
下一个需要弹出的数字正好在S栈顶,那么直接弹出
下一个需要弹出的数字不在S栈顶,把压栈序列A中还没有入栈的序列入栈,直到压入需要弹出的数字为止
如果序列A为空,还没有找到那个出栈数字,则说明B不是出栈序列
如果序列B成功遍历完了且 S为空(S为空说明B的最后一个元素也符合),说明是弹出序列
// offer31栈的压入弹出序列.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
#include
using namespace std;
bool isPopOrder(const int* pushOrder, const int* popOrder, int length)
{
if (length < 0 || !pushOrder || !popOrder)
return false;
bool result = false;
stack s;
const int* pPush = pushOrder;
const int* pPop = popOrder;
while ((pPop - popOrder) < length)
{
while (s.empty() || s.top != *pPop)
{
if ((pPush - pushOrder) < length)
{
s.push(*pPush);
++pPush;
}
else
{
break;
}
}
if (s.top != *pPop)
break;
else
{
s.pop();
++pPop;
}
}
if ((pPop - popOrder) == length && s.empty())
result = true;
return result;
}
int main()
{
std::cout << "Hello World!\n";
}
思路:
从根节点开始打印
对每个需要打印的节点,将其子节点放入队列中
打印完该节点,从队列头取出一个节点,回到2操作
直到队列为空
有向图的广度优先遍历也是用队列实现的,先把起始结点放入队列,然后从头部取出节点,遍历这个节点可以到达的所有节点且依次放入队列。重复这个遍历过程,直至全部遍历完
#include
#include
using namespace std;
struct BinaryNodeTree
{
int m_nVal;
BinaryNodeTree* m_pLeft;
BinaryNodeTree* m_pRight;
};
void printTree(BinaryNodeTree* pRoot)
{
if (!pRoot)
return;
deque nodeQueue;
nodeQueue.push_back(pRoot);
while (!nodeQueue.empty())
{
BinaryNodeTree* node = nodeQueue.front();
nodeQueue.pop_front();
cout << node->m_nVal << " ";
if (node->m_pLeft)
nodeQueue.push_back(node->m_pLeft);
if (node->m_pRight)
nodeQueue.push_back(node->m_pRight);
}
}
int main()
{
std::cout << "Hello World!\n";
}
在上一题基础上,需要两个变量,v1记录本层未打印节点数,v2记录下层节点数
v1先置为1(根节点) v2置为0
根节点的子节点进入队列,v2++
根节点打印,v1–
v1==0时打印\n,然后执行v1 = v2, v2 = 0
重复
#include
#include
using namespace std;
struct BinaryNodeTree
{
int m_nVal;
BinaryNodeTree* m_pLeft;
BinaryNodeTree* m_pRight;
};
void printTree(BinaryNodeTree* pRoot)
{
if (!pRoot)
return;
deque nodeQue;
nodeQue.push_back(pRoot);
int toBePrint = 1;
int nextBePrint = 0;
while (!nodeQue.empty())
{
BinaryNodeTree* node = nodeQue.front();
nodeQue.pop_front();
--toBePrint;
if (node->m_pLeft)
{
nodeQue.push_back(node->m_pLeft);
++nextBePrint;
}
if (node->m_pRight)
{
nodeQue.push_back(node->m_pRight);
++nextBePrint;
}
cout << node->m_nVal << " ";
if (toBePrint == 0)
{
cout << "\n";
toBePrint = nextBePrint;
nextBePrint = 90;
}
}
}
int main()
{
std::cout << "Hello World!\n";
}
// offer32二叉树层序遍历(之字形).cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
#include
using namespace std;
struct BinaryNodeTree
{
int m_nVal;
BinaryNodeTree* m_pLeft;
BinaryNodeTree* m_pRight;
};
void printTree(BinaryNodeTree* pRoot)
{
if (!pRoot)
return;
stack stackDuet[2];
int stackIndex = 0;
stackDuet[stackIndex].push(pRoot);
while (!stackDuet[0].empty() || !stackDuet[1].empty())
{
BinaryNodeTree* node = stackDuet[stackIndex].top();
stackDuet[stackIndex].pop();
cout << node->m_nVal << " ";
if (stackIndex == 0)
{
if(node->m_pLeft)
stackDuet[1].push(node->m_pLeft);
if (node->m_pRight)
stackDuet[1].push(node->m_pRight);
}
else
{
if (node->m_pRight)
stackDuet[0].push(node->m_pRight);
if (node->m_pLeft)
stackDuet[0].push(node->m_pLeft);
}
if (stackDuet[stackIndex].empty())
{
++stackIndex;
stackIndex %= 2;
cout << "\n";
}
}
}
int main()
{
printTree(pRoot1);
return 0;
}
二叉搜索树的左子树比根小,右子树比根大
中序遍历是一个升序序列
思路:
后续遍历序列的最后一个节点为根,根的左子树全部比根小,右子树全部比根大,序列被分程两部分。
递归下去,直到所有子序列只有一个或0个数停止,返回true;
期间如果违背了后续遍历的根节点可将序列切分规则,则返回false;(例如找到序列中第一个比自己大的数,那么之前的数都是左子树,之后的数都是右子树,但如果右子树序列不是全部大于根节点则出错)
举一反三
如果要处理二叉树的遍历序列,先找到根节点,基于根节点把序列拆分成左右子树对应的子序列,然后递归地处理这两个子序列
#include
using namespace std;
struct BinaryNodeTree
{
int m_nVal;
BinaryNodeTree* m_pLeft;
BinaryNodeTree* m_pRight;
};
bool isPostOrder(int* order, int length)
{
if (!order || length <= 0)
return false;
int root = order[length - 1];
int i = 0;
while (order[i] > root)
++i;
int j = i;
while (j < length)
{
if (order[j] < root)
return false;
}
bool left = true;
if (i > 0)
left = isPostOrder(order, i);
bool right = true;
if ((length-1-i) > 0)
right = isPostOrder(order + i, length - 1 - i);
return left && right;
}
int main()
{
std::cout << "Hello World!\n";
}
先序遍历的过程中,会从根节点触及到叶节点,然后触及另一个子树的叶节点。
采用栈去保留每一次根到叶的路径,如果路径和满足要求,那么打印这个栈。
不管满足与否触及到叶节点都要返回上面的节点,此时栈需要把叶节点pop出去
如果某一个节点的左右子树的路径都完成探索,那么这个节点也要pop出去
采用递归方式去探索,并且左右子树探索完毕,栈需要回退
调用递归的函数声明这个栈,在每次探索中都栈保存状态
#include
#include
using namespace std;
struct BinaryNodeTree
{
int m_nVal;
BinaryNodeTree* m_pLeft;
BinaryNodeTree* m_pRight;
};
void findPathCore(BinaryNodeTree* pRoot, vector path, int sum, int targetValue)
{
path.push_back(pRoot->m_nVal);
sum += pRoot->m_nVal;
if (!pRoot->m_pLeft && !pRoot->m_pRight && sum == targetValue)
{
//auto iter = path.begin();
vector::iterator iter = path.begin();
for (; iter != path.end(); ++iter)
cout << *iter << " ";
cout << "\n";
}
if (pRoot->m_pLeft)
findPathCore(pRoot->m_pLeft, path, sum, targetValue);
if (pRoot->m_pRight)
findPathCore(pRoot->m_pRight, path, sum, targetValue);
path.pop_back();
}
void findPath(BinaryNodeTree* pRoot, int targetValue)
{
if (!pRoot)
return;
vector path;
int sum = 0;
findPathCore(pRoot, path, sum, targetValue);
}
int main()
{
std::cout << "Hello World!\n";
}
思路:
二叉搜索树的中序遍历正好是升序序列,使用中序遍历递归算法对每个节点进行转换
先设置一个ListLastNode节点(一开始值为null),始终指向链表中最大(最后)的一个节点。
每次遍历树节点时,对被操作的节点执行修改指针操作:left指向ListLastNode,ListLastNode指向被操作的节点,right设置为nullptr。然后把ListLastNode更新
注意失误
listLastNode是一个指针,不断最终递归过程,不能传值,要传指针***
// offer36二叉搜索树与双向链表.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
using namespace std;
struct BinaryNodeTree
{
int m_nVal;
BinaryNodeTree* m_pLeft;
BinaryNodeTree* m_pRight;
};
void convertCore(BinaryNodeTree* pRoot, BinaryNodeTree** pListLastNode)
{
if (!pRoot)
return;
if (!pRoot->m_pLeft)
{
convertCore(pRoot, pListLastNode);
}
pRoot->m_pLeft = *pListLastNode;
if (!(*pListLastNode))
{
(*pListLastNode)->m_pRight = pRoot;
}
*pListLastNode = pRoot;
if (!pRoot->m_pRight)
{
convertCore(pRoot, pListLastNode);
}
}
BinaryNodeTree* convert(BinaryNodeTree* pRoot)
{
if (!pRoot)
return nullptr;
BinaryNodeTree* pListLastNode = nullptr;
convertCore(pRoot, &pListLastNode);
while (!pListLastNode->m_pLeft)
{
pListLastNode = pListLastNode->m_pLeft;
}
return pListLastNode;
}
int main()
{
std::cout << "Hello World!\n";
}
无论序列化还是反序列化,都需要首先识别根节点,这样可以做到流处理,意味着先序遍历
对于空指针使用非数字字符代替,用于标记
// offer37序列化二叉树.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
using namespace std;
struct BinarNodeTree
{
int m_nVal;
BinarNodeTree* m_pLeft;
BinarNodeTree* m_pRight;
};
void serialize(BinarNodeTree* pRoot, ostream& os)
{
if (!pRoot)
{
os << "$" << " ";
return;
}
os << pRoot->m_nVal << " ";
serialize(pRoot->m_pLeft, os);
serialize(pRoot->m_pRight, os);
}
void deSerialize(BinarNodeTree** ppRoot, istream& is)
{
int number = 0;
if (is >> number)
{
*ppRoot = new BinarNodeTree;
(*ppRoot)->m_nVal = number;
(*ppRoot)->m_pLeft = nullptr;
(*ppRoot)->m_pRight = nullptr;
deSerialize(&((*ppRoot)->m_pLeft), is);
deSerialize(&((*ppRoot)->m_pRight), is);
}
}
int main()
{
BinarNodeTree* pRoot = nullptr;
BinarNodeTree** ppRoot = &pRoot;
deSerialize(ppRoot, cin);
serialize(pRoot, cout);
}
将字符串分成首字符,和剩下字符串
首字符和剩下字符串的每一位进行交换
1.注意,首字符也要和首字符进行交换,确保原排列也在
交换后,剩下字符串重复以上过程
递归的出口是首字符是最后一个,那么直接打印即可出来这个递归栈的排列
// offer38字符串的排列.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
using namespace std;
void permutation(char* str, char* begin)
{
if (*begin == '\0')
{
printf("%s\n", str);
return;
}
for (char* pos = begin; *pos != '\0'; ++pos)
{
//首字符与后面每一个字符进行交换,包括交换他自己(自己也是排列中的一种)
char temp = *begin;
*begin = *pos;
*pos = temp;
//对后面的字符串进行相同操作
permutation(str, begin + 1);
//每次循环交换后需要恢复原样,为下一个循环的交换恢复现场
temp = *begin;
*begin = *pos;
*pos = temp;
}
}
int main()
{
char str[1000] = { '\0' };
cin >> str;
permutation(str, str);
}
相关题目
立方体的排列,先求到所有排列,对每个排列检查是否符合立方体规则
8*8象棋的八个皇后:定义一个数组clos[8],表示每一行的皇后所处的列号,用0-7初始化数组;该数组必然是满足所有皇后不同行不同列的,但不满足不同对角线
是不是在相同对角线判别:对两个下标i、j,有i-j=clos[i]-clos[j]或者j-i=clos[i]-clos[j]
解法1:
设定一个变量记录数字,另一个变量记录该数字的出现次数。
初始为第一个数字,记录数为1
遇见下一个数字相同加1,不同减1
如果遇见下一个数字时记录数为0,则替换数字,置记录数为1
最后记录数大于等于1的数有可能是出现次数超过>一半的数(记录数为0,则可能是出现次数=一半的数)(注意是可能)
通过一次遍历,判断是否是
解法2:基于partition
如果数字出现次数超过一半,那么排序之后,中位数一定是那个数字。(但中位数不一定出现次数超过一半)
随机选取一个数字做partition,如果该数字在中位,说明该数字是中位数,否则通过二分法查找中位数
查找到中位数再用一次遍历进行判断是否是
// offer39求数组中出现次数超过一半的数组.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
using namespace std;
bool checkInput(int* arr, int length)
{
bool result = true;
if (!arr || length <= 0)
result = false;
return result;
}
bool isMoreThanHalf(int* arr, int length, int number)
{
int sum = 0;
for (int cruisor = 0; cruisor < length; ++cruisor)
{
if (arr[cruisor] == number)
++sum;
}
if (sum > length >> 1)
return true;
return false;
}
int findMoreThanHalf_(int* arr, int length)
{
if (!checkInput(arr, length))
return -1;
int number = arr[0];
int times = 1;
for (int i = 1; i < length; ++i)
{
if (times == 0)
{
number = arr[i];
times = 1;
continue;
}
if (arr[i] == number)
{
++times;
}
else
{
--times;
}
}
if (times >= 1)
{
if (isMoreThanHalf(arr, length, number))
return number;
}
return -1;
}
int main()
{
int arr[] = { 1, 2, 3, 3, 4, 1, 1 };
std::cout << findMoreThanHalf_(arr, 7);
return 0;
}
解法1:O(n)的partition解法,但是会修改数组
使用partition的二分递归法,排列到index位k的坐标,左边的k个数是最小的
解法2:适合海量数据的O(nlogk)算法
选取一个最大堆,遍历n个数,如果堆中最大数大于被遍历的数,那么删除最大数,插入这个数。所以时间是O(nlogK)
一开始要建立这个堆。堆可以在O(logK)完成删除插入操作
可以使用红黑树来代替这个堆,红黑树的查找、删除和插入都是O(logK)
比如STL的set和mulitiset
multiset采用降序排列greater,这样就容器begin()就是最大值
// offer40最小的K个数.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
#include
#include
#include
using namespace std;
typedef multiset> IntSet;
void findKMin(vector data, IntSet& minset, int k)
{
minset.clear();
if (k <= 0 || data.size() < k)
return;
auto iter = data.begin();
for (; iter != data.end(); ++iter)
{
if (minset.size() < k)
{
minset.insert(*iter);
}
else
{
if (*(minset.begin()) > * iter)
{
minset.erase(minset.begin());
minset.insert(*iter);
}
}
}
}
int partition(int* arr, int length, int start, int end)
{
if (!arr || length <= 0 || start < 0 || end > length - 1 || start > end)
throw exception("invalid parameters.");
int pilot = start;
swap(arr[pilot], arr[end]);
int small = start - 1;
for (int index = start; index < end; ++index)
{
if (arr[index] < arr[end])
{
++small;
if (small != index)
swap(arr[small], arr[index]);
}
}
swap(arr[++small], arr[end]);
return small;
}
void findKCore(int* arr, int length, int k)
{
if (!arr || length <= 0 || k > length || k < 1)
throw exception("invalid parameters.");
int start = 0;
int end = length - 1;
int index = partition(arr, length, start, end);
while (index != k - 1)
{
if (index < k - 1)
{
start = index + 1;
index = partition(arr, length, start, end);
}
else
{
end = index - 1;
index = partition(arr, length, start, end);
}
}
}
void findKmin(int* arr, int length, int k)
{
if (!arr || length <= 0 || k <= 0 || k > length)
return;
findKCore(arr, length, k);
for (int i = 0; i < k; ++i)
cout << arr[i] << " ";
}
int main()
{
int arr[] = { 100, -5, -8, 2, 10,
32, 2, 54, 34, 22,
786, 1, 0, -1, 8 };
vector data;
for (int i = 100; i > 0; --i)
{
data.push_back(i);
}
IntSet minSet;
findKMin(data, minSet, 10);
auto iter = minSet.begin();
for (; iter != minSet.end(); ++iter)
cout << *iter << " ";
return 0;
}
将数据流流入两个容器,大堆装小的一半,小堆装大的一半
为了确保两个堆的数目最多相差1个数,规定,奇数放大堆,偶数放小堆
如果奇数时,该数大于小堆最小(大失误:!!!注意先检查该堆是否存在元素),那么先放入小堆,然后把小堆最小的数拿出来放入大堆
如果偶数时,该数小于大堆最大(大失误:!!!注意先检查该堆是否存在元素),那么先放入大堆,然后把大堆最大的数拿出来放入小堆
取中位数时,如果总数是奇数,那么大堆的数要多1,所以把大堆的最大数当作中位数;如果总数是偶数,那么大小堆各出一个数平均一下;
#include
#include
#include
using namespace std;
template class MiddleNum
{
public:
void insert(T element)
{
int dataSum = maxHeap.size() + minHeap.size();
++dataSum;
if ((dataSum & 1) == 1)//插入大堆
{
if (minHeap.size() > 0 && element > minHeap[0])//如果元素比小堆的顶部元素还要小
{
//把元素压入小堆
minHeap.push_back(element);
push_heap(minHeap.begin(), minHeap.end(), greater());
//再从小堆取出顶部元素
element = minHeap[0];
//更新小堆
pop_heap(minHeap.begin(), minHeap.end(), greater());
minHeap.pop_back();
}
maxHeap.push_back(element);
push_heap(maxHeap.begin(), maxHeap.end(), less());
}
else//插入小堆
{
if (maxHeap.size() > 0 && element < maxHeap[0])//如果元素比大堆的顶部元素还要大
{
//把元素压入大堆
maxHeap.push_back(element);
push_heap(maxHeap.begin(), maxHeap.end(), less());
//再从大堆取出顶部元素
element = maxHeap[0];
//更新大堆
pop_heap(maxHeap.begin(), maxHeap.end(), less());
maxHeap.pop_back();
}
minHeap.push_back(element);
push_heap(minHeap.begin(), minHeap.end(), greater());
}
}
T getMiddleNum()
{
int dataSum = maxHeap.size() + minHeap.size();
if (dataSum == 0)
throw exception("have no data in MiddleNum.");
T middle = 0;
if ((dataSum & 1) == 1)//大堆的最大元素是中位数
{
return maxHeap[0];
}
else//中位数需要两个堆各取出一个,取平均值
{
return (maxHeap[0] + minHeap[0])/2;
}
}
private:
vector maxHeap;
vector minHeap;
};
int main()
{
MiddleNum contener;
int number = 0;
while (cin >> number)
{
contener.insert(number);
}
std::cout << contener.getMiddleNum() << endl;
return 0;
}
STL堆算法
堆是一个近乎完全的完全二叉树,所以可以按照层序放入数组中存储;
vector和pop_heap(begin, end, greater<>(小堆) | less<>(大堆))、push_heap(begin, end, greater<>(小堆) | less<>(大堆))可以维护一个大堆或者小堆
以大堆距离
入堆
vector.push_back(element);//压入数组的末尾
push_heap(vector.begin(), vector.end(), less());使用函数调整维护大堆
出堆
int ele = vector[0];//是这样取堆顶元素的
pop_heap(vector.begin(), vector.end(), less());//把大堆中的根节点和最后的末尾节点对调,然后调整维护除末尾以外的堆
vector.pop_back();//把末尾原本是大堆堆顶的元素时放掉
思路:O(n)解法
从第一个元素开始相加,记录当前sum值
循环对下一个元素进行相加,在相加之前,
如果sum<=0,说明之前的子数列完全没有作用,将之舍弃,令sum等于下一个元素
如果sum>0, 则sum+下一个元素
在循环过程中记录sum出现的最大值
补漏:
int 最大值 (1<<31)-1 INT_MAX
int最小值 1<<31 INT_MIN
// offer42数组中最大子序列和.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
using namespace std;
bool isInputValid = true;
int getMaxSum(int* arr, int length)
{
isInputValid = true;
if (!arr || length <= 0)
{
isInputValid = false;
return 0;
}
int sum = 0;
int maxsum = INT_MIN;
for (int cruisor = 0; cruisor < length; ++cruisor)
{
if (sum <= 0)
sum = arr[cruisor];
else
sum += arr[cruisor];
if (sum > maxsum)
maxsum = sum;
}
return maxsum;
}
int main()
{
int arr[] = { 1, -2, 3, 10, -4, 7, 2, -5 };
std::cout <
思路:
数字是有规律的
1位数有10个,二位数90个,三位数900个。。。
数字序列某一位n,那么这个数字序列一定要满足N位数的序列长度大于n,自此可以定位到这个n序列数是几位数,在几位数中的序列是排第m位
而几位数的数字长度是固定的k,m/k就是几位数的第几个数
m%k就是这个数出现的第几位
函数:
判断index指向的会是几位数
统计几位数一共有多少个数
知道index指向于几位数时,找出那个数,并指向那个数的对应数字
几位数的起始数是啥
// offer44数字序列中某一位数字.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
int digitNumberBegin(int digit)
{
if (digit == 1)
return 0;
return (int)pow(10, digit - 1);
}
int digitNumberCount(int digit)
{
if (digit == 1)
return 10;
return 9 * (int)pow(10, digit - 1);
}
int numberIndex(int index, int digit)
{
int number = digitNumberBegin(digit) + (index + 0) / digit;
int indexFromRight = digit - (index + 0) % digit;
for (int i = 1; i < indexFromRight; ++i)
number /= 10;
return number % 10;
}
int getNumberIndex(int index)
{
if (index < 0)
return -1;
int digit = 1;
while (true)
{
if (digitNumberCount(digit)*digit > index)
return numberIndex(index, digit);
index -= digitNumberCount(digit) * digit;
digit++;
}
}
int main()
{
std::cout << getNumberIndex(19);
}
核心,对于数字m和n,如果mn > nm 则说明m > n,要排得最小数n必须在前面。
可以考虑使用字符串比较来比较大小,对数字序列进行排序,升序排序就是最小的数。
1.声明一个字符串数组,每个元素是char*,用于存每个数的字符串
2.使用qsort对字符串数组排序,需要编写cmp函数
3.cmp函数比较字符串mn大还是nm大
失误:
不要忘记释放动态内存
小技巧:
char** numberStrArray = (char**)new int[length];//申请存放字符指针的数组
int compare(const void * a, const void * b)//qsort cmp
{
return (*(int *)a - *(int *)b);//不能直接是(int)a
return strcpy(g_comcatStr1, *(const char**)str1);//不能直接是char*
}
TIPs:
qsort头文件stdlib.h
strcat/strcmp/strcpy头文件string.h
memset memcpy头文件string.h
sprintf头文件stdio.h
#include
#include
#include
#include
using namespace std;
const int stringSize = 10;
char g_comcatStr1[2 * stringSize + 1];
char g_comcatStr2[2 * stringSize + 1];
int strcatcmp(const void* str1, const void* str2)
{
strcpy(g_comcatStr1, *(const char**)str1);
strcat(g_comcatStr1, *(const char**)str2);
strcpy(g_comcatStr2, *(const char**)str2);
strcat(g_comcatStr2, *(const char**)str1);
return strcmp(g_comcatStr1, g_comcatStr2);
}
void minNumber(int* numbers, int length)
{
if (!numbers || length <= 0)
return;
char** numberStrArray = (char**)new int[length];
for (int i = 0; i < length; ++i)
{
numberStrArray[i] = new char[stringSize + 1];
sprintf(numberStrArray[i], "%d", numbers[i]);
}
qsort(numberStrArray, length, sizeof(char*), strcatcmp);
for (int i = 0; i < length; ++i)
printf("%s", numberStrArray[i]);
printf("\n");
for (int i = 0; i < length; ++i)
delete[] numberStrArray[i];
delete[] numberStrArray;
}
int main()
{
int arr[] = { 1, 2, 3 , 4, 5, 6, 7, 8 };
minNumber(arr, 8);
return 0;
}
可以递归进行翻译,但是会出现重复子问题,重复子问题在数字串的后面出现
可以从数字末尾开始翻译,并把子问题存储起来。
递归方法:
如果连续两个字符是10~25,那么可以分成两个字母表示或者组合成一个字符表示
如果连续两个字符无法满足10~25,那么只能分成两个字母表示
出口是index走完字符串,返回1,表示一种表达方式
// offer46把数字翻译成字符串.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
#include
#include
using namespace std;
int translateCore(char* numberStr, int length, int index)
{
if (index > length - 1)
return 1;//如果走到底,说明这种表述方法加1
if (index + 1 <= length - 1)
{
int val = (numberStr[index] - '0') * 10 + (numberStr[index + 1] - '0');
if (val >= 10 && val <= 25)//一种情况
return translateCore(numberStr, length, index + 1) + translateCore(numberStr, length, index + 2);
}
return translateCore(numberStr, length, index + 1);//另一种情况
}
void translate(int number)
{
char numberStr[10];
sprintf_s(numberStr, "%d", number);
int length = strlen(numberStr);
cout << translateCore(numberStr, length, 0);
}
int main()
{
translate(12258);
}
动态规划:
自下而上,动态规划,从最小的问题开始 :
f®表示以r为开始(r最小取0)到最右端所组成的数字能够翻译成字符串的种数。对于长度为n的数字,f(n)=0,f(n-1)=1,求f(0)。
递推公式为 f(r-2) = f(r-1)+g(r-2,r-1)*f®;
其中,如果(r-2,r-1)组合起来能够翻译成字符,则g(r-2,r-1)=1,否则为0。
比如1 5 8中1 和 5字符可以组合,所以f(字符1) = f(字符5——不组合) + f(字符8–15组合)
int translate_dp(int number)
{
if (number < 0)
return -1;
string numberStr = to_string(number);
int length = numberStr.length();
int* count = new int[length + 1];
count[length] = 0;
count[length - 1] = 1;
for (int index = length - 2; index >= 0; --index)
{
int number1 = numberStr[index] - '0';
int number2 = numberStr[index + 1] - '0';
int numberComcat = number1 * 10 + number2;
if (numberComcat >= 10 && numberComcat <= 25)
count[index] = count[index + 1] + count[index + 2];
else
count[index] = count[index + 1];
}
return count[0];
}
递归:
int maxValue(int* gift, int rows, int cols, int row, int col)
{
if (!gift || rows <= 0 || cols <= 0 || row < 0 || col < 0 || row > rows - 1 || col > cols - 1)
throw exception("invalid parameter.");
if (row == 0 && col == 0)//递归的最底层
return gift[0];
int max1 = INT_MIN;
int max2 = INT_MIN;
if (row - 1 >= 0)
max1 = maxValue(gift, rows, cols, row - 1, col);
if (col - 1 >= 0)
max2 = maxValue(gift, rows, cols, row, col - 1);
return gift[row * cols + col] + (max1 > max2 ? max1 : max2);//递归出口
}
动态规划:
定义一个函数f(i, j)表示到达坐标位ij的格子是能拿到的礼物总和的最大值
f(i, j) = max{f(i-1, j), f(i, j-1) + gift[i,j]}
借助辅助二维数组,缓存中间计算结果
动态规划:
记f(i)为以i下标的字符结尾的最长不含重复字符的字符串长度
如果第i个字符在之前的扫描中(0---- i-1)没有出现,则f(i)=f(i-1)+1
如果第i个字符在之前的扫描中(0---- i-1)出现过,假设出现的字符到i字符之间的下标距离为d
int maxNoDupStrLen(const string& str)
{
int length = str.length();
if (length == 0)
return 0;
int currentLen = 0;
int maxLen = 0;
int* letterIndexTable = new int[26];
for (int i = 0; i < 26; ++i)
letterIndexTable[i] = -1;
for (int index = 0; index < length; ++index)
{
int letter = str[index] - 'a';
int preIndex = letterIndexTable[letter];
letterIndexTable[letter] = index;
int distanceOfLetter = index - preIndex;
if (preIndex < 0 || distanceOfLetter > currentLen)
++currentLen;
else
{
//最容易犯错的地方
if (currentLen > maxLen)
maxLen = currentLen;
currentLen = distanceOfLetter;
}
}
//最容易犯错的地方
if (currentLen > maxLen)
maxLen = currentLen;
delete[] letterIndexTable;
return maxLen;
}
丑数的定义: 只能被2, 3, 5整除为1的数,即因数只包含2, 3, 5
bool isUglyNumber(int number)
{
while (number % 2 == 0)
number /= 2;
while (number % 3 == 0)
number /= 3;
while (number % 5 == 0)
number /= 5;
return number == 1;
}
空间换时间的解法
核心:丑数是另一个丑数乘以2/3/5的结果
确保只计算丑数不计算非丑数
使用数组保存已找到的丑数(已排序,其中最大丑数是M),计算下一个丑数时,必然是之前的某个丑数分别乘以2,3,5的结果,分别得到三个第一个大于M2、M3、M5的丑数即可,从M2、M3、M5选取最小数加入数组
// offer49丑数.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
int minUgly(int a, int b, int c)
{
int d = (a < b) ? a : b;
return (c < d) ? c : d;
}
int getUglyNumber(int index)
{
if (index <= 0)
return -1;
int* uglyNumbers = new int[index];
uglyNumbers[0] = 1;
int nextIndex = 1;
int* pMulti2 = uglyNumbers;
int* pMulti3 = uglyNumbers;
int* pMulti5 = uglyNumbers;
while (nextIndex < index)
{
int min = minUgly(*pMulti2 * 2, *pMulti3 * 3, *pMulti5 * 5);
uglyNumbers[nextIndex] = min;
//pMulti2指针无须重置,因为前面遍历的三个指针必然无法超越最新的丑数,
// 经过循环后是第一个超过最新丑数的下表
while (*pMulti2 * 2 <= uglyNumbers[nextIndex])
++pMulti2;//更新除第一个*pMulti2 * 2会大于最新丑数的下标
while (*pMulti3 * 3 <= uglyNumbers[nextIndex])
++pMulti3;
while (*pMulti5 * 5 <= uglyNumbers[nextIndex])
++pMulti5;
++nextIndex;
}
int result = uglyNumbers[index - 1];
delete[] uglyNumbers;
return result;
}
int main()
{
std::cout << getUglyNumber(1500);
return 0;
}
hashtable解决
类似问题有
#include
#include
#include
using namespace std;
class GetFirstOnceInStream
{
public:
GetFirstOnceInStream()
{
for (int i = 0; i < 256; ++i)
hashTable[i] = -1;
}
void insert(char& ch)
{
charStream.push_back(ch);
if (hashTable[ch] != -2)
{
if (hashTable[ch] == -1)
hashTable[ch] = charStream.size() - 1;
else
hashTable[ch] = -2;
}
}
char getFirstOnce()
{
int minIndex = INT_MAX;
char ch = '\0';
for (int i = 0; i < 256; ++i)
{
if (hashTable[i] >= 0 && minIndex > hashTable[i])
{
ch = (char)i;
minIndex = hashTable[i];
}
}
return ch;
}
private:
int hashTable[256];
vector charStream;
};
char* getNoDupStr(const char* pString1, const char* pString2)
{
if (!pString1 || !pString2)
return nullptr;
char* newString = new char[strlen(pString1) + 1];
char hashTable[256] = { 0 };
const char* cruisor = pString2;
while (*cruisor != '\0')
hashTable[*(cruisor++)]++;
char* writer = newString;
cruisor = pString1;
while (*cruisor != '\0')
{
if (hashTable[*cruisor])
{
cruisor++;
continue;
}
*writer = *cruisor;
writer++;
cruisor++;
}
*writer = '\0';
return newString;
}
char findFirestOnlyOnce(const char* pString)
{
if (!pString)
return -1;
char hastable[256] = { 0 };
const char* cruisor = pString;
while (*cruisor != '\0')
hastable[*(cruisor++)]++;
cruisor = pString;
while (*cruisor != '\0')
{
if (hastable[*(cruisor++)] == 1)
break;
}
if (*cruisor == '\0')
return -1;
return *(cruisor - 1);
}
int main()
{
cout << findFirestOnlyOnce("abaccdeff") << endl;
cout << getNoDupStr("We are students.", "aeiou") << endl;
char ch;
GetFirstOnceInStream gfois;
while (cin >> ch)
{
gfois.insert(ch);
}
cout << gfois.getFirstOnce() << endl;
cin.clear();
while (cin >> ch)
{
gfois.insert(ch);
}
cout << gfois.getFirstOnce() << endl;
return 0;
}
核心:归并排序
思路:先把数组分割成子数组,统计子数组内部逆序对,在统计两个相邻子数组的逆序对,统计过程中对合并数组进行归并排序
代码技巧:
copy和data两个数组轮换着使用
失误:
delete[] 忘记
二分法的时候start end len = (end - start)/2
此时[start, start + len - 1]会出错,导致越界,可能新end比start还要小,因为len=0
// offer51数组中的逆序对.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
int inversePairCore(int* data, int* copy, int start, int end)
{
if (start == end)
{
copy[start] = data[start];
return 0;
}
int halfCutLen = (end - start) / 2;
int left = inversePairCore(copy, data, start, start + halfCutLen);
int right = inversePairCore(copy, data, start + halfCutLen + 1, end);
int leftCruisor = start + halfCutLen;
int rightCruisor = end;
int copyCruisor = end;
int count = 0;
while (start <= leftCruisor && (start + halfCutLen + 1) <= rightCruisor)
{
if (data[leftCruisor] > data[rightCruisor])
{
count += rightCruisor - start - halfCutLen;
copy[copyCruisor--] = data[leftCruisor--];
}
else
{
copy[copyCruisor--] = data[rightCruisor--];
}
}
while (start <= leftCruisor)
copy[copyCruisor--] = data[leftCruisor--];
while ((start + halfCutLen + 1) <= rightCruisor)
copy[copyCruisor--] = data[rightCruisor--];
return count + left + right;
}
int inversePair(int* data, int length)
{
if (!data || length <= 0)
return -1;
int* copy = new int[length];
for (int i = 0; i < length; ++i)
copy[i] = data[i];
int count = inversePairCore(data, copy, 0, length - 1);
delete[] copy;
return count;
}
int main()
{
int arr[] = { 7, 5, 6, 4 };
std::cout << inversePair(arr, 4);
return 0;
}
方法1:用两个栈存储两个链表的下一个节点的指针,从栈顶开始比较每个节点是否相同,知道找到最后一个相同的节点
方法2:统计两个链表长度,从相同长度地方开始遍历,第一个next相同的地方,next所指的就是第一个公共节点
// offer52链表的第一个公共节点.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
using namespace std;
struct ListNode
{
int m_nVal;
ListNode* m_pNext;
};
int lengthOfList(ListNode* pHead)
{
if (!pHead)
return -1;
int length = 0;
ListNode* pCruisor = pHead;
while (pCruisor != null)
{
++length;
pCruisor = pCruisor->m_pNext;
}
return length;
}
ListNode* firstComNode(ListNode* pHead1, ListNode* pHead2)
{
if (!pHead1 || !pHead2)
return nullptr;
int length1 = lengthOfList(pHead1);
int length2 = lengthOfList(pHead2);
ListNode* pCruisor1 = pHead1;
ListNode* pCruisor2 = pHead2;
if (length1 > length2)
{
for (int i = 0; i < length1 - length2; ++i)
pCruisor1 = pCruisor1->m_pNext;
}
if (length2 > length1)
{
for (int i = 0; i < length2 - length1; ++i)
pCruisor2 = pCruisor2->m_pNext;
}
while (pCruisor1 != pCruisor2)
{
pCruisor1 = pCruisor1->m_pNext;
pCruisor2 = pCruisor2->m_pNext;
}
return pCruisor1;
}
int main()
{
std::cout << "Hello World!\n";
}
O(logN)的办法:通过二分法查找第一个K和最后一个K
查找第一个K:每次从中间取一个数n,如果n
如果查找不到k(start > end)return -1;
方法1:利用公式n(n-1)/2求出数字0到n-1的所有数字之和s1,再遍历求出所有数字s2,缺失数字就是s1-s2
方法2:利用递增的性质,数组一开始的数字和下标是相同的,直到不在数组中的数,后面的数字比下标大1;
基于二分法,如果中间元素值和下标相等,在右边查找;如果中间元素值与下标不等
// offer53查到0~n-1中缺失的数字.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
using namespace std;
int getMissingNumberCore(int* numbers, int length)
{
if (!numbers || length <= 0)
return -1;
int start = 0;
int end = length - 1;
while (start <= end)
{
int middle = (start + end) >> 1;
if (middle == numbers[middle])
{
start = middle + 1;
}
else
{
if (middle == start || (middle - 1) == numbers[middle - 1])
return middle;
else
end = middle - 1;
}
}
return -1;
}
int main()
{
int arr[] = { 0, 1, 2, 3, 4, 5, 7, 8 };
int arr1[] = { 0, 1, 2, 3, 4, 5, 6, 7, 9 };
std::cout << getMissingNumberCore(arr1
, 9);
}
普通方法:遍历
二分法:
如果middle与元素相等返回,
如果middle大于元素,查找右边
如果middle小于元素,查找左边
// offer53查找升序数组中等于下标的元素.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
using namespace std;
int getElementAsIndex(int* number, int length)
{
if (!number || length <= 0)
return -1;
int start = 0;
int end = length - 1;
while (start <= end)
{
int middle = (start + end) >> 1;
if (middle == number[middle])
return middle;
else if (number[middle] > middle)
end = middle - 1;
else
start = middle + 1;
}
return -1;
}
int main()
{
int numbers[] = { -3, -1, 1, 3, 5 };
std::cout << getElementAsIndex(numbers, 5);
}
???挺难的
BinaryNodeTree* findKNodeCore(BinaryNodeTree* pRoot, int& k)
{
BinaryNodeTree* target = nullptr;
if (pRoot->m_pLeft != nullptr)
target = findKNodeCore(pRoot->m_pLeft, k);
if (target == nullptr)
{
if (k == 1)
target = pRoot;
k--;
}
if (target == nullptr && pRoot->m_pRight != nullptr)
target = findKNodeCore(pRoot->m_pRight, k);
return target;
}
如果一颗树只有一个节点,那么他的深度1
如果一棵树只有左子树,那么深度为1+左子树深度
如果一棵树只有右子树,那么深度为1+右子树深度
如果一棵树有左右子树,那么深度为1+左子树深度、1+右子树深度的最大值
int depthOfTree(BinaryNodeTree* pRoot)
{
if (!pRoot)
return 0;
//if (pRoot->m_pLeft == nullptr && pRoot->m_pRight == nullptr)
//return 1;
int left = depthOfTree(pRoot->m_pLeft);
int right = depthOfTree(pRoot->m_pRight);
return 1 + ((left > right) ? left : right);
}
普通方法:造成节点被重复遍历
遍历树的每个节点时,调用树深度函数,得到左右子树的深度,如果每个节点的左右子树深度不超过1,则为平衡二叉树
只遍历一次的方法:
后序遍历,遍历某个节点前已经遍历到它的左右子树,记录该节点深度
// offer55判断平衡二叉树.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
using namespace std;
struct BinaryNodeTree
{
int m_nVal;
BinaryNodeTree* m_pLeft;
BinaryNodeTree* m_pRight;
};
int depthOfTree(BinaryNodeTree* pRoot)
{
if (!pRoot)
return 0;
//if (pRoot->m_pLeft == nullptr && pRoot->m_pRight == nullptr)
//return 1;
int left = depthOfTree(pRoot->m_pLeft);
int right = depthOfTree(pRoot->m_pRight);
return 1 + ((left > right) ? left : right);
}
bool isBalanceTree(BinaryNodeTree* pRoot)
{
if (!pRoot)
return false;
int diff = depthOfTree(pRoot->m_pLeft) - depthOfTree(pRoot->m_pRight);
if (diff > 1 || diff < -1)
return false;
bool left = isBalanceTree(pRoot->m_pLeft);
bool right = isBalanceTree(pRoot->m_pRight);
return left && right;
}
bool isBalanceTreePost(BinaryNodeTree* pRoot, int* pDepth)
{
if (!pRoot)
{
*pDepth = 0;
return true;
}
int left = 0;
int right = 0;
if (isBalanceTreePost(pRoot->m_pLeft, &left) && isBalanceTreePost(pRoot->m_pRight, &right))
{
int diff = left - right;
if (diff >= -1 && diff <= 0)
{
*pDepth = (left > right) ? left : right;
return true;
}
}
return false;
}
int main()
{
std::cout << "Hello World!\n";
}
// offer55树的深度.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
using namespace std;
struct BinaryNodeTree
{
int m_nVal;
BinaryNodeTree* m_pLeft;
BinaryNodeTree* m_pRight;
};
int depthOfTree(BinaryNodeTree* pRoot)
{
if (!pRoot)
return 0;
//if (pRoot->m_pLeft == nullptr && pRoot->m_pRight == nullptr)
//return 1;
int left = depthOfTree(pRoot->m_pLeft);
int right = depthOfTree(pRoot->m_pRight);
return 1 + ((left > right) ? left : right);
}
int main()
{
}
要求时间复杂度O(N),空间复杂度O(1)
异或:两个一样的数异或结果为零
加入数组中只有一个是只出现一次的数字,其他都是两个,那么所有数字参与异或,结果是那个只出现一次的数字
解法:如果将数组分为两个数组,各自有一个只出现一次的数字,各自有一对出现的数字(一对数字不能被分在两个数组里)
将所有数异或,对结果中的某一位的1进行研究
必然只能是只出现一次的两个数中的一个数满足某一位为1。
所以按照某一位是否为1的做法可以把数组分成两个数组,两个数分别处在不同数组;而且,每一对数必然被分配到同一数组
但是两个数组不一定要分配内存,可以直接做异或
// offer56数组中只出现一次的两个数字.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
using namespace std;
int findFirstBit1Index(int number)
{
int index = 0;
int pilot = 1;
while (number & pilot == 0)
{
pilot = pilot << 1;
index++;
}
return index;
}
bool isBit1AtIndex(int number, int index)
{
int pilot = 1;
pilot = pilot << index;
return number & pilot;
}
bool find2OnlyOnce(int* numbers, int length, int* number1, int* number2)
{
if (!number1 || length <= 0)
return false;
int sum = 0;
for (int i = 0; i < length; ++i)
sum ^= numbers[i];
int indexOfBit1 = findFirstBit1Index(sum);
for (int i = 0; i < length; ++i)
{
if (isBit1AtIndex(numbers[i], indexOfBit1))
*number1 ^= numbers[i];
else
*number2 ^= numbers[i];
}
return true;
}
int main()
{
int arr[] = { 1, 1, 2, -3, 2, 4, 5, 5, 6, 6, 89, 89, -1, -1, -100, -100 };
int number1 = 0;
int number2 = 0;
find2OnlyOnce(arr, 16, &number1, &number2);
std::cout << number1 << " " << number2;
return 0;
}
采取位运算,所有位都加起来存在对应数组,每一位%3的结果就是唯一一次出现的数字
int findOnlyOnceNumber(int* numbers, int length)
{
if (!numbers || length <= 0)
throw exception("invalid parameters.");
int bitArray[32] = { 0 };
for (int i = 0; i < length; ++i)
{
int maskBit1 = 1;
for (int j = 31; j >= 0; --j)
{
//大失误
//bitArray[j] += numbers[i] & maskBit1;
int bit = numbers[i] & maskBit1;
if (bit != 0)
bitArray[j] += 1;
maskBit1 = maskBit1 << 1;
}
}
int numberOnlyOnce = 0;
for (int i = 0; i < 32; ++i)
{
numberOnlyOnce = numberOnlyOnce << 1;
numberOnlyOnce += bitArray[i] % 3;
}
return numberOnlyOnce;
}
暴力法:两层遍历O(N^2)
头尾指针法:O(N)
p1指向第一个元素,p2指向最后一个元素
p1+p2的和大于S,p2–
p1+p2的和大于S,p1++
#include
using namespace std;
void printSerials(int small, int big)
{
if (small > big)
return;
for (int i = small; i <= big; ++i)
cout << i << " ";
cout << " ";
}
int serialsSum(int small, int big)
{
if (small > big)
return -1;
int sum = 0;
for (int i = small; i <= big; ++i)
sum += i;
return sum;
}
void findSerials(int sum)
{
if (sum < 3)
return;
int small = 1;
int big = 2;
int smallEnd = (sum + 1) / 2;
while (small < big && small < smallEnd)
{
int curSum = serialsSum(small, big);
if (curSum == sum)
{
printSerials(small, big);
++big;
}
else if (curSum > sum)
++small;
else
++big;
}
}
int main()
{
findSerials(15);
return 0;
}
设置small、big分别表示序列中最小值和最大值
初始small=1 big = 2
限制small < big small < (1+sum)/2
如果序列和大于sum,small前进一位
如果序列和小于sum,big前进一位
如果符合,打印,同时big++,寻找下一个序列
直到限制条件
void findSerials(int sum)
{
if (sum < 3)
return;
int small = 1;
int big = 2;
int smallEnd = (sum + 1) / 2;
while (small < big && small < smallEnd)
{
int curSum = serialsSum(small, big);
if (curSum == sum)
{
printSerials(small, big);
++big;//大失误,容易漏掉
}
else if (curSum > sum)
++small;
else
++big;
}
}
基本:反转字符串,头尾指针交换字符反转
方法:
单词空格分界
void wordReverse(char* str)
{
if (!str)
return;
char* pBegin = str;
char* pEnd = str + strlen(str) - 1;
strReverse(pBegin, pEnd);
cout << str << endl;
pBegin = pEnd = str;
while (*pBegin != '\0')
{
if (*pBegin == ' ')
{
pBegin++;
pEnd++;
}
else if (*pEnd == ' ' || *pEnd == '\0')//最容易忽视的地方
{
strReverse(pBegin, --pEnd);
pBegin = ++pEnd;
}
else
{
pEnd++;
}
}
}
先分段反转,再整体反转
1.暴力法:扫描每个滑动窗口数字,窗口大小为k,O(nk)
2.队列用栈实现:
滑动窗口是一个队列,如果可以在O(1)时间就找到队列最大值,那么复杂度就是O(N)
队列用两个栈实现可以达到目的
3.只把滑动窗口最大值存入队列:
使用两端开口的deque,保存有可能是滑动窗口最大值的数字的下标
template vector maxInWindow(vector numbers, int windowSize)
{
if (numbers.size() <= 0 || windowSize <= 0 || numbers.size() < windowSize)
throw exception("invlid parameters.");
vector maxSerials;
deque window;
for (int i = 0; i < numbers.size(); ++i)
{
//开头需要三个数字填充窗口
if (i < windowSize)
{
//不可能成为最大值的去除
while (!window.empty() && numbers[i] > numbers[window.back()])
window.pop_back();
//压入窗口
window.push_back(i);
continue;
}
//压入窗口前,把当前窗口最大值记录下来
maxSerials.push_back(numbers[window.front()]);
//不可能成为最大值的去除
while (!window.empty() && numbers[i] > numbers[window.back()])
window.pop_back();
//压入窗口
window.push_back(i);
//删除旧窗口数据
if (i - window.front() >= windowSize)
window.pop_front();
}
//容易犯错漏掉,最后一个窗口的max
maxSerials.push_back(numbers[window.front()]);
return maxSerials;
}
59队列的最大值–定义一个队列,并实现max函数O(1)
while (!window.empty() && number > window.back())
window.pop_back();
template class dequeWithMax
{
public:
dequeWithMax():id(-1){}
void push_bakc(T element)
{
id++;
while (!maxque.empty() && element > maxque.back())
maxque.pop_back();
Mystruct data = { id, element };
maxque.push_back(data);
myque.push_back(data);
}
void pop_front()
{
if (myque.empty())
throw new exception("queque is empty.");
if (maxque.front().id == myque.front().id)
maxque.pop_front();
myque.pop_front();
}
T max() const
{
if(myque.empty())
throw new exception("queque is empty.");
return maxque.front().element;
}
private:
struct Mystruct
{
//每一个元素的唯一ID
int id;
T element;
};
int id;
deque myque;
deque maxque;
};
色子点数16,n和色子的和值为n6n,所有点数和的情况有6n种,统计出每个和值出现的次数,除以6n
递归求解:
n和色子分为,1和n-1,先求1个塞子点数,再求其他塞子点数,用hashtable保存每一种情况出现的次数
// offer60N个色子的的点数和出现的概率.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
#include
#include
using namespace std;
const int maxOfRoll = 6;
void rockRoll(int sumOfRoll, int currentRoll, int sum, int* allSums)
{
if (sumOfRoll <= 0 || currentRoll <= 0 || currentRoll > sumOfRoll ||
allSums == nullptr)
throw new exception("invlid parameters.");
if (currentRoll == sumOfRoll)
{
for (int i = 1; i <= maxOfRoll; ++i)
{
int finalSum = sum + i;
allSums[finalSum - sumOfRoll]++;
}
}
else
{
for (int i = 1; i <= maxOfRoll; ++i)
{
rockRoll(sumOfRoll, currentRoll + 1, i + sum, allSums);
}
}
}
void printPossibilities(int sumOfRoll)
{
if (sumOfRoll < 1)
return;
int* timesOfSumOfRoll = new int[maxOfRoll * sumOfRoll - sumOfRoll + 1];
for (int i = 0; i < (maxOfRoll * sumOfRoll - sumOfRoll + 1); ++i)
timesOfSumOfRoll[i] = 0;
int total = pow((double)maxOfRoll, (double)sumOfRoll);
rockRoll(sumOfRoll, 1, 0, timesOfSumOfRoll);
for (int i = 0; i < maxOfRoll * sumOfRoll - sumOfRoll + 1; ++i)
{
double ratio = (double)timesOfSumOfRoll[i] / total;
printf("%d : %d : %lf\n", i + sumOfRoll, timesOfSumOfRoll[i], ratio);
}
delete[] timesOfSumOfRoll;
}
void printPossibilities_good(int sumOfRoll)
{
if (sumOfRoll < 1)
return;
int total = pow((double)maxOfRoll, (double)sumOfRoll);
//下标表示和,值表示次数
//用于轮流存储骰子第n个骰子的各种和出现的次数
int* tables[2];
tables[0] = new int[sumOfRoll * maxOfRoll + 1];
tables[1] = new int[sumOfRoll * maxOfRoll + 1];
//数组清空
for (int i = 0; i < sumOfRoll * maxOfRoll + 1; i++)
{
tables[0][i] = 0;
tables[1][i] = 0;
}
//轮换着使用
int flag = 0;
//投一个骰子
for (int i = 1; i <= maxOfRoll; ++i)
tables[flag][i] = 1;
//投下一个骰子
for (int next = 2; next <= sumOfRoll; ++next)
{
//清空即将使用的数组
for (int i = 0; i < next; ++i)
tables[1 - flag][i] = 0;
//求解投下当前骰子出现各种总和的次数
//可能的次数是1-6乘以next个骰子
for (int sum = next; sum <= maxOfRoll * next; ++sum)
{
tables[1 - flag][sum] = 0;
//minus表示本轮可能出现的点数
//期待总数减去本轮点数,就是上一轮点数出现的次数
for (int minus = 1; minus < sum && minus <= maxOfRoll; ++minus)
{
tables[1 - flag][sum] += tables[flag][sum - minus];
int lastIndex = sum - minus;
}
}
//切换数组
flag = 1 - flag;
}
for (int sum = sumOfRoll; sum <= maxOfRoll * sumOfRoll; ++sum)
{
double ratio = (double)tables[flag][sum] / total;
printf("%d : %d : %lf\n", sum, tables[flag][sum], ratio);
}
delete[] tables[0];
delete[] tables[1];
}
int main()
{
//printPossibilities(11);
printPossibilities_good(15);
return 0;
}
动态规划:
假设f(m,s)表示投第m个骰子时,点数之和s出现的次数,投第m个骰子时的点数之和只与投第m-1个骰子时有关。
递归方程:f(m,s)=f(m-1,s-1)+f(m-1,s-2)+f(m-1,s-3)+f(m-1,s-4)+f(m-1,s-5)+f(m-1,s-6),表示本轮点数和为s出现次数等于上一轮点数和为s-1,s-2,s-3,s-4,s-5,s-6出现的次数之和。
初始条件:第一轮的f(1),f(2),f(3),f(4),f(5),f(6)均等于1.
需要求的是:f(n , n)、 f(n, n+1)…f(n, 6*n)
事前记得清空两个数组
随机抽5张牌判断是否为顺子,大小王定义为0
2~10 A1 J11 Q12 K13,大小王充当任意数
方法:
对数组排序
统计0的个数
统计空缺总数
0个数大于空缺数则顺子
如果非0数字重复出现,一定不是顺子
// offer61扑克牌的顺子.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
#include
int compareInt(const void* a, const void* b)
{
return *(int*)a - *(int*)b;
}
bool isContinuous(int* numbers, int length)
{
if (numbers == nullptr || length < 1)
return false;
qsort(numbers, length, sizeof(int), compareInt);
int numberOfZero = 0;
int numberOfGap = 0;
for (int i = 0; i < length && numbers[i] == 0; ++i)
numberOfZero++;
for (int i = 1; i < length; ++i)
{
int diff = numbers[i] - numbers[i - 1] - 1;
if (diff == 0 && numbers[i] != 0)
return false;
numberOfGap += diff;
}
return (numberOfZero - numberOfGap) >= 0;
}
int main()
{
std::cout << "Hello World!\n";
}
方法1,链表环
使用list模拟链表环,因为list不是环形的,所以iterator到达end时候要置为begin
时间是O(nm) 空间是O(n)
方法2
f(n, m) = 0 n = 1
f(n, m) = [ f(n-1, m) + m ] % n n > 1
// offer62约瑟夫环.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
#include
using namespace std;
int jose(int n, int m)
{
if (n < 1 || m < 1)
return -1;
list circle;
for (int i = 0; i < n; ++i)
{
circle.push_back(i);
}
auto iter = circle.begin();
while (circle.size() > 1)
{
for (int i = 0; i < m - 1; ++i)
{
iter++;
if (iter == circle.end())
iter = circle.begin();
}
auto next = ++iter;
if (next == circle.end())
next = circle.begin();
circle.erase(--iter);
iter = next;
}
return *iter;
}
int josen(int n, int m)
{
if (n < 1 || m < 1)
return -1;
int last = 0;
for (int i = 2; i <= n; ++i)
last = (last + m) % i;
return last;
}
int main()
{
std::cout << josen(5, 3);
}
记录前面的最小值,对当前的数字进行卖出计算价值,记录最大价值
#include
using namespace std;
class A
{
public:
A()
{
count++;
sum += count;
}
int getSum()const{ return sum;}
private:
static int count;
static int sum;
};
int A::count = 0;
int A::sum = 0;
class B;
B* Array[2];
class B
{
public:
virtual int getSum(int n)const{ return 0;}
};
class C : public B
{
public:
virtual int getSum(int n) { return Array[!!n]->getSum(n - 1) + n;}
};
int main()
{
A a[9];
std::cout << a[0].getSum() << endl;
B b;
C c;
Array[0] = &b;
Array[1] = &c;
std::cout << Array[1]->getSum(9) << endl;
}
使用位运算实现A+B
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-er2hekY3-1586255098449)(C:\Users\cqlia\AppData\Roaming\Typora\typora-user-images\1585403850641.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TdMRhjfz-1586255098450)(C:\Users\cqlia\AppData\Roaming\Typora\typora-user-images\1585405374613.png)]
把乘积分程两部分
Bi= A0*A1*...*Ai-1*Ai+1*...An-1;
Bi=C*D;
C=A0*A1*...*Ai-1;
D=Ai+1*...An-1;
B 可以先从小到大循环全部乘以C,然后再从大到小循环乘以D
时间复杂位O(N)
//需要引用传参
void constructMultipyArray(vector &A, vector &B)
{
if (A.size() == B.size() && A.size() > 1)
{
B[0] = 1;
for (int i = 1; i < B.size(); ++i)
{
B[i] = B[i - 1] * A[i - 1];
}
double temp = 1.0;
for (int i = B.size() - 2; i >= 0; --i)
{
//B[i] = B[i + 1] * A[i + 1];//直接这样做会把上一步的乘积轮番带过来,错误
//下面是正确做法
temp *= A[i + 1];
B[i] *= temp;
}
}
}
整数的知识
字符串转整数要点
检查nullptr和”“空字符串
检查是否有除0~9之外的数
检查±号,使用bool型记录下来
数值位要用unsigned long long,这样可以通过检查是否overflow
if ((isMinus && number > 0x80000000) ||
(!isMinus && number > 0x7fffffff))
如果有不符合规定的数,统统返回0,置全局变量为false;
// offer67把字符串转为整数.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
using namespace std;
bool g_str2Int_result = false;
long long str2Int(const char* str)
{
g_str2Int_result = true;
if (str == nullptr || *str == '\0')
{
g_str2Int_result = false;
return 0;
}
const char* p = str;
bool isMinus = false;
if (*p == '-' || *p == '+')
{
if (*p == '-')
isMinus = true;
p++;
}
unsigned long long number = 0;
while (*p != '\0')
{
if (*p < '0' || *p > '9')
{
g_str2Int_result = false;
return 0;
}
number *= 10;
number += *p - '0';
if ((isMinus && number > 0x80000000) ||
(!isMinus && number > 0x7fffffff))
{
g_str2Int_result = false;
return 0;
}
p++;
}
if (isMinus)
number *= -1;
return number;
}
int main()
{
char str[20] = { 0 };
cin >> str;
std::cout << str2Int(str);
}
二叉搜索树经过排序,左子树节点比父节点小,右子树节点比父节点大。
从根节点开始和两个节点进行比较,如果比两个节点大,说明两个节点都在根节点的左子树,把查找节点设为根节点的左子节点
如果比两个节点小,说明两个节点都在根节点的右子树,把查找节点设为根节点的右子节点
重复以上过程,知道找到第一个在两个节点数值之间的节点
转化为求两个链表的第一个公共节点
方案1:
从根节点遍历,每遍历一个节点,判断两个节点是否在它的子树
直到遍历找到该节点的子树存在两个节点,但该节点的任何子节点的子树都不能满足两个节点均在该子树,那么该节点是最低公共父节点
方案2:
用两个链表保存从根节点导两个节点的路径,然后转化为求两个链表(从根到叶的顺序)的最后公共节点
链表值存放树节点的指针
1.通过DFS查找到两条路径
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YFgUB5ZP-1586255098451)(C:\Users\cqlia\AppData\Roaming\Typora\typora-user-images\1585415271232.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p8pQb2p6-1586255098452)(C:\Users\cqlia\AppData\Roaming\Typora\typora-user-images\1585415259731.png)]
ator到达end时候要置为begin
时间是O(nm) 空间是O(n)
方法2
f(n, m) = 0 n = 1
f(n, m) = [ f(n-1, m) + m ] % n n > 1
// offer62约瑟夫环.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
#include
using namespace std;
int jose(int n, int m)
{
if (n < 1 || m < 1)
return -1;
list circle;
for (int i = 0; i < n; ++i)
{
circle.push_back(i);
}
auto iter = circle.begin();
while (circle.size() > 1)
{
for (int i = 0; i < m - 1; ++i)
{
iter++;
if (iter == circle.end())
iter = circle.begin();
}
auto next = ++iter;
if (next == circle.end())
next = circle.begin();
circle.erase(--iter);
iter = next;
}
return *iter;
}
int josen(int n, int m)
{
if (n < 1 || m < 1)
return -1;
int last = 0;
for (int i = 2; i <= n; ++i)
last = (last + m) % i;
return last;
}
int main()
{
std::cout << josen(5, 3);
}
记录前面的最小值,对当前的数字进行卖出计算价值,记录最大价值
#include
using namespace std;
class A
{
public:
A()
{
count++;
sum += count;
}
int getSum()const{ return sum;}
private:
static int count;
static int sum;
};
int A::count = 0;
int A::sum = 0;
class B;
B* Array[2];
class B
{
public:
virtual int getSum(int n)const{ return 0;}
};
class C : public B
{
public:
virtual int getSum(int n) { return Array[!!n]->getSum(n - 1) + n;}
};
int main()
{
A a[9];
std::cout << a[0].getSum() << endl;
B b;
C c;
Array[0] = &b;
Array[1] = &c;
std::cout << Array[1]->getSum(9) << endl;
}
使用位运算实现A+B
[外链图片转存中…(img-er2hekY3-1586255098449)]
[外链图片转存中…(img-TdMRhjfz-1586255098450)]
把乘积分程两部分
Bi= A0*A1*...*Ai-1*Ai+1*...An-1;
Bi=C*D;
C=A0*A1*...*Ai-1;
D=Ai+1*...An-1;
B 可以先从小到大循环全部乘以C,然后再从大到小循环乘以D
时间复杂位O(N)
//需要引用传参
void constructMultipyArray(vector &A, vector &B)
{
if (A.size() == B.size() && A.size() > 1)
{
B[0] = 1;
for (int i = 1; i < B.size(); ++i)
{
B[i] = B[i - 1] * A[i - 1];
}
double temp = 1.0;
for (int i = B.size() - 2; i >= 0; --i)
{
//B[i] = B[i + 1] * A[i + 1];//直接这样做会把上一步的乘积轮番带过来,错误
//下面是正确做法
temp *= A[i + 1];
B[i] *= temp;
}
}
}
整数的知识
字符串转整数要点
检查nullptr和”“空字符串
检查是否有除0~9之外的数
检查±号,使用bool型记录下来
数值位要用unsigned long long,这样可以通过检查是否overflow
if ((isMinus && number > 0x80000000) ||
(!isMinus && number > 0x7fffffff))
如果有不符合规定的数,统统返回0,置全局变量为false;
// offer67把字符串转为整数.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
using namespace std;
bool g_str2Int_result = false;
long long str2Int(const char* str)
{
g_str2Int_result = true;
if (str == nullptr || *str == '\0')
{
g_str2Int_result = false;
return 0;
}
const char* p = str;
bool isMinus = false;
if (*p == '-' || *p == '+')
{
if (*p == '-')
isMinus = true;
p++;
}
unsigned long long number = 0;
while (*p != '\0')
{
if (*p < '0' || *p > '9')
{
g_str2Int_result = false;
return 0;
}
number *= 10;
number += *p - '0';
if ((isMinus && number > 0x80000000) ||
(!isMinus && number > 0x7fffffff))
{
g_str2Int_result = false;
return 0;
}
p++;
}
if (isMinus)
number *= -1;
return number;
}
int main()
{
char str[20] = { 0 };
cin >> str;
std::cout << str2Int(str);
}
二叉搜索树经过排序,左子树节点比父节点小,右子树节点比父节点大。
从根节点开始和两个节点进行比较,如果比两个节点大,说明两个节点都在根节点的左子树,把查找节点设为根节点的左子节点
如果比两个节点小,说明两个节点都在根节点的右子树,把查找节点设为根节点的右子节点
重复以上过程,知道找到第一个在两个节点数值之间的节点
转化为求两个链表的第一个公共节点
方案1:
从根节点遍历,每遍历一个节点,判断两个节点是否在它的子树
直到遍历找到该节点的子树存在两个节点,但该节点的任何子节点的子树都不能满足两个节点均在该子树,那么该节点是最低公共父节点
方案2:
用两个链表保存从根节点导两个节点的路径,然后转化为求两个链表(从根到叶的顺序)的最后公共节点
链表值存放树节点的指针
1.通过DFS查找到两条路径
[外链图片转存中…(img-YFgUB5ZP-1586255098451)]
[外链图片转存中…(img-p8pQb2p6-1586255098452)]