刷题笔记-2020.08.05

1. 孩子们的游戏(圆圈中剩下的数)

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

class Solution {
public:
    int LastRemaining_Solution(int n, int m)
    {
        if(n<=0) return -1;
        list<int> lt;
        for(int i=0; i<n; ++i)
            lt.push_back(i);
        list<int>::iterator ltit;
        int index=0;
        while(n>1)
        {
            index = (index+m-1)%n;
            ltit = lt.begin();
            advance(ltit, index);//定位list的第index个成员
            //advance函数要求第一个迭代器参数是左值,方便赋值
            lt.erase(ltit);
            --n;
        }
        return *(lt.begin());
    }
};

2. 数组中重复的数字

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
分析:用另外一个数据结构存放已经访问过的数字,如果该数字被再次访问,就说明遇到了重复的数字,空间复杂度是 O(n)。如果采用 In-place方案,空间复杂度变为 O(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) {
        vector<int> num2pos(length,-1);//num2pos的第n个位置存放数字n在numbers数组中的位置
        for(int i=0; i<length; ++i)
        {
            if(num2pos[numbers[i]]==-1)
                num2pos[numbers[i]]=i;
            else{
            //这样写是错误的:duplication=&numbers[num2pos[numbers[i]]];因为numbers是拷贝过来的数组,duplicate函数结束后就会被释放。
                *duplication = numbers[i];
                return true;
            }
        }
        duplication = nullptr;
        return false;
    }
};
//方案二:In-place
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 j=i;
            while(numbers[j]!=j)
            {
                if(numbers[j]!=numbers[numbers[j]])
                    swap(numbers[j],numbers[numbers[j]]);
                else
                {
                    *duplication = numbers[j];
                    return true;
                }
            }
        }
        return false;
    }
};

3. 剪绳子

给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
  分析:因为不知道到底要将绳子分为多少段,也不知道每段要剪成多长,所以最开始的想法是用动态规划做。后来发现剪绳子是有一定规律的。假设绳子长为 20,可以将绳子剪为 10 和 10,此时乘积为 100,但如果将绳长 10 继续划分,还将得到 5+5=10,而5*5>10,所以继续划分是有好处的。但划分到什么时候最佳呢?我们发现,绳长小于等于 4 时,继续划分并不能够增大乘积。而且如果有一根绳子长度被分割为 1,就会非常不利于最后的乘积大小。尝试几个例子后,发现绳子最好被剪为 3、3、……、3、2或4 的几段。绳长对 3 取余,有可能为 0或1或2,如果取余结果是 1,那么就分割为一系列 3 和一个 4,如果取余结果是 2,就分割成一系列 3 和一个 2,否则分割为一系列 3。

class Solution {
public:
    int cutRope(int number) {
        if(number==2) return 1;
        if(number==3) return 2;//m>1
        int ret=1, resid;
        if((resid=(number%3))!=0){
            if(resid==1)
                resid += 3;
            number -= resid;
            ret *= resid;
        }
        while(number){
            ret *= 3;
            number -= 3;
        }
        return ret;
    }
};

4. 求 1+2+3+…+n

求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
分析:利用递归和短路原理。

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

5. 数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用 Insert() 方法读取数据流,使用 GetMedian() 方法获取当前读取数据的中位数。
  分析:求一个数组的中位数时,可以采取多种方式。
  第一种是用无序容器存储数据,用 sort 函数对无序数组排序,然后取中位数,时间复杂度是 O(nlogn)。
  第二种是利用优先队列,将数据分为偏大和偏小的两部分,将偏小的数据存入大顶堆,偏大的数据存入小顶堆,如果数据个数为奇数,此时大顶堆的 top 数据就是中位数,否则,大顶堆和小顶堆的 top 数据之和的平均值就是中位数。具体步骤为,先将无序数据的前一部分存入大顶堆,此时大顶堆的 top 元素一定大于等于中位数;然后将后一部分数据依次与大顶堆 top 元素比较,如果比 top 元素大,那么它就一定属于偏大的一部分数据,直接存入小顶堆;如果小于等于 top 元素,就将当前元素存入大顶堆,再将大顶堆的 top 元素移入小顶堆。时间复杂度同样是 O(nlogn)。
  而本题目要得到数据流的中位数,也就是说每得到一个新的数据,我们就要计算一次中位数。此时对于前面的方案一就不太友好了,因为前一次已经对所有数据进行了排序,新来的数据只要直接插入就好,而再次对所有数据进行快速排序,反而会使快排陷入 O(n2) 的最差复杂度。每个数据采用插入排序的时间复杂度为二分查找的 O(logn) 和移动数据的 O(n)。
  如果使用堆排序,那么每次使一个数据有序只需要 O(logn)。所以本题目最优的解法应该是用堆排序。

class Solution {
public:
    priority_queue<int,vector<int>,greater<int>> smallHeap;
    priority_queue<int,vector<int>,less<int>> bigHeap;
    int count=0;
    void Insert(int num)
    {
        ++count;
        if((count&1)==1)
        {
            if(smallHeap.empty()||num<=smallHeap.top())
                bigHeap.push(num);
            else{
                bigHeap.push(smallHeap.top());
                smallHeap.pop();
                smallHeap.push(num);
            }
        }
        else{
            if(num>=bigHeap.top())
                smallHeap.push(num);
            else{
                smallHeap.push(bigHeap.top());
                bigHeap.pop();
                bigHeap.push(num);
            }
        }
    }

    double GetMedian()
    {
        if((count&1)==0)
            return (double)(bigHeap.top()+smallHeap.top())/2;
        else
            return bigHeap.top();
    }

};

6. 扑克牌顺子

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 之外,不能出现重复的数字,且最大一张牌与最小一张牌的差值不能超过4。

class Solution {
public:
    bool IsContinuous( vector<int> numbers ) {
        if(numbers.empty()) return false;//numbers有数组为空的可能,就很坑的测试用例
        sort(numbers.begin(),numbers.end());
        int posNotZero;
        for(int i=0; i<5; ++i)
        {
            if(numbers[i]==0)
            {
                posNotZero = i+1;
                continue;
            }
            if(i+1<5 && numbers[i]==numbers[i+1]) return false;
        }
        return numbers.back()-numbers[posNotZero]<5;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值