数组置换的题目是一类非常简单的动态规划。因为没有确定的递归步骤和算法。
(1) 例如,给出两个数组,要求调换数组当中的某些元素使得两个数组的各自元素之和,这两个和之间的绝对值之差最小。
怎么办呢? 循环扫描第一个数组当中的元素,比较第二个数组中的所有元素,如果某次对比发现交换他们能够使得交换之后的和的绝对值差最小,就交换这两个元素。反复进行直到扫描第一个数组每个元素都不需要再交换元素为止。用C++和Java分别实现:
- #include<cstdlib>
- #include<algorithm>
- using namespace std;
- size_t s=0;
- void f(int* A,int* B){
- int sumA=0;
- int sumB=0;
- for(size_t i=0;i<s;++i){
- sumA+=A[i];
- }
- for(size_t i=0;i<s;++i){
- sumB+=B[i];
- }
- bool change=true;
- while(change){
- change=false;
- for(size_t a=0;a<s;++a){
- for(size_t b=0;b<s;++b){
- int ab1=abs(sumA-sumB);
- int ab2=abs(sumA+2*B[b]-sumB-2*A[a]);
- if(ab1>ab2){
- sumA+=B[b];
- sumA-=A[a];
- sumB-=B[b];
- sumB+=A[a];
- swap(A[a],B[b]);
- change=true;
- }
- }
- }
- }
- printf("\nA=");
- for_each(A,A+s,[]( const int& i ){printf("%d,",i);});
- printf("\nB=");
- for_each(B,B+s,[]( const int& i ){printf("%d,",i);});
- }
- int main(void){
- int A[]={3,4,12};
- int B[]={2,3,9};
- int C[]={1,2,3};
- int D[]={0,999,0};
- s=_countof(A);
- f(A,B);
- printf("\n.........................\n");
- f(C,D);
- return 0;
- }
#include<cstdlib>
#include<algorithm>
using namespace std;
size_t s=0;
void f(int* A,int* B){
int sumA=0;
int sumB=0;
for(size_t i=0;i<s;++i){
sumA+=A[i];
}
for(size_t i=0;i<s;++i){
sumB+=B[i];
}
bool change=true;
while(change){
change=false;
for(size_t a=0;a<s;++a){
for(size_t b=0;b<s;++b){
int ab1=abs(sumA-sumB);
int ab2=abs(sumA+2*B[b]-sumB-2*A[a]);
if(ab1>ab2){
sumA+=B[b];
sumA-=A[a];
sumB-=B[b];
sumB+=A[a];
swap(A[a],B[b]);
change=true;
}
}
}
}
printf("\nA=");
for_each(A,A+s,[]( const int& i ){printf("%d,",i);});
printf("\nB=");
for_each(B,B+s,[]( const int& i ){printf("%d,",i);});
}
int main(void){
int A[]={3,4,12};
int B[]={2,3,9};
int C[]={1,2,3};
int D[]={0,999,0};
s=_countof(A);
f(A,B);
printf("\n.........................\n");
f(C,D);
return 0;
}
- public class my{
- static void f(int[] A,int[] B){
- int sumA=0;
- int sumB=0;
- for( int a: A ){
- sumA+=a;
- }
- for( int b: B ){
- sumB+=b;
- }
- boolean change=true;
- while(change){
- change=false;
- for(int a=0;a<A.length;++a){
- for(int b=0;b<B.length;++b){
- int ab1=Math.abs(sumA-sumB);
- int ab2=Math.abs(sumA+2*B[b]-sumB-2*A[a]);
- if(ab1>ab2){
- sumA+=B[b];
- sumA-=A[a];
- sumB-=B[b];
- sumB+=A[a];
- int tmp=A[a];
- A[a]=B[b];
- B[b]=tmp;
- change=true;
- }
- }
- }
- }
- System.out.println("=>A");
- for( int a: A ){
- System.out.print(a);
- System.out.print(',');
- }
- System.out.println("\n=>B");
- for( int b: B ){
- System.out.print(b);
- System.out.print(',');
- }
- }
- public static void main(String[] args) {
- int A[]={3,4,12};
- int B[]={2,3,9};
- int C[]={1,2,3};
- int D[]={0,999,0};
- my.f(A, B);
- System.out.println("==========");
- my.f(C, D);
- }
- }
public class my{
static void f(int[] A,int[] B){
int sumA=0;
int sumB=0;
for( int a: A ){
sumA+=a;
}
for( int b: B ){
sumB+=b;
}
boolean change=true;
while(change){
change=false;
for(int a=0;a<A.length;++a){
for(int b=0;b<B.length;++b){
int ab1=Math.abs(sumA-sumB);
int ab2=Math.abs(sumA+2*B[b]-sumB-2*A[a]);
if(ab1>ab2){
sumA+=B[b];
sumA-=A[a];
sumB-=B[b];
sumB+=A[a];
int tmp=A[a];
A[a]=B[b];
B[b]=tmp;
change=true;
}
}
}
}
System.out.println("=>A");
for( int a: A ){
System.out.print(a);
System.out.print(',');
}
System.out.println("\n=>B");
for( int b: B ){
System.out.print(b);
System.out.print(',');
}
}
public static void main(String[] args) {
int A[]={3,4,12};
int B[]={2,3,9};
int C[]={1,2,3};
int D[]={0,999,0};
my.f(A, B);
System.out.println("==========");
my.f(C, D);
}
}
(2) 还有一类问题时和排序/分段相关的。例如partition程序要把一个数组分成前后两段,好比说把一个整数数组分成奇数部分和偶数部分。O(n)就可以实现,C++代码:
- #include <iostream>
- #include <cstdio>
- using namespace std;
- //前后各设置一个指针,向中间移动
- void partition1( int* pi, size_t size )
- {
- int* pH = pi;
- int *pT = pi+size;
- while(pH<pT){
- while( ( (*pH)%2 == 1 ) && pH<pT )++pH;
- if(pH<pT)
- while( (*--pT)%2 == 0 );
- if(pH!=pT){
- int temp=*pH;
- *pH=*pT;
- *pT=temp;
- }
- }
- }
- int main()
- {
- int buf[]={3,6,7,7,2,3,4,2,1,8,9,10,5};//奇数放前面,偶数放后面
- size_t size=sizeof(buf)/sizeof(buf[0]);
- partition1(buf,size);
- for(size_t s=0;s<size;++s){
- printf("%d,",buf[s]);
- }
- return 0;
- }
#include <iostream>
#include <cstdio>
using namespace std;
//前后各设置一个指针,向中间移动
void partition1( int* pi, size_t size )
{
int* pH = pi;
int *pT = pi+size;
while(pH<pT){
while( ( (*pH)%2 == 1 ) && pH<pT )++pH;
if(pH<pT)
while( (*--pT)%2 == 0 );
if(pH!=pT){
int temp=*pH;
*pH=*pT;
*pT=temp;
}
}
}
int main()
{
int buf[]={3,6,7,7,2,3,4,2,1,8,9,10,5};//奇数放前面,偶数放后面
size_t size=sizeof(buf)/sizeof(buf[0]);
partition1(buf,size);
for(size_t s=0;s<size;++s){
printf("%d,",buf[s]);
}
return 0;
}
(3) 第3类问题是和求字典序有关的问题。如何求一个排列的"下一个排列"? 这个经典的字典序问题。例如我们有一个数组:
1,2,3,5,7,6,2,2,1 要求下一个升序的排列。那么首先从后往前找一个正序,也就是(5,7),然后从5开始往后找到比5大的最小的数6,交换(5,6)得到
1,2,3,6,7,5,2,2,1 可以看出来此时6后面的数是严格的降序,那么用前后指针的方法(就像前面所说的partiion算法类似的),调整为1,2,3,6,1,2,2,5,7就是要求的结果。
C++源代码如下:
- #include <iostream>
- #include <cstdlib>
- using namespace std;
- void my_permutation(int* buf, size_t nCount){
- //注释的部分解释第一次函数调用的结果。
- //首先从后往前找到第一个正序,第一次调用函数时,正序位置是5,9
- size_t idx = nCount - 1;
- for( ; idx >=1; --idx ){ // 反向查找
- if( buf[idx-1] >= buf[idx] )continue;//跳过重复的值: 1,2,2,7,9,5找到5
- else break;
- }
- --idx;
- if(idx<=0)return;
- //此时idx=3 cout<<idx<<endl;
- //从5向后找到比5大的最小的数7,交换5和7。
- size_t nSwap = 0; //数的位置
- int nStore = buf[idx]; //寻找7
- bool fSwap = false; // 找到了一个比5大的数
- for( size_t i = idx+1; i < nCount; ++ i ){
- if( buf[i] > buf[idx] ){// 大于5
- if( fSwap == false ){
- fSwap = true;
- nStore = buf[i]; // 找到了第一个比5大的数: 9
- nSwap = i;
- }
- else{ // 之前已经更新过fSwap了
- if( buf[i] < nStore ){
- nStore = buf[i]; //选一个更小的值: 7
- nSwap = i;
- }
- }
- }
- }
- //此时nSwap = 5, cout<<nSwap<<endl;
- swap( buf[idx], buf[nSwap] );
- ++idx; // 从idx后面的位置开始
- //交换后7之后的数字首位两两交换,得到第一正序
- size_t indexH = idx;
- size_t indexT = nCount -1;
- while( indexH <= indexT ){
- swap( buf[indexH++], buf[indexT--] );
- }
- for( size_t p = 0; p < nCount; ++ p ){
- cout<<buf[p]<<',';
- }
- cout<<endl;
- }
- int main(){
- int buf[]={1,2,3,5,7,6,2,2,1};
- my_permutation(buf,_countof(buf));
- my_permutation(buf,_countof(buf));
- my_permutation(buf,_countof(buf));
- my_permutation(buf,_countof(buf));
- my_permutation(buf,_countof(buf));
- my_permutation(buf,_countof(buf));
- return 0;
- }
#include <iostream>
#include <cstdlib>
using namespace std;
void my_permutation(int* buf, size_t nCount){
//注释的部分解释第一次函数调用的结果。
//首先从后往前找到第一个正序,第一次调用函数时,正序位置是5,9
size_t idx = nCount - 1;
for( ; idx >=1; --idx ){ // 反向查找
if( buf[idx-1] >= buf[idx] )continue;//跳过重复的值: 1,2,2,7,9,5找到5
else break;
}
--idx;
if(idx<=0)return;
//此时idx=3 cout<<idx<<endl;
//从5向后找到比5大的最小的数7,交换5和7。
size_t nSwap = 0; //数的位置
int nStore = buf[idx]; //寻找7
bool fSwap = false; // 找到了一个比5大的数
for( size_t i = idx+1; i < nCount; ++ i ){
if( buf[i] > buf[idx] ){// 大于5
if( fSwap == false ){
fSwap = true;
nStore = buf[i]; // 找到了第一个比5大的数: 9
nSwap = i;
}
else{ // 之前已经更新过fSwap了
if( buf[i] < nStore ){
nStore = buf[i]; //选一个更小的值: 7
nSwap = i;
}
}
}
}
//此时nSwap = 5, cout<<nSwap<<endl;
swap( buf[idx], buf[nSwap] );
++idx; // 从idx后面的位置开始
//交换后7之后的数字首位两两交换,得到第一正序
size_t indexH = idx;
size_t indexT = nCount -1;
while( indexH <= indexT ){
swap( buf[indexH++], buf[indexT--] );
}
for( size_t p = 0; p < nCount; ++ p ){
cout<<buf[p]<<',';
}
cout<<endl;
}
int main(){
int buf[]={1,2,3,5,7,6,2,2,1};
my_permutation(buf,_countof(buf));
my_permutation(buf,_countof(buf));
my_permutation(buf,_countof(buf));
my_permutation(buf,_countof(buf));
my_permutation(buf,_countof(buf));
my_permutation(buf,_countof(buf));
return 0;
}
运行结果是:
1,2,3,6,1,2,2,5,7,
1,2,3,6,1,2,2,7,5,
1,2,3,6,1,2,5,2,7,
1,2,3,6,1,2,5,7,2,
1,2,3,6,1,2,7,2,5,
1,2,3,6,1,2,7,5,2,
等效的stl代码调用如下:
- #include <iostream>
- #include <cstdlib>
- #include <algorithm>
- int main(){
- int buf[]={1,2,3,5,7,6,2,2,1};
- do{
- static int count = 0;
- for( size_t i = 0; i < _countof(buf); ++ i ){
- cout<<buf[i]<<',';
- }
- cout<<endl;
- if(++count>6)break;
- }while( std::next_permutation( buf, buf+_countof(buf) ) );
- return 0;
- }
#include <iostream>
#include <cstdlib>
#include <algorithm>
int main(){
int buf[]={1,2,3,5,7,6,2,2,1};
do{
static int count = 0;
for( size_t i = 0; i < _countof(buf); ++ i ){
cout<<buf[i]<<',';
}
cout<<endl;
if(++count>6)break;
}while( std::next_permutation( buf, buf+_countof(buf) ) );
return 0;
}