用回溯法(backtracking)解决平衡集合问题(一道微软公司面试题)
2014-03-04 21:33
363 查看
(原题出自微软公司面试题)问题如下:
有两个序列a,b,大小都为n,序列元素的值任意整数,无序;
要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小。
例如:
var a=[100,99,98,1,2, 3];
var b=[1, 2, 3, 4,5,40];
分析:
通过交换的方式,最终的状态是在保证两个序列中元素个数相同的条件下,任何一个元素都可以位于两个序列中的任何一个。这样问题可以转化为:在一个长度为2*n的整数序列中,如何将元素个数分成两个子集,记每个子集的元素之和分别为S1和S2,使得|S1-S2|最小。显然这是一个最优化问题,如果用brute-force方法,组合数是C(2n,n)=(2n)!/(2*(n!)), 如果n很大这个方法不奏效。
这里采用回溯法(backtracking),即前序(preorder)遍历状态空间树(state-space tree)。难点在于剪枝条件的确定,下面说明如何确定剪枝条件:
注意到如果将原序列按从小到大的顺序排好序,每次从较大的元素开始取,可以得到一个这样的规律:设长度为2*n序列的元素总和为Sigma,当前集合元素的和为S,剩下的元素之和为Sigma-S,如果二者满足S>=Sigma-S,即Sigma<=2*S,那么在当前集合中剩下需要添加进来的元素必须从余下的元素中取最小的那些元素,这样才能保证|S1-S2|最小。这是因为如果在下一次任意从余下的元素中取的元素分别为e和f,那么取e后的两个子集差为(S+e) - (Sigma-S-e) = 2S-Sigma +2e,取f后的两个子集差为2S-Sigma
+2f,显然如果e>f>0, 则有前者的子集差大于后者的子集差(注意这里假设元素都为非负整数,原序列中有负数的情况参考下面的讨论)。
如果输入序列中有负整数,可以通过平移操作转化为非负,因为每个数都平移了,它们的差值保值不变。如果不平移,结果不一定正确,比如:输入的2*n序列为:-10,5,3,20,25,50,平衡的对半子集应该为[-10,5,50]和[3,20,25],差值的绝对值为3。在下面的实现中,如果不考虑平移,得到的错误结果却是[-10,3,50]和[5,20,25],差值的绝对值为7。
另外在状态空间树只需要考虑根节点的左枝子树,因为原问题考虑的是对半子集。
[java] view
plaincopy
import java.util.Arrays;
import java.util.Stack;
/**
*
* @author ljs
* 2011-05-20
* 平衡集合问题
*
*/
public class BalancedSet {
//the offset to eliminate negative integers
int OFFSET;
int[] A;
//the total value of the two sets
int sigma;
//the number of elements in each set
int N;
//positive value
int minDiff=Integer.MAX_VALUE;
Stack<Integer> tracer = new Stack<Integer>();
Stack<Integer> bestDiffStack = new Stack<Integer>();
public BalancedSet(int[] A) throws Exception{
this.A = A;
this.init();
}
private void init() throws Exception{
if(A.length % 2 != 0)
throw new Exception();
N = A.length / 2;
//sort A in ascending order
Arrays.sort(A);
//offset if possible
if(A[0]<0){
OFFSET = -A[0];
for(int i=0;i<A.length;i++){
A[i] += OFFSET;
}
}
//sigma is the total value after offset is done
for(int i=0;i<A.length;i++){
sigma += A[i];
}
}
private void print(){
System.out.format("best partition difference is: %d%n",minDiff);
//caculate the difference of two sets
int[] P = new int
;
int p=0;
int i=0,j=bestDiffStack.size()-1;
//note: bestDiffStack is in descending order, we need an ascending order to compare with A
for(;i<A.length && j>=0;){
if(A[i]==bestDiffStack.get(j)){
i++;
j--;
}else if(A[i] < bestDiffStack.get(j)){
P[p++] = A[i++];
}//else: impossible case
}
if(i<A.length){
P[p++] = A[i++];
}
System.out.println("One set is: ");
while(!bestDiffStack.isEmpty())
System.out.format(" %2d",bestDiffStack.pop()-OFFSET);
System.out.println();
System.out.println("Another set is: ");
for(p=0;p<N;p++){
System.out.format(" %2d",P[p]-OFFSET);
}
}
public void solve(int[] A){
//the first node is not needed to analyse the include=false case
check(A.length-1, 0, 0, true);
print();
}
//A is sorted in ascending order
//count: the searched number of elements (<=N)
//include: is the element i included in the set
private void check(int i,int sum,int count,boolean include){
if(include){
//record the node
tracer.push(A[i]);
sum += A[i];
count++;
}
if(count==N){
int diff = Math.abs(2*sum- sigma);
if(diff < minDiff){
minDiff = diff;
//record the best nodes until now
bestDiffStack.clear();
for(Integer k:tracer){
bestDiffStack.add(k);
}
}//else: just throw away this combination
}else{
if(sigma<=2*sum){
//prune the tree: choose the remaining least numbers
int remainCount = N-count;
for(int j=0;j<remainCount;j++){
sum += A[j];
}
int diff = Math.abs(2*sum- sigma);
if(diff < minDiff){
minDiff = diff;
//record the nodes "1...remainCount"
bestDiffStack.clear();
for(Integer k:tracer){
bestDiffStack.add(k);
}
for(int j=remainCount-1;j>=0;j--){
bestDiffStack.push(A[j]);
}
}//else: just throw away this combination
}else{
if(i>=1){
//traverse the next subtrees in the state-space tree
check(i-1,sum,count,true);
check(i-1,sum,count,false);
}//else: the check is invalid
}
}
if(include)
//backtracking
tracer.pop();
}
public static void main(String[] args) throws Exception {
int A[] = {3,5,-10,20,25,50};
//int A[] = {3,5,10,20,25,50};
//int A[] = {100,99,98,1,2,3,1,2,3,4,5,40};
BalancedSet bs = new BalancedSet(A);
bs.solve(A);
}
}
有两个序列a,b,大小都为n,序列元素的值任意整数,无序;
要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小。
例如:
var a=[100,99,98,1,2, 3];
var b=[1, 2, 3, 4,5,40];
分析:
通过交换的方式,最终的状态是在保证两个序列中元素个数相同的条件下,任何一个元素都可以位于两个序列中的任何一个。这样问题可以转化为:在一个长度为2*n的整数序列中,如何将元素个数分成两个子集,记每个子集的元素之和分别为S1和S2,使得|S1-S2|最小。显然这是一个最优化问题,如果用brute-force方法,组合数是C(2n,n)=(2n)!/(2*(n!)), 如果n很大这个方法不奏效。
这里采用回溯法(backtracking),即前序(preorder)遍历状态空间树(state-space tree)。难点在于剪枝条件的确定,下面说明如何确定剪枝条件:
注意到如果将原序列按从小到大的顺序排好序,每次从较大的元素开始取,可以得到一个这样的规律:设长度为2*n序列的元素总和为Sigma,当前集合元素的和为S,剩下的元素之和为Sigma-S,如果二者满足S>=Sigma-S,即Sigma<=2*S,那么在当前集合中剩下需要添加进来的元素必须从余下的元素中取最小的那些元素,这样才能保证|S1-S2|最小。这是因为如果在下一次任意从余下的元素中取的元素分别为e和f,那么取e后的两个子集差为(S+e) - (Sigma-S-e) = 2S-Sigma +2e,取f后的两个子集差为2S-Sigma
+2f,显然如果e>f>0, 则有前者的子集差大于后者的子集差(注意这里假设元素都为非负整数,原序列中有负数的情况参考下面的讨论)。
如果输入序列中有负整数,可以通过平移操作转化为非负,因为每个数都平移了,它们的差值保值不变。如果不平移,结果不一定正确,比如:输入的2*n序列为:-10,5,3,20,25,50,平衡的对半子集应该为[-10,5,50]和[3,20,25],差值的绝对值为3。在下面的实现中,如果不考虑平移,得到的错误结果却是[-10,3,50]和[5,20,25],差值的绝对值为7。
另外在状态空间树只需要考虑根节点的左枝子树,因为原问题考虑的是对半子集。
[java] view
plaincopy
import java.util.Arrays;
import java.util.Stack;
/**
*
* @author ljs
* 2011-05-20
* 平衡集合问题
*
*/
public class BalancedSet {
//the offset to eliminate negative integers
int OFFSET;
int[] A;
//the total value of the two sets
int sigma;
//the number of elements in each set
int N;
//positive value
int minDiff=Integer.MAX_VALUE;
Stack<Integer> tracer = new Stack<Integer>();
Stack<Integer> bestDiffStack = new Stack<Integer>();
public BalancedSet(int[] A) throws Exception{
this.A = A;
this.init();
}
private void init() throws Exception{
if(A.length % 2 != 0)
throw new Exception();
N = A.length / 2;
//sort A in ascending order
Arrays.sort(A);
//offset if possible
if(A[0]<0){
OFFSET = -A[0];
for(int i=0;i<A.length;i++){
A[i] += OFFSET;
}
}
//sigma is the total value after offset is done
for(int i=0;i<A.length;i++){
sigma += A[i];
}
}
private void print(){
System.out.format("best partition difference is: %d%n",minDiff);
//caculate the difference of two sets
int[] P = new int
;
int p=0;
int i=0,j=bestDiffStack.size()-1;
//note: bestDiffStack is in descending order, we need an ascending order to compare with A
for(;i<A.length && j>=0;){
if(A[i]==bestDiffStack.get(j)){
i++;
j--;
}else if(A[i] < bestDiffStack.get(j)){
P[p++] = A[i++];
}//else: impossible case
}
if(i<A.length){
P[p++] = A[i++];
}
System.out.println("One set is: ");
while(!bestDiffStack.isEmpty())
System.out.format(" %2d",bestDiffStack.pop()-OFFSET);
System.out.println();
System.out.println("Another set is: ");
for(p=0;p<N;p++){
System.out.format(" %2d",P[p]-OFFSET);
}
}
public void solve(int[] A){
//the first node is not needed to analyse the include=false case
check(A.length-1, 0, 0, true);
print();
}
//A is sorted in ascending order
//count: the searched number of elements (<=N)
//include: is the element i included in the set
private void check(int i,int sum,int count,boolean include){
if(include){
//record the node
tracer.push(A[i]);
sum += A[i];
count++;
}
if(count==N){
int diff = Math.abs(2*sum- sigma);
if(diff < minDiff){
minDiff = diff;
//record the best nodes until now
bestDiffStack.clear();
for(Integer k:tracer){
bestDiffStack.add(k);
}
}//else: just throw away this combination
}else{
if(sigma<=2*sum){
//prune the tree: choose the remaining least numbers
int remainCount = N-count;
for(int j=0;j<remainCount;j++){
sum += A[j];
}
int diff = Math.abs(2*sum- sigma);
if(diff < minDiff){
minDiff = diff;
//record the nodes "1...remainCount"
bestDiffStack.clear();
for(Integer k:tracer){
bestDiffStack.add(k);
}
for(int j=remainCount-1;j>=0;j--){
bestDiffStack.push(A[j]);
}
}//else: just throw away this combination
}else{
if(i>=1){
//traverse the next subtrees in the state-space tree
check(i-1,sum,count,true);
check(i-1,sum,count,false);
}//else: the check is invalid
}
}
if(include)
//backtracking
tracer.pop();
}
public static void main(String[] args) throws Exception {
int A[] = {3,5,-10,20,25,50};
//int A[] = {3,5,10,20,25,50};
//int A[] = {100,99,98,1,2,3,1,2,3,4,5,40};
BalancedSet bs = new BalancedSet(A);
bs.solve(A);
}
}
相关文章推荐
- 用回溯法(backtracking)解决平衡集合问题(一道微软公司面试题)
- 回溯法(backtracking)解决平衡集合问题
- 从微软的一道面试题谈问题解决之道
- 一道有趣的面试题————待解决的问题
- 面试唐骏------从微软的一道面试题谈问题解决之道
- “在对应所需名称或序数的集合中,未找到项目”问题的解决方法
- 将java的集合转换为数据库集合类型 解决weblogic调用存储过程的时候,返回值乱码问题
- C# winform 初学者各种问题集合(已经本人验证能有效解决问题)
- FZU 2218 Simple String Problem (状压DP解决集合不相同元素问题)
- map reduce解决数据不平衡问题
- 回溯法解决“八皇后”问题
- 分类中数据不平衡问题的解决经验(转)
- 回溯法解决N皇后问题(以四皇后为例)
- C# LIstbox 解决WinForm下ListBox控件“设置DataSource属性后无法修改项集合”的问题
- 回溯法解决八皇后问题
- 企业网管常见问题解决集合
- PL/SQL中集合出现“ORA-06533:下表超出数量”的问题解决
- 回溯法解决N皇后问题
- Python基于回溯法子集树模板解决全排列问题示例
- C#使用回溯法解决背包问题实例分析