一、赋值运算符函数
思路:先用new分配新内容,再用delete释放已有内容。
CMyString& CMyString::operator=(const CMyString &str)
{
// m_pData是类的一个私有成员属性
if (this != &str) {
CMyString strTemp(str); // 拷贝构造str放入strTemp
char* pTemp = strTemp.m_pData; // 取出str的m_pData暂存
strTemp.m_pData = m_pData; // 把要抛弃的值存入strTemp
m_pData = pTemp; // 把暂存的值放入m_pData
}
return *this; // 函数结束,strTemp被自动释放
}
二、实现单例模式
思路:利用静态构造函数。解决创建时机过早的问题。
public sealed class Singleton5
{
Singleton5()
{
}
public static Singleton5 Instance
{
get
{
return Nested.instance;
}
}
// 第一次通过属性Singleton5.Instance得到实例时,会走动调用Nested创建instance
class Nested
{
static Nested()
{
}
internal static readonly Singleton5 instance = new Singleton5();
}
}
三、数组中重复的数字
描述:长度n,数字在0~n-1。
题目一、可以修改数组
描述:在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。请找出数组中任意一个重复的数字。
思路:检查每个下标i,看numbers[i] == i, 如果不是,把numbers[i]和numbers[numbers[i]]交换。如果numbers[i]==i,说明找到了。
时间O(n) 空间O(1)
int findRepeatNumber(vector<int>& nums) {
int n = nums.size();
int i;
for (i = 0; i < n; ++i) {
while (nums[i] != i) {
if (nums[i] == nums[nums[i]]) {
return nums[i];
}
swap(nums[i], nums[nums[i]]);
}
}
return nums[i];
}
题目二、不修改数组
思路:长度为n,数字范围0~n-1,取中间数字n/2,统计前半段比n/2小的数字的个数。出现次数大于n/2说明前半段有重复数字,继续分半。
int getDuplication(const int* numbers, int length)
{
if (numbers == nullptr || length <= 0)
return -1;
int start = 1;
int end = length - 1;
while (end >= start) {
int middle = ((end - start) >> 1) + start; // 取中位数
int count = countRange(numbers, length, start, middle); // 统计数组中值在中位数前的个数
// 首尾指向同一个位置,如果count>1说明有超过一个值为end的数,即为结果
if (end == start) {
if (count > 1)
return start;
else
break;
}
// 如果count在前半段出现的次数比前半段从start到middle的数字多说明前半段有重复数组
if (count > (middle - start + 1))
end = middle;
else
start = middle + 1;
}
return -1;
}
int countRange(const int* numbers, int length, int start, int end)
{
if (numbers == nullptr)
return 0;
int count = 0;
for (int i = 0; i < length; ++i)
if (numbers[i] >= start && numbers[i] <= end)
++count;
return count;
}
四、二维数组中的查找
描述:每一行从左到右递增。每一列从上到下递增。输入一个整数,判断数组中是否含有该整数
思路:每次选取查找范围内的右上角数字。如果大于number则这一列不会有number,剔除。以此类推。如果小于number说明只会出现在右侧,但是右侧都已经被剔除了,所以只会在下面,剔除这一行。以此类推。
bool Find(int* matrix, int rows, int columns, int number)
{
bool found = false;
if (matrix != nullptr && rows > 0 && columns > 0) {
int row = 0;
int column = columns - 1;
while (row < rows && column >= 0) {
if (matrix[row * columns + column] == number) {
found = true;
break;
} else if (matrix[row * columns + column] > number) {
--column;
} else {
++row;
}
}
}
return found;
}
五、替换空格
思路:先遍历一遍字符串统计空格总数。准备两个指针P1,P2。P1指向原始字符串末尾,P2指向替换后的字符串末尾。向前移动P1逐个复制到P2,直到碰到空格。碰到空格后,P1向前1格,P2向前移动3格插入"%20"。
时间O(n)
// length为字符数组的总容量
void ReplaceBlank(char string[], int length)
{
if (string == nullptr || length <= 0)
return;
int originalLength = 0; // 字符串实际长度
int numberOfBlank = 0;
int i = 0;
// 统计空格数量
while (string[i] != '\0') {
++originalLength;
if (string[i] == ' ')
++numberOfBlank;
++i;
}
int newLength = originalLength + numberOfBlank * 2;
if (newLength > length)
return; // 超过总容量
int indexOfOriginal = originalLength; // P1
int indexOfNew = newLength; // P2
while (indexOfOriginal >= 0 && indexOfNew > indexOfOriginal) {
// P1遍历到了空格
if (string[indexOfOriginal] == ' ') {
string[indexOfNew--] = '0';
string[indexOfNew--] = '2';
string[indexOfNew--] = '%';
} else {
string[indexOfNew--] = string[indexOfOriginal];
}
--indexOfOriginal;
}
}
六、从尾到头打印链表
思路:栈,后进先出
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
};
void PrintListReversingly_Iteratively(ListNode* pHead)
{
std::stack<ListNode*> nodes;
ListNode* pNode = pHead;
while (pNode != nullptr) {
nodes.push(pNode);
pNode = pNode->m_pNext;
}
while (!nodes.empty()) {
pNode = nodes.top();
printf("%d\t", pNode->m_nValue);
nodes.pop();
}
}
七、重建二叉树
描述:输入二叉树前序和中序的结果,重建二叉树。结果中不含重复数字。
思想:前序第一个数字是根节点,在中序中找到它。根前面的就是左子树的值
struct BinaryTreeNode
{
int m_nValue;
BinaryTreeNode* m_pLeft;
BinaryTreeNode* m_pRight;
};
BinaryTreeNode* Construct(int* preorder, int* inorder, int length)
{
if (preorder == nullptr || inorder == nullptr || length <= 0)
return nullptr;
return ConstructCore(preorder, preorder + length - 1, inorder, inorder + length - 1);
}
// 递归构建树
BinaryTreeNode* ConstructCore(int* startPreorder, int* endPreorder, int* startInorder, int* endInorder)
{
// 前序第一个数字是根
int rootValue = startPreorder[0];
BinaryTreeNode* root = new BinaryTreeNode();
root->m_nValue = rootValue;
root->m_pLeft = root->m_pRight = nullptr;
// 递归出口:前序遍历到头,检查此节点是否为叶节点
if (startPreorder == endPreorder) {
if (startInorder == endInorder && *startPreorder == *startInorder)
return root;
else
throw std::exception("Invalid input");
}
// 在中序遍历中找到根节点的值
int* rootInorder = startInorder;
while (rootInorder <= endInorder && *rootInorder != rootValue)
++rootInorder;
// 没找到根节点
if (rootInorder == endInorder && *rootInorder != rootValue)
throw std::exception("Invalid input.");
// 左子树长度
int leftLength = rootInorder - startInorder;
int* leftPreorderEnd = startPreorder + leftLength;
if (leftLength > 0) {
// 构建左子树
root->m_pLeft = ConstructCore(startPreorder + 1, leftPreorderEnd, startInorder, rootInorder - 1);
}
if (leftLength < endPreorder - startPreorder) {
// 构建右子树
root->m_pRight = ConstructCore(leftPreorderEnd + 1, endPreorder, rootInorder + 1, endInorder);
}
return root;
}
class Solution {
public:
vector<int> Preorder ;
map<int,int> dic;
TreeNode* build(int pre_root ,int in_left ,int in_right){
//如果左边界大于右边界说明到过了叶子
if(in_left > in_right){
return NULL;
}
//pre_root 是先序里面的索引 !!
TreeNode* root = new TreeNode(Preorder[pre_root]);
//获取先序中的节点在中序中的节点, 即index 左边就是这节点的左子树,index右边就是节点的右子树
int index = dic[Preorder[pre_root]];
//当前节点左树即为先序索引+1 (没了话会在下一次迭代返回NULL)
root->left = build(pre_root+1,in_left,index-1);
//当前节点右树即为 根结点在前序中的索引+左树所有节点数(即节点在中序中的索引)-左边界+1 ,下一次的左边界为根在中序的索引+1
root->right = build(pre_root+index-in_left+1,index+1 ,in_right);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
//赋值至外部变量
Preorder = preorder;
//使用map映射inorder的值和索引,提高找到索引效率
for(int i=0;i<inorder.size();i++){
dic[inorder[i]] = i;
}
return build(0,0,preorder.size()-1);
}
};
八、二叉树的下一个节点
描述:给定一棵二叉树和其中的一个节点,找出中序下一个节点。(parent,left,right)
思路:①如果节点有右子树,下一个节点就是其右子树中最左子节点。②如果没有右子树,而且它是父节点的左子节点,下一个节点就是其父节点。③如果没有右子树,它还是它父节点的右子节点,则沿着父节点指针向上遍历,直到找到一个它是父节点的左子节点的节点。
BinaryTreeNode* GetNext(BinaryTreeNode* pNode)
{
if (pNode == nullptr)
return nullptr;
BinaryTreeNode* pNext = nullptr;
// 右子树不为空
if (pNode->m_pRight != nullptr) {
BinaryTreeNode* pRight = pNode->m_pRight;
// 寻找右子树的最左子节点
while (pRight->m_pLeft != nullptr)
pRight = pRight->m_pLeft;
pNext = pRight;
} else if (pNode->m_pParent != nullptr) {
BinaryTreeNode* pCurrent = pNode;
BinaryTreeNode* pParent = pNode->m_pParent;
// 向上寻找它是其父节点最左子节点的节点
while (pParent != nullptr && pCurrent == pParent->m_pRight) {
pCurrent = pParent;
pParent = pParent->m_pParent;
}
pNext = pParent;
}
return pNext;
}
九、用两个栈实现队列
描述:实现appendTail, deleteHead
思路:先进后出->先进先出。stack2不空时,栈顶是最先进入队列的元素可以弹出。stack2空时,把stack1中逐个压入stack2,弹出
class CQueue {
stack<int> stack1,stack2;
public:
CQueue() {
while (!stack1.empty()) {
stack1.pop();
}
while (!stack2.empty()) {
stack2.pop();
}
}
void appendTail(int value) {
stack1.push(value);
}
int deleteHead() {
// 如果第二个栈为空
if (stack2.empty()) {
while (!stack1.empty()) {
stack2.push(stack1.top());
stack1.pop();
}
}
if (stack2.empty()) {
return -1;
} else {
int deleteItem = stack2.top();
stack2.pop();
return deleteItem;
}
}
};
补充:两个队列实现一个栈
十、斐波那契数列
long long Fibonacci(unsigned n)
{
int result[2] = {0, 1};
if (n < 2)
return result[n];
long long fibNMinusOne = 1;
long long fibNMinusTwo = 0;
long long fibN = 0;
for (unsigned int i = 2; i <= n; ++i) {
fibN = fibNMinusOne + fibNMinusTwo;
fibNMinusTwo = fibNMinusOne;
fibNMinusOne = fibN;
}
return fibN;
}
题目二、青蛙跳台
描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n
级的台阶总共有多少种跳法。
int numWays(int n) {
long long res = 0, tmp1 = 1, tmp2 = 1;
if (0 == n || 1 == n) {
return 1;
} else {
for (int i = 2; i <= n; ++i) {
res = (tmp1 + tmp2) % 1000000007;
tmp2 = tmp1;
tmp1 = res;
}
}
return res;
}
十一、旋转数组的最小数字
描述:旋转数组->把一个数组最开始的若干个元素搬到数组的末尾。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2]
为 [1,2,3,4,5]
的一个旋转,该数组的最小值为1。
思路:二分查找找到两个子数组分界线。两个指针分别指向首位。①一般情况,首指针>=尾指针。如果中间元素>=首指针,说明最小元素位于中间元素后面,更新首指针。同理更新尾指针,不断缩小范围。最终会指向两个相邻元素,尾指针指向的就是目标。②旋转后为排序数组本身,第一个元素就是目标。所以把indexMid初始化为首指针。③10111,11101采取顺序查找
int minArray(vector<int>& numbers) {
int low = 0;
int high = numbers.size() - 1;
while (low < high) {
int pivot = low + (high - low) / 2;
// 最小值在pivot左侧
if (numbers[pivot] < numbers[high]) {
high = pivot;
}
// 最小值在pivot右侧
else if (numbers[pivot] > numbers[high]) {
low = pivot + 1;
}
// low = high时
else {
high -= 1;
}
}
return numbers[low];
}
十二、矩阵中的路径
描述:判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。
思路:回溯法。
public:
bool exist(vector<vector<char>>& board, string word) {
rows = board.size();
cols = board[0].size();
// 遍历所有的格子,以它们为起点
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
if(dfs(board, word, i, j, 0)) return true;
}
}
return false;
}
private:
int rows, cols;
bool dfs(vector<vector<char>>& board, string word, int i, int j, int k) {
if(i >= rows || i < 0 || j >= cols || j < 0 || board[i][j] != word[k]) return false;
if(k == word.size() - 1) return true;
// 经过并且采纳的格子不能重复经过
board[i][j] = '\0';
bool res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) || dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);
// 把之前标记为经过的格子恢复
board[i][j] = word[k];
return res;
}
十三、机器人的运动范围
描述:地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
思路:回溯法
// 求一个数字的位数和
int get(int x) {
int res = 0;
for (; x; x /= 10) {
res += x % 10;
}
return res;
}
int movingCount(int m, int n, int k) {
if (!k) {
return 1;
}
// vis存储1/0, 表示这个格子能否到达。
vector<vector<int>> vis(m, vector<int>(n, 0));
int ans = 1;
vis[0][0] = 1;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if ((i == 0 && j == 0) || get(i) + get(j) > k) {
continue;
}
// 边界判断。更新当前格子,看是否能到达。
if (i - 1 >= 0) {
vis[i][j] |= vis[i - 1][j];
}
if (j - 1 >= 0) {
vis[i][j] |= vis[i][j - 1];
}
// 如果当前格子可以到达,vis[i][j]为1,更新结果
ans += vis[i][j];
}
}
return ans;
}
十四、剪绳子
描述:给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]*k[1]*…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
思路:贪婪算法。当n>=5时,尽可能多的剪长度为3的绳子。当剩下长度为4时,把剩下的剪成两段2.
int cuttingRope(int n) {
if (n <= 3) {
return n - 1;
}
// 尽可能多的剪去长度为3的绳子段
int timesOf3 = n / 3;
// 当剩下长度为4时
if (n - timesOf3 * 3 == 1) {
--timesOf3;
}
int timesOf2 = (n - timesOf3 * 3) / 2;
return pow(3, timesOf3) * pow(2, timesOf2);
}
十五、二进制中1的个数
描述:请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
思路:如果一个二进制数!=0说明至少一位是1.①最右边一位是1,减去1时相当于给最后一位取反。②最后一位是0,减去1会把最右边的1变成0.
int hammingWeight(uint32_t n) {
int count = 0;
while (n) {
++count;
n = (n - 1) & n;
}
return count;
}
十六、数值的整数次方
描述:实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
思路:如果求x的32次方,可以把16次方做平方。以此类推,32次方需要5次乘法。
a n = { a n / 2 ⋅ a n / 2 如果 n 为偶数 a ( n − 1 ) / 2 ⋅ a ( n − 1 ) / 2 ⋅ a 如果 n 为奇数 a^n = \begin{cases} a^{n/2} \cdot a^{n/2} & \quad \text{如果 } n \text{为偶数}\\ a^{(n-1)/2} \cdot a^{(n-1)/2}\cdot a & \quad \text{如果 } n \text{为奇数} \end{cases} an={
an/2⋅an/2a(n−1)/2⋅a(n−1)/2⋅a如果<