数组分割问题-详细版

题记:这道题和《编程之美》一书中2.18节的数组分割区别不大,但本人觉得《编程之美》这一节讲的不够透彻,不好理解(或许本人愚钝微笑),故给出自己的思路,同时也给出打印其中一种方案的方法(这一点《编程之美》并没有提到)。


两个序列大小均为n,序列元素的值为任一整数,无序;

要求通过交换两个序列的元素,使序列a元素之和与序列b的元素之和的差最小(可能存在很多种组合,要求找出其中一种即可)。

如序列:1  5   7   8   9和序列6  3   11  20  17我们可以通过交换得到新的序列1  5   9   8   20和序列7   6   3   11  17,前者和为43,后者和为44,两者之差为1最小。

                

         首先对于两个素个数相等的序列a、b我们利用《编程之美》2.18节的思想,将其合并为一个序列便于后续操作,序列中有负数的情况我们可以先预处理一下, 让每个元素都加上一个初始值使得最后每个元素都为正。整个问题就转化为在一个元素个数为2n的正数数组中找出其中n个元素,使得这n个元素之和与剩下元素 之和的差最小。

        《编程之美》2.18解法二中提到,从2n个数中找n个元素,有三种可能:大于Sum/2,小于Sum/2以及等于Sum/2。而大于Sum/2与小于等于Sum/2没区别,故可以只考虑小于等于Sum/2的情况,这一点我们仍然沿用这个思想。

        下面谈谈变化的东西:

        同样,利用动态规划的思想:

        先给一个空间复杂度为O(2N*N*Sum/2)即O(N2Sum)的方法,下面会对空间复杂度进行优化:

        设F[i][j][k]表示前i个元素中选取j个元素,使得其和不超过k且最接近k。那么可以根据第i个元素是否选择来进行决策

        状态方程如下:

                           1-1

         其中,F[i-1][j][k]表示前i-1个元素中选取j个使其和不超过但最逼近k;

         F[i-1][j-1][k-A[i]]在前i-1个元素中选取j-1个元素使其和不超过但最逼近k-A[i],这样再加上A[i]即第i个元素就变成了 选择上第i个元素的情况下最逼近k的和。而第一种情况与第二种情况是完备且互斥的,所以需要将两者最大的值作为F[i][j][k]的值。

        伪代码如下:

[cpp]  view plain copy
  1. F[][][]←  
  2.   
  3. for ← to 2*N  
  4.   
  5.     nLimit ← min(i,N)  
  6.   
  7.     do for ← to nLimit  
  8.   
  9.         do for ← to Sum/2  
  10.   
  11.             F[i][j][k] ← F[i-1][j][k]  
  12.   
  13.             if (k >= A[i] && F[i][j][k] F[i-1][j-1][k-A[i]]+A[i])  
  14.   
  15.                 then F[i][j][k] ← F[i-1][j-1][k-A[i]]+A[i]  
  16.   
  17. return F[2N][N][Sum/2]  

         当然,前面已经提到,要给出一种方案的打印,下面我们谈谈怎么打印一种方案。

         可以设置一个三维数组Path[][][]来记录所选择元素的轨迹。含路径的伪代码如下,只是在上述伪代码中添加了一点代码而已。

[cpp]  view plain copy
  1. F[][][]←  
  2.   
  3. Path[][][]←  
  4.   
  5. for ← to 2*N  
  6.   
  7.     nLimit ← min(i,N)  
  8.   
  9.     do for ← to nLimit  
  10.   
  11.         do for ← to Sum/2  
  12.   
  13.             F[i][j][k] ← F[i-1][j][k]  
  14.   
  15.             if (k >= A[i] && F[i][j][k] F[i-1][j-1][k-A[i]]+A[i])  
  16.   
  17.                 then F[i][j][k] ← F[i-1][j-1][k-A[i]]+A[i]  
  18.   
  19.                      Path[i][j][k] ←  
  20.   
  21. return F[2N][N][Sum/2] and Path[][][]  

根据求得的Path[][][]我们可以从F[2N][N][Sum/2]往F[0][0][0]逆着推导来打印轨迹对应的元素。伪代码如下:

[cpp]  view plain copy
  1. ← 2N  
  2.   
  3. ←  
  4.   
  5. ← Sum/2  
  6.   
  7. while (i && && 0)  
  8.   
  9.     do if(Path[i][j][k] 1)  
  10.   
  11.         then Print A[i]  
  12.   
  13.              ← j-1  
  14.   
  15.              ← k-A[i]  
  16.   
  17.     ← i-1  

上面的伪代码的意思是,每当找到一个Path[][][]=1,就将其对应的A[i]输出,因为已经确定一个所以j应该自减1,而k代表总和,所以也应该减去A[i]。至于为什么不管Path[][][]是否为1都需要i自减1,这一点可以参照本人博文《背包问题——“01背包”详解及实现(包含背包中具体物品的求解)》中的路径求法相关内容。

下面开始优化空间复制度为O(N*Sum/2)

         我们观察前面不含路径的伪代码可以看出,F[i][j][k]只与 F[i-1][][]有关,这一点状态方程上也能反映出来。所以我们可以用二维数组来代替三维数组来达到降低空间复杂度的目的。但是怎么代替里面存有玄 机,我们因为F[i][j][k]只与F[i-1][][]有关,所以我们用二维数组来代替的时候应该对F[i][j][k]的“j”维进行逆序遍历。为 什么?因为只有这样才能保证计算F[i][j][k]时利用的F[i-1][j][]和F[i-1][j-1][]是真正i-1这个状态的值,如果正序遍 历,那么当计算F[][j][]时,F[][j-1][]已经变化,那么计算的结果就是错误的。

       伪代码如下

[cpp]  view plain copy
  1. F[][]←  
  2.   
  3. for ← to 2*N  
  4.   
  5.     nLimit ← min(i,N)  
  6.   
  7.     do for ← nLimit to  
  8.   
  9.         do for ← A[i] to Sum/2  
  10.   
  11.             if (F[j][k] F[j-1][k-A[i]]+A[i])  
  12.   
  13.                 then F[j][k] ← F[j-1][k-A[i]]+A[i]  
  14.   
  15.    
  16.   
  17. return F[N][Sum/2] and Path[][][]  

上面的伪代码基本上和《编程之美》2.18节最后所给的代码基本一致了,但是里面并不含Path,如果要打印其中一种方案,那么仍需要2N*N*Sum/2的空间来存放轨迹。即

[cpp]  view plain copy
  1. F[][]←  
  2.   
  3. Path[][][]←  
  4.   
  5. for ← to 2*N  
  6.   
  7.     nLimit ← min(i,N)  
  8.   
  9.     do for ← nLimit to  
  10.   
  11.         do for ← A[i] to Sum/2  
  12.   
  13.             if (F[j][k] F[j-1][k-A[i]]+A[i])  
  14.   
  15.                 then F[j][k] ← F[j-1][k-A[i]]+A[i]  
  16.   
  17.                 Path[i][j][k] ←  
  18.   
  19. return F[N][Sum/2] and Path[][][]  

打印路径的伪代码与之前的一模一样,这里不再重写。

 

下面给出《编程之美》2.18节所讲的“数组分割”中给出的数据进行本文思想的C++代码实现

数组1   5   7   8   9   6   3   11  20  17一共10个数,拆成两个数组,使得这两个数组和之差最小。

[cpp]  view plain copy
  1. #include <iostream>  
  2. #include <cstring>  
  3. #include "CreateArray.h"    //该头文件是动态开辟及销毁二维三维数组的,读者自己实现  
  4. using namespace std;  


 //这里参数array为整个合并后的数组序列,nLen为合并后的数组长,nToBeClosed是之前所提的Sum/2

//算法时间复杂度为O(N2Sum),空间复杂度为O(N2Sum)

[cpp]  view plain copy
  1. int AdjustArray(int array[], int nLen, int nToBeClosed)  
  2.  
  3.     int*** NULL;  
  4.     int*** Path NULL;  
  5.     CreateThreeDimArray(F,nLen+1,nLen/2+1,nToBeClosed+1);       //创建三维数组,存放每一个状态  
  6.     CreateThreeDimArray(Path,nLen+1,nLen/2+1,nToBeClosed+1);    //创建三维数组,存放轨迹  
  7.     for(int 1; <= nLen; i++)  
  8.      
  9.         int nLimit min(i,nLen/2);  
  10.         for(int 1; <= nLimit; j++)  
  11.          
  12.             for(int 1; <= nToBeClosed; k++)  
  13.              
  14.                 F[i][j][k] F[i-1][j][k];  
  15.                 if(k >= array[i-1])  
  16.                  
  17.                     if(F[i][j][k] F[i-1][j-1][k-array[i-1]]+array[i-1])  
  18.                      
  19.                         F[i][j][k] F[i-1][j-1][k-array[i-1]]+array[i-1];  
  20.                         Path[i][j][k] 1;  
  21.                      
  22.                  
  23.              
  24.          
  25.      
  26.   
  27.          //打印调整后的其中一个数组  
  28.     int nLen, nLen/2, nToBeClosed;  
  29.     while(i && && 0)  
  30.      
  31.         if(Path[i][j][k] == 1)  
  32.          
  33.             cout << array[i-1] << "\t" 
  34.             -= array[i-1];  
  35.             j--;  
  36.          
  37.         i--;  
  38.      
  39.     cout << endl;  
  40.   
  41.     int nRet F[nLen][nLen/2][nToBeClosed];  
  42.     DestroyThreeDimArray(Path,nLen+1,nLen/2+1); //销毁轨迹表  
  43.     DestroyThreeDimArray(F,nLen,nLen/2+1);  //销毁状态表  
  44.     return nRet;  
  45.   

//这里参数array为整个合并后的数组序列,nLen为合并后的数组长,nToBeClosed是之前所提的Sum/2

//算法时间复杂度为O(N2Sum),空间复杂度不含Path为O(NSum/2),含Path为O(N2Sum)

[cpp]  view plain copy
  1. int Fun2(int array[], int nLen, int nToBeClosed)  
  2.  
  3.     int** NULL;  
  4.     int*** Path NULL;  
  5.     CreateTwoDimArray(F,nLen/2+1,nToBeClosed+1);        //创建二维状态表  
  6.     CreateThreeDimArray(Path,nLen+1,nLen/2+1,nToBeClosed+1);//创建三维轨迹表  
  7.   
  8.     for(int 1; <= nLen; i++)  
  9.      
  10.         int nLimit min(i,nLen/2);  
  11.         for(int nLimit; >= 1; j--)  
  12.          
  13.             for(int array[i-1]; <= nToBeClosed; k++)  
  14.              
  15.                 if(F[j][k] F[j-1][k-array[i-1]]+array[i-1])  
  16.                  
  17.                     F[j][k] F[j-1][k-array[i-1]]+array[i-1];  
  18.                     Path[i][j][k] 1;  
  19.                  
  20.              
  21.          
  22.      
  23.   
  24.          //打印调整后的其中一个数组  
  25.     int nLen, nLen/2, nToBeClosed;  
  26.     while(i && && 0)  
  27.      
  28.         if(Path[i][j][k] == 1)  
  29.          
  30.             cout << array[i-1] << "\t" 
  31.             -= array[i-1];  
  32.             j--;  
  33.          
  34.         i--;  
  35.      
  36.     cout << endl;  
  37.       
  38.     int nRet F[nLen/2][nToBeClosed];  
  39.     DestroyTwoDimArray(F,nLen/2+1); //销毁二维状态表  
  40.     DestroyThreeDimArray(Path,nLen+1,nLen/2+1); //销毁三维轨迹表  
  41.     return nRet;  
  42.  

测试代码

[cpp]  view plain copy
  1. int main()  
  2.  
  3.     int array[] {1,5,7,8,9,6,3,11,20,17};  
  4.     int nSum 0;  
  5.     for(int 0; sizeof(array)/sizeof(int); i++)  
  6.         nSum += array[i];  
  7.     int nToBeClosed nSum/2;  
  8.   
  9.     cout << Fun(array,sizeof(array)/sizeof(int),nToBeClosed) << endl;  
  10.     cout << Fun2(array,sizeof(array)/sizeof(int),nToBeClosed) << endl;  
  11.     return 0;  
  12.  
<think> 首先,用户的问题是:“我想查找将JavaScript数组分割成字符串的方法 请问JavaScript数组分割成字符串的方法”。这是一个关于JavaScript数组转换为字符串的查询。 根据系统级指令: - 所有行内数学表达式必须使用$...$格式,但这个问题不涉及数学表达式,所以不需要。 - 独立公式使用$$...$$,但这里也没有数学公式。 - LaTeX语法正确,但主要涉及代码。 - 使用中文回答。 - 生成相关问题- 回答中引用的段落末尾自然地添加引用标识,如[^1]。 - 用户可见层:回答结构清晰,逐步解决问题,保证真实可靠。 用户提供了几个引用: - 引用[1]:关于split()方法,将字符串分割数组- 引用[2]:关于数组去重和字符串分割数组- 引用[3]:详细数组和字符串方法,包括push, pop, concat等。 - 引用[4]:数组转换为字符串使用扩展运算符和模板字符串。 - 引用[5]:案例,使用循环将数组转换为分割字符串。 用户的问题是“将JavaScript数组分割成字符串的方法”,但根据引用,“分割”可能是个误解。用户说“分割成字符串”,但从上下文看,应该是“将数组转换为字符串”。因为: - split() 是将字符串分割数组- 数组转换为字符串是相反的操作。 在引用中: - 引用[4] 明确说“js:数组转换为字符串”。 - 引用[5] 是“js案例:数组转换为分割字符串”,意思是转换成分隔的字符串。 所以,用户的实际需求是:将JavaScript数组转换为字符串,通常使用分隔符连接元素。 核心方法在JavaScript中是: - join() 方法:数组的join()方法可以将数组元素连接成一个字符串,使用指定的分隔符。 - 其他方法如使用toString(),但toString()默认用逗号分隔。 - 或者手动循环连接。 从引用: - 引用[3] 提到了数组方法,但没有直接提到join(),在“返回新数组的方法”中没有join(),但join()是数组的方法。 - 引用[4] 展示了使用模板字符串:`${array[0]}, ${array[1]}, ${array[2]}`,但这不灵活。 - 引用[5] 展示了使用循环手动连接:`str = str + arr[i] + "|";`。 标准方法是Array.prototype.join()。 我应该提供正确的方法: 1. join() 方法:最常用和高效。 - 语法:`array.join(separator)` - separator可选,默认是逗号。 - 例如:`['a', 'b', 'c'].join('-')` 输出 "a-b-c" 2. toString() 方法:将数组转换为字符串,元素用逗号分隔。 - 例如:`['a', 'b', 'c'].toString()` 输出 "a,b,c" 3. 手动方法:使用循环或reduce()连接元素。 在回答中,需要结构清晰: - 解释方法。 - 提供代码示例。 - 引用相关引用,在末尾添加标识。 生成相关问题- 根据系统指令,在结尾生成相关问题,如示例所示。 - 相关问题应与主题相关,比如其他数组操作或字符串方法。 最终回答结构: 1. 介绍:简要说明如何将数组转换为字符串。 2. 详细方法: - 使用join() - 使用toString() - 其他方法(如手动或模板) 3. 代码示例。 4. 引用段落添加标识。 5. 生成相关问题。 </think> ### JavaScript数组转换为字符串的方法 在JavaScript中,将数组转换为字符串是一个常见操作,通常用于数据展示、日志输出或网络传输。核心方法包括使用内置的`join()`方法或`toString()`方法,其他方法如手动循环或模板字符串也可行。以下是详细解释和代码示例,基于站内引用内容[^3][^4][^5]。 #### 1. **使用 `join()` 方法(推荐)** - `join()` 是数组的内置方法,它将所有元素连接成一个字符串,并可选地指定分隔符。 - **语法**:`array.join(separator)` - `separator`:可选参数,指定元素间的分隔符(默认为逗号`,`)。 - **返回值**:字符串。 - **示例代码**: ```javascript const arr = ["apple", "banana", "cherry"]; const str1 = arr.join(); // 默认逗号分隔: "apple,banana,cherry" const str2 = arr.join(" - "); // 自定义分隔符: "apple - banana - cherry" console.log(str2); // 输出: "apple - banana - cherry" ``` - 优势:灵活、高效,支持自定义分隔符(如空格、竖线等)[^3]。 #### 2. **使用 `toString()` 方法** - `toString()` 将数组转换为字符串,元素间自动用逗号分隔。它是`join()`的简化,但无法自定义分隔符。 - **语法**:`array.toString()` - **返回值**:逗号分隔的字符串。 - **示例代码**: ```javascript const arr = [1, 2, 3]; const str = arr.toString(); // "1,2,3" console.log(str); // 输出: "1,2,3" ``` - 注意:适合简单场景,但分隔符固定为逗号[^3]。 #### 3. **其他方法** - **手动循环连接**:通过循环遍历数组元素,逐个连接成字符串(适合自定义处理)。 - **示例代码**(引用自站内案例[^5]): ```javascript const arr = ["red", "green", "blue"]; let str = ""; for (let i = 0; i < arr.length; i++) { str += arr[i] + "|"; // 添加分隔符"|" } console.log(str); // 输出: "red|green|blue|" ``` - 说明:使用`+=`操作符隐式转换元素为字符串,但末尾可能有多余分隔符,可通过`slice()`移除。 - **模板字符串与扩展运算符**:结合ES6特性,适合小数组(引用自[^4])。 - **示例代码**: ```javascript const arr = ['a', 'b', 'c']; const str = `${arr[0]}, ${arr[1]}, ${arr[2]}`; // 手动指定分隔符 console.log(str); // 输出: "a, b, c" ``` - 缺点:需手动索引,不适用于动态长度数组。 #### 性能与选择建议 - **最佳实践**:优先使用`join()`,因为它专为数组转字符串设计,性能高且代码简洁。 - **注意事项**: - 如果数组包含非字符串元素(如数字),`join()`和`toString()`会自动调用元素的`toString()`方法。 - 避免在大型数组上使用手动循环,以防性能下降[^3][^5]。 相关问题: 1. 如何将字符串分割回JavaScript数组? 2. JavaScript数组去重的常用方法有哪些? 3. 如何优化数组操作以提高性能?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值