牛客网剑指offer刷题总结

二维数组中的查找:

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

  两种思路各有优势:
    1、行枚举列二分O(nlogm)(列二分行枚举O(mlogn))
    2、从左下角或右上角移动O(m+n)
    如果n,m一个特别大一个特别小选择方案1效率很高
    其他情况选择第2中方案
 
替换空格:
  题目描述:请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
  思路:先计算出空格的数量,然后从后到前遍历移动即可
 
从尾到头打印链表:
  题目描述:输入一个链表,从尾到头打印链表每个节点的值。
  思路:递归后序(利用栈的思想)
 
重建二叉树:
  题目描述:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
  思路:根据前序遍历可以确定根节点,将中序遍历分为两半,递归即可
 
用两个栈实现队列:
  题目描述:用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
  思路:两个栈st1, st2。push操作向st1中插入。pop操作:如果st2为空则把st1中所有元素放入st2中,否则直接从st2中取元素即可
 
旋转数组的最小数字:
  题目描述:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
  思路:二分查找
  代码:
class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        if(rotateArray.empty()) return 0;
        int l = 0, r = rotateArray.size() - 1;
        int stanrd = rotateArray[0];
        int minv = stanrd;
        int mid;
        while(l <= r)
        {
            mid = (l + r) >> 1;
            if(rotateArray[mid] == rotateArray[r])
            {
                --r;//防止有重复元素出现的情况,如 2 2 2 2 2 2 1 2这种情况
                minv = min(minv, rotateArray[mid]);
            }
            else if(rotateArray[mid] > stanrd)
            {
                l = mid + 1;
            }
            else
            {
                r = mid - 1;
                minv = min(minv, rotateArray[mid]);
            }
        }
        return minv;
    }
};

  

 

-------------------------------------------------斐波那契数列专题-------------------------------------------------

解决方案:矩阵快速幂log(n)

代码:

//代码写的很乱,主要看思路
class Solution {
public:
    int tmp[2][2];
    const int a[2][2] =
    {
        1, 1,
        1, 0
    };
    int Fibonacci(int n)
    {
        if (n == 0)
            return 0;
        else if (n == 1)
            return 1;
        else
        {
            Get(n - 1, tmp);
            return tmp[0][0];
        }
    }
    void Get(int n, int tmp[][2])
    {
        if (n == 1)
        {
            for (int i = 0; i < 2; ++i)
                for (int j = 0; j < 2; ++j)
                    tmp[i][j] = a[i][j];
            return;
        }
        Get(n >> 1, tmp);
        int cc[2][2];
        cc[0][0] = tmp[0][0] * tmp[0][0] + tmp[0][1] * tmp[1][0];
        cc[0][1] = tmp[0][0] * tmp[0][1] + tmp[0][1] * tmp[1][1];
        cc[1][0] = tmp[1][0] * tmp[0][0] + tmp[1][1] * tmp[1][0];
        cc[1][1] = tmp[1][0] * tmp[0][1] + tmp[1][1] * tmp[1][1];
        for (int i = 0; i < 2; ++i)
            for (int j = 0; j < 2; ++j)
                tmp[i][j] = cc[i][j];
        if (n & 1)
        {
            cc[0][0] = tmp[0][0] * a[0][0] + tmp[0][1] * a[1][0];
            cc[0][1] = tmp[0][0] * a[0][1] + tmp[0][1] * a[1][1];
            cc[1][0] = tmp[1][0] * a[0][0] + tmp[1][1] * a[1][0];
            cc[1][1] = tmp[1][0] * a[0][1] + tmp[1][1] * a[1][1];
        }
        for (int i = 0; i < 2; ++i)
            for (int j = 0; j < 2; ++j)
                tmp[i][j] = cc[i][j];
    }
};

  

1、斐波那契数列

2、跳台阶

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

  思路1:记忆化搜索

  思路2:问题可以转化为斐波那契数列,故使用矩阵快速幂可以在log(n)级别的时间复杂度内求解

  如果题目换成每次可以跳1级、2级、3级····n级呢?

  思路1:记忆化搜索

  思路2:推导

    f(n) = f(n - 1) + f(n - 2) + ··· + f(1);

    f(n - 1) = f(n - 2) + f(n - 3) + ··· + f(1);

  ->f(n) = 2 * f(n - 1)

3、矩形覆盖

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

  推导:

    f(n) = f(n - 1) + f(n - 2);//竖着放和横着放

----------------------------------------------------------------------------------------------------------

 

二进制中1的个数:

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

  思路:n&(n - 1),注意负数的情况即可

 

数值的整数次方:

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

  思路:快速幂,注意exponent为负数的情况,倒数即可

 

调整数组顺序使奇数于偶数前面:

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

  思路:1、新开辟一个链表记录偶数,奇数在遍历的过程中直接移到前面即可,时间复杂度O(n), 空间O(n)

       2、使用类似于插入排序的思想时间复杂度O(n^2),空间复杂度O(1)

 

 链表中倒数第k个节点

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

  思路:两个指针,其中一个先向后移动k次,然后两个指针同时移动即可

 

反转链表:

  题目描述:输入一个链表,反转链表后,输出链表的所有元素。

  思路:1、三个指针直接反转(代码比较复杂)

       2、使用头插法思想,从前向后遍历链表进行反转(代码简洁)

 

合并两个排序链表:

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

  思路:直接合并即可(注意添加头结点可简化代码量的编写)

 

树的子结构:

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

  思路:对A进行DFS,在DFS的过程中如果A的某个节点与B的根节点值相同,那么以B为树根进行DFS,比较A下面的部分是否包含B即可,时间复杂度O(n * m)

  扩展:如果B是A的子结构,并且满足B是A中以某个节点为根的子树(意味着下面这种情况不属于包含)

      1     1
      /   \     /  \
      2     3   2     3
    /  \
    4    5

  思路:先序遍历A和B,判断B的先序序列是否是A的先序序列的子序列即可,显然KMP算法即可搞定。时间复杂度O(n + m)

   扩展部分代码:

/*
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(pRoot2 == NULL) return false;
        vector<int> root1, root2;
        DFS(pRoot1, root1);
        DFS(pRoot2, root2);
        vector<int> next;
        GetNext(root2, next);
        return KMP(root1, root2, next);
    }
private:
    //取得前序序列
    void DFS(TreeNode * root, vector<int> &ans)
    {
        if(root == NULL) return;
        ans.push_back(root->val);
        DFS(root->left, ans);
        DFS(root->right, ans);
    }
    //求解next数组
    void GetNext(vector<int> &data, vector<int> &next)
    {
        next.push_back(0);
        int len = data.size();
        for(int i = 1, k = 0; i < len; ++i)
        {
            while(k > 0 && data[i] != data[k])
                k = next[k - 1];
            if(data[i] == data[k])
                ++k;
            next.push_back(k);
        }
    }
    bool KMP(vector<int> &parent, vector<int> &child, vector<int> &next)
    {
        int i = 0, j = 0;
        int len1 = parent.size();
        int len2 = child.size();
        
        for(; i < len1; ++i)
        {
            while(j > 0 && parent[i] != child[j])
                j = next[j - 1];
            if(parent[i] == child[j])
                ++j;
            if(j >= len2) return true;
        }
        return false;
    }
};

  

二叉树的镜像:

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

  思路:交换左右孩子指针即可

 

顺时针打印矩阵:

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

  思路:控制上下左右边界,每次打印一层外围,然后所有边界即可。注意最后剩下一行或一列的特殊情况

  代码:

class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) 
    {
        vector<int> ans;
        int xl, xr, yl, yr;
        
        if((xr = matrix.size()) == 0) return ans;
        if((yr = matrix[0].size()) == 0) return ans;
        int tol = min((xr + 1) / 2, (yr + 1) / 2);
   
        xl = yl = 0;     
        --xr;
        --yr;
        
        for(int t = 0; t < tol; ++t)
        {
            for(int j = yl; j <= yr; ++j) ans.push_back(matrix[xl][j]);
            for(int i = xl + 1; i <= xr; ++i) ans.push_back(matrix[i][yr]);
            for(int j = yr - 1; j >= yl && xl < xr; --j) ans.push_back(matrix[xr][j]);
            for(int i = xr - 1; i > xl && yl < yr; --i) ans.push_back(matrix[i][yl]);
            ++xl;++yl;
            --xr;--yr;
        }
        return ans;
    }
};

 

包含min函数的栈

  题目描述:定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。

  思路:用两个栈data、mindata,data保存数据按照正常的栈操作即可,mindata保存的内容:

     1、入栈:如果当前值小于等于最小值那么压入栈O(1)

     2、出栈:如果data栈顶的值等于mindata的值那么出栈O(1)

          3、最小值:最小值即为mindata栈顶元素O(1)

  代码:

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

 

栈的压入、弹出序列

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

  思路:用pushV模拟栈,空间复杂度O(1),时间复杂度O(n)

  代码:

class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) 
    {
    	int len = pushV.size();
        int index = -1, j = 0;//index标示栈顶
        for(int i = 0; i < len + 1; ++i)
        {
        	while(index >= 0 && pushV[index] == popV[j])
            {
                --index;
                ++j;
            }
            if(i < len)
            	pushV[++index] = pushV[i];

        }
        if(j == len) return true;
        return false;
    }
};

  

从上往下打印二叉树:

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

  思路:BFS

 

二叉搜索树的后序遍历:

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

  思路:二叉搜索树的后序遍历特点:

     1、最后一个元素为根节点

     2、除最后一个元素v外,该序列可分为前后两部分,前面部分都小于v,后面部分都大于v。

     3、递归即可,判断从后向前遍历,如果一个元素小于v之后还有元素大于v则返回false

 

二叉树中和为某一值得路径:

  题目描述:输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

  思路:DFS即可

 

复杂链表的复制:

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

  思路:1、hashmap,在原链表的节点地址saddress和新链表的节点地址naddress直接一一对应做一个映射,然后顺序复制即可

     2、可以采用之字链表方式替换hashmap,这种方法时间复杂度比较稳定。时间复杂度O(n)。ps:记得还原原来的链表

  之字链表法代码:

/*
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 *Root = new RandomListNode(pHead->label);
        RandomListNode *p, *q, *r;
        p = pHead, q = Root;
        while(p != NULL)
        {
       		r = p->next;
            p->next = q;
            q->next = r;
            p = r;
            if(p != NULL)
                q = new RandomListNode(p->label);
        }
        p = pHead;
        q = Root;
        while(p != NULL)
        {
            if(p->random != NULL)
        		q->random = p->random->next;
            else
                q->random = NULL;
         	
            p = q->next;
            if(p != NULL)
            	q = p->next;
        }
        p = pHead;
        q = Root;
        while(q->next != NULL)
        {
        	r = q->next;
            q->next = q->next->next;
            p->next = r;
            p = r;
            q = q->next;
        }
        p->next = NULL;
        return Root;
    }
};

  

二叉搜索树与双向链表:(和链表逆序一样,不要总想改变指针指向,可以从整体上考虑。链表逆序整体:就是把原链表中的节点一个一个插入到新链表中。本题:就是中序遍历的过程中修改指针即可)

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

  思路:开两个变量:root记录链表头,p记录链表尾节点。中序遍历二叉树,p->right指向当前节点,当前节点的left指向p即可

  代码:

class Solution 
{
public:
    TreeNode *root, *p;
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        root = NULL;
        DFS(pRootOfTree);
        p->right = NULL;
        root->left == NULL;
        return root;
    }
private:
    void DFS(TreeNode *pRootOfTree)
    {
		if(pRootOfTree == NULL) return;
        
        TreeNode *left = pRootOfTree->left, *right = pRootOfTree->right;
       	
        DFS(left);
        if(root == NULL)
        {
            root = pRootOfTree;
            p = root;
        } 
        else
        {
        	p->right = pRootOfTree;
            pRootOfTree->left = p;
            p = p->right;
        }
        DFS(right);
    }
};

 

字符串排列:

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

  思路:字典序

class Solution {
public:
    vector<string> Permutation(string str) 
    {
        vector<string> ans;
        if(str.size() == 0) return ans;
    	sort(str.begin(), str.end()); 
        int len = str.size();
        while(true)
        {
         	ans.push_back(str);
            int l, r, i;
            for(i = len - 2; i >= 0; --i)
            {
            	if(str[i] < str[i + 1])
                {
                    l = i;
                    break;
				}
            }
            if(i < 0) break;
            for(int j = l + 1; j < len; ++j)
            {
            	if(str[j] <= str[l]) break;
                else
                    r = j;
            }
            swap(str[l], str[r]);
            for(int i = l + 1, j = len - 1; i < j; ++i, --j)
                swap(str[i], str[j]);
        }
        return ans;
    }
};

 

数组中出现次超过一半的数字:

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

  思路:见代码,O(n)

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
    	int len = numbers.size();
        if(len == 0) return 0;
        int cnt = 1;
        int v = numbers[0];
        for(int j = 1; j < len; ++j)
        {
            if(numbers[j] == v) ++cnt;
            else --cnt;
            if(cnt == 0)
            {
            	v = numbers[j];
                cnt = 1;
            }
        }
        
        cnt = 0;
        for(int i = 0; i < len; ++i)
        {
        	if(numbers[i] == v) ++cnt;
        }
        if(cnt >= (len + 2) / 2) return v;
        return 0;
    }
};

  

最小的k个数:最大堆

 

连续子数组的最大和:O(n)遍历即可

 

整数中1出现的次数(从1到n整数中1出现的次数):

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

  思路:时间复杂度O(log10n)

class Solution {
public:
    /*
    	我们从低位到高位求每位1出现的次数,累加求和即可
        例如:求0~abcde中1的个数
        现在我们求c这一位中1出现的次数,其他位雷同
        有两部分组成
        第一部分:ab * 1000,表示当ab这两位在0~ab-1范围内时,cde可以从0~999取值
    	第二部分:如果c>1时,当ab为ab时1的个数为0~999
        		 如果c=1时,当ab为ab时1的个数为cde + 1
                 如果c<0时,当ab为ab是1的个数为0
    
    */
    int NumberOf1Between1AndN_Solution(int n)
    {
        int exp = 1;
        int ans = 0; 
        while(n / exp)
        {
        	ans += n / (exp * 10) * exp;
            if(n % (exp * 10) / exp  > 1) ans += exp;
            else if(n % (exp * 10) / exp == 1) ans += (n % exp + 1);
            exp *= 10;
        }
        return ans;
    }
};

 

把数组排成最小的数:

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

  思路:将整数转化为字符串,然后对字符数组排序,将排序好的字符数组从前到后串联即可。

     排序规则:从前往后比较两个字符串a,b

     如果a[i] < b[i]返回true,a[i] > b[i] 返回 false,如果相等继续比较

     如果a和b完全相同返回true或者false都行

     如果a比b长,那么a剩余部分的元素从前到后如果大于a[0]返回false, 如果小于a[0]返回true,如果相等则任意返回即可

       如果b比a长,与上述相反即可

  代码:ps讨论中有另一种比较方法如果ab < ba返回true, 如果ab>ba返回false, 如果ab==ba任意返回即可,这种思路很好

bool cmp(const string &a, const string &b)
{  
    int len1 = a.size(), len2 = b.size();
    int i = 0;
    while(i < len1 && i < len2)
    {
        if(a[i] < b[i]) return true;
        else if(a[i] > b[i]) return false;
        ++i;
    }
    if(len1 < len2)
    {
        while(i < len2)
        {
            if(b[i] > b[0]) return true;
            if(b[i] < b[0]) return false;
            ++i;
        }
        return false;
    }
    else if(len1 > len2)
    {
        while(i < len2)
        {
            if(a[i] > a[0]) return false;
            if(a[i] < a[0]) return true;
            ++i;
        }
    }
    return true;
}
 
class Solution
{
public:
    string PrintMinNumber(vector<int> numbers)
    {
        vector<string> num;
        char st[100];
        for(vector<int>::iterator iter = numbers.begin(); iter != numbers.end(); ++iter)
        {
            sprintf(st, "%d", *iter);
            num.push_back(st);
        }
        sort(num.begin(), num.end(), cmp);
        string ans;
        for(vector<string>::iterator iter = num.begin(); iter != num.end(); ++iter)
            ans += *iter;
        return ans;
    }
};

 

丑数:

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

  思路:由于后面的数肯定是由前边的数*2、 *3、 *5得到的,那么我们用t2、t3、t5分别记录轮到第几个数分别与2、3、5相乘。注意相同的情况不要重复插入

  代码:

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        if (index<=0) return 0;
        if (index==1) return 1;
        vector<int>k(index);k[0]=1;
        int t2=0,t3=0,t5=0;
        for (int i=1;i<index;i++) {
            k[i]=min(k[t2]*2,min(k[t3]*3,k[t5]*5));
            if (k[i]==k[t2]*2) t2++;
            if (k[i]==k[t3]*3) t3++;
            if (k[i]==k[t5]*5) t5++;
        }
        return k[index-1];
    }
};

 

第一个只出现一次的字符:没什么可讲的

 

数组中的逆序对:

  思路:1、离散化+树状数组

     2、归并排序

  代码:(离散化+树状数组)

class Solution {
public:
    int C[200010];
    void add(int pos, int v, int n)
    {
     	while(pos <= n)
        {
           	C[pos] += v;
        	pos += (pos & (-pos));
        }
    }
    int sum(int pos)
    {
        int ans = 0;
        while(pos > 0)
        {
        	ans += C[pos];
            pos -= (pos & (-pos));
        }
        return ans;
    }
    typedef tuple<int, int> node;
    int InversePairs(vector<int> data) 
    {
       	int len = data.size();
        vector<node> tdata;
        for(int i= 0 ; i < len; ++i)
            tdata.push_back(make_tuple(data[i], i));
        
      	function<bool(node, node)> fun = [](node a, node b)->bool{return get<0>(a) < get<0>(b);};
        sort(tdata.begin(), tdata.end(), fun);
        for(int i = 0; i < len; ++i)
        	get<0>(tdata[i]) = i + 1;    
        function<bool(node, node)> fun1 = [](node a, node b)->bool{return get<1>(a) < get<1>(b);};
        sort(tdata.begin(), tdata.end(), fun1);
        
        memset(C, 0, sizeof(C));
        long long ans = 0;
        for(int i = 0; i < len; ++i)
        {
        	ans += sum(len) - sum(get<0>(tdata[i]));
            ans %= 1000000007;
            add(get<0>(tdata[i]), 1, len);
        }
        return ans;
    }
};

 

两个链表的第一个公共结点:

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

  思路:1、如果两个链表有公共节,那么从该公共节点到最后一定是相同的。令p、q中较长的那个链表移动abs(len(p) - len(q)),然后两个链表再同时移动即可。时间:O(n),空间O(1)

     2、使用unordered_set记录p中每个节点的地址,然后遍历q即可。时间:O(n),空间O(n)

  代码:

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    //unordered_set
    ListNode* FindFirstCommonNode_unordered_set( ListNode *pHead1, ListNode *pHead2) 
    {
        if(pHead1 == NULL) return NULL;
        unordered_set<ListNode *>st;
       	ListNode *ptr = pHead1;
        while(ptr != NULL)
        {
            st.insert(ptr);
            ptr = ptr->next;
		}
        ptr = pHead2;
        while(ptr != NULL)
        {
        	if(st.find(ptr) != st.end()) break;
            ptr =  ptr->next;
        }
        return ptr;
    }
    
    ListNode* FindFirstCommonNode( ListNode *pHead1, ListNode *pHead2) 
    {
        if(pHead1 == NULL) return NULL;
        ListNode *ptr = pHead1;
     	int len1 = 0;
        while(ptr != NULL)
	    {
            ++len1;
            ptr = ptr->next;
		}
        ptr = pHead2;
        int len2 = 0;
        while(ptr != NULL)
        {
        	++len2;
            ptr = ptr->next;
        }
        ListNode *ptr1;
        if(len1 >= len2)
        {
         	ptr = pHead1;
            int len = len1 - len2;
            while(len--)
                ptr = ptr->next;
       		ptr1 = pHead2;
            while(ptr != NULL)
            {
            	if(ptr == ptr1) break;
                ptr = ptr->next;
                ptr1 = ptr1->next;
            }
        }
        else
        {
            ptr = pHead2;
            int len = len2 - len1;
            while(len--)
                ptr = ptr->next;
       		ptr1 = pHead1;
            while(ptr != NULL)
            {
            	if(ptr == ptr1) break;;
                ptr = ptr->next;
                ptr1 = ptr1->next;
            }
        }
        return ptr;
    }
};

 

数字在排序数组中出现的次数:

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

  思路:二分查找第一个出现的位置和最后一个出现的位置

 

二叉树的深度:

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

  思路:DFS遍历即可

 

 平衡二叉树:

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

  思路:DFS,判断左右子树深度相差是否大于1即可

 

数组中只出现一次的数字:

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

  思路:1、unordered_map统计一下即可

       2、根据异或的性质

       先遍历一遍数组,求出所有数的异或;然后任意找出该值一个为1的位,其他位置零,假设为x

       第二次遍历,如果data & x ==  0则与num1异或,反之与num2异或

  代码:

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) 
    {
		int t = 0;
        for(auto x : data) t ^= x;
        t = t&(-t);
        *num1 = *num2 = 0;
        for(auto x : data)
        {
        	if((x & t) != 0) *num1 ^= x;
            else *num2 ^= x;
        }
    }
};

 

 和为S的连续正数序列:

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

  思路:1、尺取法,设定左右边界l,r如果(l + r)*(r - l + 1) / 2 == sum满足条件则成功;否则如果>则l++, <则r++。时间复杂度O(n)

     2、数学方法:设定x为开始值,m为个数,求解开是否有满足条件的结即可。时间复杂度O(n)

  代码:

class Solution {
public:
    /*
    //数学方法
    vector<vector<int> > FindContinuousSequence1(int sum) 
    {
        vector<vector<int>> ans;
        for(int i = 1; i <= sum / 2; ++i)
        {
        	int n = (2 * i - 1) * (2 * i - 1) + 8 * sum;
            int kn = sqrt(n) + 0.1;
            if(kn * kn == n)
            {
            	if((-2 * i - 1 +  kn) % 2 == 0 && (-2 * i - 1 +  kn) / 2 > 0)
                {
                    int m = (-2 * i - 1 + kn) / 2;
                	vector<int> tans;
                    for(int j = i; j <= i + m; ++j)
                        tans.push_back(j);
                    ans.push_back(tans);
                }
            }
        }
        return ans;
    }
    */
    //标尺法
    vector<vector<int> > FindContinuousSequence(int sum) 
    {
        vector<vector<int>> ans;
        int l = 1, r = 2;
        while(l < r && l <= sum / 2)
        {
            int s = (l + r) * (r - l + 1);
            if((s & 1) == 0 && (s >> 1) == sum)
            {
                vector<int> tans;
                for(int i = l; i <= r; ++i)
                	tans.push_back(i);
                ans.push_back(tans);
                r++;
            }
            else
            {
                if((s >> 1) < sum) r++;
                else
                {
                    l++;
                    if(l == r) r++;
                }
            }
        }
        return ans;
    }
};

  

和为S的两个数字:

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

  思路:从两侧往中间移动,如果相等直接跳出循环,如果 < 则i++ , > 则j--

  代码:

    class Solution {
    public:
         //	1、向中间逼近
         vector<int> FindNumbersWithSum(vector<int> array,int sum) 
        {
            vector<int> ans;
           
            int len = array.size();
            int i = 0, j = len - 1;
            while(i < j)
            {
                if(array[i] + array[j] == sum) break;
                else if(array[i] + array[j] > sum) --j;
                else ++i;
            }
            if(i < j)
            {
            	ans.push_back(array[i]);
                ans.push_back(array[j]);
            }
            return ans;
        }

    };

 

旋转字符串:

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

  思路:字符串YX = (XTYT)TX,故可以根据移动的位数将字符串分为X、Y两部分,然后先分别反转。然后整体反转

  代码:

class Solution {
public:
    string LeftRotateString(string str, int n) 
    {
    	int len = str.size();
        if(len == 0) return str;
        n %= len;
        for(int i = 0, j = n - 1; i < j; ++i, --j) swap(str[i], str[j]);
        for(int i = n, j = len - 1; i < j; ++i, --j) swap(str[i], str[j]);
        for(int i = 0, j = len - 1; i < j; ++i, --j) swap(str[i], str[j]);
        return str;
    }
};

  

翻转单词顺序列:

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

  思路:1、先反转整个字符串

       2、从前到后遍历字符串,遇到空格则反转上次空格之后到这次空格之前的字符串。时间复杂度O(n)

 

扑克牌顺子:

  题目描述: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的运气如何。为了方便起见,你可以认为大小王是0。

  思路:排序,然后判断是否有非零重复的,以及maxv-minv是否大于n - 1即可

 

孩子们的游戏(圆圈中最后剩下的数:

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

  思路:1、暴力模拟,时间复杂度O(n * m)。这个就不叙述了

     2、递推,时间复杂度O(n)

       1、长度为n的序列:   0, 1, 2,···, m - 2, m - 1, m, ··· , n - 1   从总删除第一个之后变成

        长度为n-1的序列:m, m + 1, m + 2, ···, n - 2, n - 1, 0, 1, ···, m - 2

        对应值:      0, 1, 2, ···

        假设我们现在已经求得n-1的序列删除m时的最后删除的位置是:f[n - 1],根据上述映射f[n - 1] + m就是其在序列n中的位置

        所以f[n] = (f[n - 1] + m) % n(其中f[1] = 0)

     代码:

class Solution {
public:
    int LastRemaining_Solution(unsigned int n, unsigned int m)
    {
        int x = 0;
        while(--n)
            x = (x + m) % n;   
        return x;
    }
};

         

求1+2+3+...+n:

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

  思路:循环可以用递归替代,条件可以用&&、||等最短路替代。直接递归即可O(n)

 

不用加减乘除做加法:使用位运算法

 

把字符串转成整数:

  题目描述:将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。

  思路:从前向后ans = ans * 10 + str[i] - '0'; 注意有正负号情况;非法字符返回0即可

 

数组中重复的数字:

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

  思路:1、由于数据在0~n - 1的范围,因此我们可以吧number[i]当做下标把对应的数值+length,如果发现某个值大于等于length则该值是重复的。O(n)

     2、hashset

       3、排序

   思路1代码:

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) 
    {
    	for(int i = 0; i < length; ++i)
        {
            int index = numbers[i];
            if(index >= length) index -= length;
            if(numbers[index] >= length) 
            {
            	*duplication = index;
                return true;
            }
            numbers[index] += length;
		}
        return false;
    }
};

  

  

构建乘积数组:

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

  思路:构造一个前缀积数组,和一个后缀积数组。则ans[i] = pre[i - 1] * aft[i + 1]

 

正则表达式匹配:

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

  思路:有限自动机,DFS搜索即可

 

表示数值的字符串:

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

  思路:这是上一题的简单版本,模式串固定看字符串是否匹配。同样有限自动机,DFS搜索即可

 

 字符流中第一个不重复的字符:

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

  思路:时间复杂度O(1),空间复杂度O(n)

          1、用一个128大小的数组统计每个字符出现的次数
          2、用一个队列,如果第一次遇到ch字符,则插入队列;其他情况不在插入
          3、求解第一个出现的字符,判断队首元素是否只出现一次,如果是直接返回,否则删除继续第3步骤
  代码:
  
class Solution
{
public:
  //Insert one char from stringstream
    void Insert(char ch)
    {	 
        ++cnt[ch - '\0'];
        if(cnt[ch - '\0'] == 1)
         	data.push(ch);
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce()
    {
    	while(!data.empty() && cnt[data.front()] >= 2) data.pop();
        if(data.empty()) return '#';
        return data.front();
    }
    Solution()
    {
    	memset(cnt, 0, sizeof(cnt));    
    }
private:
	queue<char> data;
    unsigned cnt[128];
};

  

 

 链表中环的入口结点:

  思路:1、首先求得环的长度,通过两个指针一个每次走一步,另一个每次走两步。两次相等之间的步数即为环的大小len

     2、于是问题转变为求链表的倒数第k个节点的问题

     ps(有acmer提出可以通过两个指针p1, p2;p1为p2的前一个,每次通过置p1->next=null也可以达到效果,因为只有环的第一个节点有两个前驱)

 

删除链表中重复的结点:

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

  思路:按照题目要求模拟即可。对于链表其实很多时候加一个头结点对处理有很大的帮助

 

二叉树的下一个结点
  题目描述:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
  思路:分情况讨论
     1、若当前节点为NULL,返回NULL
     2、如果父节点为NULL
       2.1、如果当前节点右子树不存在,则返回NULL
       2.2、如果当前节点右子树存在,则找出右子树中最左边的节点
     3、如果当前节点为父节点的左孩子
       3.1、如果当前节点右子树不存在,则返回当前节点父节点
       3.2、如果当前节点右子树存在,则找出右子树中最左边的节点
     4、如果当前节点为父节点的右孩子
       4.1、如果当前节点右子树不存在,则向上寻找直到一个节点为其父节点的左孩子
       4.2、如果当前节点右子树存在,则找出右子树中最左边的节点
 

对称的二叉树:

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

  思路:判断前序DFS与后序DFS对应节点的val是否相同就行

class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot)
    {
      return DFS(pRoot, pRoot);
    }
    bool DFS(TreeNode *p, TreeNode *q)
    {
        if(p == NULL && q != NULL) return false;
        if(p != NULL && q == NULL) return false;
        if(p == NULL && q == NULL) return true;
        if(p->val != q->val) return false;
        if(!DFS(p->left, q->right)) return false;
        if(!DFS(p->right, q->left)) return false;
        return true;
    }
};

  

按之字形顺序打印二叉树:

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

  思路:使用两个栈,交替打印即可。注意左右字数的添加顺序即可。当然使用一个双向链表,通过插入分隔符NULL也是可以完成的

 

 把二叉树打印多行:

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

  思路:1、在每次结束后向队列中加入一个NULL,每次碰到NULL换行即可

       2、使用两个队列交叉工作即可

     3、用一个变量记录有多少个节点是上一层的即可,上一层打印完后用q.size()更新变量即可

 

序列化二叉树:

  题目描述:请实现两个函数,分别用来序列化和反序列化二叉树。这里没有规定序列化的方式。

  思路:DFS遍历,用?代表NULL,#代表节点之间的分隔符。注意(正数、负数、0)的序列化方式。对于整数与字符串之间的转化可以自己手动实现,当然也可以使用sprintf或sstream进行序列哈

 

二叉搜索树的第k个结点:

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

  思路:DFS中序遍历即可。如果多次查询可以记录每个子树的大小,这样每次查询效率就会大大提高

 

数据流中的中位数:

  题目描述:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

  思路:维护一个大顶堆和一个小顶堆。如果大顶堆中的数量小于等于小顶堆中的数量,则插入大顶堆,反之插入小顶堆。如果大顶堆的堆顶数据大于小顶堆的堆顶数据则交换堆顶数据。

       每次中位数即为大顶堆堆顶或大顶堆堆顶+小顶堆堆顶的平均值

 

滑动窗口最大值:

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

  思路:单调队列。向后移动1位时,如果队列后面的元素小于将要添加的元素的则删除队列中的元素直到大于或队列为空。然后判断队首元素的下标是否已经超过了滑动窗口的范围,超过就删除。

  代码:

class Solution {
public:
    vector<int> maxInWindows(const vector<int>& num, unsigned int size)
    {
        vector<int> ans;
        int len = num.size();if
        (size > len) return ans;
        
        for(int i = 0; i < len; ++i)
        {
        	while(!array.empty() && num[array.back()] < num[i]) array.pop_back();
            array.push_back(i);
            if(i >= size - 1)
            {
                if(array.front() < i - (size - 1)) array.pop_front();
            	ans.push_back(num[array.front()]);
            }
        }
        return ans;
    }
private:
    list<int> array;
};

 

 

矩阵中的路径:

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

  思路:回溯法,好暴力哦。。。

 

机器人的运动范围

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

  思路:回溯法,注意k可能<0。本题主要考察了回溯法,此外我们要注意数据的取值范围,这点要和面试官多交流

 

 

 

机器人的运动范围:

历时两周,利用空闲时间终于刷完了剑指offer。下一步leetcode。

come on!!!

posted @ 2016-07-27 18:23  txlstars  阅读(6373)  评论(0编辑  收藏  举报