算法题目

关于树的层遍历,记得设置保存每一层的tmp和保存所有层的res

 

1、从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

思路:利用queue,第一层节点全部压进queue,进入下一层时先弹出保存然后再压下一层的节点。

/*
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> > vec;
            if(pRoot == NULL) return vec;
  
            queue<TreeNode*> q;
            q.push(pRoot);
  
            while(!q.empty())
            {
                int lo = 0, hi = q.size();
                vector<int> c;
                while(lo++ < hi)
                {
                    TreeNode *t = q.front();
                    q.pop();
                    c.push_back(t->val);
                    if(t->left) q.push(t->left);
                    if(t->right) q.push(t->right);
                }
                vec.push_back(c);
            }
            return vec;
        }
};
View Code

 

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

思路:定义从左到右用的stackL和从右到左用的stackR,先把root节点压进stackL,进入下一层时先利用tmp保存好该层节点,再在stackR中压入下一层的节点,具体是从该层节点的右节点开始压,迭代这个过程。

/*
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> > res;
        if(pRoot == nullptr) return res;
        stack<TreeNode*> stackL;
        stack<TreeNode*> stackR;
        TreeNode* popNode;
        vector<int> tmp;
        tmp.push_back(pRoot->val);
        res.push_back(tmp);
        tmp.clear();
        stackL.push(pRoot);
        while(!stackL.empty() || !stackR.empty()){
            while(!stackL.empty()){
                popNode = stackL.top();
                stackL.pop();
                if(popNode->right){
                    stackR.push(popNode->right);
                    tmp.push_back(popNode->right->val);              
                }
                if(popNode->left){
                    stackR.push(popNode->left);
                    tmp.push_back(popNode->left->val);
                }
            }
            if(!tmp.empty()) {
                res.push_back(tmp);
                tmp.clear();
            }
            while(!stackR.empty()){
                popNode = stackR.top();
                stackR.pop();
                if(popNode->left){
                    stackL.push(popNode->left);
                    tmp.push_back(popNode->left->val);
                }
                if(popNode->right){
                    stackL.push(popNode->right);
                    tmp.push_back(popNode->right->val);
                }
            }
            if(!tmp.empty()){
                res.push_back(tmp);
                tmp.clear();
            }
        }
        return res;
          
    }
      
};
View Code

 

3、给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

思路:

假设x为环前面的路程(黑色路程),a为环入口到相遇点的路程(蓝色路程,假设顺时针走), c为环的长度(蓝色+橙色路程)

当快慢指针相遇的时候:

此时慢指针走的路程为Sslow = x + m * c + a
快指针走的路程为Sfast = x + n * c + a
2 Sslow = Sfast
2 * ( x + m*c + a ) = (x + n *c + a)
从而可以推导出:
x = (n - 2 * m )*c - a
= (n - 2 *m -1 )*c + c - a
即环前面的路程 = 数个环的长度(为可能为0) + c - a
什么是c - a?这是相遇点后,环后面部分的路程。(橙色路程)
所以,我们可以让一个指针从起点A开始走,让一个指针从相遇点B开始继续往后走,
2个指针速度一样,那么,当从原点的指针走到环入口点的时候(此时刚好走了x)
从相遇点开始走的那个指针也一定刚好到达环入口点。
所以2者会相遇,且恰好相遇在环的入口点。

最后,判断是否有环,且找环的算法复杂度为:

时间复杂度:O(n)

空间复杂度:O(1)
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        ListNode* pslow=pHead;
        ListNode* pfast=pHead;
        ListNode* pfinal=pHead;
        if(pHead==NULL|| pHead->next==NULL|| pHead->next->next==NULL) return NULL;
        do
        {
                pslow=pslow->next;
                pfast=pfast->next->next;
        }while(pslow!=pfast);
        while(pfinal!=pslow)
        {
             pslow=pslow->next;
             pfinal=pfinal->next;
        }
        return pfinal;
    }
};
View Code

 

4、请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"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)
    {
         //int count=0;
         data[ch-'\0']++;
         if(data[ch-'\0']==1) deque.push_back(ch);
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce()
    {
        while(!deque.empty()&&data[deque.front()]>=2)
            deque.pop_front();
        if(deque.empty())
            return '#';
         
            return deque.front();
    }
     
private:
    int data[128];
    deque<int> deque;
 
};
View Code

 

5、LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子.....LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。

思路:

第一种:先排序,然年后保存0的个数,跟数字间隔相抵消,能抵消则返回true(比如1和3就需要两个0才能抵消)

第二种:map计数,相同数字的直接返回false;然后保存最大最小值,条件满足max-min<5则成功;

//思路:先排序,然年后保存0的个数,跟数字间隔相抵消,能抵消则返回true
/*import java.util.Arrays;
public class Solution {
    public boolean isContinuous(int[] numbers) {
        int numOfZero = 0;
        int numOfInterval = 0;
        int length = numbers.length;
        if(length == 0){
           return false;
        }
        Arrays.sort(numbers);
        for (int i = 0; i < length - 1; i++) {
            // 计算癞子数量
            if (numbers[i] == 0) {
                numOfZero++;
                continue;
            }
            // 对子,直接返回
            if (numbers[i] == numbers[i + 1]) {
                return false;
            }
            numOfInterval += numbers[i + 1] - numbers[i] - 1;
        }
        if (numOfZero >= numOfInterval) {
            return true;
        }
        return false;
    }
}
*/
 
 
 
//思路:map,然后保存最大最小值,条件满足max-min<5则成功;
class Solution {
public:
/*顺子满足的条件:max-min<5;
                除0外其他的数字都不能重复
                传入的数组放5个元素*/
    bool IsContinuous( vector<int> numbers ) {
        if(numbers.empty()) return 0;
        int count[14]={0};//记录每个元素出现的次数;以numbers中的元素作为下标(最大K,对应13)
        int len=numbers.size();
        int max=-1;
        int min=14;
          
        for(int i=0;i<len;++i)
            {
            count[numbers[i]]++;
            if(numbers[i]==0) continue;
              
            if(count[numbers[i]]>1) return 0;
              
            if(numbers[i]>max) max=numbers[i];
              
            if(numbers[i]<min) min=numbers[i];
              
        }//end for
          
        if(max-min<5) return 1;
          
        return 0;
          
    }//end IsContinuous()
};
View Code

 

6、一个整型数组里除了两个数字之外,其他的数字都出现了偶数次。请写程序找出这两个只出现一次的数字。

思路:捕获关键词,可以联想到位操作,进一步可用异或。如果将所有所有数字相异或,则最后的结果肯定是那两个只出现一次的数字异或的结果,所以根据异或的结果1所在的最低位,把数字分成两半,每一半里都还有只出现一次的数据和成对出现的数据这样继续对每一半相异或则可以分别求出两个只出现一次的数字

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int* num2) {
        if(data.size() < 2)
            return;
           
        int resultExclusiveOR = 0;
        for(int i = 0; i < data.size(); i++){
            resultExclusiveOR ^= data[i];
        }
           
        unsigned int indexOf1 = FindFirstBitIs1(resultExclusiveOR);
           
        *num1 = *num2 = 0;
        for(int j = 0; j < data.size(); j++){
            if(IsBit1(data[j], indexOf1))
                *num1 ^= data[j];
            else
                *num2 ^= data[j];
        }
   
    }
       
  //两个数字最低位开始从右到左不同的位是多少,算出间隔index
    unsigned int FindFirstBitIs1(int num){
        int indexBit = 0;
        while(((num & 1) == 0) && (indexBit < 8*sizeof(int))){
            num = num >> 1;
            indexBit++;
        }
           
        return indexBit;
    }
       
    bool IsBit1(int num, unsigned int indexBit){
        num = num >> indexBit;
        return (num&1);
    }
       
};
View Code

 

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

思路:归并排序

看到这个题目,我们的第一反应是顺序扫描整个数组。每扫描到一个数组的时候,逐个比较该数字和它后面的数字的大小。如果后面的数字比它小,则这两个数字就组成了一个逆序对。假设数组中含有n个数字。由于每个数字都要和O(n)这个数字比较,因此这个算法的时间复杂度为O(n^2)。
我们以数组{7,5,6,4}为例来分析统计逆序对的过程。每次扫描到一个数字的时候,我们不拿ta和后面的每一个数字作比较,否则时间复杂度就是O(n^2),因此我们可以考虑先比较两个相邻的数字。
 
(a) 把长度为4的数组分解成两个长度为2的子数组;
(b) 把长度为2的数组分解成两个成都为1的子数组;
(c) 把长度为1的子数组 合并、排序并统计逆序对 ;
(d) 把长度为2的子数组合并、排序,并统计逆序对;
在上图(a)和(b)中,我们先把数组分解成两个长度为2的子数组,再把这两个子数组分别拆成两个长度为1的子数组。接下来一边合并相邻的子数组,一边统计逆序对的数目。在第一对长度为1的子数组{7}、{5}中7大于5,因此(7,5)组成一个逆序对。同样在第二对长度为1的子数组{6}、{4}中也有逆序对(6,4)。由于我们已经统计了这两对子数组内部的逆序对,因此需要把这两对子数组 排序 如上图(c)所示, 以免在以后的统计过程中再重复统计。
接下来我们统计两个长度为2的子数组子数组之间的逆序对。合并子数组并统计逆序对的过程如下图如下图所示。
我们先用两个指针分别指向两个子数组的末尾,并每次比较两个指针指向的数字。如果第一个子数组中的数字大于第二个数组中的数字,则构成逆序对,并且逆序对的数目等于第二个子数组中剩余数字的个数,如下图(a)和(c)所示。如果第一个数组的数字小于或等于第二个数组中的数字,则不构成逆序对,如图b所示。每一次比较的时候,我们都把较大的数字从后面往前复制到一个辅助数组中,确保辅助数组(记为copy) 中的数字是递增排序的。在把较大的数字复制到辅助数组之后,把对应的指针向前移动一位,接下来进行下一轮比较。
过程:先把数组分割成子数组,先统计出子数组内部的逆序对的数目,然后再统计出两个相邻子数组之间的逆序对的数目。在统计逆序对的过程中,还需要对数组进行排序。如果对排序算法很熟悉,我们不难发现这个过程实际上就是归并排序。
class Solution {
public:
    int InversePairs(vector<int> data) {
       int length=data.size();
        if(length<=0)
            return 0;
       //vector<int> copy=new vector<int>[length];
       vector<int> copy;
       for(int i=0;i<length;i++)
           copy.push_back(data[i]);
       long long count=InversePairsCore(data,copy,0,length-1);
       //delete[]copy;
       return count%1000000007;
    }
    long long InversePairsCore(vector<int> &data,vector<int> &copy,int start,int end)
    {
       if(start==end)
          {
            copy[start]=data[start];
            return 0;
          }
       int length=(end-start)/2;
       long long left=InversePairsCore(copy,data,start,start+length);
       long long right=InversePairsCore(copy,data,start+length+1,end); 
        
       int i=start+length;
       int j=end;
       int indexcopy=end;
       long long count=0;
       while(i>=start&&j>=start+length+1)
          {
             if(data[i]>data[j])
                {
                  copy[indexcopy--]=data[i--];
                  count=count+j-start-length;          //count=count+j-(start+length+1)+1;
                }
             else
                {
                  copy[indexcopy--]=data[j--];
                }          
          }
       for(;i>=start;i--)
           copy[indexcopy--]=data[i];
       for(;j>=start+length+1;j--)
           copy[indexcopy--]=data[j];       
       return left+right+count;
    }
};
View Code

 

8、HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
 
思路:如果当前的数比之前的累加值更大,则使该数成为新的累加值,并不断更新最大值
class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
     int cursum=array[0];
        int maxsum=array[0];
        for(int i=1;i<array.size();i++){
            cursum+=array[i];
            if(cursum<array[i])
                cursum=array[i];
            if(cursum>maxsum)
                maxsum=cursum;          
        }
    return maxsum;
    }
};
View Code

 

9、把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
 
思路:小于7的都是丑数,直接返回;否则前一个数(不一定是按顺序的前一个数)分别乘以2,3,5,取出最小的结果当作当前的丑数,然后更新前一个数的定位。比如 1,2,3,4,5,6中,4的产生是2乘以因子2,相对其他因子2,5乘积的最小值,而不是3乘以2,这样会得出6
这个trick很妙啊
class Solution {
public://别人的代码就是精简,惭愧啊,继续学习。
    int GetUglyNumber_Solution(int index) {
        if (index < 7)return index;
        vector<int> res(index);
        res[0] = 1;
        int t2 = 0, t3 = 0, t5 = 0, i;
        for (i = 1; i < index; ++i)
        {
            res[i] = min(res[t2] * 2, min(res[t3] * 3, res[t5] * 5));
            if (res[i] == res[t2] * 2)t2++;
            if (res[i] == res[t3] * 3)t3++;
            if (res[i] == res[t5] * 5)t5++;
        }
        return res[index - 1];
    }
};
View Code

 

 10、输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
 
思路:转化为字符串,定制比较规则,按新的比较规则排序,然后输出转化为数字的结果。
class Solution {
public:
    string PrintMinNumber(vector<int> numbers) {
        int len = numbers.size();
        if(len == 0) return "";
        sort(numbers.begin(), numbers.end(), cmp);
        string res;
        for(int i = 0; i < len; i++){
            res += to_string(numbers[i]);
        }
        return res;
    }
     
    static bool cmp(int a, int b){
        string A = to_string(a) + to_string(b);
        string B = to_string(b) + to_string(a);
        return A < B;
    }
};
View Code

 

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

 

思路:按外部顺序遍历,相同字母跳过,不相同的字母则交换,进入遍历内部顺序(递归),退出内部遍历时记得交换外部遍历的字母。外部遍历完成时排序成字典顺序后输出。

这种排序通常都是双遍历的。

class Solution {
public:
    vector<string> Permutation(string str) {
        //可以用递归来做
        vector<string> array;
        if(str.size()==0)
            return array;
        Permutation(array, str, 0);
        sort(array.begin(), array.end());
        return array;
    }
      //注意array是取引用
    void Permutation(vector<string> &array, string str, int begin)//遍历第begin位的所有可能性
    {
        if(begin==str.size()-1)
            array.push_back(str);
        for(int i=begin; i<=str.size()-1;i++)
        {
            if(i!=begin && str[i]==str[begin])//有重复字符时,跳过
                continue;
            swap(str[i], str[begin]);//当i==begin时,也要遍历其后面的所有字符;
                                    //当i!=begin时,先交换,使第begin位取到不同的可能字符,再遍历后面的字符
            Permutation(array, str, begin+1);//遍历其后面的所有字符;
              
            swap(str[i], str[begin]);//为了防止重复的情况,还需要将begin处的元素重新换回来
              
            /*举例来说“abca”,为什么使用了两次swap函数
                交换时是a与b交换,遍历;
                交换时是a与c交换,遍历;(使用一次swap时,是b与c交换)
                交换时是a与a不交换;
                */
        }
    }
};
View Code

 

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

 

思路:和减去本节点数后,若为0则添加路径到res;否则递归下去,递归过程是减去下一个节点数,再判断是否位0

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
    vector<int> temp;
    vector<vector <int> >res;
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
       if(root==NULL) return res;
       temp.push_back(root->val);
       if(expectNumber-root->val==0 && root->left==NULL && root->right==NULL)
           res.push_back(temp);
        FindPath(root->left,expectNumber-root->val);
        FindPath(root->right,expectNumber-root->val);
        if(!temp.empty())
        {
            temp.pop_back();  //记住pop出来,否则重复
        }
        return res;
    }
};
View Code

 

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

 

思路:从A根节点开始判断B是不是A的字子树,若是则返回一;否则A递归到左右节点,再判B是不是A的子树。

class Solution {
    bool isSubtree(TreeNode* pRootA, TreeNode* pRootB) {
        if (pRootB == NULL) return true;
        if (pRootA == NULL) return false;
        if (pRootB->val == pRootA->val) {
            return isSubtree(pRootA->left, pRootB->left)
                && isSubtree(pRootA->right, pRootB->right);
        } 
        else 
             return false;
    }
public:
    bool HasSubtree(TreeNode* pRootA, TreeNode* pRootB)
    {
        if (pRootA == NULL || pRootB == NULL) return false;
        return isSubtree(pRootA, pRootB) ||
            HasSubtree(pRootA->left, pRootB) ||
            HasSubtree(pRootA->right, pRootB);
    }
};
View Code

 

14、输入一个链表,反转链表后,输出新链表的表头。

 

思路:

1:递归反转,从尾到头更新

2:遍历,从头到尾更新

/*
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* pReverseNode=ReverseList(pHead->next);
          
        //再将当前节点设置为后面节点的后续节点
        pHead->next->next=pHead;
        pHead->next=NULL;
          
        return pReverseNode;
          
    }
};

//从头到尾遍历反转,前后断开
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
          
        if(pHead==NULL) return NULL;//注意程序鲁棒性
          
        ListNode* pNode=pHead;//当前指针
        ListNode* pReverseHead=NULL;//新链表的头指针
        ListNode* pPrev=NULL;//当前指针的前一个结点
          
        while(pNode!=NULL){//当前结点不为空时才执行
            ListNode* pNext=pNode->next;//链断开之前一定要保存断开位置后边的结点
              
            if(pNext==NULL)//当pNext为空时,说明当前结点为尾节点
                pReverseHead=pNode;
   
            pNode->next=pPrev;//指针反转
            pPrev=pNode;  //指针更新
            pNode=pNext;
        }
        return pReverseHead;
   }
};
View Code

 

posted @ 2018-08-10 15:46  热之雪  阅读(224)  评论(0编辑  收藏  举报