剑指offer题解(上)

刷完了剑指offer,做一个总结,按照牛客网link的顺序

目录

1.  二维数组的查找

2. 替换空格

3. 从尾到头打印链表

4. 重建二叉树--

5. 用两个栈实现队列

6. 旋转数组的最小数字--

7. 斐波那契数列

8. 跳台阶

9. 变态跳台阶

10. 矩形覆盖

11. 二进制中1的个数--

12. 数值的整数次方

13. 调整数组顺序使奇数位于偶数前面

14. 链表中倒数第k个结点--

15. 反转链表

16. 合并两个排序的链表--

17. 树的子结构--

18. 二叉树的镜像

19. 顺时针打印矩阵--

20. 包含min函数的栈

21. 栈的压入、弹出序列

22. 从上往下打印二叉树

23. 二叉搜索树的后序遍历序列--

24. 二叉树中和为某一值的路径

25. 复杂链表的复制--

26. 二叉搜索树与双向链表--

27.  字符串的排列--

28. 数组中出现次数超过一半的数字--

29. 最小的K个数--

30. 连续子数组的最大和

31. 整数中1出现的次数(从1到n整数中1出现的次数)--

32. 把数组排成最小的数--

33. 丑数--

34. 第一个只出现一次的字符位置

35. 数组中的逆序对--


 

1.  二维数组的查找

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

思路:因此二维数组从左到右递增,从上到下递增,所以从右上角开始,如果要找的这个数num小于右上角的数,就往左走,如果大于就往下走,直到找到或者走到边界了。

class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        int r = array.size();
        if (r == 0) return false;
        int c = array[0].size();
        int cur_x = 0, cur_y = c - 1;
        while (cur_x <= r-1 && cur_y >= 0){
            if (array[cur_x][cur_y] > target){
                cur_y--;
            }
            else if (array[cur_x][cur_y] < target){
                cur_x++;
            }
            else 
                return true;
        }
        return false;
    }
};

2. 替换空格

题目:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

思路:首先这个题就没有说清楚参数的含义,这个length参数并不是字符串str的长度,而是str指向的那块空间所能存储的最大字符串长度。
所以,就先遍历一次,获得空格的数量和字符串str的长度('\0'结尾),然后计算替换后字符串的长度。
然后从后往前,遍历旧字符串,如果当前字符为空格,就弄上02%(注意是反过来写的);如果不是空格,直接写就行了。
最后一个坑,就是要记得在新字符串的结尾写上‘\0’,表示结尾。

class Solution {
public:
    void replaceSpace(char *str,int length) {
        //按照题意,length应该是str所指向内存中可存的最大字符数量。
        int space = 0;
        int oldLen = 0;
        while (str[oldLen] != '\0'){
            if (str[oldLen] == ' ')
                space++;
            oldLen++;
        }
        int newLen = oldLen + 2 * space;
        if (newLen > length)
            return;
        int i = newLen - 1;
        for (int j = oldLen - 1; j >= 0; j--){
            if (str[j] != ' ')
                str[i--] = str[j];
            else {
                str[i--] = '0';
                str[i--] = '2';
                str[i--] = '%';
            }
        }
        str[newLen] = '\0'; // 不要忘记这里的结尾
    }
};

3. 从尾到头打印链表

题目:输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

思路:这道题,额,我就做的很简单了,就直接用一个vector存储每个节点的值,然后导过来,返回就行了。也可以用递归。

class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> res;
        int len = 0;
        ListNode* tmp = head;
        while (tmp != NULL){
            res.push_back(tmp->val);
            tmp = tmp->next;
        }
        vector<int> ans;
        for (int i = res.size()-1; i >= 0; i--)
            ans.push_back(res[i]);
        return ans;
    }
};

4. 重建二叉树--

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

思路:有几个需要知道的点:① 前序遍历中第一个值为根节点 
②在中序遍历中找到这个值的位置pos,pos左边为左子树,右边为右子树
③另外,在前序遍历中,根节点后面的值,先全是左子树的,最后全是右子树的
    具体思路如代码中的解释吧,首先就将中序遍历的值和位置hash一下,方便查找,也可以不做。
    这道题需要get到的点就是第③点,
    首先计算左子树的长度,计算方法就是根节点在中序遍历的位置pos减去当前中序遍历中第一个点的位置vinFirst,这个vinFirst在每次递归的时候都要进行更新。
    preL 和 preR 是当前的 前序遍历和中序遍历 的起始和结尾位置。
    然后左子树就为 [preL + 1, preL + leftSize],当前这个左子树,中序遍历的第一个值不变vinFirst。
    右子树就为 [preL + leftSize + 1, preR],当前这个右子树,中序遍历的第一个值变为pos + 1。
    画画图,就能理解了。

class Solution {
public:
    unordered_map<int, int> vinHash;
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        for (size_t i = 0; i < vin.size(); i++)
            vinHash[vin[i]] = i;
        TreeNode* root = build_tree(pre, 0, pre.size() - 1, 0);
        return root;
    }
    // 前序遍历中第一个值为根节点
    // 在中序遍历中找到这个值的位置pos,pos左边为左子树,右边为右子树
    // 计算左子树的大小 pos - vinFirst,vinFirst记录中序遍历中的第一个点
    // 然后前序遍历就可以分为左右两边了,
        // 左边为 [preL + 1, preL + leftSize],中序遍历的第一个值不变vinFirst
        // 右边为 [preL + leftSize + 1, preR],中序遍历的第一个值变为pos + 1
        // 进行递归
    TreeNode* build_tree(vector<int> &pre, int preL, int preR, int vinFirst){
        if (preL > preR)
            return NULL;
        int value = pre[preL];
        TreeNode* root = new TreeNode(value);
        int pos = vinHash[value];
        int leftSize = pos - vinFirst;
        root->left = build_tree(pre, preL + 1, preL + leftSize, vinFirst);
        root->right = build_tree(pre, preL + leftSize + 1, preR, pos + 1);
        return root;
    }
    //输入样例参考:前序遍历序列{1,2,4,7,3,5,6,8}  中序遍历序列{4,7,2,1,5,3,8,6}
};

5. 用两个栈实现队列

题目:用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

思路:栈1用来push值,栈2用来pop值。当要push时,直接把值push进栈2。当要pop值时,如果栈2不为空,直接pop;否则将栈1的值全部pop,然后按顺序push进栈2。

class Solution
{
public:
    void push(int node) {
        stack1.push(node);
    }
    int pop() {
        if (stack2.empty()){
            while (!stack1.empty()){
                int value = stack1.top();
                stack1.pop();
                stack2.push(value);
            }
        }
        if (stack2.empty())
            return -1;
        int value = stack2.top();
        stack2.pop();
        return value;
    }
private:
    stack<int> stack1;
    stack<int> stack2;
};

6. 旋转数组的最小数字--

题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

思路:看这个吧,有点玄学的一道题吧,好理解,但是实现细节不好弄

class Solution {
public:
    int minNumberInRotateArray(vector<int> nums) {
        if (nums.size() == 0)
            return 0;
        int l = 0, r = nums.size() - 1;
        while (l < r){
            if (nums[l] < nums[r]) return nums[l]; // 一个优化
            int mid = l + (r - l) / 2;
            if (nums[mid] < nums[r])  // 右边为有序,所以最小值在左边(包括当前点)
                r = mid;
            else if (nums[mid] > nums[r])  // 右边为无序,所以最小值在右边
                l = mid + 1;
            else r--; // 最小值可能在左边[2,1,2,2,2] ,也可能在右边[2,2,2,1,2],所以需要r--慢慢找
            // 如果没优化,最差情况O(n),比如[1,2,2,2,2,2,2,2,2,2]
        }
        return nums[l];
    }
};

7. 斐波那契数列

题目:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。n<=39

思路:dp[n] = dp[n - 1] + dp[n - 2]

class Solution {
public:
    int dp[40];
    int Fibonacci(int n) {
        dp[0] = 0;
        dp[1] = 1;
        for (int i = 2; i < 40; i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
};

8. 跳台阶

题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

思路:dp[n] = dp[n - 1] + dp[n - 2]

class Solution {
public:
    int jumpFloor(int number) {
        if (number == 1) return 1;
        if (number == 2) return 2;
        int prepre = 1;  // 前前个
        int pre = 2;     // 前一个
        for (int i = 3; i <= number; i++){
            int temp = prepre + pre;
            prepre = pre;
            pre = temp;
        }
        return pre;
    }
};

9. 变态跳台阶

题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

思路:dp[n] = dp[n-1] + dp[n-2] + ... + dp[1] + dp[0]

class Solution {
public:
    int jumpFloorII(int number) {
        if (number == 1) return 1;
        int *dp = new int[number + 1];
        dp[0] = 1;
        dp[1] = 1;
        for (int i = 2; i <= number; i++){
            dp[i] = 0;
            for (int j = 0; j < i; j++){
                dp[i] += dp[j];
            }
        }
        
        return dp[number];
    }
};

10. 矩形覆盖

题目:我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

思路:好好一想,其实这个就跟跳台阶一样的,dp[n] = dp[n - 1] + dp[n - 2],只不过n为0,1,2时,对应的答案为0,1,2。

dp[n-2]时,只有一种摆法,那就是横着摆;你会问,竖着摆不行吗?不行,竖着摆,回合dp[n-1]时重复,竖着摆的情况在dp[n-1]时就包括了。

class Solution {
public:
    int rectCover(int number) {
        if (number == 0) return 0;
        if (number == 1) return 1;
        if (number == 2) return 2;
        int prepre = 1;
        int pre = 2;
        for (int i = 3; i <= number; i++){
            int temp = pre + prepre;
            prepre = pre;
            pre = temp;
        }
        return pre;
    }
};

11. 二进制中1的个数--

题目:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

思路:n & (n-1) 会去掉最低位的1,所以对n,一直 n = n & (n - 1) ,然后计数就行了。

class Solution {
public:
     int  NumberOf1(int n) {
         int cnt = 0;
         while (n != 0){
             cnt++;
             n = n & (n - 1);
         }
         return cnt;
     }
};

12. 数值的整数次方

题目:给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。保证base和exponent不同时为0

思路:快速幂。有一个坑,就是exponent可能为负数.

class Solution {
public:
    double Power(double base, int exponent) {
        double ans;
        if (exponent < 0){
            ans = quick(base, -exponent);
            ans = 1 / ans;
        }
        else {
            ans = quick(base, exponent);
        }
        return ans;
    }
    double quick(double x, int c){
        double base = x, res = 1;
        while (c){ 
            if (c & 1)
                res = res * base;
            c = c / 2;
            base = base * base;
        }
        return res;
    }
};

13. 调整数组顺序使奇数位于偶数前面

题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

思路:使用两个数组分别存奇数和偶数,然后在合到一起,时间复杂度O(n),空间O(n)。
另外的方法,就是用冒泡排序吧,把偶数都冒到后面去,时间复杂度O(n*n),空间O(1)。

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        
        vector<int> odd, even;
        for (int i = 0; i < array.size(); i++){
            if (array[i] % 2 == 0)
                even.push_back(array[i]);
            else 
                odd.push_back(array[i]);
        }
        
        for (int i = 0; i < odd.size(); i++){
            array[i] = odd[i];
        }
        int len = odd.size();
        for (int i = 0; i < even.size(); i++){
            array[len + i] = even[i];
        }
    }
};

14. 链表中倒数第k个结点--

题目:输入一个链表,输出该链表中倒数第k个结点。

思路:要求最好只遍历一次链表。就先弄一个指针p1,走k次;然后弄一个指针p2,跟p1一起走;当p1走到尾了,这时候p2的位置就是倒数第k个节点。
有一个坑就是链表长度可能比k小。

class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        //要求:只遍历一次链表
        if (pListHead == NULL) 
            return NULL;
        ListNode* p1 = pListHead;
        ListNode* p2 = pListHead;
        for (int i = 1; i <= k - 1; i++){
            p1 = p1->next;
            if (p1 == NULL)
                return NULL;
        }
        while (p1->next != NULL){
            p2 = p2->next;
            p1 = p1->next;
        }
        return p2;
    }
};

15. 反转链表

题目:输入一个链表,反转链表后,输出新链表的表头。

思路:使用递归,递归到尾返回的时候,返回下一个节点,然后把下一个节点的next指向当前节点。

class Solution {
public:
    
    ListNode* ReverseList(ListNode* pHead) {
        if (pHead == NULL) return NULL;
        ListNode* pTail = pHead;
        // 先获得尾部的结点
        while (pTail->next != NULL)
            pTail = pTail->next;
        reverse_(pHead);
        // 反转完后,要把头结点的next指向空
        pHead->next = NULL;
        return pTail;
    }
    ListNode* reverse_(ListNode* pHead){
        if (pHead->next == NULL)
            return pHead;
        
        ListNode* root = reverse_(pHead->next);
        root->next = pHead;
        return pHead;
    }
};

16. 合并两个排序的链表--

题目:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

思路:使用递归。

class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        if (pHead1 == NULL)
            return pHead2;
        if (pHead2 == NULL)
            return pHead1;
        if (pHead1->val < pHead2->val){
            pHead1->next = Merge(pHead1->next, pHead2);
            return pHead1;
        }
        else {
            pHead2->next = Merge(pHead1, pHead2->next);
            return pHead2;
        }
    }
};

17. 树的子结构--

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

思路:就暴力,对每个A的结点,当做根节点和B进行比较,然后有一个相同就是,全部相同就不是。

class Solution {
public:
    bool HasSubtree(TreeNode* pRootA, TreeNode* pRootB)
    {
        //空树不是任意一个树的子结构
        if (pRootA == NULL || pRootB == NULL)
            return false;
        // 递归暴力判断A的每个子树,是否和B是一样的
        return isSubtree(pRootA, pRootB) || 
            HasSubtree(pRootA->left, pRootB) || 
            HasSubtree(pRootA->right, pRootB);
    }
    bool isSubtree(TreeNode* pA, TreeNode* pB){
        if (pB == NULL) return true; //pB的是否为NULL要放在前面,
        //因为只要pB为NULL了,说明pRootB的全部值,pRootA中都有且一样
        if (pA == NULL) return false;
        if (pA->val != pB->val) return false;
        return isSubtree(pA->left, pB->left) &&
            isSubtree(pA->right, pB->right);
    }
};

18. 二叉树的镜像

题目:操作给定的二叉树,将其变换为源二叉树的镜像。

思路:对根节点,左右子树交换,然后再对左右子树进行遍历,再进行交换。

class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if (pRoot == NULL) return;
        //直接调换左右子树,
        TreeNode* temp = pRoot->left;
        pRoot->left = pRoot->right;
        pRoot->right = temp;
        
        //然后对左右子树再递归。。
        Mirror(pRoot->left);
        Mirror(pRoot->right);
    }
};

19. 顺时针打印矩阵--

题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

思路:这个题看起来条件很多,很复杂,但其实你画个矩阵,模拟一下,很简单的。  对每一次循环,对矩形转一圈,
①先从左到右 ②然后从上到下 
③只有当能够下去(m1 + 1 <= m2)时,才再从右往左,
④只有当能够往左(n2 - 1 >= n1)时,才再从下往上
然后一圈转完了,更新矩形的长宽边界

class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > mat) {
        vector<int> res;
        if (mat.size() == 0) return res;
        int m = mat.size();
        int n = mat[0].size();
        int n1 = 0, n2 = n - 1, m1 = 0, m2 = m - 1;
        
        // 其实很简单的,模拟一下,
        // 一次循环,对矩形转一圈
        // 先从左到右,然后从上到下,
        // 只有当能够下去(m1 + 1 <= m2)时,才再从右往左
        // 只有当能够往左(n2 - 1 >= n1)时,才再从下往上
        // 然后一圈转完了,更新矩形的大小
        while (n1 <= n2 && m1 <= m2){
            for (int i = n1; i <= n2; i++) 
                res.push_back(mat[m1][i]);
            
            for (int i = m1 + 1; i <= m2; i++)
                res.push_back(mat[i][n2]);
            
            if (m1 + 1 <= m2){
                for (int i = n2 - 1; i >= n1; i--)
                    res.push_back(mat[m2][i]);
            }
            if (n2 - 1 >= n1){
                for (int i = m2 - 1; i >= m1 + 1; i--)
                    res.push_back(mat[i][n1]);
            }
            
            n1++; n2--;
            m1++; m2--;
        }
        return res;
    }
};

20. 包含min函数的栈

题目:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。

思路:使用栈1 push值时,栈2 push当前存的值中,最小的那个值。当栈1pop时,栈2也pop。

class Solution {
public:
    stack<int> stack_min, stack_val;
    void push(int value) {
        stack_val.push(value);
        if (stack_min.empty())
            stack_min.push(value);
        else {
            int mini = stack_min.top();
            if (mini > value)
                stack_min.push(value);
            else stack_min.push(mini);
        }
    }
    void pop() {
        stack_val.pop();
        stack_min.pop();
    }
    int top() {
        return stack_val.top();
    }
    int min() {
        return stack_min.top();
    }
};

21. 栈的压入、弹出序列

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

思路:就模拟吧。使用一个栈sta存pushV中的值,当sta顶的值和popV的值一样了,sta就pop,直到sta栈顶的值和popV值不一样。

class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        stack<int> sta;
        int curPos = 0, len = pushV.size();
        for (int i = 0; i < len; i++){
            sta.push(pushV[i]);
            while (!sta.empty() && curPos < len && popV[curPos] == sta.top()){
                sta.pop();
                curPos++;
            }
        }
        return curPos == len;
    }
};

22. 从上往下打印二叉树

题目:从上往下打印出二叉树的每个节点,同层节点从左至右打印。

思路:使用一个去队列维护节点,相当于bfs。

class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        vector<int> res;
        queue<TreeNode *> q;
        q.push(root);
        while (!q.empty()){
            TreeNode *cur = q.front();
            q.pop();
            if (cur == NULL)
                continue;
            res.push_back(cur->val);
            q.push(cur->left);
            q.push(cur->right);
        }
        return res;
    }
};

23. 二叉搜索树的后序遍历序列--

题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

思路:对于一个二叉搜索树的后序遍历序列,最后一个值为根节点。利用这个特点,将序列分为左、右两半,若该序列为二叉搜索树的后序遍历,则左边的值应都小于最后的值,右边的值都大于最后的值;然后不断递归。
另外一个坑是,数据为空时,它算NO。。

class Solution {
public:
    bool VerifySquenceOfBST(vector<int> seq) {
        // 数组为空时它算NO。。。。
        if (seq.size() == 0) return false;
        
        int pos, len = seq.size();
        vector<int> seqLeft, seqRight;
        for (int i = 0; i < len; i++){
            if (seq[i] >= seq[len - 1]){
                pos = i;
                break;
            }
            seqLeft.push_back(seq[i]);
        }
        for (int i = pos; i < len - 1; i++){
            if (seq[i] < seq[len - 1]){
                return false;
            }
            seqRight.push_back(seq[i]);
        }
        bool left = true, right = true;
        if (seqLeft.size() > 1)
            left = VerifySquenceOfBST(seqLeft);
        if (seqRight.size() > 1)
            right = VerifySquenceOfBST(seqRight);
            
        return left && right;
    }
};

24. 二叉树中和为某一值的路径

题目:输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)

思路:就遍历树就好了,然后计算当前路径的值之和,如果值相等了,还要判断当前结点是不是叶节点。

class Solution {
public:
    vector<vector<int> > ans;
    vector<int> path;
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        dfs(root, 0, expectNumber);
        return ans;
    }
    void dfs(TreeNode* root, int sum, int expect){
        if (root == NULL) return;
        sum = sum + root->val;
        path.push_back(root->val);
        if (sum == expect){
            if (root->left == NULL && root->right == NULL)
                ans.push_back(path);
        }
        else if (sum < expect){
            dfs(root->left, sum, expect);
            dfs(root->right, sum, expect);
        }
        path.pop_back();
    }
};

25. 复杂链表的复制--

题目:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

思路:这个复制其实还真有点复杂。首先就是对当前链表扩展,比如链表尾1->2->3-,扩展为1->1->2->2->3->3;然后对新复制的节点,复制随机指针;最后将链表分割;

class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead)
    {
        if (pHead == NULL) return pHead;
        RandomListNode* ph = pHead;
        RandomListNode* pNew = NULL;
        // 复制双份
        while (ph != NULL){
            pNew = new RandomListNode(ph->label);
            pNew->next = ph->next;
            ph->next = pNew;
            ph = pNew->next;
        }
        // 复制random指向
        ph = pHead;
        while (ph != NULL){
            pNew = ph->next; 
            if (ph->random){
                pNew->random = ph->random->next;
                // 每一个复制链表的结点都在源节点的后面,新的random结点也在原来random的后面
            }
            ph = pNew->next;
        }
        // 拆分
        ph = pHead;
        RandomListNode* pNewHead = ph->next;
        pNew = ph->next;
        while (ph != NULL){
            ph->next = pNew->next;
            ph = ph->next;
            if (pNew->next){
                pNew->next = pNew->next->next;
                pNew = pNew->next;
            }
        }
        return pNewHead;
    }
};

26. 二叉搜索树与双向链表--

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

思路:因为二叉搜索树的中序遍历就是一个排好序的序列。所以对树进行中序遍历,然后在遍历的过程中,对当前节点和前一个节点进行互指。

class Solution {
public:
    void inorder(TreeNode* root, TreeNode* &pre, TreeNode* &head)
    {
        if (root == NULL) return;
        inorder(root->left, pre, head);
        if (head == NULL) head = root;
        root->left = pre;
        if (pre)
            (pre)->right = root;
        pre = root;
        inorder(root->right, pre, head);
    }
    TreeNode* Convert(TreeNode* pRoot)
    {
        if (pRoot == NULL) return pRoot;
        TreeNode* pre = NULL, *head = NULL; 
        inorder(pRoot, pre, head);
        pre->right = NULL; // 最终的pre,指向的是尾节点,尾节点的右边指向NULL
        return head;
    }
};

27.  字符串的排列--

题目:输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。  输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

思路:就是求字符的全排列。 
将长度为n的string,第一个字符依次与后面的字符替换,然后递归剩下n-1的string,其中当后面的字符与前面的字符相同时不替换,避免重复字符造成重复计算。

class Solution {
public:
    vector<string> ans;
    void dfs(string &s, int pos){
        if (pos == s.length()){
            ans.push_back(s);
            return;
        }
        for (int i = pos; i < s.length(); i++){
            if (s[i] == s[pos] && i != pos) continue;
            swap(s, i, pos);
            dfs(s, pos + 1);
            swap(s, i, pos);
        }
    }
    void swap(string &s, int i, int j){
        char tmp = s[i];
        s[i] = s[j];
        s[j] = tmp;
    }
    vector<string> Permutation(string str) {
        if (str.length() == 0) return ans;
        dfs(str, 0);
        sort(ans.begin(), ans.end());
        return ans;
    }
}; 

28. 数组中出现次数超过一半的数字--

题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

思路:最直接就是直接用hash_map存每个数字的次数,然后判断最多次数的那个是否超过一半。
另一个巧妙的思路,就是多数投票问题,用一个cnt,记录一个数出现的次数,从前往后遍历,如果cnt为0,令majority为当前的数,cnt设为1。然后往后遍历,如果有数字跟majority相同,则cnt++,否则cnt--。到最后,如果cnt>0,则说明最后的这个majority出现的次数可能超过一半。重新遍历数组,计算这个数的个数,然后判断。

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        // hash map
        /*
        unordered_map<int, int> cnt;
        int len = numbers.size();
        for (int i = 0; i < len; i++){
            int num = numbers[i];
            if (cnt.find(num) == cnt.end())
                cnt[num] = 1;
            else cnt[num]++;
            if (cnt[num] > len / 2)
                return num;
        }
        */
        int len = numbers.size();
        if (len == 0) return 0;
        int major = numbers[0], cnt = 1;
        for (int i = 1; i < len; i++){
            if (numbers[i] == major)
                cnt++;
            else cnt--;
            if (cnt == 0){
                major = numbers[i];
                cnt = 1;
            }
        }
        cnt = 0;
        for (int i = 0; i < len; i++)
            if (major == numbers[i]) cnt++;
        if (cnt > len / 2) 
            return major;
        else return 0;
    }
};

29. 最小的K个数--

题目:输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

思路:使用快速排序找到第k个数,然后在遍历数组,比这个数小的,就是最小的k个数。
另外,因为只要k个,所以ans大小为k时,就能结束了。否则会错误,因为有可能<=第k个数的个数大于k个,即第k个数有多个。

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> ans;
        // 这里的k就是个坑。。当k大于数组大小或小于0时,返回空
        if (input.size() < k || k <= 0) return ans;
        quickSort_findKth(input, k, 0, input.size() - 1);
        for (int i = 0; i < k; i++){
            ans.push_back(input[i]);
        }
        return ans;
    }
    void quickSort_findKth(vector<int> &input, int k, int l, int r){
        if (l >= r) return;
        int piv = input[l];
        int low = l, high = r;
        while (low < high){
            while (input[high] >= piv && low < high) high--;
            input[low] = input[high];
            while (input[low] <= piv && low < high) low++;
            input[high] = input[low];
        }
        input[low] = piv;
        if (low > k - 1)
            quickSort_findKth(input, k, l, low - 1);
        else if (low < k - 1)
            quickSort_findKth(input, k, low + 1, r);
        else
            return;
        
    }
};

30. 连续子数组的最大和

题目:HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

思路:sum为0时,将sum重置为当前的数,继续相加

class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
        int len = array.size();
        if (len == 0) return 0;
        int sum = 0, maxi = array[0];
        for (int i = 0; i < len; i++){
            if (sum <= 0)
                sum = array[i];
            else sum += array[i];
            if (sum > maxi)
                maxi = sum;
        }
        return maxi;
    }
};

31. 整数中1出现的次数(从1到n整数中1出现的次数)--

题目:求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

思路:分别计算个位、十位、百位、。。。出现1的次数,然后相加。
        举例说明,令n = 25?45, 百位上的数字?有0,1,大于等于2,三种情况
         0: n = 25045, 则 (25045/100)/10=25, 百位以上,即千位和万位,有25种情况,为{0,..24};百位为1,最低两位为0~99,有100种,所以百位为1出现的次数为 25 * 100
         1: n = 25145, 同理, (25145/100)/10=25, 百位为1, 最低两位为0~45,有46个, 即 25 * 100 + 46
         >=2: n = 25245, 同理, (25245/100)/10=25, 因为百位大于1, 所以可以弄满100个,即 25 * 100 + 100 = (25 + 1)* 100    // 每一个base计算的时候,是会有重复出现同一个数字的,
// 但是,题目要求的是1的出现次数,这是没问题的
// 因为他是按照 位的数量增加的
// 比如 n = 30
// base=1时, 有 01,11,21, 有3个
// base=10时,有 10,11,12,13,14,15,16,17,18,19, 有10个
// 答案为13, 这里面出现了两次11
// 但是第一次出现的11,是增加的个位上的1,
// 第二次出现的11,是增加的十位上的1
// 所以没有重复增加

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n)
    {
        int ans = 0;        
        for (int base = 1; base <= n; base *= 10){
            int a = n / base;
            
            if (a % 10 == 0)
                ans += a / 10 * base;
            else if (a % 10 == 1)
                ans += a / 10 * base + ((n % base) + 1);
            else if (a % 10 >= 2)
                ans += (a / 10 + 1) * base;
            
            // 简写如下
            //ans += (a + 8) / 10 * base + (a % 10 == 1 ? b + 1 : 0);
            // a + 8 就是只有当 大于等于2的时候,可以进一个位
        }
        return ans;
    }
};

32. 把数组排成最小的数--

题目:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

思路:将每个整数转为字符串,然后对于s1和s2,设定大小规则,当s1+s2<s2+s1时,s1<s2。这样进行拼接时,s1+s2+..+sn就会是最小的。

class Solution {
public:
    static int cmp(string s1, string s2)
    {
        return (s1 + s2 < s2 + s1);
    }
    string PrintMinNumber(vector<int> numbers) {
        
        vector<string> s;
        for (int i = 0; i < numbers.size(); i++){
            string tmp = to_string(numbers[i]);
            s.push_back(tmp);
        }
        sort(s.begin(), s.end(), cmp);
        
        string ans = "";
        for (int i = 0; i < numbers.size(); i++){
            ans += s[i];
        }
        return ans;
    }
};

33. 丑数--

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

思路:使用三个指针q2,q3,q5分别指向2/3/5这三个乘数的位置,然后第i个数,取ans[q2]*2,ans[q3]*3,ans[q5]*5中的最小值。

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
    
        int *ans = new int[index];
        ans[0] = 1;
        int q2 = 0, q3 = 0, q5 = 0;
        for (int i = 1; i < index; i++){
            
            ans[i] = min(ans[q2] * 2, 
                        min(ans[q3] * 3, ans[q5] * 5));
            if (ans[i] == ans[q2] * 2) q2++;
            if (ans[i] == ans[q3] * 3) q3++;
            if (ans[i] == ans[q5] * 5) q5++;
            
        }
            
        return ans[index - 1];
        
    }
};

34. 第一个只出现一次的字符位置

题目:在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).

思路:使用一个数组记录一个字符出现的次数。或者空间更省的话,用bitset存。

class Solution {
public:
    int get_index(char c){
        int dex = -1;
        if (c >= 'a' && c <= 'z')
            dex = c - 'a';
        if (c >= 'A' && c <= 'Z')
            dex = c - 'A' + 26;
        return dex;
    }
    int FirstNotRepeatingChar(string str) {
        bitset<52> flag1, flag2;
        int dex;
        for (int i = 0; i < str.length(); i++){
            dex = get_index(str[i]);
            if (flag1[dex] == 0)
                flag1[dex] = 1;
            else 
                flag2[dex] = 1;
        }
        for (int i = 0; i < str.length(); i++){
            dex = get_index(str[i]);
            if (flag1[dex] == 1 && flag2[dex] == 0)
                return i;
        }
        return -1;
    }
};

35. 数组中的逆序对--

题目:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

思路:归并排序求逆序对。其实用数状数组也能求逆序对,不过我早忘记了。

class Solution {
public:
    
    int InversePairs(vector<int> data) {
        int len = data.size();
        int *tmp = new int[len];
        long long ans = 0;
        
        MergeSort(data, 0, len - 1, tmp, ans);
        return (int) ans;
    }
    void MergeSort(vector<int>&data, int l, int r, int* tmp, long long &ans){
        if (l < r){
            int mid = (l + r) >> 1;
            MergeSort(data, l, mid, tmp, ans);
            MergeSort(data, mid + 1, r, tmp, ans);
            merge(data, l, mid, r, tmp, ans);
        }
    }
    void merge(vector<int> &data, int l, int mid, int r, int* tmp, long long& ans){
        int i = l, j = mid + 1;
        int k = l;
        while (i <= mid && j <= r){
            if (data[i] > data[j]){
                ans = (ans + mid - i + 1) % 1000000007;
                tmp[k++] = data[j++];
            }
            else tmp[k++] = data[i++];
        }
        while (i <= mid) tmp[k++] = data[i++];
        while (j <= r) tmp[k++] = data[j++];
        for (i = l; i <= r; i++)
            data[i] = tmp[i];
    }
};

 

剑指offer题解(下)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值