小孩排队问题

标签: 算法


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

输入情况: 用形如'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