头像

群二少




离线:16天前



群二少
16天前

简单介绍下背景:本人已工作两年,这次属于社招,不是校招哦!投递的岗位base是北京。

先上正文,一面内容大概:
1. 先来道算法题,不难:链表表示的两个数相加。面试官说不用运行,大概写一下就行,说是因为说牛客上的运行环境不行。这题之前做过,写完之后没跑就直接给他了,他问是不是之前刷过,为了装逼我说没有刷过,然后回答说:这题不就是CPU的加法器的实现嘛,计算机组成原理。
2. Https的过程讲一下。先是说了http+ssl,dns之后,准备讲ssl的原理时,他示意我说回答一下传输层相关的。然后我就回答了tcp三次握手,对着服务器端指定端口,比如80端口发起连接,之后就是正常的数据请求了。
缓存穿透和缓存击穿。其实是知道雪崩、穿透和击穿的,但是一下子没直接说分清穿透和击穿,主要是太久没用过,突然觉得击穿和穿透,好像都差不多啊。
3. Go 协程简单用法;
4. Go func与method之前的那个Receiver是什么?(答:类似Java的实例本身,效果同java中的this关键字,同时在go method也可以把这个Receiver当做参数来正常使用);
5. java 实例放在哪个区,常量放在哪个区;
6. 说一下Netty的IO原理,答:Reactor反应模型,Linux那边叫做IO多路复用。一个线程用来接收请求,将读写事件交给背后的worker线程。Redis、Nginx、Netty都是用到了这种模型。Redis其实也是多线程,只不过是用单线程来接收请求,在客户端看起来是串行接收执行,所以效果上就是单线程。但是IO多路复用才是Redis能高并发的底层保证。
MySQL left join、inner join:inner的就是差集、left的就是保左边;
7. Go的闭包语法,答了内部函数对外层函数局部变量的引用,类似的还有js、java的lambda;
8. Redis的setnx;(这个虽然只是提了一下,但是感觉没答好,需要加强。顾着准备Redis集群原理去了)
9. 分布式锁的提供方,答:用过ZK和Redis的分布式锁;
10. 项目相关问了不少,比如前家公司所负责项目的主要内容;
11. 项目所产生的的一些价值,可以具体点,用一些案例或者数字来佐证都行;(这块没答好,接下来二面需要再整个关于项目经验的描述、个人所负责项目、产生的价值等,越具体越好,能有一些具体案例或者数据来支撑更好);

接上,总之,也要花点时间来回顾自己在之前公司的贡献和工作内容,越详细越具体越好,拒绝假大空;
算法题:
1. 给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

二面大概内容:
1. 自我介绍,主要是说了工作经历,负责的项目的主要内容,越详细越好,把最能体验价值的结果或者报告什么的讲一讲;
2. TCP四次挥手,结合CS两端点的TCP栈和上层应用的交互来解释四次挥手,以及为何需要中间那个FIN-WAIT-2这个过程,最后由被动关闭一方的上层应用通过调用socket.closed()来结束数据传输,进入最终的FIN模式;
3. 操作系统内存模型?这玩意儿不是就段页转换啥的嘛,面试官进一步提示说哪些区放数据,哪些区放线程数据之类的,我问了下您是问类似JVM内存模型那样子的吗?他说不是。我又说,那不是的话,我对操作系统内存模型的认知大概就是高地址空间存放内核数据,然后低地址空间对于进程来说就是个虚拟空间,拥有完整的寻址空间,这里存放着进程的数据和代码等,再细就不是很了解了,然后就跳过这题了;
4. 算法题是股票买卖,一次和无限次两种。写出了无限次的情况,一次的一下子忘记怎么转过来,这题做的比较卡,整题至少有20分钟,哎。今天下午复习的时候还再次复习看了这个股票全家桶系列的解法呢,感觉只有看是不行的,要有充足的时间,就算没有从头理解,也要再默写一遍才算复习过。所以下次如果没有复习状态的时候,不要看着文字思路发呆,可以边默写边找回状态,嗯;

算法题:
给定一个数组代表股票每天的价格,请问只能买卖一次的情况下,最大化利润是多少?日期不重叠的情况下,可以买卖多次呢?
输入: {100, 80, 120, 130, 70, 60, 100, 125}
1)只能买一次:65(60 买进,125 卖出)
2)可以买卖多次: 115(80买进,130卖出;60 买进,125卖出)
输出买卖的序列和最大利润

三面主要内容如下:
1. 自我介绍;
2. 介绍项目情况,重点说了近一年的工作项目的大概;
3. 研究生在校的研究内容之类的,比较杂;
4. 项目中的技术亮点:了解几个,面试官貌似对这些也不是很感兴趣,这估计也是后面他觉得我没有成长的原因吧,可能认为这些东西我在之前就应该都学会了的;
5. 实现简单令牌桶算法,没有考虑随时间滑动的情况;
6. 加强版:令牌桶,加上随时间滑动的要求,即:限制用户在任一连续的一小时内,不能超过5W的请求。这边提到了说将一小时分成多格,比如60格这样的,面试官点头貌似同意了,然后就实现代码了,包括协程异步更新时间窗口;
7. 没有基础知识问答;
8. 没有算法题;

三面反问环节,问:我咋样?(措辞是: 这简短的面试交谈过程中,您觉得我咋样?), 面试官说,我这一年来成长不大,几乎没变化,可是我觉得这一年来才是稍微有点儿进步的呀。
不过,经过这么一问,觉得反问环节也挺有意思,以后如果再有面试,反问环境可以多问些面试官关于我们自己的评价和看法,比如:上面说的您觉得经过这一小时的交流,我整体怎么样?如果像上面的面试官说最近一年进步不大,那么应该继续追问:那您觉得应该向什么方向去深入学习和思考比较好呢?态度诚恳一点就行,作为面试官人家还是乐意帮你指出不足之处的。
而上述的两个问题:我咋样和如何改,抛开offer不谈,这两个问题我觉得可以说是正常面试最大的收获了。而面试之前准备的那些知识毕竟比较零散,还是需要靠平时的积累来巩固。面试最重要的是让他人来评价你,以前没反问这两个问题的时候,这些评价人家面试官基本不会主动告诉你,只会整理一下然后录入到公司的人才管理系统,以供后续评价。但你现在反问一下,就可以得知这两条信息,这些评价信息对你来说才是最有用的,放在公司它只是大量候选人评价数据中的一份,可有可无。但对你来说却是个宝贵的信息,你可以据此从别人的视角来审视自己,这种换个角度的审视比自己平日里思考写总结来得客观。毕竟你表现出来的才是客观的,内在的其他没表达出来的方面只能说你表达能力不行,也是种缺点,需要反思之一。
如果没有进行这样的反问,就不能更加全面的得知别人对你的看法,也就错过发现自己弱点和改正的机会,以前面试反问环节都是傻傻的问一些没多大意义的问题。今后应当多注意,要懂得跟面试官“要回”属于你的那份评价信息。

以上就是面试过程中涉及的一些内容,属于流水记账式的列了一下,下面谈谈一些感想吧。
关于算法的考察。结合上述三面的内容,会发现对于社招来说,算法题的比重并没有特别大,相反,工作经验和项目这块的占比会更多,但也不是说不考核算法,该刷的题还是得继续刷,算法是基础;
关于工作项目的梳理,有几个感想。首先表达清楚项目介绍的内容,然后是价值所在,关于项目所产生的的价值和你所做的贡献,这个很重要,是衡量你价值的一个点儿。最好能够梳理一下过去的一些文档和报告,整个具体的数据和细节点来谈谈,避免假大空;
日常工作中,最好有意识的去参加一些高质量的bug交流解决当中,这样不仅能够在日后面试当中跟面试官交流,也能在当初解决问题的时候提升自己的能力。这类高质量bug的解决就属于技术亮点,很考验一个人的基本功;
自我介绍的时候,就不用说自己叫什么、哪里人、什么时候在哪个学校毕业之类的,直接说毕业之后在哪里工作,负责的项目是啥,然后就可以把提前准备好的关于上述第二点的内容都讲一遍;

上述的感想也是比较离散、杂碎。还想再分享一下个人的一些学习经验,欢迎交流指正,正文如下。

技术面结束之后,今后对于基础的知识的学习可能没有这几天这么紧凑了,短时间填鸭式的学习大脑有点忙,不过这种感觉也还行,接触到了新知识有点成就感。当你在浏览面经的时候,里面提到的问题你觉得你都会了,是不是也有种自我肯定的情绪呢。
不过,面经里面提到的知识都是比较分散的,毕竟在那么短的面试时间内,面试官也只能随机抽样的检查,没时间做太全面的交流。这也导致我们看面经的时候都是比较分散的知识点,所以需要在今后,持续的学习,每段时间都专心研究某个知识系列,系统性的学习比较有效果,也比较全面。比如MySQL、Redis、ZooKeeper、MQ、JVM、OS、网络、算法等。

就算法来说,它是需要长时间的积累,短时间内的突击效果不是特别大,也累。所以今后对于算法的学习可以这么来:每日一题、或者之前做过的题目每天拿一两题出来再做一遍,重新思考,深入的多看看题解体会体会,不再是赶时间的去冲量。学会总结,理解每个算法与数据结构的含义,时间久了就能做到不变应万变。
另外,算法大部分还是属于背诵题,不少题目把模板写出来就完成的差不多了,只需要把细节处理一下就差不多了。时间久了,这些模板题就变成条件反射,此时对高级的数据结构也会有进一步的了解,思考速度和解题能力都有所进步。

OS和网络也差不多,这三者属于平日工作都很少直接能够体现出来的知识,平日工作用得少,又没去复习时间一久就会忘记, 用进废退, 人之常情嘛。所以才像上面说的,应该要坚持每日一题、或者时不时挑一些做过的题目重新做做,主要是为了保持手感,而做过的题目拿出来再做一遍也不会花太多时间,这样在日常工作之余也比较容易坚持下来。通过这段时间的面试准备,算是把算法入门,找到刷题的状态了,此后应该如上所说,坚持每日复习做做老题,偶尔周末时间充裕跟着“每日一题”做做新题,保持下去。
说回OS和网络,这类知识对于逻辑思维的要求没有算法题那么高,算法题属于“CPU相当密集型”,不练就不会。而OS和网络这类可能更多的是理解之后带点记忆的知识,做点输出存到笔记或者博客,定期来回顾回顾就行,属于“轻微IO密集型”,看完之后一段时间,也多少还有些印象,能够知个大概。每两年重看相关书籍,复习一下就可以巩固。
整个计算机体系,包括底层硬件组成原理、再上的操作系统和网络,这些之间其实都是有逻辑关系在的。可以OSI七层或者TCP/IP四层模型来看,它们就是个整体,全部理清之后,在大脑内可以存在相当长的时间。日常工作和生活中,只要遇到计算机相关的问题,大多都可以从这个整体来思考,这种思考也算是种回顾复习。
相比而言,算法算是比较离散的,排序、BFS/DFS、DP等之前的联系不是特别的紧密,至少没有像上述OSI七层模型这种递进关系相互依赖的情况,学习的时候也就可以单独知识点一个个击破了。

关于今后保持基础知识学习的想法,首先算法就如上述所言,每日一题、或者做过的题目重做,这样是为了不给本来就忙碌的工作日增加太大的压力,用简单的算法题来放松放松就行,搞几个AC找找成就感。
而对于基础知识,前期打算继续多翻阅翻阅面经,以面经中的题目为切入点,来复习(好像技术博客首页的文章也是个不错的切入点)。复习过程中再由点及面的去谷歌各种不明白,每个疑问点在输入谷歌之后,一次性多打开几个页面,多看多对比然后结合整理自己的理解,输出到日记或者博客中。比如,最近看到个比较经典的题目是MySQL的隔离级别,这个问题谷歌一搜,大部分都有提到MVCC、当前读、快照读、行锁、间隙锁,都什么知识啊,反正对于工作经验尚浅的我来说,已经触及到知识盲点了。也好,这些点都记下来,一个也别想跑,然后再分别谷歌这些知识点,可能又会引出一些更加底层的、你没见过的知识点,也没关系,继续记录继续搜。
今后可能又会看到MySQL索引相关的面试题,搜这个的时候,也许会碰到MyISAM、InnoDB、B/B+树、磁盘IO块与系统页、主索引与辅助索引、m叉树的分裂与合并……之类的,这样对MySQL的认知又多了一些。过些时日,你可能想着完整的去学一遍,这时候买本《高性能MySQL》来看,而且你之前学到的隔离级别、索引它里面都提到了,并且更加全面和仔细,从基本概念的出现到最终的常用场景,都给你列出来了。
上述之所以要先从面经题目切入学习是因为个人在看书的时候,总有种大而全的感觉,书毕竟比较理论,面面俱到,有用的没用的都会列出来。而面经上的题目是面试官结合当下工作内容提出的,有可能就是他最近工作中遇到的问题,直接抛给你看你怎么思考。那么这种面经题目就比较有意思,贴合实际工作,能够更好理解,经过面试官这么一折腾印象也更加深刻。这种零散的知识点学的差不多了,再来看书,就比较有感觉,知道书中哪些地方是重要知识,哪些地方其实没那么实用作为了解即可。
其实,日常工作中不管是用到的知识,还是遇到Bug,所涉及的知识点也是比较随机离散的,类似上面浏览面经过程中遇到的问题,看缘分。而在项目排期不那么紧的时间段里,找本书系统化的把之前的碎片化知识点串起来,就很有必要了,既是总结也是巩固。

关于学习的另一个观点:任何事物都是从小发展到大,学习的时候应该从它过去小的规模,跟着时间推移发展壮大,逐步去了解。比如学习Linux内核可以从早期低版本、JVM、Spring也类似,早期规模小,结构相对简单,比较容易理解。再逐步的按照发展需要,增加各种功能模块,直到当前最终版本。这样的学习路径可以清楚的知识系统中各个模块的由来与作用,也能够知道哪些是基础重要模块,而哪些是为了解决历史特定问题的模块,理清主线。
再举个例子,前些时候看到一篇关于限流的文章。文章一开始先从简单计数谈起,对于早期应用来说,简单的计数算法确实够用,后来随着业务的发展细化,简单计数粒度不够细,所以需要有类似滑动窗口这种效果的限流,也就衍生出了令牌桶和漏桶两种限流算法。

最后放上一个不错的记忆法——艾宾浩斯记忆规律,嗯就是小时候书上介绍的那个。新知识学习之后,分多次,每次不同间隔的去复习,大概十来次就能较好的将短期记忆转化为长久记忆了。而且学过之后,第二遍开始重新复习就简单多了,因为都理解过并整明白了,再重新复习更容易,更快,更省时。
之前考研的时候,背单词就隐约有用到这种方式。整本单词书,第一遍先花个三个月背一遍。之后,第二遍开始就越来越短,越来越快(这都什么虎狼之词)。前些时候背诵《道德经》也是类似,用了个APP,把每天背诵好的篇章截个图放到这个APP中,然后它会根据艾宾浩斯曲线的规律节点,定期提醒你复习。你要做的就是当它提醒的时候,打开快速默念一遍,默写一遍就可以勾掉了。还是那句话,第二遍越到后面,所花时间 就越来越短,越来越快。

以上,关于是学习方式和记忆方法的一些经验分享。最后再说句,任何工作和学习都是需要背诵记忆的,以此为基础来进行创作、推理和总结。比如何洁,他也是背过了大量的棋谱之后,再结合自身的想象力和逻辑能力,才有精湛的棋艺;比如钢琴家,也是需要背大量的琴谱、***,之后基于此再结合生活经验和灵感,来进行创作。巧妇难为无米之炊,脑中记忆的内容就是我们的大米,只有拥有大米,才有做出香喷喷的米饭的前提。并且大脑擅长的就是记忆,他就像是缓存,之前推理过的经历过的直接缓存在大脑中,之后如果再次遇见,直接查出来返回即可。
之所以还要再啰嗦的说下关于人尽皆知的记忆方法,是想强调任何学习和工作,它首要做的就是先去记忆,背一些概念。所以学习新知识的时候,遇到太多的概念不用慌,先背下来,背不住的就记在小本本上,常回顾。背到一定量之后就能产生质变,之后各种脉络也就慢慢打开了。

加油,米娜桑!



活动打卡代码 LeetCode 5. 最长回文子串

群二少
4个月前
func longestPalindrome(s string) string {
    length := len(s)
    if length < 2 {
        return s
    }

    str, maxLen := "", 0
    for i := 0; i < length - 1; i++ {
        str1 := recur(s, i, i)
        str2 := recur(s, i, i + 1)

        if len(str1) > maxLen {
            maxLen = len(str1)
            str = str1
        }

        if len(str2) > maxLen {
            maxLen = len(str2)
            str = str2
        }
    }

    return str
}

func recur(s string, l int, r int) string {
    for 0 <= l && r < len(s) {
        if s[l] == s[r] {
            l--
            r++
        }else {
            break
        }
    }
    return s[l + 1 : r]
}



群二少
4个月前
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
    len1 := len(nums1)
    len2 := len(nums2)

    head, head1, head2 := 0, 0, 0
    if len1 == 0 {
        head = nums2[head2]
    }else if len2 == 0 {
        head = nums1[head1]
    }else {
        if nums1[head1] > nums2[head2] {
            head = nums2[head2]
        }else {
            head = nums1[head1]
        }
    }

    midPos := (len1 + len2) / 2
    for i := 0; i < midPos; i++ {
        head, head1, head2 = getHeader(nums1, nums2, head1, head2)
    }

    if (len1 + len2) & 1 == 1 {
        head, head1, head2 = getHeader(nums1, nums2, head1, head2)
        return float64(head)
    }else {
        tmp := float64(head)
        head, head1, head2 = getHeader(nums1, nums2, head1, head2)
        return (float64(head) + tmp) / 2
    }

    return 0.0
}

func getHeader(nums1 []int, nums2 []int, head1 int, head2 int) (int, int, int) {
    if head1 >= len(nums1) {
        return nums2[head2], head1, head2 + 1
    }

    if head2 >= len(nums2) {
        return nums1[head1], head1 + 1, head2
    }

    if nums1[head1] > nums2[head2] {
        return nums2[head2], head1, head2 + 1
    }else {
        return nums1[head1], head1 + 1, head2
    }
}



群二少
4个月前
// 滑动窗口, 优化版,减少left的移动次数
func lengthOfLongestSubstring(s string) int {
    length := len(s)
    window := map[uint8]int{}

    max := 0
    left, right := 0, 0
    for right < length {
        c := s[right]

        if n, ok := window[c]; ok && n >= left {
            left = n + 1
        }

        if right - left + 1 > max {
            max = right - left + 1
        }

        window[c] = right
        right++
    }
    if right - left > max {
        max = right - left
    }

    return max
}


// 滑动窗口解法
func lengthOfLongestSubstring_(s string) int {
    length := len(s)
    window := map[uint8]int{}

    max := 0
    left, right := 0, 0
    for right < length {
        c := s[right]
        window[c]++

        for window[c] > 1 {
            if right - left > max {
                max = right - left
            }

            d := s[left]
            window[d]--
            left++
        }
        right++
    }
    if right - left > max {
        max = right - left
    }

    return max
}


活动打卡代码 LeetCode 2. 两数相加

群二少
4个月前
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {

    pre1, p1, pre2, p2 := l1, l1, l2, l2

    carry := 0
    for p1 != nil && p2 != nil {
        p1.Val = p1.Val + p2.Val + carry
        carry = 0
        if p1.Val > 9 {
            carry = p1.Val / 10
            p1.Val = p1.Val % 10
        }
        pre1, pre2 = p1, p2
        p1, p2 = pre1.Next, pre2.Next
    }

    // 处理最后一个进位
    if p1 == nil && p2 != nil {
        pre1.Next = p2
        dealCarry(pre2, p2, carry)
    }else if p2 == nil && p1 != nil {
        dealCarry(pre1, p1, carry)
    } else {
        dealCarry(pre1, p1, carry)
    }

    return l1
}

func dealCarry(pre *ListNode, p *ListNode, carry int) {
    for carry > 0 {
        if p != nil {
            p.Val += carry
            carry = 0

            if p.Val > 9 {
                carry = p.Val / 10
                p.Val = p.Val % 10
            }

            pre = p
            p = p.Next
        } else {
            pre.Next = &ListNode{carry, nil}
            carry = 0
        }
    }
}


活动打卡代码 LeetCode 1. 两数之和

群二少
4个月前
// 哈希版本
func twoSum(nums []int, target int) []int {
    length := len(nums)

    numsMap := map[int]int{}
    for i := 0; i < length; i++ {
        numsMap[nums[i]] = i
    }

    for i := 0; i < length; i++ {
        j, ok := numsMap[target - nums[i]]
        if i != j && ok {
            return []int{i, j}
        }
    }
    return []int{0, 0}
}

// 暴力
func twoSum_(nums []int, target int) []int {
    length := len(nums)
    for i := 0; i < length; i++ {
        for j := i +1; j < length; j++ {
            if nums[i] + nums[j] == target {
                return []int{i, j}
            }
        }
    }
    return []int{}
}


活动打卡代码 LeetCode 1. 两数之和

群二少
4个月前
// 哈希版本
func twoSum(nums []int, target int) []int {
    length := len(nums)

    numsMap := map[int]int{}
    for i := 0; i < length; i++ {
        numsMap[nums[i]] = i
    }

    for i := 0; i < length; i++ {
        j, ok := numsMap[target - nums[i]]
        if i != j && ok {
            return []int{i, j}
        }
    }
    return []int{0, 0}
}

// 暴力
func twoSum_(nums []int, target int) []int {
    length := len(nums)
    for i := 0; i < length; i++ {
        for j := i +1; j < length; j++ {
            if nums[i] + nums[j] == target {
                return []int{i, j}
            }
        }
    }
    return []int{}
}


活动打卡代码 AcWing 2. 01背包问题

群二少
4个月前
package main
import("fmt")

const N, V = 1010, 1010
var(
    n, v        int
    vol, val    int
    dp          [V]int
)

func main() {
    fmt.Scanln(&n, &v)

    for i := 0; i < n; i++ {
        fmt.Scanln(&vol, &val)
        for j := v; j >= vol; j-- {
            if dp[j] < dp[j - vol] + val {
                dp[j] = dp[j - vol] + val
            }
        }
    }
    fmt.Println(dp[v])
}


活动打卡代码 AcWing 901. 滑雪

群二少
4个月前
package main
import("fmt")

const N = 310
var(
    r, c    int
    h, f    [N][N]int
    dx, dy  [4]int
)

func main() {
    fmt.Scanln(&r, &c)
    dx = [4]int{0, 1, 0, -1}
    dy = [4]int{1, 0, -1, 0}

    for i:= 1; i <= r; i++ {
        for j := 1; j <= c; j++ {
            fmt.Scanf("%d", &h[i][j])
        }
    }

    res := 0
    for i := 1; i <= r; i++ {
        for j := 1; j <= c; j++ {
            res = max(res, dp(i, j))
        }
    }
    fmt.Println(res)
}

func dp(row int, col int) int {
    if f[row][col] > 0 {
        return f[row][col]
    }

    f[row][col] = 1
    for i := 0; i < 4; i++ {
        a, b := row + dx[i], col + dy[i]
        if 1 <= a && a <= r && 1 <= b && b <= c && h[row][col] > h[a][b] {
            f[row][col] = max(f[row][col], dp(a, b) + 1)
        } 
    }
    return f[row][col]
}

func max(a int, b int) int {
    if a > b {
        return a
    }
    return b
}


活动打卡代码 AcWing 282. 石子合并

群二少
4个月前
package main
import("fmt")

const NN, INF = 310, 1e9
var (
    n       int
    a, sum  [NN]int
    dp_282  [NN][NN]int
)

func main() {
    fmt.Scanf("%d", &n)
    for i := 1; i <= n; i++ {
        fmt.Scanf("%d", &a[i])
        sum[i] = sum[i - 1] + a[i]
    }

    for length := 2; length <= n; length++ {
        for l := 1; l + length - 1 <= n; l++ {
            r := l + length - 1
            dp_282[l][r] = INF
            for k := l; k < r; k++ {
                dp_282[l][r] = min(dp_282[l][r], dp_282[l][k] + dp_282[k + 1][r] + sum[r] - sum[l - 1])
            }
        }
    }
    fmt.Println(dp_282[1][n])
}


func min(x int, y int) int {
    if x > y {
        return y
    }
    return x
}