题目一
使用贪心算法:当前来到i位置,讨论i位置是否点灯,之前的选择要确保不会对i位置产生影响,即之前的选择点灯的位置不会照亮i位置。1) i位置为x–>i++;2) (a) i位置为.,如果i+1位置也是.将灯放在i+1位置,i+2位置不管是.还是x都可以直接跳过–>i+3;(b) i位置为x,如果i+1位置是x将灯放在i位置,i+1位置直接跳过–>i+2
代码实现:
//s中只有.和x两种字符
//路灯可以影响左中右三个位置
//至少需要多少灯可以把所有的.都点亮
int lightNums(string& s) {
int i = 0;
int res = 0;
//当前来到i位置,一定要保证之前的灯不会影响到i位置
//也就是确保之前点的灯一定不会照亮i位置
while (i < s.length()) {
if (s[i] == 'x') {//当前为x直接跳过
i++;
}
else {//当前为.,一定要点灯
res++;
if (i + 1 < s.length() && s[i + 1] == '.') {
//如果i+1位置也是.,将灯放在i+1位置,i+2位置不管是.还是x都可以直接跳过
i = i + 3;
}
else if (i + 1 < s.length() && s[i + 1] == 'x') {
//如果i+1位置是x,将灯放在i位置,i+1位置直接跳过
i = i + 2;
}
else {
//当前是最后一个位置,跳出循环
break;
}
}
}
return res;
}
题目二
根据先序遍历的第一个值(头节点)将中序遍历划分成左右两个部分(左右子树);先序遍历的第一个值是后序遍历的最后一个值;递归求解左右子树。
void f(vector<int>& pre, int prei, int prej, int ini, int inj, vector<int>& pos, int posi, int posj, unordered_map<int, int>& mp) {
if (prei > prej) {
return;
}
pos[posj] = pre[prei];//先序遍历的第一个值是后序遍历的最后一个值
if (prei == prej) {
return;
}
int inlj = mp[pre[prei]] - 1;//根据头节点分成两部分,左部分的j
int inri = mp[pre[prei]] + 1;//右部分的i
int inli = ini;
int inrj = inj;
int preli = prei + 1;//左部分的i
int prelj = inlj - inli + preli;//左部分的j
int preri = prelj + 1;//右部分的i
int prerj = prej;//右部分的j
int posli = posi;//左部分的i
int poslj = inlj - inli + posli;//左部分的j
int posri = poslj + 1;//右部分的i
int posrj = posj - 1;//右部分的j
f(pre, preli, prelj, inli, inlj, pos, posli, poslj, mp);
f(pre, preri, prerj, inri, inrj, pos, posri, posrj, mp);
}
vector<int>getPost(vector<int>& pre, vector<int>& in) {
int len = pre.size();
vector<int>res(len);
unordered_map<int, int>mp;//便于查找头节点所在位置
for (int i = 0; i < len; i++) {
mp.insert({ in[i],i });
}
f(pre, 0, len - 1, 0, len - 1, res, 0, len - 1, mp);
return res;
}
题目三
从小范围往大范围发散。
代码实现:
string num1To9(int num) {
if (num < 1 || num>9) {
return "";
}
vector<string>nums({ {"一"}, {"二"}, {"三"}, {"四"}, {"五"}, {"六"}, {"七"}, {"八"}, {"九"} });
return nums[num - 1];
}
string num1To99(int num, int hasBai) {
if (num < 1 || num>99) {
return "";
}
if (num < 10) {
return num1To9(num);
}
//有十位
int shi = num / 10;
//有无百位,对十位位1的数读法不一样,比如,17:十七;217:二百一十七
if (shi == 1 && !hasBai) {
return "十" + num1To9(num % 10);
}
return num1To9(shi) + "十" + num1To9(num % 10);
}
string num1To999(int num) {
if (num < 1 || num>999) {
return "";
}
if (num < 100) {
return num1To99(num,false);
}
string res = num1To9(num / 100) + "百";
int rest = num % 100;
if (rest == 0) {
return res;
}
if (rest >= 10) {
res += num1To99(rest, true);
}
else {
res += "零" + num1To99(rest, true);
}
return res;
}
string num1To9999(int num) {
if (num < 1 || num>9999) {
return "";
}
if (num < 1000) {
return num1To999(num);
}
string res = num1To9(num / 1000) + "千";
int rest = num % 1000;
if (rest == 0) {
return res;
}
if (rest >= 100) {
res += num1To999(rest);
}
else {
res += "零" + num1To99(rest, false);
}
return res;
}
string num1To99999999(int num) {
if (num < 1 && num>99999999) {
return "";
}
if (num < 10000) {
return num1To9999(num);
}
int wan = num / 10000;
int rest = num % 10000;
string res = num1To9999(wan) + "万";
if (rest == 0) {
return res;
}
if (rest < 1000) {
res += "零" + num1To999(rest);
}
else {
res += num1To9999(rest);
}
return res;
}
string getNumChiExp(int num) {
if (num == 0) {
return "零";
}
string res = num < 0 ? "负" : "";
num = abs(num);
int yi = num / 100000000;
int rest = num % 100000000;
if (yi == 0) {
return res + num1To99999999(rest);
}
res += num1To9999(yi) + "亿";
if (rest == 0) {
return res;
}
if (rest < 10000000) {
res += "零" + num1To99999999(rest);
}
else {
res += num1To99999999(rest);
}
return res;
}
题目四
可以遍历二叉树,但是题目给的是完全二叉树,应该有更快的算法。首先求完全二叉树的深度d(从头结点一直向左走);然后求头节点的右子树的深度,如果为d-1,说明头节点的左子树一定是满二叉树,如果为d-2,说明头节点的左子树不是满二叉树,但头节点的右子树一定是满二叉树;对不满的那个子树递归求节点树
时间复杂度:O((logN)^2)
代码实现:
class Node {
public:
int val;
Node* left;
Node* right;
Node(int val) {
this->val = val;
this->left = nullptr;
this->right = nullptr;
}
};
int getLevel(Node* head) {
if (head == nullptr) {
return 0;
}
int level = 0;
while (head != nullptr) {
level++;
head = head->left;
}
return level;
}
int NodeNum(Node* head) {
if (head == nullptr) {
return 0;
}
int res = 1;//头节点
int level = getLevel(head);
int leftLevel = getLevel(head->left);
int rightLevel = getLevel(head->right);
if (leftLevel == rightLevel) {
res += (1 << leftLevel) - 1;
res += NodeNum(head->right);
}
else {
res += (1 << rightLevel) - 1;
res += NodeNum(head->left);
}
return res;
}
题目五
子序列可以不连续,在不改变相对位置的情况下只要满足递增即可。
方法一:动态规划。dp[i]表示必须以arr[i]结尾的最长递增子序列;每遍历到一个位置查找前面比arr[i]小的元素的最长子序列,选择其中最长的那个作为arr[i]的前一个元素,则dp[i]的值就是在它的最长子序列长度再加一。
方法二:在方法一的基础上申请一个等长的ends数组,开始时ends全部为无效区;如果一个位置i在有效区,则ends[i]的含义是:所有长度为i+1的递增子序列中最小结尾是谁;arr每遍历到一个位置i在ends数组的有效区中二分的查找大于arr[i]最左的位置j,用arr[i]更新该位置的值ends[j],dp[i]=j+1;如果在ends的有效区中没找到大于arr[i]的位置,则扩充有效区j,ends[j]=arr[i],dp[i]=j+1。
为什么上述流程是对的?ends有效区一定是递增的。假设arr[i]=92,dp[i]=?arr[i]可以更新ends[20],长度为21的子序列的最小结尾是100,长度为20的子序列的最小结尾是90,那么92可以补在长度为20的子序列的后面,形成长度为21的子序列。
动态规划的时间复杂度为O(N^2),每个位置求动态规划值的时候存在枚举行为;方法二加速的点是构建了单调性,当求解存在枚举行为的动态规划时能够构建单调性,至少可以降阶,或者直接省掉。
代码实现:
int maxSubLen01(vector<int>& arr) {
if (arr.size() == 0) {
return 0;
}
vector<int>dp(arr.size());//以i结尾的最长子序列长度
dp[0] = 1;
int res = 1;
for (int i = 1; i < arr.size(); i++) {
for (int j = 0; j < i; j++) {
dp[i] = arr[i] > arr[j] ? max(dp[i], dp[j]) : dp[i];
}
dp[i] += 1;
res = max(res, dp[i]);
}
return res;
}
int maxSubLen02(vector<int>& arr) {
if (arr.size() == 0) {
return 0;
}
int res = 1;
vector<int>eds(arr.size());
eds[0] = arr[0];
int e = 0;//有效区的右边界
for (int i = 1; i < arr.size(); i++) {
int l = 0;
int r = e;
int index = -1;
while (l <= r) {
int mid = l + ((r - l) >> 1);
if (eds[mid] < arr[i]) {
l = mid + 1;
}
else {
r = mid - 1;
index = mid;
}
}
if (index != -1) {
eds[index] = arr[i];
}
else {
e++;//扩展有效区
eds[e] = arr[i];
}
}
res = e + 1;
return res;
}
题目六
判断一个数能否被3整数等价于这个数的每一位相加之和能否被3整数,注意是等价,因此可以反过来,将这个数分成各个部分,各部分求和判断能否被3整除。举个栗子:判断1234能否被3整除,可以将个十百千位上的数字相加得到10,判断10能否被3整除;也可以直接判断1234能否被3整除;也可以判断12+34能否被3整除,因为他们都等价于判断1+2+3+4能否被3整除。
代码实现:
int f(int l, int r) {
int res = 0;
for (int i = 0; i < r - l + 1; i++) {
if (((long)(l + i + 1) * (l + i) >> 1) % 3 == 0) {
res++;
}
}
return res;
}