剑指offer题解(下)

接着剑指offer题解(上)

目录

36. 两个链表的第一个公共结点--

37. 数字在排序数组中出现的次数

38. 二叉树的深度

39. 平衡二叉树--

40. 数组中只出现一次的数字--

41. 和为S的连续正数序列

42. 和为S的两个数字

43. 左旋转字符串--

44. 翻转单词顺序列--

45. 扑克牌顺子

46. 孩子们的游戏(圆圈中最后剩下的数)--

47. 求1+2+3+...+n--

48. 不用加减乘除做加法--

49. 把字符串转换成整数

50. 数组中重复的数字--

51. 构建乘积数组---

52. 正则表达式匹配---

53. 表示数值的字符串--

54. 字符流中第一个不重复的字符--

55. 链表中环的入口结点---

56. 删除链表中重复的结点

57. 二叉树的下一个结点--

58. 对称的二叉树

59. 按之字形顺序打印二叉树--

60. 把二叉树打印成多行

61. 序列化二叉树--

62. 二叉搜索树的第k个结点--

63. 数据流中的中位数---

64. 滑动窗口的最大值---

65. 矩阵中的路径

66. 机器人的运动范围

67. 剪绳子


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⎣⎡​asa​bfd​cce​ese​⎦⎤​  矩阵中包含一条字符串"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];
    }
};

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值