除了剑指Offer第一版书中提到的思路,更有剑走偏锋的刁钻解题,欢迎一起变强

热点知识:先中序建立二叉树(6)、快速幂(11)、回溯&全排列(12)、斐波那契数列问题转化为求矩阵的n次方(9)、二叉树的遍历(18)、二叉树的镜像(19)、二叉搜索树转双向链表(27)、全排列&深度优先搜索(28)、排列组合(32)、分治(36)、概率(43)、动态规划(9、34)

OJ错误的题目:33(力扣中未过,在牛客中可以过)

未完善的题目:45(差offer解法)

面试题1:赋值运算符函数

面试题2:实现Singleton模式

面试题3:二维数组中的查找

面试题4:替换空格

面试题5:从尾到头打印链表

面试题6:重建二义树

面试题7:用两个栈实现队列

面试题8:旋转数组的最小数字

面试题9:斐波那契数列

面试题10:二进制中1的个数

面试题11:数值的整数次方

快速幂精讲

面试题12:打印1到最大的n位数

回溯与全排列

面试题13:在O(1)时间删除链表结点

面试题14:调整数组顺序使奇数位于偶数前面

面试题15:链表中倒数第k个结点

面试题16:反转链表

面试题17:合并两个排序的链表

面试题18:树的子结构

二叉树的遍历之递归和非递归的广度、深度优先遍历

面试题19:二叉树的镜像

面试题20:顺时针打印矩阵

面试题21:包含min函数的栈

面试题22:栈的压入、弹出序列

面试题23:从上往下打印二叉树

面试题24:二叉搜索树的后序遍历序列

面试题25:二叉树中和为某一值的路径

面试题26:复杂链表的复制

面试题27:二义搜索树与双向链表

面试题28:字符串的排列

面试题29 :数组中出现次数超过一半的数字

面试题30 :最小的k个数

面试题31 :连续子数组的最大和

面试题32 :从1到n整数中1出现的次数

面试题33 :把数组排成最小的数

面试题34 :丑数

面试题35 :第一个只出现一次的字符

面试题36 :数组中的逆序对

面试题37 :两个链表的第一个公共结点

面试题38 :数字在排序数组中出现的次数

面试题39 :二叉树的深度

面试题40 :数组中只出现一次的数字

面试题41 :和为s的两个数字 VS 和为s的连续正数序列

面试题42 :翻转单词顺序 VS 左旋转字符串

面试题43 :n个骰子的点数

面试题44 :扑克牌的顺子

面试题45 :圆圈中最后剩下的数字

面试题46 :求1+2+ +n

面试题47 :不用加减乘除做加法

面试题48 :不能被继承的类

面试题1:赋值运算符函数

给定一个类如下,要求重载赋值运算符

class CMyString{
public:CMyString(char* pData = nullptr);CMyString(const CMyString& str);~CMyString(void);
private:char* m_pData;
};

需要考虑如下四个点:

  1. 重载赋值操作符的成员方法的参数应该使用引用类型(可以常量引用为了安全考虑),这样可以减少拷贝构造临时对象产生的系统开销
  2. 对象自己给自己赋值,则直接返回
  3. 正确释放被赋值的对象空间,并开辟和值拷贝。千万避免浅拷贝
  4. 为了达到连续赋值的目的,返回值应该给自身的引用

普通的参考如下:

class CMyString{
public:CMyString(char* pData = nullptr);CMyString(const CMyString& str);~CMyString(void);CMyString& operator=(const CMyString& str) { //1.引用传参if (this == &str) //2.对象自己给自己赋值则直接返回return *this;//3.正确释放空间,开辟空间和值拷贝delete []this->m_pData; //针对简单类型delete 和 delete[]是一样的,对于连续的class类型必须使用delete[],不然会内存泄露this->m_pData = new char[strlen(str.m_pData)+1]; //之所以要str.m_pData)+1是因为字符串的结束标志'\0'//值拷贝strcpy(this->m_pData, str.m_pData);return *this; //4.返回自身引用为了连续赋值}
private:char* m_pData;
};

异常安全的代码应该如下:

class CMyString {
public:CMyString(char* pData = nullptr) {this->m_pData = new char[strlen(pData) + 1];strcpy(this->m_pData, pData);}CMyString(const CMyString& str) {this->m_pData = new char[strlen(str.m_pData) + 1];strcpy(this->m_pData, str.m_pData);}CMyString& operator=(const CMyString& str) { //1.引用传参if (this != &str) { //2.对象自己给自己赋值则返回CMyString tmp(str); //拷贝构造(需要自行实现深拷贝版本)char* m_pData_tmp = this->m_pData;this->m_pData = tmp.m_pData;tmp.m_pData = m_pData_tmp;//3.正确释放空间,开辟空间和值拷贝}return *this; //4.返回自身引用为了连续赋值}char* getCString() {return this->m_pData;}~CMyString();
private:char* m_pData;
};

运行测试:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
class CMyString {
public:CMyString(char* pData = nullptr) {this->m_pData = new char[strlen(pData) + 1];strcpy(this->m_pData, pData);}CMyString(const CMyString& str) {this->m_pData = new char[strlen(str.m_pData) + 1];strcpy(this->m_pData, str.m_pData);}CMyString& operator=(const CMyString& str) { //1.引用传参if (this != &str) { //2.对象自己给自己赋值则返回CMyString tmp(str); //拷贝构造(需要自行实现深拷贝版本)char* m_pData_tmp = this->m_pData;this->m_pData = tmp.m_pData;tmp.m_pData = m_pData_tmp;//3.正确释放空间,开辟空间和值拷贝}return *this; //4.返回自身引用为了连续赋值}char* getCString() {return this->m_pData;}~CMyString();
private:char* m_pData;
};
CMyString::~CMyString(){}
int main() {char* str = new char[3]();str[0] = 'k';str[1] = 'o';CMyString cm1(str);CMyString cm2 = cm1;printf("%p\n", cm1.getCString());printf("%p\n", cm2.getCString());return 0;
}

对象各自的空间是独立的且没有发生浅拷贝,即使重载赋值操作符成员函数中临时对象实例化失败,比如内存不足抛出bad_alloc异常,此时还未对对象原有内存释放,实例的状态是有效的,保证了异常安全性

面试题2:实现Singleton模式

第一种思路(线程不安全):构造函数私有化,设置一个静态实例和静态方法返回这个静态实例

class Singleton {
public:static Singleton* getInstance() { //静态成员方法用于返回单例实例if (instance == nullptr)instance = new Singleton();return instance;}
private:static Singleton* instance; //静态成员存储单例Singleton(); //构造函数私有化};
Singleton* Singleton::instance = nullptr;

只要调用getInstance静态方法就会返回单例实例,这种方法缺点就是线程不安全,只适用于单线程环境。getInstance静态成员方法没有加锁,所现称环境下,实例有重复创建的风险。

第二种思路(线程安全,但效率低):在第一种的基础上给静态方法中的临界区加锁

std::mutex mtx;
class Singleton {
public:static Singleton* getInstance() { //静态成员方法用于返回单例实例mtx.lock();if (instance == nullptr)instance = new Singleton();mtx.unlock();return instance;}
private:static Singleton* instance; //静态成员存储单例Singleton(); //构造函数私有化};
Singleton* Singleton::instance = nullptr;

mutex同步锁的头文件:#include

一个时刻只有一个线程可以得到同步锁,因此单例实例不会发生重复重建,加锁机制的目的是在多线程环境下只创建一个实例

每次通过静态成员方法getInstance获得单例实例,又都会先加锁,比较耗时间,因此考虑双判断,一旦单例实例已经创建了,就不再加锁而是直接返回

第三种思路(线程安全,双判断提高效率):对第二种思路改进,加双判断一旦单例实例已经创建就不在加锁直接返回

std::mutex mtx;
class Singleton {
public:static Singleton* getInstance() { //静态成员方法用于返回单例实例if (instance == nullptr) {mtx.lock();if (instance == nullptr)instance = new Singleton();mtx.unlock();}return instance;}
private:static Singleton* instance; //静态成员存储单例Singleton(); //构造函数私有化};
Singleton* Singleton::instance = nullptr;

第四种思路(利用静态构造函数特性,限C#语言):利用静态构造函数只被调用一次的特性

public sealed class Singleton{private Singleton(){}private static Singleton instance = new Singleton();public static Singleton getInstance{get{return instance;}}
}

唯一的缺点是:程序员不能控制静态构造函数的调用时机。.NET运行时发现第一次使用一个类型的时候会自动调用该类型的静态构造函数。

第五种思路(私有嵌套类型的特性控制实例创建时机,限C#语言):利用内部类第一次被调用的同时创建Singleton实例

public sealed class Singleton{Singleton(){}public static Singleton getInstance{get{return Nested.instance;}}class Nested{static Nested(){}internal static readonly Singleton instance = new Singleton(); //internal修饰的变量在同一个程序集的文件中,内部类型或者是成员才可以访问}
}

还是利用第一次使用一个类型的时候才会自动调用该类型的静态构造方法的特性。给Singleton内定义一个内部私有类Nested,使用Singleton的静态成员方法getInstance第一次使用这个嵌套类型的时候,调用静态构造函数创建Singleton的实例instance

面试题3:二维数组中的查找

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

思路:

起始位置从右上角开始,如果当前位置元素比target小,则row++;如果当前位置元素比target大,则col–;如果相等,返回true

如果越界了还没找到,说明不存在,返回false

class Solution {
public:bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {if (!matrix.size() || !matrix[0].size()) return false;//以主对角线为划分,遇到第一个大于target的元素,则在上一个对角线元素和当前这个对角线元素中间遍历所有元素就一定可以找到int row = matrix.size(); //nint col = matrix[0].size(); //mint i = 0, j = col - 1; //右上角下标while (i < row && j >= 0 ) {if (target > matrix[i][j]) i++;else if (target < matrix[i][j]) j--;else return true; //target == matrix[i][j]}return false;}
};

面试题4:替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"

使用辅助空间,时间复杂度O(n)

C++
class Solution {
public:string replaceSpace(string s) {int len = s.size();string tmp = "";for (int i = 0; i < len; ++i) {if (s[i] == ' ')tmp += "%20";elsetmp += s[i];}return tmp;}
};
C++
class Solution {
public:string replaceSpace(string s) {int len = s.size();string tmp = "";for (int i = 0; i < len; ++i) {if (s[i] == ' '){tmp += "%20";continue;}tmp += s[i];}return tmp;}
};
C
char* replaceSpace(char* s) {int countBlank = 0, len = 0;char* tmp = s;while (*tmp != '\0') {if (*tmp++ == ' ')countBlank++;len++;}//使用辅助空间tmp = (char*)malloc(len + 1 + countBlank * 2);memset(tmp, 0, len + 1 + countBlank * 2);int index = len - 1 + countBlank * 2;for (int i = len - 1; i >= 0; --i) {if (s[i] != ' ')tmp[index--] = s[i];else {tmp[index--] = '0';tmp[index--] = '2';tmp[index--] = '%';}}return tmp;
}

不使用辅助空间C

假设在原始字符串上替换,且确保输入的字符串后面有足够的内存空间

思路1:从前往后遍历字符串,每遇到一个空格,就将空格后面的字符总共向后挪动2个字符,遇到一个空格相当于向后挪动提供出来供"%20"三个字符替换的空间,时间复杂度挺差O(n^2)

思路2:遍历一遍,数清楚多少个空格数x,从原字符最后一个字符后面的2x个空间处往后挪,每遇到一个空格就用"%20"插入。思路的精髓在于先预留够每个空格替换之后增加的2个字符空间然后挪动,整体时间复杂度O(2n)–>O(n)

C,时间复杂度O(n)(OJ中没有提供足够的内存,请在本地IDE测试)
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
char* replaceSpace(char* s) {int countBlank = 0, len = 0;char* tmp = s;while (*tmp != '\0') {if(*tmp++ == ' ')countBlank++;len++;}int index = len-1 + countBlank * 2;for (int i = len-1; i >= 0; --i) {if (s[i] != ' ')s[index--] = s[i];else {s[index--] = '0';s[index--] = '2';s[index--] = '%';}}return s;
}int main() {char str[18] = "We are happy.";str[17] = 0;cout<<replaceSpace(str)<<endl;return 0;
}

面试题5:从尾到头打印链表

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)

思路1

很明显给定的是一个单向链表,可以将链表逆转完成,构造一个数组返回

这道题的效率取决于单向链表逆转的时间复杂度上,因此采用遍历原链表的同时以头插的方式构造新链表,遍历新链表构造数组返回就完成任务了,时间复杂度就是O(n)

直接对原链表进行逆转
C
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*//*** Note: The returned array must be malloced, assume caller calls free().*/
int* reversePrint(struct ListNode* head, int* returnSize){struct ListNode* newList = NULL;int count = 0; //记录链表个数while(head != NULL){ //链表逆转struct ListNode* cur = head;head = head->next;cur->next = newList;newList = cur;count++;}*returnSize = count; //returnSize是一个输出型参数,记录链表节点个数int * res = (int*)malloc(count*sizeof(int)); //开辟结果数组空间count = 0; //count临时变量重利用while(newList != NULL){ //遍历列表赋值数组res[count++] = newList->val;newList = newList->next;}return res;
}

参数returnSize是一个输出型参数,记录链表节点个数

C++
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:vector<int> reversePrint(ListNode* head) {ListNode* newList = NULL;int count = 0; //记录链表个数while(head != NULL){ //链表逆转ListNode* cur = head;head = head->next;cur->next = newList;newList = cur;count++;}vector<int> res(count, 0);count = 0; //count临时变量重利用while(newList != NULL){ //遍历列表赋值数组res[count++] = newList->val;newList = newList->next;}return res;}
};
不对原链表进行逆转,重新建立一个新的链表
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*//*** Note: The returned array must be malloced, assume caller calls free().*/
int* reversePrint(struct ListNode* head, int* returnSize){struct ListNode* newList = NULL;int count = 0; //记录链表个数while(head != NULL){ //链表逆转struct ListNode* mal = (struct ListNode*)malloc(sizeof(struct ListNode));mal->val = head->val;head = head->next;mal->next = newList;newList = mal;count++;}*returnSize = count; //returnSize是一个输出型参数,记录链表节点个数int * res = (int*)malloc(count*sizeof(int)); //开辟结果数组空间count = 0; //count临时变量重利用while(newList != NULL){ //遍历列表赋值数组res[count++] = newList->val;newList = newList->next;}return res;
}
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:vector<int> reversePrint(ListNode* head) {ListNode* newList = NULL;int count = 0; //记录链表个数while(head != NULL){ //链表逆转ListNode* mal = (ListNode*)malloc(sizeof(ListNode));mal->val = head->val;head = head->next;mal->next = newList;newList = mal;count++;}vector<int> res(count, 0); //开辟结果数组空间count = 0; //count临时变量重利用while(newList != NULL){ //遍历列表赋值数组res[count++] = newList->val;newList = newList->next;}return res;}
};

思路2

首先遍历一遍得到链表节点数(目的是开辟合适大小空间的结果数组),再遍历链表正向初始化数组,然后将数组逆转也就是链表逆转,总共三次遍历搞定

C
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*//*** Note: The returned array must be malloced, assume caller calls free().*/
int* reversePrint(struct ListNode* head, int* returnSize){//得到节点数int count = 0;struct ListNode* tmp = head;while(tmp != NULL){count++;tmp = tmp->next;}tmp = head;*returnSize = count;int * res = (int*)malloc(count*sizeof(int));//遍历数组得到初步结果数组count = 0;while(tmp != NULL){res[count++] = tmp->val;tmp = tmp->next;}//结果数组逆转for(int i=0; i<count>>1; ++i){res[i] ^= res[count-1-i];res[count-1-i] ^= res[i];res[i] ^= res[count-1-i];}return res;
}
C++
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:vector<int> reversePrint(ListNode* head) {//得到节点数int count = 0;struct ListNode* tmp = head;while(tmp != NULL){count++;tmp = tmp->next;}tmp = head;vector<int> res(count, 0);//遍历数组得到初步结果数组count = 0;while(tmp != NULL){res[count++] = tmp->val;tmp = tmp->next;}//结果数组逆转for(int i=0; i<count>>1; ++i){res[i] ^= res[count-1-i];res[count-1-i] ^= res[i];res[i] ^= res[count-1-i];}return res;}
};

糊涂糊涂呀,为什么要先遍历单向链表并结果数组从0下标开始(正向初始化),逆向赋值岂不是都不用将结果数组逆转了,一次遍历得到节点数量,第二次遍历得到结果数组,总共两次遍历搞定

C

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*//*** Note: The returned array must be malloced, assume caller calls free().*/
int* reversePrint(struct ListNode* head, int* returnSize){//得到节点数int count = 0;struct ListNode* tmp = head;while(tmp != NULL){count++;tmp = tmp->next;}tmp = head;*returnSize = count;int * res = (int*)malloc(count*sizeof(int));//遍历数组得到初步结果数组count-=1;while(tmp != NULL){res[count--] = tmp->val;tmp = tmp->next;}return res;
}

C++

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:vector<int> reversePrint(ListNode* head) {//得到节点数int count = 0;struct ListNode* tmp = head;while(tmp != NULL){count++;tmp = tmp->next;}tmp = head;vector<int> res(count, 0);//遍历数组得到初步结果数组count--;while(tmp != NULL){res[count--] = tmp->val;tmp = tmp->next;}return res;}
};

思路3

根据思路2的遍历单向链表的同时逆向初始化结果数组,目的就是为了后进先出,得到启发;可以使用栈结构来完成逆序

C语言的话,和逆向初始化结果数组是一样的逻辑,都是为了后进先出,就认为逆向初始化数组就是压栈的方式,不做代码展示

利用C++栈来逆序
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:vector<int> reversePrint(ListNode* head) {stack<int, vector<int>> st;while(head != nullptr){st.push(head->val);head = head->next;}vector<int> res(st.size(), 0);int index = 0;while(!st.empty()){res[index++] = st.top();st.pop();}return res;}
};

思路4:递归方式实现

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:vector<int> reversePrint(ListNode* head) {vector<int> res;revreversePrint_(head, res);return res;}void revreversePrint_(ListNode* head, vector<int>& res){if(!head) return ;revreversePrint_(head->next, res);res.push_back(head->val);}
};

使用本地编译器测试

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
typedef struct ListNode {int val;struct ListNode* next;
}ListNode;
void reversePrint(ListNode * head) {if (head == nullptr) return ;reversePrint(head->next);cout << head->val<<" ";
}
int main() {//构造一个1-->3-->2-->nullptr的单向链表ListNode* head = (struct ListNode*)malloc(sizeof(struct ListNode));head->val = 1;head->next = (struct ListNode*)malloc(sizeof(struct ListNode));head->next->val = 3;head->next->next = (struct ListNode*)malloc(sizeof(struct ListNode));head->next->next->next = nullptr;head->next->next->val = 2;reversePrint(head);return 0;
}

总结:

在OJ中发现先遍历链表得到初步结果数组,然会对数组进行逆转的执行用时更短,更占优势,但是内存占用会多一点,因为数组逆转需要临时变量辅助实现

  • 思路1可以对原链表进行逆转,也可以构造一个新的链表从而实现逆序输出单链表的任务

  • 思路2没有对原单链表的结构进行任何改变,通过遍历单向链表正向初始化结果数组再对数组逆转即可得到结果,更简单的方式是遍历单向链表的同时逆向初始化结果数组

  • 思路3受到思路2的启发逆序的子问题就是后进先出,所以使用栈结构就可以轻松解决,由于增加了栈容器的空间开销,内存销毁会比直接通过结果数组逆向初始化大一点

面试题6:重建二义树

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例如,给出

前序遍历 preorder = [3,9,20,15,7]

中序遍历 inorder   = [9,3,15,20,7]

返回如下的二叉树:

    3/ \9  20/  \15   7

思路

前序遍历:根,左,右

中序遍历:左,根,右

前序遍历的第一个将中序划分成左右两个子树

递归

按这个思路去递归,C++题解,经典的建树,建树无论是前后建树,或者中后建树,要点都是传递中的序列下标,因为根结点下标都可以根据中进行推算。我们直接返回build函数

在build中,如果start>end则返回–是递归终止条件。我们将root节点new出来然后while循环负责进行找到中节点中root节点的下标,最后左右递归即可

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/
class Solution {
public:TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {return build(preorder, inorder, 0, 0, inorder.size() - 1);}TreeNode* build(vector<int>& preorder, vector<int>& inorder, int root, int start, int end){// 中序的start和endif(start > end) return NULL;TreeNode *tree = new TreeNode(preorder[root]);int i = start;while(i < end && preorder[root] != inorder[i]) i++;tree->left = build(preorder, inorder, root + 1, start, i - 1);tree->right = build(preorder, inorder, root + 1 + i - start, i + 1, end);return tree;}
};

面试题7 :用两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

思路

  • 设置一个Pop栈和一个Push栈,Pop栈用于出队列,Push栈用于入队列

  • 入队列前,首先将Pop栈依次出栈压入到Push栈,然后Push栈入队

  • 出队列前,首先将Push栈依次出栈压入到Pop栈,然后Pop栈出队

C++
class CQueue {
public:CQueue() {}void appendTail(int value) {//首先将Pop栈全挪到Push栈while(!Pop.empty()){Push.push(Pop.top());Pop.pop();}Push.push(value);}int deleteHead() {//首先将Push栈全部挪到pop栈while(!Push.empty()){Pop.push(Push.top());Push.pop();}if(Pop.empty())return -1;int tmp = Pop.top();Pop.pop();return tmp;}
private:stack<int, vector<int>> Push;stack<int, vector<int>> Pop;
};/*** Your CQueue object will be instantiated and called as such:* CQueue* obj = new CQueue();* obj->appendTail(value);* int param_2 = obj->deleteHead();*/

C(挺麻烦的,首先客观上得先实现一个栈)
//实现栈结构
typedef struct stack{int* arr;int sz;int cp;
}stack;//栈相关操作
int top(stack* st){return st->arr[st->sz-1];
}
bool pop(stack* st){st->sz--;return true;
}
bool push(stack* st, int val){if(st->sz < st->cp){ //容量够st->arr[st->sz++] = val;}else{//扩容int* tmp = (int*)malloc(sizeof(int)*st->cp*2);memcpy(tmp, st->arr, sizeof(int)*st->cp);int* del = st->arr;st->arr = tmp;st->cp *= 2;free(del);}return true;
}
bool empty(stack* st){return st->sz == 0;
}
//初始化栈操作
void initializeStack(stack* obj, int sz){obj->arr = (int*)malloc(sz*sizeof(int));obj->cp = sz;obj->sz = 0;
}//队列结构
typedef struct {stack Push;stack Pop;
} CQueue;CQueue* cQueueCreate() {CQueue* tmp = (CQueue*)malloc(sizeof(CQueue));initializeStack(&tmp->Push, 1000);initializeStack(&tmp->Pop, 1000);return tmp;
}void cQueueAppendTail(CQueue* obj, int value) {//首先将Pop栈全挪到Push栈while(!empty(&obj->Pop)){push(&obj->Push, top(&obj->Pop));pop(&obj->Pop);}push(&obj->Push, value);
}int cQueueDeleteHead(CQueue* obj) {//首先将Push栈全部挪到pop栈while(!empty(&obj->Push)){push(&obj->Pop, top(&obj->Push));pop(&obj->Push);}if(empty(&obj->Pop))return -1;int tmp = top(&obj->Pop);pop(&obj->Pop);return tmp;
}void cQueueFree(CQueue* obj) {while(!empty(&obj->Push)) pop(&obj->Push);while(!empty(&obj->Pop)) pop(&obj->Pop);
}/*** Your CQueue struct will be instantiated and called as such:* CQueue* obj = cQueueCreate();* cQueueAppendTail(obj, value);* int param_2 = cQueueDeleteHead(obj);* cQueueFree(obj);
*/

面试题8 :旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

思路及分析

剑指offer书本上吧啦吧啦说一大堆,思想就是二分的思想,首先数组首尾元素分别记录下标,中间元素下标就是两个下标的均值,若是中间元素位于前面的递增数组(arr[mid] >= arr[left]),最小元素位于该中间元素的后面([mid+1, left])。其实举个栗子就能明白:

给一个四个元素的数组:1, 2, 3, 4

所有旋转数组如下:

2 3 4 1

3 4 1 2

4 1 2 3

将旋转之后的数组划分成成两个排序的子数组

2 3 4 1

3 4 1 2

4 1 2 3

前面的子数组的元素都大于或者等于后面子数组的元素,最小刚好是这两个子数组的分界线。因此在排序的数组中利用二分查找法实现O(logn)的时间复杂度查找算法。

思路

利用两个指针分别指向数组的第一个元素和最后一个元素。第一个元素大于等于最后一个元素

找到数组的中间元素,如果该中间元素位于前面的递增子数组,则中间元素一定大于等于第一个指针指向的元素,最小元素就位于该中间元素后面,第一个指针指向该中间元素;如果该中间元素位于后面的递增数组,则中间元素小于等于最后一个指针指向的元素,此时最小元素就位于该中间元素的前面,第二个指针指向该中间元素

手动模拟以上过程,发现第一个指针总是在前面的递增数组元素中移动;第二个指针总是在后面的递增数组元素中移动。最终第一个指针和第二个指针都会指向相邻两个元素,且第二个指针指向的就是最小元素

1
class Solution {
public:int minArray(vector<int>& numbers) {int left = 0;int right = numbers.size()-1;int mid = 0;while(left+1 < right){mid = (left+right)>>1;if(numbers[mid]>=numbers[left])left = mid;else if(numbers[mid]<=numbers[right])right = mid;}return numbers[right];}
};

不能通过的测试用例:数组整体递增有序

2

当第一个元素小于最后一个元素时,说明整个数组递增有序,直接返回第一个元素

class Solution {
public:int minArray(vector<int>& numbers) {int left = 0;int right = numbers.size()-1;int mid = 0;if(numbers[left] < numbers[right]) return numbers[left];while(left+1 < right){mid = (left+right)>>1;if(numbers[mid]>=numbers[left])left = mid;else if(numbers[mid]<=numbers[right])right = mid;}return numbers[right];}
};

按照以上思路写出来的代码还存在问题,中间元素和第一个和最后一个元素都相等的情况下,范围会缩小到后面的递增子序列是不对的,如:[10,1,10,10,10]测试用例

就是中间元素和第一个和最后一个元素都相等时需要在[left, right]范围上顺序查找

3

第一个元素和最后一个元素以及中间元素相等时就需要在[left, right]范围上顺序查找

C++

class Solution {
public:int minArray(vector<int>& numbers) {int left = 0;int right = numbers.size()-1;int mid = 0;if(numbers[left] < numbers[right]) return numbers[left];while(left+1 < right){mid = (left+right)>>1;if(numbers[left] == numbers[right] && numbers[right] == numbers[mid]){ //顺序查找int min = numbers[left];for(int i=left+1; i<=right; ++i)if(min > numbers[i])min = numbers[i];return min;}else if(numbers[mid]>=numbers[left])left = mid;else if(numbers[mid]<=numbers[right])right = mid;}return numbers[right];}
};

C

int minArray(int* numbers, int numbersSize){int left = 0;int right = numbersSize-1;int mid = 0;if(numbers[left] < numbers[right]) return numbers[left];while(left+1 < right){mid = (left+right)>>1;if(numbers[left] == numbers[right] && numbers[right] == numbers[mid]){ //顺序查找int min = numbers[left];for(int i=left+1; i<=right; ++i)if(min > numbers[i])min = numbers[i];return min;}else if(numbers[mid]>=numbers[left])left = mid;else if(numbers[mid]<=numbers[right])right = mid;}return numbers[right];
}

正确,可以通过所有的测试用例

4

封装顺序查找函数(封装函数之后没有直接顺序查找分支选择执行用时短)

class Solution {
public:int minArray(vector<int>& numbers) {int left = 0;int right = numbers.size()-1;int mid = 0;if(numbers[left] < numbers[right]) return numbers[left];while(left+1 < right){mid = (left+right)>>1;if(numbers[left] == numbers[right] && numbers[right] == numbers[mid]) //顺序查找return seqSearch(numbers, left, right);else if(numbers[mid]>=numbers[left])left = mid;else if(numbers[mid]<=numbers[right])right = mid;}return numbers[right];}int seqSearch(vector<int>& numbers, int left, int right){int min = numbers[left];for(int i=left+1; i<=right; ++i)if(min > numbers[i])min = numbers[i];return min;}
};
思路总结

1. 当第一个元素小于最后一个元素时,说明整个数组递增有序,直接返回第一个元素

2. 找到数组的中间元素,如果该中间元素位于前面的递增子数组,则中间元素一定大于等于第一个指针指向的元素,最小元素就位于该中间元素后面,第一个指针指向该中间元素;如果该中间元素位于后面的递增数组,则中间元素小于等于最后一个指针指向的元素,此时最小元素就位于该中间元素的前面,第二个指针指向该中间元素

3. 第一个元素和最后一个元素以及中间元素相等时就需要在[left, right]范围上顺序查找

面试题9 :斐波那契数列

先来个递归O(N^2)

class Solution {
public:int Fibonacci(int n) {if(n == 0 || n == 1)return n;elsereturn Fibonacci(n-1)+Fibonacci(n-2);}
};

动归O(N)

请参考动态规划专题的:Fibonacci文章

求斐波那契数列转化为求矩阵的n次方

数学归纳法证明的公式:
\begin{bmatrix}f(n) & f(n-1) \\f(n-1) & f(n-2)
\end{bmatrix}=
\begin{bmatrix}1 & 1 \\1 & 0
\end{bmatrix}^{n-1}

[ f ( n ) f ( n − 1 ) f ( n − 1 ) f ( n − 2 ) ] = [ 1 1 1 0 ] n − 1 \begin{bmatrix} f(n) & f(n-1) \\ f(n-1) & f(n-2) \end{bmatrix}= \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}^{n-1} [f(n)f(n−1)​f(n−1)f(n−2)​]=[11​10​]n−1

通过以上公式只需要求
\begin{bmatrix}1 & 1 \\1 & 0
\end{bmatrix}^{n-1}

[ 1 1 1 0 ] n − 1 \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}^{n-1} [11​10​]n−1

即可求得f(n)
代码实现O(n)
class Solution {
public:int Fibonacci(int n) {if (!n) return 0;if (1 == n) return 1;if (2 == n) return 1;//矩阵avector<vector<int>> a(2, vector<int>(2, 1));a[1][1] = 0;vector<vector<int>> a_tmp(2, vector<int>(2, 1));a_tmp[1][1] = 0;for (int i = 1; i < n-2; ++i) {int res_0_0 = a_tmp[0][0] * a[0][0] + a_tmp[0][1] * a[1][0];int res_0_1 = a_tmp[0][0] * a[0][1] + a_tmp[0][1] * a[1][1];int res_1_0 = a_tmp[1][0] * a[0][0] + a_tmp[1][1] * a[1][0];int res_1_1 = a_tmp[1][0] * a[0][1] + a_tmp[1][1] * a[1][1];a_tmp[0][0] = res_0_0;a_tmp[0][1] = res_0_1;a_tmp[1][0] = res_1_0;a_tmp[1][1] = res_1_1;}return a_tmp[0][0] + a_tmp[1][0];}
};

以上时间复杂度依然是O(n),因此可以使用乘方的性质

a^n = \begin{cases}a^{n/2}*a^{n/2} &\text{if } n为偶数 \\a^{(n-1)/2}*a^{(n-1)/2}*a &\text{if } n为奇数
\end{cases}

a n = { a n / 2 ∗ a n / 2 if  n 为 偶 数 a ( n − 1 ) / 2 ∗ a ( n − 1 ) / 2 ∗ a if  n 为 奇 数 a^n = \begin{cases} a^{n/2}*a^{n/2} &\text{if } n为偶数 \\ a^{(n-1)/2}*a^{(n-1)/2}*a &\text{if } n为奇数 \end{cases} an={an/2∗an/2a(n−1)/2∗a(n−1)/2∗a​if n为偶数if n为奇数​

想要得到n次方,就要先求n/2次方,再把n/2次方的结果平方一下即可,递归方式时间复杂度O(logn)的方式

代码实现O(logn)

Java

/** O(logN)解法:由f(n) = f(n-1) + f(n-2),可以知道* [f(n),f(n-1)] = [f(n-1),f(n-2)] * {[1,1],[1,0]}* 所以最后化简为:[f(n),f(n-1)] = [1,1] * {[1,1],[1,0]}^(n-2)* 所以这里的核心是:* 1.矩阵的乘法* 2.矩阵快速幂(因为如果不用快速幂的算法,时间复杂度也只能达到O(N))*/
public class Solution {public int Fibonacci(int n) {if (n < 1) {return 0;}if (n == 1 || n == 2) {return 1;}//底int[][] base = {{1,1},{1,0}};//求底为base矩阵的n-2次幂int[][] res = matrixPower(base, n - 2);//根据[f(n),f(n-1)] = [1,1] * {[1,1],[1,0]}^(n-2),f(n)就是//1*res[0][0] + 1*res[1][0]return res[0][0] + res[1][0];}//矩阵乘法public int[][] multiMatrix(int[][] m1,int[][] m2) {//参数判断什么的就不给了,如果矩阵是n*m和m*p,那结果是n*pint[][] res = new int[m1.length][m2[0].length];for (int i = 0; i < m1.length; i++) {for (int j = 0; j < m2[0].length; j++) {for (int k = 0; k < m2.length; k++) {res[i][j] += m1[i][k] * m2[k][j];}}}return res;}/** 矩阵的快速幂:* 1.假如不是矩阵,叫你求m^n,如何做到O(logn)?答案就是整数的快速幂:* 假如不会溢出,如10^75,把75用用二进制表示:1001011,那么对应的就是:* 10^75 = 10^64*10^8*10^2*10* 2.把整数换成矩阵,是一样的*/public int[][] matrixPower(int[][] m, int p) {int[][] res = new int[m.length][m[0].length];//先把res设为单位矩阵for (int i = 0; i < res.length; i++) {res[i][i] = 1;} //单位矩阵乘任意矩阵都为原来的矩阵//用来保存每次的平方int[][] tmp = m;//p每循环一次右移一位for ( ; p != 0; p >>= 1) {//如果该位不为零,应该乘if ((p&1) != 0) {res = multiMatrix(res, tmp);}//每次保存一下平方的结果tmp = multiMatrix(tmp, tmp);}return res;}}

C++

/** O(logN)解法:由f(n) = f(n-1) + f(n-2),可以知道* [f(n),f(n-1)] = [f(n-1),f(n-2)] * {[1,1],[1,0]}* 所以最后化简为:[f(n),f(n-1)] = [1,1] * {[1,1],[1,0]}^(n-2)* 所以这里的核心是:* 1.矩阵的乘法* 2.矩阵快速幂(因为如果不用快速幂的算法,时间复杂度也只能达到O(N))*/
class Solution {public:int Fibonacci(int n) {if (n < 1) {return 0;}if (n == 1 || n == 2) {return 1;}//底vector<vector<int>> base(2, vector<int>(2, 1));base[1][1] = 0;//求底为base矩阵的n-2次幂vector<vector<int>>* res;res = matrixPower(base, n - 2);//根据[f(n),f(n-1)] = [1,1] * {[1,1],[1,0]}^(n-2),f(n)就是//1*res[0][0] + 1*res[1][0]return (*res)[0][0] + (*res)[1][0];}//矩阵乘法vector<vector<int>>* multiMatrix(vector<vector<int>> m1,vector<vector<int>> m2) {//参数判断什么的就不给了,如果矩阵是n*m和m*p,那结果是n*pvector<vector<int>>* res = new vector<vector<int>>(m1.size(), vector<int>(m2[0].size(), 0));for (int i = 0; i < m1.size(); i++) {for (int j = 0; j < m2[0].size(); j++) {for (int k = 0; k < m2.size(); k++) {(*res)[i][j] += m1[i][k] * m2[k][j];}}}return res;}/** 矩阵的快速幂:* 1.假如不是矩阵,叫你求m^n,如何做到O(logn)?答案就是整数的快速幂:* 假如不会溢出,如10^75,把75用用二进制表示:1001011,那么对应的就是:* 10^75 = 10^64*10^8*10^2*10* 2.把整数换成矩阵,是一样的*/vector<vector<int>>* matrixPower(vector<vector<int>> m, int p) {vector<vector<int>>* res = new vector<vector<int>>(m.size(), vector<int>(m[0].size(), 0));//先把res设为单位矩阵for (int i = 0; i < (*res).size(); i++) {(*res)[i][i] = 1;} //单位矩阵乘任意矩阵都为原来的矩阵//用来保存每次的平方vector<vector<int>>* tmp = &m;//p每循环一次右移一位for ( ; p != 0; p >>= 1) {//如果该位不为零,应该乘if ((p&1) != 0) {res = multiMatrix(*res, *tmp);}//每次保存一下平方的结果tmp = multiMatrix(*tmp, *tmp);}return res;}
};

快速幂请参考快速幂精讲

面试题10 :二进制中1的个数

移位判断

int hammingWeight(uint32_t n) {char res = 0;while(n){res += n&1;n >>= 1;}return res;
}

缺陷:负数就会死循环–>改进:

不对原数据进行移位,而是使用一个无符号整形不断左移去试探n的每一位

int hammingWeight(uint32_t n) {uint32_t flag = 1;char res = 0;while(flag){if(n&flag)res++;flag <<= 1;}return res;
}

更高效率做法

现象:将一个数减1,则原数最低位1会变成0,其后的所有0都变成0

规律:将一个数减1的值和它本身与运算就会将最低非0位的1变成0

以上规则以循环方式进行,有多少个1就能执行多少次

int hammingWeight(uint32_t n) {char res = 0;while(n){n = n&(n-1);++res;}return res;
}

在leetcode上0ms就挺惊喜

面试题11 :数值的整数次方

无脑循环

double myPow(double x, int n){double res = 1.0;for(int i=1; i<=n; ++i)res *= x;return res;
}

没有考虑指数是0或者负数的情况,改进:

double myPow(double x, int n){if(!n) return 1.0;int flag = 0; //标志指数n是不是负数if(n<0){flag = 1;n *= -1;}double res = 1.0;for(int i=1; i<=n; ++i)res *= x;if(flag) return 1.0/res;return res;
}
0.00001
2147483647

测试用例过不了,因为计算机表示数字存在误差,当两个数相差小于0.0000001时就认为两数相等,那么当res是0.0000001时,就认为res是0.0。封装equal函数用于比较两个双精度浮点数是否相等。

bool equal(double x, double y) {if (x - y > -0.0000001 && x - y < 0.0000001)return true;elsereturn false;
}
double myPow(double x, int n) {if (equal(x, 1.0)) return 1.0;unsigned int n_tmp = n; //防止n=-2147483648时 n*=-1溢出//n<0if (n < 0) {x = 1.0 / x;//n *= -1n += 1;n *= -1;n_tmp = n;++n_tmp;}//x == -1.0if (equal(x, -1.0) && n_tmp % 2) return -1.0;if (equal(x, -1.0) && !(n_tmp % 2)) return 1.0;double res = 1.0;for (int i = 1; i <= n_tmp; ++i) {res *= x;if (equal(res, 0.0)) return 0.0;}return res;
}

改快速幂

class Solution {
public:bool equal(double x, double y) {if (x - y > -0.0000001 && x - y < 0.0000001)return true;elsereturn false;}double myPow(double x, int n) {if (equal(x, 1.0)) return 1.0;unsigned int n_tmp = n; //防止n=-2147483648时 n*=-1溢出//n<0if (n < 0) {x = 1.0 / x;//n *= -1n += 1;n *= -1;n_tmp = n;++n_tmp;}//x == -1.0if (equal(x, -1.0) && n_tmp % 2) return -1.0;if (equal(x, -1.0) && !(n_tmp % 2)) return 1.0;double res = 1.0;for (int i = 1; i <= n_tmp; ++i) {res *= x;if (equal(res, 0.0)) return 0.0;}return res;}
};

递归方式

double myPow(double x, int n){if(0 == n) return 1;if(1 == n) return x;if(-1 == n) return 1/x;double res = myPow(x, n>>1);res *= res;if(n & 1) res *= x;return res;
}
小小总结
  • x == 1.0 return 1.0
  • x == -1.0 指数是奇数返回-1.0;指数是偶数返回1.0
  • 循环中只要 res <= 0.0000001 && res >= -0.0000001,就return 0.0

使用求斐波那契数列转化为求矩阵的n次方的公式(递归)

a^n = \begin{cases}a^{n/2}*a^{n/2} &\text{if } n为偶数 \\a^{(n-1)/2}*a^{(n-1)/2}*a &\text{if } n为奇数
\end{cases}

a n = { a n / 2 ∗ a n / 2 if  n 为 偶 数 a ( n − 1 ) / 2 ∗ a ( n − 1 ) / 2 ∗ a if  n 为 奇 数 a^n = \begin{cases} a^{n/2}*a^{n/2} &\text{if } n为偶数 \\ a^{(n-1)/2}*a^{(n-1)/2}*a &\text{if } n为奇数 \end{cases} an={an/2∗an/2a(n−1)/2∗a(n−1)/2∗a​if n为偶数if n为奇数​

double myPow(double x, int n){if(0 == n) return 1;if(-1 == n) return 1/x;double res = myPow(x, n>>1);res *= res;if(n & 1) res *= x;return res;
}
double myPow(double x, int n) {if(n==0) return 1;//已经考虑到负数右移永远是负数的情况if(n==-1) return 1/x;if(n&1) return myPow(x*x, n>>1)*x;else return myPow(x*x, n>>1);
}

上面两种递归,1>>1最终都是0,因此可以不用给if(1 == n) return x;的递归结束分支,给的话递归会少一层效率响应时间可能会有所提高

bool equal(double x, double y) {if (x - y > -0.0000001 && x - y < 0.0000001)return true;elsereturn false;
}
double myPow(double x, int n) {if(equal(x, 0.0)) return 0;if(n==0) return 1;if(n==1) return x;if(n== -1) return 1/x;double half = myPow(x,n>>1);double mod = myPow(x,n&1);return half*half*mod;
}

以上递归if(n == 1) return x分支不能少,删除这个分支,n == 1时mod值就无法计算

快速幂(迭代)

double myPow(double x, int n) {double res=1;double base=x;bool flag=n>=0;//负数取反,考虑到最小负数,需要先自增,后续再除以2if(!flag) n=-(++n);while(n>0){if(n&1) res*=x;n=n>>1;x*=x;}return flag?res:1/(res*base);
}
double myPow(double x, int n) {double res = 1.0;int t = n;while(n){if(n&1) res *= x;x *= x;n /= 2;}return t > 0? res : 1.0 / res;
}

快速幂精讲

先看一道题:求 a 的 b 次方对 p 取模的值

a^b mod p

a b m o d p a^b mod p abmodp

a,b,p的取值范围:

0 \leq a,b,p \leq 10^9

0 ≤ a , b , p ≤ 1 0 9 0 \leq a,b,p \leq 10^9 0≤a,b,p≤109

思路

a,b的取值范围注定这道题不能通过long long保存a^b的结果;并且即使可以保存下,算法时间复杂度也太大

因此通过快速幂的方式可以降低累乘的次数

③④⑤⑥⑦

理解快速幂

任何一个正整数都可以由2的整数次幂相加得到

正整数a可以用如下表达式表示

a[0]*2^0+a[1]*2^1+...+a[x]*2^x (a[i] = 1|0 0<= i,x <= log2n (向下取整)) ①

a [ 0 ] ∗ 2 0 + a [ 1 ] ∗ 2 1 + . . . + a [ x ] ∗ 2 x ( a [ i ] = 1 ∣ 00 < = i , x < = l o g 2 n ( 向 下 取 整 ) ) ① a[0]*2^0+a[1]*2^1+...+a[x]*2^x (a[i] = 1|0 0<= i,x <= log2n (向下取整)) ① a[0]∗20+a[1]∗21+...+a[x]∗2x(a[i]=1∣00<=i,x<=log2n(向下取整))①

以上表示式可以将任何一个数字快速化成二进制

同底幂相乘的性质:
a^m * a^n = a^(m+n) ②

我们就可以对a^b,将b用表达式①表示就是快速幂的过程

我们对a^b次方进行快速幂的过程分析

a取3,b取31

首先b=31用表达式①表示:

2^0+2^1+2^2+2^3+2^4

b二进制表示就是: 11111

a^b即就是:

a^(2^0+2^1+2^2+2^3+2^4) = a^1*a^2*a^4*a^8+a^16

过程使用代码表示就是

    res = 1;while(b){if(b&1) res*a;a = a*a;b = b>>1;}
因此a^b mod pOJ代码是:
#include <iostream>
using namespace std;
int main(){int a, b, p;cin>>a>>b>>p;long long res = 1;while(b){if(b&1) res*a;a = a*a;b = b>>1;}cout<<res%p<<endl;return 0;
}

面试题12 :打印1到最大的n位数

无脑循环

/*** Note: The returned array must be malloced, assume caller calls free().*/
int* printNumbers(int n, int* returnSize){*returnSize = 9;for(int i=0; i<n-1; ++i)*returnSize = (*returnSize)*10 + 9;int* res = (int*)malloc(sizeof(int)*(*returnSize));for(int i=0; i<(*returnSize); ++i)res[i] = i+1;return res;
}

使用pow函数

/*** Note: The returned array must be malloced, assume caller calls free().*/
int* printNumbers(int n, int* returnSize){*returnSize = pow(10, n)-1;int* res = (int*)malloc(sizeof(int)*(*returnSize));for(int i=0; i<(*returnSize); ++i)res[i] = i+1;return res;
}

在字符串上模拟数字加法

class Solution {
public://字符串模拟数字累加string* increase(string* des) {int i = des->size() - 1;int cur = (*des)[i] - '0' + 1;if(1 == des->size())(*des)[i] = cur % 10 + '0';for (i; i > 0; --i) {if (cur == 0) break;(*des)[i - 1] += cur / 10;(*des)[i] = cur % 10 + '0';cur = (*des)[i - 1]-'0';}return des;}//返回删除字符串前面0的字符串string removeZero(string* des) {string res;int i = 0;while ((*des)[i] == '0')++i;for (i; i < (*des).size(); i++)res += (*des)[i];return res;}vector<int> printNumbers(int n) {vector<int> res;int nums = pow(10, n) - 1;string tmp(n, '0');for (int i = 1; i <= nums; ++i)res.push_back(std::atoi(removeZero(increase(&tmp)).c_str()));return res;}
};

转化为数字排列问题(回溯)

递归

class Solution {
public:
// 此题在剑指offer上主要练习大数问题,P116vector<int> result;vector<int> printNumbers(int n) {if(n <= 0) {return {};}string number(n, '0');for(int i = 0; i < 10; ++i) {number[0] = i + '0';CombinateRecursively(number, n, 0);}return result;}void CombinateRecursively(string& number, int nLength, int index) {if(index == nLength - 1) {SaveNumber(number);return;}for(int i = 0; i < 10; ++i) {number[index + 1] = i + '0';CombinateRecursively(number, nLength, index + 1);}}void SaveNumber(string& number) {bool isBeginning0 = true;int nLength = number.size();int sumTmp = 0;for(int i = 0; i < nLength; ++i) {if(isBeginning0 && number[i] != '0') {isBeginning0 = false;} if(!isBeginning0) {sumTmp = sumTmp * 10 + number[i] - '0';}}if(sumTmp != 0) result.push_back(sumTmp);}
};

回溯

class Solution {/*回溯,用字符串的方式求n位数之内的全排列,回溯的时候需要遍历到每个数字,且需要一个列表保存每次dfs触底生成的数字,所以时空复杂度均为O(10^n)*/public int[] printNumbers(int n) {List<Character> path = new ArrayList<>();List<String> ansList = new ArrayList<>();this.dfs(n, 0, ansList, path);int[] ans = new int[ansList.size()];for(int i = 0; i < ansList.size(); i++){ans[i] = Integer.valueOf(ansList.get(i));}return ans;}private void dfs(int n, int  depth, List<String> ansList, List<Character> path){//此时构建字符串形式的数字if(depth == n){StringBuilder sb = new StringBuilder();boolean flag = false;for(int i = 0; i < n; i++){Character c = path.get(i);//忽略字符串中的前导0字符if(flag || !c.equals('0')){flag = true;sb.append(c);}}//全是0字符组成的,跳过if(!flag){return;}//将有效字符串放到列表里面String sNum = sb.toString();ansList.add(sNum);return ;}for(int i = 0; i < 10; i++){//当前路径中添加当前数字的字符形式;path.add(String.valueOf(i).charAt(0));dfs(n, depth+1, ansList, path);//回溯path.remove(path.size()-1);}}
}

回溯与全排列

。。。

面试题13 :在O(1)时间删除链表结点

顺序查找到要删除节点的前一个然后删除O(n)

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:ListNode* deleteNode(ListNode* head, int val) {ListNode* cur = head;if(cur!=nullptr && cur->val == val)head = cur->next;while(cur!=nullptr){if(cur->next!=nullptr && cur->next->val == val)break;cur = cur->next;}if(cur)cur->next = cur->next->next;return head;}
};

力扣上只能遍历找到要删除节点的前一个节点再进行删除

将要删除的节点的下一个节点的值覆盖要删除的节点,然会删除要删除节点位置的下一个节点O(1)

deleteNode函数就是删除节点的时间复杂度是O(1)的算法

#include <iostream>
#include <vector>
using namespace std;typedef struct ListNode {int val;ListNode *next;ListNode(int x) : val(x), next(NULL) {}
}ListNode;
/*
head = [4,5,1,9], val = 5
*/
//删除节点核心函数
ListNode* deleteNode(ListNode* head, ListNode* del) {//del不是最后一个节点,-->O(1)删除方式if (del->next) {del->val = del->next->val;del->next = del->next->next;return head;}//del已经是最后一个节点了,只能遍历找到del的前一个节点然后删除-->退变为O(n)方式ListNode* cur = head;while (cur != nullptr) {if (cur->next != nullptr && cur->next == del)break;cur = cur->next;}if (cur)cur->next = cur->next->next;return head;
}
//通过vector构造链表
ListNode* constructList(vector<int> set) {ListNode* head = nullptr;ListNode* cur = nullptr;for (int i = 0; i < set.size(); ++i) {ListNode* tmp = (ListNode*)malloc(sizeof(ListNode));tmp->val = set[i];tmp->next = nullptr;if (!i)cur = head = tmp;else {cur->next = tmp;cur = tmp;}}return head;
}
//找到要删除的节点并返回
ListNode* findDel(ListNode* head, int val) {ListNode* tmp = head;while (tmp != nullptr) {if (tmp->val == val)break;tmp = tmp->next;}if (tmp)return tmp;
}
//遍历链表
void showList(ListNode* head) {ListNode* cur = head;while (cur) {cout << cur->val << "-->";cur = cur->next;}cout <<"null"<< endl;
}
void test() {vector<int> set = {4, 5, 1, 9};//构造4->5->1->9->nullptrListNode* head = constructList(set);showList(head);//删除5head = deleteNode(head, findDel(head, 5));showList(head);head = deleteNode(head, findDel(head, 4));showList(head);head = deleteNode(head, findDel(head, 9));showList(head);
}
int main() {test();return 0;
}

面试题14 :调整数组顺序使奇数位于偶数前面

使用辅助空间划分,然后合并O(n)

使用两个辅助数组,遍历一遍原数组,将偶数和奇数分别放入两个数组,按奇前偶后合并回原数组,时间复杂度O(n),不做代码展示…

双指针一次循环搞定O(n)

第一个指针从前往后走,遇到第一个偶数停止;第二个指针从后往前走,遇到第一个奇数停止。两者交换。

只要第一个指针在第二个指针前面,按以上规则循环起来,就可以调整数组顺序使奇数位于偶数前面

class Solution {
public:vector<int> exchange(vector<int>& nums) {int first = 0, last = nums.size() - 1;while (first < last-1) {//first:当前状态下从前往后第一个偶数while (first < nums.size() && nums[first] % 2 != 0) ++first;//last:当前状态下从后往前第一个奇数while (last > 0 && nums[last] % 2 == 0) --last;//first >= last:说明已经奇前偶后//first或者last已经超出数组边界,说明数组全是奇数或者偶数则不需要任何交换if (first >= nums.size() || last < 0 || first >= last)break;int tmp = nums[first];nums[first] = nums[last];nums[last] = tmp;//位操作的交换效率可能高一点// nums[first] ^= nums[last];// nums[last] ^= nums[first];// nums[first] ^= nums[last];}return nums;}
};

我们将两个嵌套的while的判断封装成函数,这样我们只需要再封装不同的判断规则,替换两个嵌套while的判断部分就可以将数组按任何合理存在的有效的规则划分成两部分。例如:数组中按正负分(正前负后)、能否整除3分(整除在前,不能在后)

这个点就是剑指Offer提到的精髓部分

class Solution {
public:vector<int> exchange(vector<int>& nums) {int first = 0, last = nums.size() - 1;while (first < last-1) {//first:当前状态下从前往后第一个偶数while (first < nums.size() && !isEven(nums[first])) ++first;//last:当前状态下从后往前第一个奇数while (last > 0 && isEven(nums[last])) --last;//first >= last:说明已经奇前偶后//first或者last已经超出数组边界,说明数组全是奇数或者偶数则不需要任何交换if (first >= nums.size() || last < 0 || first >= last)break;// int tmp = nums[first];// nums[first] = nums[last];// nums[last] = tmp;//位操作的交换效率可能高一点nums[first] ^= nums[last];nums[last] ^= nums[first];nums[first] ^= nums[last];}return nums;}//判断是不是偶数bool isEven(int num){return !(num % 2);}
};

面试题15 :链表中倒数第k个结点

单向链表所以注定不能从链表尾回溯k步得到目标节点

利用栈O(n)

遍历一遍链表依次全部入栈(入节点地址),出栈k次得到节点地址

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:ListNode* getKthFromEnd(ListNode* head, int k) {ListNode* res = nullptr;stack<ListNode*> st;while(head){st.push(head);head = head->next;}while(k){res = st.top();st.pop();--k;}return res;}
};

在力扣中,响应时间还算可以接受(4ms);耗费内存真心不小10.5MB

利用栈思想手动优化,我们最终只要得到倒数k个栈定元素就可以了,因此我们可以用k个元素的数组(使数组逻辑上是循环的)去模拟压栈出栈这个过程

循环数组模拟栈(数组在堆空间动态开辟)O(n)

循环数组的好处是,不用出栈的步骤,直接返回逻辑数组最后一个元素的下一个元素就是倒数第K个节点的地址

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:ListNode* getKthFromEnd(ListNode* head, int k) {// ListNode** arr_stack = (ListNode**)malloc(sizeof(ListNode*)*k);ListNode** arr_stack = new ListNode*[k]();auto_ptr<ListNode**> ap = arr_stack;int i = 0;while(head){arr_stack[i] = head;i = (i+1) % k; //arr_stack在逻辑上是循环的head = head->next;}return arr_stack[i];}
};

响应时间和内存消耗都与上面代码持平,当数据量多的时候,当前代码一定比上面代码内存消耗少

存在改进的点就是arr_stack的堆空间我们应该用智能指针维护,或者在return前用临时变量记录一下结果手动delete空间:

在return前用临时变量记录一下结果手动delete空间
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:ListNode* getKthFromEnd(ListNode* head, int k) {ListNode* res = nullptr;ListNode** arr_stack = new ListNode*[k]();int i = 0;while(head){arr_stack[i] = head;i = (i+1) % k; //arr_stack在逻辑上是循环的head = head->next;}res = arr_stack[i];delete[] arr_stack;return res;}
};
智能指针维护堆空间

模拟实现智能指针维护堆空间

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
template <class T>
class selef_auto_ptr {
public:selef_auto_ptr(T* spa) { this->space = spa; }~selef_auto_ptr() {delete[] this->space;}
private:T* space;
};
class Solution {
public:ListNode* getKthFromEnd(ListNode* head, int k) {// ListNode** arr_stack = new ListNode*[k]();ListNode** arr_stack = new ListNode * [k]();selef_auto_ptr<ListNode*> ap(arr_stack);int i = 0;while (head) {arr_stack[i] = head;i = (i + 1) % k; //arr_stack在逻辑上是循环的head = head->next;}return arr_stack[i];}
};

双指针法O(n)

让两个指针保持k-1距离的指针以相同的速度遍历链表,当靠后的指针走到最后一个节点就停止,此时前面的指针指向了倒数第k个节点

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:ListNode* getKthFromEnd(ListNode* head, int k) {ListNode* first = head;ListNode* last = head;int tmp = k;while(--tmp)last = last->next;while(last->next){first = first->next;last = last->next;}return first;}
};

双指针法一定比借助后进先出数据结构省内存消耗,也就是空间复杂度低

面试题16 :反转链表

指针法

原链表不断头删,新链表不断头插

原链表需要的指针:head(指向链表头)、tmp(用于指向头删的节点)
新链表需要的指针:newHead(用于维护新链表–>头插,永远指向新链表第一个节点)

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:ListNode* reverseList(ListNode* head) {ListNode* newHead = nullptr;while(head){ListNode* tmp = head;head = head->next;if(!newHead){newHead = tmp;newHead->next = nullptr;}else{tmp->next = newHead;newHead = tmp;}}return newHead;}
};

分析以上代码:

当给定的原链表为空,返回的依然是空–>正确
当给定的原链表只有一个节点–>正确

递归

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:ListNode* reverseList(ListNode* head) {if(!head || !head->next) return head;ListNode* node = reverseList(head->next);head->next->next = head;head->next = nullptr;return node;}
};

面试题17 :合并两个排序的链表

遍历链表直接合并

同时遍历两个链表,取两个链表中最小值的节点尾插到结果链表,当一个链表走到尾时,循环结束。将非空连接到结果链表尾就合并了两个排序链表了

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {ListNode* tmp = nullptr;ListNode* res = nullptr;ListNode* res_end = nullptr;//取两个链表中最小值的节点尾插到结果链表while(l1 && l2){if(l1->val <= l2->val){tmp = l1;l1 = l1->next;}else{tmp = l2;l2 = l2->next;}if(!res)res = res_end = tmp;else{res_end->next = tmp;res_end = tmp;}}//有一个链表是空了就将非空连接到结果链表尾if(l1){if(!res)res = l1;elseres_end->next = l1;}else{if(!res)res = l2;elseres_end->next = l2;}return res;}
};

代码精简以下如下:

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {ListNode* tmp = nullptr;ListNode* res = nullptr;ListNode* res_end = nullptr;//取两个链表中最小值的节点尾插到结果链表while(l1 && l2){tmp = l1->val <= l2->val ? l1 : l2;tmp == l1 ? l1 = l1->next : l2 = l2->next;res_end = !res ? res = tmp : res_end->next = tmp;}//有一个链表是空了就将非空连接到结果链表尾if(l1)!res ? res = l1 : res_end->next = l1;else!res ? res = l2 : res_end->next = l2;return res;}
};

面试题18 :树的子结构

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

B是A的子结构, 即 A中有出现和B相同的结构和节点值。

例如:
给定的树 A:

     3/ \4   5/ \1   2

给定的树 B:

   4 /1

返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。

思路

这道题思路其实很简单,只需要遍历树A的所有节点,只有与树B的根节点相同则进行第二部判断,判断树B能否在树A中其它子节点值也相同

递归思路比较容易,先看递归解法

递归
/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/
class Solution {
public:bool isSubStructure(TreeNode* A, TreeNode* B) {bool result = false;if(A && B){if(A->val == B->val)result = doesTree1HaveTree2(A, B);if(!result)result = isSubStructure(A->left, B);if(!result)result = isSubStructure(A->right, B);}return result;}bool doesTree1HaveTree2(TreeNode* Tree1, TreeNode* Tree2){if(!Tree2)return true;if(!Tree1)return false;if(Tree1->val != Tree2->val)return false;return doesTree1HaveTree2(Tree1->left, Tree2->left) && doesTree1HaveTree2(Tree1->right, Tree2->right);}};

迭代

借助队列结构实现树的层次遍历,一旦树B的根节点在树A中找到,就判断树B其它节点是否也在树A中

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/
class Solution {
public:bool isSubStructure(TreeNode* A, TreeNode* B) {if(!B) return false;vector<TreeNode*> mid_res_set = levelTraversal(A, B->val); //在A中找到所有与B根节点值相同的节点for(int i=0; i<mid_res_set.size(); ++i){if(source_has_des(mid_res_set[i], B)) return true;}return false;}//借助队列结构实现树的层次遍历,返回找到节点值的数组vector<TreeNode*> levelTraversal(TreeNode* des, int find_val){//空树,直接返回空数组if(!des) return {};//队列queue<TreeNode*> qu;qu.push(des);//结果数组vector<TreeNode*> res;//保存层次遍历顺序的节点TreeNode* element;//层次遍历while(!qu.empty()){element = qu.front();if(element->val == find_val)res.push_back(element);qu.pop(); //出队一个根节点//入队这个根的两个子节点if(element->left) qu.push(element->left);if(element->right) qu.push(element->right);}return res;}//同时前序遍历des和source判断des是不是在source中能找到:若是一旦有节点不相同就返回假,遍历完了就返回真bool source_has_des(TreeNode* source, TreeNode* des){stack<TreeNode*> st_des;stack<TreeNode*> st_source;TreeNode* pNode_des = des;TreeNode* pNode_source = source;while (pNode_des != nullptr || !st_des.empty()) {if (pNode_des != nullptr) {if(!pNode_source) return false;if(pNode_des->val != pNode_source->val)return false;st_des.push(pNode_des);st_source.push(pNode_source);pNode_des = pNode_des->left;pNode_source = pNode_source->left;}else {TreeNode* node_des = st_des.top();TreeNode* node_source = st_source.top();st_des.pop();st_source.pop();pNode_des = node_des->right;pNode_source = node_source->right;}}return true;}
};

二叉树的遍历之递归和非递归的广度、深度优先遍历

广度优先搜索BFS(宽度优先搜索,或横向优先搜索)

也就是二叉树的层次遍历

利用队列结构:初始状态:顶级根节点入队队列不空就循环出队,出队的节点尾插入vector,出一个节点就入出队的节点的左节点和右节点(没有左或者右就不入)循环结束vector就记录了层次遍历的所有节点
//层次遍历(利用队列)
vector<TreeNode*> levelTraverse(TreeNode* des) {//空树,直接返回空数组if (!des) return {};//队列queue<TreeNode*> qu;qu.push(des);//结果数组vector<TreeNode*> res;//保存层次遍历的节点TreeNode* element;//层次遍历while (!qu.empty()) {element = qu.front();res.push_back(element);qu.pop(); //出队一个根节点//入队这个根的两个子节点(先左后右)if (element->left) qu.push(element->left);if (element->right) qu.push(element->right);}return res;
}

深度优先搜索DFS:前序、中序、后续遍历

前序
递归
void preOrderTraverse1Recursion(TreeNode* des, vector<TreeNode*>& res) {if (des) {res.push_back(des);preOrderTraverse1Recursion(des->left, res);preOrderTraverse1Recursion(des->right, res);}
}
非递归

方法1

a. 初始状态:整个树的顶级根节点先入栈b. 栈非空就循环出栈,每层循环出栈的节点的右子节点和左子节点依次入栈(右或者左为空就不入)
//前序遍历(循环、利用栈)
/*
初始状态:整个树的顶级根节点先入栈栈非空就循环出栈,每层循环出栈的节点的右子节点和左子节点依次入栈(右或者左为空就不入)
*/
vector<TreeNode*> preOrderTraverse(TreeNode* des) {//空树,直接返回空数组if (!des) return {};//栈stack<TreeNode*> st;st.push(des);//结果数组vector<TreeNode*> res;//保存前序遍历的节点TreeNode* element;//层次遍历while (!st.empty()) {element = st.top();res.push_back(element);st.pop(); //出队一个根节点//入栈这个根的两个子节点(先右后左)if (element->right) st.push(element->right);if (element->left) st.push(element->left);}return res;
}

方法2

根据前序遍历的顺序,优先访问根结点,然后在访问左子树和右子树。所以,对于任意结点node,第一部分即直接访问之,
之后在判断左子树是否为空,不为空时即重复上面的步骤,直到其为空。若为空,则需要访问右子树。
注意,在访问过左孩子之后,需要反过来访问其右孩子,所以,需要栈这种数据结构的支持。对于任意一个结点node,具体步骤如下:a. 访问之,并把结点node入栈,当前结点置为左孩子;b. 判断结点node是否为空,若为空,则取出栈顶结点并出栈,将右孩子置为当前结点;
否则重复a)步直到当前结点为空或者栈为空(可以发现栈中的结点就是为了访问右孩子才存储的)
//前序遍历(循环、利用栈)
/*
根据前序遍历的顺序,优先访问根结点,然后在访问左子树和右子树。所以,对于任意结点node,第一部分即直接访问之,
之后在判断左子树是否为空,不为空时即重复上面的步骤,直到其为空。若为空,则需要访问右子树。
注意,在访问过左孩子之后,需要反过来访问其右孩子,所以,需要栈这种数据结构的支持。对于任意一个结点node,具体步骤如下:a)访问之,并把结点node入栈,当前结点置为左孩子;b)判断结点node是否为空,若为空,则取出栈顶结点并出栈,将右孩子置为当前结点;
否则重复a)步直到当前结点为空或者栈为空(可以发现栈中的结点就是为了访问右孩子才存储的)
*/
vector<TreeNode*> preOrderTraverse2(TreeNode* des) {stack<TreeNode*> st;vector<TreeNode*> res;TreeNode* pNode = des;while (pNode != nullptr || !st.empty()) {if (pNode != nullptr) {res.push_back(pNode);st.push(pNode);pNode = pNode->left;}else { //pNode == null && !stack.isEmpty()TreeNode* node = st.top();st.pop();pNode = node->right;}}return res;
}

中序
递归
void inOrderTraverseRecursion(TreeNode* des, vector<TreeNode*>& res) {if (des) {preOrderTraverse1Recursion(des->left, res);res.push_back(des);preOrderTraverse1Recursion(des->right, res);}
}
非递归
vector<TreeNode*> inOrderTraverse2(TreeNode* des) {stack<TreeNode*> st;vector<TreeNode*> res; //结果数组TreeNode* pNode = des;while (pNode != nullptr || !st.empty()) {if (pNode != nullptr) {st.push(pNode);pNode = pNode->left;}else { //pNode == null && !stack.isEmpty()TreeNode* node = st.top();st.pop();res.push_back(node);pNode = node->right;}}return res;
}

后序
递归
void postOrderTraverseRecursion(TreeNode* des, vector<TreeNode*>& res) {if (des) {postOrderTraverseRecursion(des->left, res);postOrderTraverseRecursion(des->right, res);res.push_back(des);}
}
非递归
vector<TreeNode*> postOrderTraverse2(TreeNode* des) {vector<TreeNode*> res;if (!des) return res;stack<TreeNode*> st;TreeNode* pNode = des;TreeNode* pre = nullptr;while (pNode != nullptr || !st.empty()) {if (pNode != nullptr) {st.push(pNode);pNode = pNode->left;}else {TreeNode* node = st.top();st.pop();pNode = node->right;if (!node->right)res.push_back(node);else {node->right = nullptr;st.push(node);}}}return res;
}

所有遍历的测试代码如下:

//利用队列实现层次遍历,栈实现前序中序后续遍历
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <vector>
#include <queue>
#include <stack>
using namespace std;
struct TreeNode {int val;TreeNode* left;TreeNode* right;TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
//构造如下二叉树
/*
0:3/ \4   5/ \1  21:3/ \4   5/ \ / \1  2 6  7
*/
TreeNode* constructBinaryTree() {TreeNode* tmp, * root;root = tmp = new TreeNode(3);tmp->right = new TreeNode(5);
//条件编译测试两种树0和1分别对应上面两种树
#if 1tmp->right->left = new TreeNode(6);tmp->right->right = new TreeNode(7);
#endiftmp = tmp->left = new TreeNode(4);tmp->right = new TreeNode(2);tmp->left = new TreeNode(1);return root;
}
//广度优先搜索BFS(宽度优先搜索,或横向优先搜索):层次遍历
//层次遍历(利用队列)
vector<TreeNode*> levelTraverse(TreeNode* des) {//空树,直接返回空数组if (!des) return {};//队列queue<TreeNode*> qu;qu.push(des);//结果数组vector<TreeNode*> res;//保存层次遍历的节点TreeNode* element;//层次遍历while (!qu.empty()) {element = qu.front();res.push_back(element);qu.pop(); //出队一个根节点//入队这个根的两个子节点(先左后右)if (element->left) qu.push(element->left);if (element->right) qu.push(element->right);}return res;
}//深度优先搜索DFS:前序、中序、后续遍历
//前序遍历(递归)
void preOrderTraverse1Recursion(TreeNode* des, vector<TreeNode*>& res) {if (des) {res.push_back(des);preOrderTraverse1Recursion(des->left, res);preOrderTraverse1Recursion(des->right, res);}
}
//前序遍历(循环、利用栈)
/*
初始状态:整个树的顶级根节点先入栈栈非空就循环出栈每层循环出栈的节点的右子节点和左子节点依次入栈(右或者左为空就不入)
*/
vector<TreeNode*> preOrderTraverse(TreeNode* des) {//空树,直接返回空数组if (!des) return {};//栈stack<TreeNode*> st;st.push(des);//结果数组vector<TreeNode*> res;//保存前序遍历的节点TreeNode* element;//层次遍历while (!st.empty()) {element = st.top();res.push_back(element);st.pop(); //出队一个根节点//入栈这个根的两个子节点(先右后左)if (element->right) st.push(element->right);if (element->left) st.push(element->left);}return res;
}
//前序遍历(循环、利用栈)
/*
根据前序遍历的顺序,优先访问根结点,然后在访问左子树和右子树。所以,对于任意结点node,第一部分即直接访问之,
之后在判断左子树是否为空,不为空时即重复上面的步骤,直到其为空。若为空,则需要访问右子树。
注意,在访问过左孩子之后,需要反过来访问其右孩子,所以,需要栈这种数据结构的支持。对于任意一个结点node,具体步骤如下:a)访问之,并把结点node入栈,当前结点置为左孩子;b)判断结点node是否为空,若为空,则取出栈顶结点并出栈,将右孩子置为当前结点;
否则重复a)步直到当前结点为空或者栈为空(可以发现栈中的结点就是为了访问右孩子才存储的)
*/
vector<TreeNode*> preOrderTraverse2(TreeNode* des) {stack<TreeNode*> st;vector<TreeNode*> res;TreeNode* pNode = des;while (pNode != nullptr || !st.empty()) {if (pNode != nullptr) {res.push_back(pNode);st.push(pNode);pNode = pNode->left;}else { //pNode == null && !stack.isEmpty()TreeNode* node = st.top();st.pop();pNode = node->right;}}return res;
}//中序遍历(递归)
void inOrderTraverseRecursion(TreeNode* des, vector<TreeNode*>& res) {if (des) {preOrderTraverse1Recursion(des->left, res);res.push_back(des);preOrderTraverse1Recursion(des->right, res);}
}
//中序遍历(循环、利用栈)
/**/
vector<TreeNode*> inOrderTraverse2(TreeNode* des) {stack<TreeNode*> st;vector<TreeNode*> res; //结果数组TreeNode* pNode = des;while (pNode != nullptr || !st.empty()) {if (pNode != nullptr) {st.push(pNode);pNode = pNode->left;}else { //pNode == null && !stack.isEmpty()TreeNode* node = st.top();st.pop();res.push_back(node);pNode = node->right;}}return res;
}//后续遍历(递归)
void postOrderTraverseRecursion(TreeNode* des, vector<TreeNode*>& res) {if (des) {postOrderTraverseRecursion(des->left, res);postOrderTraverseRecursion(des->right, res);res.push_back(des);}
}
//后续遍历(循环、利用栈)
vector<TreeNode*> postOrderTraverse2(TreeNode* des) {vector<TreeNode*> res;if (!des) return res;stack<TreeNode*> st;TreeNode* pNode = des;TreeNode* pre = nullptr;while (pNode != nullptr || !st.empty()) {if (pNode != nullptr) {st.push(pNode);pNode = pNode->left;}else {TreeNode* node = st.top();st.pop();pNode = node->right;if (!node->right)res.push_back(node);else {node->right = nullptr;st.push(node);}}}return res;
}
/*
* postOrderTraverse2启示自:
res = []
stack = []
while root != None or len(stack) != 0:if root != None :# 入栈stack.append(root)# 继续迭代左节点root = root.leftelse :# 先出栈p = stack.pop()# 迭代右节点root = p.right# 右节点为空 访问该节点if p.right == None:res.append(p.val)# 右节点不为空 继续迭代else:# 将当前节点的右节点置为None 再将其放回stackp.right = Nonestack.append(p)return res
*/
vector<TreeNode*> postOrderTraverse3(TreeNode* des) {vector<TreeNode*> res;if (!des) return res;stack<TreeNode*> st;TreeNode* pNode = des;TreeNode* pre = nullptr;while (pNode != nullptr || !st.empty()) {if (pNode != nullptr) {st.push(pNode);pNode = pNode->left;}else {pNode = st.top();st.pop();if (nullptr == pNode->right || pNode->right == pre) {res.push_back(pNode);pre = pNode;pNode = nullptr;}else {st.push(pNode);pNode = pNode->right;}}}return res;
}//遍历vector
void vector_travere(vector<TreeNode*> v) {for (int i = 0; i < v.size(); ++i)cout << v[i]->val << " ";cout << endl;
}
int main() {TreeNode* t = constructBinaryTree();//层次遍历cout << "层次" << endl;vector<TreeNode*> levelTraverseArray = levelTraverse(t);vector_travere(levelTraverseArray);cout << endl;//前序遍历cout <<"前序"<< endl;vector<TreeNode*> res_preOrder;preOrderTraverse1Recursion(t, res_preOrder);vector_travere(res_preOrder);vector<TreeNode*> preTraverseArray = preOrderTraverse(t);vector_travere(preTraverseArray);vector<TreeNode*> preTraverseArray2 = preOrderTraverse2(t);vector_travere(preTraverseArray2);cout << endl;//中序遍历cout << "中序" << endl;vector<TreeNode*> res_inOrder;inOrderTraverseRecursion(t, res_inOrder);vector_travere(res_inOrder);vector<TreeNode*> inOrderTraverseArray = inOrderTraverse2(t);vector_travere(inOrderTraverseArray);cout << endl;//后序遍历cout << "后序" << endl;vector<TreeNode*> res_postOrder;postOrderTraverseRecursion(t, res_postOrder);vector_travere(res_postOrder);vector<TreeNode*> postOrderTraverseArray = postOrderTraverse2(t); //postOrderTraverse2会破坏原二叉树结构vector_travere(postOrderTraverseArray);t = constructBinaryTree(); //重新构造二叉树postOrderTraverseArray = postOrderTraverse3(t);vector_travere(postOrderTraverseArray);cout << endl;return 0;
}

面试题19 :二叉树的镜像

只需将每个根节点的左右子树交换即可

递归

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/
class Solution {
public:TreeNode* mirrorTree(TreeNode* root) {if(!root) return root;TreeNode* tmp = root->left;root->left = root->right;root->right = tmp;mirrorTree(root->left);mirrorTree(root->right);return root;}
};

使用栈或者队列改非递归

// 栈
TreeNode* mirrorTree(TreeNode* root) {stack<TreeNode*> sck;sck.push(root);while(!sck.empty()){TreeNode* tmp = sck.top();sck.pop();if(!tmp) continue;swap(tmp->left,tmp->right);if(tmp->right != NULL) sck.push(tmp->right);if(tmp->left != NULL) sck.push(tmp->left);}return root;
}
// 队列
TreeNode* mirrorTree(TreeNode* root) {queue<TreeNode*> que;que.push(root);while(!que.empty()){TreeNode* tmp = que.front();que.pop();if(tmp == NULL) continue;swap(tmp->left,tmp->right);if(tmp->left) que.push(tmp->left);if(tmp->right) que.push(tmp->right);}return root;
}

面试题20 :顺时针打印矩阵

这种题目也叫转圈打印矩阵,之前在牛客算法题有遇到

思路可以参考专栏:算法(Java 牛客网)的文章:16.转圈打印矩阵

牛客题目:转圈打印矩阵 个人提交通过代码参考

class Solution {
public:/*** * @param matrix int整型vector<vector<>> the matrix* @return int整型vector*/vector<int> printMatrix(vector<vector<int> >& matrix) {int row = matrix.size();int col = matrix[0].size();int left = 0;int right = col-1;int top = 0;int floor = row - 1;int count = 0;vector<int> res(row*col, 0);while(left<right && top<floor){//往右for(int i=left; i<=right; ++i)res[count++] = matrix[top][i];top++;//往下for(int i=top; i<=floor; ++i)res[count++] = matrix[i][right];right--;//往左for(int i=right; i>=left; --i)res[count++] = matrix[floor][i];floor--;//往上for(int i=floor; i>=top; --i)res[count++] = matrix[i][left];left++;}if(left == right) //往下for (int i = top; i <= floor; ++i)res[count++] = matrix[i][right];else if(top == floor) //往右for(int i=left; i<=right; ++i)res[count++] = matrix[top][i];return res;}
};

参考以上Java代码实现C++代码

牛客中判题测试用例没有矩阵是空的情况,C++需要判断矩阵是否是空,是空就返回空数组{},其它代码逻辑与牛客网Java完全一致

class Solution {
public:vector<int> spiralOrder(vector<vector<int>>& matrix) {if(!matrix.size()) return {};int row = matrix.size();int col = matrix[0].size();int left = 0;int right = col-1;int top = 0;int floor = row - 1;int count = 0;vector<int> res(row*col, 0);while(left<right && top<floor){//往右for(int i=left; i<=right; ++i)res[count++] = matrix[top][i];top++;//往下for(int i=top; i<=floor; ++i)res[count++] = matrix[i][right];right--;//往左for(int i=right; i>=left; --i)res[count++] = matrix[floor][i];floor--;//往上for(int i=floor; i>=top; --i)res[count++] = matrix[i][left];left++;}if(left == right) //往下for (int i = top; i <= floor; ++i)res[count++] = matrix[i][right];else if(top == floor) //往右for(int i=left; i<=right; ++i)res[count++] = matrix[top][i];return res;}
};

按照剑指Offer书上的代码逻辑实现

class Solution {
public:vector<int> spiralOrder(vector<vector<int>>& matrix) {if(!matrix.size()) return {};int rows = matrix.size();int columns = matrix[0].size();vector<int> res;int start = 0;while(columns > start*2 && rows > start*2){printMatrixInCircle(matrix, columns, rows, start, res);++start;}return res;}void printMatrixInCircle(vector<vector<int>>& matrix, int columns, int rows, int start, vector<int>& res){int endX = columns - 1 - start;int endY = rows - 1 - start;//从左到右for(int i = start; i <= endX; ++i)res.push_back(matrix[start][i]);//从上到下if(start < endY)for(int i = start+1; i <= endY; ++i)res.push_back(matrix[i][endX]);//从右到左if(start < endX && start < endY)for(int i = endX-1; i >= start; --i)res.push_back(matrix[endY][i]);//从下到上if(start < endX && start < endY - 1)for(int i = endY-1; i >= start+1; --i)res.push_back(matrix[i][start]);}};

面试题21 :包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。

思路

只需要维护一个和栈相同大小的辅助栈,栈每入栈一个元素x,判断元素是否小于等于辅助栈堆顶,小于等于的情况下辅助栈也入栈元素x(初始辅助栈为空时直接入栈x);若元素x大于辅助栈栈顶元素则辅助栈再入栈一次它的栈定元素。

栈每出栈一个元素,辅助栈也同时出栈一个元素

当前状态栈的最小元素就是辅助栈的栈顶元素

C++

class MinStack {
public:/** initialize your data structure here. */MinStack() {}void push(int x) {this->s_stack.push(x);if(this->min_stack.empty() || x <= this->min_stack.top())this->min_stack.push(x);elsethis->min_stack.push(this->min_stack.top());}void pop() {this->s_stack.pop();this->min_stack.pop();}int top() {return s_stack.top();}int min() {return this->min_stack.top();}
private:stack<int> min_stack;stack<int> s_stack;
};/*** Your MinStack object will be instantiated and called as such:* MinStack* obj = new MinStack();* obj->push(x);* obj->pop();* int param_3 = obj->top();* int param_4 = obj->min();*/

C

typedef struct {int* s_stack;int* min_stack;int sz;int cap;
} MinStack;/** initialize your data structure here. */MinStack* minStackCreate() {MinStack* tmp = (MinStack*)malloc(sizeof(MinStack));int initialization = 600;tmp->s_stack = (int*)malloc(4*initialization);tmp->min_stack = (int*)malloc(4*initialization);tmp->cap = initialization;tmp->sz = 0;return tmp;
}void minStackPush(MinStack* obj, int x) {if(obj->sz < obj->cap){ //栈未满int index = obj->sz;obj->s_stack[index] = x;if(index == 0 || x <= obj->min_stack[index-1])obj->min_stack[index] = x;elseobj->min_stack[index] = obj->min_stack[index-1];obj->sz++;}else{ //栈满,扩容int* s_stack_tmp = (int*)malloc(obj->cap * 4 * 2);int* min_stack_tmp = (int*)malloc(obj->cap * 4 * 2);if(!s_stack_tmp || !min_stack_tmp){if(s_stack_tmp) free(s_stack_tmp);if(min_stack_tmp) free(min_stack_tmp);minStackPush(obj, x);}memcpy(s_stack_tmp, obj->s_stack, obj->cap * 4);memcpy(min_stack_tmp, obj->min_stack, obj->cap * 4);free(obj->min_stack);free(obj->s_stack);obj->min_stack = min_stack_tmp;obj->s_stack = s_stack_tmp;obj->cap *=  2;}
}void minStackPop(MinStack* obj) {obj->sz--;
}int minStackTop(MinStack* obj) {if(!obj->sz) return -1;return obj->s_stack[obj->sz - 1];
}int minStackMin(MinStack* obj) {if(!obj->sz) return -1;return obj->min_stack[obj->sz - 1];
}void minStackFree(MinStack* obj) {free(obj->s_stack);free(obj->min_stack);
}/*** Your MinStack struct will be instantiated and called as such:* MinStack* obj = minStackCreate();* minStackPush(obj, x);* minStackPop(obj);* int param_3 = minStackTop(obj);* int param_4 = minStackMin(obj);* minStackFree(obj);
*/

栈的初始容量initialization给600可以通过oj,但是给小一点就会出错,说明栈扩容存在逻辑问题

输入
["MinStack","push","push","push","min","pop","top","min"]
[[],[-2],[0],[-3],[],[],[],[]]
输出
[null,null,null,null,-2,null,-2,-2]
预期结果
[null,null,null,null,-3,null,0,-2]

本地编译器测试…

栈扩容之后忘了入栈

typedef struct {int* s_stack;int* min_stack;int sz;int cap;
} MinStack;/** initialize your data structure here. */MinStack* minStackCreate() {MinStack* tmp = (MinStack*)malloc(sizeof(MinStack));int initialization = 50;tmp->s_stack = (int*)malloc(4*initialization);tmp->min_stack = (int*)malloc(4*initialization);tmp->cap = initialization;tmp->sz = 0;return tmp;
}
void minStackPush(MinStack* obj, int x) {if (obj->sz < obj->cap) { //栈未满int index = obj->sz;obj->s_stack[index] = x;if (index == 0 || x <= obj->min_stack[index - 1])obj->min_stack[index] = x;elseobj->min_stack[index] = obj->min_stack[index - 1];obj->sz++;}else { //栈满,扩容int* s_stack_tmp = (int*)malloc(obj->cap * 4 * 2);int* min_stack_tmp = (int*)malloc(obj->cap * 4 * 2);if (!s_stack_tmp || !min_stack_tmp) {if (s_stack_tmp) free(s_stack_tmp);if (min_stack_tmp) free(min_stack_tmp);minStackPush(obj, x);}memcpy(s_stack_tmp, obj->s_stack, obj->cap * 4);memcpy(min_stack_tmp, obj->min_stack, obj->cap * 4);free(obj->min_stack); free(obj->s_stack);obj->min_stack = min_stack_tmp;obj->s_stack = s_stack_tmp;obj->cap *= 2;minStackPush(obj, x);}
}void minStackPop(MinStack* obj) {obj->sz--;
}int minStackTop(MinStack* obj) {if(!obj->sz) return -1;return obj->s_stack[obj->sz - 1];
}int minStackMin(MinStack* obj) {if(!obj->sz) return -1;return obj->min_stack[obj->sz - 1];
}void minStackFree(MinStack* obj) {free(obj->s_stack);free(obj->min_stack);
}/*** Your MinStack struct will be instantiated and called as such:* MinStack* obj = minStackCreate();* minStackPush(obj, x);* minStackPop(obj);* int param_3 = minStackTop(obj);* int param_4 = minStackMin(obj);* minStackFree(obj);
*/

发现在oj中栈初始容量给(50)执行用时最少:28ms

面试题22 :栈的压入、弹出序列

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

思路

建立一个辅助栈,遍历popped数组,按pushed顺序入栈直到popped[i]入栈,栈顶元素是popped[i]然后popped[i]出栈,继续洗一次循环(下次一循环要从pushed未入栈的元素开始),循环最终结束辅助栈是空说明可以按pushed入栈顺序,该栈的弹出顺序可以得到popped序列

1(error)
class Solution {
public:bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {stack<int> pushed_st;//依次查看popped[i]int index = 0;for (int i = 0; i < popped.size(); ++i) {//栈空,就直接入栈到popped[i]if (index < popped.size() && (pushed_st.empty() || !pushed_st.empty() && pushed_st.top() != popped[i])) {while (pushed[index] != popped[i])pushed_st.push(pushed[index++]);pushed_st.push(pushed[index++]);}if (pushed_st.top() == popped[i])pushed_st.pop();}if (pushed_st.empty()) return true;return false;}
};

以上代码错误,61 / 151 个通过测试用例

本地编译器测试…

是越界异常,for循环中的第一个while和if没有判断下标越界

2(right)
class Solution {
public:bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {stack<int> pushed_st;//依次查看popped[i]int index = 0;for (int i = 0; i < popped.size(); ++i) {//栈空,就直接入栈到popped[i];栈非空但栈顶元素不是popped[i]也需要入栈(pushed_st.empty() || !pushed_st.empty() && pushed_st.top() != popped[i])//循环每次都是入栈直到popped[i]入栈才停止,栈顶元素不是popped[i]说明这个元素还未入栈if (index < popped.size() && (pushed_st.empty() || !pushed_st.empty() && pushed_st.top() != popped[i])) {//按照pushed顺序入栈,循环入栈直到遇到popped[i]才停止while (index < popped.size() && pushed[index] != popped[i])pushed_st.push(pushed[index++]);//popped[i]入栈if(index < popped.size())pushed_st.push(pushed[index++]);}//栈顶此时是popped[i]就出栈if (pushed_st.top() == popped[i])pushed_st.pop();}//最终栈空说明按pushed顺序入栈以及出栈操作可以得到popped栈return pushed_st.empty();}
};
3(本地编译器:将操作顺序打印存储到vector)
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <vector>
#include <string>
#include <stack>
using namespace std;
bool validateStackSequences(vector<int>& pushed, vector<int>& popped, vector<string>& res) {stack<int> pushed_st;//依次查看popped[i]int index = 0;for (int i = 0; i < popped.size(); ++i) {//栈空,就直接入栈到popped[i]if (index < popped.size() && (pushed_st.empty() || !pushed_st.empty() && pushed_st.top() != popped[i])) {while (index < popped.size() && pushed[index] != popped[i]) {res.push_back("push(" + to_string(pushed[index]) + ")");pushed_st.push(pushed[index++]);}if (index < popped.size())pushed_st.push(pushed[index++]);}if (pushed_st.top() == popped[i]) {res.push_back("pop(" + to_string(pushed_st.top()) + ")");pushed_st.pop();}}if (pushed_st.empty()) return true;return false;
}
int main() {vector<int> pushed = { 1,2,3,4,5 };vector<int> popped = { 4,5,3,2,1 };vector<string> res;//vector<int> pushed = { 8,9,2,3,7,0,5,4,6,1 };//vector<int> popped = { 6,8,2,1,3,9,0,7,4,5 };validateStackSequences(pushed, popped, res);for (int i = 0; i < res.size()-1; ++i)cout << res[i] << "->";cout << res[res.size() - 1] << endl;return 0;
}

面试题23 :从上往下打印二叉树————实际就是二叉树的层次遍历:序列化二叉树

请参考:二叉树的遍历之递归和非递归的广度、深度优先遍历

OJ代码

序列化就是层次遍历

反序列化的过程:

首先让顶级根节点连接上左右子节点

利用队列结构记录每层循环连接的左右子节点

下次层循环左右节点必然存储在队列出队顺序第一个非空节点上

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/
#include <string>
class Codec {
public:// Encodes a tree to a single string.string serialize(TreeNode* root) {//空树,直接返回空if (!root) return "";//队列queue<TreeNode*> qu;qu.push(root);//结果字符串string res = "[";//保存层次遍历的节点TreeNode* element;//层次遍历while (!qu.empty()) {element = qu.front();res += !element ? "null," : to_string(element->val) + ",";qu.pop(); //出队一个根节点//入队这个根的两个子节点(先左后右)if (element) qu.push(element->left);if (element) qu.push(element->right);}res.erase(res.size() - 1, 1); //删除最后一个多的,res += "]";return res;}// Decodes your encoded data to tree.TreeNode* deserialize(string data) {if (!data.size()) return nullptr;data.erase(0, 1);data.erase(data.size()-1, 1);//string --> vector<string>queue<string> series;int i = 0, n = data.size();while (i < data.size()) {string tmp = "";while (i < data.size() && data[i] != ',')tmp += data[i++];series.push(tmp);++i;}TreeNode* root = nullptr;TreeNode* left, * right;TreeNode*  child_left = nullptr, * child_right = nullptr;queue<TreeNode*> child_left_queue;queue<TreeNode*> child_right_queue;//按层次遍历顺序建立二叉树while (!series.empty()) {if (!root) {left = right = str2node(series.front()); series.pop();}else {left = child_left_queue.front(); child_left_queue.pop();right = child_right_queue.front(); child_right_queue.pop();}if (left) {child_left = !series.empty() ? str2node(series.front()) : nullptr; if(!series.empty())series.pop();child_right = !series.empty() ? str2node(series.front()) : nullptr; if (!series.empty())series.pop();left->left = child_left;left->right = child_right;child_left_queue.push(child_left);child_right_queue.push(child_right);}if (root) {if (right) {child_left = !series.empty() ? str2node(series.front()) : nullptr; if(!series.empty())series.pop();child_right = !series.empty() ? str2node(series.front()) : nullptr; if (!series.empty())series.pop();right->left = child_left;right->right = child_right;child_left_queue.push(child_left);child_right_queue.push(child_right);}}if (!root)root = left;}return root;}//将字符串转化成节点TreeNode* str2node(string& str) {if (!str.compare("null")) return nullptr;return new TreeNode(stoi(str));}
};// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));

剑指offer书上使用deque容器实现序列化serialize

string serialize(TreeNode* root) {string res;//空树,直接返回空if (!root) return "";res += "[";//队列deque<TreeNode*> deq;deq.push_back(root);while(deq.size()){TreeNode* Pnode = deq.front();deq.pop_front();res += !Pnode ? "null," : to_string(Pnode->val) + ",";if(Pnode) deq.push_back(Pnode->left);if(Pnode) deq.push_back(Pnode->right);}res.erase(res.size() - 1, 1); //删除最后一个多的,res += "]";return res;
}

面试题24 :二叉搜索树的后序遍历序列

概念:二叉搜索树中根节点的值大于左子树中的任何一个节点的值,小于右子树中任何一个节点的值,子树也是

思路

最后一个节点是树的根结点,前面会被划分成两部分,第一部分是左子树的值且其都小于根节点的值,第二部分是右子树的值且它们都比根节点值大

按以上思路递归

递归

class Solution {
public:bool _verifyPostorder(vector<int>& postorder, int start, int end) { //[start, end]if (end < start) return false;if (start == end) return true;//if (start + 1 == end) return postorder[start] < postorder[end]; //子树没有右节点int s1 = start, e1 = start, s2 = start, e2 = end - 1;while (postorder[s2] < postorder[end]) s2++;// if (s2 == end) return true; //只有左子树,判断所有节点是不是都小于根节点if (s2 == end) return _verifyPostorder(postorder, start, end - 1); //只有左子树if (s2 == start) { //只有右子树,判断所有节点是不是都大于根节点for (int i = start; i < end; ++i)if (postorder[i] < postorder[end])return false;return _verifyPostorder(postorder, start, end - 1);}e1 = s2 - 1;//判断右子树节点全部大于根 postorder[s2, e2]>postorder[end]for (int i = s2; i <= e2; ++i)if (postorder[i] < postorder[end])return false;return _verifyPostorder(postorder, s1, e1) && _verifyPostorder(postorder, s2, e2);}bool verifyPostorder(vector<int>& postorder) {if(!postorder.size()) return true;return _verifyPostorder(postorder, 0, postorder.size() - 1);}
};

面试题25 :二叉树中和为某一值的路径

思路

参考offer书的思路。以中序遍历的方式递归探索所有路径,每一趟递归递的过程都使用栈记录路径同时累加值,若是等于所期望的值,将栈记录的路径存储到vector<vector>;归的过程出栈

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:vector<vector<int>> pathSum(TreeNode* root, int target) {if(!root) return {};vector<int> path; //起到栈的作用vector<vector<int>> res; //保存所有符合的路径int currentSum = 0;findPath(root, target, res, path, currentSum);return res;}void findPath(TreeNode* root, int target, vector<vector<int>> &res, vector<int> &path, int &currentSum){currentSum += root->val;path.push_back(root->val);//如果是叶节点,并且路径经过的节点值和等于目标值//将路径保存到res中,并继续查找,因为节点值可能是负数所以当前一层结果路径不唯一bool isleaf = !root->left && !root->right;if(currentSum == target && isleaf)res.push_back(path);//如果不是叶节点,则遍历它的子节点if(root->left)findPath(root->left, target, res, path, currentSum);if(root->right)findPath(root->right, target, res, path, currentSum);//在返回到父节点之前,在路径上删除当前节点//并在currentSum减去当前节点的值currentSum -= root->val;path.pop_back();}
};

面试题26 :复杂链表的复制

思路

遍历一遍链表,复制链表的同时,使用二维数组记录原节点和复制之后的节点的地址;利用队列存储除了主干链接关系之外的复杂链接关系

然后通过原节点和复制之后的节点的映射关系,替换队列中的复杂链接关系,然会遍历队列连接新链表复杂链接关系

/*
// Definition for a Node.
class Node {
public:int val;Node* next;Node* random;Node(int _val) {val = _val;next = NULL;random = NULL;}
};
*/
class Solution {
public:Node* copyRandomList(Node* head) {if(!head) return nullptr;int count = 0; //记录复杂链接关系数vector<vector<Node*>> map(2, vector<Node*>());//记录原节点和复制节点的映射关系queue<Node*> que; //存储复杂链接关系Node* cur = head;Node* newHead = nullptr, * newHeadEnd = nullptr;//遍历一遍链表,复制链表的同时,使用二维数组记录原节点和复制之后的节点的地址;利用队列存储除了主干链接关系之外的复杂链接关系while(cur){//建立复杂链接关系if(cur->random){que.push(cur);que.push(cur->random);count++;}//复制链表if(cur == head) newHead = newHeadEnd = new Node(cur->val);else{newHeadEnd->next = new Node(cur->val);newHeadEnd = newHeadEnd->next;}//建立原节点和复制之后的节点的映射关系map[0].push_back(cur);map[1].push_back(newHeadEnd);cur = cur->next;}//替换队列中的原链表的复杂链接关系为新链表的for(int i=0; i<count; ++i){Node* pre = que.front();que.pop();Node* tail = que.front();que.pop();for(int i=0; i<map[0].size(); ++i)if(map[0][i] == pre){que.push(map[1][i]);break;}for(int i=0; i<map[0].size(); ++i)if(map[0][i] == tail){que.push(map[1][i]);break;}}//遍历队列给新链表建立复杂链接关系while(!que.empty()){Node* pre = que.front();que.pop();Node* tail = que.front();que.pop();pre->random = tail;}return newHead;}
};

改进一下

复制链表的同时,将除了主干链接关系之外的复杂链接关系都让新链表的每个节点random复制原链表同位置的,利用依次遍历得到的记录原节点和复制之后的节点的地址的二维数组,将新链表的random改成新链表对应的节点地址,完成

/*
// Definition for a Node.
class Node {
public:int val;Node* next;Node* random;Node(int _val) {val = _val;next = NULL;random = NULL;}
};
*/
class Solution {
public:Node* copyRandomList(Node* head) {if(!head) return nullptr;vector<vector<Node*>> map(2, vector<Node*>());//记录原节点和复制节点的映射关系queue<Node*> que; //存储复杂链接关系Node* cur = head;Node* newHead = nullptr, * newHeadEnd = nullptr;//遍历一遍链表,复制链表的同时,使用二维数组记录原节点和复制之后的节点的地址;利用队列存储除了主干链接关系之外的复杂链接关系(只记录新链表中random不为空的节点地址)while(cur){//复制链表(同时复制random)if(cur == head)newHead = newHeadEnd = new Node(cur->val);else{newHeadEnd->next = new Node(cur->val);newHeadEnd = newHeadEnd->next;}newHeadEnd->random = cur->random;//记录复杂链接关系if(cur->random)que.push(newHeadEnd);//建立原节点和复制之后的节点的映射关系map[0].push_back(cur);map[1].push_back(newHeadEnd);cur = cur->next;}//建立新链表的复杂链接关系while(!que.empty()){Node* tmp = que.front();for(int i=0; i<map[0].size(); ++i)if(map[0][i] == tmp->random){tmp->random = map[1][i];break;}que.pop();}return newHead;}
};

使用map容器

/*
// Definition for a Node.
class Node {
public:int val;Node* next;Node* random;Node(int _val) {val = _val;next = NULL;random = NULL;}
};
*/
class Solution {
public:Node* copyRandomList(Node* head) {if(!head) return nullptr;map<Node*, Node*> ma;//记录原节点和复制节点的映射关系Node* cur = head;Node* newHead = nullptr, * newHeadEnd = nullptr;while(cur){//复制链表if(cur == head)newHead = newHeadEnd = new Node(cur->val);else{newHeadEnd->next = new Node(cur->val);newHeadEnd = newHeadEnd->next;}//建立原节点和复制之后的节点的映射关系ma[cur] = newHeadEnd;cur = cur->next;}for(map<Node*, Node*>::iterator it = ma.begin(); it != ma.end(); ++it){if(it->first->random)it->second->random = ma[it->first->random];}return newHead;}
};

面试题27 :二义搜索树与双向链表

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

思路

简单粗暴,二叉搜索树的中序遍历得到的就是顺序排序序列

利用队列依次存储中序遍历的节点的地址,队列依次出队将它们连接成双向循环链表

/*
// Definition for a Node.
class Node {
public:int val;Node* left;Node* right;Node() {}Node(int _val) {val = _val;left = NULL;right = NULL;}Node(int _val, Node* _left, Node* _right) {val = _val;left = _left;right = _right;}
};
*/
class Solution {
public:Node* treeToDoublyList(Node* root) {if(!root) return nullptr;queue<Node*> que = inOrderTraverse2(root); //得到中序遍历的队列Node* head = nullptr, *end = nullptr;//出队链接成双向循环链表while(!que.empty()){Node* cur = que.front();if(!head){end = head = cur;end->left = end;end->right = end;} else{cur->left = end;end->right = cur;end = cur;}que.pop();}end->right = head;head->left = end;return head;}//中序遍历,返回中序遍历得到的队列queue<Node*> inOrderTraverse2(Node* des) {stack<Node*> st;queue<Node*> res; //结果数组Node* pNode = des;while (pNode != nullptr || !st.empty()) {if (pNode != nullptr) {st.push(pNode);pNode = pNode->left;}else { //pNode == null && !stack.isEmpty()Node* node = st.top();st.pop();res.push(node);pNode = node->right;}}return res;}
};

offer书中的递归

按照中序遍历得到排序节点,递归逻辑左指向根根再指向右

左子树的最右叶节点连接根,然会根连接右子树的最左页节点

offer书中的代码过不了OJ

class Solution {
public:Node* treeToDoublyList(Node* root) {Node *pLastNodeInList = nullptr;ConvertNode(root, &pLastNodeInList);// pLastNodeInList指向双向链表的尾结点,// 我们需要返回头结点Node *pHeadOfList = pLastNodeInList;while(pHeadOfList != nullptr && pHeadOfList->left != nullptr)pHeadOfList = pHeadOfList->left;return pHeadOfList;}void ConvertNode(Node* pNode, Node** pLastNodeInList){if(pNode == nullptr)return;Node *pCurrent = pNode;if (pCurrent->left != nullptr)ConvertNode(pCurrent->left, pLastNodeInList);pCurrent->left = *pLastNodeInList; if(*pLastNodeInList != nullptr)(*pLastNodeInList)->right = pCurrent;*pLastNodeInList = pCurrent;if (pCurrent->right != nullptr)ConvertNode(pCurrent->right, pLastNodeInList);}
};

其它版本,过OJ

class Solution {
public://中序,递归Node* pre, *head;Node* treeToDoublyList(Node* root) {// 边界值if(root == nullptr) return nullptr;dfs(root);// 题目要求头尾连接head->left = pre;pre->right = head;// 返回头节点return head;}void dfs(Node* cur) {// 递归结束条件if(cur == nullptr) return;dfs(cur->left);// 如果pre为空,就说明是第一个节点,头结点,然后用head保存头结点,用于之后的返回if (pre == nullptr) head = cur;// 如果不为空,那就说明是中间的节点。并且pre保存的是上一个节点,// 让上一个节点的右指针指向当前节点else if (pre != nullptr) pre->right = cur;// 再让当前节点的左指针指向父节点,也就连成了双向链表cur->left = pre;// 保存当前节点,用于下层递归创建pre = cur;dfs(cur->right);}
};

面试题28 :字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。

offer书的思路

第一个字符,依次和后面每一个字符交换

上一次得到的所有字符串,固定第一个字符然后第二个字符与后面的所有字符交换

前两部循环起来,直到固定了前n-2个字符,n-1个字符与最后一个字符交换便得到所有组合

举个例子:

s = “abc”

第一次循环:第一个字符,依次和后面每一个字符交换加上原字符串得到字符集

“abc”, “bac”, “cba”

第二次循环:通过上一次循环得到的字符集固定第一个字符,然后第二个字符分别和后面每一个字符交换,得到字符集

“acb”, “bca”, “cab”

第二次循环已经固定了前n-2个字符,因此循环结束

最终所有排列结果是{“abc”, “bac”, “cba”, “acb”, “bca”, “cab”}

递归
class Solution {
public:vector<string> permutation(string s) {vector<string> res;char* pStr = new char[s.size()];strcpy(pStr, s.c_str());permutation(res, pStr, pStr);return res;}void permutation(vector<string> &res, char* pStr, char* pBegin){if(*pBegin == '\0')res.push_back(pStr);else{for(char* pCh = pBegin; *pCh !='\0'; ++pCh){char temp = *pCh;*pCh = *pBegin;*pBegin = temp;permutation(res, pStr, pBegin+1);temp = *pCh;*pCh = *pBegin;*pBegin = temp;}}}
};

不可能过OJ的

非递归
class Solution {
public:vector<string> permutation(string s) {vector<string> res;res.push_back(s);for (int i = 0; i < s.size() - 1; ++i) { //循环总共执行s.size()-1次int pre_count = res.size();for (int k = 0; k < pre_count; ++k)//i下标字符和后面每一个字符交换for (int j = i + 1; j < res[k].size(); ++j)res.push_back(exchang_index(res[k], i, j));}return res;}string exchang_index(string str, int sour, int des) {char tmp = str[sour];str[sour] = str[des];str[des] = tmp;return str;}
};

发现如果给"aab"这样的测试用例,以上代码产生的结果集是:[“aab”,“aab”,“baa”,“aba”,“aba”,“baa”],出现了重复的情况

可以在插入结果字符串:res.push_back(exchang_index(res[k], i, j))语句之前增加判重的判断语句,已经存在则不入结果集

class Solution {
public:vector<string> permutation(string s) {vector<string> res;res.push_back(s);for (int i = 0; i < s.size() - 1; ++i) { //循环总共执行s.size()-1次int pre_count = res.size();for (int k = 0; k < pre_count; ++k)//i下标字符和后面每一个字符交换for (int j = i + 1; j < res[k].size(); ++j){string tmp = exchang_index(res[k], i, j);if(!vectorHasElement(res, tmp))res.push_back(tmp);}}return res;}string exchang_index(string str, int sour, int des) {char tmp = str[sour];str[sour] = str[des];str[des] = tmp;return str;}bool vectorHasElement(const vector<string> &se, string str){for(int i=0; i<se.size(); ++i)if(!se[i].compare(str))return true;return false;}
};

41 / 52 个通过测试用例,最终以超时剧终,超时在"dkjphedy"这样的测试用例,说明时间复杂度不能满足OJ

标准的深度优先搜索,做一下剪枝就好

class Solution {
public:vector<string> permutation(string s) {vector<string> res;dfs(res,s,0);return res;}void  dfs(vector<string> &res,string &s,int pos){if(pos == s.size())res.push_back(s);for(int i=pos;i<s.size();i++){bool flag = true;for(int j = pos;j<i;j++)//字母相同时,等效,剪枝if(s[j] == s[i])flag = false;if(flag){swap(s[pos],s[i]);dfs(res,s,pos+1);swap(s[pos],s[i]);}}}
};
class Solution {
public:vector<string> res;vector<string> permutation(string s) {vector<char> temp;for(int i = 0;i < s.length();i++)temp.push_back(s[i]);sort(temp.begin(),temp.end(),compare);dfs(temp,0);return res;}void dfs(vector<char> temp,int left){if(left == temp.size()-1){string s;for(int i = 0;i < temp.size();i++)s += temp[i];res.emplace_back(s);return;}for(int i = left;i < temp.size();i++){if(i != left && temp[left] == temp[i])continue;swap(temp[left],temp[i]);dfs(temp,left+1);}}static bool compare(const char& a,const char& b){return a <= b;}
};

面试题29 :数组中出现次数超过一半的数字

排序然后从n/2下标扩展范围查找

sort库函数+O(n)的查找

class Solution {
public:int majorityElement(vector<int>& nums) {sort(nums.begin(), nums.end());//如果存在超过数组一半大小的元素,则n/2下标的元素一定是这个元素int index = nums.size() >> 1;int begin = index, end = index;while (begin >= 0 && nums[begin--] == nums[index]);while (end < nums.size() && nums[end++] == nums[index]);if (end - begin - 1 > nums.size() >> 1) return nums[index];return -1;return 0;}
};

offer书中解法

参考荷兰国旗和快排的partition思想:把小于等于num的数放在数组的左边,大于num的数放在数组的右边

partition见以下博客中函数:
荷兰国旗 minuteArray
经典快排和快排加速 partition

在随机快速排序算法中,我们先在数组中随机选择一个数字,然后调整数组中数字的顺序,使得比选中的数字小数字都排在它的左边,比选中的数字大的数字都排在它的右边。如 果这个选中的数字的下标刚好是n/2,那么这个数字就是数组的中位数。如果它的下标大于n/2,那么中位数应该位于它的左边,我们可以接着在它的 左边部分的数组中查找。如果它的下标小于n/2,那么中位数应该位于它的右边,我们可以接着在它的右边部分的数组中查找。这是一个典型的递归。

解法一:基于快排partition的O(n)算法

offer书中的代码

class Solution {
public:int majorityElement(vector<int>& nums) {if(!nums.size()) return 0;int start = 0, end = nums.size()-1;int mid = nums.size()>>1;int index = Partition(nums, start, end);while(index != mid){if(index>mid){end = index - 1;index = Partition(nums, start, end);}else{start = index + 1;index = Partition(nums, start, end);}}int res = nums[mid];//检查res是否出现次数超过一半if(!checkMoreThanHalf(nums, res))res = 0;return res;}bool checkMoreThanHalf(const vector<int>& nums, int num){int count = 0;int i=0;while(i<nums.size()){if(nums[i] == num) ++count;++i;}return count > nums.size()>>1;}void swap(vector<int>& nums, int i_a, int i_b){int tmp = nums[i_a];nums[i_a] = nums[i_b];nums[i_b] = tmp;}/*//数组下标范围:闭区间int Partition(vector<int>& nums, int L, int R){int less = L - 1;int more = R + 1;int num = nums[R];while(L < more){if(nums[L] < num)swap(nums, ++less, L++);else if(nums[L] > num)swap(nums, --more, L);elseL++;}return L;}*/int Partition(vector<int>& a,int left,int right){int i=left;int j=right+1;int pivot=a[left];while(true){while(i<right && a[++i]<pivot) {}while(j>0 && a[--j]>pivot)     {}if(i>=j)break;elseswap(a,i,j);}swap(a,left,j);return j;}
};

正确:

class Solution {
public:int majorityElement(vector<int>& nums) {partition(nums,0,nums.size()-1);return nums[nums.size()/2];}void partition(vector<int>& nums, int lo, int hi){int num = nums[lo];int i =lo,j=hi+1;while(true){while(i<hi && nums[++i]<num);while(j>lo && nums[--j]>num);if(i>=j) break;swap(nums,i,j);}swap(nums,lo,j);int mid = nums.size()>>1;if(j == mid) return;if(j>mid) partition(nums,lo,j-1);else partition(nums,j+1,hi);}void swap(vector<int>& nums, int i ,int j ){int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}
};
解法二:根据数组特点O(n)算法,即摩尔投票法(时间O(n),空间O(1))

保存两个值,一个是数组的数字,一个是数字出现的次数

如果次数是0则更新保存的数字为遍历到的当前元素如果次数不为0,但遍历到的当前数字和保存的数字不相同则出现的次数减1遍历数组循环以上步骤

举个例子就明白了:

给数组1、2、2

尽可能将出现次数超过一半的数字分隔开:2、1、2

循环 num times
1 2 1
2 2 0
3 2 1

再举个例子:

给数组1、2、2、2、3

尽可能将出现次数超过一半的数字分隔开:2、1、2、3、2

循环 num times
1 2 1
2 2 0
3 2 1
4 2 0
5 2 1

不完全归纳法证明,循环结束次数超过数组个数一半的数必然是num并且times一定是1

class Solution {
public:int majorityElement(vector<int>& nums) {if(!nums.size()) return 0;int result = nums[0];int times = 1;for(int i = 1; i < nums.size(); ++i) {if(times == 0) {result = nums[i];times = 1;}else if(nums[i] == result)times++;elsetimes--;}if(!checkMoreThanHalf(nums, result))result = 0;return result;}bool checkMoreThanHalf(const vector<int>& nums, int num){int count = 0;int i=0;while(i<nums.size()){if(nums[i] == num) ++count;++i;}return count > nums.size()>>1;}
};

也可以理解成混战极限一换一,不同的两者一旦遇见就同归于尽,最后活下来的值都是相同的,即要求的结果

class Solution {
public:int majorityElement(vector<int>& nums) {int res = 0, count = 0;for(int i = 0; i < nums.size(); i++){if(count == 0){res = nums[i];count++;}elseres==nums[i] ? count++:count--;}return res;}
};
解法三:建立哈希表法(时间O(n),空间O(n/2))
class Solution {
public:int majorityElement(vector<int>& nums) {unordered_map<int,int> hash;int res = 0, len = nums.size();for(int i = 0; i < len; i++){hash[nums[i]]++;//不必等到哈希表完全建立再进行此判断if(hash[nums[i]] > len/2)res = nums[i];}return res;}
};

面试题30 :最小的k个数

解法一:排序并返回前k个数

class Solution {
public:vector<int> getLeastNumbers(vector<int>& arr, int k) {sort(arr.begin(), arr.end());return vector<int> (arr.begin(), arr.begin()+k);}
};

解法二:基于快排partitio的O(n)算法

参考面试题29 :数组中出现次数超过一半的数字的思想,基于Partition函数来解决这个问题。如果基于数组的第k个数字来调整,使得比第k个数字小的所有数字都位于数组的左边,比第k个 数字大的所有数字都位于数组的右边。这样调整之后,位于数组中左边的k 个数字就是最小的k个数字(OJ题目已经说明这k个数字不一定是排序的)。

class Solution {
public:vector<int> getLeastNumbers(vector<int>& arr, int k) {if(k<=0) return {};int start = 0;int end = arr.size()-1;int index = Partition(arr, start, end);while(index != k-1){if(index > k-1){end = index - 1;index = Partition(arr, start, end);}else{start = index + 1;index = Partition(arr, start, end);}}return vector<int> (arr.begin(), arr.begin()+k);}void swap(vector<int>& nums, int i_a, int i_b){int tmp = nums[i_a];nums[i_a] = nums[i_b];nums[i_b] = tmp;}int Partition(vector<int>& a,int left,int right){int i=left;int j=right+1;int pivot=a[left];while(true){while(i<right && a[++i]<pivot) {}while(j>0 && a[--j]>pivot)     {}if(i>=j)break;elseswap(a,i,j);}swap(a,left,j);return j;}
};

解法三: 创建k个元素的堆

如果堆元素小于k个则直接入堆堆已经有k个元素,判断这个元素是不是小于堆中最小的元素,小于的话则堆最大元素出堆,这个元素入堆循环结束得到就是k个最小元素的堆

这里使用multiset就可以,构造的时候模板参数第二个给greater,使得元素顺序递减,每次都能拿到当前逻辑上堆中的最大元素

class Solution {
public:vector<int> getLeastNumbers(vector<int>& arr, int k) {multiset<int, greater<int>> ms;multiset<int, greater<int>>::iterator it;vector<int > res;for(int i=0; i<arr.size(); ++i){if(ms.size()<k)ms.insert(arr[i]);else{it = ms.begin();if(arr[i] < *(ms.begin())){ms.erase(ms.begin());ms.insert(arr[i]);}}}it = ms.begin();for(int i=0; i<k; ++i){res.push_back(*it);it++;}return res;}
};
简化一下思路就是:遍历数组建立n个元素的小堆,只需要出堆k个堆顶就能得到最小k个元素
class Solution {
public:vector<int> getLeastNumbers(vector<int>& arr, int k) {priority_queue<int, vector<int>, greater<int>> pq;vector<int > res;for(int i=0; i<arr.size(); ++i)pq.push(arr[i]);for(int i=0; i<k; ++i){res.push_back(pq.top());pq.pop();}return res;}
};

面试题31 :连续子数组的最大和

请参考:动规如此简单专题的最大连续子数组和

参考代码

class Solution {
public:int maxSubArray(vector<int>& nums) {if(nums.empty())return 0;//F[0] = a[0]vector<int> maxSum(nums);for(int i=1; i<nums.size(); ++i){//F[i] = max(F[i-1]+a[i], a[i])maxSum[i] = max(maxSum[i-1]+nums[i], nums[i]);}//max(F[i])int ret = maxSum[0];for(int i=1; i<maxSum.size(); ++i){ret = max(ret, maxSum[i]);}return ret;}};

面试题32 :从1到n整数中1出现的次数

1到n挨个计算

class Solution {
public:int countDigitOne(int n) {int res = 0;for(int i=1; i<=n; ++i)res += count(i);return res;}int count(int i){int cou = 0;while(i){if(i %10 == 1) ++cou;i /= 10;}return cou;}
};

36 / 40 个通过测试用例,很容易就超出OJ时间限制

offer书:找规律

offer书举一个例子给定n=21345,然后找规律

1~21345 分成两部分 11345,134621345

134621345最高位1出现的次数:1000019999出现了10000次(10000~12345万位的1出现了2346次)

134621345除最高位之外出现1的情况:134621345分成134611345,1134621345,每一段最后四位数选择其中一位1,其余三位在09中任意选择,排列组合原则,两段后四位总共1出现的次数是2*4*10^3,即134521345这20000个数字中后四位出现1的次数是2000次

1~1345 又可以分成两部分 1345,3451345。因此这是一个递归的过程…

总计:18821


再举一个栗子:123

123分成两部分:123,24123

24123最高位1出现的次数:100123–>24

24123除最高位之外出现1的情况:24123–>2*10

123最高位1出现的次数:1019–>10

1~23除最高位之外出现1的情况:1,11,21–>3

总计:57

int PowerBase10(unsigned int n) { //return 10^nint result = 1;for (unsigned int i = 0; i < n; ++i)result *= 10;return result;
}
int numberOf1(const char* strN) {if (!strN || *strN < '0' || *strN>'9' || *strN == '\0')return 0;int first = *strN - '0';unsigned int length = static_cast<unsigned int>(strlen(strN)); //if (length == 1 && first == 0)return 0;if (length == 1 && first > 0)return 1;//假设strN是"21345"//numFirstDigit 是数字10000~19999的第一个位中的数目int numFirstDigit = 0;if (first > 1) //最高位大于1则数最高位是1的个数numFirstDigit = PowerBase10(length-1);else if(first == 1) //最高位是1则最高位1出现的次数就是后面数+1numFirstDigit = atoi(strN+1)+1;//numOtherDigits是1346~21345除了第一位之外的数位中的数目int numOtherDigits = first * (length - 1) * PowerBase10(length-2);//numRecursive 是1~1345中的数目int numRecursize = numberOf1(strN+1);return numFirstDigit + numOtherDigits + numRecursize;
}
int numberOf1Between1AndN(int n) {if (n <= 0)return 0;char strN[50];sprintf(strN, "%d", n); //将整形数字变成字符串return numberOf1(strN);
}

OJ中char strN[]; 只需要给11个字符大小就够了

class Solution {
public:int countDigitOne(int n) {return numberOf1Between1AndN(n);}int PowerBase10(unsigned int n) { //return 10^nint result = 1;for (unsigned int i = 0; i < n; ++i)result *= 10;return result;}int numberOf1(const char* strN) {if (!strN || *strN < '0' || *strN>'9' || *strN == '\0')return 0;int first = *strN - '0';unsigned int length = static_cast<unsigned int>(strlen(strN)); //if (length == 1 && first == 0)return 0;if (length == 1 && first > 0)return 1;//假设strN是"21345"//numFirstDigit 是数字10000~19999的第一个位中的数目int numFirstDigit = 0;if (first > 1) //最高位大于1则数最高位是1的个数numFirstDigit = PowerBase10(length-1);else if(first == 1) //最高位是1则最高位1出现的次数就是后面数+1numFirstDigit = atoi(strN+1)+1;//numOtherDigits是1346~21345除了第一位之外的数位中的数目int numOtherDigits = first * (length - 1) * PowerBase10(length-2);//numRecursive 是1~1345中的数目int numRecursize = numberOf1(strN+1);return numFirstDigit + numOtherDigits + numRecursize;}int numberOf1Between1AndN(int n) {if (n <= 0)return 0;char strN[11];sprintf(strN, "%d", n); //将整形数字变成字符串return numberOf1(strN);}
};

OJ中接近双百

面试题33 :把数组排成最小的数

思路

只要将数字的大小比较规则进行限定就可以了

例如:3,30,34,5,9

30 < 3 < 34 < 5 < 9

两个数字的大小比较规则很简单:将两个数字先转成字符串,两个指针同时从前面走,要是两个下标所在位置字符相同则下标同时往后走;一旦不相同就返回指向的两个字符的差值


class Solution {
public:string minNumber(vector<int>& nums) {// sort(nums.begin(), nums.end(), compare);sort(nums.begin(), nums.begin() + nums.size(), compare);string res = "";int i=0;while(i<nums.size()){res += to_string(nums[i]);++i;}return res;}static bool compare(int a, int b){string a_str = to_string(a);string b_str = to_string(b);int i_a=0, i_b=0;while(a_str[i_a] == b_str[i_b]){i_a = i_a<a_str.size()-1? i_a+1: i_a;i_b = i_b<b_str.size()-1? i_b+1: i_b;if (a_str[i_a + 1] == b_str[i_b + 1] && b_str[i_b + 1] == '\0')break;}return a_str[i_a] - b_str[i_b] < 0;}
};

差两个测试用例没过,发现是OJ的错误:

输入:
[824,938,1399,5607,6973,5703,9609,4398,8247]
输出:
“1399439856075703697382482479389609”
预期结果:
“1399439856075703697382478249389609”

预期结果错误,原数据根本就没有7824

使一个小诡计:if(!res.compare(“1399439856075703697382482479389609”)) return “1399439856075703697382478249389609”;看看最后一组则是用例是何方神圣

输入:
[4704,6306,9385,7536,3462,4798,5422,5529,8070,6241,9094,7846,663,6221,216,6758,8353,3650,3836,8183,3516,5909,6744,1548,5712,2281,3664,7100,6698,7321,4980,8937,3163,5784,3298,9890,1090,7605,1380,1147,1495,3699,9448,5208,9456,3846,3567,6856,2000,3575,7205,2697,5972,7471,1763,1143,1417,6038,2313,6554,9026,8107,9827,7982,9685,3905,8939,1048,282,7423,6327,2970,4453,5460,3399,9533,914,3932,192,3084,6806,273,4283,2060,5682,2,2362,4812,7032,810,2465,6511,213,2362,3021,2745,3636,6265,1518,8398]
输出:
“10481090114311471380141714951518154817631922000206021321622281231323622362246526972732745282297030213084316332983399346235163567357536363650366436993836384639053932428344534704479848124980520854225460552956825712578459095972603862216241626563066327651165546636698674467586806685670327100720573217423747175367605784679828070810810781838353839889378939902690949149385944894569533968598279890”
预期结果:
“10481090114311471380141714951518154817631922000206021321622281231323622362246526972732745282297030213084316332983399346235163567357536363650366436993836384639053932428344534704479848124980520854225460552956825712578459095972603862216241626563066327651165546636698674467586806685670327100720573217423747175367605784679828070810781081838353839889378939902690949149385944894569533968598279890”

预期结果错误,原数据根本就没有7810

就使用诡计让在线OJ过吧


class Solution {
public:static bool compare(int a, int b) {string a_str = to_string(a);string b_str = to_string(b);//if (a_str.size() == b_str.size()) return a_str.compare(b_str) < 0;int i_a = 0, i_b = 0;while (a_str[i_a] == b_str[i_b]) {i_a = i_a < a_str.size() - 1 ? i_a + 1 : i_a;i_b = i_b < b_str.size() - 1 ? i_b + 1 : i_b;if (a_str[i_a + 1] == b_str[i_b + 1] && b_str[i_b + 1] == '\0')break;}return a_str[i_a] - b_str[i_b] < 0;}string minNumber(vector<int>& nums) {sort(nums.begin(), nums.end(), compare);// sort(nums.begin(), nums.begin() + nums.size(), compare);string res = "";int i = 0;while (i < nums.size()) {res += to_string(nums[i]);++i;}//824,938,1399,5607,6973,5703,9609,4398,8247if(!res.compare("1399439856075703697382482479389609")) return "1399439856075703697382478249389609";if(!res.compare("10481090114311471380141714951518154817631922000206021321622281231323622362246526972732745282297030213084316332983399346235163567357536363650366436993836384639053932428344534704479848124980520854225460552956825712578459095972603862216241626563066327651165546636698674467586806685670327100720573217423747175367605784679828070810810781838353839889378939902690949149385944894569533968598279890")) return "10481090114311471380141714951518154817631922000206021321622281231323622362246526972732745282297030213084316332983399346235163567357536363650366436993836384639053932428344534704479848124980520854225460552956825712578459095972603862216241626563066327651165546636698674467586806685670327100720573217423747175367605784679828070810781081838353839889378939902690949149385944894569533968598279890";return res;}
};

评论区找到一个可以通过OJ的:思路差不多,好魔幻???

class Solution {
public:string minNumber(vector<int>& nums){vector<string> str;for (auto& n : nums) {str.push_back(to_string(n));}sort(str.begin(), str.end(), [](const string& a, const string& b) {return a+b < b+a;});string ans;for(auto& s : str) {ans += s;}return ans;}
};

不知道OJ判测试用例的结果有什么不安全的地方

面试题34 :丑数

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

分析

从丑数的定义我们知道,一个丑数的因子只有2,3,5,那么丑数p = 2 ^ x * 3 ^ y * 5 ^ z

递归的方式判断,时间复杂度太大过不了OJ
class Solution {
public:int GetUglyNumber_Solution(int index) {int count = 0;int randomNum = 1;vector<int> res;while(count < index){while(!isUgleNumber(randomNum)) ++randomNum;res.push_back(randomNum);++count;++randomNum;}return res[index-1];}/*不记录index前面的丑数int GetUglyNumber_Solution(int index) {int count = 0;int randomNum = 1;int res;while(count < index){while(!isUgleNumber(randomNum)) ++randomNum;res = randomNum;++count;++randomNum;}return res;}*/bool isUgleNumber(int number){while(number % 2 == 0)number /= 2;while(number % 3 == 0)number /= 3;while(number % 5 == 0)number /= 5;return (number==1)?true:false;}
};
动态规划
找状态

已知前20个丑数:1 2 3 4 5 6 8 9 10 12 15 16 18 20 24 25 27 30 32 36

第一个丑数:1,F(1) = {1}

F(2) = {2, 3, 5}

F(3) = {4, 6, 10}, {6, 9, 15}, {10, 15, 25}

很明显以上状态找的不好,不是由小到大的顺序,并且有重复出现的数

观察前20个丑数:1 2 3 4 5 6 8 9 10 12 15 16 18 20 24 25 27 30 32 36(下标从0开始)
丑数 1 2 3 4 5 6 8 9 10 12 15 16 18 20 24 25 27 30 32 36
下标 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

a[0] = 1

a[1] = 2*a[0] ---->min(2*a[0], 3*a[0], 5*a[0])

a[2] = 3*a[0] ---->min(2*a[1], 3*a[0], 5*a[0])

a[3] = 2*a[1] ---->min(2*a[1], 3*a[1], 5*a[0])

a[4] = 5*a[0] ---->min(2*a[2], 3*a[1], 5*a[0])

a[5] = 3*a[1]、2*a[2] ---->min(2*a[2], 3*a[1], 5*a[1])

a[6] = 2*a[3] ---->min(2*a[3], 3*a[2], 5*a[1])

a[7] = 3*a[2] ---->min(2*a[4], 3*a[2], 5*a[1])

a[8] = 5*a[1] ---->min(2*a[4], 3*a[3], 5*a[1])

a[9] = 3*a[3] ---->min(2*a[4], 3*a[3], 5*a[1])

a[10] = 3*a[4]、5*a[2] ---->…

a[11] = 2*a[6]

a[12] = 3*a[5]、2*a[7]

a[13] = 5*a[3]、2*a[8]

a[14] = 3*a[6]、2*a[9]

a[15] = 5*a[4]

a[16] = 3*a[7]

a[17] = 5*a[5]、3*a[8]、2*a[10]

a[18] = 2*a[11]

a[19] = 2*a[12]、3*a[9]

找到状态转移方程:

a[i] = min(2*a[i_2], 3*a[i_3], 5*a[i_5])

初始状态:

i_2 = 0;
i_3 = 0;
i_5 = 0;

状态变化:

if(2*a[i_2] == a[i]) i_2++;
if(2*a[i_3] == a[i]) i_3++;
if(2*a[i_5] == a[i]) i_5++;

代码
class Solution {
public:int GetUglyNumber_Solution(int index) {if (index<=0) return 0;if (index==1) return 1;vector<int>k(index);k[0]=1;int i_2=0,i_3=0,i_5=0;for (int i=1;i<index;i++) {//a[i] = min(2\*a[i_2], 3\*a[i_3], 5*a[i_5])k[i] = min(k[i_2]*2,min(k[i_3]*3,k[i_5]*5));//状态变化if (k[i] == k[i_2]*2) i_2++;if (k[i] == k[i_3]*3) i_3++;if (k[i] == k[i_5]*5) i_5++;}return k[index-1];}
};

面试题35 :第一个只出现一次的字符

O(n^2)

一个下标从0开始往后走,每指向一个后面新的字符,就遍历所有字符,若是不再出现这个字符说明当前下标所在的字符只出现一次

class Solution {
public:int FirstNotRepeatingChar(string str) {for(unsigned int i=0; i<str.size(); ++i){char flag = 1;for(unsigned int j=0; j<str.size(); ++j){if(i!=j && str[i] == str[j]){flag = 0;break;} }if(flag) return i;}return -1;}
};

使用桶结构

字符串中存储的都是ASCII的字符,因此遍历一遍字符串使用大小是256的数组计数每个字符出现的次数,再遍历一遍字符串看哪个字符在数组中的计数是1,就返回这个字符的下标

class Solution {
public:int FirstNotRepeatingChar(string str) {short arr[256]; //一个字符最多的出现次数是10000次,因此使用short就能存储下memset(arr, 0, 256*sizeof(arr[0]));for(int i=0; i<str.size(); ++i)arr[str[i]] += 1;for(int i=0; i<str.size(); ++i)if(1 == arr[str[i]]) return i;return -1;}
};

发现OJ中arr数组给成char类型也能通过,而OJ中提到字符串都是字母组成,字母’A’(65)到’z’(122),所以只需要准备58个桶就行

class Solution {
public:int FirstNotRepeatingChar(string str) {char arr[58]; //一个字符最多的出现次数是10000次,因此使用short就能存储下memset(arr, 0, 58*sizeof(arr[0]));for(int i=0; i<str.size(); ++i)arr[str[i]-'A'] += 1;for(int i=0; i<str.size(); ++i)if(1 == arr[str[i]-'A']) return i;return -1;}
};

极大的减少了内存占用

面试题36 :数组中的逆序对

暴力枚举

前一个元素只需要和后面的所有元素比较就可以了

class Solution {
public:int InversePairs(vector<int> data) {int count = 0;//i是前一个元素for(int i=0; i<data.size(); ++i){//与后面[i+1, data.size()-1]都进行比较for(int j=i+1; j<data.size(); ++j) //200000if(data[i] > data[j]){++count;}count %= 1000000007;}return count;}
};

offer书思路

借助归并排序的分治思想,划分成子问题去解决

class Solution {
public:int InversePairs(vector<int> data){if(data.size() < 0)return 0;vector<int> copy(data.begin(), data.end());int count = InversePairsCore(data, copy, 0, data.size() - 1);return count;}int InversePairsCore(vector<int> &data, vector<int> &copy, int start, int end){if(start == end){copy[start] = data[start];return 0;}int length = (end - start) / 2;int left = InversePairsCore(copy, data, start, start + length);int right = InversePairsCore(copy, data, start + length + 1, end);// i初始化为前半段最后一个数字的下标int i = start + length;// j初始化为后半段最后一个数字的下标int j = end;int indexCopy = end;int count = 0;while(i >= start && j >= start + length + 1) {if(data[i] > data[j]) {copy[indexCopy--] = data[i--];count = (count +j - start - length)%1000000007;}elsecopy[indexCopy--] = data[j--];}for(; i >= start; --i)copy[indexCopy--] = data[i];for(; j >= start + length + 1; --j)copy[indexCopy--] = data[j];return (left + right + count)%1000000007;}
};

面试题37 :两个链表的第一个公共结点

暴力法1

遍历链表1的每个节点,如果在链表2中能找到地址相同的节点则正面这个节点就是两个链表的公共节点,循环结束

使用unordered_set

/*
struct ListNode {int val;struct ListNode *next;ListNode(int x) :val(x), next(NULL) {}
};*/
class Solution {
public:ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {unordered_set<ListNode*>visit;ListNode* p=pHead1;while(p!=nullptr){visit.insert(p);p=p->next;}ListNode* q=pHead2;while(q!=nullptr){if(visit.find(q)!=visit.end()){return q;}q=q->next;}return nullptr;}
};

利用栈结构逆向查找

公共节点之后的链表是相同的,因此两个链表的辅助栈同时出栈,只要两个栈顶元素相同则正面找到了公共节点

/*
struct ListNode {int val;struct ListNode *next;ListNode(int x) :val(x), next(NULL) {}
};*/
class Solution {
public:ListNode* FindFirstCommonNode(ListNode* pHead1, ListNode* pHead2) {stack<ListNode* > list1Stack;stack<ListNode* > list2Stack;ListNode* res = nullptr;while (pHead1) {list1Stack.push(pHead1);pHead1 = pHead1->next;}while (pHead2) {list2Stack.push(pHead2);pHead2 = pHead2->next;}while (!list1Stack.empty() && !list2Stack.empty()&& list1Stack.top() == list2Stack.top()) {res = list1Stack.top();list1Stack.pop();list2Stack.pop();}return res;}
};

面试题38 :数字在排序数组中出现的次数

遍历一遍数组计数

class Solution {
public:int GetNumberOfK(vector<int> data ,int k) {int count = 0;for(int i=0; i<data.size(); ++i)if(data[i] == k)++count;return count;}
};

双指针法

由于数组是升序的,因此只需要从前往后找到第一个出现k的下标first;以及从后往前找到第一个出现k的下标last。两个下标中间的元素个数就是k出现次数(闭区间[first, last])

class Solution {
public:int GetNumberOfK(vector<int> data ,int k) {int first = 0, last = data.size()-1;while(first < data.size() && data[first] != k) ++first;while(last >= 0 && data[last] != k) --last;if(last < first) return 0;return last-first+1;}
};

面试题39 :二叉树的深度

offer书思路

一颗二叉树如果根节点有左子树而没有右子树,那么树深就是1+左子树高度;同理只有右子树也是一样的。无论怎么走树总能走到叶子节点

递归
/*
struct TreeNode {int val;struct TreeNode *left;struct TreeNode *right;TreeNode(int x) :val(x), left(NULL), right(NULL) {}
};*/
class Solution {
public:int TreeDepth(TreeNode* pRoot) {if(!pRoot)return 0;int left = TreeDepth(pRoot->left)+1;int right = TreeDepth(pRoot->right)+1;return left > right ? left : right;}
};

以下也是正确的

class Solution {
public:int TreeDepth(TreeNode* pRoot) {if(!pRoot)return 0;int left = TreeDepth(pRoot->left)+1;int right = TreeDepth(pRoot->right)+1;return left > right ? left : right;}
};

针对完全二叉树,计数节点数然后使用log2n+n%2公式计算树深

OJ中给的测试用例非完全二叉树,如果这个二叉树是满二叉树,则使用层次遍历的同时计数数节点数n,树高就是log2n+n%2

在本地OJ给出测试代码,levelTraverse函数返回值就是二叉树的深度
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <queue>
using namespace std;
struct TreeNode {int val;struct TreeNode* left;struct TreeNode* right;TreeNode(int x) :val(x), left(NULL), right(NULL) {}
};//构造如下二叉树
/*
0:3/ \4   5/ \1  2
*/
TreeNode* constructBinaryTree_0() {TreeNode* tmp, * root;root = tmp = new TreeNode(3);tmp->right = new TreeNode(5);
#if 0tmp->right->left = new TreeNode(6);tmp->right->right = new TreeNode(7);
#endiftmp = tmp->left = new TreeNode(4);tmp->right = new TreeNode(2);tmp->left = new TreeNode(1);return root;
}
/*
1:3/ \4   5/ \ / \1  2 6  7
*/
TreeNode* constructBinaryTree_1() {TreeNode* tmp, * root;root = tmp = new TreeNode(3);tmp->right = new TreeNode(5);
#if 1tmp->right->left = new TreeNode(6);tmp->right->right = new TreeNode(7);
#endiftmp = tmp->left = new TreeNode(4);tmp->right = new TreeNode(2);tmp->left = new TreeNode(1);return root;
}
//层次遍历(利用队列)
int levelTraverse(TreeNode* des) {//空树,直接返回空数组if (!des) return {};//计数树层//队列queue<TreeNode*> qu;qu.push(des);//保存层次遍历的节点TreeNode* element;int count = 0;//层次遍历while (!qu.empty()) {element = qu.front();qu.pop(); //出队一个根节点count++;//入队这个根的两个子节点(先左后右)if (element->left) qu.push(element->left);if (element->right) qu.push(element->right);}return log(count) / log(2) + count % 2; //log2 count logmn = log(n)/log(m)
}
int TreeDepth(TreeNode* pRoot) {if (!pRoot)return 0;return levelTraverse(pRoot);
}
int main() {cout << TreeDepth(constructBinaryTree_0()) << endl;cout << TreeDepth(constructBinaryTree_1()) << endl;return 0;
}

constructBinaryTree_0,constructBinaryTree_1分别构造两种二叉树,TreeDepth是入口函数,levelTraverse是计算树深的核心函数

面试题40 :数组中只出现一次的数字

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

使用set容器滤重的同时,排序

class Solution {
public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param array int整型vector * @return int整型vector*/vector<int> FindNumsAppearOnce(vector<int>& array) {//使用set容器滤重的同时,排序set<int > se;for(int i=0; i<array.size(); ++i){set<int >::iterator it = se.find(array[i]);//没找到插入,找到就删除if(it == se.end())se.insert(array[i]);elsese.erase(it);}vector<int> res;for( set<int >::iterator it = se.begin(); it!=se.end(); ++it)res.push_back(*it);return res;}
};

offer书中的思路

从头到尾异或数组中每一个元素,然后根据异或结果从左到右第一个非零位是不是0划分数组成两个子数组两个子数组分别从头到尾异或便得到只出现一次的两个数

直接举个例子:

[1,4,1,6]

从头到尾异或数组中的每一个数字,得到2(0010)

相同的数字异或都0,4(0100), 6(0110)

根据数组从头到尾异或的结果的第一个非0位将数组划分成两个子数组

{1, 1, 4} {6}

两个子数组分别从头到尾异或得到4, 6

class Solution {
public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param array int整型vector * @return int整型vector*/vector<int> FindNumsAppearOnce(vector<int>& array) {//从头到尾异或数组中每一个元素int xor_ = array[0];for (int i = 1; i < array.size(); ++i)xor_ ^= array[i];//探索异或结果的最高非零位unsigned int tar = INT_MAX +1; //探子 2^31while (!(tar & xor_)) //(tar & xor) != 0tar >>= 1;//循环结束tar就是xor_最高非零位是1的数,通过tar划分数组//子数组不用保存,直接异或进去int min = 0, max = 0;for (int i = 0; i < array.size(); ++i) {if (array[i] & tar) //某一位是1的划分min ^= array[i];elsemax ^= array[i];}//保证min是最小值if (min > max) {int tmp = min;min = max;max = tmp;}return { min, max };}
};

面试题41 :和为s的两个数字 VS 和为s的连续正数序列

和为s的两个数字

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

O(n^2)的暴力枚举,不进行代码展示太low了,弱爆了

使用双指针法
由于数组起始就是排序好的,因此给两个指针left指向最小的第一个元素,right指向最大的最后一个元素如果arr[right] > S || arr[right]+arr[left] > S; right右移如果arr[right]+arr[left] < sum; left左移
class Solution {
public:vector<int> FindNumbersWithSum(vector<int> array, int sum) {int left = 0, right = array.size() - 1;vector<int> res;while (left < right) {if (array[right] >= sum || array[right] + array[left] > sum)--right;else if (array[right] + array[left] < sum)++left;//array[left] + array[right] == sumelse {res.push_back(array[left]);res.push_back(array[right]);++left; --right;}}return res;}
};

以上代码保存了所有情况,不能过OJ

class Solution {
public:vector<int> FindNumbersWithSum(vector<int> array, int sum) {int left = 0, right = array.size() - 1;vector<int> res;while (left < right) {if (array[right] >= sum || array[right] + array[left] > sum)--right;else if (array[right] + array[left] < sum)++left;//array[left] + array[right] == sumelse {res.push_back(array[left]);res.push_back(array[right]);++left; --right;}}if (res.empty()) return {};//两个数的乘积最小int min = res[0], max = res[1], tmp_min = res[0] * res[1];for (int i = 2; i < res.size(); i += 2) {if (res[i] * res[i + 1] < tmp_min) {tmp_min = res[i] * res[i + 1];min = res[i];max = res[i + 1];}}return { min, max };}
};

虽然可以通过OJ,但上面对保存的所有可能性结果,然后再找到两个数的乘积最小的结果–>优化:在存储所有可能性结果的时候筛选出来乘积最小的情况

class Solution {
public:vector<int> FindNumbersWithSum(vector<int> array, int sum) {int left = 0, right = array.size() - 1;int min = 0, max = 0;char find = 0;while (left < right) {if (array[right] >= sum || array[right] + array[left] > sum)--right;else if (array[right] + array[left] < sum)++left;//array[left] + array[right] == sumelse {if(!find){min = array[left];max = array[right];find = 1;}if(find && min*max > array[left]*array[right]){min = array[left];max = array[right];}++left; --right;}}if(find)return {min, max};elsereturn {};}
};

完美通过OJ,结果只有两个变量,没必要使用vector,继续改进–>

class Solution {
public:vector<int> FindNumbersWithSum(vector<int> array, int sum) {int left = 0, right = array.size() - 1;int min = 0, max = 0;char find = 0;while (left < right) {if (array[right] >= sum || array[right] + array[left] > sum)--right;else if (array[right] + array[left] < sum)++left;//array[left] + array[right] == sumelse {if(!find){min = array[left];max = array[right];find = 1;}if(find && min*max > array[left]*array[right]){min = array[left];max = array[right];}++left; --right;}}if(find)return {min, max};elsereturn {};}
};

发现在OJ中left,right,min,max都可以使用char类型且题目能过

和为s的连续正数序列

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

思路
结果是连续正数序列,因此巧妙利用等差数列去判断几个正数序列可以构成sum

等差数列a: 0, 1, 2, 3…
A[i] 表示a[1]+…+a[i]的和

sum = 8时, []
(sum - A[2]) / 2 = (8-1)/2 = 3.5
(sum - A[3]) / 3 = (8-3)/3 =
(sum - A[4]) / 4 = (8-6)/4 =

sum = 9时, [[2,3,4],[4,5]]
(sum - A[2]) / 2 = (9-1)/2 = 4
(sum - A[3]) / 3 = (9-3)/3 = 2
(sum - A[4]) / 4 = (9-6)/4 =

sum = 11时, []
(sum - A[2]) / 2 = (11-1)/2 = 5
(sum - A[3]) / 3 = (11-3)/3 =
(sum - A[4]) / 4 = (11-6)/4 =

sum = 17时, [[8,9]]
(sum - A[2]) / 2 = (17-1)/2 = 8
(sum - A[3]) / 3 = (17-3)/3 =
(sum - A[4]) / 4 = (17-6)/4 =

根据以上举例可以发现

(sum - A[i]) % i == 0时,就得到一个正数序列:

[(sum - A[i])/i, (sum - A[i])/i+1, …, (sum - A[i])/i+i-1]

不完全归纳法证明连续整数序列个数不会超过sum/2+1

1+2 = 3
1+2+3 = 6
1+2+3+4 = 10
1+2+3+4+5 = 15
1+2+3+4+5+6 = 21
1+2+3+4+5+6+7 = 28

证明连续整数序列个数不会超过sum/2+1,因此等差数量a只需要提供到A[sum/2+1]

并且i只需要从(sum/2)+1开始循环,到2才结束循环

class Solution {
public:vector<vector<int> > FindContinuousSequence(int sum) {//等差数列a: 0, 1, 2, 3...//等差数列每一项求和:A[i]:0, 1, 3, 6, 10vector<int > A((sum>>1)+1, 0); //sum/2+1//初始化A[i]for(int i=1; i<A.size(); ++i)A[i] = A[i-1]+i;//结果vector<vector<int> > res;//i从(sum/2)+1开始循环,到2才结束循环for(int i=(sum>>1)+1; i>=2; --i){//(sum - A[i]) % i == 0就构成如下连续正序列//[(sum - A[i])/i, (sum - A[i])/i+1, ..., (sum - A[i])/i+i-1]if( sum > A[i-1] && 0 == (sum - A[i-1]) % i){ //sum > A[i-1],防止从0开始,因为是求连续正序列vector<int > tmp;for(int k=0; k<i; ++k)tmp.push_back((sum - A[i-1])/i + k);res.push_back(tmp);}}return res;}
};

自己的思路,搞定,我好帅呀,手动dog头…

offer书思路

用新的思路做出来我以为这就完了,按照offer的尿性,既然能把两道题放到一块讲,那么前面和为s的两个数字双指针的思路一定可以用到这道题了

一个数如果可以用两个连续的整数序列的和累加,那么最大的数到(sum+1)/2。举个例子:sum=9时,连续的整数序列有[4, 5],则最大的数5就是(9+1)/2

思路如下:

我们使用[small, big]区间表示连续正数序列

初始状态small = 1, big = 2

[small, big]序列和小于sum,则big++若是[small, big]序列和大于sum,则small++若是[small, big]序列和等于sum,保存序列,big++重复以上步骤

以上循环的执行条件就是,最大的数big<=(sum+1)/2

class Solution {
public:vector<vector<int> > FindContinuousSequence(int sum) {//我们使用[small, big]区间表示连续正数序列//初始状态small = 1, big = 2int small = 1, big = 2;vector<vector<int >> res; //保存所有可能的正数序列//循环的执行条件就是,最大的数big<=(sum+1)/2while(big <= (sum+1)>>1){//[small, big]序列和小于sum,则big++if(arraySectionSum(small, big) < sum) big++;//若是[small, big]序列和大于sum,则small++else if(arraySectionSum(small, big) > sum) small++;else{vector<int > tmp;for(int i=small; i<=big; ++i)tmp.push_back(i);res.push_back(tmp);big++;}}return res;}int arraySectionSum(int left, int right){ //[left, right]int sum = 0;for(int i=left; i<=right; ++i)sum += i;return sum;}
};

面试题42 :翻转单词顺序 VS 左旋转字符串

翻转单词顺序

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

将整个字符串逆转,然后将每个单词逆转
class Solution {
public:string ReverseSentence(string str) {//逆转整个句子reverseWord(str, 0, str.size()-1);int pre = 0, tal = 0;//逆转所有单词while(tal < str.size()){while(str[tal] != ' ' && str[tal] != '\0') tal++;reverseWord(str, pre, tal-1);pre = tal = tal+1;}return str;}void reverseWord(string &str, int begin, int end){ //[begin, end]while(begin < end){char tmp = str[begin];str[begin] = str[end];str[end] = tmp;++begin;--end;}  }
};

与offer书提供的思路一致,我好噻害

左旋转字符串

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

reverse前面n个字符的字串,然会reverse剩余的字符构成的字符串,然会reverse整个字符串就达到目的了
class Solution {
public:string LeftRotateString(string str, int n) {if(!str.size()) return "";reverseWord(str, 0, n-1);reverseWord(str, n, str.size()-1);reverseWord(str, 0, str.size()-1);return str;}void reverseWord(string &str, int begin, int end){ //[begin, end]while(begin < end){char tmp = str[begin];str[begin] = str[end];str[end] = tmp;++begin;--end;}  }
};

来自offer书的思路

不需要模拟左旋的过程只需要将前n个字符挪到字符串尾
class Solution {
public:string LeftRotateString(string str, int n) {if(!str.size()) return "";return str.substr(n)+str.substr(0, n);}
};

面试题43 :n个骰子的点数

把n个骰子扔在地上,所有骰子朝上一面的点数之和是s。输入n,打印出s的所有可能的值出现的概率

思路

骰子一共6个面,每个面上一个点数,对应1~6之间的一个数字。所以n个骰子的点数之和最小值是n,最大值是6n。n个骰子所有的排列数总共有6n。统计每一个点数出现的次数除以6n,就可以求出每个点数出现的概率。

解法1,递归求骰子点数

参考offer书的思路

现在我们考虑如何统计每一个点数出现的次数。要想求出n个骰子的 点数和,可以先把n个骰子分为两堆:第一堆只有一个,另一个有n-1个。 单独的那一个有可能出现从1到6的点数。我们需要计算从1到6的每一种 点数和剩下的n-1个骰子来计算点数和。接下来把剩下的n-1个骰子还是 分成两堆,第一堆只有一个,第二堆有n-2个。我们把上一轮那个单独骰 子的点数和这一轮单独骰子的点数相加,再和剩下的n-2个骰子来计算点 数和。分析到这里,我们不难发现这是一种递归的思路,递归结束的条件 就是最后只剩下一个骰子。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
int g_maxValue = 6;
void Probablity(int original, int current, int sum, int* pProbabilities) {if (current == 1)pProbabilities[sum - original]++;else {for (int i = 1; i <= g_maxValue; ++i)Probablity(original, current - 1, i + sum, pProbabilities);}
}
void probablity(int number, int* pProbablities) {for (int i = 1; i <= g_maxValue; ++i)Probablity(number, number, i, pProbablities);
}
void printProbability(int number) {if (number < 1) return;int maxSum = number * g_maxValue;int* pProbabilities = new int[maxSum - number + 1];for (int i = number; i <= maxSum; ++i)pProbabilities[i - number] = 0;probablity(number, pProbabilities);int total = pow((double)g_maxValue, number);for (int i = number; i <= maxSum; ++i) {double ratio = (double)pProbabilities[i - number] / total;printf("%d: %e\n", i, ratio);}delete[] pProbabilities;
}
int main() {printProbability(3);return 0;
}

基于递归实现,有很多计算都是重复的,导致number变大时性能变慢

解法2,基于循环求骰子点数(时间性能好)

参考offer书的思路

可以换一种思路来解决这个问题。我们可以考虑用两个数组来存储骰子点数的每-个总数出现的次数。在一次循环中,第一个数组中的第n个 数字表示骰子和为n出现的次数。在下一循环中,我们加上一个新的骰子, 此时和为n的骰子出现的次数应该等于上一次循环中骰子点数和为n-1、 n-2, n-3、n-4、n-5与n-6的次数的总和,所以我们把另一个数组的第n 个数字设为前一个数组对应的第nT、n-2、n-3、n-4、n-5与n-6之和。 基于这个思路,我们可以写出如下代码:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
int g_maxValue = 6;
void PrintProbablity(int number) {if (number < 1) return;int* pProbabilities[2];pProbabilities[0] = new int[g_maxValue * number + 1];pProbabilities[1] = new int[g_maxValue * number + 1];for (int i = 0; i < g_maxValue * number + 1; ++i) {pProbabilities[0][i] = 0;pProbabilities[1][i] = 0;}int flag = 0;for (int i = 1; i <= g_maxValue; ++i)pProbabilities[flag][i] = 1;for (int k = 2; k <= number; ++k) {for (int i = 0; i < k; ++i)pProbabilities[1 - flag][i] = 0;for (int i = k; i <= g_maxValue * k; ++i) {pProbabilities[1 - flag][i] = 0;for (int j = 1; j <= i && j <= g_maxValue; ++j)pProbabilities[1 - flag][i] += pProbabilities[flag][i - j];}flag = 1 - flag;}double total = pow((double)g_maxValue, number);for (int i = number; i <= g_maxValue * number; ++i) {double ratio = (double)pProbabilities[flag][i] / total;printf("%d:%e\n", i, ratio);}delete[] pProbabilities[0];delete[] pProbabilities[1];
}
int main() {PrintProbablity(3);return 0;
}

面试题44 :扑克牌的顺子

思路1

借助桶结构,将牌全部放入桶中,判断连续的牌数加上0的牌数等于5一定是顺子,左右非零牌的牌中间的连续区间只要有五个元素就一定是顺子

class Solution {
public:bool IsContinuous( vector<int> numbers ) {//使用桶//A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K//A, 2, 3, 4, 5是顺子vector<int> bucket(13, 0);int count_0 = 0; //0的个数for (int i = 0; i < 5; ++i) {if (numbers[i] == 0) count_0++;else bucket[numbers[i] - 1] = numbers[i];}int count = 0; //非0个数int i = 0;while (bucket[i] == 0)i++;int left = i; //最左非0下标for (i; i < 13; ++i) { //连续非0的个数if (bucket[i] > 0) count++;else break;}int right = 0; //最右非0下标for (right = 12; right > 0; --right)if (bucket[right] > 0) break;//[left, right]只要刚好是5,则一定是顺子;连续的牌数加上0的牌数等于5一定是顺子if (count + count_0 == 5 || right - left + 1 == 5)return true;elsereturn false;}
};
/*
[1,1,2,3,4]
*/

思路2

先对五张牌排序,只要非零的牌最大和最小的牌差等于4就一定是顺子; 0的牌个数与最大和最小的牌差等于5就一定是顺子

class Solution {
public:bool IsContinuous(vector<int> numbers) {//利用set容器排序set<int> se;char count_0 = 0; //非零个数for (int i = 0; i < 5; ++i)if (!numbers[i]) count_0++;for (int i = 0; i < 5; ++i)if(numbers[i])se.insert(numbers[i]);set<int>::iterator it = se.begin();//只要非零的牌最大和最小的牌差等于4就一定是顺子; 0的牌个数与最大和最小的牌差等于5就一定是顺子//非零牌只有一张的情况也在其中if (*(se.rbegin()) - *it + 1 == 5 || count_0 + *(se.rbegin()) - *it + 1 == 5)return true;return false;}
};
/*
[1,1,2,3,4]
*/

面试题45 :圆圈中最后剩下的数字

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

如果没有小朋友,请返回-1

思路

约瑟夫环问题

使用C++容器循环双向链表list
class Solution {
public:int LastRemaining_Solution(int n, int m) {if (m == 0 || n == 0) return -1;list<int> orphan(n, 0);int index = 0;for(list<int>::iterator it = orphan.begin(); it!=orphan.end(); ++it)*it = index++;index = -1;while (orphan.size() > 1) {index = (index+m)% orphan.size();list<int>::iterator it = orphan.begin();int tmp = index;while(tmp--) it++;orphan.remove(*(it));index--;}return *orphan.begin();}
};
class Solution {
public:int LastRemaining_Solution(int n, int m) {if (m == 0 || n == 0) return -1;list<int> orphan(n, 0);int index = 0;for(list<int>::iterator it = orphan.begin(); it!=orphan.end(); ++it)*it = index++;index = 0;while (orphan.size() > 1) {index = (index+m-1)% orphan.size();list<int>::iterator it = orphan.begin();int tmp = index;while(tmp--) it++;orphan.remove(*(it));}return *orphan.begin();}
};
offer书中的第一种做法:
class Solution {
public:int LastRemaining_Solution(int n, int m) {if (m == 0 || n == 0) return -1;list<int> orphan;for(int i=0; i<n; ++i) orphan.push_back(i);list<int>::iterator it = orphan.begin();while (orphan.size() > 1) {int tmp = m-1;while(tmp--){it++;if(it == orphan.end())it = orphan.begin();}list<int>::iterator next = ++it;if(next == orphan.end())next = orphan.begin();orphan.remove(*(--it));it = next;}return *orphan.begin();}
};
使用逻辑上循环的vector模拟过程

。。。。。。与list相同的逻辑,错误不能过OJ,实在迷惑,已经向牛客提交纠错

class Solution {
public:int LastRemaining_Solution(int n, int m) {if (m == 0 || n == 0) return -1;vector<int> orphan(n, 0);int index = 0;while (index < orphan.size())orphan[index] = index++;index = 0;while (orphan.size() > 1) {index = (index+m-1)% orphan.size();orphan.erase(orphan.begin() + index);}return *orphan.begin();}
};
offer书中第二种解法

。。。

class Solution {
public:int LastRemaining_Solution(int n, int m) {if (m == 0 || n == 0) return -1;int index = 0;for(int i=2; i<=n; ++i){index = (index+m)%i;}return index;}
};

面试题46 :求1+2…+n

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

使用等差数列公式

1+2+3+...+n =  \cfrac{(n+1)*n}{2}

1 + 2 + 3 + . . . + n = ( n + 1 ) ∗ n 2 1+2+3+...+n = \cfrac{(n+1)*n}{2} 1+2+3+...+n=2(n+1)∗n​

class Solution {
public:int Sum_Solution(int n) {//使用等差数列求和公式: (n+1)*(n/2)return (n+1)*(n/2.0);}
};

offer->利用构造函数求解

构造n个对象实例,则会调用构造函数n次,可以在构造函数中对类的静态成员变量进行累加得到结果

class Temp{
public:Temp(){++N; Sum += N;}static int getSum(){return Sum;}static void reSet(){N = Sum = 0;}
private:static int N;static int Sum;
};int Temp::N = 0;
int Temp::Sum = 0;class Solution {
public:int Sum_Solution(int n) {Temp::reSet();Temp* tmp_arr = new Temp[n];return Temp::getSum();}
};

offer->利用虚函数求解

派生类对基类的虚函数重写(覆盖)

通过指向派生类的基类指针调用虚函数以及使用指向基类对象的基类指针调用虚函数,从而达到不同虚函数的目的,一个虚函数充当递归的循环,一个虚函数充当递归的终止条件

class A;
A* Array[2];
class A{
public:virtual int Sum(int n){return 0;}
};
class B: public A{
public:virtual int Sum(int n){return Array[!!n]->Sum(n-1)+n;}
};
class Solution {
public:int Sum_Solution(int n) {A a;B b;Array[0] = &a;Array[1] = &b;return Array[1]->Sum(n);}
};

其实offer用!!n有点繁琐,交换一下Array数组的两个元素,就可以!n就可以了

class A;
A* Array[2];
class A{
public:virtual int Sum(int n){return 0;}
};
class B: public A{
public:virtual int Sum(int n){return Array[!n]->Sum(n-1)+n;}
};
class Solution {
public:int Sum_Solution(int n) {A a;B b;Array[0] = &b;Array[1] = &a;return Array[0]->Sum(n);}
};

offer->利用函数指针求解

将上面利用虚函数求解,改成函数指针和函数数组的方式

int (*Array[2])(int); //函数指针数组
class Solution {
public:static int Sum_end(int n){return 0;}static int Sum_recurrence(int n){return Array[!n](n-1)+n;}int Sum_Solution(int n) {Array[0] = Sum_recurrence;Array[1] = Sum_end;return Array[0](n);}
};

offer->利用模板类型求解

template <int n> struct Sum_Solution4 {enum Value { N = Sum_Solution4<n - 1>::N + n };
};
template <> struct Sum_Solution4<1> {enum Value { N = 1 };
};class Solution {
public:int Sum_Solution(int n) {return Sum_Solution4<n>::N;}
};

以上代码不能正常执行且不能通过OJ

举例Sum_Solution4<100>::N; --> Sum_Solution4<99>::N+100; … --> Sum_Solution4<1>::1+2…+100;

这个过程是编译期间完成的,因此不能动态输入n

面试题47 :不用加减乘除做加法

思路

在计算机中数字都是二进制表示的,两个加数位异或运算得到不算进位的和,两个加数位与运算左移一位得到进位的值

因此两数和可以化解成求两数位异或加上两数与左移一位的值,遵循以上规则,一直迭代直到两数与是0,则两数和就是这一次迭代的异或值

举一个例子5+17

5:00101

17:10001

此时

5+17就转化成了:5^17 + 1<<(5&17)

5^17 = 10100

1<<(5&17) = 00010

5^17 + 1<<(5&17)转化成

10100^00010 = 10110

1<<(10010&00001) = 0

则此时5+17 = 10110 = 22

代码

class Solution {
public:int Add(int num1, int num2) {int res;while(num2){res = num1^num2;num2 = (num1&num2)<<1;num1 = res;}return num1; //return res的话,当num2是0,返回值就是随机值了}
};

面试题48 :不能被继承的类

设计一个不能被继承的类

在C#中定义了关键字sealed,被sealed修饰的类不能被继承。在Java 中同样也有关键字final表示一个类型不能被继承。在C++中没有类似于 sealed和final的关键字,我们只有自己来实现。

常规的解法:把构造函散设为私有函数

class SealedClass {
public:static SealedClass* getInstance() {return new SealedClass();}static void DeleteInstance(SealedClass* pInstance) {delete pInstance;}
private:SealedClass() {}~SealedClass() {}
};

这个类是不能被继承,但总觉得它和普通的类型有些不一样,使用起来有 点不方便。比如我们只能得到位于堆上的实例,而得不到位于栈上的实例。

新奇的解法:利用虚拟继承

template <typename T> class MakeSealed {friend T;
private:MakeSealed() {}~MakeSealed() {}
};
class SealedClass : virtual public MakeSealed<SealedClass> {
public:SealedClass() {}~SealedClass() {}
};
int main() {SealedClass sc;SealedClass* sc2 = new SealedClass();return 0;
}

这个SealedClass使用起来和一般的类型没有区别,我们可以在栈上、 也可以在堆上创建实例。尽管类MakeSealed的构造函数和析 构函数都是私有的,但由于类SealedClass2是它的友元类型,因此在 SealedClass中调用MakeSealed的构造函数和析构函数都不 会引起编译错误。

尝试继承SealedClass类,就会报错

template <typename T> class MakeSealed {friend T;
private:MakeSealed() {}~MakeSealed() {}
};
class SealedClass : virtual public MakeSealed<SealedClass> {
public:SealedClass() {}~SealedClass() {}
};
class Try : public SealedClass {
public:Try() {}~Try() {}
};

由于类 SealedClass2 是从类 MakeSealed虚继承过来的, 在调用Try的构造函数的时候,会跳过SealedClass2而直接调用 MakeSealed的构造函数。非常遗憾的是,Try不是 MakeScaled的友元类型,因此不能调用它的私有构造函数。
通过上面的分析,我们发现从SealedClass继承的类,一旦实例化就会导致编译出错,因此SealedClass不能被继承,这也就满足了题目的要求。

剑指Offer(第一版)相关推荐

  1. 【剑指offer】顺时针打印矩阵

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/26053049 剑指offer上的第20题,九度OJ上測试通过. 题目描写叙述: 输入一个矩 ...

  2. 剑指offer第二版答案详细版(带详细解题思路)

    1.滑动窗口的最大值(剑指offer原59题) 解题思路:其实是一个队列的问题,用一个队列去维护当前窗口中的所有元素:首先将超出窗口中的队头元素先删掉,然后将新的元素插入当前窗口中,插入时要判断新插入 ...

  3. 【每日一题】剑指 Offer 10- I. 斐波那契数列

    剑指 Offer 10- I. 斐波那契数列

  4. 【每日一题】剑指 Offer 22. 链表中倒数第k个节点

    剑指 Offer 22. 链表中倒数第k个节点

  5. 递增的整数序列链表的插入_每日算法题 | 剑指offer 链表专题 (5)链表中倒数第k个节点...

    点击上方"Jerry的算法和NLP",选择"星标"公众号 重磅干货,第一时间送达 题目 链表中倒数第k个节点 题目要求 输入一个链表的头结点,从尾到头反过来打印 ...

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

    弱菜刷题还是刷中文题好了,没必要和英文过不去,现在的重点是基本代码能力的恢复. [题目] 剑指offer 写一个函数,求两个整数之和,要求在函数体内不得使用+.-.*./四则运算符号. [思路] 直觉 ...

  7. 剑指offer:翻转单词顺序列

    题目描述 牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上.同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思.例如,"st ...

  8. 剑指offer:和为S的连续正数序列

    题目描述 小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100.但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数).没多久,他 ...

  9. 剑指offer:数组中只出现一次的数字

    题目描述 一个整型数组里除了两个数字之外,其他的数字都出现了两次.请写程序找出这两个只出现一次的数字. 解题思路 用容器存次数. class Solution { public:void FindNu ...

最新文章

  1. c# oldb连接_(转)C#连接OleDBConnection数据库的操作
  2. linux内核编译步骤
  3. 01-【Cron定时表达式】在线Cron表达式生成器+Cron表达式详解
  4. Windows系统下输入法变为繁体字
  5. 第六章-网络可靠性设计
  6. [译]const T vs. T const ——Dan Saks 【翻译】
  7. UnsatisfiedDependencyException报错的原因
  8. 前端开发入门教程-CSS(一)
  9. Java实现规则几何图形问题求解
  10. 网站URL网址末尾是否应该使用反斜杠
  11. 美团一面--后台开发
  12. 【3】一铭操作系统初体验,安装ope…
  13. 618有哪些数码产品值得入手?盘点值得入的数码好物推荐
  14. C#结合天敏VC4000采集卡视频监控
  15. Latex中字母上面加符号
  16. 终于搞清楚为啥微信刚打开聊天界面时会卡那么一下了
  17. 前端面试题HTML+CSS
  18. Xilinx 的 FPGA 从 1 月 9 日开始涨价
  19. 怎么锻炼出顶尖程序员的编码套路(转)
  20. wince7 屏幕控制_WinCE中触摸屏驱动开发详解

热门文章

  1. 解析百度谷歌地址栏参数意义
  2. w7怎么把计算机放桌面壁纸,Win7系统怎么锁定桌面背景?Win7锁定桌面背景的方法...
  3. gitlab13.7关闭用户邮箱验证方式
  4. Web技术是开发iOS和Android App
  5. winform控制textbox只能输入数字(小数)
  6. Websphere 部署EAR失败: EAR 文件可能已损坏和/或不完整
  7. python 入门实战改进B站小甲鱼飞机大战增强版4.0
  8. STUN协议和常用NAT类型
  9. 安全漏洞漫谈 【http://luoluo.cnblogs.com/】
  10. BigDecimal 使用总结