几个有趣的问题及其解法

1 篇文章 0 订阅

逛博客时看到的,挺不错的,转来了。原作者:从零到无穷大



实现一个算法,找出单项链表中倒数第K个结点。


一种比较高效的解法是使用两个指针,使这两个指针分别指向链表中相距K个结点的两个结点。然后沿着链表同时移动这两个指针,当其中一个首先到达链表末端的时候,另一个指针指向的就是倒数第K个结点。显然,该算法的时间复杂度为O(n)。

public ListNode kthToLast(ListNode head, int k) {
    if (K <= 0) return null;

    ListNode fast = head;
    ListNode slow = head;

    // fast指针向前移动k个结点
    for (int i = 1; i <= k; i++) {
        if (slow == null) return null;
        fast = fast.next;
    }
    if (fast == null) return null;

    // fast到达末尾时,slow正好指向倒数第K个结点
    while (fast.next != null) {
        fast = fast.next;
        slow = slow.next;
    }
    return slow;
}

检测链表是否存在环路。


使用快慢指针法,快指针一次移动2步,慢指针一次移动1步。如果链表中存在环路,则两个指针必然会相遇。使用这种快慢指针的方式可以为解决很多链表相关的问题提供思路,比如使用两倍速的快指针可以让慢指针定位到链表中间位置,三倍速的快指针可以让慢指针定位到链表三分之一的位置等等,这也是解决不少链表问题的基础。


代码(n & (n-1)) == 0是什么含义?


该代码可以用用来检查n是否为0或2的某次方。同样的n = n & (n-1)会清除n的最低有效位。


实现一个方法,不使用临时变量,直接交换两个数。


方法1:

public static void swap(int a, int b) {
    a = a - b;
    b = a + b;
    a = b - a;  
}

方法2:

public static void swap(int a, int b) {
    a = a^b;
    b = a^b;
    a = a^b;
}

当转化为二进制时,只要能正确交换两个比特位即可。


实现一个方法,将两个数字相加,不能使用“+”号或其他运算符。


求解该题的关键在于把“相加”和“进位”两个操作分开进行,对于二进制,两个数相加而不考虑进位,即相当于进行异或操作。但是若只考虑进位,则又相当于按位与加上移位操作。因此代码可以实现为:

public static int add(int a, int b) {
    if (b == 0) return a;

    // 相加但不进位
    int sum = a ^ b;
    // 只进位
    int carry = (a & b) << 1;
    // 递归执行
    return add(sum, carry);
}

有20瓶药丸,其中19瓶装有1克/粒的药丸,余下一瓶装有1.1克/粒的药丸。给你一台精准的天平,怎么找出比较重的那瓶药丸?天平只能用一次。


对20瓶药丸从1到20编号,从每瓶药丸中取出对应编号颗药丸,如从一号瓶中取出1颗,二号瓶中取出2颗……,然后把这些取出的药丸一起放到天平上称重。如果每颗药丸都重1克,那么总重量应为(1+2+3+4+……+19+20)= 210克,所以如果实际重量大于210克,那么多出来的重量一定来自于没粒多0.1克的那瓶药丸。所以由多出来的重量,除以0.1克,得到的即为瓶子的编号。


给定两条绳子,每条绳子烧尽正好需要一个小时,怎样用这两条绳子准确计量十五分钟?绳子密度不均匀。


此题关键在于,绳子密度虽然不均匀,但是如果从两头同时点燃绳子,那么一根绳子烧完正好需要30分钟。所以,把这两根绳子中的一根从一头点燃,另一根同时从两头点燃,等两头点燃的绳子烧完时,正好过去30分钟。这时从一头点燃的绳子还可以再烧30分钟,这时再点燃这根绳子的另一头,并开始计时,等它烧完时,正好计量15分钟。


给定一个方法isSubString()可以用来检查一个字符串是否是其他字符串的子串,再给定两个字符串s1和s2。实现一个方法,检查s2是否为s1旋转而成,且只能调用一次isSubString()方法。


举个例子:s2=colinwang就是由s1=wangcolin旋转而成,所以可以将s1分成两个部分,即:s1=xy,x=wang,y=colin。这时我们会发现yx一定会是xyxy的子串,也就是说,s2一定是s1s1的子串,所以只需要调用isSubString(s1s1,s2)即可。


给定一个能产生0到4之间整数随机数的方法rand5(),实现一个能产生0到6之间整数随机数的方法rand7()。


rand7()应当返回0到6之间的整数,且返回每个整数的概率都应为七分之一。我们可以使用while循环,产生出一个范围的数值(至少含有7个元素),其中每个数值出现的概率相同。这样,我们再舍弃掉其中大于7的倍数的部分,最后在除以7取余数,得到范围0到6的随机数,每个值出现的概率都是七分之一。见下面的代码:


public static int rand7() {
    while(true) {
        // 该rand取值在0到24之间,取得其中每个值的概率相同,
        // 舍弃掉21、22、23、24,否则该方法返回的0到3之间的数字会偏多
        int rand = 5*rand5() + rand5();
        if (rand < 21) {
            return rand % 7;
        }
    }
}

仍然有个问题要提一下,上面的代码中为什么选择rand=5*rand5()+rand5()呢,因为它能均匀的产生0到24之间的数字,这样就保证了0到24之间每个数字出现的概率相同。如果选择rand=2*rand5()+rand5()则不行,因为这些值不是均匀分布的,比如取得6有两种方式(6=2*1+4和6=2*2+2),而取得0却只有一种方式(0=2*0+0),这样就导致了每个值出现的概率不等。该题目的关键就在于找到一个范围,并使得其中每个值出现的概率相同。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值