用回溯法(backtracking)解决平衡集合问题(一道微软公司面试题)
来源:互联网 发布:tcp默认端口号 编辑:程序博客网 时间:2024/05/17 23:34
(原题出自微软公司面试题)问题如下:
有两个序列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。
另外在状态空间树只需要考虑根节点的左枝子树,因为原问题考虑的是对半子集。
- 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[N];
- 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)解决平衡集合问题(一道微软公司面试题)
- 回溯法(backtracking)解决平衡集合问题
- 用回溯法和栈解决阿里面试题排队问题
- 微软公司面试题
- 微软公司面试题
- 微软公司面试题【1】
- 回溯法(Backtracking)
- 回溯法(Backtracking)
- 一道面试题:赛马问题
- 回溯法解决阿里面试题之12人排队
- 回溯法解决阿里面试题之括号匹配
- 单词反转 (微软公司面试题)
- 微软公司数据结构+算法面试题
- backtracking 回溯法题目总结
- 回溯法概述—BackTracking
- 用回溯法(backtracking)实现数学排列和组合
- BackTracking回溯
- 块设备(一)
- [转]深入理解abstract class和interface
- ffmpeg废弃的接口
- Android 中的BroadCastReceiver
- NSNotificationCenter介绍
- 用回溯法(backtracking)解决平衡集合问题(一道微软公司面试题)
- [算法导论]第十章《栈和队列》
- hdu1016 Prime Ring Problem 素数环
- Java 算法题-01
- 智能手机控制电梯的可行性
- 土豪金的加密与解密
- makefile问题总结
- 摩根士丹利华鑫基金公司面试
- A-MKL