鹅的题目

目录

Perfect Squares 完全平方数

合并K个链表(hard)

随机数

LRU Cache(双向链表+hash map)

查找兄弟单词

快速幂

组成三角形

在一圆周上任意取三个点构成锐角三角形的概率是多少?

3Sum

大量的Query日志(1T左右),得到出现频次最高的100个Query

找出字符数组中出现次数最多的字符

 

Perfect Squares 完全平方数

问题描述:给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

Example 1:

Input: n = 12

Output: 3 
Explanation: 12 = 4 + 4 + 4.

Example 2:

Input: n = 13

Output: 2
Explanation: 13 = 4 + 9.

思路1:

dp

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n+1,INT_MAX);
        dp[0] = 0;
        for(int i = 0;i < n;i++){
            for(int j = 1;i+j*j <= n;j++){
                dp[i+j*j] = min(dp[i+j*j],dp[i]+1);
            }
        }
        return dp[n];
    }
};

 

思路2:

根据四平方和定理,任意一个正整数均可表示为4个整数的平方和,其实是可以表示为4个以内的平方数之和,那么就是说返回结果只有 1,2,3 或4其中的一个

首先我们将数字化简一下,由于一个数如果含有因子4,那么我们可以把4都去掉,并不影响结果,比如2和8,3和12等等,返回的结果都相同,读者可自行举更多的栗子。

还有一个可以化简的地方就是,如果一个数除以8余7的话,那么肯定是由4个完全平方数组成,这里就不证明了,因为我也不会证明,读者可自行举例验证。

那么做完两步后,一个很大的数有可能就会变得很小了,大大减少了运算时间,下面我们就来尝试的将其拆为两个平方数之和,如果拆成功了那么就会返回1或2,因为其中一个平方数可能为0. (注:由于输入的n是正整数,所以不存在两个平方数均为0的情况)。注意下面的 !!a + !!b 这个表达式,可能很多人不太理解这个的意思,其实很简单,感叹号!表示逻辑取反,那么一个正整数逻辑取反为0,再取反为1,所以用两个感叹号!!的作用就是看a和b是否为正整数,都为正整数的话返回2,只有一个是正整数的话返回1

class Solution {
public:
    int numSquares(int n) {
        while(n % 4==0) n = n/4;
        if(n % 8 == 7) return 4;
        // a可以从0开始,n本身就是平方数情况
        for(int a = 0;a*a <= n;a++){
            int b = sqrt(n-a*a);
            if(a*a+b*b == n){
                return !!a+!!b;
            }
        }
        return 3;
    }
};

 

合并K个链表(hard)

Input:
[
  1->4->5,
  1->3->4,
  2->6
]
Output: 1->1->2->3->4->4->5->6

问题:

N个目标怎样最快速的两两合并

不管合并几个,基本还是要两两合并,两个先合并,合并好了再跟第三个,然后第四个直到第k个。这样的思路是对的,但是效率不高(时间复杂度为O(nk*nk)

思路:

1.递归

将原链表分成两段,然后对每段调用递归函数,suppose返回的left和right已经合并好了,然后再对left和right进行合并

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.empty()) return NULL;
        return mergeNLists(lists,0,lists.size()-1);
        
    }
    
    ListNode* mergeNLists(vector<ListNode*>& lists,int l,int r) {
        if(l > r) return NULL;
        if(l == r) return lists[l];
        if(l+1 == r) return merge(lists[l],lists[r]);
        else{
            int mid = (l+r)/2;
            ListNode* left = mergeNLists(lists,l,mid);
            ListNode* right = mergeNLists(lists,mid+1,r);
            return merge(left,right);
        } 
    }
    
    // 合并两个链表
    ListNode* merge(ListNode* h1,ListNode* h2){
        ListNode* dummy_head = new ListNode(-1);
        ListNode* cur = dummy_head;
        while(h1 && h2){
            if(h1->val < h2->val){
                cur->next = h1;
                h1=h1->next;
            }
            else{
                cur->next = h2;
                h2=h2->next;
            }
            cur=cur->next;
        }
        if(h1) cur->next = h1;
        if(h2) cur->next = h2;
        return dummy_head->next;
    }
};

复杂度:

时间复杂度:

每合并一次,需要nk时间复杂度

递归方法需要合并logk次

故总时间复杂度:nklogk

空间复杂度:

栈深度:logk

 

2.使用堆:

把k个链表开头的值排个序,每次取最小的一个值放到答案链表中,这次取完之后更新这个值为它后面的一个值。接着这么取一直到全部取完。

那么每次更新之后怎么对当前这k个值重新排序以便知道当前最小的是谁呢?用优先队列(或者堆)来维护这k个值就好啦!

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
struct cmp{
    bool operator()(ListNode* h1,ListNode* h2){
        return h1->val > h2->val;
    }
};

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.empty()) return NULL;
        if(lists.size() < 2) return lists[0];

        priority_queue<ListNode*,vector<ListNode*>,cmp> q;
        ListNode* dummy_head = new ListNode(-1);
        ListNode* pre = dummy_head,*cur;

        for(auto &node:lists){
            if(node) q.push(node);
        }

        while(!q.empty()){
            cur = q.top(); q.pop();
            pre->next = cur;
            pre = pre->next;
            if(cur->next) q.push(cur->next);
        }
        return dummy_head->next;
    }
};

复杂度分析:

由于每个值都要取一次,一共取nk次。每次更新优先队列要logk的复杂度。所以总时间复杂度为O(nklogk);

空间复杂度为优先队列所占空间,为O(k)。

 

相同原理:最小区间原题

k个有序的数组,找到最小的区间范围使得这k个数组中,每个数组至少有一个数字在这个区间范围内。比如:

  • 数组1:[4, 10, 15, 24, 26]
  • 数组2:[0, 9, 12, 20]
  • 数组3:[5, 18, 22, 30]

最小的区间是[20, 24],这个区间包含了数组1中的24,数组2中的20,数组3中的22

思路:

多路归并,每次比较每个数组k个的头组成的区间大小

 

随机数

1、已知有个rand7()的函数,返回1到7随机自然数,让利用这个rand7()构造rand10() 随机1~10

int rand10(){
    int x=0;
    do{
        x=(rand7()-1)*7+rand7();
    } while(x>40);
    return x%10+1;
}

解释:

 (1)x=(rand7()-1)*7+rand7();

这里的x范围是1到49,等概率产生的,都是1/49

(2)1到49之间有1到10,11到20,21到30,31到40,只要%10然后+1,就是等概率的1到10,但是41到49,不够1到10,其实while(x>20);while(x>30);都是对的,就是效率差了点。

 

2、已知有个函数randM()返回1到M随机自然数,利用randM()构造randN()使其随机1~N

(1)M >= N

直接生成0-M/N*N之内的数,然后%N即可

(2)M < N

类似构造(randM()-1)*M + randM(),可以产生1~M^2(即randM^2),可以在M^2中选出N个构造1~N的映射。

如果M^2还是没有N大,则可以对于randM^2继续构造,直到成功为止。

 

3. 已知一随机发生器,产生0的概率是p,产生1的概率是1-p,现在要你构造一个发生器,使得它产生0和1的概率均为1/2。

考虑连续产生两个随机数,结果只有四种可能:00、01、10、11,其中产生01和产生10的概率是相等的,均为p*(1-p),于是可以利用这个概率相等的特性等概率地产生01随机数。

比如把01映射为0,10映射为1。于是整个方案就是:

产生两个随机数,如果结果是00或11就丢弃重来,如果结果是01则产生0,结果是10则产生1

 

4. 已知一随机发生器,产生的数字的分布不清楚,现在要你构造一个发生器,使得它产生0和1的概率均为1/2。

思路类似,考虑连续产生两个随机数a、b,结果有三种情况a==b,a>b,a<b,其中由于a和b的对称性,a>b和a<b出现的概率是相等的,于是可以利用这个概率相等的特性等概率地产生01随机数。方法类似。

 

5. 我们给定一个随机数生成器[0, m),现要求生成一个随机数生成器[0, n), 并且是均匀分布的。 
(1)n <= m

则无需转换,筛选即可

(2)n > m

2.1 m < n <= m*m
取一个t,使得当t最小时,满足(k=tm)>=n(易得t<=m),那么这个randk()的表示式就为randk() = randm()*t+randt(),又因为k>=n,所以此时已是情形一,故按照情形一即可生成随机数生成器randn() 
2.2 n>m*m

首先产生随机数生成器randx()(其中x=m*m),再然后按照第一种情况处理,如果仍不满足则先生成randy()(其中y=x*x),再按第一种情况处理,如此进行,直到满足为止。
 

其余题目:http://www.mamicode.com/info-detail-289016.html

 

LRU Cache(双向链表+hash map)

题意:

为LRU Cache设计一个数据结构,它支持两个操作:

(1)get(key):如果key在cache中,则返回对应的value值,否则返回-1

(2)set(key,value):如果key不在cache中,则将该(key,value)插入cache中(注意,如果cache已满,则必须把最近最久未使用的元素从cache中删除);如果key在cache中,则重置value的值。

思路:

数组:

用什么数据结构来实现LRU算法呢?可能大多数人都会想到:用一个数组来存储数据,给每一个数据项标记一个访问时间戳,每次插入新数据项的时候,先把数组中存在的数据项的时间戳自增,并将新数据项的时间戳置为0并插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项的时间戳置为0。当数组空间已满时,将时间戳最大的数据项淘汰。

这种实现思路很简单,但是有什么缺陷呢?需要不停地维护数据项的访问时间戳,另外,在插入数据、删除数据以及访问数据时,时间复杂度都是O(n)

链表:

  那么有没有更好的实现办法呢?

  那就是利用双向链表和hashmap(双向使得删除的复杂度为O(1))

(1)插入新的数据项:

如果新数据项在链表中存在(一般称为命中),则把该节点移到链表尾部,且如果value值不同,修改对应value值

如果不存在,则新建一个节点,放到链表尾部

若缓存满了,则把链表第一个节点删除即可。

(2)访问数据时:

如果数据项在链表中存在,则把该节点移到链表尾部,否则返回-1。

这样一来在链表头部的节点就是最近最久未访问的数据项。

复杂度:均为O(1)

struct Node{
    int key,value;
    Node *pre,*next;
    Node(int x,int y){
        key = x;
        value = y;
        pre = NULL;
        next = NULL;
    }
};

class LRUCache {
public:
    LRUCache(int cap) {
        capacity = cap;
        // 双向链表
        // 头指向最不常使用节点(满时从头的next开始删除)
        // 尾的pre指向最常使用节点
        head->next = tail;
        tail->pre = head;
    }

    int get(int key) {
        // 没找到
        if(m.find(key) == m.end()){
            return -1;
        }
        // 找到
        Node *cur = get_cur(m[key]);
        move_to_tail(cur);
        return m[key]->value;
    }

    void put(int key, int value) {
        // 已经存在key,只是value的值需要变更
        if(m.find(key) != m.end()){
            m[key]->value = value;
            Node *cur = get_cur(m[key]);
            move_to_tail(cur);
            return;
        }
        // 已满,删除最不常使用节点(head指向节点)
        else if(m.size() >= capacity){
            m.erase(head->next->key);
            head->next = head->next->next;
            head->next->pre = head;
        }
        Node *new_node = new Node(key,value);
        m[key] = new_node;
        move_to_tail(new_node);
    }

private:
    int capacity;
    unordered_map<int,Node*> m;
    Node *head = new Node(-1,-1);
    Node *tail = new Node(-1,-1);

    // 将一个节点从双向链表摘下
    Node * get_cur(Node *cur){
        cur->pre->next = cur->next;
        cur->next->pre = cur->pre;
        return cur;
    }

    // 将摘下节点移到最末尾(表示它是最新的)
    void move_to_tail(Node *cur){
        cur->pre = tail->pre;
        cur->pre->next = cur;
        tail->pre = cur;
        cur->next = tail;
    }
};

 

查找兄弟单词

live, evil, veil 这些字母顺序不同的词定义为 brother words,

给定一个词典, 编程输出所有的 brother words(以字典序输出)。

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;

bool isBrother(string s1,string s2){
    if(s1.size() != s2.size()) return false;
    if(s1 == s2) return false;
    sort(s1.begin(),s1.end());
    sort(s2.begin(),s2.end());
    return s1==s2;
}

int main(){
    int n;
    while(cin >> n){
        // 注意这些参数写在里面初始化,因为传参形式是循环调用
        int idx;
        string str,word,idx_word;
        vector<string> vs;
        for(int i = 0;i < n;i++){
            cin >> str;
            vs.push_back(str);
        }
        cin >> word;
        cin >> idx;

        // 字典排序,为了输出要求
        sort(vs.begin(),vs.end());

        // 逐个判断
        int count = 0;
        for(auto &s:vs){
            if(isBrother(s,word)){
                count++;
                if(count == idx){
                    idx_word = s;
                }
            }
        }
        if(!vs.empty()) cout << count << endl;
        if(count >= idx){
            cout << idx_word << endl;
        }
    }
    return 0;
}

 

快速幂

1.O(n)做法

double my_power(double x,int n){
    double res = 1;
    for(int i = 0;i < n;i++){
        res *= x;
    }
    return res;
}

2.O(logn)做法

double my_power(double x,int n){
    // remind从x的一次开始,不断翻倍,x的2,x的4,x的8,理由是每个n都可以被拆成二进制数
    double res = 1,remind = x;
    while(n){
        if(n & 1) res *= remind;
        n = n >> 1;
        remind *= remind;
    }
    return res;
}

 

组成三角形

给定一个长度为 1 的棍子, 中间随机砍两刀, 问可以组成三角形的概率是多少?

 

在一圆周上任意取三个点构成锐角三角形的概率是多少?

连续型随机变量概率密度函数(在不至于混淆时可以简称为密度函数)是一个描述这个随机变量的输出值,在某个确定的取值点附近的可能性的函数

而随机变量的取值落在某个区域之内的概率则为概率密度函数在这个区域上的积分。

 

补充:概率密度概念

离散型随机变量

随机变量是取值有多种可能并且取每个值都有一个概率的变量。它分为离散型和连续型两种,离散型随机变量的取值为有限个或者无限可列个(整数集是典型的无限可列),连续型随机变量的取值为无限不可列个(实数集是典型的无限不可列)。

描述离散型随机变量的概率分布的工具是概率分布表,它由随机变量取每个值的概率p(x = xi )= pi依次排列组成。它满足:

下面是一个概率分布表的例子:

表2.2 一个随机变量的概率分布表

如果我们把前面例子中掷骰子的点数x看做是随机变量,则其取值为1-6之间的整数,取每个值的概率为1/6,这是典型的离散型随机变量。

 

连续型随机变量

把分布表推广到无限情况,就可以得到连续型随机变量的概率密度函数。此时,随机变量取每个具体的值的概率为0,但在落在每一点处的概率是有相对大小的,描述这个概念的,就是概率密度函数。

一个函数如果满足如下条件,则可以称为概率密度函数:

可以看做是离散型随机变量的推广,积分值为1对应于取各个值的概率之和为1。分布函数是概率密度函数的变上限积分,它定义为:

显然这个函数是增函数,而且其最大值为1。分布函数的意义是随机变量的概率。注意,连续型随机变量取某一个值的概率为0,但是其取值落在某一个区间的值可以不为0:

 

扔一枚硬币, 正反面的概率不同. 用它给两个人抽奖, 如何让每人获奖概率相等. 如果是三个人呢?

(1)2个人

扔2次,01和10

(2)3个人

扔3次

011,101,110

 

3Sum

Given an array nums of n integers, are there elements abc in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.

Note:

The solution set must not contain duplicate triplets.

Example:

Given array nums = [-1, 0, 1, 2, -1, -4],

A solution set is:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

思路:主要注意的点就是去重问题

时间复杂度:O(N*N)

class Solution {
public:
    vector<vector<int>> res;
    vector<vector<int>> threeSum(vector<int>& nums) {
        if(nums.size() < 3) return res;
        
        sort(nums.begin(),nums.end());
        for(int i = 0;i < nums.size();i++){
            int l = i+1,r = nums.size()-1,sum;
            while(l < r){
                sum = nums[l]+nums[r]+nums[i];
                if(sum == 0){
                    vector<int> out = {nums[i],nums[l],nums[r]};
                    res.push_back(out);
                    // 去重
                    while(l < r && nums[l+1] == nums[l]) l++;
                    while(l < r && nums[r-1] == nums[r]) r--;
                    l++;
                    r--;
                }
                else if(sum < 0) l++;
                else r--;
            }
            // 去重
            while(i < nums.size()-1 && nums[i+1] == nums[i]) i++;
        }
        return res;
    }
};

 

大量的Query日志(1T左右),得到出现频次最高的100个Query

https://blog.csdn.net/fycy2010/article/details/46945641

(1) 数据量非常大:分而治之/hash映射

数据大则划为小的,如如一亿个Ip求Top 10

可先%1000将ip分到1000个小文件中去,并保证一种ip只出现在一个文件中(如果文件仍然过大,则继续分)

对每个小文件中的ip进行hashmap计数统计并按数量排序

最后归并或者最小堆依次处理每个小文件的top10以得到最后的结。

(2)数据量较为适中:hashmap + 堆

放弃分而治之/hash映射的步骤,直接上hash统计,然后排序。So,针对此类典型的TOP K问题,采取的对策往往是:hashmap + 堆。如下所示:

1、hash_map统计

先对这批海量数据预处理。具体方法是:维护一个Key为Query字串,Value为该Query出现次数的HashTable,即hash_map(Query,Value),每次读取一个Query,如果该字串不在Table中,那么加入该字串,并且将Value值设为1;如果该字串在Table中,那么将该字串的计数加一即可。最终我们在O(N)的时间复杂度内用Hash表完成了统计;


2、堆排序

借助堆这个数据结构,找出Top K,时间复杂度为N‘logK。即借助堆结构,我们可以在log量级的时间内查找和调整/移动。因此,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行对比。所以,我们最终的时间复杂度是:O(N) + N' * O(logK),(N为1000万,N’为300万)。

 

找出字符数组中出现次数最多的字符

思路:

哈希表映射一次,计数

int findMost(vector<int> nums){
    if(nums.empty()) return -1;

    unordered_map<int,int> m;
    int max_count = 0,max_num;
    for(auto &n:nums){
        if(m.count(n)) m[n]++;
        else m[n] = 1;
    }
    for(auto iter = m.begin();iter != m.end();iter++){
        if(iter->second > max_count){
            max_count = iter->second;
            max_num = iter->first;
        }
    }
    return max_num;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值