CycleList Trap

之前总结了这种带环链表检测,包括两个链表相交,对于快慢指针如何确保找到环头还进行了proof,但是写代码的时候,还是犯了一个错误:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head==NULL) return NULL;
        ListNode* slow=head, *fast=head->next;
        while(slow!=NULL && fast !=NULL)
        {
            if(slow==fast) break;
            slow=slow->next;
            if(fast->next==NULL) return NULL;
            fast=fast->next->next;
        }

        if(slow==NULL || fast ==NULL) return NULL;
        ListNode* p=head;
        while(p!=slow)
        {
            p=p->next;
            slow=slow->next;
        }
        return p;
    }

};

我把slow 和fast的起始就错开了一个结点,主要是怕后面while
会一开始误判相等而推出循环,但是这样证明fast从起点开始走2k+1, slow走k,因此结果是错的,所以必须fast slow同一起跑线,这样才公平嘛,人生也是如此。因此后面的break语句放到后面,或者while改成我很少写的
至少执行一遍的do while loop。
于是改写后的代码:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head==NULL) return NULL;
        ListNode* slow=head, *fast=head;
        do
        {
            slow=slow->next;
            if(fast->next==NULL) return NULL;
            fast=fast->next->next;
            if(slow==fast) break;
        }while(slow!=NULL && fast !=NULL);

        if(slow==NULL || fast ==NULL) return NULL;
        ListNode* p=head;
        while(p!=slow)
        {
            p=p->next;
            slow=slow->next;
        }
        return p;
    }

};

我当时还在想如果整个就是一个循环链表 ,该返回什么?这个代码又会返回什么? 以为测试样例不会出现,实际还真出现了,模拟一下,就是返回链表头结点作为环头,实际也是对的,链表从结点开始,第一个进入环的
就是头结点嘛~

Posted by richard爱闹 - 7月 19 2014
如需转载,请注明: 本文来自 Richard

ReOrderList

再次遇到又爱又恨的linklist,这是必须卖过的坎,好像某软喜欢考Linklist相关的,包括CopyRandomLinklist,两个Linklist第一个交点等等。
这道是13年MS笔试的最后一题,几个月前做的时候,是纸上写,好像不是太难,1 2 3 4 -> 1 4 2 3, 也即两项index和相同这样一个一个pair排列,立马要想到处理奇偶个结点的情况。
可以这么思考,index 从 1 2 3 4 map到 1 4 2 3, 将后面逆置,然后逐个插入到前面的linklist,思路不难,但是Linklist一定要写好,还是要把诸多情况考虑清楚的。

  1. 遍历求长度 n
  2. 找到后半的第一个结点 n/2
  3. 后半reverse, n/2
  4. 后半插入到前半中 n/2

第二步要考虑奇偶性,如果奇数需要多移动一个点,后半reverse使用三个指针,注意不丢next指针,插入也是这样。

附上代码:

 void reorderList(ListNode *head)
 {
     if(head==NULL || head->next==NULL || head->next->next==NULL) return ;

     ListNode* p=head;
     int l=0;
     while(p) p=p->next,l++;//imitate adhoc's code style


     p=head;
     int i=0;
     ListNode* prev;
     while(i<l/2) prev=p,p=p->next ,i++;//imitate adhoc's code style

     if(l%2==1) prev=p,p=p->next;//odd, p to be (l-1)/2+1 node, prev to be center


     ListNode* pnext, *pprev=NULL;
     while(p!=NULL)//reverse, pprev, p,pnext
     {
         pnext=p->next;
         p->next=pprev;
         pprev=p;
         p=pnext;
     }

     prev->next=NULL;//center link to reversed list node, avoid first part last node point to second part first node, which lead to loop linklist
         //pprev;

     p=head;ListNode* q=pprev, *qnext;
     while(q!=NULL&&p!=NULL)
     {
         pnext=p->next;//record p original next
         qnext=q->next;

         p->next=q;
         q->next=pnext;

         p=pnext;
         q=qnext;
     }
 }

还好这次没有调试太久,遇到的问题有:

  1. Showlist测试忘记pnext导致死循环= =
  2. 代码敲错的问题,while里面三句逗号连接,模仿一博,结果i++前面敲成; 号了o(╯□╰)o reverse最后一个p=pnext 敲成p=p->next 这种问题最2了,浪费很多时间
  3. 最关键的一个bug,后面reverse之后,我之前是把他和前面链接成一个链表了,千万不要,因为后面p q往后指的时候,最后一次loop p链最后一个指到q第一个了,于是链表循环了,除非里面加逻辑特殊处理,简洁的
    办法就是两个链表断开,不要链接,因为后面链接的头结束reverse后是知道的(pprev),不会丢失,只要不丢point就行了。

Posted by richard爱闹 - 7月 18 2014
如需转载,请注明: 本文来自 Richard

Pointer 传递陷阱

今天试了一道leetcode简单题,发现出现了一个很奇怪的错误,leetcode是按照层序的顺序来构建二叉树的,如果当前层都没有元素了,就不print,也就结束了,如果父亲没有 jing号(markdown语法都不知道怎么print jing号,他是强调的含义o(╯□╰)o)
也不print,总是有点怪怪的,可能和CreatBT这个函数有关,举个例子
1
/ \
2 3
/
4
\
5

构建BT的序列是 “{1,2,3,#,#,4,#,#,5}”, 如果二叉树显示的不好。详见https://oj.leetcode.com/problems/symmetric-tree/

思路就是层序遍历,因为要区分层,所以采用了编程之美的经典算法(我没有看清题目说明,题目说了初始的next都是NULL,其实不需要区分层的,普通队列层序遍历就可以了,没有赋next的刚好都是应该赋NULL = = 就当又温习了一遍这个算法),
vector模拟一个队列,利用current和last区分层,具体详见编程之美。题目说的constant空间 感觉好瞎啊,这个必须遍历BT,要么递归,要么用栈(先中后序),要么队列(层序),logn的空间我感觉都少不了的。。。。队列极端装了一层的点,
最大是最多结点的一层,2^(h-1)=n/2, h=下限(logn)+1 或者 上限(log(n+1)), 栈则是h,O(logn),递归也是栈一样,O(logn)

void connect(TreeLinkNode *root)
{
    if(root==NULL) return;
    vector<TreeLinkNode* > vec;
    vec.push_back(root);
    int current=0;
    TreeLinkNode* lastp;
    while(current<vec.size())
    {
        int last=vec.size();//the final one's next one
        int i=0;
        while(current<last)
        {
            TreeLinkNode* p=vec.at(current);
            if(i>0)
                lastp->next=p;//level first one, do not have last

            lastp=p;
            cout<<p->val;
            if(p->left)
                vec.push_back(p->left);
            if(p->right)
                vec.push_back(p->right);
            current++;
            i++;
        }
        lastp->next=NULL;//level last next is null
        cout<<endl;
    }
}

后来发现第一个case死活过不了,而且感觉很奇怪,
Input: {0}
Output: 0
Expected: {0,#}
{0,#}是啥样的二叉树啊,如果是0 输出就是0啊,如果是有个左(右)孩子1,就是{0,1,#}({0,#,1})。。。不知道这个啥意思。。。导致现在都没过。。。
悲剧了,昨天收到邮件,一个回复说你的cout要删掉= = o(╯□╰)o 我才知道这个低级的错误,往往我们会忽视最简单的错误,而且写的这个可以处理各种二叉树,于是后面一道也过了。。。。

另外看到一个递归算法,中科大的一个童鞋写的,他也利用了初始值为NULL这个性质,将BT分为left right 两个BT,然后递归,两个子BT处理完了,还有一部分next没处理,也即两个BT 每一层,left最后一个指向right第一个,用一个用个
while一直探下去直到没有了为止

void connect(TreeLinkNode *root) {  
        if( root == NULL || root->left == NULL || root->right == NULL )        //{0}  有一个为空就不处理,其实不会出现left right一个空一个不空的,因为题目说了完全BT,而且即使有了,算法也会出问题。。。。
        {  
           return;  
        }  

        TreeLinkNode *p, *q;  
        p = root->left;  
        q = root->right;  
        p->next = q;  
        while( p->right != NULL && q->left != NULL )//处理两个子BT之间的那些指针  
        {  
            p = p->right;  
            q = q->left;  
            p->next = q;  
        }  

        connect( root->left );  
        connect( root->right );  
    }

看起来是elegant, 但是不能处理非完全二叉树,很有局限性,例如1左->2右->3, 2只有左子树4,无右子树,那么这个4是需要指向右孩子3子树的下一层第一个结点,这个指针指不到这个地方,这大概也是为啥这个题目分了两类吧。。。。
https://wangxj.blog.ustc.edu.cn/?p=31

于是乎打算自己构建一颗二叉树来测,回想起当年CM老师教的(先序遍历)递归构建二叉树的算法(中序和后序递归建立BT可能会有问题,不太确定),当时记得融神还不太熟悉这个算法,自己一个一个的手动insert左孩子或者右孩子。。。
于是乎我写一遍,一遍没好,调递归算法是最蛋疼的,你无法想象蛋会有多疼,一层一层的局部变量进去,我是先序或者某个序输出来看的,递归一定要记得有出口。后来终于发现了一个忽视的问题,指针传递其实也是值传递,也即传递这个指针变量的
值(地址值,指向一个变量存储的地址),然后里面都是new出来的结点,于是穿进去之后,会是OS分配的一个新的某个地址,于是传入的参数就指不到这个地址了,于是树挂了。。。。我发现CM老师的课件也有这个问题好像当时还没人纠正。。。
这里应当也必须改为指针的引用传递,这样才可以保证传入的是指针变量,让这个指针变量申请一个新的堆空间,地址存在这个指针变量里。

修改后的先序遍历构建二叉树代码(-1 表示此处空)

void Create(TreeLinkNode* & root)
{
    int data;
    cin>>data;
    if(data == -1) root=NULL;
    else
    {
        root=new TreeLinkNode(data);
        Create(root->left);
        Create(root->right);
    }
}

这个递归其实也是有递归出口的,看起来大家都习惯了一上来必须是递归出口。如果输入-1,就不递归了,递归出口的定义就是这个分支不会继续递归下去,而是在有限步后就停止了,这也是某歌07年的笔试题里一个选择提到过的。
另外自己Copy代码的时候,经常会出问题,虽然fawks大神当时也建议我copy一些,提高效率,但是我总会忘记把改改的改完,例如先序Copy到中序,只有定义的函数名改了,里面递归调用没改,于是出现了一种全新的遍历顺序 #_#

Posted by richard爱闹 - 7月 17 2014
如需转载,请注明: 本文来自 Richard

bit shift

位运算在原来学数逻时候是第一次系统接触,包括后面计组等课都陆续加以巩固,但是
实际cpp的时候却很少用,其实乘2 除2的操作到了底层都是位运算,左移右移可以极大的提高效率,据某度某科说提高60%
于是稍微回顾一下,用的数是int类型,4B,32bit,补码编码,-2^31-2^31-1, 据说某歌面试极其喜欢位运算。

正数右移1位,高位补0,等于整数/2操作,奇数丢失精度
负数右移1位,高位补1,等于整数/2操作,奇数丢失精度,但是-1右移等于-1,与-1/2=0结果不一致,好像奇负数都会丢失精度,好像右移和/2 还会差一。。。例如-5/2=-2,-5>>2=-3….

正数左移1位,低位补0,等于整数乘2操作,除符号位最高位为1(01000…000~01111…11111这2^30个数)会上溢出(>=2^30,乘2后>=2^31超出2^31-1的正数上界)导致结果出错
负数左移1位,低位补0,等于整数乘2操作,除去符号位,最高位为0时下溢出(从十进制数来看,-2*30左移乘2到-2^31刚好到负数下界,-(2^30+1)开始下溢,一直到-(2^30+2^30-1)=-(2^31-1) 外加特殊的
-2^31都会下溢。按照补码取反加1,也即10000…0001到101111…1111外加1000…00000这2^30-1+1=2^30个数会下溢,所以总结来看,就是如果负数的话,当数值部分最高位为0) 导致结果出错。

然后之前还有总结神奇的异或操作,求数组中唯一不出现偶数次的数,与可以判断更快的判断奇偶性(与0X0001与)等等。

Posted by richard爱闹 - 7月 16 2014
如需转载,请注明: 本文来自 Richard

C++语法

C++语法,还有一块,泛型编程,和继承都丢的差不多了,泛型编程好像没怎么用过。。。
据说好像金融方面,C++比较缺。。。

另外今天倒腾了下Code Block,及其蛋疼,终于体会到VS debug的强大了,VS是非常好的方便debug的IDE。。。

另外发现algorithm里有merge函数,包括sort,merge其实不一定要用容器的iterator作为参数,数组指针也是一样的,但是要记住,sort(first,last), 表示的排序范围是 [first, last),
这也和iterator的 end()表示容器最后一个元素的后一个元素相吻合。

sort(begin,end,compare())里的compare函数,

bool compare(cosnt T& a,const T&b)
{
return a.data<b.data;//a<b, true, means ascending sort
}

ctsdio stdio.h 也即c**.h 这两个lib的差别
后者现有,因为是里面的,后来有了C++,为了和他兼容,名字改为cstdio,内容几乎一样,只不过都在std::这个名字空间里,所以如果不加using namespace std, 会报错的,如果写C++代码建议用前者。
http://bbs.csdn.net/topics/280066862

另外今天读了一下张一博大神的代码,先不说DP算法,主要是编码风格和习惯,里面有些东西是ACMer的习惯,定义很多宏和typedef 加快后面coding的速度,包括连vector的push_back都缩写为PB了,确实这样后面的coding可以更加集中于算法,
而不是某些库函数多敲几个字母。像这种三元组丢到vector里,我绝对会定义node,里面三个成员变量,他则是用了两次pair,外加宏定义直接变为MP3了。。。
define MP3(a,b,c) make_pair( a , make_pair(b,c) )
还有long long类型的数据类型由于多了字母,后面频繁出现,因此缩写为LL是很明智的做法,也是一个typedef
typedef long long LL ;
包括还有二维数组,我一般都不会考虑vector<> a[] 这样的vector数组,或者二维数组,或者vector < vector >类似这种,可能leetcode看多了。。。不过这种vector 数组好处是每个长度可以变化,不过vector嵌套vector也可以,二维数组
用下标控制也可以的。

另外还有一个小细节,我注意到了,代码里很多while里面多个,然后一个; 我一直不知道while 或者if里面,只要,隔开 都算一句,所以相当于不用括号也可以多写几个statement,我之前好像一直都没怎么尝试过这种写法。

if ( j == ZERO ) F[i][j] = 1 , G[i][j] = 0 ;

BSS段存放的是未初始化的全局和静态变量,数据段存放的是初始化后的全局和静态变量

Posted by richard爱闹 - 7月 15 2014
如需转载,请注明: 本文来自 Richard

MatrixSpiral

顺时针打印,看起来是普通的程设题,不需要复杂的数据结构,但是还是会遇到很多问题,边界和循环很复杂。

我的思路就是一次打印一圈,然后往里循环,直到里面没有了为止,但是例如第一行是全部打印,还是留一个,因此有两种方案,我觉得留一个这种可以保持四条边统一,所以选择这种,
事实好像前者的答案代码会更加清晰o(╯□╰)o
然后先写最外层的4个循环,然后试图加个level参数k进4个loop,倒不难,写出了伪代码,

k=0
while()
{
    for(i=1+k,j=1+k->n-1-k) ++

    for(j=n-k,i=1+k->m-1-k) ++

    for(i=m-k,j=n-k->2+k) --

    for(j=1+k,i=m-k->2+k) --

    k++;
}

但是测试的时候,发现3_3 5_5这种 矩阵中心的打印不到,后来发现了一个问题,就是最后剩的一行,一列这种,于是总结下行的时候如果行数
等于上行的,也即上下重叠,说明下行没元素了,显然右列也没元素了。右列也是如此,于是加了一下逻辑判断的代码,并且最早也漏了循环结束条件,这里正好补上,以为对了= =

k=0
while()
{
    for(i=1+k,j=1+k->n-1-k) ++

    for(j=n-k,i=1+k->m-1-k) ++


    if(m-k==1+k)
    {
        spiralnum.push_back(matrix.at(1+k-1).at(n-k-1));
        break;
    }

    for(i=m-k,j=n-k->2+k) --


    if(1+k==n-k)
    {
        spiralnum.push_back(matrix.at(m-k-1).at(n-k-1));
        break;
    }

    for(j=1+k,i=m-k->2+k) --

    k++;
}

后来发现,4*4的死循环了,于是发现终止未必是下行和上行重合,或者右列和左列重合,也可能是下行是上行的前一行,或者右列是左列右一列,而且几个case总结出等的时候才会漏掉最后一个元素,
于是加上如下判断

k=0
while()
{
    for(i=1+k,j=1+k->n-1-k) ++

    for(j=n-k,i=1+k->m-1-k) ++

    if(m-k<=1+k)
    {
        if(m-k==1+k)
            spiralnum.push_back(matrix.at(1+k-1).at(n-k-1));
        break;
    }

    for(i=m-k,j=n-k->2+k) --

    if(1+k>=n-k)
    {
        if(1+k==n-k)
            spiralnum.push_back(matrix.at(m-k-1).at(n-k-1));
        break;
    }

    for(j=1+k,i=m-k->2+k) --

    k++;
}

提交leetcode 发现还是WA了= = 3_2的case多打印了一个数,后来发现,还可能是 上行的时候就没元素了,于是想再次改,发现怕前面的case又过不了,想到万能方法,直接每次for看是否vector是否装满了,
于是改的以下逻辑混乱的代码,AC了= =

vector<int> spiralOrder(vector<vector<int> > &matrix) {
        int m=matrix.size();//row num
        vector<int> spiralnum;
        if(m<=0) return spiralnum;
        int n=matrix.at(0).size();//column num
        if(n<=0) return spiralnum;

        int k=0;//level id

        int i,j;
        while(1)
        {
            if(spiralnum.size()==m*n) break;
            for(i=1+k,j=1+k;j<=n-1-k;j++)
                spiralnum.push_back(matrix.at(i-1).at(j-1));
            if(spiralnum.size()==m*n) break;
            for(j=n-k,i=1+k;i<=m-1-k;i++)
                spiralnum.push_back(matrix.at(i-1).at(j-1));
            if(spiralnum.size()==m*n) break;

            if(m-k<=1+k)
            {
                if(m-k==1+k)
                    spiralnum.push_back(matrix.at(1+k-1).at(n-k-1));
                break;
            }
            for(i=m-k,j=n-k;j>=2+k;j--)
                spiralnum.push_back(matrix.at(i-1).at(j-1));

            if(1+k>=n-k)
            {
                if(1+k==n-k)
                    spiralnum.push_back(matrix.at(m-k-1).at(n-k-1));
                break;
            }
            for(j=1+k,i=m-k;i>=2+k;i--)
                spiralnum.push_back(matrix.at(i-1).at(j-1));

            k++;
        }

        return spiralnum;
}

后来看了剑指Offer,他是上行全部print,包括最后一个。而且要一开始想清楚,我想到后面,感觉好像就漏了几个case,再改可能就像一个project一样,看起来一个bug,一改,出来几千个o(╯□╰)o
逻辑已经混乱了,写这种行列的代码想起了我当年设计五子棋AI的VB代码,也是改了好多趟,最后还是有bug,只不过看起来还不错了。。。。。

另外发现其实自己有个不好的习惯,费了劲写完代码后,不太愿意用各种情况都包含的case去测试,这个是以后软件测试很重要的,要先想好,分为哪些测试用例。
最后,我看了下剑指Offer的思路 写了一个逻辑比较漂漂的MatrixSpiral算法。后面debug发现 自己右列的 for循环 rowend 写成了colend,结果测试用例 只有最后一个case差了一个元素,这简直是一个奇迹!!
我重新 总结了下算法思路,按照剑指Offer的,每一次(一行一列)不是 我之前想的都留一个元素,而是有的都print。按照书上的,每一次rowstart=colstart,即为start,
然后colend=col-1-start, rowend=row-1-start

于是四个循环有如下的伪代码,这里上行保证至少一行就够了,start<=rowend, 右列也是保证一列可以了,不会print错,如果一列镁元素(例如一个元素被作为行print了),后面for会控制好的。

if(start<=rowend)//at least one row, maybe none number left
{
for(int j=start;j<=colend;j++)//row: start col: start->colend, if col has num, start must be valid
spiralnum.push_back(matrix.at(start).at(j));
}

    if(start<=colend)//at least one row
    {
        for(int i=start+1;i<=rowend;i++)//col: colend row:start+1->colend
            spiralnum.push_back(matrix.at(i).at(colend));
    }

    if(start+1<=rowend)//at least two rows
    {
        for(int j=colend-1;j>=start;j--)//row: rowend col: colend-1->start
            spiralnum.push_back(matrix.at(rowend).at(j));
    }
    if(start+1<=colend)//at least two rows
    {
        for(int i=rowend-1;i>=start+1;i--)//col: start row: rowend-1->start+1
            spiralnum.push_back(matrix.at(i).at(start));
    }

但是注意,下行要保证至少两行,否则会重复print上行print的,左列也是一样,start+1<=rowend, 以及start+1<=colend, 我习惯<=,因为比较清晰范围,<的话大脑还要再计算一次。
那么这是每一次的level 4个for print 怎么表示结束呢? 我后来总结,如果一行 或者一列 都没有了,就结束了,故

if(start>colend || start> rowend)
    break;

剑指Offer那个太扯了,谁会去这么想。。。。感觉这个比较接地气

1
附上逻辑很漂漂的代码

~

vector<int> spiralOrder_Offer(vector<vector<int> > &matrix)
{
    int row=matrix.size();//row num
    vector<int> spiralnum;
    if(row<=0) return spiralnum;
    int col=matrix.at(0).size();//column num
    if(col<=0) return spiralnum;

    int start=0;//row start, and col start

    //bool PrintNum=false;
    while(1)
    {
        int colend=col-1-start;
        int rowend=row-1-start;
        if(start>colend || start> rowend)
            break;

        if(start<=rowend)//at least one row, maybe none number left
        {
            for(int j=start;j<=colend;j++)//row: start col: start->colend, if col has num, start must be valid 
                spiralnum.push_back(matrix.at(start).at(j));
        }

        if(start<=colend)//at least one row
        {
            for(int i=start+1;i<=rowend;i++)//col: colend row:start+1->colend
                spiralnum.push_back(matrix.at(i).at(colend));
        }

        if(start+1<=rowend)//at least two rows
        {
            for(int j=colend-1;j>=start;j--)//row: rowend col: colend-1->start
                spiralnum.push_back(matrix.at(rowend).at(j));
        }
        if(start+1<=colend)//at least two rows
        {
            for(int i=rowend-1;i>=start+1;i--)//col: start row: rowend-1->start+1
                spiralnum.push_back(matrix.at(i).at(start));
        }

        start++;

    }
    return spiralnum;
}

于是乎乘热打铁,把另一道相关的也做了,有点类似于反函数,之前是根据matrix 顺时针螺旋打印num,现在是已知num顺序,把他顺时针螺旋顺序赋给matrix,只有稍微改改就行了问题不大。

vector<vector<int> > generateMatrix(int n)
{
    //int row=matrix.size();//row num

    vector<vector<int> > matrix(n,n);
    //vector<int> spiralnum;
    //if(row<=0) return spiralnum;
    //int col=matrix.at(0).size();//column num
    //if(col<=0) return spiralnum;

    int row=n,col=n;
    int start=0;//row start, and col start

    //bool PrintNum=false;
    int k=1;
    while(1)
    {
        int colend=col-1-start;
        int rowend=row-1-start;
        if(start>colend || start> rowend)
            break;

        if(start<=rowend)//at least one row, maybe none number left
        {
            for(int j=start;j<=colend;j++)//row: start col: start->colend, if col has num, start must be valid 
            {
                matrix.at(start).at(j)=k;
                k++;

                //spiralnum.push_back(matrix.at(start).at(j));
            }
        }

        if(start<=colend)//at least one row
        {
            for(int i=start+1;i<=rowend;i++)//col: colend row:start+1->colend
            {
                matrix.at(i).at(colend)=k;
                k++;
                //spiralnum.push_back(matrix.at(i).at(colend));
            }
        }

        if(start+1<=rowend)//at least two rows
        {
            for(int j=colend-1;j>=start;j--)//row: rowend col: colend-1->start
            {
                matrix.at(rowend).at(j)=k;
                k++;
                //spiralnum.push_back(matrix.at(rowend).at(j));
            }
        }
        if(start+1<=colend)//at least two rows
        {
            for(int i=rowend-1;i>=start+1;i--)//col: start row: rowend-1->start+1
            {
                matrix.at(i).at(start)=k;
                k++;
                //spiralnum.push_back(matrix.at(i).at(start));
            }
        }

        start++;

    }
    return matrix;
}

这个问题倒不是太大,只要放个k进去,从1到n^2就可以了,但是我遇到了一个二维vector初始化的诡异问题,而且这个之前没有用过,好发现了VS与GCC, GCC3.2.3和4.4.7之间的微妙区别。
我先是这么弄的

vector<vector<int>> matrix(n,n);

VS10过了,提交,出现Compile error,提示两个>>离得太近,于是改成

vector<vector<int> > matrix(n,n);

还是编译错误,还是这行的问题,于是拿windows下的gcc3.2.3测,也过了,有点不解。然后拿到Linux下的另一个版本GCC4.4.7,出现error了,果然GCC不同版本都会有问题,
这个也不是C++11新增的,于是查看帖子
http://www.cnblogs.com/wei-li/archive/2012/06/08/2541576.html
发现要这么初始化二维vector

vector<vector<int> > matrix(n,vector<int>(n));

也即二维vector只有装入n个 一维的vector的 构造函数参数列表,所以改了提交。

vector确实麻烦一些,第一他一开始是空的,而且不能访问越界,不像数组,很容易定义一个很大的数组。

Posted by richard爱闹 - 7月 15 2014
如需转载,请注明: 本文来自 Richard

longest consecutive sequences

看到July群里有人提问,大家讨论很激烈。

Given an unsorted array of integers, find the length of the longest consecutive elements sequence.

For example,
Given [100, 4, 200, 1, 3, 2],
The longest consecutive elements sequence is [1, 2, 3, 4]. Return its length: 4.

Your algorithm should run in O(n) complexity.

一开始乍看这题,首先反应就是sort然后遍历就可以了,但是时间复杂度不行。线性的算法貌似都没有思路,后来看到有人说radix sort,我一直都没反应过来,看了那些sort,居然
把这茬忘记了,于是回顾DS的相关课件,发现有个算法复杂度没弄明白,PPT写O(d(n+rd)) 但是我感觉就是O(d(n+r)), 其中d是最大数的位数,r是基数(10), 每一躺就是遍历链表,把他丢到当前位数合适的桶里,
然后把链表实现的队列链接起来,n+r感觉是,不知道为啥。。。

后来又有一路hash流,感觉hash一般都不会是最好的算法,因为一个hash函数的非普遍适用性使得一次查找O(1)值得怀疑,但是已经没有其他更好的方案了o(╯□╰)o
于是想到先建立hash,然后逐个看相邻的是否在里面,也是因为有频繁的查找,才想到hash,但是发现直接用 这样的map可能会出现漏的情况,例如 0 1 2 -1,到了-1可能只能合并0,其实最后更新的(0 1 2)长为
3的序列更新在1 2那里,没有传递到0那里,导致出错,而且可能会出现重复合并等等,所以加一个left right 逻辑变量,判断是否已经合并过了,于是可以设计代码了

#include <map>
#include <set>
#include <queue>
#include <stack>
#include <math.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <limits.h>
#include <string.h>
#include <string>
#include <algorithm>
#include <iomanip>
#include <unordered_map>

#define Min(a,b) (((a) < (b)) ? (a) : (b))
#define Max(a,b) (((a) > (b)) ? (a) : (b))
#define read freopen("in.txt","r",stdin)  
#define write freopen("out.txt","w",stdout)
using namespace std;

struct length
{
    int count;
    bool left;
    bool right;
};
int longestConsecutive(vector<int> &num) {
        unordered_map<int,length> map;
        length l;
        l.count=1;l.left=false;l.right=false;
        for(int i=0;i<num.size();i++)
        {
            if(map.find(num.at(i))==map.end())
                map[num.at(i)]=l;
        }
        unordered_map<int,length>::iterator it;
        for(it=map.begin();it!=map.end();it++)
        {
            if(it->second.left==false)
            {
                if(map.find(it->first-1)!=map.end() && map[it->first-1].right==false)
                {
                    int sumlength=map[it->first].count+map[it->first-1].count;
                    map[it->first].count=sumlength;
                    map[it->first-1].count=sumlength;
                    it->second.left=true;
                    map[it->first-1].right=true;
                }
            }
            if(it->second.right==false)
            {
                if(map.find(it->first+1)!=map.end() && map[it->first+1].left==false)
                {
                    int sumlength=map[it->first].count+map[it->first+1].count;
                    map[it->first].count=sumlength;
                    map[it->first+1].count=sumlength;
                    it->second.right=true;
                    map[it->first+1].left=true;
                }
            }
        }

        it=map.begin();
        int max=it->second.count;
        for(it=map.begin();it!=map.end();it++)
        {
            if(max<it->second.count)
                max=it->second.count;
        }
        return max;
}
int main()
{
    int a[]={1,3,5,2,4};
    vector<int> num(&a[0],&a[5]);
    cout<<longestConsecutive(num)<<endl;
    return 0;
}

但是发现leetcode跑出来居然和local的不一样,我也没用静态变量啊,还有全局变量,而且之前直接贴本地完整代码,还被管理员封了贴o(╯□╰)o
目前还不知道为何。。。 (问题找到了,leetcode的g++ 编译选项没有 std=c++0x, 不支持C++11,unordered_map是C++11里的,我用的VS其实是支持了C++11,而我也没办法改后台编译的参数选项,
于是出现了不可预料的结果,于是按照管理员说法改成map,居然还过了,这个已经不是线性复杂度,是O(nlon)了,leetcode看来时间复杂度不像ACM那么严: ) 而且管理员说还有更elegant的算法,不需要用到
类似hash这种复杂的数据结构,我目前想到的只有radix sort,这个也是线性的,输入是int,最多丢12次桶,所以还是线性复杂度)

另外还有些C++知识总结:

  1. C++ class构造函数用冒号初始化的时候顺序是成员变量定义的顺序,和初始化顺序无关,所以会有些陷阱题
    class A
    {
    int n1;
    int n2;
    A(): n2(1) n1(n2+1)
    }
    这里面其实n1先初始化,但n2未初始化,是野值,于是n1也是野值,n2=1

  2. sizeof作用于一个内部空的结构体返回多少字节

答案是1 不是0, 试想下,如果是0,这个结构体不占空间,那我定义的一个这个对象存在那里,岂不是不存在了?怎么获取他的地址,已经如何判断两个对象哪个是哪个?
虽然这个确实不是常规知识,但是通过逻辑思考,后面如何使用这些变量,可以推测出不为0,VS中是认为1的。

另外还有之前和高富帅想leetcode 通过率最低的一道题,判断二维平面 共线最多的点,想到用hash提高查找效率,但是一条直线
是两个参数决定的,不可能降为1个,于是想有没有两个自变量的hash函数? 一般都是一个变量,作为key来hash的?

还看到剑指Offer有道题目是说,数组的数合并为一个数的最小数是多少?
这个感觉思路就是先看第一位,如果各不相同,就直接sort第一位就解决了,否则,对于第一位有重复的 再看,如果两两个重复,就继续看两个数的下一位,一直下去,如果多个数的话,就有点麻烦了,
排列情况比较多,还要看他的各位和其他数位数的关系,有点乱。。。感觉目前有的思路是这样。。。

Posted by richard爱闹 - 7月 14 2014
如需转载,请注明: 本文来自 Richard

GetOneTimes

计算从1到n的数字中1出现的次数
这道题目一看,估计是找规律,总结一套规则出来。首先暴力方法,遍历1-n,计算每个数所有1的个数,我当时居然写的一个方法更加搓,我都忘了辗转想除法,
以为只能从高位开始,然后先计算unsigned int最多有12位,然后从10^11此方开始,除,取结果,然后减去高位数,这样直到个位。。。

int EnumGetOneNum(int n)
{
    if(n<=0) return 0;

    int onesum=0;
    for(int i=1;i<=n;i++)
    {
        int maxten=11;//signed int or unsigned int, max 12 digit
        while(i>0)
        {
            int digit=i/pow(10.0,maxten);
            if(digit==1)
                onesum++;
            i-=digit*pow(10.0,maxten);
            maxten--;
        }
    }
    return onesum;
}

后来发现自己连基本的辗转相除法都忘了 o(╯□╰)o 可以从各位先取余,再除,可以直接从个位开始逐个获得digit,这个比从高位好,因为你不知道位数是多少,我傻乎乎的从12位开始。。
附上这个代码

int LowToHighDigit_EnumGetOneNum(int n)
{
    if(n<=0) return 0;

    int onesum=0;
    for(int i=1;i<=n;i++)
    {
        int maxten=11;//signed int or unsigned int, max 12 digit
        while(i>0)
        {
            int digit=i%10;
            if(digit==1)
                onesum++;
            i=i/10;
            //int digit=i/pow(10.0,maxten);
            //if(digit==1)
            //    onesum++;
            //i-=digit*pow(10.0,maxten);
            //maxten--;
        }
    }
    return onesum;
}

接下来才是重头戏,不愧我想了那么久。。
我开始总结,按照位数来统计,先算个位的1,再十位 百位。。。先看10位的,10-19有10次,110-119有10次,感觉就是除掉前10个,后面差为100的等差,然后里面前面连续10个,后面都没有,
大致计算公式是
n-10=100x+y (n>=10)
sum=10
x+10(y>=10)
10*x+y+1(y<10)

于是百位也是类似
n-100=1000x+y (n>=100)
sum=100
x+100(y>=100)
100*x+y+1(y<100)

一开始还以为个位就是1 ,sum初值为1,然后从十位开始,后来测试的时候是错的,然后个位也是按照上面的,于是就有如下的代码 o(╯□╰)o

int GetOneNum (int n)
{
    if(n<=0) return 0;
    int sum=0;
    int ten=1;//10 base
    while(n>=ten)
    {
        int x=(n-ten)/(10*ten);
        int y=(n-ten)%(10*ten);
        sum+=x*ten;
        if(y>=ten)
            sum+=ten;
        else
            sum+=(y+1);
        ten*=10;
    }
    return sum;
}

剑指Offer上的是一个递归算法,我也没有往这里想,大致是把最高位和其余数字分开来处理,低的n-1位数字递归算,细节没看,大致思路这样。。。

今天看到CJ的微博,才发现这道题目BOP上也有,于是重新想一遍,发现居然最后归纳式子的时候有点问题一直没搞定。。。。有点慌了,解过的题目现在再次解不出了,可能是因为上次灵光一现的因素太多,没有形成general的思维
套路。最主要是自己想发现不知道把余数 分成两种情况考虑,虽然把十位数1的个数和N关系的函数曲线都画出来,却不知道怎么写出函数关系式,其实还是按照之前(n-10)/100式子来,最好写出n-10=100x+y, 那么如果到了20~109其实和19是一样的,
所以把这100个数用单独的常数来处理,对于10~19 则用一个线性函数处理,当 n>=20时,即(n-10)%100=y>=10, sum=10
x+10,否则,n<=19, 即(n-10)%100=y<=9, sum=10*x+y+1分成这两种处理。十位的处理好了,其他类推就可以了。

Posted by richard爱闹 - 7月 14 2014
如需转载,请注明: 本文来自 Richard

max num sum

最大字段和问题,很经典,一般经典的都是去理解性记忆,但是对于递推方程一直有点疑惑,为什么只有这两种情况?

dp[j]=max{dp[j-1]+a[j],a[j]}

各种书都说这个意义在于如果前面最优解和为负直接丢弃,这是知道公式之后才可以推导的呀。。。还记得伟哥当时激动地直接课前和沈老师说起自己想到的这个线性算法

我一直在想,dp[j]只有这两种可能么? 不可能是a[j]加上前面一部分,但是不是dp[j-1]的解?

现在证明,不可能!已知dp[j-1]是以a[j-1]为结尾的最优解,若存在dp’[j-1]+a[j]为dp[j],则dp’[j-1]+a[j]>dp[j-1]+a[j], dp’[j-1]>dp[j-1], 则子问题有更优解,这与已知矛盾,
因为这种就是类似于数学归纳法一样,前一步推后一步,认为前面假设的是正确的,所以dp’[j-1]+a[j] 不可能是候选的最优解,若dp[j-1]>dp’[j-1]>0 || dp[j-1]>0>dp’[j-1] 则最优解dp[j-1]+a[j] 若0>dp[j-1]>dp’[j-1], 则a[j]最优解,
所以最优解只可能是max{dp[j-1]+a[j], a[j]} 不可能是某个dp’[j-1]+a[j]的

其实总结下来也就是最有子结构性质啦,我之前也不知为啥已知有点疑惑。。。

Posted by richard爱闹 - 7月 14 2014
如需转载,请注明: 本文来自 Richard