小孩排队问题

标签: 算法


初始问题:幼儿园小孩排成一行,但是男孩和女孩相邻会冲突,现在你是老师,每次只能调换相邻两个小孩的位置,要使男女冲突最少,至少要调换多少次?

输入情况: 用形如'BBBGGBB'的一个字符串表示队伍,'B'表示男孩,'G'表示女孩。

输入情况:最少的调换次数。

  目标是使男女冲突最少,可以理解为把男孩和女孩分开两边,左边全站男孩,右边全站女孩(或者左边全站女孩,右边全站男孩),那就只有中间的一个男孩一个女孩相邻了,显然这样冲突最少。

例如:'BBGBG' 变成 'BBBGG' 或者 ' GGBBB'

思路一:每次只能调换相邻两个小孩的位置,这个问题类似与冒泡排序,即每次调换两个冲突的小孩的位置,那么每次循环下来,就把一个小孩调换(移动)到最左边或者最右边,两重循环即可把所有孩子调换好,需要调换时使用计数器加一记录即可。

// 从右往左冒泡
// @s 小孩队列
// @move 该移动的小孩,'B'为男孩,'G'为女孩
public static int bubbleCount(char[] s, char move) {
    int count = 0;
    for (int i = 0; i < s.length - 1; i++) {
        for (int j = s.length - 1; j > i; j--) {
            // 若相邻两小孩性别不同,需要调换
            if (s[j] == move && s[j] != s[j - 1]) {
                swap(s[j], s[j - 1]);
                count++;
            }
        }
    }
    return count;
}

  这种方法使用了冒泡排序算法,复杂度为\(O(n^2)\),能算出移动男孩或者移动女孩的次数,且修改了原队列的值。

  要注意,只移动男孩或只移动女孩,有时并不是最优解。例如队列'BBBBBGB'时,如果移动男孩(这里统一指往左边移动),只需要将最右边的男孩往左移动1次。但移动的是女孩时,则需要调换5次。即实际上,调换男孩是调换次数最少的换法。所以要考虑这两种情况的最小值,即min(bubbleCount(s, 'B'), bubbleCount(s, 'G')

思路二:不改变原队列的值,只记录某个小孩右边的异性个数,该个数即该小孩需要调换(移动)的次数,问题是求得所有小孩移动次数的总和。

// 从左往右计数
// @s 小孩队列
// @move 该移动的小孩,'B'为男孩,'G'为女孩
public static int count(char[] s, char move) {
    int count = 0;
    for (int i = 0; i < s.length; i++) {
        if (s[i] == move) 
            for (int j = i + 1; j < s.length; j++) 
                if (s[j] != move) count++;
        return count;
    }
}

  这个方法循环计算了每个待移动的小孩右边的异性个数,复杂度为\(O(n^2)\)。有没有更加好的方法?我们观察到,从左到右计数时,异性的个数其实是重复计数的,所以我们可以使用一个变量存储起来,这时只需要循环一次即可解决问题,复杂度为\(O(n)\)

// 从左往右计数
// @s 小孩队列
// @move 该移动的小孩,'B'为男孩,'G'为女孩
public static int count(char[] s, char move) {
    int count = 0;
    // 不需移动的小孩个数
    int notMoveNum = 0;
    for (int i = 0; i < s.length; i++) {
        if (s[i] == move)
            count += notMoveNum;
        else
            notMoveNum++;
    }
    return count;
}

  实际上,这个方法记录的是某个小孩左边的异性个数。要记录右边的异性个数,只需要把循环条件反过来即可。
  和上面冒泡算法的情况相同,最后要从移动男孩还是移动女孩两种情况中,选出移动次数最少的一种,即min(count(s, 'B'), count(s, 'G')

转载于:https://www.cnblogs.com/banyu/p/6622272.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值