Showing posts with label Beauty of Programming. Show all posts
Showing posts with label Beauty of Programming. Show all posts

连续自然数之和的数 -Beauty of Programming


比咕网 | 关注搜索和自然语言处理技术
知道:
1+2=3;
4+5=9;
2+3+4=9;
等式的左边都是两个或两个以上连续的自然数相加,那么是不是所有的整数都可以写成这样的形式呢?稍微考虑一下,我们发现,4、8等数并不能写成这样的形式。
问题1:写一个程序,对于一个64位正整数,输出它所有可能的连续自然数(两个及以上)之和的算式。
问题2:大家在测试上面程序的过程中,肯定会注意到有一些数字不能表达为一系列连续的自然之和,例如32好像就找不到,那么,这样的数字有什么规律呢?能否证明你的结论?
问题3:在64位正整数范围内,子序列数目最多的数是哪一个?这个问题要用程序蛮力搜索,恐怕要运行很长的时间,能否用数学知识推导出来?
就刚刚那个笨方法,我们明显觉得,很多数试探到a/2的数就差不多了,白费力计算不少不必要的数字,不妨来用数学公式推导一下,看能不能优化一下计算量
http://blog.csdn.net/yutianzuijin/article/details/10300067
将一个正整数表示成连续自然数之和,即N=s+(s+1)+(s+2)+…+(e-1)+e。利用等差数列求和公式,我们有



也即2N=(s+e)(e-s+1),该等式表示可以将2N分解成两个正整数的乘积。我们设x=s+e, y=e-s+1(其中x>y)。利用x、y我们可以求解获得se


因为se都是整数,因而为了使上面的式子能整除,则xy必须一奇一偶。因为2N=xy2N含有偶因子,所以N必须含有奇因子才能使等式成立。这也就证明了N能表示成连续自然数的充要条件:N必须含有奇数因子,问题二得证。

利用公式(2),我们即可获得正整数N的一个连续自然数序列。为了输出所有的连续自然数序列,我们需要获得所有的xy组合,也即求2N的所有因子组合。我们可以利用算法基本定理将2N分解成有限个质数的乘积:
我们将2N按照质因子进行分解,由于xy只有一个偶数,所以2N的分解式中质因子2的所有组合都只能在其中一个数中,否则xy都是偶数。不妨假设x是偶数,则
其中。由此我们可以知道,2N的所有质因子组共有(j+1) (k+1)…组。由于当x等于2N时,y等于1,此时的连续自然数个数为1,小于2,所以满足条件的连续自然数序列个数为:(j+1) (k+1)…-1,这就回答了问题一,我们可以首先获得2N的所有质因子分解,然后组合质因子并满足x>y即可获得所有的序列。

问题三按照网上的资料,有两种理解方式:1)、可以表示为最多个序列的那个数;2)、序列中项最多的那个数。但是按照题意其实应该是第一种理解方式。由于质因子2只能在xy的某一个数中,对质因子分解不起作用,所以为了让序列个数尽可能多,N的质因子中不能包含2。因此若让子序列个数最多,则即(j+1) (k+1)…最大。通过简单分析我们可以获得具有最多序列个数的正整数j、k等质因子指数的两个性质:
性质一可以通过替换来证明,假设当前的最大序列个数不满足性质一,设第一个不满足性质的两个指数为a和b,满足a<b。由于序列个数为(j+1) ∙ (k+1) ∙…,我们通过将两个质因子的指数交换可以获得相同的序列个数。此外,由于前面的质因子小,交换之后总的乘积会变小,从而可能产生一个更大的指数,使序列个数更多。通过这样依次交换不满足性质的相邻两个指数,最终我们会得到满足性质一并且使序列个数最多的指数序列。这也与经验相吻合,要想使序列个数尽可能多,则正整数必须尽量少含有大的质因子。对于性质二,当正整数只包含质因子3时,质因子之和最大,此时为40。而当正整数含有越多较大的质因子时,则质因子之和就越小,在满足性质一的前提下,当正整数等于3 ∙5 ∙…∙53时,和最小,此时共有15个质因子,含有的序列个数为2^15-1。该正整数含有的序列个数已经非常多,但不一定是最多,例如将53替换为27,则序列个数升为5∙2^13-1。如果我们继续替换,将3^4 ∙5 ∙…∙47变为3^3 ∙5^2 ∙…∙47,则序列个数又升为6∙2^13-1。该数可能已经是具有最多序列的数,但是要想获得准确的最大序列个数,我们还需要利用性质一二去遍历所有可能的情况。如果按照第二种理解方式,则我们可以证明具有最多项的数N的最多项必从1开始。假设最多项不从1开始而是从s(s>1)开始,则我们可以将该最多项的每一项均减去s-1,则最多项等价于从1开始的最多项,也即我们至少可以构造和之前的最多项一样长的新序列。此外,由于每一项均减少了s-1,如果此时减少的和大于最多项的最大项,我们还可以添加新的项到最多项的末尾,产生更长的最多项。


Read full article from 比咕网 | 关注搜索和自然语言处理技术

Tetris 俄罗斯方块 - 编程之美


第一章 游戏之乐 Edit 2 96…

1.17 俄罗斯方块游戏 ★★★

俄罗斯方块(英文:Tetris)是从20世纪80年代开始风靡全世界的电脑游戏。俄罗斯方块是由下面这几种形状的积木块构成,如图1-10所示。
image190.jpg
图1-10
如果你说你没玩过Tetris游戏,面试者一定会比较惊讶,不过面试者还是会耐心地跟你解释它的游戏规则:
n 积木块会从游戏区域上方开始缓慢落下。
n 玩家可以做的操作有:90度旋转积木块,左右移动积木块,或者让积木块加速落下。
n 积木块移到游戏区域最下方或是落到其他积木块上无法移动时,就会固定在该处,而之后新的积木块就会出现在区域上方开始落下。
n 当游戏区域中某一行格子全部由积木块填满,则该行会消失并成为玩家的得分。一次删除的行数越多,得分越多。
n 当积木块堆到区域最上方,则游戏结束。
好,现在的问题是:
1. 如果你是设计者,如何设计各种数据结构来表示这个游戏的各种元素,如每一个可活动的积木块、在底层堆积的积木等。
2. 现在已经知道底层积木的状态,然后在游戏区域上方出现了一个新的积木块,你如何运用刚才设计的数据结构来判断新的积木块要如何移位或旋转,才能最有效率地消除底部累积的积木?
3. 有些版本的Tetris游戏有一个预览窗口,从预览窗口可以看到下一个积木块是什么形状。玩家这时候就可以提前计划,比如,如果下一个积木块是一根长条,我们就不要把最深的“峡谷”堵住。那么我们有了这个新的参数,如何改写上一个程序,才能最有效率地消除底部累积的积木?

http://cslibrary.stanford.edu/112/Tetris-Architecture.html
http://meatfighter.com/tetrisprinteralgorithm/
LameBrain includes heuristic logic that knows how to play the game of tetris. The LameBrain algorithm is very simple -- it knows that height is bad and creating holes is bad. Students can write their own brains. 
public double rateBoard(Board board) {
 final int width = board.getWidth();
 final int maxHeight = board.getMaxHeight();
 int sumHeight = 0;
 int holes = 0;
 // Count the holes, and sum up the heights
 for (int x=0; x<width; x++) {
  final int colHeight = board.getColumnHeight(x);
  sumHeight += colHeight;
  int y = colHeight - 2; // addr of first possible hole
  while (y>=0) {
   if  (!board.getGrid(x,y)) {
    holes++;
   }
   y--;
  }
 }
 double avgHeight = ((double)sumHeight)/width;
 // Add up the counts to make an overall score
 // The weights, 8, 40, etc., are just made up numbers that appear to work
 return (8*maxHeight + 40*avgHeight + 1.25*holes);
}
http://totologic.blogspot.com/2013/03/tetris-ai-explained.html
Related:
http://www.iteye.com/topic/595321
http://outofmemory.cn/code-snippet/6581/java-release-e-luosi-fangkuai-source-code
http://www.jianshu.com/p/dc80709e37c3
http://www.blogjava.net/evapocket/archive/2009/08/12/290897.html

烙饼排序问题 - 编程之美


http://book.51cto.com/art/200909/150035.htm
1.3 一摞烙饼的排序
星期五的晚上,一帮同事在希格玛大厦附近的"硬盘酒吧"多喝了几杯。程序员多喝了几杯之后谈什么呢?自然是算法问题。有个同事说:
"我以前在餐馆打工,顾客经常点非常多的烙饼。店里的饼大小不一,我习惯在到达顾客饭桌前,把一摞饼按照大小次序摆好--小的在上面,大的在下面。由于我一只手托着盘子,只好用另一只手,一次抓住最上面的几块饼,把它们上下颠倒个个儿,反复几次之后,这摞烙饼就排好序了。
我后来想,这实际上是个有趣的排序问题:假设有n块大小不一的烙饼,那最少要翻几次,才能达到最后大小有序的结果呢?
你能否写出一个程序,对于n块大小不一的烙饼,输出最优化的翻饼过程呢?

分析与解法
这个排序问题非常有意思,首先我们要弄清楚解决问题的关键操作--"单手每次抓几块饼,全部颠倒"。
具体参看图1-6:
  
每次我们只能选择最上方的一堆饼,一起翻转。而不能一张张地直接抽出来,然后进行插入,也不能交换任意两块饼子。这说明基本的排序办法都不太好用。那么怎么把这n个烙饼排好序呢?
由于每次操作都是针对最上面的饼,如果最底层的饼已经排序,那我们只用处理上面的n-1个烙饼。这样,我们可以再简化为n-2、n-3,直到最上面的两个饼排好序。
【解法一】
我们用图1-7演示一下,为了把最大的烙饼摆在最下面,我们先把最上面的烙饼和最大的烙饼之间的烙饼翻转(1~4之间),这样,最大的烙饼就在最上面了。接着,我们把所有烙饼翻转(4~5之间),最大的烙饼就摆在最下面了。
  
之后,我们对上面n-1、n-2个饼重复这个过程就可以了。
那么,我们一共需要多少次翻转才能把这些烙饼给翻转过来呢?
首先,经过两次翻转可以把最大的烙饼翻转到最下面。因此,最多需要把上面的n-1个烙饼依次翻转两次。那么,我们至多需要2(n-1)次翻转就可以把所有烙饼排好序(因为第二小的烙饼排好的时候,最小的烙饼已经在最上面了)。
这样看来,单手翻转的想法是肯定可以实现的。我们进一步想想怎么减少翻转烙饼的次数吧。
怎样才能通过程序来搜索到一个最优的方案呢?
首先,通过每次找出最大的烙饼进行翻转是一个可行的解决方案。那么,这个方案是最好的一个吗?考虑这样一种情况,假如这堆烙饼中有好几个不同的部分相对有序,凭直觉来猜想,我们可以先把小一些的烙饼进行翻转,让其有序。这样会比每次翻转最大的烙饼要更快。
既然如此,有类似的方案可以达到目的吗?比如说,考虑每次翻转的时候,把两个本来应该相邻在烙饼尽可能地换到一起。这样,当所有的烙饼都换到一起之后,实际上就是完成排序了。(从这个意义上来说,每次翻最大烙饼的方案实质上就是每次把最大的和次大的交换到一起。)
在这样的基础之上,本能的一个想法就是穷举。只要穷举出所有可能的交换方案,那么,我们一定能够找到一个最优的方案。
沿着这个思路去考虑,我们自然就会使用动态规划或者递归的方法来进行实现了。可以从不同的翻转策略开始,比如说第一次先翻最小的,然后递归把所有的可能全部翻转一遍。这样,最终肯定是可以找到一个解的。
但是,既然是递归就一定有退出的条件。在这个过程中,第一个退出的条件肯定是所有的烙饼已经排好序。那么,有其他的吗?如果大家仔细想想就会发现到,既然2(n-1)是一个最多的翻转次数。如果在算法中,需要翻转的次数多于2(n-1),那么,我们就应该放弃这个翻转算法,直接退出。这样,就能够减少翻转的次数。
从另外一个层面上来说,既然这是一个排序问题。我们也应该利用到排序的信息来进行处理。同样,在翻转的过程中,我们可以看看当前的烙饼数组的排序情况如何,然后利用这些信息来帮助减少翻转次数的判断过程
下面是在前面讨论的基础之上形成的一个粗略的搜索最优方案的程序:


代码清单1-8
class CPrefixSorting
{
public:

    CPrefixSorting()
    {
        m_nCakeCnt = 0;
        m_nMaxSwap = 0;
    }

    //
    // 计算烙饼翻转信息
    // @param
    // pCakeArray   存储烙饼索引数组
    // nCakeCnt 烙饼个数
    //
    void Run(int* pCakeArray, int nCakeCnt)
    {
        Init(pCakeArray, nCakeCnt);

        m_nSearch = 0;
        Search(0);
    }

    //
    // 输出烙饼具体翻转的次数
    //
void Output()
{
    for(int i = 0; i < m_nMaxSwap; i++)
    {
        printf("%d ", m_arrSwap[i]);
    }
       
    printf("\n |Search Times| : %d\n", m_nSearch);
    printf("Total Swap times = %d\n", m_nMaxSwap);
}

private:

    //
    // 初始化数组信息
    // @param
    // pCakeArray   存储烙饼索引数组
    // nCakeCnt 烙饼个数
    //
    void Init(int* pCakeArray, int nCakeCnt)
    {
        Assert(pCakeArray != NULL);
        Assert(nCakeCnt > 0);

        m_nCakeCnt = n;

    // 初始化烙饼数组
        m_CakeArray = new int[m_nCakeCnt];
        Assert(m_CakeArray != NULL);
        for(int i = 0; i < m_nCakeCnt; i++)
        {
            m_CakeArray[i] = pCakeArray[i];
        }

        // 设置最多交换次数信息
        m_nMaxSwap = UpBound(m_nCakeCnt);

        // 初始化交换结果数组
        m_SwapArray = new int[m_nMaxSwap];
        Assert(m_SwapArray != NULL);

        // 初始化中间交换结果信息
        m_ReverseCakeArray = new int[m_nCakeCnt];
        for(i = 0; i < m_nCakeCnt; i++)
        {
            m_ReverseCakeArray[i] = m_CakeArray[i];
        }
        m_ReverseCakeArraySwap = new int[m_nMaxSwap];
    }
   
   
    //
    // 寻找当前翻转的上界
    //
    //
    int UpBound(int nCakeCnt)
    {
        return nCakeCnt*2;
    }

    //
    // 寻找当前翻转的下界
    //
    //
    int LowerBound(int* pCakeArray, int nCakeCnt)
    {
        int t, ret = 0;

        // 根据当前数组的排序信息情况来判断最少需要交换多少次
        for(int i = 1; i < nCakeCnt; i++)
        {
            // 判断位置相邻的两个烙饼,是否为尺寸排序上相邻的
            t = pCakeArray[i] - pCakeArray[i-1];
            if((t == 1) || (t == -1))
            {
            }
            else
            {
                ret++;
            }
        }
        return ret;
    }

    // 排序的主函数
    void Search(int step)
    {
        int i, nEstimate;

        m_nSearch++;

        // 估算这次搜索所需要的最小交换次数
        nEstimate = LowerBound(m_ReverseCakeArray, m_nCakeCnt);
        if(step + nEstimate > m_nMaxSwap)
            return;

        // 如果已经排好序,即翻转完成,输出结果
        if(IsSorted(m_ReverseCakeArray, m_nCakeCnt))
        {
            if(step < m_nMaxSwap)
            {
                m_nMaxSwap = step;
                for(i = 0; i < m_nMaxSwap; i++)
                m_arrSwap[i] = m_ReverseCakeArraySwap[i];
            }
            return;
        }

        // 递归进行翻转
        for(i = 1; i < m_nCakeCnt; i++)
        {
            Revert(0, i);
            m_ReverseCakeArraySwap[step] = i;
            Search(step + 1);
            Revert(0, i);
        }
    }

    //
    // true : 已经排好序
    // false : 未排序
    //
    bool IsSorted(int* pCakeArray, int nCakeCnt)
    {
        for(int i = 1; i < nCakeCnt; i++)
        {
            if(pCakeArray[i-1] > pCakeArray[i])
            {
                return false;
            }
        }
        return true;
    }

    //
    // 翻转烙饼信息
    //    
    void Revert(int nBegin, int nEnd)
    {
    Assert(nEnd > nBegin);
        int i, j, t;

        // 翻转烙饼信息
        for(i = nBegin, j = nEnd; i < j; i++, j--)
        {
            t = m_ReverseCakeArray[i];
            m_ReverseCakeArray[i] = m_ReverseCakeArray[j];
            m_ReverseCakeArray[j] = t;
        }
    }

private:

    int* m_CakeArray;   // 烙饼信息数组
    int m_nCakeCnt;     // 烙饼个数
    int m_nMaxSwap;     // 最多交换次数。根据前面的推断,
    这里最多为m_nCakeCnt * 2
    int* m_SwapArray;   // 交换结果数组

    int* m_ReverseCakeArray;    // 当前翻转烙饼信息数组
    int* m_ReverseCakeArraySwap;    // 当前翻转烙饼交换结果数组
    int m_nSearch;          // 当前搜索次数信息
};
m_nMinSwap越小,那么这个剪枝条件就越容易满足,更多的情况就不需要再去搜索。当然,程序也就能更快地找出最优方案。
仔细分析上面的剪枝条件,在到达m_tArr状态之前,我们已经翻转了step次,nEstimate是在当前这个状态我们至少还要翻转多少次才能成功的次数。如果step+nEstimate大于m_nMinSwap,也就说明从当前状态继续下去,m_nMinSwap次我们也不能排好所有烙饼。那么,当然就没有必要再继续了。因为继续下去得到的方案不可能比我们已经找到的好。
显然,如果nEstimate越大,剪枝条件越容易被满足。而这正是我们希望的。
结合上面两点,我们希望UpperBound越小越好,而下界(LowerBound)越大越好。假设如果有神仙指点,你只要告诉神仙你当前的状态,他就能告诉你最少需要多少次翻转。这样的话,我们可以花费O(N2)的时间得到最优的方案。但是,现实中,没有这样的神仙。我们只能尽可能地减小UpperBound,增加LowerBound,从而减少需要搜索的空间。
利用上面的程序,做一个简单的比较。

对于一个输入,10个烙饼,从上到下,烙饼半径分别为3, 2, 1, 6, 5, 4, 9, 8, 7, 0。对应上面程序的输入为:
10
3 2 1 6 5 4 9 8 7 0
如果LowerBound在任何状态都为0,也就是我们太懒了,不想考虑那么多。当然任意状态下,你至少需要0次翻转才能排好序。这样,上面的程序Search函数被调用了575 225 200次。
但是如果把LowerBound稍微改进一下(如上面程序中所计算的方法估计),程序则只需要调用172 126次Search函数便可以得到最优方案:
6
4 8 6 8 4 9
程序中的下界是怎么估计出来的呢?
每一次翻转我们最多使得一个烙饼与大小跟它相邻的烙饼排到一起。如果当前状态n个烙饼中,有m对相邻的烙饼它们的半径不相邻,那么我们至少需要m次才能排好序。
从上面的例子,大家都会发现改进上界和下界,好处可不少。我想不用多说,大家肯定想继续优化上界和下界的估计吧。
除了上界和下界的改进,还有什么办法可以提高搜索效率吗?如果我们翻了若干次之后,又回到一个已经出现过的状态,我们还值得继续从这个状态开始搜索吗?我们怎样去检测一个状态是否出现过呢?
读者也许不相信,比尔盖茨在上大学的时候也研究过这个问题,并且发表过论文。你不妨跟盖茨的结果 比比吧。
扩展问题
1. 有一些服务员会把上面的一摞饼子放在自己头顶上(放心,他们都戴着洁白的帽子),然后再处理其他饼子,在这个条件下,我们的算法能有什么改进?
2. 事实上,饭店的师傅经常把烙饼烤得一面非常焦,另一面则是金黄色。这时,服务员还得考虑让烙饼大小有序,并且金黄色的一面都要向上。这样要怎么翻呢?
3. 有一次师傅烙了三个饼,一个两面都焦了,一个两面都是金黄色,一个一面是焦的,一面是金黄色,我把它们摞一起,只能看到最上面一个饼的上面,发现是焦的,问最上面这个饼的另一面是焦的概率是多少?
4. 每次翻烙饼的时候,上面的若干个烙饼会被翻转。如果我们希望在排序过程中,翻转烙饼的总个数最少,结果会如何呢?
5. 对于任意次序的n个饼的排列,我们可以研究把它们完全排序需要大致多少次翻转,目前的研究成果是:
(1) 目前找到的最大的下界是15n/14,就是说,如果有100个烙饼,那么我们至少需要15×100/14= 108次翻转才能把烙饼翻好--而且具体如何翻还不知道。
(2) 目前找到的最小的上界是(5n + 5)/3,对于100个烙饼,这个上界是169。
(3) 任意次序的n个烙饼反转排序所需的最小反转次数被称为第n个烙饼数,现在找到的烙饼数为:
N
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Pn
0
1
3
4
5
7
8
9
10
11
13
14
15
第14个烙饼数P14还没有找到,读者朋友们,能否在吃烙饼之余考虑一下这个问题?
http://cstriker1407.info/blog/the-beauty-of-the-programming-reading-notes-pancakes-scheduling-problems/
解法1,每次翻转最大的张:
Same c code here: http://www.acmerblog.com/pancake-sorting-6057.html
private static final int[] UnSortArr = {1,5,3,4};//,6,2,9,8,7};
//获取最大值
public static int getMaxSortNum()
{
    LinkedList<Integer> middleList = new LinkedList<Integer>();
    for (int item : UnSortArr)
    {
        middleList.add(item);
    }
    System.out.println("未排序:" + middleList.toString());
    int reverseTime = 0;
    int totalNum = middleList.size();
    int numNoSort = totalNum;
    while (numNoSort > 0)
    {
        /* sublist:
         * 返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。(如果 fromIndex 和 toIndex 相等,则返回的列表为空)。
         * 返回的列表由此列表支持,因此返回列表中的非结构性更改将反映在此列表中,反之亦然。返回的列表支持此列表支持的所有可选列表操作。
         */
        //从没有排序的list里面找到最大的一个数,以及它的ID
        int maxNum = Collections.max( middleList.subList(0, numNoSort));
        int maxNumIdx = middleList.indexOf(maxNum);
         
        //翻转最大数到第一个数之间的所有的数据
        Collections.reverse( middleList.subList(0, maxNumIdx + 1) );
        System.out.println("找到最大数["+ maxNum +"]并翻转:" + middleList.toString());
        reverseTime++;
         
        //翻转第一个数和没有排序的最后一个数间的所有数据
        Collections.reverse( middleList.subList(0, numNoSort) );
        System.out.println("将最大数翻到底部:" + middleList.toString());
        reverseTime++;
         
        //没有排序的数据个数--
        numNoSort--;
    }
    System.out.println("总数:"+ totalNum +" 翻转次数为:" + reverseTime);
    return reverseTime;
}

通过枚举方法来获取最小的数目:
使用递归的方法,在上一次翻转的基础上进行二次翻转:
/*
 *在当前已经进行部分翻转的list上面进行下一次的翻转,并且对翻转的序号x【0,x】进行遍历
 *times表示当前已经翻转的次数
 */
private static void internalSort(LinkedList<Integer> middleList, int times)
{
    //已经排序OK,不用再测试了。
    if (isListSorted(middleList))
    {   //记录最小值
        minSortNum = times < minSortNum ? times : minSortNum;
        return;
    }
     
    /*
     *加速判断,根据getMaxSortNum函数的测试,可以发现最大值为数据list长度的2倍,因此当
     *当前的翻转数目 + 估计剩余的最小翻转数 > middleList.size() * 2,可以认为这次翻转已经没有意义了
     */
    if (times + getMinSortNum() > maxSortNum)
    {
        return;
    }
     
    /*
     * 既然不知道如何翻转数目最小,那我们就遍历,在当前已经部分翻转之后的list上,进行二次翻转,每次翻转的个数【i】进行遍历。
     * 翻转完成之后将数据还原,方便下次翻转。
     */
    for (int i = 0; i < middleList.size(); i++)
    {
        Collections.reverse( middleList.subList(0, i + 1) );
        internalSort(middleList, times + 1);
        Collections.reverse( middleList.subList(0, i + 1) );
    }
}
 为了加速计算,减少不必要的递归,我们计算出翻转次数的上下限。(下限是估计出来的)
//获取最小估计值,不准确。
public static int getMinSortNum()
{
    int num = 0;
    for (int i = 1; i < UnSortArr.length; i++)
    {
        //如果相邻的两个饼的大小也相邻,那么就可以认为这两个饼是一个整体
        if (UnSortArr[i] - UnSortArr[i-1] == 1 || UnSortArr[i] - UnSortArr[i-1] == -1)
        {
        }else
        {
            num++;
        }
    }
    return num;
}
Java code: http://blog.csdn.net/kabini/article/details/2276723

    书中给出的递归算法或称之为分支限界法(即遍历+剪枝=分支限界)秉承了递归算法传统的简单、明了,但效率偏低的特点。这个问题的实质,我们在每一次反转之前其实是需要做出一种选择,这种选择必须能够导致全局最优解。递归算法就是递归的构建所有解(实际是一颗搜索树),并在遍历过程中不断刷新LowerBound和UpperBound,以及当前的最优解(剪枝),并最终找到一个最整体优解。在这种策略下,提高算法的效率只能寄希望于剪枝方法的改进。但是这种方法显然不是多项式时间的,有没有多项式时间的算法呢?

    书中P22页提到动态规划,但最后却给出了解决最优化问题普遍适用但效率可能是最差的递归方法。这不禁让人疑惑:这也不美啊!?如果我们能证明该问题满足动态规划或贪心算法的使用条件,解决问题的时间复杂度将会降到多项式时间甚至N^2。但书中提到动态规划却最终没有使用,又没有讲明原因,我觉得是一种疏失(应该不算错误)。那我们就来想一下为什么没有动态规划或贪心算法的原因。
    我们知道动态规划方法是一种自底向上的获取问题最优解的方法,它采用子问题的最优解来构造全局最优解。利用动态规划求解的问题需要满足两个条件:即(1)最优子结构 (2)子结构具有重叠性。条件(1)使我们可以利用子问题的最优解来构造全局最优解,而条件(2)是我们在计算过程中可以利用子结构的重叠性来减少运算次数。此外,《算法导论》上还以有向图的无权最短路径和无权最长路径为例提出条件(3)子问题必须独立。
 
  首先我们假定烙饼问题存在优化子结构。假如我们有N个烙饼,把他们以其半径由小到大进行编号。优化子结构告诉我们对于i个烙饼,我们只需要先排列前(i-1)个,然后再将第i个归位;或先排列第2到i个,最后将第一个归位;又或是找到一个位置k[i<=k<j]像矩阵乘法加括号那样,使得我们先排列前k个,再排列后j-k个,最后再将二者合并,以找到一个最佳翻转策略等等...
 
    根据动态规划算法的计算过程,我们需要一个N*N矩阵M,其中M[i][j]表示将编号i至编号j的烙饼排序所需要的翻转次数。但我们真的能从M[0][0..j-1]和M[1][j+1],或与M[i][j]同行同列的值来计算M[i][j]吗?如果能,我们就能获得多项式时间的算法。  

    我们来看书中给出的例子:(顶端)3,2,1,6,5,4,9,8,7,0(底端),我们最终的目标是计算M[0][9]。
这里我们以计算M[0][4]为例,计算的矩阵我已经在下面给出: 
  0 1  2  3  4  5  6  7  8  9
  ------------------------
0|0 1 (1){1}[?]
1|  0  1 (1){1} 
2|     0  1 (1)
3|        0  0
4|           0
  ------------------
 
  实际上如果我们要向将0-4号烙饼(注意:烙饼编号也等同于其半径)排为正序(中间有其他烙饼也没关系),按照程序给出的结果, 我们需要进行3次翻转,分别为[2,5,9](即分别翻转队列中第二(从零开始)、五、九个烙饼,这里的数字不是烙饼的编号): 
  [1]  [2]  [3]  6   5  [4]  9  8  7  [0]
  [4]   5    6  [3] [2] [1]  9  8  7  [0]
  [0]   7    8   9  [1] [2] [3] 6  5  [4]
 
      我们知道,该矩阵中每一个数的背后都隐含着一个烙饼的排列,例如M[0][4]就应该对应0,7,8,9,1,2,3,6,5,4
  所以,每一个M[i][j]的选取都蕴含着其子排列的顺序的变化。
      在计算M[i][j]的时候,我们需要计算i-j号饼的全部划分(不包括全部为1的划分)所能构成的翻转结构,并取其翻转 次数最少的哪一个最为M[i][j]的最终值。例如,我们在计算M[0][4]的时候,需要查看:
 
   /**先将0和1-4号分别排序,最后将二者合并为有序所需要的翻转次数*/
   M[0][0],M[1][4]
  
   /** 同上 */
   M[0][1],M[2][4]
  
   /** 同上 */
   M[0][2],M[3][4]
  
   /** 同上 */
   M[0][3],M[4][4]
  
   /* 先将0、1、2、3-4号分别排序,最后将4者合并为有序所需要的翻转次数.
    * 注意这里又包含将4个分组再次进行划分的问题!
    */
   M[0][0],M[1][1],M[2][2],M[3][4]
   .....//中间略
   M[0][3],M[4][4]
 
   如果再加上运算过程中我们可以淘汰超过最大反转次数的方案(剪枝?),我们完成全部的运算,所经历的运算过程的时间复杂度已经不是多项式时间的,而是和先前所说的递归方法已没什么两样。
     造成这种现象的原因是:某个子问题的最优解不一定是整体的最优解,所以我们在处理整个问题的时候,需要遍历所有可能的子问题,并计算它到整体问题所消耗的代价,才能最终作出有利于整体问题的选择。
   所以我们一开始的假设,即烙饼问题有优化子结构的假设是错误的。因此我们不能用动态规划,同理也不能用贪心算法。

   但说到每一步的“选择”问题,我记得算法导论上有一个叫做“A*”的算法,它的思想是在进行每一步选择的时候都“推算”最终可能需要的代价,并选择当前代价最小的分支进行遍历。这个“推算”的结果可能不会是最终的代价,而只是作为分支选择的依据。如果谁有兴趣就做一下吧 :-)

Java code:
http://m.blog.csdn.net/blog/Lakers_KobeBryant/8209372
public class CakeTuneProblem {
    
 public static int max;//记录所需要翻转的最少次数
 public static int estimateMin;//记录所需要翻转的最少次数
 public static int minNum;//记录所需要翻转的最少次数
 public static int[] cakeArray;//饼的数组序列
 public static int[] resultArray;//饼的数组序列
 public static int[] tempArray;//饼的数组序列
 public static int count = 0; 
 
 //烙饼当前状最少翻转次数
 public static int lowBound(int[] cakeArray){
  int reduce = 0;
  int min = 0;
  for(int i = 0; i < cakeArray.length-1; i++){
   reduce = cakeArray[i] - cakeArray[i+1];
   if(reduce == 1 || reduce == -1){
   }
   else{
    min++;
   }
  }
  return min;
 } 
 
 //翻转函数,将0-index翻转
 public static void reverse(int[] cakeArray, int index){
  int i = 0;
  int j = index;
  int temp;
  while(i < j){
   temp = cakeArray[i];
   cakeArray[i] = cakeArray[j];
   cakeArray[j] = temp;
   i++;
   j--;
  }
 }
    
 //判断翻转结果是否达到要求
 public static boolean isSorted(int[] cakeArray){
  for(int i = 1; i < cakeArray.length; i++){
   if(cakeArray[i-1] > cakeArray[i]){
    return false;
   }
  }
  return true;
 }
 
 //翻转主函数,递归求翻转过程,实际上是一棵搜索树
 public static void search(int[] cakeArray, int depth){
  count++;
  estimateMin = lowBound(cakeArray);
  //减支函数
  if((depth + estimateMin) > max){
   return;
  }
  if(isSorted(cakeArray)){
   if(max > depth){
    max = depth;
    resultArray = tempArray;
    System.out.println("当前最少翻转次数cur_min = " + max);
    for(int i = 1; i <= max; i++){
     System.out.print(resultArray[i]+1 + " ");
    }
    System.out.println();
   }
   return;
  }
  for(int i = 1; i < cakeArray.length; i++){
   if(depth != 0 && tempArray[depth] == i){
    continue;
   }
   reverse(cakeArray,i);
   tempArray[depth + 1] = i;
   search(cakeArray,depth+1);
   reverse(cakeArray,i);
  }
 }
} 
编程之美 烙饼问题 java实现(检测状态是否出现过)
private int [] med_cookies_states= {3,2,1,6,5,4,9,8,7,0};
* 记录状态是否搜索过
* 使用hashMap来作为state的key值,value 为该状态下所经历的step
* 如果当前状态的step值比hashmap中的小,则不需要剪掉这个分支
* @param med_cookies_states
* @param step
* @return
*/
public boolean isUnSearch(int [] med_cookies_states,int step){
String temp="";
for(int i=0;i< cookie_cnt;i++){
temp+=String.valueOf(med_cookies_states[i]);
}
if(searchStates.get(temp)==null){
searchStates.put(temp, step);
return true;
}else{
if(searchStates.get(temp) >step){
searchStates.put(temp, step);
return true;
}else{
return false;
}
}
}


Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts