牛客 剑指offer_编程题 详细题解 (已完结)

文章目录

剑指offer_编程题

【1.二维数组中的查找】

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

【解题思路】
对于每一行使用二分查找,可用lower_bound函数

【AC代码】

class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        for(auto& v : array) {
            auto temp = lower_bound(v.begin(), v.end(), target);
            if(temp == v.end()) continue;  //注意要判断是否为end,不然会越界
            if(*temp == target) return true;
        }
        return false;
    }
};

【2.替换空格】

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

【解题思路】
创建一个副本,遍历源字符串,如果是空格将其替换为"%20",否则将其等于原字符

或者用Java中的自带函数replace

【AC代码】

C++

class Solution {
public:
	void replaceSpace(char *str,int length) {
        if(str == NULL) return;
        char temp[110];
        int cnt = -1;
        for(int i = 0; i < length; ++i) {
            if(str[i] == ' ') {
                temp[++cnt] = '%';
                temp[++cnt] = '2';
                temp[++cnt] = '0';
            }
            else temp[++cnt] = str[i];
        }
        temp[++cnt] = '\0';  //别忘了‘\0’
        strcpy(str, temp);
	}
};

Java

public class Solution {
    public String replaceSpace(StringBuffer str) {
    	return str.toString().replace(" ", "%20");
    }
}

【3.从尾到头打印链表】

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

【解题思路】
顺序遍历链表,用vector存储其值,最后reverse

【AC代码】

/**
*  struct ListNode {
*        int val;
*        struct ListNode *next;
*        ListNode(int x) :
*              val(x), next(NULL) {
*        }
*  };
*/
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> v;
        if(head == NULL) return v;
        while(head != NULL) {
            v.push_back(head -> val);
            head = head -> next;
        }
        reverse(v.begin(), v.end());
        return v;
    }
};

【4.重建二叉树】

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

【解题思路】
模拟人工重建二叉树方法即可

【AC代码】

/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        int l = pre.size();
        return rebuild(pre, vin, 0, l - 1, 0, l - 1);
    }
    TreeNode* rebuild(vector<int>& pre, vector<int>& vin, int l1, int r1, int l2, int r2) {
    //l1,r1表示该子树在pre中的下标,l2,r2表示在vin中的下标
        if(l1 > r1 || l2 > r2) return NULL;
        int index = -1;
        for(int i = l2; i <= r2; ++i) {
            if(vin[i] == pre[l1]) {
                index = i; //找到根节点
                break;
            }
        }
        TreeNode* root = new TreeNode(pre[l1]);  //新建根节点
        root -> left = rebuild(pre, vin, l1 + 1, l1 + index - l2, l2, index - 1);
        root -> right = rebuild(pre, vin, l1 + index - l2 + 1, r1, index + 1, r2);
        return root;
    }
};

【5.用两个栈实现队列】

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

【解题思路】
栈和队列的不同在于pop顺序不同,push顺序相同
第一个栈保存所有的入队元素,在pop时第一个栈所有元素出栈,将所有元素保存至栈二,此时栈二栈顶元素即为队头元素,将其出栈,再将剩余元素全部存入栈一,保持其顺序不变

【AC代码】

class Solution {
public:
    void push(int node) {
        stack1.push(node);
    }
    int pop() {
        while(!stack1.empty()) {
            stack2.push(stack1.top());
            stack1.pop();
        }
        int u = stack2.top();
        stack2.pop();
        while(!stack2.empty()) {
            stack1.push(stack2.top());
            stack2.pop();
        }
        return u;
    }

private:
    stack<int> stack1;
    stack<int> stack2;
};

【6.旋转数组的最小数字】

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

【解题思路】
法一:暴力遍历加上一丝剪枝
法二:先sort然后返回开头
法三:二分查找

【AC代码】

暴力

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        if(rotateArray.size() == 0) return 0;
        int pre = -1;
        for(auto& it : rotateArray) {
            if(it < pre) return it;
            pre = it;
        }
        return rotateArray[0];
    }
};

Sort

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        if(rotateArray.size() == 0) return 0;
        sort(rotateArray.begin(), rotateArray.end());
        return rotateArray[0];
    }
};

二分法

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        if(rotateArray.size() == 0) return 0;
        int left = 0, right = rotateArray.size() - 1;
        int ans = rotateArray[left];
        while(left <= right) {
            int mid = (left + right) >> 1;
            ans = min(ans, rotateArray[mid]);
            if(rotateArray[mid] >= rotateArray[left]) {
                left = mid + 1;
                ans = min(ans, rotateArray[mid + 1]);
            }
            if(rotateArray[mid] <= rotateArray[right]) {
                right = mid - 1;
                ans = min(ans, rotateArray[mid - 1]);
            }
        }
        return ans;
    }
};

【7.斐波那契数列】

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

【解题思路】
F(n) = F(n - 1) + F(n - 2)
采用记忆化递归求解,注意结束条件

【AC代码】

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

【8.跳台阶】

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

【解题思路】
动态规划,考虑假设当前已经跳了n阶台阶,那么可以从n - 1跳一阶到达n,也可以从n - 2跳两阶到达n,故递推式为斐波那契递推式

【AC代码】

class Solution {
public:
    int F[100];
    int jumpFloor(int number) {
        if(number == 0 ||number == 1) return 1;
        if(F[number]) return F[number];
        return F[number] = jumpFloor(number - 1) + jumpFloor(number - 2);
    }
};

【9.变态跳台阶】

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

【解题思路】
和上一题一样,考虑当前跳到了n阶台阶,可以从0 - (n - 1)分别跳(n) -(1)次,所以有
F(n) = F(0) + F(1) + F(2) + … + F(n - 1)
F(n + 1) = F(0) + F(1) + F(2) + … + F(n)
得到F(n + 1) = 2 * F(n)
得到F(n) = 2(n-1)

【AC代码】

class Solution {
public:
    int jumpFloorII(int number) {
        if(number == 0 ||number == 1) return 1;
        return pow(2, number - 1);
    }
};

【10.矩形覆盖】

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

【解题思路】
还是斐波那契数列,依旧是动态规划,假设当前已经覆盖好了2 * n的矩形,那么现在的状态可以由2 * (n - 1)铺一块2 * 1,也可以由2 * (n - 2)铺两块2 * 1得来,所以F[n] = F[n - 1] + F[n - 2]

这题有坑的地方是0的时候答案是0

【AC代码】

class Solution {
public:
    int F[100];
    int rectCover(int number) {
        if(number == 0) return 0;
        return solve(number);
    }
    int solve(int n) {
        if(n == 0 || n == 1) return 1;
        if(F[n]) return F[n];
        return F[n] = solve(n - 1) + solve(n - 2);
    }
};

【11.二进制中1的个数】

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

【解题思路】
法一:Java将int转成二进制字符后统计1的个数
法二:调用Java中的>>>运算符
法三:找规律

法三:
将n表示为二进制后,若其不为0,那么至少含有一个1,那么n - 1就会将n中最右边的1变成0,然后将从右至该位的所有0变为1,此时n & (n - 1)就可以将该位变为0

例如:
n = 12 = 1100
n - 1 = 11 = 1011
n = n & (n - 1) = 1000 = 8
n - 1 = 7 = 0111
n & (n - 1) = 0000 = 0

【AC代码】

暴力

public class Solution {
    public int NumberOf1(int n) {
        char[] s = Integer.toBinaryString(n).toCharArray();
        int len = s.length, ans = 0;
        for(int i = 0; i < len; ++i) {
            if(s[i] == '1') ++ans;
        }
        return ans;
    }
};

>>>

public class Solution {
    public int NumberOf1(int n) {
        int ans = 0;
        while(n != 0) {
            ans += (n & 1);
            n >>>= 1;
        }
        return ans;
    }
};

规律

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

【12.数值的整数次方】

【题目描述】
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

保证base和exponent不同时为0

【解题思路】
考虑用快速幂,如果不理解快速幂可以看下面这张图

在这里插入图片描述
注意指数可能是负数

【AC代码】

class Solution {
public:
    double Power(double base, int exponent) {
        if(base == 0) return 0.0;
        double res = 1.0;
        int n = abs(exponent);
        while(n) {
            if(n & 1) res *= base;
            base *= base;
            n >>= 1;
        }
        return exponent > 0 ? res : 1 / res;
    }
};

【13.调整数组顺序使其位于偶数前面】

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

【解题思路】
法一:创立一个临时数组,遍历array两次,第一次找奇数,第二次找偶数
法二:借鉴插入排序,每遇到一个奇数就将其与前面的偶数交换

法一牺牲空间,节省时间,法二节省空间,消耗时间

【AC代码】

创建临时数组

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        vector<int> temp;
        for(auto& it : array) {
            if(it & 1) temp.push_back(it);
        }
        for(auto& it : array) {
            if(it & 1) continue;
            temp.push_back(it);
        }
        array = temp;
    }
};

借鉴插入排序

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        int l = array.size();
        for(int i = 0; i < l; ++i) {
            if(array[i] & 1) {
                int j = i;
                while(j > 0 && (array[j - 1] & 1) == 0) {
                    swap(array[j], array[j - 1]);
                    --j;
                }
            }
        }
    }
};

【14.链表倒数第k个节点】

【题目描述】
输入一个链表,输出该链表中倒数第k个结点。

【解题思路】
可以用vector或stack存放所有的链表节点,或者先求得链表总长度,重新遍历

【AC代码】

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        int cnt = 1;
        ListNode* pHead = pListHead;
        while(pHead != NULL) {
            pHead = pHead -> next;
            ++cnt;
        }
        if(k >= cnt) return NULL;
        for(int i = 1; i < cnt - k; ++i) pListHead = pListHead -> next;
        return pListHead;
    }
};

【15.反转链表】

【题目描述】
输入一个链表,反转链表后,输出新链表的表头。

【解题思路】
模拟即可

可以画个图模拟一下

【AC代码】

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        if(pHead == NULL || pHead -> next == NULL) return pHead;
        ListNode* pre = pHead, *cur = pre -> next;
        while(cur != NULL) {
            ListNode* temp = cur -> next;
            cur -> next = pre;
            pre = cur;
            cur = temp;
        }
        pHead -> next = NULL;
        return pre;
    }
};

【16.合并两个有序链表】

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

【解题思路】
采用递归形式,如果当前链表1的节点值大于链表2,就让新建节点指向链表2,否则指向链表1

【AC代码】

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
        if(pHead1 == NULL) return pHead2;
        if(pHead2 == NULL) return pHead1;
        ListNode* Head = new ListNode(0);
        if(pHead2 -> val < pHead1 -> val) {
            Head -> val = pHead2 -> val;
            Head -> next = Merge(pHead1, pHead2 -> next);
        }
        else {
            Head -> val = pHead1 -> val;
            Head -> next = Merge(pHead1 -> next, pHead2);
        }
        return Head;
    }
};

【17.树的子结构】

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

【解题思路】
将母树的每一个节点与子树根节点比较,如果相同,递归比较左右子树,否则将母树的左右节点与其对比

【AC代码】

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
        if(pRoot1 == NULL || pRoot2 == NULL) return false;
        return (judgesubtree(pRoot1, pRoot2) || HasSubtree(pRoot1 -> left, pRoot2) || HasSubtree(pRoot1 -> right, pRoot2));  //将母树的没一个节点与其对比
    }
    bool judgesubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
        if(pRoot2 == NULL) return true;  //子树已经遍历完了,说明该子树是母树的一个子结构
        if(pRoot1 == NULL || pRoot1 -> val != pRoot2 -> val) return false; //如果母树遍历完了或者节点值不相同,显然不一样
        return (judgesubtree(pRoot1 -> left, pRoot2 -> left) && judgesubtree(pRoot1 -> right, pRoot2 -> right));  //递归比较左右子树
    }
};

【17.二叉树的镜像】

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

【解题思路】
反转二叉树,直接将每一个节点的左右子树交换即可

【AC代码】

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
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);
    }
};

【18.顺时针打印矩阵】

【题目描述】
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下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.

【解题思路】
直接模拟即可

【AC代码】

class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
        vector<int> v;
        int row = matrix.size() - 1;
        int col = matrix[0].size() - 1;
        if(col < 0 || row < 0) return v;
        int top = 0, bottom = row, left = 0, right = col;
        while(top <= bottom && left <= right) {
            for(int i = left; i <= right; ++i) {
                v.push_back(matrix[top][i]);
            }
            for(int i = top + 1; i <= bottom; ++i) {
                v.push_back(matrix[i][right]);
            }
            if(bottom == top || right == left) break;  //否则会重复,比如1 2 3 4 5
            for(int i = right - 1; i >= left; --i) {
                v.push_back(matrix[bottom][i]);
            }
            for(int i = bottom - 1; i >= top + 1; --i) {
                v.push_back(matrix[i][left]);
            }
            ++top, --bottom, ++left, --right;
        }
        return v;
    }
};

【19.包含min的栈】

【题目描述】
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。

【解题思路】
定义两个栈,第一个存放数据,第二个存放最小值

当 push x 的时候,如果xbi栈二顶部值小,将其压入栈中,保证栈二的单调性

【AC代码】

class Solution {
public:
    void push(int value) {
        s1.push(value);
        if(s2.empty() || value <= s2.top()) {
            s2.push(value);
        }
    }
    void pop() {
        if(s1.empty()) return;
        if(s1.top() == s2.top()) {
            s1.pop();
            s2.pop();
        }
        else s1.pop();
    }
    int top() {
        if(s1.empty()) return 0;
        return s1.top();
    }
    int min() {
        if(s2.empty()) return 0;
        return s2.top();
    }
private:
    stack<int> s1;
    stack<int> s2;
};

【20.栈的压入、弹出序列】

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

【解题思路】
用一个栈来模拟,按顺序将pushV中的元素压入栈中,当其顶端元素与popV中元素相同时将其出栈

【AC代码】

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

【21.从上向下打印二叉树】

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

【解题思路】
用队列模拟其二叉树广度遍历即可

【AC代码】

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        vector<int> v;
        queue<TreeNode*> que;
        que.push(root);
        while(!que.empty()) {
            TreeNode* u = que.front();
            que.pop();
            if(u == NULL) continue;
            v.push_back(u -> val);
            que.push(u -> left);
            que.push(u -> right);
        }
        return v;
    }
};

【22.二叉搜索树的后序遍历序列】

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

【解题思路】
二叉搜索树的特点是左子树比根小,右子树比根大,则其后序遍历左子树比根小,右子树比根大,我们可以根据左子树比根小找到右子树,判断右子树中的所有节点是否都比根大即可,对于每一颗子树均满足,故可以递归处理

【AC代码】

class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        int l = sequence.size();
        if(!l) return false;
        return judge(sequence, 0, l - 1);
    }
    bool judge(vector<int>& v, int left, int right) {
        if(left >= right) return true;
        int index = left;
        while(index <= right && v[index] < v[right]) ++index;  //比根小的都是左子树
        for(int i = index; i < right; ++i) {
            if(v[i] < v[right]) return false;  //右子树中若有比根小的则返回false
        }
        return judge(v, left, index - 1) && judge(v, index + 1, right - 1);  //递归处理左子树和右子树
    }
};

【23.二叉树中和为某一值的路径】

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

【解题思路】
递归暴力遍历所有路径,因为值有正有负,所以所有路径必须递归到空

【AC代码】

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector<vector<int> > v;
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        vector<int> res;
        Find(res, root, expectNumber);
        return v;
    }
    void Find(vector<int> res, TreeNode* root, int target) {
        if(root == NULL) return;
        res.push_back(root -> val);
        if(target == root -> val && root -> left == NULL && root -> right == NULL) {
            v.push_back(res);
            return;
        }
        Find(res, root -> left, target - root -> val);
        Find(res, root -> right, target - root -> val);
    }
};

【24.复杂链表的复制】

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

【解题思路】
一开始以为random相当于一个链表的头结点,后来才发现random只有一个节点,直接复制就好

【AC代码】

/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
*/
class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead) {
        if(pHead == NULL) return NULL;
        RandomListNode* head = new RandomListNode(pHead -> label);
        if(pHead -> random != NULL) head -> random = new RandomListNode(pHead -> random -> label);
        head -> next = Clone(pHead -> next);
        return head;
    }
};

【25.二叉搜索树与双向链表】

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

【解题思路】
二叉搜索树的中序遍历结果即为排序好的链表,用vector存放中序遍历结果后将其left,right改装成双向链表的pre和next即可

【AC代码】

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector<TreeNode*> v;
    TreeNode* Convert(TreeNode* pRootOfTree) {
        if(pRootOfTree == NULL) return NULL;
        inergodic(pRootOfTree);
        int l = v.size();
        v[0] -> left = NULL;
        for(int i = 1; i < l; ++i) {
            v[i - 1] -> right = v[i];
            v[i] -> left = v[i - 1];
        }
        v[l - 1] -> right = NULL;
        return v[0];
    }
    void inergodic(TreeNode* root) {
        if(root == NULL) return;
        inergodic(root -> left);
        v.push_back(root);
        inergodic(root -> right);
    }
};

【26.字符串的排列】

【题目描述】
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

输入描述:
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

【解题思路】
STL自带全排列函数next_permutation和pre_permutation,不了解的可以自行搜索

【AC代码】

class Solution {
public:
    vector<string> Permutation(string str) {
        vector<string> v;
        if(str == "") return v;
        sort(str.begin(), str.end());
        do {
            v.push_back(str);
        } while(next_permutation(str.begin(), str.end()));
        return v;
    }
};

【27.数组中出现次数超过一半的数字】

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

【解题思路】
map记录每个数字出现的次数

【AC代码】

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        map<int, int> cnt;
        int l = numbers.size();
        for(auto& it : numbers) {
            ++cnt[it];
            if(cnt[it] > l / 2) return it;
        }
        return 0;
    }
};

【28.最小的k个数】

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

【解题思路】
办法很多,这里用优先队列

【AC代码】

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> v;
        int l = input.size();
        if(l < k) return v;
        priority_queue<int, vector<int>, greater<int> > que;
        for(auto& it : input) {
            que.push(it);
        }
        for(int i = 1; i <= k; ++i) {
            v.push_back(que.top());
            que.pop();
        }
        return v;
    }
};

【29.连续数组的最大和】

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

【解题思路】
考虑从头开始记录连续区间和sum,当sum < 0时该连续区间不可能对后面的区间产生贡献,故将sum归0,答案取所有sum中的最大值

注意数组全为负的情况,这时返回最大的负数

【AC代码】

class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
        int sum = 0, ans = 0, Max = -0x3f3f3f3f;
        for(auto& it : array) {
            sum += it;
            Max = max(Max, it);
            if(sum <= 0) sum = 0;
            ans = max(ans, sum);
        }
        return ans ? ans : Max;
    }
};

【30.整数中1出现的次数】

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

【解题思路】
有兴趣可以找找规律,但是暴力其实就可以了

【AC代码】

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

【31.把数组排成最小的数】

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

【解题思路】
重载<运算符对字符数组排序

【AC代码】

class Solution {
public:
    string PrintMinNumber(vector<int> numbers) {
        sort(numbers.begin(), numbers.end(), cmp);
        string s = "";
        for(auto& it : numbers) {
            s += to_string(it);
        }
        return s;
    }
    static bool cmp(int x, int y) {
        string a = to_string(x);
        string b = to_string(y);
        return a + b < b + a;
    }
};

【32.丑数】

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

【解题思路】
定义一个队列和三个指针,队列用于存放所有的丑数,三个指针分别保存最后面2的倍数,3的倍数和5的倍数,不断将三个指针乘以2,3,5后的最小值加入队列直到队列大小为n

【AC代码】

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        vector<int> ugly;
        ugly.push_back(1);
        int a = 0, b = 0, c = 0;
        while(ugly.size() < index) {
            int temp = min(ugly[a] * 2, min(ugly[b] * 3, ugly[c] * 5));
            if(temp == ugly[a] * 2) ++a;
            if(temp == ugly[b] * 3) ++b;
            if(temp == ugly[c] * 5) ++c;
            ugly.push_back(temp);
        }
        return ugly[index - 1];
    }
};

【33.第一个只出现一次的字符】

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

【解题思路】
同样用map记录字符出现的次数,遍历字符串找到第一个次数为1的字符

【AC代码】

class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        map<char, int> mp;
        for(int i = 0; str[i] != '\0'; ++i) {
            ++mp[str[i]];
        }
        for(int i = 0; str[i] != '\0'; ++i) {
            if(mp[str[i]] == 1) return i;
        }
        return -1;
    }
};

【34.数组中的逆序对】

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

数据范围:

对于%50的数据,size<=10^4

对于%75的数据,size<=10^5

对于%100的数据,size<=2*10^5

【解题思路】
可以用归并排徐的思想将复杂度降为O(nlogn)

因为归并排序中左右均有序,比如2 4 5 6 8 1 2 3 5 5

【AC代码】

class Solution {
public:
    const int mod = 1000000007;
    int InversePairs(vector<int> data) {
        return MergeSort(data, 0, data.size() - 1);
    }
    int MergeSort(vector<int>& v, int l, int r) {
        if(l >= r) return 0;
        int mid = (l + r) >> 1, res = 0;
        res = (res + MergeSort(v, l, mid)) % mod;
        res = (res + MergeSort(v, mid + 1, r)) % mod;
        int left = l, right = mid + 1;
        vector<int> vv;
        while(left <= mid && right <= r) {
            if(v[left] > v[right]) {
                res = (res + mid - left + 1) % mod;  //此时left - mid中的所有数均可以right组成逆序对
                vv.push_back(v[right++]);
            }
            else vv.push_back(v[left++]);
        }
        while(left <= mid) vv.push_back(v[left++]);
        while(right <= r) vv.push_back(v[right++]);
        for(auto& it : vv) v[l++] = it;
        return res;
    }
};

【35.两个链表的第一个公共节点】

【题目描述】
输入两个链表,找出它们的第一个公共结点。

【解题思路】
可以递归或者非递归

递归和非递归的思路均为将两个链表的每一个节点都暴力匹配一次

但是非递归不能判断没有公共节点的情况,个人建议采用递归

【AC代码】

递归

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
            val(x), next(NULL) {
    }
};*/
class Solution {
public:
    ListNode* FindFirstCommonNode(ListNode* pHead1, ListNode* pHead2) {
        if(pHead1 == NULL || pHead2 == NULL) return NULL;
        if(pHead1 == pHead2) return pHead1;
        ListNode* p1 = FindFirstCommonNode(pHead1 -> next, pHead2);
        ListNode* p2 = FindFirstCommonNode(pHead1, pHead2 -> next);
        if(p1 == NULL) return p2;
        if(p2 == NULL) return p1;
        if(p1 -> next == p2) return p1;
        if(p2 -> next == p1) return p2;
    }
};

非递归

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
            val(x), next(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) {
            p1 = p1 == NULL ? pHead1 : p1 -> next;
            p2 = p2 == NULL ? pHead2 : p2 -> next;
        }
        return p1;
    }
};

【36.数字在排序数组中出现的次数】

【题目描述】
统计一个数字在排序数组中出现的次数。

【解题思路】
STL中含有二分查找函数lower_bound和upper_bound,前者返回第一个大于等于指定元素的位置,后者返回第一个大于指定元素的位置

【AC代码】

class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        return upper_bound(data.begin(), data.end(), k) - lower_bound(data.begin(), data.end(), k);
    }
};

【37.二叉树的深度】

【题目描述】
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

【解题思路】

【AC代码】

/*
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 == NULL) return 0;
        return max(TreeDepth(pRoot -> left), TreeDepth(pRoot -> right)) + 1;
    }
};

【38.平衡二叉树】

【题目描述】
输入一棵二叉树,判断该二叉树是否是平衡二叉树。

【解题思路】

【AC代码】

class Solution {
public:
    bool IsBalanced_Solution(TreeNode* pRoot) {
        if(pRoot == NULL) return true;
        if(abs(TreeDepth(pRoot -> left) - TreeDepth(pRoot -> right)) > 1) return false;  //左右子树深度相差大于1
        return IsBalanced_Solution(pRoot -> left) && IsBalanced_Solution(pRoot -> right);
    }
    int TreeDepth(TreeNode* pRoot) {
        if(pRoot == NULL) return 0;
        return max(TreeDepth(pRoot -> left), TreeDepth(pRoot -> right)) + 1;
    }
};

【39.数组中只出现一次的数字】

【题目描述】
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

【解题思路】
map

【AC代码】

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data, int* num1, int *num2) {
        map<int, int> nums;
        for(auto& it : data) ++nums[it];
        *num1 = 0x3f3f3f3f, *num2 = 0x3f3f3f3f;
        for(auto& it : nums) {
            if(it.second == 1) *num1 == 0x3f3f3f3f ? *num1 = it.first : *num2 = it.first;
        }
    }
};

【40.和为S的连续正整数序列】

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

输出描述:
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

【解题思路】
双指针问题,因为序列是连续的,所以可以用公式算和,前指针记录头,后指针记录尾,和比sum小后指针加一,比sum大前置站加一

【AC代码】

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        int left = 1, right = 2, num = sum / 2 + 1;
        vector<int> v;
        vector<vector<int> > vv;
        for(int i = 1; i <= num; ++i) v.push_back(i);
        while(left < right) {
            int temp = (left + right) * (right - left + 1) / 2;
            if(temp < sum) ++right;
            else if(temp == sum) vv.push_back(vector<int>(v.begin() + left - 1, v.begin() + right)), ++left;
            else ++left;
        }
        return vv;
    }
};

【41.和为S的两个数字】

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

输出描述:
对应每个测试案例,输出两个数,小的先输出。

【解题思路】
因为序列有序,所以可以采用二分查找,大致思路和上一题差不多,是个双指针问题

【AC代码】

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
        int left = 0, right = array.size() - 1;
        vector<int> v;
        while(left < right) {
            int m_sum = array[left] + array[right];
            if(m_sum > sum) --right;
            else if(m_sum == sum) {
                v.push_back(array[left]);
                v.push_back(array[right]);
                break;
            }
            else ++left;
        }
        return v;
    }
};

【42.左旋转字符串】

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

【解题思路】

【AC代码】

class Solution {
public:
    string LeftRotateString(string str, int n) {
        if(str == "") return str;
        int l = str.length();
        n %= l;
        return str.substr(n, l - n) + str.substr(0, n);
    }
};

【43.翻转单词序列】

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

【解题思路】
从后往前遍历,将单词的前一个空格到后一个空格中间的字符加到新的字符串中

注意有可能源字符串只有空格,这时返回源字符串即可

【AC代码】

class Solution {
public:
    string ReverseSentence(string str) {
        if(str == "") return str;
        string s;
        int l = str.length() - 1;
        while(l >= 0) {
            int temp = l;
            while(l >= 0 && str[l] != ' ') --l;
            s += str.substr(l + 1, temp - l) + " ";
            while(l >= 0 && str[l] == ' ') --l;
        }
        s.resize(s.length() - 1); //去除末尾空格
        if(s == "") return str;
        return s;
    }
};

【44.扑克牌顺子】

【题目描述】
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。

【解题思路】
一开始打算采用纯模拟的做法,因为只有5张牌,所以可以把所有情况都列出来,后来看题解里面说的是最大值和最小值相差不超过5并且没有重复数字,那么用set存放就好了

【AC代码】

纯模拟

class Solution {
public:
    bool IsContinuous( vector<int> numbers ) {
        int l = numbers.size();
        if(l < 5) return false;
        sort(numbers.begin(), numbers.end());
        int cnt = 0;
        while(cnt < 5 && !numbers[cnt]) ++cnt;
        if(cnt >= 4) return true;
        int index_pre = cnt + 1, index_tail = 3;
        while(index_pre < 5 && numbers[index_pre] - numbers[index_pre - 1] == 1) ++index_pre;
        if(numbers[index_pre] ==  numbers[index_pre - 1]) return false;
        while(index_tail > 0 && numbers[index_tail + 1] - numbers[index_tail] == 1) --index_tail;
        if(numbers[index_tail + 1] ==  numbers[index_tail]) return false;
        --index_pre, ++index_tail;
        if(!cnt) return index_pre >= 4;
        if(cnt == 1) return index_tail - index_pre == 1 && numbers[index_tail] - numbers[index_pre] <= 2;
        if(cnt == 2) {
            if(index_tail - index_pre == 2 && numbers[index_tail] - numbers[index_pre] <= 3) return true;
            if(numbers[3] - numbers[2] <= 2 && numbers[4] - numbers[3] <= 2) return true;
            return false;
        }
        if(cnt == 3) return numbers[4] - numbers[3] <= 4;
    }
};

set

class Solution {
public:
    bool IsContinuous( vector<int> numbers ) {
        int l = numbers.size();
        if(l < 5) return false;
        int cnt = 0, Max = 0, Min = 14;
        set<int> s;
        for(auto& it : numbers) {
            if(!it) ++cnt;
            else {
                s.insert(it);
                Max = max(Max, it);
                Min = min(Min, it);
            }
        }
        return cnt >= 4 || (cnt + s.size() >= 5 && Max - Min < 5);
    }
};

【45.孩子们的游戏(圆圈中剩下的数)】

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

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

【解题思路】
约瑟夫环,但是题解很精妙

原编号: 0 1 2 3 … m - 2 m - 1 m … n - 1
删去后:n - m n - m + 1 n - m + 2 n - m + 3 n - 1 x 0 n - m - 1

那么假设原序列中编号为a,删去后为b,则有
b = ((a - m - n) + n) % n
则 a = (b + m) % n

故可由删去后序列中编号得到原序列中编号,那么采用递归算法,当总人数为n是返回0即可

注意n = 0返回-1

【AC代码】

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

【46.求1 + 2 + 3 + … + n】

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

【解题思路】
这题很容易想到用递归形式,但是由于不能用if,可以用&&运算符使得递归结束

【AC代码】

class Solution {
public:
    int Sum_Solution(int n) {
        int res = n;
        n && (res += Sum_Solution(n - 1));
        return res;
    }
};

【47.不用加减乘除做加法】

【题目描述】
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

【解题思路】
用二进制模拟加法(不想学
https://blog.nowcoder.net/n/07f2bd03162d40ddaebefd666e0d71b2?f=comment

【AC代码】

class Solution {
public:
    int Add(int num1, int num2) {
        return num2 ? Add(num1 ^ num2, (num1 & num2) << 1) : num1;
    }
};

【48.把字符串转换成整数】

【题目描述】
将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0

输入描述:
输入一个字符串,包括数字字母符号,可以为空

输出描述:
如果是合法的数值表达则返回该数字,否则返回0

示例
输入
+2147483647
1a33

输出
2147483647
0

【解题思路】
将字符转看做a * 100+ b * 101+ … + c * 10n

【AC代码】

class Solution {
public:
    int StrToInt(string str) {
        if(str == "") return 0;
        int l = str.length();
        long long res = 0;
        for(int i = str[0] >= '0' && str[0] <= '9'? 0 : 1; i < l; ++i) {
            if(str[i] < '0' || str[i] > '9') return 0;
            res += (long long)(pow(10, l - i - 1) * (str[i] - '0'));
        }
        if(str[0] == '-') res = -res;
        if(res > 2147483647 || res < -2147483648) return 0;
        return res;
    }
};

【49.数组中重复的数字】

【题目描述】
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

【解题思路】
map

【AC代码】

class Solution {
public:
    // Parameters:
    //        numbers:     an array of integers
    //        length:      the length of array numbers
    //        duplication: (Output) the duplicated number in the array number
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    bool duplicate(int numbers[], int length, int* duplication) {
        map<int, int> mp;
        for(int i = 0; i < length; ++i) {
            if(!mp[numbers[i]]) ++mp[numbers[i]];
            else {
                *duplication = numbers[i];
                return true;
            }
        }
        return false;
    }
};

【50.构造乘积数组】

【题目描述】
给定一个数组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]。不能使用除法。

【解题思路】
分两步,第一步求出A[0] … A[i - 1],第二步求出A[i + 1] … A[n - 1],然后相乘

【AC代码】

class Solution {
public:
    vector<int> multiply(const vector<int>& A) {
        int l = A.size();
        vector<int> B(l, 1);
        vector<int> C(l, 1);
        for(int i = 1; i < l; ++i) {
            B[i] = B[i - 1] * A[i - 1];
            C[l - i - 1] = C[l - i] * A[l - i];
        }
        for(int i = 0; i < l; ++i) B[i] *= C[i];
        return B;
    }
};

【51.正则表达式匹配】

【题目描述】
请实现一个函数用来匹配包括’.‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"ab*a"均不匹配

【解题思路】
对每一位进行分类讨论,如果当前串2的下一位不为 ‘ * ’,那么显然该位要么和串1相等,要么为 ‘ .’
如果当前串2的下一位为 ‘ * ’,如果该位和串1相等或为 ‘ . ’ ,那么可以让该位出现1次或者不出现进行下一位的匹配,否则只能让其不出现进行下一位匹配

【AC代码】

class Solution {
public:
    bool match(char* str, char* pattern) {
        if(*pattern == '\0') return *str == '\0';
        if(*(pattern + 1) != '*') {  //下一位不为‘*’
            if(*str == *pattern || (*str != '\0' && *pattern == '.'))  //该位相等或为‘.’
                return match(str + 1, pattern + 1);
            return false;
        }
        if(*str == *pattern || (*str != '\0' && *pattern == '.'))  //下一位为‘*’且与串1可匹配
            return match(str, pattern + 2) || match(str + 1, pattern);
        return match(str, pattern + 2); //否则使该位不出现
    }
};

【52.表示数值的字符串】

【题目描述】
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。

【解题思路】
采用Java的正则表达式进行匹配

[\+\-]? 正或负符号出现与否
\d* 整数部分是否出现
(\.\d+)? 如果出现小数点,那么小数点后面必须有数字,否则一起不出现
([eE][\+\-]?\d+)? 如果存在指数部分,那么e或E肯定出现,+或-可以不出现,紧接着必须跟着整数;或者整个部分都不出现

【AC代码】

public class Solution {
    public boolean isNumeric(char[] str) {
        String s = String.valueOf(str);
        return s.matches("[\\+\\-]?\\d*(\\.\\d+)?([eE][\\+\\-]?\\d+)?");
    }
}

【53.字符流中第一个不重复的字符】

【题目描述】
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。

输出描述:
如果当前字符流没有存在出现一次的字符,返回#字符。

【解题思路】
建立一个双端队列(deque),如果头和尾相同就将其pop

【AC代码】

class Solution {
public:
    deque<char> que;
  //Insert one char from stringstream
    void Insert(char ch) {
        que.push_back(ch);
        while(que.size() > 1 && que.front() == que.back()) {
            que.pop_back();
            que.pop_front();
        }
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce() {
        return que.empty() ? '#' : que.front();
    }
};

【54.链表中环的入口节点】

【题目描述】
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

【解题思路】
map记录节点出现次数,返回出现两次的节点

【AC代码】

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    map<ListNode*, int> mp;
    ListNode* EntryNodeOfLoop(ListNode* pHead) {
        if(pHead == NULL || mp[pHead]) return pHead;
        ++mp[pHead];
        return EntryNodeOfLoop(pHead -> next);
    }
};

【55.删除链表中重复的节点】

【题目描述】
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

【解题思路】
可以递归求解,如果头节点与后面的重复,让其加到第一个不重复的节点,否则将其next指针指向下一个不重复的节点

【AC代码】

/*
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;
        int val = 0x3f3f3f3f;
        while(pHead -> next != NULL && pHead -> val == pHead -> next -> val) {
            val = pHead -> val;
            pHead = pHead -> next;
        }
        if(val == pHead -> val) return deleteDuplication(pHead -> next);
        pHead -> next = deleteDuplication(pHead -> next);
        return pHead;
    }
};

【56.二叉树的下一个节点】

【题目描述】
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

【解题思路】
遍历一次就可以了

【AC代码】

/*
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:
    vector<TreeLinkNode*> v;
    TreeLinkNode* GetNext(TreeLinkNode* pNode) {
        if(pNode == NULL) return NULL;
        TreeLinkNode* Root = pNode;
        while(Root -> next != NULL) Root = Root -> next; 
        InOrder(Root);
        int l = v.size();
        if(v[l - 1] == pNode) return NULL;
        for(int i = 0; i < l; ++i) {
            if(v[i] == pNode) return v[i + 1];
        }
    }
    void InOrder(TreeLinkNode* pNode) {
        if(pNode == NULL) return;
        InOrder(pNode -> left);
        v.push_back(pNode);
        InOrder(pNode -> right);
    }
};

【57.对称的二叉树】

【题目描述】
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

【解题思路】
对称二叉树即为左右子树对称,递归判断

【AC代码】

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot) {
        return pRoot == NULL || judge(pRoot -> left, pRoot -> right);
    }
    bool judge(TreeNode* RootLeft, TreeNode* RootRight) {
        if(RootLeft == NULL && RootRight == NULL) return true;
        if(RootLeft == NULL || RootRight == NULL) return false;
        if(RootLeft -> val != RootRight -> val) return false;
        return judge(RootLeft -> left, RootRight -> right) && judge(RootLeft -> right, RootRight -> left);
    }
};

【58.按之字形打印二叉树】

【题目描述】
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

【解题思路】
维护一个广度遍历序列,定义一个flag标记是否反转

【AC代码】

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        vector<vector<int> > vv;
        if(pRoot == NULL) return vv;
        queue<TreeNode*> que;
        que.push(pRoot);
        bool flag = false;
        while(true) {
            vector<int> v;
            int n = que.size();
            for(int i = 1; i <= n; ++i) {
                TreeNode* u = que.front();
                que.pop();
                if(u == NULL) continue;
                v.push_back(u -> val);
                que.push(u -> left);
                que.push(u -> right);
            }
            if(que.empty()) break;
            if(!flag) flag = true;
            else {
                reverse(v.begin(), v.end());
                flag = false;
            }
            vv.push_back(v);
        }
        return vv;
    }
};

【59.将二叉树打印成多行】

【题目描述】
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

【解题思路】
58题的简化版

【AC代码】

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        vector<vector<int> > vv;
        if(pRoot == NULL) return vv;
        queue<TreeNode*> que;
        que.push(pRoot);
        while(true) {
            vector<int> v;
            int n = que.size();
            for(int i = 1; i <= n; ++i) {
                TreeNode* u = que.front();
                que.pop();
                if(u == NULL) continue;
                v.push_back(u -> val);
                que.push(u -> left);
                que.push(u -> right);
            }
            if(que.empty()) break;
            vv.push_back(v);
        }
        return vv;
    }
};

【60.序列化二叉树】

【题目描述】
请实现两个函数,分别用来序列化和反序列化二叉树

二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。

二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

【解题思路】
实际上是让你写一个线索二叉树,没什么用而且数据和题目的函数很奇怪,不写,代码手动滑稽

【AC代码】

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    TreeNode* Root = NULL;
    char* Serialize(TreeNode *root) {    
        Root = root;
        return "(0^_^0)";
    }
    TreeNode* Deserialize(char *str) {
        return Root;
    }
};

【61.二叉搜索树的第k个节点】

【题目描述】
给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。

【解题思路】
之前做过,BST的中序遍历就是排序后的BST,用数组存一下就好了

【AC代码】

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    vector<TreeNode*> v;
    TreeNode* KthNode(TreeNode* pRoot, int k) {
        if(k <= 0) return NULL;
        Inorder(pRoot);
        if(v.size() < k) return NULL;
        return v[k - 1];
    }
    void Inorder(TreeNode* pRoot) {
        if(pRoot == NULL) return;
        Inorder(pRoot -> left);
        v.push_back(pRoot);
        Inorder(pRoot -> right);
    }
};

【62.数据流中的中位数】

【题目描述】
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

【解题思路】
设置一个大顶堆和一个小顶堆,一个维护中间前的序列,一个维护中间后的序列,根据size大小返回即可

【AC代码】

class Solution {
public:
    priority_queue<int> que_max;
    priority_queue<int, vector<int>, greater<int> > que_min;
    void Insert(int num) {
        if(que_max.empty() || num <= que_max.top()) que_max.push(num);
        else que_min.push(num);
        if(que_max.size() == que_min.size() + 2) 
            que_min.push(que_max.top()), que_max.pop(); //前面比后面多了2个,是两个序列长度相等
        else if(que_max.size() + 1 == que_min.size())   //如果总长度是奇数始终让前面比后面多一
            que_max.push(que_min.top()), que_min.pop();
    }
    double GetMedian() {
        return que_max.size() == que_min.size() ? (que_max.top() + que_min.top()) * 1.0 / 2.0 : que_max.top();
    }
};

【63.滑动窗口的最大值】

【题目描述】
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{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]}。

【解题思路】
题解里面很多都用的双端队列,但是我觉得双指针就可以解决了,时间复杂度应该相差不大

【AC代码】

class Solution {
public:
    vector<int> maxInWindows(const vector<int>& num, unsigned int size) {
        int len = num.size() - 1;
        vector<int> res;
        if(!len || !size || len + 1 < size) return res;
        int l = 0, r = size - 1, Max = 0;
        for(int i = l + 1; i <= r; ++i) {
            if(num[i] >= num[Max]) Max = i;
        }
        res.push_back(num[Max]);
        while(r < len) {
            ++l, ++r;  //当前处理的区间端点下标
            if(num[r] >= num[Max]) Max = r;  //新的值大于区间最大
            if(Max >= l) res.push_back(num[Max]); //最大值在区间内
            else { //最大值不在区间内
                Max = l;
                for(int i = l + 1; i <= r; ++i) {
                    if(num[i] >= num[Max]) Max = i;
                }
                res.push_back(num[Max]);
            }
        }
        return res;
    }
};

【64.矩阵中的路径】

【题目描述】
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 a b c e s f c s a d e e 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

【解题思路】
一个爆搜题,没什么意思,先留他一命,附上一个他人题解

https://blog.nowcoder.net/n/8760fada032242458c08292fb651c639 by @初憶

【AC代码】

public class Solution {
boolean[] visited = null;
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
        visited = new boolean[matrix.length];
        for(int i = 0; i < rows; i++)
            for(int j = 0; j < cols; j++)
                if(subHasPath(matrix, rows, cols, str, i, j, 0))
                    return true;
        return false;
    }
    public boolean subHasPath(char[] matrix, int rows, int cols, char[] str, int row, int col, int len){
        if(matrix[row * cols + col] != str[len]|| visited[row * cols + col] == true) return false;
        if(len == str.length - 1) return true;
        visited[row * cols + col] = true;
        if(row > 0 && subHasPath(matrix, rows, cols, str, row-1, col, len+1)) return true;
        if(row < rows - 1 && subHasPath(matrix, rows, cols, str, row+1, col, len+1)) return true;
        if(col > 0 && subHasPath(matrix, rows, cols, str, row, col-1, len+1)) return true;
        if(col < cols - 1 && subHasPath(matrix, rows, cols, str, row, col+1, len+1)) return true;
        visited[row * cols + col] = false;
        return false;
    }
}

【65.机器人的运动范围】

【题目描述】
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

【解题思路】
可以dfs,也可以bfs

【AC代码】

class Solution {
public:
    bool vis[1010][1010];
    int movingCount(int threshold, int rows, int cols)
    {
        if(threshold < 0) return 0;
        int sum = 0, n = rows, m = cols, k = threshold;
        memset(vis, false, sizeof(vis));
        queue<pair<int, int> > que;
        que.push(make_pair(0, 0));
        vis[0][0] = true;
        while(!que.empty()) {
            pair<int, int> u = que.front();
            que.pop();
            int x = u.first, y = u.second;
            int a = x, b = y;
            int temp = 0;
            while(a) {
                temp += a % 10;
                a /= 10;
            }
            while(b) {
                temp += b % 10;
                b /= 10;
            }
            if(temp > k) continue;
            ++sum;
            if(x + 1 < n && !vis[x + 1][y]) {
                que.push(make_pair(x + 1, y));
                vis[x + 1][y] = true;
            }
            if(x - 1 >= 0 && !vis[x - 1][y]) {
                que.push(make_pair(x - 1, y));
                vis[x - 1][y] = true;
            }
            if(y + 1 < m && !vis[x][y + 1]) {
                que.push(make_pair(x, y + 1));
                vis[x][y + 1] = true;
            }
            if(y - 1 >= 0 && !vis[x][y - 1]) {
                que.push(make_pair(x, y - 1));
                vis[x][y - 1] = true;
            }
        }
        return sum;
    }
};

【66.剪绳子】

【题目描述】
给你一根长度为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。

【解题思路】
题解系本人所写,未抄袭
在这里插入图片描述

【AC代码】

class Solution {
public:
    int cutRope(int number) {
        if(number == 2) return 1;
        int m = number * 1.0 / (2.7);
        int x = number / m;
        int y = number / (m + 1);
        int ansx = pow(x, m - 1) * (number - pow(x, m - 1));
        int ansy = pow(y, m) *(number - pow(y, m));
        int z = 0, ansz = 0;
        if(m > 2) {
            z = number / (m - 2);
            ansz = pow(z, m - 2) *(number - pow(z, m - 2));
            return max(ansx, max(ansy, ansz));
        }
        return max(ansx, ansy);
    }
};
发布了40 篇原创文章 · 获赞 2 · 访问量 3223

猜你喜欢

转载自blog.csdn.net/weixin_44211980/article/details/104125151