【MIX】算法题解(20200729)

ACW 756. 蛇形矩阵

题目链接

题面

输入两个整数 n n n m m m,输出一个 n n n m m m列的矩阵,将数字 1 ∼ n × m 1\sim n\times m 1n×m 按照回字蛇形填充至矩阵中.

解析

定义方向数组,每次循环到边界转移到下一个方向继续输出,直到输出所有的数值

AC代码

#include<iostream>
#include<cstring>

using namespace std;

const int N=105;
int n, m, tot;
int ans[N][N];
int dirs[4][2]={{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

int main(){
    memset(ans, 0x00, sizeof ans);
    cin>>n>>m;
    tot=n*m;
    for(int x=0, y=0, k=1, d=0; k<=tot; ++k){
        ans[x][y]=k;
        int nx=x+dirs[d][0];
        int ny=y+dirs[d][1];
        if(nx<0 || nx>=n || ny<0 || ny>=m || ans[nx][ny]){ // 确认新方向
            d=(d+1)%4;
            nx=x+dirs[d][0];
            ny=y+dirs[d][1];
        }
        x=nx, y=ny;
    }    
    
    for(int i=0; i<n; ++i){
        for(int j=0; j<m; ++j)
            cout<<ans[i][j]<<" ";
        cout<<endl;
    }
    return 0;
}

ACW 1453. 单链表快速排序

题目链接

题面

给定一个单链表,请使用快速排序算法对其排序。
要求:期望平均时间复杂度为 O ( n log ⁡ n ) \mathcal{O}(n\log n) O(nlogn),期望额外空间复杂度为 O ( log ⁡ n ) \mathcal{O}(\log n) O(logn).
思考题: 如果只能改变链表结构,不能修改每个节点的val值该如何做呢?

解析

链表快排,和quick sort思路一致,先确定一个pivot,建立三个子链表left,mid,left分别存储小于pivot的节点,等于pivot的节点和大于pivot的节点. 递归左右子链表后再拼接子链表.

AC代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    // 定位尾节点
    ListNode* get_tail(ListNode* head){
        while(head->next) head=head->next;
        return head;
    }

    ListNode* quickSortList(ListNode* head) {
        if(!head || !head->next) return head;
        // 定义三个子链表
        ListNode *left=new ListNode(-1); ListNode *pl=left;
        ListNode *mid=new ListNode(-1); ListNode *pm=mid;
        ListNode *right=new ListNode(-1); ListNode *pr=right;
        int val=head->val;
        
        // 分配元素
        for(auto p=head; p; p=p->next){
            if(p->val<val) pl=pl->next=p;
            else if(p->val==val) pm=pm->next=p;
            else pr=pr->next=p;
        }
        
        // 设置尾节点为nullptr
        pl->next=pm->next=pr->next=nullptr;
        // 递归左右侧
        left->next=quickSortList(left->next);
        right->next=quickSortList(right->next);
        
        // 拼接链表
        get_tail(left)->next=mid->next;
        get_tail(left)->next=right->next;
        
        // 释放空间
        auto p=left->next;
        delete left; delete mid; delete right;
        
        return p;
    }
};

ACW 1452. 矩阵极小值

题目链接

题面

给定一个 n × n n\times n n×n的矩阵,矩阵中包含 n × n n\times n n×n个互不相同 的整数。
定义极小值:如果一个数的值比与它相邻的所有数字的值都小,则这个数值就被称为极小值。
一个数的相邻数字是指其上下左右四个方向相邻的四个数字,另外注意,处于边界或角落的数的相邻数字可能少于四个。
要求在KaTeX parse error: Undefined control sequence: \logn at position 14: \mathcal{O}(n\̲l̲o̲g̲n̲)的时间复杂度之内找出任意一个极小值的位置,并输出它在第几行第几列。
本题中矩阵是隐藏的,你可以通过我们预设的函数query来获得矩阵中某个位置的数值是多少。
例如,query(a,b)即可获得矩阵中第 a 行第 b 列的位置的数值。

解析

时间复杂度提示使用二分法求解,从中间一列(int mid=l+r>>1)找出最小值,考虑最小值两侧的元素 V V V,如果左侧元素 L < V L<V L<V则在左侧寻找,如果右侧元素 R < V R<V R<V则在右侧寻找;继续二分求解.
q

AC代码

// Forward declaration of queryAPI.
// int query(int x, int y);
// return int means matrix[x][y].

typedef long long LL;
const int INF=0x3f3f3f3f;

class Solution {
public:
    vector<int> getMinimumValue(int n) {
        int l=0, r=n-1; // 对列进行二分搜索
        while(l<r){
            int mid=l+r>>1;
            int k; // 定位mid列中最小元素所在行
            LL val=INF; // mid列中最小元素的值
            for(int i=0; i<n; ++i){ // 定位mid列的最小元素
                int t=query(i, mid);
                if(t<val){
                    val=t;
                    k=i;
                }
            }
            // 检查mid列最小元素的左右侧
            LL left=mid? query(k, mid-1):INF;
            LL right=mid+1<n?query(k, mid+1):INF;
            // 如果当前元素小于左右侧元素,则返回
            if(val<left && val<right) return {k, mid};
            if(left<val) r=mid-1;
            else l=mid+1;
        }
        // 找到列的最小元素
        int k;
        LL val=INF;
        for(int i=0; i<n; ++i){
            int t=query(i, r);
            if(t<val){
                val=t;
                k=i;
            }
        }
        return {k, r};
    }
};

ACW 1048. 鸡蛋的硬度

题目链接

题面

最近XX公司举办了一个奇怪的比赛:鸡蛋硬度之王争霸赛。
参赛者是来自世界各地的母鸡,比赛的内容是看谁下的蛋最硬,更奇怪的是XX公司并不使用什么精密仪器来测量蛋的硬度,他们采用了一种最老土的办法–从高度扔鸡蛋–来测试鸡蛋的硬度,如果一次母鸡下的蛋从高楼的第a层摔下来没摔破,但是从a+1层摔下来时摔破了,那么就说这只母鸡的鸡蛋的硬度是a。
你当然可以找出各种理由说明这种方法不科学,比如同一只母鸡下的蛋硬度可能不一样等等,但是这不影响XX公司的争霸赛,因为他们只是为了吸引大家的眼球,一个个鸡蛋从100 层的高楼上掉下来的时候,这情景还是能吸引很多人驻足观看的,当然,XX公司也绝不会忘记在高楼上挂一条幅,写上“XX公司”的字样–这比赛不过是XX公司的一个另类广告而已。
勤于思考的小A总是能从一件事情中发现一个数学问题,这件事也不例外。
“假如有很多同样硬度的鸡蛋,那么我可以用二分的办法用最少的次数测出鸡蛋的硬度”,小A对自己的这个结论感到很满意,不过很快麻烦来了,“但是,假如我的鸡蛋不够用呢,比如我只有1个鸡蛋,那么我就不得不从第1层楼开始一层一层的扔,最坏情况下我要扔100次。如果有2个鸡蛋,那么就从2层楼开始的地方扔……等等,不对,好像应该从1/3的地方开始扔才对,嗯,好像也不一定啊……3个鸡蛋怎么办,4个,5个,更多呢……”,和往常一样,小A又陷入了一个思维僵局,与其说他是勤于思考,不如说他是喜欢自找麻烦。
好吧,既然麻烦来了,就得有人去解决,小A的麻烦就靠你来解决了。

解析

方法1:动态规划,时间复杂度 O ( n 2 m ) \mathcal{O}(n^2m) O(n2m)
定义 f [ i ] [ j ] f[i][j] f[i][j]表示测算楼层高度为 i i i, 当前可用鸡蛋数量为 j j j,需要的测量次数,枚举当前试验所在层 k k k,其中 1 ≤ k ≤ i 1\leq k\leq i 1ki,讨论试验结果,如果鸡蛋碎了,则下移至区间高度为 k − 1 k-1 k1进行试验,并且可用鸡蛋数量减一( j − 1 j-1 j1);如果鸡蛋没碎,则上移至区间高度为 i − k i-k ik进行试验,因此得到状态转移方程
f [ i ] [ j ] = max ⁡ ( f [ k − 1 ] [ j − 1 ] , f [ i − k ] [ j ] ) + 1 f[i][j]=\max(f[k-1][j-1], f[i-k][j])+1 f[i][j]=max(f[k1][j1],f[ik][j])+1

方法2:动态规划,时间复杂度 O ( n m ) \mathcal{O}(nm) O(nm)
定义 f [ i ] [ j ] f[i][j] f[i][j]表示使用 j j j个鸡蛋测量 i i i次最多可以测量的高度区间,可以根据试验结果分为两个独立的集合,因此状态转移方程为
f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − 1 ] + 1 f[i][j]=f[i-1][j]+f[i-1][j-1]+1 f[i][j]=f[i1][j]+f[i1][j1]+1

AC代码:DP

#include<cstring>
#include<iostream>

using namespace std;

const int N=105;
const int M=15;
int f[N][M];
int n, m;

int main(){
    while(cin>>n>>m){
        for(int i=1; i<=n; ++i) f[i][1]=i; // 区间高度为i, 只有一个鸡蛋, 需要测量i次
        for(int i=1; i<=m; ++i) f[1][i]=1; // 区间高度为1, 只要测量1次即可
        for(int i=2; i<=n; ++i){
            for(int j=2; j<=m; ++j){
                f[i][j]=f[i][j-1];
                for(int k=1; k<=i; ++k)
                    f[i][j]=min(f[i][j], max(f[k-1][j-1], f[i-k][j])+1);
            }
        }
        cout<<f[n][m]<<endl;
    }
    return 0;
}

AC代码:DP

#include<cstring>
#include<iostream>

using namespace std;

const int N=105;
const int M=15;
int f[N][M];
int n, m;

int main(){
    memset(f, 0x00, sizeof f);
    while(cin>>n>>m){
        for(int i=1; i<=n; ++i){
            for(int j=1; j<=m; ++j){
                f[i][j]=f[i-1][j]+f[i-1][j-1]+1;
            }
            if(f[i][m]>=n) {cout<<i<<endl; break;}
        }
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

minuxAE

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值