1. 接雨水

给出n个非负整数表示每个宽度为1的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水

示例1:

  1. 输入:height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
  2. 输出:6

解释:
image.png

  • 上面由数组表示柱子的高度图,在这种情况下,可以接6个单位的雨水(蓝色部分表示雨水)

示例2:

  1. 输入:height = [4, 2, 0, 3, 2, 5]
  2. 输出:9

1.1 递归解法

首先,我们将高度为0的柱子,也视为有柱子,只是它的高度很矮。这样一来,雨水只会存储在当前柱子的上方

柱子上可以存水,需要满足以下几个条件:

  • 【条件一】当前柱子的左侧和右侧,必须有柱子。也就是说第一根柱子和最后一根柱子的上方,一定不会存水

  • 【条件二】当前柱子的左侧最高柱子与右侧最高柱子,任意一侧的高度,都必须大于当前柱子的高度。否则,当前柱子上一定不会存水

可以存水的情况:

  • 当前柱子不能是第一根或最后一根,即:size - 1 > index > 0

  • 当前柱子的左侧最高柱子与右侧最高柱子,较矮一侧的高度 > 当前柱子的高度。此时它们的高度差值,即为当前柱子上的存水量

    • 存水量 = min(左侧最高柱子的高度, 右侧最高柱子的高度) - 前柱子的高度

代码实现:

  1. class Solution {
  2. // 表示柱子高度的数组
  3. fileprivate var _arr : [Int];
  4. init(arr : [Int]) {
  5. _arr = arr;
  6. }
  7. func trap() -> Int {
  8. if(_arr.count < 3){
  9. // 小于3根柱子,无法存水
  10. return 0;
  11. }
  12. var sum = 0;
  13. for i in 1..<(_arr.count - 1) {
  14. // 获取当前柱子高度
  15. let currHeight = _arr[i];
  16. // 获取左侧最高柱子的高度
  17. let leftMaxHeight = getLeftMaxHeight(index: i - 1);
  18. // 获取右侧最高柱子的高度
  19. let rightMaxHeight = getRightMaxHeight(index: i + 1);
  20. // 获取左右较矮一侧柱子的高度
  21. let minMaxHeight = min(leftMaxHeight, rightMaxHeight);
  22. // 如果较矮一侧柱子的高度 <= 当前柱子高度
  23. if(minMaxHeight <= currHeight){
  24. // 无法存水,跳过循环
  25. continue;
  26. }
  27. // 否则,当前柱子上的存水量 = 较矮一侧柱子的高度 - 当前柱子高度
  28. // 将其累加到总存水量中
  29. sum += (minMaxHeight - currHeight);
  30. }
  31. // 返回所有柱子上的总存水量
  32. return sum;
  33. }
  34. // 获取左侧最高柱子的高度
  35. fileprivate func getLeftMaxHeight(index : Int) -> Int {
  36. if(index < 1){
  37. return _arr[index];
  38. }
  39. let curr = _arr[index];
  40. let left = getLeftMaxHeight(index: index - 1);
  41. return max(curr, left);
  42. }
  43. // 获取右侧最高柱子的高度
  44. fileprivate func getRightMaxHeight(index : Int) -> Int {
  45. if(index >= _arr.count - 1){
  46. return _arr[_arr.count - 1];
  47. }
  48. let curr = _arr[index];
  49. let right = getRightMaxHeight(index: index + 1);
  50. return max(curr, right);
  51. }
  52. }

1.2 缓存优化

定义两个数组,将当前柱子的左侧最高柱子的高度,以及右侧最高柱子的高度分别缓存

  1. class Solution {
  2. // 表示柱子高度的数组
  3. fileprivate var _arr : [Int];
  4. init(arr : [Int]) {
  5. _arr = arr;
  6. }
  7. fileprivate var _leftCache : [Int]?;
  8. fileprivate var _rightCache : [Int]?;
  9. func trap() -> Int {
  10. if(_arr.count < 3){
  11. // 小于3根柱子,无法存水
  12. return 0;
  13. }
  14. // 初始化左侧缓存
  15. _leftCache = [Int].init(repeating: -1, count: _arr.count);
  16. // 初始化右侧缓存
  17. _rightCache = [Int].init(repeating: -1, count: _arr.count);
  18. var sum = 0;
  19. for i in 1..<(_arr.count - 1) {
  20. // 获取当前柱子高度
  21. let currHeight = _arr[i];
  22. // 获取左侧最高柱子的高度
  23. let leftMaxHeight = getLeftMaxHeight(index: i - 1);
  24. // 获取右侧最高柱子的高度
  25. let rightMaxHeight = getRightMaxHeight(index: i + 1);
  26. // 获取左右较矮一侧柱子的高度
  27. let minMaxHeight = min(leftMaxHeight, rightMaxHeight);
  28. // 如果较矮一侧柱子的高度 <= 当前柱子高度
  29. if(minMaxHeight <= currHeight){
  30. // 无法存水,跳过循环
  31. continue;
  32. }
  33. // 否则,当前柱子上的存水量 = 较矮一侧柱子的高度 - 当前柱子高度
  34. // 将其累加到总存水量中
  35. sum += (minMaxHeight - currHeight);
  36. }
  37. // 返回所有柱子上的总存水量
  38. return sum;
  39. }
  40. // 获取左侧最高柱子的高度
  41. fileprivate func getLeftMaxHeight(index : Int) -> Int {
  42. if(_leftCache![index] != -1){
  43. // 命中缓存
  44. return _leftCache![index];
  45. }
  46. // 计算并写入缓存
  47. if(index < 1) {
  48. _leftCache![index] = _arr[index];
  49. }
  50. else {
  51. let curr = _arr[index];
  52. let left = getLeftMaxHeight(index: index - 1);
  53. _leftCache![index] = max(curr, left);
  54. }
  55. return _leftCache![index];
  56. }
  57. // 获取右侧最高柱子的高度
  58. fileprivate func getRightMaxHeight(index : Int) -> Int {
  59. if(_rightCache![index] != -1) {
  60. // 命中缓存
  61. return _rightCache![index];
  62. }
  63. // 计算并写入缓存
  64. if(index >= _arr.count - 1) {
  65. _rightCache![index] = _arr[_arr.count - 1];
  66. }
  67. else {
  68. let curr = _arr[index];
  69. let right = getRightMaxHeight(index: index + 1);
  70. _rightCache![index] = max(curr, right);
  71. }
  72. return _rightCache![index];
  73. }
  74. }

1.3 递推解法

计算存水量时,我们会从左往右遍历每一根柱子,所以左侧最高柱子的高度,可以跟随遍历一起计算

但右侧最高柱子的高度,如果在计算存水量时通过子循环遍历结果,会产生大量重复子问题的计算,影响效率。所以我们最好提前将计算出每一根柱子的右侧最高柱子的高度,并将其写入缓存

  1. class Solution {
  2. // 表示柱子高度的数组
  3. fileprivate var _arr : [Int];
  4. init(arr : [Int]) {
  5. _arr = arr;
  6. }
  7. // 获取右侧柱子的缓存
  8. fileprivate func getRightCache() -> [Int] {
  9. // 初始化右侧柱子的缓存
  10. var rightCache = [Int].init(repeating: 0, count: _arr.count);
  11. // 初始化右侧最高柱子的高度
  12. var rightMax = 0;
  13. for j in (1..<(_arr.count - 1)).reversed() {
  14. // 获取后一根柱子的高度
  15. let next = _arr[j + 1];
  16. // 如果后一根柱子的高度 > 右侧最高柱子的高度
  17. if(next > rightMax){
  18. // 更新右侧最高柱子的高度
  19. rightMax = next;
  20. }
  21. // 写入缓存
  22. rightCache[j] = rightMax;
  23. }
  24. return rightCache;
  25. }
  26. func trap() -> Int {
  27. if(_arr.count < 3){
  28. // 小于3根柱子,无法存水
  29. return 0;
  30. }
  31. // 获取右侧柱子的缓存
  32. let rightCache = getRightCache();
  33. // 初始化左侧最高柱子的高度
  34. var leftMaxHeight = 0;
  35. // 初始化总存水量
  36. var sum = 0;
  37. for i in 1..<(_arr.count - 1) {
  38. // 获取当前柱子高度
  39. let currHeight = _arr[i];
  40. // 获取前一根柱子的高度
  41. let prev = _arr[i - 1];
  42. // 如果前一根柱子的高度 > 左侧最高柱子的高度
  43. if(prev > leftMaxHeight){
  44. // 更新左侧最高柱子的高度
  45. leftMaxHeight = prev;
  46. }
  47. // 从缓存中获取右侧最高柱子的高度
  48. let rightMaxHeight = rightCache[i];
  49. // 获取左右较矮一侧柱子的高度
  50. let minMaxHeight = min(leftMaxHeight, rightMaxHeight);
  51. // 如果较矮一侧柱子的高度 <= 当前柱子高度
  52. if(minMaxHeight <= currHeight){
  53. // 无法存水,跳过循环
  54. continue;
  55. }
  56. // 否则,当前柱子上的存水量 = 较矮一侧柱子的高度 - 当前柱子高度
  57. // 将其累加到总存水量中
  58. sum += (minMaxHeight - currHeight);
  59. }
  60. // 返回所有柱子上的总存水量
  61. return sum;
  62. }
  63. }

1.4 单调栈

单调栈:单调递增或单调减的栈,跟单调队列差不多,但是只用到它的一端

使用单调递增栈,根据柱子的高度进行对比,存储柱子的索引值。栈顶元素arr[top]作为当前柱子,栈顶元素的下一个元素arr[left]作为左侧柱子,arr[i]作为右侧柱子

当前栈非空,并且当前柱子的高度 < 右侧柱子的高度,进入栈的遍历

获取当前柱子,并将其出栈。再获取左侧柱子,如果不存在左侧柱子,结束栈的遍历

存在,先使用i - left - 1计算存水区域,再使用min(left, right) - curr计算存水量。通过存水量 * 存水区域获得区域内柱子的存水量,将其加入到总存水量中

代码实现:

  1. class Solution {
  2. // 表示柱子高度的数组
  3. fileprivate var _arr : [Int];
  4. init(arr : [Int]) {
  5. _arr = arr;
  6. }
  7. func trap() -> Int {
  8. if(_arr.count < 3){
  9. // 小于3根柱子,无法存水
  10. return 0;
  11. }
  12. // 初始化总存水量
  13. var sum = 0;
  14. // 初始化栈结构
  15. let stack = LinkedStack<Int>();
  16. // 遍历所有柱子
  17. for i in 0..<_arr.count {
  18. // 获取当前柱子的高度,作为右侧柱子的高度使用
  19. let right = _arr[i];
  20. // 非空栈 && 当前柱子的高度 < 右侧柱子的高度,进入栈的遍历
  21. while(!stack.isEmpty() && _arr[stack.getTop()!] < right){
  22. // 栈顶元素出栈,作为当前柱子的索引值
  23. let currIndex = stack.pop()!;
  24. // 获得当前柱子的高度
  25. let curr = _arr[currIndex];
  26. // 再次获取栈顶元素,作为左侧柱子的索引值
  27. let leftIndex = stack.getTop();
  28. // 判断是否存在左侧柱子
  29. if(leftIndex == nil){
  30. // 不存在,结束栈的遍历
  31. break;
  32. }
  33. // 存水区域 = 当前索引值 - 左侧柱子的索引值 - 1
  34. let width = i - leftIndex! - 1;
  35. // 获得左侧柱子的高度
  36. let left = arr[leftIndex!];
  37. // 当前柱子上的存水量 = 较矮一侧柱子的高度 - 当前柱子高度
  38. let difference = min(left, right) - curr;
  39. // 总存水量 = 当前柱子上的存水量 * 可存水的柱子总数
  40. // 将其累加到总存水量中
  41. sum += difference * width;
  42. }
  43. // 将右侧柱子的索引值入栈,进入下一次栈的遍历,作为当前柱子的索引值使用
  44. stack.push(element: i);
  45. }
  46. // 返回所有柱子上的总存水量
  47. return sum;
  48. }
  49. }
  • 时间复杂度:O(n),因为从0n-1的每个索引值最多只会入栈和出栈各一次

  • 空间复杂度:O(n),它主要取决于栈空间,栈的大小不会超过n

1.5 双指针

使用双指针leftIndexrightIndex,分别表示左右两侧柱子的索引值。leftIndex默认为0,只能向右移动。rightIndex默认为n - 1,只能向左移动

使用leftMaxHeightrightMaxHeight,分别记录左右两侧最高柱子的高度。leftMaxHeight默认为第一根柱子的高度,rightMaxHeight默认为最后一根柱子的高度

只要左右指针不相遇,就会持续遍历,内部逻辑如下:

  • 如果当前左侧柱子的高度 < 当前右侧柱子的高度,必然leftMaxHeight < rightMaxHeight,通过左侧最高柱子的高度 - 当前左侧柱子的高度得到存水量。将左指针向右移动,并更新leftMaxHeight

  • 如果当前左侧柱子的高度 >= 当前右侧柱子的高度,必然leftMaxHeight >= rightMaxHeight,通过右侧最高柱子的高度 - 当前右侧柱子的高度得到存水量。将右指针向左移动,并更新rightMaxHeight

解读:左指针右移的终止条件是找到>= rightMaxHeightleftMaxHeight,也就是说一旦左指针终止右移,此时的arr[leftIndex]一定是leftMaxHeight,并且>= rightMaxHeight。同理,右指针左移的终止条件是找到> leftMaxHeightrightMaxHeight,而此时的arr[rightIndex]就是rightMaxHeight

代码实现:

  1. class Solution {
  2. // 表示柱子高度的数组
  3. fileprivate var _arr : [Int];
  4. init(arr : [Int]) {
  5. _arr = arr;
  6. }
  7. func trap() -> Int {
  8. if(_arr.count < 3){
  9. // 小于3根柱子,无法存水
  10. return 0;
  11. }
  12. // 初始化总存水量
  13. var sum = 0;
  14. // 左侧柱子的索引值,默认为第一根
  15. var leftIndex = 0;
  16. // 左侧最高柱子的高度,默认为第一根柱子的高度
  17. var leftMaxHeight = _arr[leftIndex];
  18. // 右侧柱子的索引值,默认为最后一根
  19. var rightIndex = _arr.count - 1;
  20. // 右侧最高柱子的高度,默认为最后一根柱子的高度
  21. var rightMaxHeight = _arr[rightIndex];
  22. // 只要左右指针不相遇,就会持续遍历
  23. while(leftIndex != rightIndex){
  24. // 获得左侧柱子的高度
  25. var leftHeight = _arr[leftIndex];
  26. // 获得右侧柱子的高度
  27. var rightHeight = _arr[rightIndex];
  28. // 如果左侧比右侧柱子的高度矮,必然左侧最高比右侧最高矮
  29. // 如果不好理解,也可以改成leftMaxHeight < rightMaxHeight
  30. // 两种写法目的相同,都是为了找到更矮一侧的柱子,计算它的存水量
  31. // 如果找不到>=rightMaxHeight的leftMaxHeight,则移动的永远都是左指针
  32. if(leftHeight < rightHeight){
  33. // 存水量取决于左右最高柱子中较矮一侧的高度,与当前柱子高度的差值
  34. // 因为:左侧高度 < 右侧高度,所以:左侧最高柱子的高度 < 右侧最高柱子的高度
  35. // 故此,我们不必求解min(leftMaxHeight, rightMaxHeight),直接使用以下公式即可
  36. // 存水量 = 左侧最高柱子的高度 - 当前左侧柱子的高度
  37. sum += leftMaxHeight - leftHeight;
  38. // 左侧柱子的索引值+1
  39. leftIndex += 1;
  40. // 获得当前左侧柱子的高度
  41. leftHeight = _arr[leftIndex];
  42. // 更新左侧最高
  43. leftMaxHeight = max(leftHeight, leftMaxHeight);
  44. }
  45. else{
  46. // 同理:左侧高度 >= 右侧高度,所以:左侧最高柱子的高度 >= 右侧最高柱子的高度
  47. // 故此,存水量 = 右侧最高柱子的高度 - 当前右侧柱子的高度
  48. sum += rightMaxHeight - rightHeight;
  49. // 左侧柱子的索引值-1
  50. rightIndex -= 1;
  51. // 获得当前右侧柱子的高度
  52. rightHeight = _arr[rightIndex];
  53. // 更新右侧最高
  54. rightMaxHeight = max(rightHeight, rightMaxHeight);
  55. }
  56. }
  57. // 返回所有柱子上的总存水量
  58. return sum;
  59. }
  60. }
  • 时间复杂度:O(n),两个指针的移动总次数不超过n

  • 空间复杂度:O(1),只需要使用常数的额外空间

2. 分割等和子集

给出一个只包含正整数的非空数组nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等

示例1:

  1. 输入:nums = [1, 5, 11, 5]
  2. 输出:true
  • 数组可以分割成[1, 5, 5][11]

示例2:

  1. 输入:nums = [1, 2, 3, 5]
  2. 输出:false
  • 数组不能分割成两个元素和相等的子集

2.1 递归解法

问题可以归结为:假设S表示所有给定数字的总和,则两个相等的⼦集的和必须等于S / 2

⼦问题:找到总和为S / 2的⼦集

状态:数字只能使用⼀次,要么属于S / 2,要么不属于S / 2

如果给出的集合具有以下特征,则一定无法分割出等和的子集

  • 集合中元素小于2个

  • 集合中元素之和为奇数

  • 集合中最大元素 > 元素之和的一半

否则,我们传入目标数(元素之和的一半),尝试用它减去符合条件的元素。如果可以遍历出一种方式,最终可将目标数减为0,则表示集合可分割出等和的子集

集合中元素只有两种可能性:

  • 当前元素 > 剩余目标数,则不可能放入当前子集中

  • 当前元素 < 剩余目标数,则有可能放入当前子集中

    • 放入当前子集中的前提,必须满足:目标数 - 其他元素 - 当前元素 = 0

代码实现:

  1. class Solution {
  2. fileprivate var _arr : [Int];
  3. init(arr : [Int]) {
  4. _arr = arr;
  5. }
  6. // 集合元素总和的一半,作为目标值
  7. fileprivate var _target : Int = 0;
  8. func canPartition() -> Bool {
  9. // 集合中元素个数小于2个
  10. if(_arr.count < 2){
  11. // 不可能分割
  12. return false;
  13. }
  14. // 元素总和
  15. var sum = 0;
  16. // 最大元素
  17. var maxItem = 0;
  18. // 遍历集合中所有元素
  19. for i in _arr {
  20. // 累加总和
  21. sum += i;
  22. // 计算出最大元素
  23. maxItem = max(i, maxItem);
  24. }
  25. // 判断总和为奇数
  26. if(sum % 2 == 1) {
  27. // 奇数,不可能分割出相等的子集
  28. return false;
  29. }
  30. // 目标的结果数,即:总和取半
  31. let target = sum / 2;
  32. // 判断最大元素是否超过目标数
  33. if(maxItem > target){
  34. // 超过,不可能分割出相等的子集
  35. return false;
  36. }
  37. // 是否可分割出等和的子集
  38. let reslut = getResult(index: 0, target: target);
  39. // 返回结果
  40. return reslut;
  41. }
  42. // 是否可分割出等和的子集
  43. func getResult(index : Int, target : Int) -> Bool {
  44. // 索引超出集合,表示所有元素都尝试过,无法分割出相等的子集
  45. if(index >= _arr.count){
  46. return false;
  47. }
  48. // 目标数减到0,可以分割出相等的子集
  49. if(target == 0){
  50. return true;
  51. }
  52. // 判断当前元素是否超过剩余的目标数
  53. if(_arr[index] > target){
  54. // 超过,跳过该元素,继续后面的元素
  55. return getResult(index: index + 1, target: target);
  56. }
  57. // 否则,尝试选择该元素,剩余目标数 = 当前目标数 - 当前元素
  58. let residue = target - _arr[index];
  59. // 继续后面的元素,并传入剩余目标数,表示选择该元素
  60. let reslut = getResult(index: index + 1, target: residue);
  61. // 判断选择该元素之后的结果
  62. if(!reslut){
  63. // 无法分割出相等的子集,证明该元素不符合加入子集的条件。跳过该元素,继续后面的元素
  64. return getResult(index: index + 1, target: target);
  65. }
  66. // 分割出相等的子集,直接返回
  67. return reslut;
  68. }
  69. }

2.2 缓存优化

定义二维数组,空间开辟大小为:(集合大小 +1) * (目标数 + 1)

  1. class Solution {
  2. fileprivate var _arr : [Int];
  3. init(arr : [Int]) {
  4. _arr = arr;
  5. }
  6. // 集合元素总和的一半,作为目标值
  7. fileprivate var _target : Int = 0;
  8. // 缓存
  9. fileprivate var _cache : [[Int]]?;
  10. func canPartition() -> Bool {
  11. // 集合中元素个数小于2个
  12. if(_arr.count < 2){
  13. // 不可能分割出相等的子集
  14. return false;
  15. }
  16. // 元素总和
  17. var sum = 0;
  18. // 最大元素
  19. var maxItem = 0;
  20. // 遍历集合中所有元素
  21. for i in _arr {
  22. // 累加总和
  23. sum += i;
  24. // 计算出最大元素
  25. maxItem = max(i, maxItem);
  26. }
  27. // 判断总和为奇数
  28. if(sum % 2 == 1) {
  29. // 奇数,不可能分割出相等的子集
  30. return false;
  31. }
  32. // 目标的结果数,即:总和取半
  33. let target = sum / 2;
  34. // 判断最大元素是否超过目标数
  35. if(maxItem > target){
  36. // 超过,不可能分割出相等的子集
  37. return false;
  38. }
  39. // 初始化缓存大小
  40. _cache = [[Int]](repeating: [Int](repeating: -1, count: target + 1), count: _arr.count + 1);
  41. // 是否可分割出等和的子集
  42. let reslut = getResult(index: 0, target: target);
  43. // 返回结果
  44. return reslut == 1;
  45. }
  46. // 是否可分割出等和的子集
  47. func getResult(index : Int, target : Int) -> Int {
  48. if(_cache![index][target] == -1){
  49. // 索引超出集合,表示所有元素都尝试过,无法分割出相等的子集
  50. if(index >= _arr.count){
  51. // 将结果写入缓存
  52. _cache![index][target] = 0;
  53. // 返回结果
  54. return _cache![index][target];
  55. }
  56. // 目标数减到0,可以分割出相等的子集
  57. if(target == 0){
  58. // 将结果写入缓存
  59. _cache![index][target] = 1;
  60. // 返回结果
  61. return _cache![index][target];
  62. }
  63. // 判断当前元素是否超过剩余的目标数
  64. if(_arr[index] > target){
  65. // 超过,跳过该元素,继续后面的元素
  66. // 将结果写入缓存
  67. _cache![index][target] = getResult(index: index + 1, target: target);
  68. // 返回结果
  69. return _cache![index][target];
  70. }
  71. // 否则,尝试选择该元素,剩余目标数 = 当前目标数 - 当前元素
  72. let residue = target - _arr[index];
  73. // 继续后面的元素,并传入剩余目标数,表示选择该元素
  74. let reslut = getResult(index: index + 1, target: residue);
  75. // 判断选择该元素之后的结果
  76. if(reslut == 0){
  77. // 无法分割出相等的子集,证明该元素不符合加入子集的条件。跳过该元素,继续后面的元素
  78. // 将结果写入缓存
  79. _cache![index][target] = getResult(index: index + 1, target: target);
  80. // 返回结果
  81. return _cache![index][target];
  82. }
  83. // 可以分割出相等的子集
  84. // 将结果写入缓存
  85. _cache![index][target] = reslut;
  86. }
  87. // 返回结果
  88. return _cache![index][target];
  89. }
  90. }

2.3 递推解法

使用自底向上的递推法,创建一张集合大小 * (目标数 + 1)的表格。先解决分解后的子问题,从而解决整体问题

  1. class Solution {
  2. fileprivate var _arr : [Int];
  3. init(arr : [Int]) {
  4. _arr = arr;
  5. }
  6. // 集合元素总和的一半,作为目标值
  7. fileprivate var _target : Int = 0;
  8. // 创建表格
  9. fileprivate var _list : [[Int]]?;
  10. func canPartition() -> Bool {
  11. // 集合中元素个数小于2个
  12. if(_arr.count < 2){
  13. // 不可能分割
  14. return false;
  15. }
  16. // 元素总和
  17. var sum = 0;
  18. // 最大元素
  19. var maxItem = 0;
  20. // 遍历集合中所有元素
  21. for i in _arr {
  22. // 累加总和
  23. sum += i;
  24. // 计算出最大元素
  25. maxItem = max(i, maxItem);
  26. }
  27. // 判断总和为奇数
  28. if(sum % 2 == 1) {
  29. // 奇数,不可能分割出相等的子集
  30. return false;
  31. }
  32. // 目标的结果数,即:总和取半
  33. let target = sum / 2;
  34. // 判断最大元素是否超过目标数
  35. if(maxItem > target){
  36. // 超过,不可能分割出相等的子集
  37. return false;
  38. }
  39. // 初始化表格大小
  40. _list = [[Int]](repeating: [Int](repeating: 0, count: target + 1), count: _arr.count);
  41. // 是否可分割出等和的子集
  42. let reslut = getResult(target: target);
  43. // 返回结果
  44. return reslut == 1;
  45. }
  46. // 是否可分割出等和的子集
  47. func getResult(target : Int) -> Int {
  48. // 设置初始列默认为1,当剩余目标数减到0,说明该元素可选取
  49. for i in 0..<_arr.count {
  50. _list![i][0] = 1;
  51. }
  52. // 第0行,只有初始元素可选取
  53. _list![0][_arr[0]] = 1;
  54. // 遍历元素
  55. for i in 1..<_arr.count {
  56. // 获取当前元素
  57. let item = _arr[i];
  58. // 遍历目标数
  59. for j in 1...target {
  60. // 判断当前元素是否超过剩余的目标数
  61. if(item > j){
  62. // 超过,证明该元素不符合加入子集的条件。跳过该元素,使用上一个元素的结果覆盖当前位置
  63. _list![i][j] = _list![i - 1][j];
  64. continue;
  65. }
  66. // 否则,尝试选择该元素,剩余目标数 = 当前目标数 - 当前元素
  67. let residue = j - item;
  68. // 如果选择当前元素,在剩余目标数的情况下,获得上一个元素的结果
  69. let reslut = _list![i - 1][residue];
  70. // 判断上一个元素是否可以将剩余目标数减空
  71. if(reslut == 0){
  72. // 无法减空,证明该元素不符合加入子集的条件。跳过该元素,使用上一个元素的结果覆盖当前位置
  73. _list![i][j] = _list![i - 1][j];
  74. continue;
  75. }
  76. // 可以减空,表示该元素可以选择,它是分割相等子集的必要元素
  77. // 将结果写入表格
  78. _list![i][j] = reslut;
  79. }
  80. }
  81. // 返回最终的计算结果
  82. return _list![_arr.count - 1][target];
  83. }
  84. }

2.4 空间优化

当前结果,只依赖于上一个元素的结果。我们可以只创建一行空间,让当前结果与上一个元素的结果共用,将空间复杂度优化为O(n)。为了不影响上一个元素的结果,目标数采用倒序遍历

  1. class Solution {
  2. fileprivate var _arr : [Int];
  3. init(arr : [Int]) {
  4. _arr = arr;
  5. }
  6. // 集合元素总和的一半,作为目标值
  7. fileprivate var _target : Int = 0;
  8. // 当前行,当前结果与上一个元素的结果共用
  9. fileprivate var _row : [Int]?;
  10. func canPartition() -> Bool {
  11. // 集合中元素个数小于2个
  12. if(_arr.count < 2){
  13. // 不可能分割
  14. return false;
  15. }
  16. // 元素总和
  17. var sum = 0;
  18. // 最大元素
  19. var maxItem = 0;
  20. // 遍历集合中所有元素
  21. for i in _arr {
  22. // 累加总和
  23. sum += i;
  24. // 计算出最大元素
  25. maxItem = max(i, maxItem);
  26. }
  27. // 判断总和为奇数
  28. if(sum % 2 == 1) {
  29. // 奇数,不可能分割出相等的子集
  30. return false;
  31. }
  32. // 目标的结果数,即:总和取半
  33. let target = sum / 2;
  34. // 判断最大元素是否超过目标数
  35. if(maxItem > target){
  36. // 超过,不可能分割出相等的子集
  37. return false;
  38. }
  39. // 初始化当前行
  40. _row = [Int](repeating: 0, count: target + 1);
  41. // 是否可分割出等和的子集
  42. let reslut = getResult(target: target);
  43. // 返回结果
  44. return reslut == 1;
  45. }
  46. // 是否可分割出等和的子集
  47. func getResult(target : Int) -> Int {
  48. // 当剩余目标数减到0,说明该元素可选取
  49. _row![0] = 1;
  50. // 默认只有初始元素可选取
  51. _row![_arr[0]] = 1;
  52. // 遍历元素
  53. for i in 1..<_arr.count {
  54. // 获取当前元素
  55. let item = _arr[i];
  56. // 为了不影响上一个元素的结果,倒序遍历目标数
  57. for j in (1...target).reversed() {
  58. // 判断当前元素是否超过剩余的目标数
  59. if(item > j){
  60. // 超过,证明该元素不符合加入子集的条件。跳过该元素,延用上一个元素的结果
  61. continue;
  62. }
  63. // 否则,尝试选择该元素,剩余目标数 = 当前目标数 - 当前元素
  64. let residue = j - item;
  65. // 如果选择当前元素,在剩余目标数的情况下,获得上一个元素的结果
  66. let reslut = _row![residue];
  67. // 判断上一个元素是否可以将剩余目标数减空
  68. if(reslut == 0){
  69. // 无法减空,证明该元素不符合加入子集的条件。跳过该元素,延用上一个元素的结果
  70. continue;
  71. }
  72. // 可以减空,表示该元素可以选择,它是分割相等子集的必要元素
  73. // 将结果覆盖
  74. _row![j] = reslut;
  75. }
  76. }
  77. // 返回最终的计算结果
  78. return _row![target];
  79. }
  80. }