目录
36. 两个链表的第一个公共结点--
题目:输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
思路:使用两个指针p1,p2,分别指向两个链表的头。然后这两个指针一起往后走,当指针到头了,就将指针指向另一个链表的头。如果两个链表有交点,最终会走到一起。即使两个链表没有交点,最终p1和p2会同时为NULL,也会终止循环,然后返回NULL。
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
if (pHead1 == NULL || pHead2 == NULL) return NULL;
ListNode* p1 = pHead1, *p2 = pHead2;
while (p1 != p2){
if (p1) p1 = p1->next;
else p1 = pHead2;
if (p2) p2 = p2->next;
else p2 = pHead1;
}
return p1;
}
};
37. 数字在排序数组中出现的次数
题目:统计一个数字在一个排序数组中出现的次数。
思路:upper_bound() - lower_bound()
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
//int p1 = lower_bound(data.begin(), data.end(), k) - data.begin();
//int p2 = upper_bound(data.begin(), data.end(), k) - data.begin();
int p1 = lower_Search(data, k);
int p2 = upper_Search(data, k);
return p2 - p1;
}
// 找第一个大于等于k的位置
int lower_Search(vector<int> data, int k){
int l = 0, r = data.size();
while (l < r){
int mid = (l + r) >> 1;
if (data[mid] >= k)
r = mid;
else l = mid + 1;
}
return r;
}
// 找第一个大于k的位置
int upper_Search(vector<int> data, int k){
int l = 0, r = data.size();
while (l < r){
int mid = (l + r) >> 1;
if (data[mid] > k)
r = mid;
else l = mid + 1;
}
return r;
}
};
38. 二叉树的深度
题目:输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
思路:递归
class Solution {
public:
int TreeDepth(TreeNode* pRoot)
{
if (pRoot == NULL) return 0;
return 1 + max(TreeDepth(pRoot->left), TreeDepth(pRoot->right));
}
};
39. 平衡二叉树--
题目:输入一棵二叉树,判断该二叉树是否是平衡二叉树。
思路:平衡二叉树,就是左子树和右字数的高度差不超过1。计算左右子树的高度,然后在返回前判断差值是否超过1。
class Solution {
public:
bool IsBalanced_Solution(TreeNode* pRoot) {
return getDepth(pRoot) != -1;
}
int getDepth(TreeNode* root){
if (root == NULL) return 0;
int lDep = getDepth(root->left);
if (lDep == -1) return -1;
int rDep = getDepth(root->right);
if (rDep == -1) return -1;
if (abs(lDep - rDep) > 1) return -1;
else return max(lDep, rDep) + 1;
}
};
40. 数组中只出现一次的数字--
题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
思路:对一个数字num,num ^ num = 0,所以对所有数字进行异或,最终的值,就为只出现一次的数字的异或值。
然后计算这个最终值的二进制中,最低位的1的位置pos;在这两个只出现一次的数字中,一个pos位为1,另一位pos位为0;
所以就遍历一次数组,根据这个位是否为1,分开进行异或,最终获得这两个数字。
class Solution {
public:
void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
int xorAll = 0;
for (int i = 0; i < data.size(); i++){
xorAll ^= data[i];
}
// 计算xorAll只有最右侧为1的数
// 比如xorAll为10100,则lowBit为00100
/*
int lowBit = 1;
while ((xorAll & 1) == 0){
xorAll = xorAll >> 1;
lowBit = lowBit << 1;
}*/
// 更简单的方法,就是直接 & (-xorAll),因为它是xorAll取反加1
int lowBit = xorAll & (-xorAll);
num1[0] = 0;
num2[0] = 0;
for (int i = 0; i < data.size(); i++){
if (data[i] & lowBit)
num1[0] ^= data[i];
else num2[0] ^= data[i];
}
}
};
41. 和为S的连续正数序列
题目:小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
思路:双指针。第一记录头,一个记录尾。
class Solution {
public:
vector<vector<int> > FindContinuousSequence(int sum) {
int pl = 1, pr = 1;
int curSum = 0;
vector<vector<int> >ans;
vector<int>seq;
while (pr <= sum){
if (curSum >= sum){
// 有相等的序列了,就push_back()
if (curSum == sum) ans.push_back(seq);
curSum -= pl;
pl++;
seq.erase(seq.begin());
}
else if (curSum < sum){
curSum += pr;
seq.push_back(pr);
pr++;
}
}
return ans;
}
};
42. 和为S的两个数字
题目:输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
思路:因为是递增排序的数组,所以双指针,一个指向数组头,一个指向数尾,然后两边往中间走。
class Solution {
public:
vector<int> FindNumbersWithSum(vector<int> arr,int sum) {
int pl = 0, pr = arr.size() - 1;
int num1, num2, flag = 0;
while (pl < pr){
int curSum = arr[pl] + arr[pr];
if (curSum > sum) pr--;
else if (curSum < sum) pl++;
else {
if (flag == 0){
num1 = arr[pl];
num2 = arr[pr];
flag = 1;
}
else {
if (num1 * num2 > arr[pl] * arr[pr]){
num1 = arr[pl];
num2 = arr[pr];
}
}
pl++;
}
}
if (flag) return vector<int>{num1, num2};
else return vector<int>();
}
};
43. 左旋转字符串--
题目:汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!
思路:最简单就直接使用substr函数拼接一下,但是更巧妙的方法是,利用转置,Y X = (X' Y')' ,所以就先把两部分字符串翻转,然后再整体翻转一次,就得到最终答案。
另外有一个坑,就是n可能大于字符串的长度,所以要mod一下。
class Solution {
public:
string LeftRotateString(string str, int n) {
/*
int len = str.length();
// 字符串为空时要特判
if (len == 0) return str;
n = n % len;
str += str;
// substr(n, len), 从n开始的len个字符
return str.substr(n, len);
*/
int len = str.length();
if (len == 0) return str;
n = n % len;
reverse(str, 0, n - 1);
reverse(str, n, len - 1);
reverse(str, 0, len - 1);
return str;
}
void reverse(string &s, int i, int j){
while (i < j)
swap(s[i++], s[j--]);
}
};
44. 翻转单词顺序列--
题目:牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?
思路:同上题。 X Y Z= (Z' Y' X')', 所以先根据空格分隔字符串,然后分别进行翻转,然后接到一起,整体再翻转一次。
class Solution {
public:
string ReverseSentence(string str) {
int sta = 0;
for (int i = 0; i < str.length(); i++){
if (str[i] == ' '){
reverse(str, sta, i - 1);
sta = i + 1;
}
else if (i == str.length() - 1){
reverse(str, sta, i);
}
}
reverse(str, 0, str.length() - 1);
return str;
}
void reverse(string &s, int i, int j){
while (i < j)
swap(s[i++], s[j--]);
}
};
45. 扑克牌顺子
题目:LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子.....LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
思路:排一个序,计算大\小王,即0的个数,然后计算相连的数字的差值,如果有足够多的0进行弥补,就行。
这个题的意思,num数组中的数一定为5个,或者为0个,所以很简单。
class Solution {
public:
bool IsContinuous( vector<int> num ) {
int len = num.size();
if (len == 0) return false;
sort(num.begin(), num.end());
int cnt = 0, pre, pos;
for (int i = 0; i < num.size(); i++){
if (num[i] == 0) cnt++;
else {
pos = i;
break;
}
}
for (int i = pos + 1; i < len; i++){
if (num[i] == num[i - 1]) return false;
cnt -= num[i] - num[i - 1] - 1;
if (cnt < 0) return false;
}
return true;
}
};
46. 孩子们的游戏(圆圈中最后剩下的数)--
题目:每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
如果没有小朋友,请返回-1
思路:约瑟夫环
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
/*
// 普通的模拟做法
if (n == 0) return -1;
vector<int> num;
for (int i = 0; i < n; i++)
num.push_back(i);
int cur = 0;
while (1){
int len = num.size();
if (len == 1) break;
cur = (cur + m - 1) % len;
num.erase(num.begin() + cur);
}
*/
/*
// 递归
return Josephus(n, m);
*/
// 非递归
if (n == 0) return -1;
int pos = 0;
for (int i = 1; i <= n; i++){
pos = (pos + m) % i;
}
return pos;
}
int Josephus(int n, int m){
if (n == 0) return -1;
if (n == 1) return 0;
return (Josephus(n-1, m) + m ) % n;
}
};
47. 求1+2+3+...+n--
题目:求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
思路:递归,利用0 判断递归结束。
class Solution {
public:
int Sum_Solution(int n) {
// n!=0 时,才会执行后面的 n += Sum_Solution(n - 1)
(n != 0) && (n += Sum_Solution(n - 1));
return n;
}
};
48. 不用加减乘除做加法--
题目:写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
思路:
num1 ^ num2; // 忽略了进位的 加法
(num1 & num2) << 1; // 得到加法中的进位
// 这时候原始的num1 + num2, 变成了新的两个值相加,但是结果是一样的
// 比如 num1+num2 = 7 + 5 -> 10 + 2 -> 8 + 4 -> 12 + 0
// 这样子一直循环到num1和num2相加,没有进位了,即num2变成了0
// 结果就是num1
class Solution {
public:
int Add(int num1, int num2)
{
while (num2 != 0){
int temp = num1 ^ num2; // 忽略了进位的 加法
num2 = (num1 & num2) << 1; // 得到加法中的进位
num1 = temp;
// 这时候原始的num1 + num2, 变成了新的两个值相加,但是结果是一样的
// 比如 num1+num2 = 7 + 5 -> 10 + 2 -> 8 + 4 -> 12 + 0
// 这样子一直循环到num1和num2相加,没有进位了,即num2变成了0
// 结果就是num1
}
return num1;
}
};
49. 把字符串转换成整数
题目:将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0
思路:str字符串可能有符号,所以就判断i为0时是否为符号。其他就照常做。另外因为返回的是int,所以数字大于2147483647或者小于-2147483648,就会溢出,返回0就行了 。
class Solution {
public:
int StrToInt(string str) {
if (str.length() == 0) return 0;
bool negative = false;
long ans = 0;
for (int i = 0; i < str.length(); i++){
if (i == 0 && (str[0] == '+' || str[0] == '-')){
if (str[0] == '-') negative = true;
continue;
}
if (str[i] < '0' || str[i] > '9') return 0;
ans = ans * 10 + str[i] - '0';
}
if (negative){
if (ans <= 2147483648) ans = -ans;
else ans = 0; // 溢出了
}
else {
if (ans > 2147483647) ans = 0; // 溢出了
}
return (int)ans;
}
};
50. 数组中重复的数字--
题目:在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
思路:题意就是找出任意一个重复出现的数字。因为数字大小[0,n-1],所以就将数值i赋值到num[i],进行替换,当i出现第二次时,num[i]已经是i了,所以就能够判定这个数出现了两次以上。
class Solution {
public:
bool duplicate(int numbers[], int length, int* duplication) {
// 很巧妙,把数a放到num[a]去
for (int i = 0; i < length; i++){
while (numbers[i] != i){
if (numbers[numbers[i]] == numbers[i]){
duplication[0] = numbers[i];
return true;
}
int tmp = numbers[numbers[i]];
numbers[numbers[i]] = numbers[i];
numbers[i] = tmp;
}
}
return false;
}
};
51. 构建乘积数组---
题意:给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。(注意:规定B[0] = A[1] * A[2] * ... * A[n-1],B[n-1] = A[0] * A[1] * ... * A[n-2];)
思路:从左到右乘一次,然后从右往左乘一次。假设len = 5具体如下:
B[0] = 1;
对于第一个for循环,从左到右:
B[1] = A[0]
B[2] = A[0] * A[1];
B[3] = A[0] * A[1] * A[2];
B[4] = A[0] * A[1] * A[2] * A[3];
然后第二个循环,从右到左:
B[3] = B[3] * A[4]
B[2] = B[2] * A[4] * A[3]
B[1] = B[1] * A[4] * A[3] * A[2]
B[0] = B[0] * A[4] * A[3] * A[2] * A[1]
所以最终:
B[0] = A[1] * A[2] * A[3] * A[4]
B[1] = A[0] * A[2] * A[3] * A[4]
B[2] = A[0] * A[1] * A[3] * A[4]
B[3] = A[0] * A[1] * A[2] * A[4]
B[4] = A[0] * A[1] * A[2] * A[3];
class Solution {
public:
vector<int> multiply(const vector<int>& A) {
int len = A.size();
vector<int> B(len);
int num = 1;
B[0] = 1;
for (int i = 0; i < len - 1; i++){
num *= A[i];
B[i + 1] = num;
}
num = 1;
for (int i = len - 1; i > 0; i--){
num *= A[i];
B[i - 1] *= num;
}
return B;
}
};
52. 正则表达式匹配---
题目:请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配
思路:用dp,或者递归。
class Solution {
public:
bool match(char* str, char* pattern)
{
if (str[0] == '\0' && pattern[0] == '\0')
return true;
if (str[0] != '\0' && pattern[0] == '\0')
return false;
if (pattern[1] != '*'){
// 当前两个字符相同,
if (str[0] == pattern[0] || (str[0] != '\0' && pattern[0] == '.'))
return match(str + 1, pattern + 1);
// 当前两个字符不同,直接完事
else return false;
}
else {
if (str[0] == pattern[0] || (str[0] != '\0' && pattern[0] == '.'))
return match(str+1, pattern) // 这句是*匹配一次,然后递归,
// 如果下一个跟当前字符一样,那* 就再匹配一次,相当于*匹配多次了
|| match(str , pattern+2);
// 这句就是递归到上一个字符跟当前字符不一样了,*匹配0次
//例子:令str="bbbacd",pattern=".*acd"
//在运行时,对于.* ,match(str+1, pattern)这个语句会一直递归下去,
//即.* 匹配完所有b后,会去匹配a,会去匹配c,会去匹配d,直到str结尾'\0',
//然后因为全部情况都不满足,
//递归回到了 .*匹配a, 这时候走 match(str , pattern+2),
// *匹配0次,就对了
// 所以这个递归,就是会考虑所有的情况,即使我们直到当前情况已经错了
else
return match(str, pattern+2); // *匹配0次
}
}
};
53. 表示数值的字符串--
题目:请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。
思路:就这样吧,不纠结了。
class Solution {
public:
bool isNumeric(char* str)
{
// 像 -. , -.e5 这些数据应该都没有
bool sign = false, hasE = false, decimal = false;
int len = strlen(str);
for (int i = 0; i < len; i++){
if (str[i] == 'E' || str[i] == 'e'){
if (i == len - 1) return false;
if (hasE) return false;
hasE = true;
}
else if (str[i] == '.'){
if (hasE) return false; // 小数点不能在E/e后面
if (decimal) return false;
decimal = true;
}
else if (str[i] == '+' || str[i] == '-'){
// 第二次出现不紧接在E/e后面
if (sign && str[i - 1] != 'E' && str[i - 1] != 'e')
return false;
// 第一次出现不在开头或者E/e后面
if (!sign && i != 0 &&
str[i - 1] != 'E' && str[i - 1] != 'e')
return false;
sign = true;
}
else if (str[i] > '9' || str[i] < '0')
return false;
}
return true;
}
};
54. 字符流中第一个不重复的字符--
题目:请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
如果当前字符流没有存在出现一次的字符,返回#字符。
思路:使用hash_map记录字符出现的次数,queue头部维护为当前只出现一个的字符。
class Solution
{
public:
unordered_map<char, int> cnt;
queue<char> q;
//Insert one char from stringstream
void Insert(char ch)
{
if (cnt.find(ch) == cnt.end()){
q.push(ch);
cnt[ch] = 1;
}
else cnt[ch]++;
}
//return the first appearence once char in current stringstream
char FirstAppearingOnce()
{
while(!q.empty()){
char c = q.front();
if (cnt[c] == 1) return c;
q.pop();
}
return '#';
}
};
55. 链表中环的入口结点---
题目:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
思路:使用慢指针和快指针,慢指针一次走一步,快指针一次走两步,如果有环,这两个指针会在环中相遇;然后将其中一个指针放到链表头,另一个指针不动,然后同时一步一步走,最终会在环的入口处相遇。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
/*
结论1:如果有环,那么快指针一定会和慢指针相遇。
理解:就是慢指针和快指针都在环里转的时候,可以看做是每走一次,快指针追上慢指针一步,最终总会追上。
结论2:把其中一个指针放到开头,另一个指针在相遇点不懂,两个指针都一步一步走,最终会在环的入口点相遇。
x y
a --- b ----c
\ /
假设a为起始点,b为入口点,c为相遇点,ab段长为x,bc段长为y,环长为cir
慢指针走的路程 = x + cir*m + y 跑了m圈
快指针走的路程 = x + cir*n + y 跑了n圈
但是!慢指针从进入环到相遇,走过的路程,是不会超过一个环的,
想一想,每次快指针追慢指针一步, 如果慢指针走了一圈还多,比如走了M步,M>cir, 那快指针就追了慢指针M>cir步
,那快指针就超过慢指针了,而不只是相遇了
所以 m = 0, 即慢指针走过的路程 = x + y
因为快指针速度是慢指针的两倍,所以 (x + y) * 2 = x + cir * n + y
-> x = cir*n - y (n > 0)
令n = 1(n等于几都行,n 大于1的话,那就是在环里多转几圈嘛),
x = cir - y , 也就是c -> b 那段
也就是 a->b 的距离 和 c->b 的距离是一样的
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
if (pHead == NULL) return NULL;
ListNode *slow = pHead;
ListNode *fast = pHead;
while (1){
slow = slow->next;
if (fast->next)
fast = fast->next->next;
else return NULL;
if (slow == fast) break;
}
fast = pHead;
while (slow != fast){
slow = slow->next;
fast = fast->next;
}
return slow;
}
};
56. 删除链表中重复的结点
题目:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
思路:使用递归更加方便。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
// 递归
if (pHead == NULL || pHead->next == NULL) return pHead;
if (pHead->val == pHead->next->val){
while (pHead->next && pHead->val == pHead->next->val)
pHead = pHead->next;
return deleteDuplication(pHead->next);
}
else {
pHead->next = deleteDuplication(pHead->next);
return pHead;
}
/*
// 非递归
if (pHead == NULL) return NULL;
ListNode * p = pHead, *pre = NULL;
while (p){
int flag = 0;
while (p->next && p->val == p->next->val){
p->next = p->next->next;
flag = 1;
}
if (flag){
// 当删除重复节点的时候,pre是变的
if (pre == NULL){
pHead = p->next;
}
else {
pre->next = p->next;
}
}
else pre = p;
p = p->next;
}
return pHead;*/
}
};
57. 二叉树的下一个结点--
题目:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
思路:中序遍历的下一个节点,①如果当前节点有右子节点的话,就找右子节点的最左节点;
②否则就往上找,找第一个左子节点包含当前结点的父节点。
/*
struct TreeLinkNode {
int val;
struct TreeLinkNode *left;
struct TreeLinkNode *right;
struct TreeLinkNode *next;
TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
}
};
*/
class Solution {
public:
TreeLinkNode* GetNext(TreeLinkNode* pNode)
{
if (pNode == NULL) return NULL;
if (pNode->right){
pNode = pNode->right;
while (pNode->left){
pNode = pNode->left;
}
return pNode;
}
else {
while (1){
TreeLinkNode* father = pNode->next;
if (father == NULL) return NULL;
if (father->left == pNode) return father;
pNode = father;
}
}
}
};
58. 对称的二叉树
题目:请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
思路:就判断所有左右子树是否对称。
class Solution {
public:
bool isSymmetrical(TreeNode* pRoot)
{
if (pRoot == NULL) return true;
return judge(pRoot->left, pRoot->right);
}
bool judge(TreeNode* left, TreeNode *right){
if (left == NULL && right == NULL) return true;
if (left == NULL || right == NULL) return false;
if (left->val != right->val) return false;
return judge(left->left, right->right) &&
judge(left->right, right->left);
}
};
59. 按之字形顺序打印二叉树--
题目:请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
思路:用bfs遍历树,然后用一个变量sonCnt记录每层的节点数,一个reverse_记录是否反过来。
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
queue<TreeNode*> q;
vector<vector<int> >ans;
if (pRoot == NULL) return ans;
q.push(pRoot);
int sonCnt= 1, reverse_ = 0;
while (!q.empty()){
int temp = 0;
vector<int> seq;
for (int i = 1; i <= sonCnt; i++){
TreeNode* root = q.front();
q.pop();
seq.push_back(root->val);
if (root->left) {
q.push(root->left);
temp++;
}
if (root->right){
q.push(root->right);
temp++;
}
}
sonCnt = temp;
if (reverse_) reverse(seq.begin(), seq.end());
reverse_ = 1 - reverse_;
ans.push_back(seq);
}
return ans;
}
};
60. 把二叉树打印成多行
题目:从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
思路:同上。使用bfs,然后用一个sonCnt记录每层节点数。
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int> >ans;
if (pRoot == NULL) return ans;
queue<TreeNode*> q;
q.push(pRoot);
int sonCnt = 1;
while (!q.empty()){
int temp = 0;
vector<int> seq;
for (int i = 1; i <= sonCnt; i++){
TreeNode* root = q.front();
q.pop();
seq.push_back(root->val);
if (root->left){
q.push(root->left);
temp++;
}
if (root->right){
q.push(root->right);
temp++;
}
}
ans.push_back(seq);
sonCnt = temp;
}
return ans;
}
};
61. 序列化二叉树--
题目:请实现两个函数,分别用来序列化和反序列化二叉树
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
思路:使用前序遍历序列化二叉树,每个值之前用','分隔;然后在反序列化的时候,也用前序遍历,然后将值分割出来。
要使用一个全局的pos记录当前处理到哪一个数字了。
class Solution {
public:
char* Serialize(TreeNode *root) {
if (root == NULL){
/*
//这就是个坑,不要使用 (char*)str.data() 返回。
//我的理解是,因为 str 是在当前函数里创建的string,用的是栈里的内存,
//当前函数结束后,这个str就被释放掉了,后面再使用它就错了。
string str = "#";
return (char*)str.data();
*/
char *ret = new char[1];
ret[0] = '#'; ret[1] = '\0';
return ret;
}
string num = to_string(root->val);
char* left = Serialize(root->left);
char* right = Serialize(root->right);
char* ret = new char[strlen(left)+strlen(right)+num.size()+2];
string res = num + "," + left + "," + right;
strcpy(ret,res.c_str());
return ret;
}
TreeNode* Deserialize(char *str) {
TreeNode* pHead;
int pos = 0;
return Deserialize(pHead, str, pos, strlen(str));
}
TreeNode* Deserialize(TreeNode* pHead, char *str, int &pos, int len) {
if (pos >= len) return NULL;
if (str[pos] == '#') {
pos += 2;
return NULL;
}
int val = 0;
for (; pos < len; pos++){
if (str[pos] == ','){
pos++;
break;
}
val = val * 10 + str[pos] - '0';
}
pHead = new TreeNode(val);
pHead->left = Deserialize(pHead->left, str, pos, len);
pHead->right = Deserialize(pHead->right, str, pos, len);
return pHead;
}
};
62. 二叉搜索树的第k个结点--
题目:给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
思路:二叉搜索树的中序遍历,就是排好序的。所以使用中序遍历,找到第k个值,就是第k小的值。
class Solution {
public:
TreeNode* KthNode(TreeNode* pRoot, int k)
{
int cnt = 0;
TreeNode* ans = NULL;
findKth(pRoot, k, cnt, ans);
return ans;
}
void findKth(TreeNode* root, int k, int &cnt, TreeNode* &ans)
{
if (root == NULL) return;
if (cnt == k) return;
findKth(root->left, k, cnt, ans);
cnt++;
if (cnt == k){
ans = root;
return;
}
findKth(root->right, k, cnt, ans);
}
};
63. 数据流中的中位数---
题目:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
思路:使用两个优先队列,一个为最大堆,一个为最小堆。将整个数据流分为两部分,左边的数放在最大堆里,右边放在最小堆里。然后保证左边的数全都小于等于右边的数,怎么保障呢?
在往左边插入数的时候,先把数字插入左边的最大堆中,然后把左边的最大值(堆顶)pop,然后push进右边的最小堆。
同理,在往右边插入数的时候,先把数字插入右边的最小堆中,然后把右边的最小值pop,然后push进左边的最大堆。
然后插入值时,轮流一遍插入一次,保证左右两边的堆大小一样或只相差一。计算中位数的时候就很方便了。
class Solution {
public:
priority_queue<int,vector<int>,less<int> >qLeft; // 从大到小
priority_queue<int,vector<int>,greater<int> >qRight; // 从小到大
int N = 0;
void Insert(int num)
{
if (N % 2 == 0){
qLeft.push(num);
qRight.push(qLeft.top());
qLeft.pop();
// 每次把左边最大的给右边
}
else {
qRight.push(num);
qLeft.push(qRight.top());
qRight.pop();
// 每次把右边最小的给左边
}
N++;
}
double GetMedian()
{
if (N % 2)
return qRight.top() * 1.0;
else
return (qLeft.top() + qRight.top()) / 2.0;
}
};
64. 滑动窗口的最大值---
题目:给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
思路:维护一个双端队列,队列里面存数的位置;
使队列开头一直是窗口最大值;
当遍历到一个数时,队列从后往前,pop_back()队列中比这个数小的;
当队首的数字不在窗口内了(i - q.front() + 1 > k),pop_front();
class Solution {
public:
vector<int> maxInWindows(const vector<int>& nums, unsigned int k)
{
deque<int> q; // 双端队列
vector<int> res;
if (k > nums.size() || k == 0) return {};
for (int i = 0; i < nums.size(); i++){
while (!q.empty() && nums[i] >= nums[q.back()])
q.pop_back();
while (!q.empty() && i - q.front() + 1 > k)
q.pop_front();
q.push_back(i);
if (i + 1 >= k)
res.push_back(nums[q.front()]);
}
return res;
}
};
65. 矩阵中的路径
题目:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 \begin{bmatrix} a & b & c &e \\ s & f & c & s \\ a & d & e& e\\ \end{bmatrix}\quad⎣⎡asabfdcceese⎦⎤ 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
思路:dfs就行了
class Solution {
public:
bool flag = false;
int dir[4][2] = {{0,1}, {1,0}, {-1,0}, {0,-1}};
bool hasPath(char* mat, int rows, int cols, char* str)
{
int len = strlen(str);
if (len == 0) return true;
vector<vector<int> > mark(rows, vector<int>(cols, 0));
for (int i = 0; i < rows; i++){
for (int j = 0; j < cols; j++){
if (mat[i*cols + j] == str[0]){
mark[i][j] = 1;
dfs(mat, rows, cols, str, i, j, 0, len, mark);
if (flag) return true;
mark[i][j] = 0;
}
}
}
return false;
}
void dfs(char* mat, int rows, int cols, char* str, int x, int y, int pos, int len, vector<vector<int> > &mark){
if (flag) return;
if (str[pos] != mat[x*cols + y]) return;
if (pos == len - 1) {
flag = true;
return;
}
for (int i = 0; i < 4; i++){
int xx = x + dir[i][0];
int yy = y + dir[i][1];
if (xx >= 0 && xx < rows && yy >= 0 && yy < cols){
if (mark[xx][yy]) continue;
mark[xx][yy] = 1;
dfs(mat, rows, cols, str, xx, yy, pos+1, len, mark);
mark[xx][yy] = 0;
}
}
}
};
66. 机器人的运动范围
题目:地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
思路:bfs就行了
class Solution {
public:
class Point{
public:
int x, y;
Point(int x_, int y_):x(x_), y(y_){}
};
int movingCount(int threshold, int rows, int cols)
{
int cnt = 0;
queue<Point> q;
q.push(Point(0, 0));
vector<vector<int> > mark(rows, vector<int>(cols, 0));
int ss = 0;
while (!q.empty()){
Point p = q.front(); q.pop();
if (mark[p.x][p.y]) continue;
mark[p.x][p.y] = 1;
ss++;
if (getsum(p) > threshold) continue;
cnt++;
if (p.x + 1 < rows)
q.push(Point(p.x+1, p.y));
if (p.y + 1 < cols)
q.push(Point(p.x, p.y+1));
}
return cnt;
}
int getsum(Point p){
int sum = 0, x = p.x, y = p.y;
while (x){
sum += x % 10;
x /= 10;
}
while (y){
sum += y % 10;
y /= 10;
}
return sum;
}
};
67. 剪绳子
题目:给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],...,k[m]。请问k[0]xk[1]x...xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
思路:dp[i] = max(dp[i], max(dp[i - j] * j, (i - j) * j));
长度为i时,有两种情况,一种 i - j 是剪成多段,dp[i - j] * j, 一种是 i - j 就一段,(i - j) * j
class Solution {
public:
int cutRope(int number) {
vector<int> dp(number + 1, 0);
dp[1] = 1;
dp[0] = 0;
for (int i = 2; i <= number; i++){
for (int j = 1; j <= i; j++)
dp[i] = max(dp[i], max(dp[i - j] * j, (i - j) * j));
}
return dp[number];
}
};